/**
  * @covers Base64UrlSafe::encode()
  * @covers Base64UrlSafe::decode()
  */
 public function testRandom()
 {
     for ($i = 1; $i < 32; ++$i) {
         for ($j = 0; $j < 50; ++$j) {
             $random = \random_bytes($i);
             $enc = Base64UrlSafe::encode($random);
             $this->assertSame($random, Base64UrlSafe::decode($enc));
             $this->assertSame(\strtr(\base64_encode($random), '+/', '-_'), $enc);
         }
     }
 }
Exemple #2
0
 /**
  * Peer constructor.
  * @param array $config
  */
 public function __construct(array $config = [])
 {
     $this->name = $config['name'];
     $this->publicKey = new SignaturePublicKey(Base64UrlSafe::decode($config['public_key']));
     $this->urls = $config['urls'];
     foreach ($this->urls as $url) {
         if (\Airship\isOnionUrl($url)) {
             $this->onion = true;
             break;
         }
     }
 }
Exemple #3
0
 /**
  * Get the configuration for this version of halite
  *
  * @param string $stored   A stored password hash
  * @return SymmetricConfig
  * @throws InvalidMessage
  */
 protected static function getConfig(string $stored) : SymmetricConfig
 {
     $length = Util::safeStrlen($stored);
     // This doesn't even have a header.
     if ($length < 8) {
         throw new InvalidMessage('Encrypted password hash is way too short.');
     }
     if (\hash_equals(Util::safeSubstr($stored, 0, 5), Halite::VERSION_PREFIX)) {
         return SymmetricConfig::getConfig(Base64UrlSafe::decode($stored), 'encrypt');
     }
     $v = \Sodium\hex2bin(Util::safeSubstr($stored, 0, 8));
     return SymmetricConfig::getConfig($v, 'encrypt');
 }
 /**
  * Coerce a string into base64 format.
  *
  * @param string $hash
  * @param string $algo
  * @return string
  * @throws \Exception
  */
 protected function coerceBase64(string $hash, string $algo = 'sha256') : string
 {
     switch ($algo) {
         case 'sha256':
             $limits = ['raw' => 32, 'hex' => 64, 'pad_min' => 40, 'pad_max' => 44];
             break;
         default:
             throw new \Exception('Browsers currently only support sha256 public key pins.');
     }
     $len = Binary::safeStrlen($hash);
     if ($len === $limits['hex']) {
         $hash = Base64::encode(Hex::decode($hash));
     } elseif ($len === $limits['raw']) {
         $hash = Base64::encode($hash);
     } elseif ($len > $limits['pad_min'] && $len < $limits['pad_max']) {
         // Padding was stripped!
         $hash .= \str_repeat('=', $len % 4);
         // Base64UrlSsafe encoded.
         if (\strpos($hash, '_') !== false || \strpos($hash, '-') !== false) {
             $hash = Base64UrlSafe::decode($hash);
         } else {
             $hash = Base64::decode($hash);
         }
         $hash = Base64::encode($hash);
     }
     return $hash;
 }
Exemple #5
0
 /**
  * Validate a request based on $_SESSION and $_POST data
  *
  * @return bool
  */
 public function check() : bool
 {
     if (!isset($_SESSION[$this->sessionIndex])) {
         // We don't even have a session array initialized
         $_SESSION[$this->sessionIndex] = [];
         return false;
     }
     if (!isset($_POST[self::FORM_TOKEN]) || !\is_string($_POST[self::FORM_TOKEN])) {
         return false;
     }
     if (\strpos($_POST[self::FORM_TOKEN], ':') === false) {
         return false;
     }
     // Let's pull the POST data
     list($index, $token) = \explode(':', $_POST[self::FORM_TOKEN]);
     if (empty($index) || empty($token)) {
         return false;
     }
     if (!isset($_SESSION[$this->sessionIndex][$index])) {
         // CSRF Token not found
         return false;
     }
     // Grab the value stored at $index
     $stored = $_SESSION[$this->sessionIndex][$index];
     // We don't need this anymore
     unset($_SESSION[$this->sessionIndex][$index]);
     // Which form action="" is this token locked to?
     $lockTo = $_SERVER['REQUEST_URI'];
     if (\preg_match('#/$#', $lockTo)) {
         // Trailing slashes are to be ignored
         $lockTo = substr($lockTo, 0, strlen($lockTo) - 1);
     }
     if (!empty($stored['lockto'])) {
         if (!\hash_equals($lockTo, $stored['lockto'])) {
             // Form target did not match the request this token is locked to!
             return false;
         }
     }
     // This is the expected token value
     if ($this->hmacIP === false) {
         // We just stored it wholesale
         $expected = $stored['token'];
     } else {
         // We mixed in the client IP address to generate the output
         $expected = Base64UrlSafe::encode(CryptoUtil::raw_keyed_hash($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1', Base64UrlSafe::decode($stored['token'])));
     }
     return \hash_equals($token, $expected);
 }
Exemple #6
0
 /**
  * Interpret the TreeUpdate objects from the API response. OR verify the signature
  * of the "no updates" message to prevent a DoS.
  *
  * Dear future security auditors: This is important.
  *
  * @param Channel $chan
  * @param array $response
  * @return TreeUpdate[]
  * @throws ChannelSignatureFailed
  * @throws CouldNotUpdate
  */
 protected function parseTreeUpdateResponse(Channel $chan, array $response) : array
 {
     if (!empty($response['no_updates'])) {
         // The "no updates" message should be authenticated.
         $signatureVerified = AsymmetricCrypto::verify($response['no_updates'], $chan->getPublicKey(), Base64UrlSafe::decode($response['signature']), true);
         if (!$signatureVerified) {
             throw new ChannelSignatureFailed();
         }
         $datetime = new \DateTime($response['no_updates']);
         // One day ago:
         $stale = (new \DateTime('now'))->sub(new \DateInterval('P01D'));
         if ($datetime < $stale) {
             throw new CouldNotUpdate(\__('Stale response.'));
         }
         // We got nothing to do:
         return [];
     }
     // We were given updates. Let's validate them!
     $TreeUpdateArray = [];
     foreach ($response['updates'] as $update) {
         $data = Base64UrlSafe::decode($update['data']);
         $sig = Base64UrlSafe::decode($update['signature']);
         $signatureVerified = AsymmetricCrypto::verify($data, $chan->getPublicKey(), $sig, true);
         if (!$signatureVerified) {
             // Invalid signature
             throw new ChannelSignatureFailed();
         }
         // Now that we know it was signed by the channel, time to update
         $TreeUpdateArray[] = new TreeUpdate($chan, \json_decode($data, true));
     }
     // Sort by ID
     \uasort($TreeUpdateArray, function (TreeUpdate $a, TreeUpdate $b) : int {
         return (int) ($a->getChannelId() <=> $b->getChannelId());
     });
     return $TreeUpdateArray;
 }