first commit

This commit is contained in:
2025-06-17 11:53:18 +02:00
commit 9f0f7ba12b
8804 changed files with 1369176 additions and 0 deletions

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Spomky-Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

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 mb_substr($signature, 0, intdiv($this->getSignatureLength(), 8), '8bit');
}
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 mb_strlen;
use function mb_substr;
use function str_pad;
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(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
);
}
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(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 (
mb_strpos($data, self::ASN1_NEGATIVE_INTEGER, 0, '8bit') === 0
&& 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;
}
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 (
mb_strpos($data, self::ASN1_NEGATIVE_INTEGER, 0, '8bit') === 0
&& mb_substr($data, 2, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT
) {
$data = mb_substr($data, 2, null, '8bit');
}
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,183 @@
<?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 mb_strlen;
use function mb_substr;
use function ord;
use function pack;
use function random_bytes;
use function str_pad;
use function str_repeat;
use const STR_PAD_LEFT;
/**
* @internal
*/
abstract class PSSRSA implements Signature
{
public function sign(string $data, Key $key): string
{
$key = $this->handleKey($key);
$modulusLength = mb_strlen($key->n(), '8bit');
$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 = mb_strlen($key->n(), '8bit');
if (mb_strlen($signature, '8bit') !== $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 (mb_strlen($xB, '8bit') > $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 mb_substr($t, 0, $maskLen, '8bit');
}
/**
* 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[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 = $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 (mb_strpos($db, str_repeat(chr(0), $temp), 0, '8bit') !== 0) {
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);
}
}

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,108 @@
<?php
declare(strict_types=1);
namespace Cose;
use Brick\Math\BigInteger as BrickBigInteger;
use function chr;
use function hex2bin;
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 !== (mb_strlen($temp, '8bit') & 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,195 @@
<?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;
/**
* @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 (mb_strlen((string) $data[self::DATA_X], '8bit') !== self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]]) {
throw new InvalidArgumentException('Invalid length for x coordinate');
}
if (mb_strlen((string) $data[self::DATA_Y], '8bit') !== 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", mb_strtoupper($type)) .
chunk_split(base64_encode($der), 64, "\n") .
sprintf("-----END %s-----\n", mb_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);
}
}