public function testDecrypt() { $plaintext = 'Live long and prosper.'; $token = 'eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ.AxY8DCtDaGlsbGljb3RoZQ.KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.U0m_YmjN04DJvceFICbCVQ'; $private_set = $this->getPrivateKeySet(); $test_jwe = JWE::decrypt($token, $private_set, 'A128KW'); $this->assertEquals($plaintext, $test_jwe->getPlaintext()); }
/** * Decrypts and verifies a nested JWT. * * If the supplied token is a JWT, this function calls {@link getObject()} * to decode the JWT. * * If the supplied token is a JWE, the JWE is firstly decrypted, then the underlying * plaintext is treated as a JWT, and further decoded. * * @param SimpleJWT\Keys\KeySet $keys the key set containing the decryption * and verification keys * @param string $expected_jwe_alg the expected value of the `alg` parameter for the * JWE, which should be agreed between the parties out-of-band * @param string $expected_jwt_alg the expected value of the `alg` parameter for the * underlying JWT, which should be agreed between the parties out-of-band * @param string $jwe_kid the ID of the key to use for decryption. If null, this * is automatically retrieved * @param string $jwt_kid the ID of the key to use for verification. If null, this * is automatically retrieved * @return JWT the decoded JWT * @throws InvalidTokenException if the token is invalid for any reason */ function getJWTObject($keys, $expected_jwe_alg, $expected_jwt_alg, $jwe_kid = null, $jwt_kid = null) { switch ($this->type) { case 'JWT': return $this->getObject($keys, $expected_jwt_alg, $jwt_kid); case 'JWE': $jwe = JWE::decrypt($this->data, $keys, $expected_jwe_alg, $jwe_kid, $this->format); if ($jwe->getHeader('cty') != 'JWT') { throw new InvalidTokenException('Not a nested JWT', InvalidTokenException::TOKEN_PARSE_ERROR); } return JWT::decode($jwe->getPlaintext(), $keys, $expected_jwt_alg, $jwt_kid); } }
/** * Builds the JOSE response. This will return one of the following: * * - A JSON encoded string, if {@link $signed_response_alg} and * {@link $encrypted_response_alg} are both null * - A signed JWT (JWS), if {@link $signed_response_alg} is set * - A JWE containing a nested JWT, if both {@link $signed_response_alg} * and {@link $encrypted_response_alg} are set * * @param SimpleJWT\Keys\KeySet $set the key set used to sign and/or * encrypt the token. If set to null, the default set of keys * configured for the client and the server are loaded * @return string the response body */ function buildJOSE($set = null) { $rand = new Random(); $typ = $this->getType(); if ($typ == 'json') { return json_encode($this->container); } if ($set == null) { $builder = new KeySetBuilder($client); $set = $builder->addClientSecret()->addClientPublicKeys()->addServerPrivateKeys()->toKeySet(); } $headers = array_merge($this->headers, array('alg' => $this->signed_response_alg)); $claims = array_merge($this->container, array('iss' => $this->issuer, 'aud' => $this->client->getStoreID(), 'jti' => $rand->id())); $jwt = new JWT($headers, $claims); try { $token = $jwt->encode($set); } catch (CryptException $e) { return null; } if ($typ == 'jwt') { return $token; } $headers = array('alg' => $this->encrypted_response_alg, 'enc' => $this->encrypted_response_enc, 'cty' => 'JWT'); $jwe = new JWE($headers, $token); try { return $jwe->encrypt($set); } catch (CryptException $e) { return null; } }
/** * Detects the format of key data and returns a key object. * * The supported formats are: * * - `php` - JSON web key formatted as a PHP associative array * - `json` - JSON web key * - `pem` - the public or private key encoded in PEM (base64 encoded DER) format * - `jwe` - Encrypted JSON web key * * @param string $data the key data * @param string $format the format * @param string $password the password, if the key is password protected * @param string $alg the algorithm, if the key is password protected * @return Key the key object * @throws KeyException if an error occurs in reading the data */ public static function create($data, $format = null, $password = null, $alg = 'PBES2-HS256+A128KW') { // 1. Detect format if ($format == null || $format == 'auto') { if (is_array($data)) { $format = 'php'; } elseif (json_decode($data, true) != null) { $format = 'json'; } elseif (substr_count($data, '.') == 5) { $format = 'jwe'; } elseif (preg_match('/-----([^-:]+)-----/', $data)) { $format = 'pem'; } } if ($format == null || $format == 'auto') { throw new KeyException('Cannot detect key format'); } // 2. Decode JSON if ($format == 'json') { $json = json_decode($data, true); if (isset($json['ciphertext'])) { $format = 'jwe'; } else { $data = $json; $format = 'php'; } } // 3. JWE if ($format == 'jwe') { if ($password == null) { throw new KeyException('No password for encrypted key'); } else { $keys = KeySet::createFromSecret($password, 'bin'); try { $jwe = JWE::decrypt($data, $keys, $alg, isset($data['ciphertext']) ? JWE::JSON_FORMAT : JWE::COMPACT_FORMAT); $data = json_decode($jwe->getPlaintext()); $format = 'php'; } catch (CryptException $e) { throw new KeyException('Cannot decrypt key', 0, $e); } } } // 4. PHP/JSON if ($format == 'php') { if ($data != null && isset($data['kty'])) { if (isset(self::$jwk_kty_map[$data['kty']])) { return new self::$jwk_kty_map[$data['kty']]($data, 'php'); } } } // 4. PEM if ($format == 'pem') { if (preg_match(Key::PEM_PUBLIC, $data, $matches)) { $der = base64_decode($matches[1]); if ($der === FALSE) { throw new KeyException('Cannot read PEM key'); } $offset = 0; $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $algorithm); // OBJECT IDENTIFIER - AlgorithmIdentifier $oid = ASN1::decodeOID($algorithm); if (isset(self::$oid_map[$oid])) { return new self::$oid_map[$oid]($data, 'pem'); } } else { foreach (self::$pem_map as $regex => $cls) { if (preg_match($regex, $data)) { return new $cls($data, 'pem'); } } } } // 5. Symmetric key if ($format == 'base64url' || $format == 'base64' || $format == 'bin') { return new SymmetricKey($data, $format); } return null; }
/** * Returns a key set as a JSON web key set. * * If `$password` is null, an unencrypted JSON structure is returned. * * If `$password` is not null, a JWE is created using PBES2 key encryption. * * @param string $password the password * @return string the key set */ function toJWKS($password = null) { $result = array_map(function ($key) { return $key->getKeyData(); }, $this->keys); $json = json_encode(array('keys' => $result)); if ($password == null) { return $json; } $keys = KeySet::createFromSecret($password, 'bin'); $headers = array('alg' => 'PBES2-HS256+A128KW', 'enc' => 'A128CBC-HS256', 'cty' => 'jwk-set+json'); $jwe = new JWE($headers, $json); return $jwe->encrypt($keys); }
/** * Returns a key as a JSON web key. * * If `$password` is null or if the key is a public key, an unencrypted JSON * structure is returned. * * If `$password` is not null and the key is a private key, a JWE is created * using PBES2 key encryption. * * @param string $password the password * @return string the key set */ public function toJWK($password = null) { $json = json_encode($this->data); if ($password == null || $this->isPublic()) { return $json; } $keys = KeySet::createFromSecret($password, 'bin'); $headers = array('alg' => 'PBES2-HS256+A128KW', 'enc' => 'A128CBC-HS256', 'cty' => 'jwk+json'); $jwe = new JWE($headers, $json); return $jwe->encrypt($keys); }