diff options
Diffstat (limited to 'vendor/web-token/jwt-core/Util')
-rw-r--r-- | vendor/web-token/jwt-core/Util/BigInteger.php | 232 | ||||
-rw-r--r-- | vendor/web-token/jwt-core/Util/ECKey.php | 306 | ||||
-rw-r--r-- | vendor/web-token/jwt-core/Util/ECSignature.php | 93 | ||||
-rw-r--r-- | vendor/web-token/jwt-core/Util/Hash.php | 90 | ||||
-rw-r--r-- | vendor/web-token/jwt-core/Util/JsonConverter.php | 27 | ||||
-rw-r--r-- | vendor/web-token/jwt-core/Util/KeyChecker.php | 107 | ||||
-rw-r--r-- | vendor/web-token/jwt-core/Util/RSAKey.php | 322 |
7 files changed, 1177 insertions, 0 deletions
diff --git a/vendor/web-token/jwt-core/Util/BigInteger.php b/vendor/web-token/jwt-core/Util/BigInteger.php new file mode 100644 index 0000000..2513670 --- /dev/null +++ b/vendor/web-token/jwt-core/Util/BigInteger.php @@ -0,0 +1,232 @@ +<?php + +declare(strict_types=1); + +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Spomky-Labs + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +namespace Jose\Component\Core\Util; + +/** + * @internal + */ +class BigInteger +{ + /** + * Holds the BigInteger's value. + * + * @var \GMP + */ + private $value; + + private function __construct(\GMP $value) + { + $this->value = $value; + } + + /** + * @return BigInteger + */ + public static function createFromGMPResource(\GMP $value): self + { + return new self($value); + } + + /** + * @return BigInteger + */ + public static function createFromBinaryString(string $value): self + { + $value = '0x'.\unpack('H*', $value)[1]; + $value = \gmp_init($value, 16); + + return new self($value); + } + + /** + * @return BigInteger + */ + public static function createFromDecimal(int $value): self + { + $value = \gmp_init($value, 10); + + return new self($value); + } + + /** + * Converts a BigInteger to a binary string. + */ + public function toBytes(): string + { + if (0 === \gmp_cmp($this->value, \gmp_init(0))) { + return ''; + } + + $temp = \gmp_strval(\gmp_abs($this->value), 16); + $temp = \mb_strlen($temp, '8bit') & 1 ? '0'.$temp : $temp; + $temp = \hex2bin($temp); + + return \ltrim($temp, \chr(0)); + } + + /** + * Adds two BigIntegers. + * + * @param BigInteger $y + * + * @return BigInteger + */ + public function add(self $y): self + { + $value = \gmp_add($this->value, $y->value); + + return self::createFromGMPResource($value); + } + + /** + * Subtracts two BigIntegers. + * + * @param BigInteger $y + * + * @return BigInteger + */ + public function subtract(self $y): self + { + $value = \gmp_sub($this->value, $y->value); + + return self::createFromGMPResource($value); + } + + /** + * Multiplies two BigIntegers. + * + * @param BigInteger $x + * + * @return BigInteger + */ + public function multiply(self $x): self + { + $value = \gmp_mul($this->value, $x->value); + + return self::createFromGMPResource($value); + } + + /** + * Divides two BigIntegers. + * + * @param BigInteger $x + * + * @return BigInteger + */ + public function divide(self $x): self + { + $value = \gmp_div($this->value, $x->value); + + return self::createFromGMPResource($value); + } + + /** + * Performs modular exponentiation. + * + * @param BigInteger $e + * @param BigInteger $n + * + * @return BigInteger + */ + public function modPow(self $e, self $n): self + { + $value = \gmp_powm($this->value, $e->value, $n->value); + + return self::createFromGMPResource($value); + } + + /** + * Performs modular exponentiation. + * + * @param BigInteger $d + * + * @return BigInteger + */ + public function mod(self $d): self + { + $value = \gmp_mod($this->value, $d->value); + + return self::createFromGMPResource($value); + } + + /** + * Calculates modular inverses. + * + * @param BigInteger $n + * + * @return BigInteger + */ + public function modInverse(self $n): self + { + $value = \gmp_invert($this->value, $n->value); + + return self::createFromGMPResource($value); + } + + /** + * Compares two numbers. + * + * @param BigInteger $y + */ + public function compare(self $y): int + { + return \gmp_cmp($this->value, $y->value); + } + + /** + * @param BigInteger $y + */ + public function equals(self $y): bool + { + return 0 === $this->compare($y); + } + + /** + * @param BigInteger $y + * + * @return BigInteger + */ + public static function random(self $y): self + { + $zero = self::createFromDecimal(0); + + return self::createFromGMPResource(\gmp_random_range($zero->value, $y->value)); + } + + /** + * @param BigInteger $y + * + * @return BigInteger + */ + public function gcd(self $y): self + { + return self::createFromGMPResource(\gmp_gcd($this->value, $y->value)); + } + + /** + * @param BigInteger $y + */ + public function lowerThan(self $y): bool + { + return 0 > $this->compare($y); + } + + public function isEven(): bool + { + $zero = self::createFromDecimal(0); + $two = self::createFromDecimal(2); + + return $this->mod($two)->equals($zero); + } +} diff --git a/vendor/web-token/jwt-core/Util/ECKey.php b/vendor/web-token/jwt-core/Util/ECKey.php new file mode 100644 index 0000000..da409ba --- /dev/null +++ b/vendor/web-token/jwt-core/Util/ECKey.php @@ -0,0 +1,306 @@ +<?php + +declare(strict_types=1); + +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Spomky-Labs + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +namespace Jose\Component\Core\Util; + +use Base64Url\Base64Url; +use InvalidArgumentException; +use Jose\Component\Core\JWK; +use Jose\Component\Core\Util\Ecc\Curve; +use Jose\Component\Core\Util\Ecc\NistCurve; +use RuntimeException; +use Throwable; + +/** + * @internal + */ +class ECKey +{ + public static function convertToPEM(JWK $jwk): string + { + if ($jwk->has('d')) { + return self::convertPrivateKeyToPEM($jwk); + } + + return self::convertPublicKeyToPEM($jwk); + } + + public static function convertPublicKeyToPEM(JWK $jwk): string + { + switch ($jwk->get('crv')) { + case 'P-256': + $der = self::p256PublicKey(); + + break; + case 'P-384': + $der = self::p384PublicKey(); + + break; + case 'P-521': + $der = self::p521PublicKey(); + + break; + default: + throw new InvalidArgumentException('Unsupported curve.'); + } + $der .= self::getKey($jwk); + $pem = '-----BEGIN PUBLIC KEY-----'.PHP_EOL; + $pem .= chunk_split(base64_encode($der), 64, PHP_EOL); + $pem .= '-----END PUBLIC KEY-----'.PHP_EOL; + + return $pem; + } + + public static function convertPrivateKeyToPEM(JWK $jwk): string + { + switch ($jwk->get('crv')) { + case 'P-256': + $der = self::p256PrivateKey($jwk); + + break; + case 'P-384': + $der = self::p384PrivateKey($jwk); + + break; + case 'P-521': + $der = self::p521PrivateKey($jwk); + + break; + default: + throw new InvalidArgumentException('Unsupported curve.'); + } + $der .= self::getKey($jwk); + $pem = '-----BEGIN EC PRIVATE KEY-----'.PHP_EOL; + $pem .= chunk_split(base64_encode($der), 64, PHP_EOL); + $pem .= '-----END EC PRIVATE KEY-----'.PHP_EOL; + + return $pem; + } + + /** + * Creates a EC key with the given curve and additional values. + * + * @param string $curve The curve + * @param array $values values to configure the key + */ + public static function createECKey(string $curve, array $values = []): JWK + { + try { + $jwk = self::createECKeyUsingOpenSSL($curve); + } catch (Throwable $e) { + $jwk = self::createECKeyUsingPurePhp($curve); + } + $values = array_merge($values, $jwk); + + return new JWK($values); + } + + private static function getNistCurve(string $curve): Curve + { + switch ($curve) { + case 'P-256': + return NistCurve::curve256(); + case 'P-384': + return NistCurve::curve384(); + case 'P-521': + return NistCurve::curve521(); + default: + throw new InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve)); + } + } + + private static function getNistCurveSize(string $curve): int + { + switch ($curve) { + case 'P-256': + return 256; + case 'P-384': + return 384; + case 'P-521': + return 521; + default: + throw new InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve)); + } + } + + private static function createECKeyUsingPurePhp(string $curve): array + { + $nistCurve = self::getNistCurve($curve); + $privateKey = $nistCurve->createPrivateKey(); + $publicKey = $nistCurve->createPublicKey($privateKey); + + return [ + 'kty' => 'EC', + 'crv' => $curve, + 'x' => Base64Url::encode(str_pad(gmp_export($publicKey->getPoint()->getX()), (int) ceil($nistCurve->getSize() / 8), "\0", STR_PAD_LEFT)), + 'y' => Base64Url::encode(str_pad(gmp_export($publicKey->getPoint()->getY()), (int) ceil($nistCurve->getSize() / 8), "\0", STR_PAD_LEFT)), + 'd' => Base64Url::encode(str_pad(gmp_export($privateKey->getSecret()), (int) ceil($nistCurve->getSize() / 8), "\0", STR_PAD_LEFT)), + ]; + } + + private static function createECKeyUsingOpenSSL(string $curve): array + { + $key = openssl_pkey_new([ + 'curve_name' => self::getOpensslCurveName($curve), + 'private_key_type' => OPENSSL_KEYTYPE_EC, + ]); + if (false === $key) { + throw new RuntimeException('Unable to create the key'); + } + $result = openssl_pkey_export($key, $out); + if (false === $result) { + throw new RuntimeException('Unable to create the key'); + } + $res = openssl_pkey_get_private($out); + if (false === $res) { + throw new RuntimeException('Unable to create the key'); + } + $details = openssl_pkey_get_details($res); + $nistCurveSize = self::getNistCurveSize($curve); + + return [ + 'kty' => 'EC', + 'crv' => $curve, + 'd' => Base64Url::encode(str_pad($details['ec']['d'], (int) ceil($nistCurveSize / 8), "\0", STR_PAD_LEFT)), + 'x' => Base64Url::encode(str_pad($details['ec']['x'], (int) ceil($nistCurveSize / 8), "\0", STR_PAD_LEFT)), + 'y' => Base64Url::encode(str_pad($details['ec']['y'], (int) ceil($nistCurveSize / 8), "\0", STR_PAD_LEFT)), + ]; + } + + private static function getOpensslCurveName(string $curve): string + { + switch ($curve) { + case 'P-256': + return 'prime256v1'; + case 'P-384': + return 'secp384r1'; + case 'P-521': + return 'secp521r1'; + default: + throw new InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve)); + } + } + + private static function p256PublicKey(): string + { + return pack( + 'H*', + '3059' // SEQUENCE, length 89 + .'3013' // SEQUENCE, length 19 + .'0607' // OID, length 7 + .'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key + .'0608' // OID, length 8 + .'2a8648ce3d030107' // 1.2.840.10045.3.1.7 = P-256 Curve + .'0342' // BIT STRING, length 66 + .'00' // prepend with NUL - pubkey will follow + ); + } + + private static function p384PublicKey(): string + { + return pack( + 'H*', + '3076' // SEQUENCE, length 118 + .'3010' // SEQUENCE, length 16 + .'0607' // OID, length 7 + .'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key + .'0605' // OID, length 5 + .'2b81040022' // 1.3.132.0.34 = P-384 Curve + .'0362' // BIT STRING, length 98 + .'00' // prepend with NUL - pubkey will follow + ); + } + + private static function p521PublicKey(): string + { + return pack( + 'H*', + '30819b' // SEQUENCE, length 154 + .'3010' // SEQUENCE, length 16 + .'0607' // OID, length 7 + .'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key + .'0605' // OID, length 5 + .'2b81040023' // 1.3.132.0.35 = P-521 Curve + .'038186' // BIT STRING, length 134 + .'00' // prepend with NUL - pubkey will follow + ); + } + + private static function p256PrivateKey(JWK $jwk): string + { + $d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 32, "\0", STR_PAD_LEFT))[1]; + + return pack( + 'H*', + '3077' // SEQUENCE, length 87+length($d)=32 + .'020101' // INTEGER, 1 + .'0420' // OCTET STRING, length($d) = 32 + .$d + .'a00a' // TAGGED OBJECT #0, length 10 + .'0608' // OID, length 8 + .'2a8648ce3d030107' // 1.3.132.0.34 = P-384 Curve + .'a144' // TAGGED OBJECT #1, length 68 + .'0342' // BIT STRING, length 66 + .'00' // prepend with NUL - pubkey will follow + ); + } + + private static function p384PrivateKey(JWK $jwk): string + { + $d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 48, "\0", STR_PAD_LEFT))[1]; + + return pack( + 'H*', + '3081a4' // SEQUENCE, length 116 + length($d)=48 + .'020101' // INTEGER, 1 + .'0430' // OCTET STRING, length($d) = 30 + .$d + .'a007' // TAGGED OBJECT #0, length 7 + .'0605' // OID, length 5 + .'2b81040022' // 1.3.132.0.34 = P-384 Curve + .'a164' // TAGGED OBJECT #1, length 100 + .'0362' // BIT STRING, length 98 + .'00' // prepend with NUL - pubkey will follow + ); + } + + private static function p521PrivateKey(JWK $jwk): string + { + $d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 66, "\0", STR_PAD_LEFT))[1]; + + return pack( + 'H*', + '3081dc' // SEQUENCE, length 154 + length($d)=66 + .'020101' // INTEGER, 1 + .'0442' // OCTET STRING, length(d) = 66 + .$d + .'a007' // TAGGED OBJECT #0, length 7 + .'0605' // OID, length 5 + .'2b81040023' // 1.3.132.0.35 = P-521 Curve + .'a18189' // TAGGED OBJECT #1, length 137 + .'038186' // BIT STRING, length 134 + .'00' // prepend with NUL - pubkey will follow + ); + } + + private static function getKey(JWK $jwk): string + { + $nistCurveSize = self::getNistCurveSize($jwk->get('crv')); + $length = (int) ceil($nistCurveSize / 8); + + return + "\04" + .str_pad(Base64Url::decode($jwk->get('x')), $length, "\0", STR_PAD_LEFT) + .str_pad(Base64Url::decode($jwk->get('y')), $length, "\0", STR_PAD_LEFT); + } +} diff --git a/vendor/web-token/jwt-core/Util/ECSignature.php b/vendor/web-token/jwt-core/Util/ECSignature.php new file mode 100644 index 0000000..5bfa5b0 --- /dev/null +++ b/vendor/web-token/jwt-core/Util/ECSignature.php @@ -0,0 +1,93 @@ +<?php + +declare(strict_types=1); + +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Spomky-Labs + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +namespace Jose\Component\Core\Util; + +/** + * @internal + */ +class ECSignature +{ + public static function toDER(string $signature, int $partLength): string + { + $signature = \unpack('H*', $signature)[1]; + if (\mb_strlen($signature, '8bit') !== 2 * $partLength) { + throw new \InvalidArgumentException('Invalid length.'); + } + $R = \mb_substr($signature, 0, $partLength, '8bit'); + $S = \mb_substr($signature, $partLength, null, '8bit'); + + $R = self::preparePositiveInteger($R); + $Rl = \mb_strlen($R, '8bit') / 2; + $S = self::preparePositiveInteger($S); + $Sl = \mb_strlen($S, '8bit') / 2; + $der = \pack('H*', + '30'.($Rl + $Sl + 4 > 128 ? '81' : '').\dechex($Rl + $Sl + 4) + .'02'.\dechex($Rl).$R + .'02'.\dechex($Sl).$S + ); + + return $der; + } + + public static function fromDER(string $der, int $partLength): string + { + $hex = \unpack('H*', $der)[1]; + if ('30' !== \mb_substr($hex, 0, 2, '8bit')) { // SEQUENCE + throw new \RuntimeException(); + } + if ('81' === \mb_substr($hex, 2, 2, '8bit')) { // LENGTH > 128 + $hex = \mb_substr($hex, 6, null, '8bit'); + } else { + $hex = \mb_substr($hex, 4, null, '8bit'); + } + if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER + throw new \RuntimeException(); + } + + $Rl = \hexdec(\mb_substr($hex, 2, 2, '8bit')); + $R = self::retrievePositiveInteger(\mb_substr($hex, 4, $Rl * 2, '8bit')); + $R = \str_pad($R, $partLength, '0', STR_PAD_LEFT); + + $hex = \mb_substr($hex, 4 + $Rl * 2, null, '8bit'); + if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER + throw new \RuntimeException(); + } + $Sl = \hexdec(\mb_substr($hex, 2, 2, '8bit')); + $S = self::retrievePositiveInteger(\mb_substr($hex, 4, $Sl * 2, '8bit')); + $S = \str_pad($S, $partLength, '0', STR_PAD_LEFT); + + return \pack('H*', $R.$S); + } + + private static function preparePositiveInteger(string $data): string + { + if (\mb_substr($data, 0, 2, '8bit') > '7f') { + return '00'.$data; + } + while ('00' === \mb_substr($data, 0, 2, '8bit') && \mb_substr($data, 2, 2, '8bit') <= '7f') { + $data = \mb_substr($data, 2, null, '8bit'); + } + + return $data; + } + + private static function retrievePositiveInteger(string $data): string + { + while ('00' === \mb_substr($data, 0, 2, '8bit') && \mb_substr($data, 2, 2, '8bit') > '7f') { + $data = \mb_substr($data, 2, null, '8bit'); + } + + return $data; + } +} diff --git a/vendor/web-token/jwt-core/Util/Hash.php b/vendor/web-token/jwt-core/Util/Hash.php new file mode 100644 index 0000000..105c865 --- /dev/null +++ b/vendor/web-token/jwt-core/Util/Hash.php @@ -0,0 +1,90 @@ +<?php + +declare(strict_types=1); + +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Spomky-Labs + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +namespace Jose\Component\Core\Util; + +/** + * @internal + */ +class Hash +{ + /** + * Hash Parameter. + * + * @var string + */ + private $hash; + + /** + * Hash Length. + * + * @var int + */ + private $length; + + /** + * @return Hash + */ + public static function sha1(): self + { + return new self('sha1', 20); + } + + /** + * @return Hash + */ + public static function sha256(): self + { + return new self('sha256', 32); + } + + /** + * @return Hash + */ + public static function sha384(): self + { + return new self('sha384', 48); + } + + /** + * @return Hash + */ + public static function sha512(): self + { + return new self('sha512', 64); + } + + private function __construct(string $hash, int $length) + { + $this->hash = $hash; + $this->length = $length; + } + + public function getLength(): int + { + return $this->length; + } + + /** + * Compute the HMAC. + */ + public function hash(string $text): string + { + return \hash($this->hash, $text, true); + } + + public function name(): string + { + return $this->hash; + } +} diff --git a/vendor/web-token/jwt-core/Util/JsonConverter.php b/vendor/web-token/jwt-core/Util/JsonConverter.php new file mode 100644 index 0000000..ef95126 --- /dev/null +++ b/vendor/web-token/jwt-core/Util/JsonConverter.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Spomky-Labs + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +namespace Jose\Component\Core\Util; + +final class JsonConverter +{ + public static function encode($payload): string + { + return \json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + public static function decode(string $payload) + { + return \json_decode($payload, true, 512, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } +} diff --git a/vendor/web-token/jwt-core/Util/KeyChecker.php b/vendor/web-token/jwt-core/Util/KeyChecker.php new file mode 100644 index 0000000..09385a4 --- /dev/null +++ b/vendor/web-token/jwt-core/Util/KeyChecker.php @@ -0,0 +1,107 @@ +<?php + +declare(strict_types=1); + +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Spomky-Labs + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +namespace Jose\Component\Core\Util; + +use Jose\Component\Core\JWK; + +/** + * @internal + */ +class KeyChecker +{ + /** + * @throws \InvalidArgumentException + */ + public static function checkKeyUsage(JWK $key, string $usage): bool + { + if ($key->has('use')) { + return self::checkUsage($key, $usage); + } + if ($key->has('key_ops')) { + return self::checkOperation($key, $usage); + } + + return true; + } + + private static function checkOperation(JWK $key, string $usage): bool + { + $ops = $key->get('key_ops'); + if (!\is_array($ops)) { + $ops = [$ops]; + } + switch ($usage) { + case 'verification': + if (!\in_array('verify', $ops, true)) { + throw new \InvalidArgumentException('Key cannot be used to verify a signature'); + } + + return true; + case 'signature': + if (!\in_array('sign', $ops, true)) { + throw new \InvalidArgumentException('Key cannot be used to sign'); + } + + return true; + case 'encryption': + if (!\in_array('encrypt', $ops, true) && !\in_array('wrapKey', $ops, true)) { + throw new \InvalidArgumentException('Key cannot be used to encrypt'); + } + + return true; + case 'decryption': + if (!\in_array('decrypt', $ops, true) && !\in_array('unwrapKey', $ops, true)) { + throw new \InvalidArgumentException('Key cannot be used to decrypt'); + } + + return true; + default: + throw new \InvalidArgumentException('Unsupported key usage.'); + } + } + + private static function checkUsage(JWK $key, string $usage): bool + { + $use = $key->get('use'); + switch ($usage) { + case 'verification': + case 'signature': + if ('sig' !== $use) { + throw new \InvalidArgumentException('Key cannot be used to sign or verify a signature.'); + } + + return true; + case 'encryption': + case 'decryption': + if ('enc' !== $use) { + throw new \InvalidArgumentException('Key cannot be used to encrypt or decrypt.'); + } + + return true; + default: + throw new \InvalidArgumentException('Unsupported key usage.'); + } + } + + public static function checkKeyAlgorithm(JWK $key, string $algorithm) + { + if (!$key->has('alg')) { + return; + } + + if ($key->get('alg') !== $algorithm) { + throw new \InvalidArgumentException(\sprintf('Key is only allowed for algorithm "%s".', $key->get('alg'))); + } + } +} diff --git a/vendor/web-token/jwt-core/Util/RSAKey.php b/vendor/web-token/jwt-core/Util/RSAKey.php new file mode 100644 index 0000000..3670034 --- /dev/null +++ b/vendor/web-token/jwt-core/Util/RSAKey.php @@ -0,0 +1,322 @@ +<?php + +declare(strict_types=1); + +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Spomky-Labs + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +namespace Jose\Component\Core\Util; + +use Base64Url\Base64Url; +use FG\ASN1\Universal\BitString; +use FG\ASN1\Universal\Integer; +use FG\ASN1\Universal\NullObject; +use FG\ASN1\Universal\ObjectIdentifier; +use FG\ASN1\Universal\OctetString; +use FG\ASN1\Universal\Sequence; +use Jose\Component\Core\JWK; + +/** + * @internal + */ +class RSAKey +{ + /** + * @var Sequence + */ + private $sequence; + + /** + * @var bool + */ + private $private = false; + + /** + * @var array + */ + private $values = []; + + /** + * @var BigInteger + */ + private $modulus; + + /** + * @var int + */ + private $modulus_length; + + /** + * @var BigInteger + */ + private $public_exponent; + + /** + * @var BigInteger|null + */ + private $private_exponent = null; + + /** + * @var BigInteger[] + */ + private $primes = []; + + /** + * @var BigInteger[] + */ + private $exponents = []; + + /** + * @var BigInteger|null + */ + private $coefficient = null; + + private function __construct(JWK $data) + { + $this->loadJWK($data->all()); + $this->populateBigIntegers(); + $this->private = \array_key_exists('d', $this->values); + } + + /** + * @return RSAKey + */ + public static function createFromJWK(JWK $jwk): self + { + return new self($jwk); + } + + public function getModulus(): BigInteger + { + return $this->modulus; + } + + public function getModulusLength(): int + { + return $this->modulus_length; + } + + public function getExponent(): BigInteger + { + $d = $this->getPrivateExponent(); + if (null !== $d) { + return $d; + } + + return $this->getPublicExponent(); + } + + public function getPublicExponent(): BigInteger + { + return $this->public_exponent; + } + + public function getPrivateExponent(): ?BigInteger + { + return $this->private_exponent; + } + + /** + * @return BigInteger[] + */ + public function getPrimes(): array + { + return $this->primes; + } + + /** + * @return BigInteger[] + */ + public function getExponents(): array + { + return $this->exponents; + } + + public function getCoefficient(): ?BigInteger + { + return $this->coefficient; + } + + public function isPublic(): bool + { + return !\array_key_exists('d', $this->values); + } + + /** + * @param RSAKey $private + * + * @return RSAKey + */ + public static function toPublic(self $private): self + { + $data = $private->toArray(); + $keys = ['p', 'd', 'q', 'dp', 'dq', 'qi']; + foreach ($keys as $key) { + if (\array_key_exists($key, $data)) { + unset($data[$key]); + } + } + + return new self(new JWK($data)); + } + + public function toArray(): array + { + return $this->values; + } + + private function loadJWK(array $jwk) + { + if (!\array_key_exists('kty', $jwk)) { + throw new \InvalidArgumentException('The key parameter "kty" is missing.'); + } + if ('RSA' !== $jwk['kty']) { + throw new \InvalidArgumentException('The JWK is not a RSA key.'); + } + + $this->values = $jwk; + } + + private function populateBigIntegers() + { + $this->modulus = $this->convertBase64StringToBigInteger($this->values['n']); + $this->modulus_length = \mb_strlen($this->getModulus()->toBytes(), '8bit'); + $this->public_exponent = $this->convertBase64StringToBigInteger($this->values['e']); + + if (!$this->isPublic()) { + $this->private_exponent = $this->convertBase64StringToBigInteger($this->values['d']); + + if (\array_key_exists('p', $this->values) && \array_key_exists('q', $this->values)) { + $this->primes = [ + $this->convertBase64StringToBigInteger($this->values['p']), + $this->convertBase64StringToBigInteger($this->values['q']), + ]; + if (\array_key_exists('dp', $this->values) && \array_key_exists('dq', $this->values) && \array_key_exists('qi', $this->values)) { + $this->exponents = [ + $this->convertBase64StringToBigInteger($this->values['dp']), + $this->convertBase64StringToBigInteger($this->values['dq']), + ]; + $this->coefficient = $this->convertBase64StringToBigInteger($this->values['qi']); + } + } + } + } + + private function convertBase64StringToBigInteger(string $value): BigInteger + { + return BigInteger::createFromBinaryString(Base64Url::decode($value)); + } + + /** + * @throws \Exception + */ + public function toPEM(): string + { + if (null === $this->sequence) { + $this->sequence = new Sequence(); + if (\array_key_exists('d', $this->values)) { + $this->initPrivateKey(); + } else { + $this->initPublicKey(); + } + } + $result = '-----BEGIN '.($this->private ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL; + $result .= \chunk_split(\base64_encode($this->sequence->getBinary()), 64, PHP_EOL); + $result .= '-----END '.($this->private ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL; + + return $result; + } + + /** + * @throws \Exception + */ + private function initPublicKey() + { + $oid_sequence = new Sequence(); + $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1')); + $oid_sequence->addChild(new NullObject()); + $this->sequence->addChild($oid_sequence); + $n = new Integer($this->fromBase64ToInteger($this->values['n'])); + $e = new Integer($this->fromBase64ToInteger($this->values['e'])); + $key_sequence = new Sequence(); + $key_sequence->addChild($n); + $key_sequence->addChild($e); + $key_bit_string = new BitString(\bin2hex($key_sequence->getBinary())); + $this->sequence->addChild($key_bit_string); + } + + private function initPrivateKey() + { + $this->sequence->addChild(new Integer(0)); + $oid_sequence = new Sequence(); + $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1')); + $oid_sequence->addChild(new NullObject()); + $this->sequence->addChild($oid_sequence); + $v = new Integer(0); + $n = new Integer($this->fromBase64ToInteger($this->values['n'])); + $e = new Integer($this->fromBase64ToInteger($this->values['e'])); + $d = new Integer($this->fromBase64ToInteger($this->values['d'])); + $p = new Integer($this->fromBase64ToInteger($this->values['p'])); + $q = new Integer($this->fromBase64ToInteger($this->values['q'])); + $dp = \array_key_exists('dp', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dp'])) : new Integer(0); + $dq = \array_key_exists('dq', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dq'])) : new Integer(0); + $qi = \array_key_exists('qi', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['qi'])) : new Integer(0); + $key_sequence = new Sequence(); + $key_sequence->addChild($v); + $key_sequence->addChild($n); + $key_sequence->addChild($e); + $key_sequence->addChild($d); + $key_sequence->addChild($p); + $key_sequence->addChild($q); + $key_sequence->addChild($dp); + $key_sequence->addChild($dq); + $key_sequence->addChild($qi); + $key_octet_string = new OctetString(\bin2hex($key_sequence->getBinary())); + $this->sequence->addChild($key_octet_string); + } + + /** + * @param string $value + * + * @return string + */ + private function fromBase64ToInteger($value) + { + return \gmp_strval(\gmp_init(\current(\unpack('H*', Base64Url::decode($value))), 16), 10); + } + + /** + * Exponentiate with or without Chinese Remainder Theorem. + * Operation with primes 'p' and 'q' is appox. 2x faster. + * + * @param RSAKey $key + */ + public static function exponentiate(self $key, BigInteger $c): BigInteger + { + if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare($key->getModulus()) > 0) { + throw new \RuntimeException(); + } + if ($key->isPublic() || empty($key->getPrimes()) || empty($key->getExponents()) || null === $key->getCoefficient()) { + return $c->modPow($key->getExponent(), $key->getModulus()); + } + + $p = $key->getPrimes()[0]; + $q = $key->getPrimes()[1]; + $dP = $key->getExponents()[0]; + $dQ = $key->getExponents()[1]; + $qInv = $key->getCoefficient(); + + $m1 = $c->modPow($dP, $p); + $m2 = $c->modPow($dQ, $q); + $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p); + $m = $m2->add($h->multiply($q)); + + return $m; + } +} |