Exemplo n.º 1
0
 function testPBES2Encrypt()
 {
     $cek = Util::base64url_decode('bxsZNEIdFE5csDjwQdBScKGDJDfK7LmsgReZwsMw_bY');
     $password = '******';
     $keys = $this->getKeySet($password);
     $stub = $this->getMockBuilder('SimpleJWT\\Crypt\\PBES2')->setMethods(array('generateSaltInput'))->setConstructorArgs(array('PBES2-HS256+A128KW'))->getMock();
     $stub->method('generateSaltInput')->willReturn(Util::base64url_decode('2WCTcJZ1Rvd_CJuJripQ1w'));
     $stub->setIterations(4096);
     $headers = array('alg' => 'PBES2-HS256+A128KW');
     $encrypted_key = $stub->encryptKey($cek, $keys, $headers);
     $this->assertEquals(4096, $headers['p2c']);
     $this->assertEquals('2WCTcJZ1Rvd_CJuJripQ1w', $headers['p2s']);
     $this->assertEquals('TrqXOwuNUfDV9VPTNbyGvEJ9JMjefAVn-TR1uIxR9p6hsRQh9Tk7BA', $encrypted_key);
 }
Exemplo n.º 2
0
 /**
  * Decodes a serialised JWT.
  *
  * @param string $token the serialised JWT
  * @param SimpleJWT\Keys\KeySet $keys the key set containing the key to verify the
  * JWT's signature
  * @param string $expected_alg the expected value of the `alg` parameter, which
  * should be agreed between the parties out-of-band
  * @param string $kid the ID of the key to use to verify the signature. If null, this
  * is automatically retrieved
  * @param bool|array $skip_validation an array of headers or claims that
  * should be ignored as part of the validation process (e.g. if expired tokens
  * are to be accepted), or false if all validation
  * is to be performed.
  * @param string $format the JWT serialisation format
  * @return JWT the decoded JWT
  * @throws InvalidTokenException if the token is invalid for any reason
  */
 public static function decode($token, $keys, $expected_alg, $kid = null, $skip_validation = array(), $format = self::COMPACT_FORMAT)
 {
     if ($skip_validation === false) {
         $skip_validation = array();
     }
     switch ($format) {
         case self::COMPACT_FORMAT:
             $parts = explode('.', $token, 3);
             if (count($parts) != 3) {
                 throw new InvalidTokenException('Cannot decode compact serialisation', InvalidTokenException::TOKEN_PARSE_ERROR);
             }
             list($protected, $payload, $signature) = $parts;
             break;
         case self::JSON_FORMAT:
             $obj = json_decode($token, true);
             if ($obj == null) {
                 throw new InvalidTokenException('Cannot decode JSON', InvalidTokenException::TOKEN_PARSE_ERROR);
             }
             $payload = $obj['payload'];
             if (isset($obj['signatures'])) {
                 foreach ($obj['signatures'] as $signature_obj) {
                     if (isset($signature_obj['header']['kid'])) {
                         $target_kid = $signature_obj['header']['kid'];
                         if ($target_kid == $kid || $keys->getById($target_kid) != null) {
                             $unprotected = $signature_obj['header'];
                             $protected = $signature_obj['protected'];
                             $signature = $signature_obj['signature'];
                             break;
                         }
                     }
                     throw new InvalidTokenException('Cannot find verifiable signature', InvalidTokenException::TOKEN_PARSE_ERROR);
                 }
             } else {
                 $unprotected = $obj['header'];
                 $protected = $obj['protected'];
                 $signature = $obj['signature'];
             }
             break;
         default:
             throw new \InvalidArgumentException('Incorrect format');
     }
     $headers = json_decode(Util::base64url_decode($protected), true);
     if ($headers == null) {
         throw new InvalidTokenException('Cannot decode header', InvalidTokenException::TOKEN_PARSE_ERROR);
     }
     // Process crit
     if (isset($headers['crit'])) {
         foreach ($headers['crit'] as $critical) {
             if (!in_array($critical, array('nbf', 'exp', 'alg', 'kid'))) {
                 throw new InvalidTokenException('Critical header not supported: ' . $critical, InvalidTokenException::UNSUPPORTED_ERROR);
             }
         }
     }
     if (isset($unprotected)) {
         $headers = array_merge($headers, $unprotected);
     }
     $claims = json_decode(Util::base64url_decode($payload), true);
     if ($claims == null) {
         throw new InvalidTokenException('Cannot decode claims', InvalidTokenException::TOKEN_PARSE_ERROR);
     }
     // Check signatures
     if ($headers['alg'] != $expected_alg) {
         throw new InvalidTokenException('Unexpected algorithm', InvalidTokenException::SIGNATURE_VERIFICATION_ERROR);
     }
     $signer = AlgorithmFactory::create($expected_alg);
     $signing_input = $protected . '.' . $payload;
     try {
         if (isset($headers['kid'])) {
             $kid = $headers['kid'];
         }
         $result = $signer->verify($signature, $signing_input, $keys, $kid);
     } catch (KeyException $e) {
         throw new InvalidTokenException($e->getMessage(), InvalidTokenException::SIGNATURE_VERIFICATION_ERROR, $e);
     } catch (CryptException $e) {
         throw new InvalidTokenException($e->getMessage(), InvalidTokenException::SIGNATURE_VERIFICATION_ERROR, $e);
     }
     if (!$result) {
         throw new InvalidTokenException('Incorrect signature', InvalidTokenException::SIGNATURE_VERIFICATION_ERROR);
     }
     // Check time, etc
     $time = time();
     if (isset($claims['nbf']) && !in_array('nbf', $skip_validation)) {
         if ($time < $claims['nbf'] - self::$TIME_ALLOWANCE) {
             throw new InvalidTokenException('Too early due to nbf claim', InvalidTokenException::TOO_EARLY_ERROR, null, $claims['nbf']);
         }
     }
     if (isset($claims['exp']) && !in_array('exp', $skip_validation)) {
         if ($time > $claims['exp'] + self::$TIME_ALLOWANCE) {
             throw new InvalidTokenException('Too late due to exp claim', InvalidTokenException::TOO_LATE_ERROR, null, $claims['exp']);
         }
     }
     return new JWT($headers, $claims);
 }
Exemplo n.º 3
0
 /**
  * Returns the symmetric key in binary representation
  *
  * @return string the key
  */
 public function toBinary()
 {
     return Util::base64url_decode($this->data['k']);
 }
Exemplo n.º 4
0
 public function decryptAndVerify($ciphertext, $tag, $cek, $additional, $iv)
 {
     $params = self::$alg_params[$this->getAlg()];
     if (strlen($cek) != $this->getCEKSize() / 8) {
         throw new CryptException('Incorrect key length');
     }
     $iv = Util::base64url_decode($iv);
     if (strlen($iv) != $this->getIVSize() / 8) {
         throw new CryptException('Incorrect IV length');
     }
     list($mac_key, $enc_key) = str_split($cek, (int) (strlen($cek) / 2));
     $al = Util::packInt64(strlen($additional) * 8);
     $e = Util::base64url_decode($ciphertext);
     $m = hash_hmac($params['hash'], $additional . $iv . $e . $al, $mac_key, true);
     $t = substr($m, 0, $params['tag']);
     if (!Util::secure_compare(Util::base64url_decode($tag), $t)) {
         throw new CryptException('Authentication tag does not match');
     }
     $plaintext = openssl_decrypt($e, $params['cipher'], $enc_key, OPENSSL_RAW_DATA, $iv);
     return $plaintext;
 }
Exemplo n.º 5
0
 public function toPEM()
 {
     if ($this->isPublic()) {
         $der = ASN1::encodeDER(ASN1::SEQUENCE, ASN1::encodeDER(ASN1::SEQUENCE, ASN1::encodeDER(ASN1::OID, ASN1::encodeOID(self::OID)) . ASN1::encodeDER(ASN1::NULL_TYPE), false) . ASN1::encodeDER(ASN1::BIT_STRING, chr(0x0) . ASN1::encodeDER(ASN1::SEQUENCE, ASN1::encodeDER(ASN1::INTEGER_TYPE, Util::base64url_decode($this->data['n'])) . ASN1::encodeDER(ASN1::INTEGER_TYPE, Util::base64url_decode($this->data['e'])), false)), false);
         return wordwrap("-----BEGIN PUBLIC KEY-----\n" . base64_encode($der) . "\n-----END PUBLIC KEY-----\n", 64, "\n", true);
     } else {
         $der = ASN1::encodeDER(ASN1::SEQUENCE, ASN1::encodeDER(ASN1::INTEGER_TYPE, chr(0)) . ASN1::encodeDER(ASN1::INTEGER_TYPE, Util::base64url_decode($this->data['n'])) . ASN1::encodeDER(ASN1::INTEGER_TYPE, Util::base64url_decode($this->data['e'])) . ASN1::encodeDER(ASN1::INTEGER_TYPE, Util::base64url_decode($this->data['d'])) . ASN1::encodeDER(ASN1::INTEGER_TYPE, Util::base64url_decode($this->data['p'])) . ASN1::encodeDER(ASN1::INTEGER_TYPE, Util::base64url_decode($this->data['q'])) . ASN1::encodeDER(ASN1::INTEGER_TYPE, Util::base64url_decode($this->data['dp'])) . ASN1::encodeDER(ASN1::INTEGER_TYPE, Util::base64url_decode($this->data['dq'])) . ASN1::encodeDER(ASN1::INTEGER_TYPE, Util::base64url_decode($this->data['qi'])), false);
         return wordwrap("-----BEGIN RSA PRIVATE KEY-----\n" . base64_encode($der) . "\n-----END RSA PRIVATE KEY-----\n", 64, "\n", true);
     }
 }
Exemplo n.º 6
0
 /**
  * Decrypts a JWE.
  *
  * @param string $token the serialised JWE
  * @param SimpleJWT\Keys\KeySet $keys the key set containing the key to verify the
  * JWT's signature
  * @param string $expected_alg the expected value of the `alg` parameter, which
  * should be agreed between the parties out-of-band
  * @param string $format the JWE serialisation format
  * @return JWE the decrypted JWE
  * @throws InvalidTokenException if the token is invalid for any reason
  */
 public static function decrypt($token, $keys, $expected_alg, $format = self::COMPACT_FORMAT)
 {
     switch ($format) {
         case self::COMPACT_FORMAT:
             $parts = explode('.', $token, 5);
             if (count($parts) != 5) {
                 throw new InvalidTokenException('Cannot decode compact serialisation', InvalidTokenException::TOKEN_PARSE_ERROR);
             }
             list($protected, $encrypted_key, $iv, $ciphertext, $tag) = $parts;
             break;
         case self::JSON_FORMAT:
             $obj = json_decode($token, true);
             if ($obj == null) {
                 throw new InvalidTokenException('Cannot decode JSON', InvalidTokenException::TOKEN_PARSE_ERROR);
             }
             $protected = $obj['protected'];
             $unprotected = $obj['unprotected'];
             $iv = $obj['iv'];
             $ciphertext = $obj['ciphertext'];
             $tag = $obj['tag'];
             if (isset($obj['recipients'])) {
                 foreach ($obj['recipients'] as $recipient) {
                     if (isset($recipient_obj['header']['kid'])) {
                         $target_kid = $recipient_obj['header']['kid'];
                         if ($keys->getById($target_kid) != null) {
                             $unprotected = isset($unprotected) ? array_merge($unprotected, $recipient_obj['header']) : $recipient_obj['header'];
                             $encrypted_key = $recipient_obj['encrypted_key'];
                             break;
                         }
                     }
                     throw new InvalidTokenException('Cannot find verifiable signature', InvalidTokenException::TOKEN_PARSE_ERROR);
                 }
             } else {
                 $unprotected = isset($unprotected) ? array_merge($unprotected, $obj['header']) : $obj['header'];
                 $encrypted_key = $obj['encrypted_key'];
             }
             break;
         default:
             throw new \InvalidArgumentException('Incorrect format');
     }
     $headers = json_decode(Util::base64url_decode($protected), true);
     if ($headers == null) {
         throw new InvalidTokenException('Cannot decode header', InvalidTokenException::TOKEN_PARSE_ERROR);
     }
     if (isset($unprotected)) {
         $headers = array_merge($headers, $unprotected);
     }
     // Process crit
     if (isset($headers['crit'])) {
         foreach ($headers['crit'] as $critical) {
             if (!in_array($critical, array('alg', 'enc', 'kid', 'zip'))) {
                 throw new InvalidTokenException('Critical header not supported: ' . $critical, InvalidTokenException::UNSUPPORTED_ERROR);
             }
         }
     }
     if (!isset($headers['alg'])) {
         throw new InvalidTokenException('alg parameter missing', InvalidTokenException::TOKEN_PARSE_ERROR);
     }
     if (!isset($headers['enc'])) {
         throw new InvalidTokenException('enc parameter missing', InvalidTokenException::TOKEN_PARSE_ERROR);
     }
     if ($headers['alg'] != $expected_alg) {
         throw new InvalidTokenException('Unexpected algorithm', InvalidTokenException::DECRYPTION_ERROR);
     }
     $key_enc = AlgorithmFactory::create($headers['alg']);
     $content_enc = AlgorithmFactory::create($headers['enc']);
     if ($key_enc instanceof KeyDerivationAlgorithm) {
         try {
             $agreed_key = $key_enc->deriveKey($keys, $headers, $kid);
         } catch (KeyException $e) {
             throw new InvalidTokenException($e->getMessage(), InvalidTokenException::DECRYPTION_ERROR, $e);
         } catch (CryptException $e) {
             throw new InvalidTokenException($e->getMessage(), InvalidTokenException::DECRYPTION_ERROR, $e);
         }
         if ($key_enc instanceof KeyEncryptionAlgorithm) {
             // Key agreement with wrapping
             $keys->add(new SymmetricKey(array('kty' => SymmetricKey::KTY, 'alg' => $this->headers['alg'], 'k' => Util::base64url_encode($agreed_key), 'kid' => '#key-agreement-with-wrapping'), 'php'));
             $kid = '#key-agreement-with-wrapping';
         } else {
             // Direct key agreement or direct encryption
             $cek = $agreed_key;
             if ($encrypted_key != '') {
                 throw new InvalidTokenException('encrypted key should be empty', InvalidTokenException::TOKEN_PARSE_ERROR);
             }
         }
     }
     if (!isset($cek) && $key_enc instanceof KeyEncryptionAlgorithm) {
         try {
             if (!isset($kid)) {
                 $kid = isset($headers['kid']) ? $headers['kid'] : null;
             }
             $cek = $key_enc->decryptKey($encrypted_key, $keys, $headers, $kid);
         } catch (KeyException $e) {
             throw new InvalidTokenException($e->getMessage(), InvalidTokenException::DECRYPTION_ERROR, $e);
         } catch (CryptException $e) {
             throw new InvalidTokenException($e->getMessage(), InvalidTokenException::DECRYPTION_ERROR, $e);
         }
     }
     if (!$cek) {
         throw new InvalidTokenException('alg parameter incorrect', InvalidTokenException::TOKEN_PARSE_ERROR);
     }
     try {
         $plaintext = $content_enc->decryptAndVerify($ciphertext, $tag, $cek, $protected, $iv);
         if (isset($headers['zip'])) {
             switch ($headers['zip']) {
                 case 'DEF':
                     $plaintext = gzinflate($plaintext);
                     break;
                 default:
                     throw new InvalidTokenException('Unsupported zip header:' . $headers['zip'], InvalidTokenException::UNSUPPORTED_ERROR);
             }
         }
     } catch (CryptException $e) {
         throw new InvalidTokenException($e->getMessage(), InvalidTokenException::DECRYPTION_ERROR, $e);
     }
     return new JWE($headers, $plaintext);
 }
Exemplo n.º 7
0
 private function getKeySetFromPassword($password, $headers)
 {
     $salt = $headers['alg'] . "" . Util::base64url_decode($headers['p2s']);
     $hash = hash_pbkdf2($this->hash_alg, $password, $salt, $headers['p2c'], $this->getAESKWKeySize() / 8, true);
     $keys = new KeySet();
     $keys->add(new SymmetricKey($hash, 'bin'));
     return $keys;
 }
Exemplo n.º 8
0
 public function verify($signature, $data, $keys, $kid = null)
 {
     $key = $this->selectKey($keys, $kid, array(Key::PUBLIC_PROPERTY => true, '~use' => 'sig'));
     if ($key == null) {
         throw new KeyException('Key not found or is invalid');
     }
     $binary = Util::base64url_decode($signature);
     if ($key->getKeyType() == \SimpleJWT\Keys\ECKey::KTY) {
         // For ECDSA signatures, OpenSSL expects a ASN.1 DER SEQUENCE
         list($r, $s) = str_split($binary, (int) (strlen($binary) / 2));
         // Trim leading zeros
         $r = ltrim($r, "");
         $s = ltrim($s, "");
         // Convert r and s from unsigned big-endian integers to signed two's complement
         if (ord($r[0]) > 0x7f) {
             $r = "" . $r;
         }
         if (ord($s[0]) > 0x7f) {
             $s = "" . $s;
         }
         $binary = ASN1::encodeDER(ASN1::SEQUENCE, ASN1::encodeDER(ASN1::INTEGER_TYPE, $r) . ASN1::encodeDER(ASN1::INTEGER_TYPE, $s), false);
     }
     $result = openssl_verify($data, $binary, $key->toPEM(), 'SHA' . $this->size);
     switch ($result) {
         case 1:
             return true;
             break;
         case 0:
             return false;
             break;
         default:
             $messages = array();
             while ($message = openssl_error_string()) {
                 $messages[] = $message;
             }
             throw new CryptException('Cannot verify signature: ' . implode("\n", $messages));
             return false;
             break;
     }
 }
Exemplo n.º 9
0
 public function toPEM()
 {
     $oid = $this->getOID($this->data['crv']);
     if ($oid == null) {
         throw new KeyException('Unrecognised EC curve');
     }
     if ($this->isPublic()) {
         $der = ASN1::encodeDER(ASN1::SEQUENCE, ASN1::encodeDER(ASN1::SEQUENCE, ASN1::encodeDER(ASN1::OID, ASN1::encodeOID(self::EC_OID)) . ASN1::encodeDER(ASN1::OID, ASN1::encodeOID($oid)), false) . ASN1::encodeDER(ASN1::BIT_STRING, chr(0x0) . chr(0x4) . Util::base64url_decode($this->data['x']) . Util::base64url_decode($this->data['y'])), false);
         return wordwrap("-----BEGIN PUBLIC KEY-----\n" . base64_encode($der) . "\n-----END PUBLIC KEY-----\n", 64, "\n", true);
     } else {
         $der = ASN1::encodeDER(ASN1::SEQUENCE, ASN1::encodeDER(ASN1::INTEGER_TYPE, chr(0x1)) . ASN1::encodeDER(ASN1::OCTET_STRING, Util::base64url_decode($this->data['d'])) . ASN1::encodeDER(0x0, ASN1::encodeDER(ASN1::OID, ASN1::encodeOID($oid)), false, ASN1::CONTEXT_CLASS) . ASN1::encodeDER(0x1, ASN1::encodeDER(ASN1::BIT_STRING, chr(0x0) . chr(0x4) . Util::base64url_decode($this->data['x']) . Util::base64url_decode($this->data['y'])), false, ASN1::CONTEXT_CLASS), false);
         return wordwrap("-----BEGIN EC PRIVATE KEY-----\n" . base64_encode($der) . "\n-----END EC PRIVATE KEY-----\n", 64, "\n", true);
     }
 }
Exemplo n.º 10
0
 public function decryptKey($encrypted_key, $keys, $headers, $kid = null)
 {
     $key = $this->selectKey($keys, $kid, array(Key::PUBLIC_PROPERTY => false));
     if ($key == null || $key->isPublic()) {
         throw new CryptException('Key not found or is invalid');
     }
     $params = self::$alg_params[$this->getAlg()];
     $cek = '';
     if (!openssl_private_decrypt(Util::base64url_decode($encrypted_key), $cek, $key->toPEM(), $params['padding'])) {
         $messages = array();
         while ($message = openssl_error_string()) {
             $messages[] = $message;
         }
         throw new CryptException('Cannot decrypt key: ' . implode("\n", $messages));
     }
     if (isset($params['oaep'])) {
         // $key->getSize() ignores the first octet when calculating the key size,
         // therefore we need to add it back in
         $cek = $this->oaep_decode($cek, 1 + $key->getSize() / 8, $params['oaep']);
     }
     return $cek;
 }