Exemplo n.º 1
0
 /**
  * Convert a binary string into a hexadecimal string without cache-timing
  * leaks, returning uppercase letters (as per RFC 4648)
  *
  * @param string $bin_string (raw binary)
  * @return string
  */
 public static function encodeUpper(string $bin_string) : string
 {
     $hex = '';
     $len = Binary::safeStrlen($bin_string);
     for ($i = 0; $i < $len; ++$i) {
         $chunk = \unpack('C', Binary::safeSubstr($bin_string, $i, 2));
         $c = $chunk[1] & 0xf;
         $b = $chunk[1] >> 4;
         $hex .= pack('CC', 55 + $b + ($b - 10 >> 8 & ~6), 55 + $c + ($c - 10 >> 8 & ~6));
     }
     return $hex;
 }
Exemplo n.º 2
0
/**
 * Create a unique ID (e.g. for permalinks)
 *
 * @param int $length
 * @return string
 */
function uniqueId(int $length = 24) : string
{
    if ($length < 1) {
        return '';
    }
    $n = (int) ceil($length * 0.75);
    $str = \random_bytes($n);
    return Binary::safeSubstr(Base64UrlSafe::encode($str), 0, $length);
}
Exemplo n.º 3
0
 /**
  * Get/verify/parse a JSON response
  *
  * The _server_ is the one that signs the message.
  * We're just verifying the Ed25519 signature.
  *
  * @param string $url
  * @param SignaturePublicKey $publicKey
  * @param array $args
  * @param array $options
  * @return array
  * @throws \Exception
  */
 public static function postSignedJSON(string $url, SignaturePublicKey $publicKey, array $args = [], array $options = []) : array
 {
     $body = self::post($url, $args, $options);
     if (empty($body)) {
         throw new \Exception('Empty response from ' . $url);
     }
     if (self::$debug) {
         \var_dump($body);
     }
     $firstNewLine = \strpos($body, "\n");
     // There should be a newline immediately after the base64urlsafe-encoded signature
     if ($firstNewLine !== self::ENCODED_SIGNATURE_LENGTH) {
         throw new \Exception('Invalid Signature');
     }
     $sig = Base64UrlSafe::decode(Binary::safeSubstr($body, 0, self::ENCODED_SIGNATURE_LENGTH));
     $msg = Binary::safeSubstr($body, self::ENCODED_SIGNATURE_LENGTH + 1);
     if (!Asymmetric::verify($msg, $publicKey, $sig, true)) {
         throw new \Exception('Invalid Signature');
     }
     return \json_decode($msg, true);
 }
Exemplo n.º 4
0
 /**
  * decode from base64 into binary
  *
  * Base64 character set "./[A-Z][a-z][0-9]"
  *
  * @param string $src
  * @param bool $strictPadding
  * @return string|bool
  * @throws \RangeException
  */
 public static function decode(string $src, bool $strictPadding = false) : string
 {
     // Remove padding
     $srcLen = Binary::safeStrlen($src);
     if ($srcLen === 0) {
         return '';
     }
     if ($strictPadding) {
         if (($srcLen & 3) === 0) {
             if ($src[$srcLen - 1] === '=') {
                 $srcLen--;
                 if ($src[$srcLen - 1] === '=') {
                     $srcLen--;
                 }
             }
         }
         if (($srcLen & 3) === 1) {
             throw new \RangeException('Incorrect padding');
         }
         if ($src[$srcLen - 1] === '=') {
             throw new \RangeException('Incorrect padding');
         }
     } else {
         $src = \rtrim($src, '=');
         $srcLen = Binary::safeStrlen($src);
     }
     $err = 0;
     $dest = '';
     // Main loop (no padding):
     for ($i = 0; $i + 4 <= $srcLen; $i += 4) {
         $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 4));
         $c0 = static::decode6Bits($chunk[1]);
         $c1 = static::decode6Bits($chunk[2]);
         $c2 = static::decode6Bits($chunk[3]);
         $c3 = static::decode6Bits($chunk[4]);
         $dest .= \pack('CCC', ($c0 << 2 | $c1 >> 4) & 0xff, ($c1 << 4 | $c2 >> 2) & 0xff, ($c2 << 6 | $c3) & 0xff);
         $err |= ($c0 | $c1 | $c2 | $c3) >> 8;
     }
     // The last chunk, which may have padding:
     if ($i < $srcLen) {
         $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
         $c0 = static::decode6Bits($chunk[1]);
         if ($i + 2 < $srcLen) {
             $c1 = static::decode6Bits($chunk[2]);
             $c2 = static::decode6Bits($chunk[3]);
             $dest .= \pack('CC', ($c0 << 2 | $c1 >> 4) & 0xff, ($c1 << 4 | $c2 >> 2) & 0xff);
             $err |= ($c0 | $c1 | $c2) >> 8;
         } elseif ($i + 1 < $srcLen) {
             $c1 = static::decode6Bits($chunk[2]);
             $dest .= \pack('C', ($c0 << 2 | $c1 >> 4) & 0xff);
             $err |= ($c0 | $c1) >> 8;
         } elseif ($i < $srcLen && $strictPadding) {
             $err |= 1;
         }
     }
     if ($err !== 0) {
         throw new \RangeException('Base64::decode() only expects characters in the correct base64 alphabet');
     }
     return $dest;
 }
Exemplo n.º 5
0
 /**
  * Generate, store, and return the index and token
  *
  * @param string $lockTo What URI endpoint this is valid for
  * @return string[]
  */
 protected function generateToken(string $lockTo) : array
 {
     $index = Base64::encode(\random_bytes(18));
     $token = Base64::encode(\random_bytes(33));
     $this->session[$this->sessionIndex][$index] = ['created' => \intval(\date('YmdHis')), 'uri' => isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : $this->server['SCRIPT_NAME'], 'token' => $token];
     if (\preg_match('#/$#', $lockTo)) {
         $lockTo = Binary::safeSubstr($lockTo, 0, Binary::safeStrlen($lockTo) - 1);
     }
     $this->session[$this->sessionIndex][$index]['lockTo'] = $lockTo;
     $this->recycleTokens();
     return [$index, $token];
 }
Exemplo n.º 6
0
 /**
  * This propagates the new update through the network.
  */
 protected function notifyPeersOfNewUpdate()
 {
     $state = State::instance();
     if (IDE_HACKS) {
         $state->hail = new Hail(new Client());
     }
     $resp = [];
     $peers = \Airship\loadJSON(ROOT . '/config/channel_peers/' . $this->channel . '.json');
     foreach ($peers as $peer) {
         foreach ($peer['urls'] as $url) {
             $resp[] = $state->hail->getAsync($url, ['challenge' => Base64UrlSafe::encode(\random_bytes(21))]);
         }
     }
     foreach ($resp as $r) {
         $r->then(function (ResponseInterface $response) {
             $body = (string) $response->getBody();
             $context = \json_decode(Binary::safeSubstr($body, 89));
             $this->log('Peer notified of channel update', LogLevel::INFO, $context);
         });
     }
 }
Exemplo n.º 7
0
 /**
  * Base32 Decoding
  *
  * @param string $src
  * @param bool $upper
  * @return string
  */
 protected static function doEncode(string $src, bool $upper = false) : string
 {
     // We do this to reduce code duplication:
     $method = $upper ? 'encode5BitsUpper' : 'encode5Bits';
     $dest = '';
     $srcLen = Binary::safeStrlen($src);
     // Main loop (no padding):
     for ($i = 0; $i + 5 <= $srcLen; $i += 5) {
         $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 5));
         $b0 = $chunk[1];
         $b1 = $chunk[2];
         $b2 = $chunk[3];
         $b3 = $chunk[4];
         $b4 = $chunk[5];
         $dest .= static::$method($b0 >> 3 & 31) . static::$method(($b0 << 2 | $b1 >> 6) & 31) . static::$method($b1 >> 1 & 31) . static::$method(($b1 << 4 | $b2 >> 4) & 31) . static::$method(($b2 << 1 | $b3 >> 7) & 31) . static::$method($b3 >> 2 & 31) . static::$method(($b3 << 3 | $b4 >> 5) & 31) . static::$method($b4 & 31);
     }
     // The last chunk, which may have padding:
     if ($i < $srcLen) {
         $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
         $b0 = $chunk[1];
         if ($i + 3 < $srcLen) {
             $b1 = $chunk[2];
             $b2 = $chunk[3];
             $b3 = $chunk[4];
             $dest .= static::$method($b0 >> 3 & 31) . static::$method(($b0 << 2 | $b1 >> 6) & 31) . static::$method($b1 >> 1 & 31) . static::$method(($b1 << 4 | $b2 >> 4) & 31) . static::$method(($b2 << 1 | $b3 >> 7) & 31) . static::$method($b3 >> 2 & 31) . static::$method($b3 << 3 & 31) . '=';
         } elseif ($i + 2 < $srcLen) {
             $b1 = $chunk[2];
             $b2 = $chunk[3];
             $dest .= static::$method($b0 >> 3 & 31) . static::$method(($b0 << 2 | $b1 >> 6) & 31) . static::$method($b1 >> 1 & 31) . static::$method(($b1 << 4 | $b2 >> 4) & 31) . static::$method($b2 << 1 & 31) . '===';
         } elseif ($i + 1 < $srcLen) {
             $b1 = $chunk[2];
             $dest .= static::$method($b0 >> 3 & 31) . static::$method(($b0 << 2 | $b1 >> 6) & 31) . static::$method($b1 >> 1 & 31) . static::$method($b1 << 4 & 31) . '====';
         } else {
             $dest .= static::$method($b0 >> 3 & 31) . static::$method($b0 << 2 & 31) . '======';
         }
     }
     return $dest;
 }
Exemplo n.º 8
0
/**
 * Renders ReStructuredText
 *
 * @param string $string
 * @param bool $return
 * @output HTML
 * @return string
 */
function render_rst(string $string = '', bool $return = false) : string
{
    static $rst = null;
    if (empty($rst)) {
        $rst = (new RSTParser())->setIncludePolicy(false);
    }
    $checksum = CryptoUtil::hash('ReStructuredText' . $string);
    $h1 = Binary::safeSubstr($checksum, 0, 2);
    $h2 = Binary::safeSubstr($checksum, 2, 2);
    $hash = Binary::safeSubstr($checksum, 4);
    $cacheDir = \implode('/', [ROOT, 'tmp', 'cache', 'rst', $h1, $h2]);
    if (\file_exists($cacheDir . '/' . $hash . '.txt')) {
        $output = \file_get_contents($cacheDir . '/' . $hash . '.txt');
    } else {
        if (!\is_dir($cacheDir)) {
            \mkdir($cacheDir, 0775, true);
        }
        $output = (string) $rst->parse($string);
        // Cache for later
        \file_put_contents($cacheDir . '/' . $hash . '.txt', $output);
        \chmod($cacheDir . '/' . $hash . '.txt', 0664);
    }
    if ($return) {
        return $output;
    }
    echo $output;
    return '';
}
Exemplo n.º 9
0
 /**
  * Parse a signed JSON response
  *
  * @param Response $response
  * @param SignaturePublicKey $publicKey
  * @return mixed
  * @throws SignatureFailed
  * @throws TransferException
  */
 public function parseSignedJSON(Response $response, SignaturePublicKey $publicKey)
 {
     $code = $response->getStatusCode();
     if ($code >= 200 && $code < 300) {
         $body = (string) $response->getBody();
         $firstNewLine = \strpos($body, "\n");
         // There should be a newline immediately after the base64urlsafe-encoded signature
         if ($firstNewLine !== self::ENCODED_SIGNATURE_LENGTH) {
             throw new SignatureFailed(\sprintf("First newline found at position %s, expected %d.\n%s", \print_r($firstNewLine, true), \print_r(self::ENCODED_SIGNATURE_LENGTH, true), Base64::encode($body)));
         }
         $sig = Base64UrlSafe::decode(Binary::safeSubstr($body, 0, 88));
         $msg = Binary::safeSubstr($body, 89);
         if (!Asymmetric::verify($msg, $publicKey, $sig, true)) {
             throw new SignatureFailed();
         }
         return \Airship\parseJSON($msg, true);
     }
     throw new TransferException();
 }
Exemplo n.º 10
0
 /**
  * Replace the existing long-term authentication cookie
  *
  * @param string $token
  * @param int $userId
  * @return mixed
  */
 public function rotateToken(string $token, int $userId = 0)
 {
     try {
         $decoded = Base64::decode($token);
     } catch (\RangeException $ex) {
         return false;
     }
     if ($decoded === false) {
         return false;
     } elseif (Binary::safeStrlen($decoded) !== self::LONG_TERM_AUTH_BYTES) {
         return false;
     }
     $sel = Binary::safeSubstr($decoded, 0, self::SELECTOR_BYTES);
     \Sodium\memzero($decoded);
     // Delete the old token
     $this->db->delete($this->tableConfig['table']['longterm'], [$this->tableConfig['fields']['longterm']['selector'] => Base64::encode($sel)]);
     // Let's get a new token
     return $this->createAuthToken($userId);
 }