added predis and eseye back in.

This commit is contained in:
2020-12-25 11:28:41 +00:00
parent 0ddd298350
commit 017f72b42e
670 changed files with 60992 additions and 10 deletions

View File

@@ -0,0 +1,223 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 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 Brick\Math\BigInteger as BrickBigInteger;
use function chr;
/**
* @internal
*/
class BigInteger
{
/**
* Holds the BigInteger's value.
*
* @var BrickBigInteger
*/
private $value;
private function __construct(BrickBigInteger $value)
{
$this->value = $value;
}
/**
* @return BigInteger
*/
public static function createFromBinaryString(string $value): self
{
$data = current(unpack('H*', $value));
return new self(BrickBigInteger::fromBase($data, 16));
}
/**
* @return BigInteger
*/
public static function createFromDecimal(int $value): self
{
return new self(BrickBigInteger::of($value));
}
/**
* @return BigInteger
*/
public static function createFromBigInteger(BrickBigInteger $value): self
{
return new self($value);
}
/**
* Converts a BigInteger to a binary string.
*/
public function toBytes(): string
{
if ($this->value->isEqualTo(BrickBigInteger::zero())) {
return '';
}
$temp = $this->value->toBase(16);
$temp = 0 !== (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 = $this->value->plus($y->value);
return new self($value);
}
/**
* Subtracts two BigIntegers.
*
* @param BigInteger $y
*
* @return BigInteger
*/
public function subtract(self $y): self
{
$value = $this->value->minus($y->value);
return new self($value);
}
/**
* Multiplies two BigIntegers.
*
* @param BigInteger $x
*
* @return BigInteger
*/
public function multiply(self $x): self
{
$value = $this->value->multipliedBy($x->value);
return new self($value);
}
/**
* Divides two BigIntegers.
*
* @param BigInteger $x
*
* @return BigInteger
*/
public function divide(self $x): self
{
$value = $this->value->dividedBy($x->value);
return new self($value);
}
/**
* Performs modular exponentiation.
*
* @param BigInteger $e
* @param BigInteger $n
*
* @return BigInteger
*/
public function modPow(self $e, self $n): self
{
$value = $this->value->modPow($e->value, $n->value);
return new self($value);
}
/**
* Performs modular exponentiation.
*
* @param BigInteger $d
*
* @return BigInteger
*/
public function mod(self $d): self
{
$value = $this->value->mod($d->value);
return new self($value);
}
public function modInverse(BigInteger $m): BigInteger
{
return new self($this->value->modInverse($m->value));
}
/**
* Compares two numbers.
*
* @param BigInteger $y
*/
public function compare(self $y): int
{
return $this->value->compareTo($y->value);
}
/**
* @param BigInteger $y
*/
public function equals(self $y): bool
{
return $this->value->isEqualTo($y->value);
}
/**
* @param BigInteger $y
*
* @return BigInteger
*/
public static function random(self $y): self
{
return new self(BrickBigInteger::randomRange(0, $y->value));
}
/**
* @param BigInteger $y
*
* @return BigInteger
*/
public function gcd(self $y): self
{
return new self($this->value->gcd($y->value));
}
/**
* @param BigInteger $y
*/
public function lowerThan(self $y): bool
{
return $this->value->isLessThan($y->value);
}
public function isEven(): bool
{
return $this->value->isEven();
}
public function get(): BrickBigInteger
{
return $this->value;
}
}

335
vendor/web-token/jwt-core/Util/ECKey.php vendored Normal file
View File

@@ -0,0 +1,335 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 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 function extension_loaded;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
use RuntimeException;
/**
* @internal
*/
class ECKey
{
public static function convertToPEM(JWK $jwk): string
{
if ($jwk->has('d')) {
return self::convertPrivateKeyToPEM($jwk);
}
return self::convertPublicKeyToPEM($jwk);
}
/**
* @throws InvalidArgumentException if the curve is not supported
*/
public static function convertPublicKeyToPEM(JWK $jwk): string
{
switch ($jwk->get('crv')) {
case 'P-256':
$der = self::p256PublicKey();
break;
case 'secp256k1':
$der = self::p256KPublicKey();
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;
}
/**
* @throws InvalidArgumentException if the curve is not supported
*/
public static function convertPrivateKeyToPEM(JWK $jwk): string
{
switch ($jwk->get('crv')) {
case 'P-256':
$der = self::p256PrivateKey($jwk);
break;
case 'secp256k1':
$der = self::p256KPrivateKey($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
{
$jwk = self::createECKeyUsingOpenSSL($curve);
$values = array_merge($values, $jwk);
return new JWK($values);
}
/**
* @throws InvalidArgumentException if the curve is not supported
*/
private static function getNistCurveSize(string $curve): int
{
switch ($curve) {
case 'P-256':
case 'secp256k1':
return 256;
case 'P-384':
return 384;
case 'P-521':
return 521;
default:
throw new InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
}
}
/**
* @throws RuntimeException if the extension OpenSSL is not available
* @throws RuntimeException if the key cannot be created
*/
private static function createECKeyUsingOpenSSL(string $curve): array
{
if (!extension_loaded('openssl')) {
throw new RuntimeException('Please install the OpenSSL extension');
}
$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)),
];
}
/**
* @throws InvalidArgumentException if the curve is not supported
*/
private static function getOpensslCurveName(string $curve): string
{
switch ($curve) {
case 'P-256':
return 'prime256v1';
case 'secp256k1':
return 'secp256k1';
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 p256KPublicKey(): string
{
return pack(
'H*',
'3056' // SEQUENCE, length 86
.'3010' // SEQUENCE, length 16
.'0607' // OID, length 7
.'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
.'0605' // OID, length 8
.'2B8104000A' // 1.3.132.0.10 secp256k1
.'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-256 Curve
.'a144' // TAGGED OBJECT #1, length 68
.'0342' // BIT STRING, length 66
.'00' // prepend with NUL - pubkey will follow
);
}
private static function p256KPrivateKey(JWK $jwk): string
{
$d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 32, "\0", STR_PAD_LEFT))[1];
return pack(
'H*',
'3074' // SEQUENCE, length 84+length($d)=32
.'020101' // INTEGER, 1
.'0420' // OCTET STRING, length($d) = 32
.$d
.'a007' // TAGGED OBJECT #0, length 7
.'0605' // OID, length 5
.'2b8104000a' // 1.3.132.0.10 secp256k1
.'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);
}
}

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 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 InvalidArgumentException;
use const STR_PAD_LEFT;
/**
* @internal
*/
final class ECSignature
{
private const ASN1_SEQUENCE = '30';
private const ASN1_INTEGER = '02';
private const ASN1_MAX_SINGLE_BYTE = 128;
private const ASN1_LENGTH_2BYTES = '81';
private const ASN1_BIG_INTEGER_LIMIT = '7f';
private const ASN1_NEGATIVE_INTEGER = '00';
private const BYTE_SIZE = 2;
/**
* @throws InvalidArgumentException if the length of the signature is invalid
*/
public static function toAsn1(string $signature, int $length): string
{
$signature = bin2hex($signature);
if (self::octetLength($signature) !== $length) {
throw new InvalidArgumentException('Invalid signature length.');
}
$pointR = self::preparePositiveInteger(mb_substr($signature, 0, $length, '8bit'));
$pointS = self::preparePositiveInteger(mb_substr($signature, $length, null, '8bit'));
$lengthR = self::octetLength($pointR);
$lengthS = self::octetLength($pointS);
$totalLength = $lengthR + $lengthS + self::BYTE_SIZE + self::BYTE_SIZE;
$lengthPrefix = $totalLength > self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : '';
return hex2bin(
self::ASN1_SEQUENCE
.$lengthPrefix.dechex($totalLength)
.self::ASN1_INTEGER.dechex($lengthR).$pointR
.self::ASN1_INTEGER.dechex($lengthS).$pointS
);
}
/**
* @throws InvalidArgumentException if the signature is not an ASN.1 sequence
*/
public static function fromAsn1(string $signature, int $length): string
{
$message = bin2hex($signature);
$position = 0;
if (self::ASN1_SEQUENCE !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
throw new InvalidArgumentException('Invalid data. Should start with a sequence.');
}
if (self::ASN1_LENGTH_2BYTES === self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
$position += self::BYTE_SIZE;
}
$pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
$pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
return hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT).str_pad($pointS, $length, '0', STR_PAD_LEFT));
}
private static function octetLength(string $data): int
{
return (int) (mb_strlen($data, '8bit') / self::BYTE_SIZE);
}
private static function preparePositiveInteger(string $data): string
{
if (mb_substr($data, 0, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) {
return self::ASN1_NEGATIVE_INTEGER.$data;
}
while (0 === mb_strpos($data, self::ASN1_NEGATIVE_INTEGER, 0, '8bit')
&& mb_substr($data, 2, self::BYTE_SIZE, '8bit') <= self::ASN1_BIG_INTEGER_LIMIT) {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
private static function readAsn1Content(string $message, int &$position, int $length): string
{
$content = mb_substr($message, $position, $length, '8bit');
$position += $length;
return $content;
}
/**
* @throws InvalidArgumentException if the data is not an integer
*/
private static function readAsn1Integer(string $message, int &$position): string
{
if (self::ASN1_INTEGER !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
throw new InvalidArgumentException('Invalid data. Should contain an integer.');
}
$length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE));
return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE);
}
private static function retrievePositiveInteger(string $data): string
{
while (0 === mb_strpos($data, self::ASN1_NEGATIVE_INTEGER, 0, '8bit')
&& mb_substr($data, 2, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
}

103
vendor/web-token/jwt-core/Util/Hash.php vendored Normal file
View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 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;
/**
* DER encoding T.
*
* @var string
*/
private $t;
/**
* Hash Length.
*
* @var int
*/
private $length;
private function __construct(string $hash, int $length, string $t)
{
$this->hash = $hash;
$this->length = $length;
$this->t = $t;
}
/**
* @return Hash
*/
public static function sha1(): self
{
return new self('sha1', 20, "\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14");
}
/**
* @return Hash
*/
public static function sha256(): self
{
return new self('sha256', 32, "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20");
}
/**
* @return Hash
*/
public static function sha384(): self
{
return new self('sha384', 48, "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30");
}
/**
* @return Hash
*/
public static function sha512(): self
{
return new self('sha512', 64, "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40");
}
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;
}
public function t(): string
{
return $this->t;
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 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 RuntimeException;
use Throwable;
final class JsonConverter
{
/**
* @param mixed $payload
*
* @throws RuntimeException if the payload cannot be encoded
*/
public static function encode($payload): string
{
try {
return json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (Throwable $throwable) {
throw new RuntimeException('Invalid content.', $throwable->getCode(), $throwable);
}
}
/**
* @throws RuntimeException if the payload cannot be decoded
*
* @return mixed
*/
public static function decode(string $payload)
{
try {
return json_decode($payload, true, 512, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (Throwable $throwable) {
throw new RuntimeException('Invalid content.', $throwable->getCode(), $throwable);
}
}
}

View File

@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 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 function in_array;
use InvalidArgumentException;
use function is_array;
use Jose\Component\Core\JWK;
/**
* @internal
*/
class KeyChecker
{
public static function checkKeyUsage(JWK $key, string $usage): void
{
if ($key->has('use')) {
self::checkUsage($key, $usage);
}
if ($key->has('key_ops')) {
self::checkOperation($key, $usage);
}
}
/**
* @throws InvalidArgumentException if the key is not suitable for the selected algorithm
*/
public static function checkKeyAlgorithm(JWK $key, string $algorithm): void
{
if (!$key->has('alg')) {
return;
}
if ($key->get('alg') !== $algorithm) {
throw new InvalidArgumentException(sprintf('Key is only allowed for algorithm "%s".', $key->get('alg')));
}
}
/**
* @throws InvalidArgumentException if the key is not suitable for the selected operation
*/
private static function checkOperation(JWK $key, string $usage): void
{
$ops = $key->get('key_ops');
if (!is_array($ops)) {
throw new InvalidArgumentException('Invalid key parameter "key_ops". Should be a list of key operations');
}
switch ($usage) {
case 'verification':
if (!in_array('verify', $ops, true)) {
throw new InvalidArgumentException('Key cannot be used to verify a signature');
}
break;
case 'signature':
if (!in_array('sign', $ops, true)) {
throw new InvalidArgumentException('Key cannot be used to sign');
}
break;
case 'encryption':
if (!in_array('encrypt', $ops, true) && !in_array('wrapKey', $ops, true) && !in_array('deriveKey', $ops, true)) {
throw new InvalidArgumentException('Key cannot be used to encrypt');
}
break;
case 'decryption':
if (!in_array('decrypt', $ops, true) && !in_array('unwrapKey', $ops, true) && !in_array('deriveBits', $ops, true)) {
throw new InvalidArgumentException('Key cannot be used to decrypt');
}
break;
default:
throw new InvalidArgumentException('Unsupported key usage.');
}
}
/**
* @throws InvalidArgumentException if the key is not suitable for the selected operation
*/
private static function checkUsage(JWK $key, string $usage): void
{
$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.');
}
break;
case 'encryption':
case 'decryption':
if ('enc' !== $use) {
throw new InvalidArgumentException('Key cannot be used to encrypt or decrypt.');
}
break;
default:
throw new InvalidArgumentException('Unsupported key usage.');
}
}
}

View File

@@ -0,0 +1,310 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2020 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 function array_key_exists;
use Base64Url\Base64Url;
use function count;
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;
use RuntimeException;
/**
* @internal
*/
class RSAKey
{
/**
* @var Sequence
*/
private $sequence;
/**
* @var bool
*/
private $private;
/**
* @var array
*/
private $values;
/**
* @var BigInteger
*/
private $modulus;
/**
* @var int
*/
private $modulus_length;
/**
* @var BigInteger
*/
private $public_exponent;
/**
* @var null|BigInteger
*/
private $private_exponent;
/**
* @var BigInteger[]
*/
private $primes = [];
/**
* @var BigInteger[]
*/
private $exponents = [];
/**
* @var null|BigInteger
*/
private $coefficient;
private function __construct(JWK $data)
{
$this->values = $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;
}
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;
}
/**
* Exponentiate with or without Chinese Remainder Theorem.
* Operation with primes 'p' and 'q' is appox. 2x faster.
*
* @param RSAKey $key
*
* @throws RuntimeException if the exponentiation cannot be achieved
*/
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() || null === $key->getCoefficient() || 0 === count($key->getPrimes()) || 0 === count($key->getExponents())) {
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);
return $m2->add($h->multiply($q));
}
private function populateBigIntegers(): void
{
$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));
}
private function initPublicKey(): void
{
$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(): void
{
$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)
{
$hex = current(unpack('H*', Base64Url::decode($value)));
return \Brick\Math\BigInteger::fromBase($hex, 16)->toBase(10);
}
}