primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
use Jose\Component\Core\Util\ECKey;
use Jose\Component\Core\Util\ECSignature;
use LogicException;
use RuntimeException;
use Throwable;
use function defined;
use function extension_loaded;
use function in_array;
abstract class ECDSA implements SignatureAlgorithm
{
public function __construct()
{
if (! extension_loaded('openssl')) {
throw new RuntimeException('Please install the OpenSSL extension');
}
if (! defined('OPENSSL_KEYTYPE_EC')) {
throw new LogicException('Elliptic Curve key type not supported by your environment.');
}
}
public function allowedKeyTypes(): array
{
return ['EC'];
}
public function sign(JWK $key, string $input): string
{
$this->checkKey($key);
if (! $key->has('d')) {
throw new InvalidArgumentException('The EC key is not private');
}
$pem = ECKey::convertPrivateKeyToPEM($key);
openssl_sign($input, $signature, $pem, $this->getHashAlgorithm());
return ECSignature::fromAsn1($signature, $this->getSignaturePartLength());
}
public function verify(JWK $key, string $input, string $signature): bool
{
$this->checkKey($key);
try {
$der = ECSignature::toAsn1($signature, $this->getSignaturePartLength());
$pem = ECKey::convertPublicKeyToPEM($key);
return openssl_verify($input, $der, $pem, $this->getHashAlgorithm()) === 1;
} catch (Throwable) {
return false;
}
}
abstract protected function getHashAlgorithm(): string;
abstract protected function getSignaturePartLength(): int;
private function checkKey(JWK $key): void
{
if (! in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
throw new InvalidArgumentException('Wrong key type.');
}
foreach (['x', 'y', 'crv'] as $k) {
if (! $key->has($k)) {
throw new InvalidArgumentException(sprintf('The key parameter "%s" is missing.', $k));
}
}
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
final class ES256 extends ECDSA
{
public function name(): string
{
return 'ES256';
}
protected function getHashAlgorithm(): string
{
return 'sha256';
}
protected function getSignaturePartLength(): int
{
return 64;
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
final class ES384 extends ECDSA
{
public function name(): string
{
return 'ES384';
}
protected function getHashAlgorithm(): string
{
return 'sha384';
}
protected function getSignaturePartLength(): int
{
return 96;
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
final class ES512 extends ECDSA
{
public function name(): string
{
return 'ES512';
}
protected function getHashAlgorithm(): string
{
return 'sha512';
}
protected function getSignaturePartLength(): int
{
return 132;
}
}

View File

@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
use Jose\Component\Core\Util\Base64UrlSafe;
use ParagonIE\Sodium\Core\Ed25519;
use RuntimeException;
use function assert;
use function extension_loaded;
use function in_array;
use function is_string;
final class EdDSA implements SignatureAlgorithm
{
public function __construct()
{
if (! extension_loaded('sodium')) {
throw new RuntimeException('The extension "sodium" is not available. Please install it to use this method');
}
}
public function allowedKeyTypes(): array
{
return ['OKP'];
}
/**
* @return non-empty-string
*/
public function sign(JWK $key, string $input): string
{
$this->checkKey($key);
if (! $key->has('d')) {
throw new InvalidArgumentException('The EC key is not private');
}
$d = $key->get('d');
if (! is_string($d) || $d === '') {
throw new InvalidArgumentException('Invalid "d" parameter.');
}
if (! $key->has('x')) {
$x = self::getPublicKey($key);
} else {
$x = $key->get('x');
}
if (! is_string($x) || $x === '') {
throw new InvalidArgumentException('Invalid "x" parameter.');
}
/** @var non-empty-string $x */
$x = Base64UrlSafe::decodeNoPadding($x);
/** @var non-empty-string $d */
$d = Base64UrlSafe::decodeNoPadding($d);
$secret = $d . $x;
return match ($key->get('crv')) {
'Ed25519' => sodium_crypto_sign_detached($input, $secret),
default => throw new InvalidArgumentException('Unsupported curve'),
};
}
/**
* @param non-empty-string $signature
*/
public function verify(JWK $key, string $input, string $signature): bool
{
$this->checkKey($key);
$x = $key->get('x');
if (! is_string($x)) {
throw new InvalidArgumentException('Invalid "x" parameter.');
}
/** @var non-empty-string $public */
$public = Base64UrlSafe::decodeNoPadding($x);
return match ($key->get('crv')) {
'Ed25519' => sodium_crypto_sign_verify_detached($signature, $input, $public),
default => throw new InvalidArgumentException('Unsupported curve'),
};
}
public function name(): string
{
return 'EdDSA';
}
private static function getPublicKey(JWK $key): string
{
$d = $key->get('d');
assert(is_string($d), 'Unsupported key type');
switch ($key->get('crv')) {
case 'Ed25519':
return Ed25519::publickey_from_secretkey($d);
case 'X25519':
if (extension_loaded('sodium')) {
return sodium_crypto_scalarmult_base($d);
}
// no break
default:
throw new InvalidArgumentException('Unsupported key type');
}
}
private function checkKey(JWK $key): void
{
if (! in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
throw new InvalidArgumentException('Wrong key type.');
}
foreach (['x', 'crv'] as $k) {
if (! $key->has($k)) {
throw new InvalidArgumentException(sprintf('The key parameter "%s" is missing.', $k));
}
}
if ($key->get('crv') !== 'Ed25519') {
throw new InvalidArgumentException('Unsupported curve.');
}
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
use Jose\Component\Core\Util\Base64UrlSafe;
use function in_array;
use function is_string;
abstract class HMAC implements MacAlgorithm
{
public function allowedKeyTypes(): array
{
return ['oct'];
}
public function verify(JWK $key, string $input, string $signature): bool
{
return hash_equals($this->hash($key, $input), $signature);
}
public function hash(JWK $key, string $input): string
{
$k = $this->getKey($key);
return hash_hmac($this->getHashAlgorithm(), $input, $k, true);
}
protected function getKey(JWK $key): string
{
if (! in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
throw new InvalidArgumentException('Wrong key type.');
}
if (! $key->has('k')) {
throw new InvalidArgumentException('The key parameter "k" is missing.');
}
$k = $key->get('k');
if (! is_string($k)) {
throw new InvalidArgumentException('The key parameter "k" is invalid.');
}
return Base64UrlSafe::decodeNoPadding($k);
}
abstract protected function getHashAlgorithm(): string;
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
final class HS256 extends HMAC
{
public function name(): string
{
return 'HS256';
}
protected function getHashAlgorithm(): string
{
return 'sha256';
}
protected function getKey(JWK $key): string
{
$k = parent::getKey($key);
if (mb_strlen($k, '8bit') < 32) {
throw new InvalidArgumentException('Invalid key length.');
}
return $k;
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
final class HS384 extends HMAC
{
public function name(): string
{
return 'HS384';
}
protected function getHashAlgorithm(): string
{
return 'sha384';
}
protected function getKey(JWK $key): string
{
$k = parent::getKey($key);
if (mb_strlen($k, '8bit') < 48) {
throw new InvalidArgumentException('Invalid key length.');
}
return $k;
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
final class HS512 extends HMAC
{
public function name(): string
{
return 'HS512';
}
protected function getHashAlgorithm(): string
{
return 'sha512';
}
protected function getKey(JWK $key): string
{
$k = parent::getKey($key);
if (mb_strlen($k, '8bit') < 64) {
throw new InvalidArgumentException('Invalid key length.');
}
return $k;
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
use Jose\Component\Core\Algorithm;
use Jose\Component\Core\JWK;
interface MacAlgorithm extends Algorithm
{
/**
* Sign the input.
*
* @param JWK $key The private key used to hash the data
* @param string $input The input
*/
public function hash(JWK $key, string $input): string;
/**
* Verify the signature of data.
*
* @param JWK $key The private key used to hash the data
* @param string $input The input
* @param string $signature The signature to verify
*/
public function verify(JWK $key, string $input, string $signature): bool;
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
use function in_array;
final class None implements SignatureAlgorithm
{
public function allowedKeyTypes(): array
{
return ['none'];
}
public function sign(JWK $key, string $input): string
{
$this->checkKey($key);
return '';
}
public function verify(JWK $key, string $input, string $signature): bool
{
return $signature === '';
}
public function name(): string
{
return 'none';
}
private function checkKey(JWK $key): void
{
if (! in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
throw new InvalidArgumentException('Wrong key type.');
}
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
final class PS256 extends RSAPSS
{
public function name(): string
{
return 'PS256';
}
protected function getAlgorithm(): string
{
return 'sha256';
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
final class PS384 extends RSAPSS
{
public function name(): string
{
return 'PS384';
}
protected function getAlgorithm(): string
{
return 'sha384';
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
final class PS512 extends RSAPSS
{
public function name(): string
{
return 'PS512';
}
protected function getAlgorithm(): string
{
return 'sha512';
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
final class RS256 extends RSAPKCS1
{
public function name(): string
{
return 'RS256';
}
protected function getAlgorithm(): string
{
return 'sha256';
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
final class RS384 extends RSAPKCS1
{
public function name(): string
{
return 'RS384';
}
protected function getAlgorithm(): string
{
return 'sha384';
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
final class RS512 extends RSAPKCS1
{
public function name(): string
{
return 'RS512';
}
protected function getAlgorithm(): string
{
return 'sha512';
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
use Jose\Component\Core\Util\RSAKey;
use RuntimeException;
use function extension_loaded;
use function in_array;
abstract class RSAPKCS1 implements SignatureAlgorithm
{
public function __construct()
{
if (! extension_loaded('openssl')) {
throw new RuntimeException('Please install the OpenSSL extension');
}
}
public function allowedKeyTypes(): array
{
return ['RSA'];
}
public function verify(JWK $key, string $input, string $signature): bool
{
$this->checkKey($key);
$pub = RSAKey::createFromJWK($key->toPublic());
return openssl_verify($input, $signature, $pub->toPEM(), $this->getAlgorithm()) === 1;
}
public function sign(JWK $key, string $input): string
{
$this->checkKey($key);
if (! $key->has('d')) {
throw new InvalidArgumentException('The key is not a private key.');
}
$priv = RSAKey::createFromJWK($key);
$result = openssl_sign($input, $signature, $priv->toPEM(), $this->getAlgorithm());
if ($result !== true) {
throw new RuntimeException('Unable to sign');
}
return $signature;
}
abstract protected function getAlgorithm(): string;
private function checkKey(JWK $key): void
{
if (! in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
throw new InvalidArgumentException('Wrong key type.');
}
foreach (['n', 'e'] as $k) {
if (! $key->has($k)) {
throw new InvalidArgumentException(sprintf('The key parameter "%s" is missing.', $k));
}
}
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
use Jose\Component\Core\Util\RSAKey;
use Jose\Component\Signature\Algorithm\Util\RSA as JoseRSA;
use function in_array;
abstract class RSAPSS implements SignatureAlgorithm
{
public function allowedKeyTypes(): array
{
return ['RSA'];
}
public function verify(JWK $key, string $input, string $signature): bool
{
$this->checkKey($key);
$pub = RSAKey::createFromJWK($key->toPublic());
return JoseRSA::verify($pub, $input, $signature, $this->getAlgorithm(), JoseRSA::SIGNATURE_PSS);
}
/**
* @return non-empty-string
*/
public function sign(JWK $key, string $input): string
{
$this->checkKey($key);
if (! $key->has('d')) {
throw new InvalidArgumentException('The key is not a private key.');
}
$priv = RSAKey::createFromJWK($key);
return JoseRSA::sign($priv, $input, $this->getAlgorithm(), JoseRSA::SIGNATURE_PSS);
}
abstract protected function getAlgorithm(): string;
private function checkKey(JWK $key): void
{
if (! in_array($key->get('kty'), $this->allowedKeyTypes(), true)) {
throw new InvalidArgumentException('Wrong key type.');
}
foreach (['n', 'e'] as $k) {
if (! $key->has($k)) {
throw new InvalidArgumentException(sprintf('The key parameter "%s" is missing.', $k));
}
}
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm;
use Jose\Component\Core\Algorithm;
use Jose\Component\Core\JWK;
interface SignatureAlgorithm extends Algorithm
{
/**
* Sign the input.
*
* @param JWK $key The private key used to sign the data
* @param string $input The input
*/
public function sign(JWK $key, string $input): string;
/**
* Verify the signature of data.
*
* @param JWK $key The private key used to sign the data
* @param non-empty-string $input The input
* @param non-empty-string $signature The signature to verify
*/
public function verify(JWK $key, string $input, string $signature): bool;
}

View File

@ -0,0 +1,190 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Signature\Algorithm\Util;
use InvalidArgumentException;
use Jose\Component\Core\Util\BigInteger;
use Jose\Component\Core\Util\Hash;
use Jose\Component\Core\Util\RSAKey;
use RuntimeException;
use function chr;
use function extension_loaded;
use function ord;
use const STR_PAD_LEFT;
/**
* @internal
*/
final class RSA
{
/**
* Probabilistic Signature Scheme.
*/
public const SIGNATURE_PSS = 1;
/**
* Use the PKCS#1.
*/
public const SIGNATURE_PKCS1 = 2;
/**
* @return non-empty-string
*/
public static function sign(RSAKey $key, string $message, string $hash, int $mode): string
{
switch ($mode) {
case self::SIGNATURE_PSS:
return self::signWithPSS($key, $message, $hash);
case self::SIGNATURE_PKCS1:
if (! extension_loaded('openssl')) {
throw new RuntimeException('Please install the OpenSSL extension');
}
$result = openssl_sign($message, $signature, $key->toPEM(), $hash);
if ($result !== true) {
throw new RuntimeException('Unable to sign the data');
}
return $signature;
default:
throw new InvalidArgumentException('Unsupported mode.');
}
}
/**
* Create a signature.
*
* @return non-empty-string
*/
public static function signWithPSS(RSAKey $key, string $message, string $hash): string
{
$em = self::encodeEMSAPSS($message, 8 * $key->getModulusLength() - 1, Hash::$hash());
$message = BigInteger::createFromBinaryString($em);
$signature = RSAKey::exponentiate($key, $message);
$result = self::convertIntegerToOctetString($signature, $key->getModulusLength());
if ($result === '') {
throw new InvalidArgumentException('Invalid signature.');
}
return $result;
}
public static function verify(RSAKey $key, string $message, string $signature, string $hash, int $mode): bool
{
switch ($mode) {
case self::SIGNATURE_PSS:
return self::verifyWithPSS($key, $message, $signature, $hash);
case self::SIGNATURE_PKCS1:
if (! extension_loaded('openssl')) {
throw new RuntimeException('Please install the OpenSSL extension');
}
return openssl_verify($message, $signature, $key->toPEM(), $hash) === 1;
default:
throw new InvalidArgumentException('Unsupported mode.');
}
}
/**
* Verifies a signature.
*/
public static function verifyWithPSS(RSAKey $key, string $message, string $signature, string $hash): bool
{
if (mb_strlen($signature, '8bit') !== $key->getModulusLength()) {
throw new RuntimeException();
}
$s2 = BigInteger::createFromBinaryString($signature);
$m2 = RSAKey::exponentiate($key, $s2);
$em = self::convertIntegerToOctetString($m2, $key->getModulusLength());
$modBits = 8 * $key->getModulusLength();
return self::verifyEMSAPSS($message, $em, $modBits - 1, Hash::$hash());
}
private static function convertIntegerToOctetString(BigInteger $x, int $xLen): string
{
$x = $x->toBytes();
if (mb_strlen($x, '8bit') > $xLen) {
throw new RuntimeException();
}
return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
}
/**
* MGF1.
*/
private static function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string
{
$t = '';
$count = ceil($maskLen / $mgfHash->getLength());
for ($i = 0; $i < $count; ++$i) {
$c = pack('N', $i);
$t .= $mgfHash->hash($mgfSeed . $c);
}
return mb_substr($t, 0, $maskLen, '8bit');
}
/**
* EMSA-PSS-ENCODE.
*/
private static function encodeEMSAPSS(string $message, int $modulusLength, Hash $hash): string
{
$emLen = ($modulusLength + 1) >> 3;
$sLen = $hash->getLength();
$mHash = $hash->hash($message);
if ($emLen <= $hash->getLength() + $sLen + 2) {
throw new RuntimeException();
}
$salt = random_bytes($sLen);
$m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
$h = $hash->hash($m2);
$ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2);
$db = $ps . chr(1) . $salt;
$dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash);
$maskedDB = $db ^ $dbMask;
$maskedDB[0] = ~chr(0xFF << ($modulusLength & 7)) & $maskedDB[0];
return $maskedDB . $h . chr(0xBC);
}
/**
* EMSA-PSS-VERIFY.
*/
private static function verifyEMSAPSS(string $m, string $em, int $emBits, Hash $hash): bool
{
$emLen = ($emBits + 1) >> 3;
$sLen = $hash->getLength();
$mHash = $hash->hash($m);
if ($emLen < $hash->getLength() + $sLen + 2) {
throw new InvalidArgumentException();
}
if ($em[mb_strlen($em, '8bit') - 1] !== chr(0xBC)) {
throw new InvalidArgumentException();
}
$maskedDB = mb_substr($em, 0, -$hash->getLength() - 1, '8bit');
$h = mb_substr($em, -$hash->getLength() - 1, $hash->getLength(), '8bit');
$temp = chr(0xFF << ($emBits & 7));
if ((~$maskedDB[0] & $temp) !== $temp) {
throw new InvalidArgumentException();
}
$dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
$db = $maskedDB ^ $dbMask;
$db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
$temp = $emLen - $hash->getLength() - $sLen - 2;
if (mb_substr($db, 0, $temp, '8bit') !== str_repeat(chr(0), $temp)) {
throw new InvalidArgumentException();
}
if (ord($db[$temp]) !== 1) {
throw new InvalidArgumentException();
}
$salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long
$m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
$h2 = $hash->hash($m2);
return hash_equals($h, $h2);
}
}