From d0d75eb83d1da7a2a2789fbfc90c98968f1ac4e5 Mon Sep 17 00:00:00 2001 From: Juha Jantunen Date: Wed, 25 Mar 2020 18:38:11 +0400 Subject: [PATCH] Refactor code for first release --- .gitignore | 1 + .travis.yml | 11 +- README.md | 118 ++++++++++++++++++ examples/full-flow.php | 44 +++++++ phpunit.xml | 8 ++ src/Converter.php | 86 ------------- src/{Key.php => Key/AbstractKey.php} | 91 ++++++-------- src/Key/KeyInterface.php | 58 +++++++++ src/Key/Rsa.php | 94 ++++++++++++++ src/KeyConverter.php | 58 +++++++++ src/KeyFactory.php | 85 +++++++++++++ src/KeySet.php | 53 +++++--- src/KeySetFactory.php | 60 +++++++++ src/Util/Base64UrlConverter.php | 36 ++++++ src/Util/Base64UrlConverterInterface.php | 37 ++++++ tests/ConverterTest.php | 101 --------------- tests/{KeyTest.php => Key/RsaTest.php} | 10 +- tests/KeyConverterTest.php | 56 +++++++++ tests/KeyFactoryTest.php | 63 ++++++++++ .../{KeySetTest.php => KeySetFactoryTest.php} | 19 +-- tests/Util/Base64UrlConverterTest.php | 59 +++++++++ 21 files changed, 873 insertions(+), 275 deletions(-) create mode 100644 examples/full-flow.php delete mode 100644 src/Converter.php rename src/{Key.php => Key/AbstractKey.php} (54%) create mode 100644 src/Key/KeyInterface.php create mode 100644 src/Key/Rsa.php create mode 100644 src/KeyConverter.php create mode 100644 src/KeyFactory.php create mode 100644 src/KeySetFactory.php create mode 100644 src/Util/Base64UrlConverter.php create mode 100644 src/Util/Base64UrlConverterInterface.php delete mode 100644 tests/ConverterTest.php rename tests/{KeyTest.php => Key/RsaTest.php} (88%) create mode 100644 tests/KeyConverterTest.php create mode 100644 tests/KeyFactoryTest.php rename tests/{KeySetTest.php => KeySetFactoryTest.php} (73%) create mode 100644 tests/Util/Base64UrlConverterTest.php diff --git a/.gitignore b/.gitignore index b99bef9..1090ab5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .phpunit.result.cache composer.lock vendor +coverage.xml diff --git a/.travis.yml b/.travis.yml index e2bb3fd..639ddcb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,15 @@ language: php php: - 7.2 + - 7.3 sudo: false -before_script: composer install -script: phpunit +before_script: + - composer install + +script: + - phpunit + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index 973c408..700a167 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,120 @@ # php-jwk + A small PHP library to handle JWKs (Json Web Keys) + +This library helps to create json web key sets from PEM and is also able to pull out PEMs from json web key sets. + +Please note that **only RSA keys are supported at the moment!** + +See [JSON Web Key RFC](https://tools.ietf.org/html/rfc7517) for reference. + +## Installation + +This library requires PHP version 7.2 or higher and can be installed with composer: + +```bash +composer require strobotti/php-jwk +``` + +## Example usage + +See full example [here](blob/master/examples/full-flow.php). + +### Create a key-object from PEM + +```php + 'sig', + 'alg' => 'RS256', + 'kid' => 'eXaunmL', +]; + +$keyFactory = new Strobotti\JWK\KeyFactory(); +$key = $keyFactory->createFromPem($pem, $options); + +echo "$key"; +``` + +Outputs: + +```json +{ + "kty": "RSA", + "use": "sig", + "alg": "RS256", + "kid": "eXaunmL", + "n": "4dGQ7bQK8LgILOdLsYzfZjkEAoQeVC_aqyc8GC6RX7dq_KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdDNq1n52TpxQwI2EqxSk7I9fKPKhRt4F8-2yETlYvye-2s6NeWJim0KBtOVrk0gWvEDgd6WOqJl_yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X-Tip84wqwyRpUlq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll-p_Dg8vAXxJLIJ4SNLcqgFeZe4OfHLgdzMvxXZJnPp_VgmkcpUdRotazKZumj6dBPcXI_XID4Z4Z3OM1KrZPJNdUhxw", + "e": "AQAB" +} +``` + +### Create a JWK set (jwks) from a key + +```php +addKey($key); + +echo "$keySet" ; + +``` + +Outputs: + +```json +{ + "keys": [ + { + "kty": "RSA", + "use": "sig", + "alg": "RS256", + "kid": "eXaunmL", + "n": "4dGQ7bQK8LgILOdLsYzfZjkEAoQeVC_aqyc8GC6RX7dq_KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdDNq1n52TpxQwI2EqxSk7I9fKPKhRt4F8-2yETlYvye-2s6NeWJim0KBtOVrk0gWvEDgd6WOqJl_yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X-Tip84wqwyRpUlq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll-p_Dg8vAXxJLIJ4SNLcqgFeZe4OfHLgdzMvxXZJnPp_VgmkcpUdRotazKZumj6dBPcXI_XID4Z4Z3OM1KrZPJNdUhxw", + "e": "AQAB" + } + ] +} +``` + +### Get a key from keyset by `kid` and convert it to PEM + +```php +getKeyById('eXaunmL'); +$pem = (new \Strobotti\JWK\KeyConverter())->keyToPem($key); + +echo "$pem"; + +``` + +Outputs: + +```text +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4dGQ7bQK8LgILOdLsYzf +ZjkEAoQeVC/aqyc8GC6RX7dq/KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdD +Nq1n52TpxQwI2EqxSk7I9fKPKhRt4F8+2yETlYvye+2s6NeWJim0KBtOVrk0gWvE +Dgd6WOqJl/yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X+Tip84wqwyRpU +lq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll+p/Dg8vAXxJLIJ4SNLcqgFeZe +4OfHLgdzMvxXZJnPp/VgmkcpUdRotazKZumj6dBPcXI/XID4Z4Z3OM1KrZPJNdUh +xwIDAQAB +-----END PUBLIC KEY----- +``` diff --git a/examples/full-flow.php b/examples/full-flow.php new file mode 100644 index 0000000..ab37401 --- /dev/null +++ b/examples/full-flow.php @@ -0,0 +1,44 @@ + 'sig', + 'alg' => 'RS256', + 'kid' => 'eXaunmL', +]; + +$keyFactory = new KeyFactory(); +$key = $keyFactory->createFromPem($pem, $options); + +echo $key . PHP_EOL . PHP_EOL; + +echo "Adding the key to the KeySet:" . PHP_EOL; + +$keySet = new \Strobotti\JWK\KeySet(); +$keySet->addKey($key); + +echo $keySet . PHP_EOL . PHP_EOL; + +echo "Fetching the key by it's ID (`kid`) and convert it back to PEM:" . PHP_EOL; + +$key = $keySet->getKeyById('eXaunmL'); +$pem = (new \Strobotti\JWK\KeyConverter())->keyToPem($key); + +echo $pem . PHP_EOL . PHP_EOL; diff --git a/phpunit.xml b/phpunit.xml index 8a97eff..80dd7a5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,4 +6,12 @@ tests + + + ./src/ + + + + + diff --git a/src/Converter.php b/src/Converter.php deleted file mode 100644 index 36f769b..0000000 --- a/src/Converter.php +++ /dev/null @@ -1,86 +0,0 @@ - - * @license https://opensource.org/licenses/MIT MIT - * @link https://github.com/Strobotti/php-jwk - */ -class Converter -{ - /** - * @param Key $key - * - * @return string - */ - public function keyToPem(Key $key): string - { - // TODO implement strategies to support different key types - $rsa = new RSA(); - - $modulus = $this->base64UrlDecode($key->getRsaModulus(), true); - - $rsa->loadKey([ - 'e' => new BigInteger(\base64_decode($key->getRsaExponent(), true), 256), - 'n' => new BigInteger($modulus, 256), - ]); - - return $rsa->getPublicKey(); - } - - /** - * @param string $pem A PEM encoded (RSA) Public Key - * @param array $options An array of key-options, such as ['kid' => 'eXaunmL', 'use' => 'sig', ...] - * - * @return Key - */ - public function pemToKey(string $pem, array $options = []): Key - { - $keyInfo = openssl_pkey_get_details(openssl_pkey_get_public($pem)); - - $jsonData = array_merge( - $options, - [ - 'kty' => 'RSA', - 'n' => $this->base64UrlEncode($keyInfo['rsa']['n']), - 'e' => $this->base64UrlEncode($keyInfo['rsa']['e']), - ] - ); - - return Key::createFromJSON(json_encode($jsonData)); - } - - /** - * https://tools.ietf.org/html/rfc4648#section-5. - * - * @param string $data - * @param bool $strict - * - * @return string - */ - private function base64UrlDecode(string $data, $strict = false): string - { - $b64 = \strtr($data, '-_', '+/'); - - return \base64_decode($b64, $strict); - } - - /** - * https://tools.ietf.org/html/rfc4648#section-5. - * - * @param string $data - * - * @return string - */ - private function base64UrlEncode(string $data): string - { - return rtrim(\strtr(\base64_encode($data), '+/', '-_'), '='); - } -} diff --git a/src/Key.php b/src/Key/AbstractKey.php similarity index 54% rename from src/Key.php rename to src/Key/AbstractKey.php index b9b9484..764de9d 100644 --- a/src/Key.php +++ b/src/Key/AbstractKey.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Strobotti\JWK; +namespace Strobotti\JWK\Key; /** * @package Strobotti\JWK @@ -10,15 +10,8 @@ * @license https://opensource.org/licenses/MIT MIT * @link https://github.com/Strobotti/php-jwk */ -class Key implements \JsonSerializable +abstract class AbstractKey implements KeyInterface { - public const KEY_TYPE_RSA = 'RSA'; - - public const PUBLIC_KEY_USE_SIGNATURE = 'sig'; - public const PUBLIC_KEY_USE_ENCRYPTION = 'enc'; - - public const ALGORITHM_RS256 = 'RS256'; - /** * The key type. * @@ -48,21 +41,7 @@ class Key implements \JsonSerializable private $alg; /** - * The modulus for the RSA public key. - * - * @var null|string - */ - private $n; - - /** - * The exponent for the RSA public key. - * - * @var null|string - */ - private $e; - - /** - * @return string + * {@inheritdoc} */ public function getKeyType(): string { @@ -70,7 +49,7 @@ public function getKeyType(): string } /** - * @return string + * {@inheritdoc} */ public function getKeyId(): string { @@ -78,7 +57,7 @@ public function getKeyId(): string } /** - * @return string + * {@inheritdoc} */ public function getPublicKeyUse(): string { @@ -86,7 +65,7 @@ public function getPublicKeyUse(): string } /** - * @return string + * {@inheritdoc} */ public function getAlgorithm(): string { @@ -94,27 +73,15 @@ public function getAlgorithm(): string } /** - * Returns the modulus for the RSA public key. + * @param string $kty * - * @todo Implement different key types through inheritance - * - * @return null|string + * @return self */ - public function getRsaModulus(): ?string + protected function setKeyType(string $kty): self { - return $this->n; - } + $this->kty = $kty; - /** - * Returns the exponent for the RSA public key. - * - * @todo Implement different key types through inheritance - * - * @return null|string - */ - public function getRsaExponent(): ?string - { - return $this->e; + return $this; } /** @@ -126,41 +93,53 @@ public function jsonSerialize(): array { $assoc = [ 'kty' => $this->kty, - 'kid' => $this->kid, 'use' => $this->use, 'alg' => $this->alg, ]; - if (null !== $this->n) { - $assoc['n'] = $this->n; - } - - if (null !== $this->e) { - $assoc['e'] = $this->e; + if (null !== $this->kid) { + $assoc['kid'] = $this->kid; } return $assoc; } /** - * @param string $json + * @param string $json + * @param KeyInterface|null $prototype * - * @return static + * @return KeyInterface */ - public static function createFromJSON(string $json): self + public static function createFromJSON(string $json, KeyInterface $prototype = null): KeyInterface { $assoc = \json_decode($json, true); - $instance = new static(); + if ($prototype) { + $instance = clone $prototype; + } else { + $instance = new static(); + } foreach ($assoc as $key => $value) { if (!\property_exists($instance, $key)) { continue; } - $instance->{$key} = $value; + try { + $instance->{$key} = $value; + } catch (\Throwable $e) { + // only set what you can + } } return $instance; } + + /** + * @return false|string + */ + public function __toString() + { + return json_encode($this->jsonSerialize(), JSON_PRETTY_PRINT); + } } diff --git a/src/Key/KeyInterface.php b/src/Key/KeyInterface.php new file mode 100644 index 0000000..5c1591e --- /dev/null +++ b/src/Key/KeyInterface.php @@ -0,0 +1,58 @@ + + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/Strobotti/php-jwk + * + * @method array jsonSerialize() + */ +interface KeyInterface extends \JsonSerializable +{ + public const KEY_TYPE_RSA = 'RSA'; + public const KEY_TYPE_OKP = 'OKP'; + public const KEY_TYPE_EC = 'EC'; + + public const PUBLIC_KEY_USE_SIGNATURE = 'sig'; + public const PUBLIC_KEY_USE_ENCRYPTION = 'enc'; + + public const ALGORITHM_RS256 = 'RS256'; + + /** + * Gets the key type, ie. the value of the `kty` field + * + * @return string + */ + public function getKeyType(): string; + + /** + * Gets the key id, ie. the value of the `kid` field + * + * @return null|string + */ + public function getKeyId(): ?string; + + /** + * Gets the public key use, ie. the value of the `use` field + * + * @return string + */ + public function getPublicKeyUse(): string; + + /** + * Gets the cryptographic algorithm used to sign the key, ie. the value of the `alg` field + * + * @return string + */ + public function getAlgorithm(): string; + + /** + * @return string|bool + */ + public function __toString(); +} diff --git a/src/Key/Rsa.php b/src/Key/Rsa.php new file mode 100644 index 0000000..cd2a3d4 --- /dev/null +++ b/src/Key/Rsa.php @@ -0,0 +1,94 @@ + + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/Strobotti/php-jwk + */ +class Rsa extends AbstractKey +{ + /** + * The modulus for the RSA public key. + * + * @var string + */ + private $n; + + /** + * The exponent for the RSA public key. + * + * @var string + */ + private $e; + + /** + * Rsa key constructor. + */ + public function __construct() + { + $this->setKeyType(KeyInterface::KEY_TYPE_RSA); + } + + /** + * Returns the exponent for the RSA public key. + * + * @return string + */ + public function getExponent(): string + { + return $this->e; + } + + /** + * Returns the modulus for the RSA public key. + * + * @return string + */ + public function getModulus(): string + { + return $this->n; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize(): array + { + $assoc = parent::jsonSerialize(); + $assoc['n'] = $this->n; + $assoc['e'] = $this->e; + + return $assoc; + } + + /** + * {@inheritdoc} + * + * @return self + */ + public static function createFromJSON(string $json, KeyInterface $prototype = null): KeyInterface + { + if (!$prototype instanceof Rsa) { + $prototype = new static(); + } + + $instance = parent::createFromJSON($json, $prototype); + + $assoc = \json_decode($json, true); + + foreach ($assoc as $key => $value) { + if (!\property_exists($instance, $key)) { + continue; + } + + $instance->{$key} = $value; + } + + return $instance; + } +} diff --git a/src/KeyConverter.php b/src/KeyConverter.php new file mode 100644 index 0000000..6131795 --- /dev/null +++ b/src/KeyConverter.php @@ -0,0 +1,58 @@ + + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/Strobotti/php-jwk + */ +class KeyConverter +{ + /** + * @var Base64UrlConverterInterface + */ + private $base64UrlConverter; + + /** + * KeyConverter constructor. + */ + public function __construct() + { + $this->base64UrlConverter = new Base64UrlConverter(); + } + + /** + * @param KeyInterface $key + * + * @return string + */ + public function keyToPem(KeyInterface $key): string + { + if (!$key instanceof \Strobotti\JWK\Key\Rsa) { + throw new \InvalidArgumentException(); + } + + /** @var \Strobotti\JWK\Key\Rsa $key */ + + $rsa = new RSA(); + + $modulus = $this->base64UrlConverter->decode($key->getModulus(), true); + + $rsa->loadKey([ + 'e' => new BigInteger(\base64_decode($key->getExponent(), true), 256), + 'n' => new BigInteger($modulus, 256), + ]); + + return $rsa->getPublicKey(); + } +} diff --git a/src/KeyFactory.php b/src/KeyFactory.php new file mode 100644 index 0000000..3f4564b --- /dev/null +++ b/src/KeyFactory.php @@ -0,0 +1,85 @@ + + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/Strobotti/php-jwk + */ +class KeyFactory +{ + /** + * @var Base64UrlConverterInterface + */ + private $base64UrlConverter; + + /** + * KeyFactory constructor. + */ + public function __construct() + { + $this->base64UrlConverter = new Base64UrlConverter(); + } + + /** + * @param Base64UrlConverterInterface $base64UrlConverter + * + * @return KeyFactory + */ + public function setBase64UrlConverter(Base64UrlConverterInterface $base64UrlConverter): self + { + $this->base64UrlConverter = $base64UrlConverter; + + return $this; + } + + /** + * @return Base64UrlConverterInterface + */ + public function getBase64UrlConverter(): Base64UrlConverterInterface + { + return $this->base64UrlConverter; + } + + /** + * @param string $pem + * @param array $options + * + * @return KeyInterface + */ + public function createFromPem(string $pem, array $options = []): KeyInterface + { + $keyInfo = openssl_pkey_get_details(openssl_pkey_get_public($pem)); + + $jsonData = array_merge( + $options, + [ + 'kty' => 'RSA', + 'n' => $this->base64UrlConverter->encode($keyInfo['rsa']['n']), + 'e' => $this->base64UrlConverter->encode($keyInfo['rsa']['e']), + ] + ); + + // TODO Only RSA is supported atm + return Key\Rsa::createFromJSON(json_encode($jsonData)); + } + + /** + * @param string $json + * + * @return KeyInterface + */ + public function createFromJson(string $json): KeyInterface + { + // TODO Only RSA is supported atm + return Key\Rsa::createFromJSON($json); + } +} diff --git a/src/KeySet.php b/src/KeySet.php index 2a34b5f..f4f1f50 100644 --- a/src/KeySet.php +++ b/src/KeySet.php @@ -4,6 +4,8 @@ namespace Strobotti\JWK; +use Strobotti\JWK\Key\KeyInterface; + /** * @package Strobotti\JWK * @author Juha Jantunen @@ -13,10 +15,35 @@ class KeySet implements \JsonSerializable { /** - * @var Key[] + * @var KeyFactory + */ + private $keyFactory; + + /** + * @var KeyInterface[] */ private $keys = []; + /** + * KeySet constructor. + */ + public function __construct() + { + $this->keyFactory = new KeyFactory(); + } + + /** + * @param KeyFactory $keyFactory + * + * @return self + */ + public function setKeyFactory(KeyFactory $keyFactory): self + { + $this->keyFactory = $keyFactory; + + return $this; + } + /** * @param string $kid * @@ -30,9 +57,9 @@ public function containsKey(string $kid): bool /** * @param string $kid * - * @return null|Key + * @return null|KeyInterface */ - public function getKeyById(string $kid): ?Key + public function getKeyById(string $kid): ?KeyInterface { if (!$this->containsKey($kid)) { return null; @@ -42,11 +69,11 @@ public function getKeyById(string $kid): ?Key } /** - * @param Key $key + * @param KeyInterface $key * * @return KeySet */ - public function addKey(Key $key): self + public function addKey(KeyInterface $key): self { if ($this->containsKey($key->getKeyId())) { throw new \InvalidArgumentException(\sprintf( @@ -77,20 +104,10 @@ public function jsonSerialize(): array } /** - * @param string $json - * - * @return static + * @return false|string */ - public static function createFromJSON(string $json): self + public function __toString() { - $assoc = \json_decode($json, true); - - $instance = new static(); - - foreach ($assoc['keys'] as $key) { - $instance->addKey(Key::createFromJSON(\json_encode($key))); - } - - return $instance; + return json_encode($this->jsonSerialize(), JSON_PRETTY_PRINT); } } diff --git a/src/KeySetFactory.php b/src/KeySetFactory.php new file mode 100644 index 0000000..cc5d641 --- /dev/null +++ b/src/KeySetFactory.php @@ -0,0 +1,60 @@ + + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/Strobotti/php-jwk + */ +class KeySetFactory +{ + /** + * @var KeyFactory + */ + private $keyFactory; + + /** + * KeySet constructor. + */ + public function __construct() + { + $this->keyFactory = new KeyFactory(); + } + + /** + * @param KeyFactory $keyFactory + * + * @return self + */ + public function setKeyFactory(KeyFactory $keyFactory): self + { + $this->keyFactory = $keyFactory; + + return $this; + } + + /** + * @param string $json + * + * @return KeySet + */ + public function createFromJSON(string $json): KeySet + { + $assoc = \json_decode($json, true); + + $instance = new KeySet(); + + foreach ($assoc['keys'] as $keyData) { + $key = $this->keyFactory->createFromJson(\json_encode($keyData)); + + $instance->addKey($key); + } + + return $instance; + } + +} diff --git a/src/Util/Base64UrlConverter.php b/src/Util/Base64UrlConverter.php new file mode 100644 index 0000000..6998761 --- /dev/null +++ b/src/Util/Base64UrlConverter.php @@ -0,0 +1,36 @@ + + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/Strobotti/php-jwk + */ +class Base64UrlConverter implements Base64UrlConverterInterface +{ + /** + * {@inheritdoc} + */ + public function decode(string $data, $strict = false): string + { + $b64 = \strtr($data, '-_', '+/'); + + return \base64_decode($b64, $strict); + } + + /** + * {@inheritdoc} + */ + public function encode(string $data): string + { + return rtrim(\strtr(\base64_encode($data), '+/', '-_'), '='); + } +} diff --git a/src/Util/Base64UrlConverterInterface.php b/src/Util/Base64UrlConverterInterface.php new file mode 100644 index 0000000..51b0ea1 --- /dev/null +++ b/src/Util/Base64UrlConverterInterface.php @@ -0,0 +1,37 @@ + + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/Strobotti/php-jwk + */ +interface Base64UrlConverterInterface +{ + /** + * Decodes Base64url formatted data to a string + * + * @param string $data + * @param bool $strict + * + * @return string + */ + public function decode(string $data, $strict = false): string; + + /** + * Encodes a string to a base64url formatted data + * + * @param string $data + * + * @return string + */ + public function encode(string $data): string; +} diff --git a/tests/ConverterTest.php b/tests/ConverterTest.php deleted file mode 100644 index c473ef8..0000000 --- a/tests/ConverterTest.php +++ /dev/null @@ -1,101 +0,0 @@ -keyToPem($key)) - ); - } - - /** - * @return \Generator - */ - public function provideKeyToPem(): \Generator - { - yield [ - 'key' => Key::createFromJSON('{ - "kty": "RSA", - "kid": "eXaunmL", - "use": "sig", - "alg": "RS256", - "n": "4dGQ7bQK8LgILOdLsYzfZjkEAoQeVC_aqyc8GC6RX7dq_KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdDNq1n52TpxQwI2EqxSk7I9fKPKhRt4F8-2yETlYvye-2s6NeWJim0KBtOVrk0gWvEDgd6WOqJl_yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X-Tip84wqwyRpUlq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll-p_Dg8vAXxJLIJ4SNLcqgFeZe4OfHLgdzMvxXZJnPp_VgmkcpUdRotazKZumj6dBPcXI_XID4Z4Z3OM1KrZPJNdUhxw", - "e": "AQAB" - }'), - 'expected' => <<<'EOT' ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4dGQ7bQK8LgILOdLsYzf -ZjkEAoQeVC/aqyc8GC6RX7dq/KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdD -Nq1n52TpxQwI2EqxSk7I9fKPKhRt4F8+2yETlYvye+2s6NeWJim0KBtOVrk0gWvE -Dgd6WOqJl/yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X+Tip84wqwyRpU -lq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll+p/Dg8vAXxJLIJ4SNLcqgFeZe -4OfHLgdzMvxXZJnPp/VgmkcpUdRotazKZumj6dBPcXI/XID4Z4Z3OM1KrZPJNdUh -xwIDAQAB ------END PUBLIC KEY----- -EOT - ]; - } - - /** - * @param string $pem - * @param array $key - * - * @dataProvider providePemToKey - */ - public function testPemToKey(string $pem, array $options, array $key) - { - $converter = new Converter(); - static::assertSame($key, $converter->pemToKey($pem, $options)->jsonSerialize()); - } - - /** - * @return \Generator - */ - public function providePemToKey(): \Generator - { - yield [ - 'pem' => <<<'EOT' ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4dGQ7bQK8LgILOdLsYzf -ZjkEAoQeVC/aqyc8GC6RX7dq/KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdD -Nq1n52TpxQwI2EqxSk7I9fKPKhRt4F8+2yETlYvye+2s6NeWJim0KBtOVrk0gWvE -Dgd6WOqJl/yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X+Tip84wqwyRpU -lq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll+p/Dg8vAXxJLIJ4SNLcqgFeZe -4OfHLgdzMvxXZJnPp/VgmkcpUdRotazKZumj6dBPcXI/XID4Z4Z3OM1KrZPJNdUh -xwIDAQAB ------END PUBLIC KEY----- -EOT - , - 'options' => [ - 'kid' => 'eXaunmL', - 'use' => 'sig', - 'alg' => 'RS256', - ], - 'key' => [ - 'kty' => 'RSA', - 'kid' => 'eXaunmL', - 'use' => 'sig', - 'alg' => 'RS256', - 'n' => '4dGQ7bQK8LgILOdLsYzfZjkEAoQeVC_aqyc8GC6RX7dq_KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdDNq1n52TpxQwI2EqxSk7I9fKPKhRt4F8-2yETlYvye-2s6NeWJim0KBtOVrk0gWvEDgd6WOqJl_yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X-Tip84wqwyRpUlq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll-p_Dg8vAXxJLIJ4SNLcqgFeZe4OfHLgdzMvxXZJnPp_VgmkcpUdRotazKZumj6dBPcXI_XID4Z4Z3OM1KrZPJNdUhxw', - 'e' => 'AQAB', - ], - ]; - } -} diff --git a/tests/KeyTest.php b/tests/Key/RsaTest.php similarity index 88% rename from tests/KeyTest.php rename to tests/Key/RsaTest.php index e4c5a7e..6044c8c 100644 --- a/tests/KeyTest.php +++ b/tests/Key/RsaTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Strobotti\JWK\Tests; +namespace Strobotti\JWK\Key\Tests; use PHPUnit\Framework\TestCase; -use Strobotti\JWK\Key; +use Strobotti\JWK\Key\Rsa; final class KeyTest extends TestCase { @@ -17,14 +17,14 @@ final class KeyTest extends TestCase */ public function testCreateFromJSON(array $expected, string $input): void { - $key = Key::createFromJSON($input); + $key = Rsa::createFromJSON($input); static::assertSame($expected['kty'], $key->getKeyType()); static::assertSame($expected['kid'], $key->getKeyId()); static::assertSame($expected['use'], $key->getPublicKeyUse()); static::assertSame($expected['alg'], $key->getAlgorithm()); - static::assertSame($expected['n'], $key->getRsaModulus()); - static::assertSame($expected['e'], $key->getRsaExponent()); + static::assertSame($expected['n'], $key->getModulus()); + static::assertSame($expected['e'], $key->getExponent()); } /** diff --git a/tests/KeyConverterTest.php b/tests/KeyConverterTest.php new file mode 100644 index 0000000..e76006c --- /dev/null +++ b/tests/KeyConverterTest.php @@ -0,0 +1,56 @@ +keyToPem($key)) + ); + } + + /** + * @return \Generator + */ + public function provideKeyToPem(): \Generator + { + yield [ + 'key' => Rsa::createFromJSON('{ + "kty": "RSA", + "kid": "eXaunmL", + "use": "sig", + "alg": "RS256", + "n": "4dGQ7bQK8LgILOdLsYzfZjkEAoQeVC_aqyc8GC6RX7dq_KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdDNq1n52TpxQwI2EqxSk7I9fKPKhRt4F8-2yETlYvye-2s6NeWJim0KBtOVrk0gWvEDgd6WOqJl_yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X-Tip84wqwyRpUlq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll-p_Dg8vAXxJLIJ4SNLcqgFeZe4OfHLgdzMvxXZJnPp_VgmkcpUdRotazKZumj6dBPcXI_XID4Z4Z3OM1KrZPJNdUhxw", + "e": "AQAB" + }'), + 'expected' => <<<'EOT' +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4dGQ7bQK8LgILOdLsYzf +ZjkEAoQeVC/aqyc8GC6RX7dq/KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdD +Nq1n52TpxQwI2EqxSk7I9fKPKhRt4F8+2yETlYvye+2s6NeWJim0KBtOVrk0gWvE +Dgd6WOqJl/yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X+Tip84wqwyRpU +lq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll+p/Dg8vAXxJLIJ4SNLcqgFeZe +4OfHLgdzMvxXZJnPp/VgmkcpUdRotazKZumj6dBPcXI/XID4Z4Z3OM1KrZPJNdUh +xwIDAQAB +-----END PUBLIC KEY----- +EOT + ]; + } +} diff --git a/tests/KeyFactoryTest.php b/tests/KeyFactoryTest.php new file mode 100644 index 0000000..9264a99 --- /dev/null +++ b/tests/KeyFactoryTest.php @@ -0,0 +1,63 @@ +createFromPem($pem, $options); + + $this->assertInstanceOf($expectedInstance, $key); + static::assertEquals($json, $key->jsonSerialize()); + } + + /** + * @return \Generator + */ + public function provideCreateFromPem(): \Generator + { + yield [ + 'pem' => <<<'EOT' +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4dGQ7bQK8LgILOdLsYzf +ZjkEAoQeVC/aqyc8GC6RX7dq/KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdD +Nq1n52TpxQwI2EqxSk7I9fKPKhRt4F8+2yETlYvye+2s6NeWJim0KBtOVrk0gWvE +Dgd6WOqJl/yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X+Tip84wqwyRpU +lq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll+p/Dg8vAXxJLIJ4SNLcqgFeZe +4OfHLgdzMvxXZJnPp/VgmkcpUdRotazKZumj6dBPcXI/XID4Z4Z3OM1KrZPJNdUh +xwIDAQAB +-----END PUBLIC KEY----- +EOT + , + 'options' => [ + 'use' => 'sig', + 'alg' => 'RS256', + 'kid' => 'eXaunmL', + ], + 'json' => [ + 'kty' => 'RSA', + 'use' => 'sig', + 'alg' => 'RS256', + 'kid' => 'eXaunmL', + 'n' => '4dGQ7bQK8LgILOdLsYzfZjkEAoQeVC_aqyc8GC6RX7dq_KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdDNq1n52TpxQwI2EqxSk7I9fKPKhRt4F8-2yETlYvye-2s6NeWJim0KBtOVrk0gWvEDgd6WOqJl_yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X-Tip84wqwyRpUlq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll-p_Dg8vAXxJLIJ4SNLcqgFeZe4OfHLgdzMvxXZJnPp_VgmkcpUdRotazKZumj6dBPcXI_XID4Z4Z3OM1KrZPJNdUhxw', + 'e' => 'AQAB', + ], + 'expectedInstance' => Rsa::class, + ]; + } +} diff --git a/tests/KeySetTest.php b/tests/KeySetFactoryTest.php similarity index 73% rename from tests/KeySetTest.php rename to tests/KeySetFactoryTest.php index 27d2f94..32bd36a 100644 --- a/tests/KeySetTest.php +++ b/tests/KeySetFactoryTest.php @@ -5,23 +5,28 @@ namespace Strobotti\JWK\Tests; use PHPUnit\Framework\TestCase; -use Strobotti\JWK\KeySet; +use Strobotti\JWK\KeySetFactory; -final class KeySetTest extends TestCase +final class KeySetFactoryTest extends TestCase { /** * @param string $input - * @dataProvider provideJSONConversion + * @dataProvider provideCreateFromJSON */ - public function testJSONConversion(string $input): void + public function testCreateFromJSON(string $input): void { - $keys = KeySet::createFromJSON($input); + $factory = new KeySetFactory(); + + $keys = $factory->createFromJSON($input); $json = $keys->jsonSerialize(); - static::assertSame(\json_decode($input, true), $json); + static::assertEquals(\json_decode($input, true), $json); } - public function provideJSONConversion(): \Generator + /** + * @return \Generator + */ + public function provideCreateFromJSON(): \Generator { yield [ 'input' => <<<'EOT' diff --git a/tests/Util/Base64UrlConverterTest.php b/tests/Util/Base64UrlConverterTest.php new file mode 100644 index 0000000..17591c8 --- /dev/null +++ b/tests/Util/Base64UrlConverterTest.php @@ -0,0 +1,59 @@ +assertSame($expected, $converter->decode($input)); + } + + /** + * @return \Generator + */ + public function provideDecode(): \Generator + { + yield [ + 'expected' => '/a+quick+brown+fox/jumped-over/the_lazy_dog/', + 'input' => 'L2ErcXVpY2srYnJvd24rZm94L2p1bXBlZC1vdmVyL3RoZV9sYXp5X2RvZy8', + ]; + } + + /** + * @param string $expected + * @param string $input + * + * @dataProvider provideEncode + */ + public function testEncode(string $expected, string $input): void + { + $converter = new Base64UrlConverter(); + + $this->assertSame($expected, $converter->encode($input)); + } + + /** + * @return \Generator + */ + public function provideEncode(): \Generator + { + yield [ + 'expected' => 'L2ErcXVpY2srYnJvd24rZm94L2p1bXBlZC1vdmVyL3RoZV9sYXp5X2RvZy8', + 'input' => '/a+quick+brown+fox/jumped-over/the_lazy_dog/', + ]; + } +}