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,10 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm;
interface Algorithm
{
public static function identifier(): int;
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Mac;
final class HS256 extends Hmac
{
public const ID = 5;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): string
{
return 'sha256';
}
protected function getSignatureLength(): int
{
return 256;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Mac;
final class HS256Truncated64 extends Hmac
{
public const ID = 4;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): string
{
return 'sha256';
}
protected function getSignatureLength(): int
{
return 64;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Mac;
final class HS384 extends Hmac
{
public const ID = 6;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): string
{
return 'sha384';
}
protected function getSignatureLength(): int
{
return 384;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Mac;
final class HS512 extends Hmac
{
public const ID = 7;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): string
{
return 'sha512';
}
protected function getSignatureLength(): int
{
return 512;
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Mac;
use Cose\Key\Key;
use Cose\Key\SymmetricKey;
use InvalidArgumentException;
/**
* @see \Cose\Tests\Algorithm\Mac\HmacTest
*/
abstract class Hmac implements Mac
{
public function hash(string $data, Key $key): string
{
$this->checKey($key);
$signature = hash_hmac($this->getHashAlgorithm(), $data, (string) $key->get(SymmetricKey::DATA_K), true);
return substr($signature, 0, intdiv($this->getSignatureLength(), 8));
}
public function verify(string $data, Key $key, string $signature): bool
{
return hash_equals($this->hash($data, $key), $signature);
}
abstract protected function getHashAlgorithm(): string;
abstract protected function getSignatureLength(): int;
private function checKey(Key $key): void
{
if ($key->type() !== Key::TYPE_OCT && $key->type() !== Key::TYPE_NAME_OCT) {
throw new InvalidArgumentException('Invalid key. Must be of type symmetric');
}
if (! $key->has(SymmetricKey::DATA_K)) {
throw new InvalidArgumentException('Invalid key. The value of the key is missing');
}
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Mac;
use Cose\Algorithm\Algorithm;
use Cose\Key\Key;
interface Mac extends Algorithm
{
public function hash(string $data, Key $key): string;
public function verify(string $data, Key $key, string $signature): bool;
}

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm;
use InvalidArgumentException;
use function array_key_exists;
final class Manager
{
/**
* @var array<int, Algorithm>
*/
private array $algorithms = [];
public static function create(): self
{
return new self();
}
public function add(Algorithm ...$algorithms): self
{
foreach ($algorithms as $algorithm) {
$identifier = $algorithm::identifier();
$this->algorithms[$identifier] = $algorithm;
}
return $this;
}
/**
* @return iterable<int>
*/
public function list(): iterable
{
yield from array_keys($this->algorithms);
}
/**
* @return iterable<int, Algorithm>
*/
public function all(): iterable
{
yield from $this->algorithms;
}
public function has(int $identifier): bool
{
return array_key_exists($identifier, $this->algorithms);
}
public function get(int $identifier): Algorithm
{
if (! $this->has($identifier)) {
throw new InvalidArgumentException('Unsupported algorithm');
}
return $this->algorithms[$identifier];
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm;
use InvalidArgumentException;
use function array_key_exists;
final class ManagerFactory
{
/**
* @var array<string, Algorithm>
*/
private array $algorithms = [];
public static function create(): self
{
return new self();
}
public function add(string $alias, Algorithm $algorithm): self
{
$this->algorithms[$alias] = $algorithm;
return $this;
}
/**
* @return string[]
*/
public function list(): iterable
{
yield from array_keys($this->algorithms);
}
/**
* @return Algorithm[]
*/
public function all(): iterable
{
yield from $this->algorithms;
}
public function generate(string ...$aliases): Manager
{
$manager = Manager::create();
foreach ($aliases as $alias) {
if (! array_key_exists($alias, $this->algorithms)) {
throw new InvalidArgumentException(sprintf('The algorithm with alias "%s" is not supported', $alias));
}
$manager->add($this->algorithms[$alias]);
}
return $manager;
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\ECDSA;
use Cose\Algorithm\Signature\Signature;
use Cose\Key\Ec2Key;
use Cose\Key\Key;
use InvalidArgumentException;
use function openssl_sign;
use function openssl_verify;
/**
* @see \Cose\Tests\Algorithm\Signature\ECDSA\ECDSATest
*/
abstract class ECDSA implements Signature
{
public function sign(string $data, Key $key): string
{
$key = $this->handleKey($key);
openssl_sign($data, $signature, $key->asPEM(), $this->getHashAlgorithm());
return ECSignature::fromAsn1($signature, $this->getSignaturePartLength());
}
public function verify(string $data, Key $key, string $signature): bool
{
$key = $this->handleKey($key);
$publicKey = $key->toPublic();
$signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength());
return openssl_verify($data, $signature, $publicKey->asPEM(), $this->getHashAlgorithm()) === 1;
}
abstract protected function getCurve(): int;
abstract protected function getHashAlgorithm(): int;
abstract protected function getSignaturePartLength(): int;
private function handleKey(Key $key): Ec2Key
{
$key = Ec2Key::create($key->getData());
if ($key->curve() !== $this->getCurve()) {
throw new InvalidArgumentException('This key cannot be used with this algorithm');
}
return $key;
}
}

View File

@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\ECDSA;
use InvalidArgumentException;
use function bin2hex;
use function dechex;
use function hex2bin;
use function hexdec;
use function str_pad;
use function strlen;
use function substr;
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;
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(substr($signature, 0, $length));
$pointS = self::preparePositiveInteger(substr($signature, $length, null));
$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
);
}
public static function fromAsn1(string $signature, int $length): string
{
$message = bin2hex($signature);
$position = 0;
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_SEQUENCE) {
throw new InvalidArgumentException('Invalid data. Should start with a sequence.');
}
// @phpstan-ignore-next-line
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) === self::ASN1_LENGTH_2BYTES) {
$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 intdiv(strlen($data), self::BYTE_SIZE);
}
private static function preparePositiveInteger(string $data): string
{
if (substr($data, 0, self::BYTE_SIZE) > self::ASN1_BIG_INTEGER_LIMIT) {
return self::ASN1_NEGATIVE_INTEGER . $data;
}
while (
str_starts_with($data, self::ASN1_NEGATIVE_INTEGER)
&& substr($data, 2, self::BYTE_SIZE) <= self::ASN1_BIG_INTEGER_LIMIT
) {
$data = substr($data, 2, null);
}
return $data;
}
private static function readAsn1Content(string $message, int &$position, int $length): string
{
$content = substr($message, $position, $length);
$position += $length;
return $content;
}
private static function readAsn1Integer(string $message, int &$position): string
{
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_INTEGER) {
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 (
str_starts_with($data, self::ASN1_NEGATIVE_INTEGER)
&& substr($data, 2, self::BYTE_SIZE) > self::ASN1_BIG_INTEGER_LIMIT
) {
$data = substr($data, 2, null);
}
return $data;
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\ECDSA;
use Cose\Key\Ec2Key;
use const OPENSSL_ALGO_SHA256;
final class ES256 extends ECDSA
{
public const ID = -7;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): int
{
return OPENSSL_ALGO_SHA256;
}
protected function getCurve(): int
{
return Ec2Key::CURVE_P256;
}
protected function getSignaturePartLength(): int
{
return 64;
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\ECDSA;
use Cose\Key\Ec2Key;
use const OPENSSL_ALGO_SHA256;
final class ES256K extends ECDSA
{
public const ID = -46;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): int
{
return OPENSSL_ALGO_SHA256;
}
protected function getCurve(): int
{
return Ec2Key::CURVE_P256K;
}
protected function getSignaturePartLength(): int
{
return 64;
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\ECDSA;
use Cose\Key\Ec2Key;
use const OPENSSL_ALGO_SHA384;
final class ES384 extends ECDSA
{
public const ID = -35;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): int
{
return OPENSSL_ALGO_SHA384;
}
protected function getCurve(): int
{
return Ec2Key::CURVE_P384;
}
protected function getSignaturePartLength(): int
{
return 96;
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\ECDSA;
use Cose\Key\Ec2Key;
use const OPENSSL_ALGO_SHA512;
final class ES512 extends ECDSA
{
public const ID = -36;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): int
{
return OPENSSL_ALGO_SHA512;
}
protected function getCurve(): int
{
return Ec2Key::CURVE_P521;
}
protected function getSignaturePartLength(): int
{
return 132;
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\EdDSA;
final class Ed25519 extends EdDSA
{
public const ID = -8;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\EdDSA;
use Cose\Key\Key;
final class Ed256 extends EdDSA
{
public const ID = -260;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
public function sign(string $data, Key $key): string
{
$hashedData = hash('sha256', $data, true);
return parent::sign($hashedData, $key);
}
public function verify(string $data, Key $key, string $signature): bool
{
$hashedData = hash('sha256', $data, true);
return parent::verify($hashedData, $key, $signature);
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\EdDSA;
use Cose\Key\Key;
final class Ed512 extends EdDSA
{
public const ID = -261;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
public function sign(string $data, Key $key): string
{
$hashedData = hash('sha512', $data, true);
return parent::sign($hashedData, $key);
}
public function verify(string $data, Key $key, string $signature): bool
{
$hashedData = hash('sha512', $data, true);
return parent::verify($hashedData, $key, $signature);
}
}

View File

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\EdDSA;
use Cose\Algorithm\Signature\Signature;
use Cose\Algorithms;
use Cose\Key\Key;
use Cose\Key\OkpKey;
use InvalidArgumentException;
use Throwable;
use function sodium_crypto_sign_detached;
use function sodium_crypto_sign_verify_detached;
/**
* @see \Cose\Tests\Algorithm\Signature\EdDSA\EdDSATest
*/
class EdDSA implements Signature
{
public function sign(string $data, Key $key): string
{
$key = $this->handleKey($key);
if (! $key->isPrivate()) {
throw new InvalidArgumentException('The key is not private.');
}
$x = $key->x();
$d = $key->d();
$secret = $d . $x;
return match ($key->curve()) {
OkpKey::CURVE_ED25519 => sodium_crypto_sign_detached($data, $secret),
OkpKey::CURVE_NAME_ED25519 => sodium_crypto_sign_detached($data, $secret),
default => throw new InvalidArgumentException('Unsupported curve'),
};
}
public function verify(string $data, Key $key, string $signature): bool
{
$key = $this->handleKey($key);
if ($key->curve() !== OkpKey::CURVE_ED25519 && $key->curve() !== OkpKey::CURVE_NAME_ED25519) {
throw new InvalidArgumentException('Unsupported curve');
}
try {
sodium_crypto_sign_verify_detached($signature, $data, $key->x());
} catch (Throwable) {
return false;
}
return true;
}
public static function identifier(): int
{
return Algorithms::COSE_ALGORITHM_EDDSA;
}
private function handleKey(Key $key): OkpKey
{
return OkpKey::create($key->getData());
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\RSA;
use Cose\Hash;
final class PS256 extends PSSRSA
{
public const ID = -37;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): Hash
{
return Hash::sha256();
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\RSA;
use Cose\Hash;
final class PS384 extends PSSRSA
{
public const ID = -38;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): Hash
{
return Hash::sha384();
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\RSA;
use Cose\Hash;
final class PS512 extends PSSRSA
{
public const ID = -39;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): Hash
{
return Hash::sha512();
}
}

View File

@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\RSA;
use Cose\Algorithm\Signature\Signature;
use Cose\BigInteger;
use Cose\Hash;
use Cose\Key\Key;
use Cose\Key\RsaKey;
use InvalidArgumentException;
use RuntimeException;
use function ceil;
use function chr;
use function hash_equals;
use function ord;
use function pack;
use function random_bytes;
use function str_pad;
use function str_repeat;
use function strlen;
use const STR_PAD_LEFT;
/**
* @internal
*/
abstract class PSSRSA implements Signature
{
public function sign(string $data, Key $key): string
{
$key = $this->handleKey($key);
$modulusLength = strlen($key->n());
$em = $this->encodeEMSAPSS($data, 8 * $modulusLength - 1, $this->getHashAlgorithm());
$message = BigInteger::createFromBinaryString($em);
$signature = $this->exponentiate($key, $message);
return $this->convertIntegerToOctetString($signature, $modulusLength);
}
public function verify(string $data, Key $key, string $signature): bool
{
$key = $this->handleKey($key);
$modulusLength = strlen($key->n());
if (strlen($signature) !== $modulusLength) {
throw new InvalidArgumentException('Invalid modulus length');
}
$s2 = BigInteger::createFromBinaryString($signature);
$m2 = $this->exponentiate($key, $s2);
$em = $this->convertIntegerToOctetString($m2, $modulusLength);
$modBits = 8 * $modulusLength;
return $this->verifyEMSAPSS($data, $em, $modBits - 1, $this->getHashAlgorithm());
}
/**
* Exponentiate with or without Chinese Remainder Theorem. Operation with primes 'p' and 'q' is appox. 2x faster.
*/
public function exponentiate(RsaKey $key, BigInteger $c): BigInteger
{
if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare(
BigInteger::createFromBinaryString($key->n())
) > 0) {
throw new RuntimeException();
}
if ($key->isPublic() || ! $key->hasPrimes() || ! $key->hasExponents() || ! $key->hasCoefficient()) {
return $c->modPow(
BigInteger::createFromBinaryString($key->e()),
BigInteger::createFromBinaryString($key->n())
);
}
[$pS, $qS] = $key->primes();
[$dPS, $dQS] = $key->exponents();
$qInv = BigInteger::createFromBinaryString($key->QInv());
$p = BigInteger::createFromBinaryString($pS);
$q = BigInteger::createFromBinaryString($qS);
$dP = BigInteger::createFromBinaryString($dPS);
$dQ = BigInteger::createFromBinaryString($dQS);
$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));
}
abstract protected function getHashAlgorithm(): Hash;
private function handleKey(Key $key): RsaKey
{
return RsaKey::create($key->getData());
}
private function convertIntegerToOctetString(BigInteger $x, int $xLen): string
{
$xB = $x->toBytes();
if (strlen($xB) > $xLen) {
throw new RuntimeException('Unable to convert the integer');
}
return str_pad($xB, $xLen, chr(0), STR_PAD_LEFT);
}
/**
* MGF1.
*/
private 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 substr($t, 0, $maskLen);
}
/**
* EMSA-PSS-ENCODE.
*/
private 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 = $this->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 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[strlen($em) - 1] !== chr(0xBC)) {
throw new InvalidArgumentException();
}
$maskedDB = substr($em, 0, -$hash->getLength() - 1);
$h = substr($em, -$hash->getLength() - 1, $hash->getLength());
$temp = chr(0xFF << ($emBits & 7));
if ((~$maskedDB[0] & $temp) !== $temp) {
throw new InvalidArgumentException();
}
$dbMask = $this->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 (! str_starts_with($db, str_repeat(chr(0), $temp))) {
throw new InvalidArgumentException();
}
if (ord($db[$temp]) !== 1) {
throw new InvalidArgumentException();
}
$salt = substr($db, $temp + 1, null); // should be $sLen long
$m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
$h2 = $hash->hash($m2);
return hash_equals($h, $h2);
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\RSA;
use const OPENSSL_ALGO_SHA1;
final class RS1 extends RSA
{
public const ID = -65535;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): int
{
return OPENSSL_ALGO_SHA1;
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\RSA;
use const OPENSSL_ALGO_SHA256;
final class RS256 extends RSA
{
public const ID = -257;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): int
{
return OPENSSL_ALGO_SHA256;
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\RSA;
use const OPENSSL_ALGO_SHA384;
final class RS384 extends RSA
{
public const ID = -258;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): int
{
return OPENSSL_ALGO_SHA384;
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\RSA;
use const OPENSSL_ALGO_SHA512;
final class RS512 extends RSA
{
public const ID = -259;
public static function create(): self
{
return new self();
}
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): int
{
return OPENSSL_ALGO_SHA512;
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature\RSA;
use Cose\Algorithm\Signature\Signature;
use Cose\Key\Key;
use Cose\Key\RsaKey;
use InvalidArgumentException;
use Throwable;
use function openssl_sign;
use function openssl_verify;
/**
* @see \Cose\Tests\Algorithm\Signature\RSA\RSATest
*/
abstract class RSA implements Signature
{
public function sign(string $data, Key $key): string
{
$key = $this->handleKey($key);
if (! $key->isPrivate()) {
throw new InvalidArgumentException('The key is not private.');
}
try {
openssl_sign($data, $signature, $key->asPem(), $this->getHashAlgorithm());
} catch (Throwable $e) {
throw new InvalidArgumentException('Unable to sign the data', 0, $e);
}
return $signature;
}
public function verify(string $data, Key $key, string $signature): bool
{
$key = $this->handleKey($key);
return openssl_verify($data, $signature, $key->toPublic()->asPem(), $this->getHashAlgorithm()) === 1;
}
abstract protected function getHashAlgorithm(): int;
private function handleKey(Key $key): RsaKey
{
return RsaKey::create($key->getData());
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Cose\Algorithm\Signature;
use Cose\Algorithm\Algorithm;
use Cose\Key\Key;
interface Signature extends Algorithm
{
public function sign(string $data, Key $key): string;
public function verify(string $data, Key $key, string $signature): bool;
}

View File

@ -0,0 +1,175 @@
<?php
declare(strict_types=1);
namespace Cose;
use InvalidArgumentException;
use function array_key_exists;
use const OPENSSL_ALGO_SHA1;
use const OPENSSL_ALGO_SHA256;
use const OPENSSL_ALGO_SHA384;
use const OPENSSL_ALGO_SHA512;
/**
* @see https://www.iana.org/assignments/cose/cose.xhtml#algorithms
*/
abstract class Algorithms
{
final public const COSE_ALGORITHM_AES_CCM_64_128_256 = 33;
final public const COSE_ALGORITHM_AES_CCM_64_128_128 = 32;
final public const COSE_ALGORITHM_AES_CCM_16_128_256 = 31;
final public const COSE_ALGORITHM_AES_CCM_16_128_128 = 30;
final public const COSE_ALGORITHM_AES_MAC_256_128 = 26;
final public const COSE_ALGORITHM_AES_MAC_128_128 = 25;
final public const COSE_ALGORITHM_CHACHA20_POLY1305 = 24;
final public const COSE_ALGORITHM_AES_MAC_256_64 = 15;
final public const COSE_ALGORITHM_AES_MAC_128_64 = 14;
final public const COSE_ALGORITHM_AES_CCM_64_64_256 = 13;
final public const COSE_ALGORITHM_AES_CCM_64_64_128 = 12;
final public const COSE_ALGORITHM_AES_CCM_16_64_256 = 11;
final public const COSE_ALGORITHM_AES_CCM_16_64_128 = 10;
final public const COSE_ALGORITHM_HS512 = 7;
final public const COSE_ALGORITHM_HS384 = 6;
final public const COSE_ALGORITHM_HS256 = 5;
final public const COSE_ALGORITHM_HS256_64 = 4;
final public const COSE_ALGORITHM_A256GCM = 3;
final public const COSE_ALGORITHM_A192GCM = 2;
final public const COSE_ALGORITHM_A128GCM = 1;
final public const COSE_ALGORITHM_A128KW = -3;
final public const COSE_ALGORITHM_A192KW = -4;
final public const COSE_ALGORITHM_A256KW = -5;
final public const COSE_ALGORITHM_DIRECT = -6;
final public const COSE_ALGORITHM_ES256 = -7;
/**
* @deprecated since v4.0.6. Please use COSE_ALGORITHM_EDDSA instead. Will be removed in v5.0.0
*/
final public const COSE_ALGORITHM_EdDSA = -8;
final public const COSE_ALGORITHM_EDDSA = -8;
final public const COSE_ALGORITHM_ED256 = -260;
final public const COSE_ALGORITHM_ED512 = -261;
final public const COSE_ALGORITHM_DIRECT_HKDF_SHA_256 = -10;
final public const COSE_ALGORITHM_DIRECT_HKDF_SHA_512 = -11;
final public const COSE_ALGORITHM_DIRECT_HKDF_AES_128 = -12;
final public const COSE_ALGORITHM_DIRECT_HKDF_AES_256 = -13;
final public const COSE_ALGORITHM_ECDH_ES_HKDF_256 = -25;
final public const COSE_ALGORITHM_ECDH_ES_HKDF_512 = -26;
final public const COSE_ALGORITHM_ECDH_SS_HKDF_256 = -27;
final public const COSE_ALGORITHM_ECDH_SS_HKDF_512 = -28;
final public const COSE_ALGORITHM_ECDH_ES_A128KW = -29;
final public const COSE_ALGORITHM_ECDH_ES_A192KW = -30;
final public const COSE_ALGORITHM_ECDH_ES_A256KW = -31;
final public const COSE_ALGORITHM_ECDH_SS_A128KW = -32;
final public const COSE_ALGORITHM_ECDH_SS_A192KW = -33;
final public const COSE_ALGORITHM_ECDH_SS_A256KW = -34;
final public const COSE_ALGORITHM_ES384 = -35;
final public const COSE_ALGORITHM_ES512 = -36;
final public const COSE_ALGORITHM_PS256 = -37;
final public const COSE_ALGORITHM_PS384 = -38;
final public const COSE_ALGORITHM_PS512 = -39;
final public const COSE_ALGORITHM_RSAES_OAEP = -40;
final public const COSE_ALGORITHM_RSAES_OAEP_256 = -41;
final public const COSE_ALGORITHM_RSAES_OAEP_512 = -42;
final public const COSE_ALGORITHM_ES256K = -46;
final public const COSE_ALGORITHM_RS256 = -257;
final public const COSE_ALGORITHM_RS384 = -258;
final public const COSE_ALGORITHM_RS512 = -259;
final public const COSE_ALGORITHM_RS1 = -65535;
final public const COSE_ALGORITHM_MAP = [
self::COSE_ALGORITHM_ES256 => OPENSSL_ALGO_SHA256,
self::COSE_ALGORITHM_ES384 => OPENSSL_ALGO_SHA384,
self::COSE_ALGORITHM_ES512 => OPENSSL_ALGO_SHA512,
self::COSE_ALGORITHM_RS256 => OPENSSL_ALGO_SHA256,
self::COSE_ALGORITHM_RS384 => OPENSSL_ALGO_SHA384,
self::COSE_ALGORITHM_RS512 => OPENSSL_ALGO_SHA512,
self::COSE_ALGORITHM_RS1 => OPENSSL_ALGO_SHA1,
];
final public const COSE_HASH_MAP = [
self::COSE_ALGORITHM_ES256K => 'sha256',
self::COSE_ALGORITHM_ES256 => 'sha256',
self::COSE_ALGORITHM_ES384 => 'sha384',
self::COSE_ALGORITHM_ES512 => 'sha512',
self::COSE_ALGORITHM_RS256 => 'sha256',
self::COSE_ALGORITHM_RS384 => 'sha384',
self::COSE_ALGORITHM_RS512 => 'sha512',
self::COSE_ALGORITHM_PS256 => 'sha256',
self::COSE_ALGORITHM_PS384 => 'sha384',
self::COSE_ALGORITHM_PS512 => 'sha512',
self::COSE_ALGORITHM_RS1 => 'sha1',
];
public static function getOpensslAlgorithmFor(int $algorithmIdentifier): int
{
if (! array_key_exists($algorithmIdentifier, self::COSE_ALGORITHM_MAP)) {
throw new InvalidArgumentException('The specified algorithm identifier is not supported');
}
return self::COSE_ALGORITHM_MAP[$algorithmIdentifier];
}
public static function getHashAlgorithmFor(int $algorithmIdentifier): string
{
if (! array_key_exists($algorithmIdentifier, self::COSE_HASH_MAP)) {
throw new InvalidArgumentException('The specified algorithm identifier is not supported');
}
return self::COSE_HASH_MAP[$algorithmIdentifier];
}
}

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Cose;
use Brick\Math\BigInteger as BrickBigInteger;
use function chr;
use function hex2bin;
use function strlen;
use function unpack;
/**
* @internal
*/
final class BigInteger
{
private function __construct(
private readonly BrickBigInteger $value
) {
}
public static function createFromBinaryString(string $value): self
{
$res = unpack('H*', $value);
$data = current($res);
return new self(BrickBigInteger::fromBase($data, 16));
}
public static function createFromDecimal(int $value): self
{
return new self(BrickBigInteger::of($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 !== (strlen($temp) & 1) ? '0' . $temp : $temp;
$temp = hex2bin($temp);
return ltrim($temp, chr(0));
}
/**
* Adds two BigIntegers.
*/
public function add(self $y): self
{
$value = $this->value->plus($y->value);
return new self($value);
}
/**
* Subtracts two BigIntegers.
*/
public function subtract(self $y): self
{
$value = $this->value->minus($y->value);
return new self($value);
}
/**
* Multiplies two BigIntegers.
*/
public function multiply(self $x): self
{
$value = $this->value->multipliedBy($x->value);
return new self($value);
}
/**
* Performs modular exponentiation.
*/
public function modPow(self $e, self $n): self
{
$value = $this->value->modPow($e->value, $n->value);
return new self($value);
}
/**
* Performs modular exponentiation.
*/
public function mod(self $d): self
{
$value = $this->value->mod($d->value);
return new self($value);
}
/**
* Compares two numbers.
*/
public function compare(self $y): int
{
return $this->value->compareTo($y->value);
}
}

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Cose;
/**
* @internal
*/
final class Hash
{
private function __construct(
private readonly string $hash,
private readonly int $length,
private readonly string $t
) {
}
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");
}
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");
}
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");
}
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,196 @@
<?php
declare(strict_types=1);
namespace Cose\Key;
use InvalidArgumentException;
use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence;
use SpomkyLabs\Pki\ASN1\Type\Primitive\BitString;
use SpomkyLabs\Pki\ASN1\Type\Primitive\Integer;
use SpomkyLabs\Pki\ASN1\Type\Primitive\ObjectIdentifier;
use SpomkyLabs\Pki\ASN1\Type\Primitive\OctetString;
use SpomkyLabs\Pki\ASN1\Type\Tagged\ExplicitlyTaggedType;
use function array_key_exists;
use function in_array;
use function is_int;
use function strlen;
/**
* @final
* @see \Cose\Tests\Key\Ec2KeyTest
*/
class Ec2Key extends Key
{
final public const CURVE_P256 = 1;
final public const CURVE_P256K = 8;
final public const CURVE_P384 = 2;
final public const CURVE_P521 = 3;
final public const CURVE_NAME_P256 = 'P-256';
final public const CURVE_NAME_P256K = 'P-256K';
final public const CURVE_NAME_P384 = 'P-384';
final public const CURVE_NAME_P521 = 'P-521';
final public const DATA_CURVE = -1;
final public const DATA_X = -2;
final public const DATA_Y = -3;
final public const DATA_D = -4;
private const SUPPORTED_CURVES_INT = [self::CURVE_P256, self::CURVE_P256K, self::CURVE_P384, self::CURVE_P521];
private const SUPPORTED_CURVES_NAMES = [
self::CURVE_NAME_P256,
self::CURVE_NAME_P256K,
self::CURVE_NAME_P384,
self::CURVE_NAME_P521,
];
private const NAMED_CURVE_OID = [
self::CURVE_P256 => '1.2.840.10045.3.1.7',
// NIST P-256 / secp256r1
self::CURVE_P256K => '1.3.132.0.10',
// NIST P-256K / secp256k1
self::CURVE_P384 => '1.3.132.0.34',
// NIST P-384 / secp384r1
self::CURVE_P521 => '1.3.132.0.35',
// NIST P-521 / secp521r1
];
private const CURVE_KEY_LENGTH = [
self::CURVE_P256 => 32,
self::CURVE_P256K => 32,
self::CURVE_P384 => 48,
self::CURVE_P521 => 66,
self::CURVE_NAME_P256 => 32,
self::CURVE_NAME_P256K => 32,
self::CURVE_NAME_P384 => 48,
self::CURVE_NAME_P521 => 66,
];
/**
* @param array<int|string, mixed> $data
*/
public function __construct(array $data)
{
foreach ([self::DATA_CURVE, self::TYPE] as $key) {
if (is_numeric($data[$key])) {
$data[$key] = (int) $data[$key];
}
}
parent::__construct($data);
if ($data[self::TYPE] !== self::TYPE_EC2 && $data[self::TYPE] !== self::TYPE_NAME_EC2) {
throw new InvalidArgumentException('Invalid EC2 key. The key type does not correspond to an EC2 key');
}
if (! isset($data[self::DATA_CURVE], $data[self::DATA_X], $data[self::DATA_Y])) {
throw new InvalidArgumentException('Invalid EC2 key. The curve or the "x/y" coordinates are missing');
}
if (strlen((string) $data[self::DATA_X]) !== self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]]) {
throw new InvalidArgumentException('Invalid length for x coordinate');
}
if (strlen((string) $data[self::DATA_Y]) !== self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]]) {
throw new InvalidArgumentException('Invalid length for y coordinate');
}
if (is_int($data[self::DATA_CURVE])) {
if (! in_array($data[self::DATA_CURVE], self::SUPPORTED_CURVES_INT, true)) {
throw new InvalidArgumentException('The curve is not supported');
}
} elseif (! in_array($data[self::DATA_CURVE], self::SUPPORTED_CURVES_NAMES, true)) {
throw new InvalidArgumentException('The curve is not supported');
}
}
/**
* @param array<int|string, mixed> $data
*/
public static function create(array $data): self
{
return new self($data);
}
public function toPublic(): self
{
$data = $this->getData();
unset($data[self::DATA_D]);
return new self($data);
}
public function x(): string
{
return $this->get(self::DATA_X);
}
public function y(): string
{
return $this->get(self::DATA_Y);
}
public function isPrivate(): bool
{
return array_key_exists(self::DATA_D, $this->getData());
}
public function d(): string
{
if (! $this->isPrivate()) {
throw new InvalidArgumentException('The key is not private.');
}
return $this->get(self::DATA_D);
}
public function curve(): int|string
{
return $this->get(self::DATA_CURVE);
}
public function asPEM(): string
{
if ($this->isPrivate()) {
$der = Sequence::create(
Integer::create(1),
OctetString::create($this->d()),
ExplicitlyTaggedType::create(0, ObjectIdentifier::create($this->getCurveOid())),
ExplicitlyTaggedType::create(1, BitString::create($this->getUncompressedCoordinates())),
);
return $this->pem('EC PRIVATE KEY', $der->toDER());
}
$der = Sequence::create(
Sequence::create(
ObjectIdentifier::create('1.2.840.10045.2.1'),
ObjectIdentifier::create($this->getCurveOid())
),
BitString::create($this->getUncompressedCoordinates())
);
return $this->pem('PUBLIC KEY', $der->toDER());
}
public function getUncompressedCoordinates(): string
{
return "\x04" . $this->x() . $this->y();
}
private function getCurveOid(): string
{
return self::NAMED_CURVE_OID[$this->curve()];
}
private function pem(string $type, string $der): string
{
return sprintf("-----BEGIN %s-----\n", strtoupper($type)) .
chunk_split(base64_encode($der), 64, "\n") .
sprintf("-----END %s-----\n", strtoupper($type));
}
}

View File

@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Cose\Key;
use InvalidArgumentException;
use function array_key_exists;
class Key
{
public const TYPE = 1;
public const TYPE_OKP = 1;
public const TYPE_EC2 = 2;
public const TYPE_RSA = 3;
public const TYPE_OCT = 4;
public const TYPE_NAME_OKP = 'OKP';
public const TYPE_NAME_EC2 = 'EC';
public const TYPE_NAME_RSA = 'RSA';
public const TYPE_NAME_OCT = 'oct';
public const KID = 2;
public const ALG = 3;
public const KEY_OPS = 4;
public const BASE_IV = 5;
/**
* @var array<int|string, mixed>
*/
private readonly array $data;
/**
* @param array<int|string, mixed> $data
*/
public function __construct(array $data)
{
if (! array_key_exists(self::TYPE, $data)) {
throw new InvalidArgumentException('Invalid key: the type is not defined');
}
$this->data = $data;
}
/**
* @param array<int|string, mixed> $data
*/
public static function create(array $data): self
{
return new self($data);
}
/**
* @param array<int, mixed> $data
*/
public static function createFromData(array $data): self
{
if (! array_key_exists(self::TYPE, $data)) {
throw new InvalidArgumentException('Invalid key: the type is not defined');
}
return match ($data[self::TYPE]) {
'1' => new OkpKey($data),
'2' => new Ec2Key($data),
'3' => new RsaKey($data),
'4' => new SymmetricKey($data),
default => self::create($data),
};
}
public function type(): int|string
{
return $this->data[self::TYPE];
}
public function alg(): int
{
return (int) $this->get(self::ALG);
}
/**
* @return array<int|string, mixed>
*/
public function getData(): array
{
return $this->data;
}
public function has(int|string $key): bool
{
return array_key_exists($key, $this->data);
}
public function get(int|string $key): mixed
{
if (! array_key_exists($key, $this->data)) {
throw new InvalidArgumentException(sprintf('The key has no data at index %d', $key));
}
return $this->data[$key];
}
}

View File

@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace Cose\Key;
use InvalidArgumentException;
use function array_key_exists;
use function in_array;
/**
* @final
* @see \Cose\Tests\Key\OkpKeyTest
*/
class OkpKey extends Key
{
final public const CURVE_X25519 = 4;
final public const CURVE_X448 = 5;
final public const CURVE_ED25519 = 6;
final public const CURVE_ED448 = 7;
final public const CURVE_NAME_X25519 = 'X25519';
final public const CURVE_NAME_X448 = 'X448';
final public const CURVE_NAME_ED25519 = 'Ed25519';
final public const CURVE_NAME_ED448 = 'Ed448';
final public const DATA_CURVE = -1;
final public const DATA_X = -2;
final public const DATA_D = -4;
private const SUPPORTED_CURVES_INT = [
self::CURVE_X25519,
self::CURVE_X448,
self::CURVE_ED25519,
self::CURVE_ED448,
];
private const SUPPORTED_CURVES_NAME = [
self::CURVE_NAME_X25519,
self::CURVE_NAME_X448,
self::CURVE_NAME_ED25519,
self::CURVE_NAME_ED448,
];
/**
* @param array<int|string, mixed> $data
*/
public function __construct(array $data)
{
foreach ([self::DATA_CURVE, self::TYPE] as $key) {
if (is_numeric($data[$key])) {
$data[$key] = (int) $data[$key];
}
}
parent::__construct($data);
if ($data[self::TYPE] !== self::TYPE_OKP && $data[self::TYPE] !== self::TYPE_NAME_OKP) {
throw new InvalidArgumentException('Invalid OKP key. The key type does not correspond to an OKP key');
}
if (! isset($data[self::DATA_CURVE], $data[self::DATA_X])) {
throw new InvalidArgumentException('Invalid EC2 key. The curve or the "x" coordinate is missing');
}
if (is_numeric($data[self::DATA_CURVE])) {
if (! in_array((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES_INT, true)) {
throw new InvalidArgumentException('The curve is not supported');
}
} elseif (! in_array($data[self::DATA_CURVE], self::SUPPORTED_CURVES_NAME, true)) {
throw new InvalidArgumentException('The curve is not supported');
}
}
/**
* @param array<int|string, mixed> $data
*/
public static function create(array $data): self
{
return new self($data);
}
public function x(): string
{
return $this->get(self::DATA_X);
}
public function isPrivate(): bool
{
return array_key_exists(self::DATA_D, $this->getData());
}
public function d(): string
{
if (! $this->isPrivate()) {
throw new InvalidArgumentException('The key is not private.');
}
return $this->get(self::DATA_D);
}
public function curve(): int|string
{
return $this->get(self::DATA_CURVE);
}
}

View File

@ -0,0 +1,262 @@
<?php
declare(strict_types=1);
namespace Cose\Key;
use Brick\Math\BigInteger;
use InvalidArgumentException;
use SpomkyLabs\Pki\CryptoTypes\Asymmetric\PublicKeyInfo;
use SpomkyLabs\Pki\CryptoTypes\Asymmetric\RSA\RSAPrivateKey;
use SpomkyLabs\Pki\CryptoTypes\Asymmetric\RSA\RSAPublicKey;
use function array_key_exists;
use function in_array;
/**
* @final
* @see \Cose\Tests\Key\RsaKeyTest
*/
class RsaKey extends Key
{
final public const DATA_N = -1;
final public const DATA_E = -2;
final public const DATA_D = -3;
final public const DATA_P = -4;
final public const DATA_Q = -5;
final public const DATA_DP = -6;
final public const DATA_DQ = -7;
final public const DATA_QI = -8;
final public const DATA_OTHER = -9;
final public const DATA_RI = -10;
final public const DATA_DI = -11;
final public const DATA_TI = -12;
/**
* @param array<int|string, mixed> $data
*/
public function __construct(array $data)
{
foreach ([self::TYPE] as $key) {
if (is_numeric($data[$key])) {
$data[$key] = (int) $data[$key];
}
}
parent::__construct($data);
if ($data[self::TYPE] !== self::TYPE_RSA && $data[self::TYPE] !== self::TYPE_NAME_RSA) {
throw new InvalidArgumentException('Invalid RSA key. The key type does not correspond to a RSA key');
}
if (! isset($data[self::DATA_N], $data[self::DATA_E])) {
throw new InvalidArgumentException('Invalid RSA key. The modulus or the exponent is missing');
}
}
/**
* @param array<int|string, mixed> $data
*/
public static function create(array $data): self
{
return new self($data);
}
public function n(): string
{
return $this->get(self::DATA_N);
}
public function e(): string
{
return $this->get(self::DATA_E);
}
public function d(): string
{
$this->checkKeyIsPrivate();
return $this->get(self::DATA_D);
}
public function p(): string
{
$this->checkKeyIsPrivate();
return $this->get(self::DATA_P);
}
public function q(): string
{
$this->checkKeyIsPrivate();
return $this->get(self::DATA_Q);
}
public function dP(): string
{
$this->checkKeyIsPrivate();
return $this->get(self::DATA_DP);
}
public function dQ(): string
{
$this->checkKeyIsPrivate();
return $this->get(self::DATA_DQ);
}
public function QInv(): string
{
$this->checkKeyIsPrivate();
return $this->get(self::DATA_QI);
}
/**
* @return array<mixed>
*/
public function other(): array
{
$this->checkKeyIsPrivate();
return $this->get(self::DATA_OTHER);
}
public function rI(): string
{
$this->checkKeyIsPrivate();
return $this->get(self::DATA_RI);
}
public function dI(): string
{
$this->checkKeyIsPrivate();
return $this->get(self::DATA_DI);
}
public function tI(): string
{
$this->checkKeyIsPrivate();
return $this->get(self::DATA_TI);
}
public function hasPrimes(): bool
{
return $this->has(self::DATA_P) && $this->has(self::DATA_Q);
}
/**
* @return string[]
*/
public function primes(): array
{
return [$this->p(), $this->q()];
}
public function hasExponents(): bool
{
return $this->has(self::DATA_DP) && $this->has(self::DATA_DQ);
}
/**
* @return string[]
*/
public function exponents(): array
{
return [$this->dP(), $this->dQ()];
}
public function hasCoefficient(): bool
{
return $this->has(self::DATA_QI);
}
public function isPublic(): bool
{
return ! $this->isPrivate();
}
public function isPrivate(): bool
{
return array_key_exists(self::DATA_D, $this->getData());
}
public function asPem(): string
{
if ($this->isPrivate()) {
$privateKey = RSAPrivateKey::create(
$this->binaryToBigInteger($this->n()),
$this->binaryToBigInteger($this->e()),
$this->binaryToBigInteger($this->d()),
$this->binaryToBigInteger($this->p()),
$this->binaryToBigInteger($this->q()),
$this->binaryToBigInteger($this->dP()),
$this->binaryToBigInteger($this->dQ()),
$this->binaryToBigInteger($this->QInv())
);
return $privateKey->toPEM()
->string();
}
$publicKey = RSAPublicKey::create(
$this->binaryToBigInteger($this->n()),
$this->binaryToBigInteger($this->e())
);
$rsaKey = PublicKeyInfo::fromPublicKey($publicKey);
return $rsaKey->toPEM()
->string();
}
public function toPublic(): static
{
$toBeRemoved = [
self::DATA_D,
self::DATA_P,
self::DATA_Q,
self::DATA_DP,
self::DATA_DQ,
self::DATA_QI,
self::DATA_OTHER,
self::DATA_RI,
self::DATA_DI,
self::DATA_TI,
];
$data = $this->getData();
foreach ($data as $k => $v) {
if (in_array($k, $toBeRemoved, true)) {
unset($data[$k]);
}
}
return new static($data);
}
private function checkKeyIsPrivate(): void
{
if (! $this->isPrivate()) {
throw new InvalidArgumentException('The key is not private.');
}
}
private function binaryToBigInteger(string $data): string
{
$res = unpack('H*', $data);
$res = current($res);
return BigInteger::fromBase($res, 16)->toBase(10);
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Cose\Key;
use InvalidArgumentException;
/**
* @final
*/
class SymmetricKey extends Key
{
final public const DATA_K = -1;
/**
* @param array<int|string, mixed> $data
*/
public function __construct(array $data)
{
parent::__construct($data);
if (! isset($data[self::TYPE]) || (int) $data[self::TYPE] !== self::TYPE_OCT) {
throw new InvalidArgumentException(
'Invalid symmetric key. The key type does not correspond to a symmetric key'
);
}
if (! isset($data[self::DATA_K])) {
throw new InvalidArgumentException('Invalid symmetric key. The parameter "k" is missing');
}
}
/**
* @param array<int|string, mixed> $data
*/
public static function create(array $data): self
{
return new self($data);
}
public function k(): string
{
return $this->get(self::DATA_K);
}
}