316 lines
8.9 KiB
PHP
316 lines
8.9 KiB
PHP
<?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 InvalidArgumentException;
|
|
use function is_array;
|
|
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)
|
|
{
|
|
$unpacked = unpack('H*', Base64Url::decode($value));
|
|
if (!is_array($unpacked) || 0 === count($unpacked)) {
|
|
throw new InvalidArgumentException('Unable to get the private key');
|
|
}
|
|
|
|
return \Brick\Math\BigInteger::fromBase(current($unpacked), 16)->toBase(10);
|
|
}
|
|
}
|