Update website

This commit is contained in:
Guilhem Lavaux 2024-11-23 20:45:29 +01:00
parent 41ce1aa076
commit ea0eb1c6e0
4222 changed files with 721797 additions and 14 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,31 @@
{
"name": "web-auth/cose-lib",
"type": "library",
"license": "MIT",
"description": "CBOR Object Signing and Encryption (COSE) For PHP",
"keywords": ["COSE", "RFC8152"],
"homepage": "https://github.com/web-auth",
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/web-auth/cose/contributors"
}
],
"require": {
"php": ">=7.2",
"ext-json": "*",
"ext-openssl": "*",
"ext-mbstring": "*",
"fgrosse/phpasn1": "^2.1",
"beberlei/assert": "^3.2"
},
"autoload": {
"psr-4": {
"Cose\\": "src/"
}
}
}

View file

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm;
interface Algorithm
{
public static function identifier(): int;
}

View file

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Mac;
final class HS256 extends Hmac
{
public const ID = 5;
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,34 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Mac;
final class HS256Truncated64 extends Hmac
{
public const ID = 4;
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,34 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Mac;
final class HS384 extends Hmac
{
public const ID = 6;
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,34 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Mac;
final class HS512 extends Hmac
{
public const ID = 7;
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);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Mac;
use Assert\Assertion;
use Cose\Key\Key;
abstract class Hmac implements Mac
{
public function hash(string $data, Key $key): string
{
$this->checKey($key);
$signature = hash_hmac($this->getHashAlgorithm(), $data, $key->get(-1), 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
{
Assertion::eq($key->type(), 4, 'Invalid key. Must be of type symmetric');
Assertion::true($key->has(-1), 'Invalid key. The value of the key is missing');
}
}

View file

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
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,56 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm;
use function array_key_exists;
use Assert\Assertion;
class Manager
{
/**
* @var Algorithm[]
*/
private $algorithms = [];
public function add(Algorithm $algorithm): void
{
$identifier = $algorithm::identifier();
$this->algorithms[$identifier] = $algorithm;
}
public function list(): iterable
{
yield from array_keys($this->algorithms);
}
/**
* @return 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
{
Assertion::true($this->has($identifier), 'Unsupported algorithm');
return $this->algorithms[$identifier];
}
}

View file

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm;
use Assert\Assertion;
class ManagerFactory
{
/**
* @var Algorithm[]
*/
private $algorithms = [];
public function add(string $alias, Algorithm $algorithm): void
{
$this->algorithms[$alias] = $algorithm;
}
public function list(): iterable
{
yield from array_keys($this->algorithms);
}
public function all(): iterable
{
yield from array_keys($this->algorithms);
}
public function create(array $aliases): Manager
{
$manager = new Manager();
foreach ($aliases as $alias) {
Assertion::keyExists($this->algorithms, $alias, sprintf('The algorithm with alias "%s" is not supported', $alias));
$manager->add($this->algorithms[$alias]);
}
return $manager;
}
}

View file

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\ECDSA;
use Assert\Assertion;
use Cose\Algorithm\Signature\Signature;
use Cose\Key\Ec2Key;
use Cose\Key\Key;
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 1 === openssl_verify($data, $signature, $publicKey->asPEM(), $this->getHashAlgorithm());
}
abstract protected function getCurve(): int;
abstract protected function getHashAlgorithm(): int;
abstract protected function getSignaturePartLength(): int;
private function handleKey(Key $key): Ec2Key
{
$key = new Ec2Key($key->getData());
Assertion::eq($key->curve(), $this->getCurve(), 'This key cannot be used with this algorithm');
return $key;
}
}

View file

@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\ECDSA;
use function bin2hex;
use function dechex;
use function hexdec;
use InvalidArgumentException;
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 : '';
$bin = hex2bin(
self::ASN1_SEQUENCE
.$lengthPrefix.dechex($totalLength)
.self::ASN1_INTEGER.dechex($lengthR).$pointR
.self::ASN1_INTEGER.dechex($lengthS).$pointS
);
if (false === $bin) {
throw new InvalidArgumentException('Unable to convert into ASN.1');
}
return $bin;
}
public static function fromAsn1(string $signature, int $length): string
{
$message = bin2hex($signature);
$position = 0;
if (self::ASN1_SEQUENCE !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
throw new InvalidArgumentException('Invalid data. Should start with a sequence.');
}
// @phpstan-ignore-next-line
if (self::ASN1_LENGTH_2BYTES === self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
$position += self::BYTE_SIZE;
}
$pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
$pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
$bin = hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT).str_pad($pointS, $length, '0', STR_PAD_LEFT));
if (false === $bin) {
throw new InvalidArgumentException('Unable to convert from ASN.1');
}
return $bin;
}
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 (
self::ASN1_NEGATIVE_INTEGER === mb_substr($data, 0, self::BYTE_SIZE, '8bit')
&& mb_substr($data, 2, self::BYTE_SIZE, '8bit') <= self::ASN1_BIG_INTEGER_LIMIT
) {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
private static function readAsn1Content(string $message, int &$position, int $length): string
{
$content = mb_substr($message, $position, $length, '8bit');
$position += $length;
return $content;
}
private static function readAsn1Integer(string $message, int &$position): string
{
if (self::ASN1_INTEGER !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) {
throw new InvalidArgumentException('Invalid data. Should contain an integer.');
}
$length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE));
return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE);
}
private static function retrievePositiveInteger(string $data): string
{
while (
self::ASN1_NEGATIVE_INTEGER === mb_substr($data, 0, self::BYTE_SIZE, '8bit')
&& 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,41 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\ECDSA;
use Cose\Key\Ec2Key;
final class ES256 extends ECDSA
{
public const ID = -7;
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,41 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\ECDSA;
use Cose\Key\Ec2Key;
final class ES256K extends ECDSA
{
public const ID = -46;
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,41 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\ECDSA;
use Cose\Key\Ec2Key;
final class ES384 extends ECDSA
{
public const ID = -35;
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,41 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\ECDSA;
use Cose\Key\Ec2Key;
final class ES512 extends ECDSA
{
public const ID = -36;
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,40 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\EdDSA;
use Cose\Key\Key;
final class ED256 extends EdDSA
{
public const ID = -260;
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,40 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\EdDSA;
use Cose\Key\Key;
final class ED512 extends EdDSA
{
public const ID = -261;
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,24 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\EdDSA;
final class Ed25519 extends EdDSA
{
public const ID = -8;
public static function identifier(): int
{
return self::ID;
}
}

View file

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\EdDSA;
use Assert\Assertion;
use Cose\Algorithm\Signature\Signature;
use Cose\Algorithms;
use Cose\Key\Key;
use Cose\Key\OkpKey;
use InvalidArgumentException;
use function sodium_crypto_sign_detached;
use function sodium_crypto_sign_verify_detached;
class EdDSA implements Signature
{
public function sign(string $data, Key $key): string
{
$key = $this->handleKey($key);
Assertion::true($key->isPrivate(), 'The key is not private');
$x = $key->x();
$d = $key->d();
$secret = $d.$x;
switch ($key->curve()) {
case OkpKey::CURVE_ED25519:
return 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);
switch ($key->curve()) {
case OkpKey::CURVE_ED25519:
return sodium_crypto_sign_verify_detached($signature, $data, $key->x());
default:
throw new InvalidArgumentException('Unsupported curve');
}
}
public static function identifier(): int
{
return Algorithms::COSE_ALGORITHM_EdDSA;
}
private function handleKey(Key $key): OkpKey
{
return new OkpKey($key->getData());
}
}

View file

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\RSA;
use Cose\Hash;
final class PS256 extends PSSRSA
{
public const ID = -37;
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): Hash
{
return Hash::sha256();
}
}

View file

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\RSA;
use Cose\Hash;
final class PS384 extends PSSRSA
{
public const ID = -38;
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): Hash
{
return Hash::sha384();
}
}

View file

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\RSA;
use Cose\Hash;
final class PS512 extends PSSRSA
{
public const ID = -39;
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): Hash
{
return Hash::sha512();
}
}

View file

@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\RSA;
use function ceil;
use function chr;
use Cose\Algorithm\Signature\Signature;
use Cose\BigInteger;
use Cose\Hash;
use Cose\Key\Key;
use Cose\Key\RsaKey;
use function hash_equals;
use InvalidArgumentException;
use function mb_strlen;
use function mb_substr;
use function ord;
use function random_bytes;
use RuntimeException;
use function str_pad;
use function str_repeat;
/**
* @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()));
}
[$p, $q] = $key->primes();
[$dP, $dQ] = $key->exponents();
$qInv = BigInteger::createFromBinaryString($key->QInv());
$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 new RsaKey($key->getData());
}
private function convertIntegerToOctetString(BigInteger $x, int $xLen): string
{
$x = $x->toBytes();
if (mb_strlen($x, '8bit') > $xLen) {
throw new RuntimeException('Unable to convert the integer');
}
return str_pad($x, $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_substr($db, 0, $temp, '8bit') !== str_repeat(chr(0), $temp)) {
throw new InvalidArgumentException();
}
if (1 !== ord($db[$temp])) {
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,29 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\RSA;
final class RS1 extends RSA
{
public const ID = -65535;
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): int
{
return OPENSSL_ALGO_SHA1;
}
}

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\RSA;
final class RS256 extends RSA
{
public const ID = -257;
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): int
{
return OPENSSL_ALGO_SHA256;
}
}

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\RSA;
final class RS384 extends RSA
{
public const ID = -258;
public static function identifier(): int
{
return self::ID;
}
protected function getHashAlgorithm(): int
{
return OPENSSL_ALGO_SHA384;
}
}

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\RSA;
final class RS512 extends RSA
{
public const ID = -259;
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);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Algorithm\Signature\RSA;
use Assert\Assertion;
use Cose\Algorithm\Signature\Signature;
use Cose\Key\Key;
use Cose\Key\RsaKey;
use InvalidArgumentException;
abstract class RSA implements Signature
{
public function sign(string $data, Key $key): string
{
$key = $this->handleKey($key);
Assertion::true($key->isPrivate(), 'The key is not private');
if (false === openssl_sign($data, $signature, $key->asPem(), $this->getHashAlgorithm())) {
throw new InvalidArgumentException('Unable to sign the data');
}
return $signature;
}
public function verify(string $data, Key $key, string $signature): bool
{
$key = $this->handleKey($key);
return 1 === openssl_verify($data, $signature, $key->asPem(), $this->getHashAlgorithm());
}
abstract protected function getHashAlgorithm(): int;
private function handleKey(Key $key): RsaKey
{
return new RsaKey($key->getData());
}
}

View file

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
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,123 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose;
use Assert\Assertion;
use Assert\AssertionFailedException;
/**
* @see https://www.iana.org/assignments/cose/cose.xhtml#algorithms
*/
abstract class Algorithms
{
public const COSE_ALGORITHM_AES_CCM_64_128_256 = 33;
public const COSE_ALGORITHM_AES_CCM_64_128_128 = 32;
public const COSE_ALGORITHM_AES_CCM_16_128_256 = 31;
public const COSE_ALGORITHM_AES_CCM_16_128_128 = 30;
public const COSE_ALGORITHM_AES_MAC_256_128 = 26;
public const COSE_ALGORITHM_AES_MAC_128_128 = 25;
public const COSE_ALGORITHM_CHACHA20_POLY1305 = 24;
public const COSE_ALGORITHM_AES_MAC_256_64 = 15;
public const COSE_ALGORITHM_AES_MAC_128_64 = 14;
public const COSE_ALGORITHM_AES_CCM_64_64_256 = 13;
public const COSE_ALGORITHM_AES_CCM_64_64_128 = 12;
public const COSE_ALGORITHM_AES_CCM_16_64_256 = 11;
public const COSE_ALGORITHM_AES_CCM_16_64_128 = 10;
public const COSE_ALGORITHM_HS512 = 7;
public const COSE_ALGORITHM_HS384 = 6;
public const COSE_ALGORITHM_HS256 = 5;
public const COSE_ALGORITHM_HS256_64 = 4;
public const COSE_ALGORITHM_A256GCM = 3;
public const COSE_ALGORITHM_A192GCM = 2;
public const COSE_ALGORITHM_A128GCM = 1;
public const COSE_ALGORITHM_A128KW = -3;
public const COSE_ALGORITHM_A192KW = -4;
public const COSE_ALGORITHM_A256KW = -5;
public const COSE_ALGORITHM_DIRECT = -6;
public const COSE_ALGORITHM_ES256 = -7;
public const COSE_ALGORITHM_EdDSA = -8;
public const COSE_ALGORITHM_ED256 = -260;
public const COSE_ALGORITHM_ED512 = -261;
public const COSE_ALGORITHM_DIRECT_HKDF_SHA_256 = -10;
public const COSE_ALGORITHM_DIRECT_HKDF_SHA_512 = -11;
public const COSE_ALGORITHM_DIRECT_HKDF_AES_128 = -12;
public const COSE_ALGORITHM_DIRECT_HKDF_AES_256 = -13;
public const COSE_ALGORITHM_ECDH_ES_HKDF_256 = -25;
public const COSE_ALGORITHM_ECDH_ES_HKDF_512 = -26;
public const COSE_ALGORITHM_ECDH_SS_HKDF_256 = -27;
public const COSE_ALGORITHM_ECDH_SS_HKDF_512 = -28;
public const COSE_ALGORITHM_ECDH_ES_A128KW = -29;
public const COSE_ALGORITHM_ECDH_ES_A192KW = -30;
public const COSE_ALGORITHM_ECDH_ES_A256KW = -31;
public const COSE_ALGORITHM_ECDH_SS_A128KW = -32;
public const COSE_ALGORITHM_ECDH_SS_A192KW = -33;
public const COSE_ALGORITHM_ECDH_SS_A256KW = -34;
public const COSE_ALGORITHM_ES384 = -35;
public const COSE_ALGORITHM_ES512 = -36;
public const COSE_ALGORITHM_PS256 = -37;
public const COSE_ALGORITHM_PS384 = -38;
public const COSE_ALGORITHM_PS512 = -39;
public const COSE_ALGORITHM_RSAES_OAEP = -40;
public const COSE_ALGORITHM_RSAES_OAEP_256 = -41;
public const COSE_ALGORITHM_RSAES_OAEP_512 = -42;
public const COSE_ALGORITHM_ES256K = -46;
public const COSE_ALGORITHM_RS256 = -257;
public const COSE_ALGORITHM_RS384 = -258;
public const COSE_ALGORITHM_RS512 = -259;
public const COSE_ALGORITHM_RS1 = -65535;
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,
];
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',
];
/**
* @throws AssertionFailedException
*/
public static function getOpensslAlgorithmFor(int $algorithmIdentifier): int
{
Assertion::keyExists(self::COSE_ALGORITHM_MAP, $algorithmIdentifier, 'The specified algorithm identifier is not supported');
return self::COSE_ALGORITHM_MAP[$algorithmIdentifier];
}
/**
* @throws AssertionFailedException
*/
public static function getHashAlgorithmFor(int $algorithmIdentifier): string
{
Assertion::keyExists(self::COSE_HASH_MAP, $algorithmIdentifier, 'The specified algorithm identifier is not supported');
return self::COSE_HASH_MAP[$algorithmIdentifier];
}
}

View file

@ -0,0 +1,154 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose;
use Brick\Math\BigInteger as BrickBigInteger;
use function chr;
use function hex2bin;
use InvalidArgumentException;
use function unpack;
/**
* @internal
*/
class BigInteger
{
/**
* Holds the BigInteger's value.
*
* @var BrickBigInteger
*/
private $value;
private function __construct(BrickBigInteger $value)
{
$this->value = $value;
}
public static function createFromBinaryString(string $value): self
{
$res = unpack('H*', $value);
if (false === $res) {
throw new InvalidArgumentException('Unable to convert the data from binary');
}
$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);
if (false === $temp) {
throw new InvalidArgumentException('Unable to convert the data into binary');
}
return ltrim($temp, chr(0));
}
/**
* Adds two BigIntegers.
*
* @param BigInteger $y
*
* @return BigInteger
*/
public function add(self $y): self
{
$value = $this->value->plus($y->value);
return new self($value);
}
/**
* Subtracts two BigIntegers.
*
* @param BigInteger $y
*
* @return BigInteger
*/
public function subtract(self $y): self
{
$value = $this->value->minus($y->value);
return new self($value);
}
/**
* Multiplies two BigIntegers.
*
* @param BigInteger $x
*
* @return BigInteger
*/
public function multiply(self $x): self
{
$value = $this->value->multipliedBy($x->value);
return new self($value);
}
/**
* Performs modular exponentiation.
*
* @param BigInteger $e
* @param BigInteger $n
*
* @return BigInteger
*/
public function modPow(self $e, self $n): self
{
$value = $this->value->modPow($e->value, $n->value);
return new self($value);
}
/**
* Performs modular exponentiation.
*
* @param BigInteger $d
*
* @return BigInteger
*/
public function mod(self $d): self
{
$value = $this->value->mod($d->value);
return new self($value);
}
/**
* Compares two numbers.
*
* @param BigInteger $y
*/
public function compare(self $y): int
{
return $this->value->compareTo($y->value);
}
}

View file

@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose;
/**
* @internal
*/
class Hash
{
/**
* Hash Parameter.
*
* @var string
*/
private $hash;
/**
* DER encoding T.
*
* @var string
*/
private $t;
/**
* Hash Length.
*
* @var int
*/
private $length;
private function __construct(string $hash, int $length, string $t)
{
$this->hash = $hash;
$this->length = $length;
$this->t = $t;
}
/**
* @return Hash
*/
public static function sha1(): self
{
return new self('sha1', 20, "\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14");
}
/**
* @return Hash
*/
public static function sha256(): self
{
return new self('sha256', 32, "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20");
}
/**
* @return Hash
*/
public static function sha384(): self
{
return new self('sha384', 48, "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30");
}
/**
* @return Hash
*/
public static function sha512(): self
{
return new self('sha512', 64, "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40");
}
public function getLength(): int
{
return $this->length;
}
/**
* Compute the HMAC.
*/
public function hash(string $text): string
{
return hash($this->hash, $text, true);
}
public function name(): string
{
return $this->hash;
}
public function t(): string
{
return $this->t;
}
}

View file

@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Key;
use function array_key_exists;
use Assert\Assertion;
use FG\ASN1\ExplicitlyTaggedObject;
use FG\ASN1\Universal\BitString;
use FG\ASN1\Universal\Integer;
use FG\ASN1\Universal\ObjectIdentifier;
use FG\ASN1\Universal\OctetString;
use FG\ASN1\Universal\Sequence;
class Ec2Key extends Key
{
public const CURVE_P256 = 1;
public const CURVE_P256K = 8;
public const CURVE_P384 = 2;
public const CURVE_P521 = 3;
public const DATA_CURVE = -1;
public const DATA_X = -2;
public const DATA_Y = -3;
public const DATA_D = -4;
private const SUPPORTED_CURVES = [
self::CURVE_P256,
self::CURVE_P256K,
self::CURVE_P384,
self::CURVE_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,
];
public function __construct(array $data)
{
parent::__construct($data);
Assertion::eq($data[self::TYPE], self::TYPE_EC2, 'Invalid EC2 key. The key type does not correspond to an EC2 key');
Assertion::keyExists($data, self::DATA_CURVE, 'Invalid EC2 key. The curve is missing');
Assertion::keyExists($data, self::DATA_X, 'Invalid EC2 key. The x coordinate is missing');
Assertion::keyExists($data, self::DATA_Y, 'Invalid EC2 key. The y coordinate is missing');
Assertion::length($data[self::DATA_X], self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]], 'Invalid length for x coordinate', null, '8bit');
Assertion::length($data[self::DATA_Y], self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]], 'Invalid length for y coordinate', null, '8bit');
Assertion::inArray((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES, 'The curve is not supported');
}
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
{
Assertion::true($this->isPrivate(), 'The key is not private');
return $this->get(self::DATA_D);
}
public function curve(): int
{
return (int) $this->get(self::DATA_CURVE);
}
public function asPEM(): string
{
if ($this->isPrivate()) {
$der = new Sequence(
new Integer(1),
new OctetString(bin2hex($this->d())),
new ExplicitlyTaggedObject(0, new ObjectIdentifier($this->getCurveOid())),
new ExplicitlyTaggedObject(1, new BitString(bin2hex($this->getUncompressedCoordinates())))
);
return $this->pem('EC PRIVATE KEY', $der->getBinary());
}
$der = new Sequence(
new Sequence(
new ObjectIdentifier('1.2.840.10045.2.1'),
new ObjectIdentifier($this->getCurveOid())
),
new BitString(bin2hex($this->getUncompressedCoordinates()))
);
return $this->pem('PUBLIC KEY', $der->getBinary());
}
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,91 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Key;
use function array_key_exists;
use Assert\Assertion;
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 KID = 2;
public const ALG = 3;
public const KEY_OPS = 4;
public const BASE_IV = 5;
/**
* @var array
*/
private $data;
public function __construct(array $data)
{
Assertion::keyExists($data, self::TYPE, 'Invalid key: the type is not defined');
$this->data = $data;
}
public static function createFromData(array $data): self
{
Assertion::keyExists($data, self::TYPE, 'Invalid key: the type is not defined');
switch ($data[self::TYPE]) {
case 1:
return new OkpKey($data);
case 2:
return new Ec2Key($data);
case 3:
return new RsaKey($data);
case 4:
return new SymmetricKey($data);
default:
return new self($data);
}
}
/**
* @return int|string
*/
public function type()
{
return $this->data[self::TYPE];
}
public function alg(): int
{
return (int) $this->get(self::ALG);
}
public function getData(): array
{
return $this->data;
}
public function has(int $key): bool
{
return array_key_exists($key, $this->data);
}
/**
* @return mixed
*/
public function get(int $key)
{
Assertion::keyExists($this->data, $key, sprintf('The key has no data at index %d', $key));
return $this->data[$key];
}
}

View file

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Key;
use function array_key_exists;
use Assert\Assertion;
class OkpKey extends Key
{
public const CURVE_X25519 = 4;
public const CURVE_X448 = 5;
public const CURVE_ED25519 = 6;
public const CURVE_ED448 = 7;
public const DATA_CURVE = -1;
public const DATA_X = -2;
public const DATA_D = -4;
private const SUPPORTED_CURVES = [
self::CURVE_X25519,
self::CURVE_X448,
self::CURVE_ED25519,
self::CURVE_ED448,
];
public function __construct(array $data)
{
parent::__construct($data);
Assertion::eq($data[self::TYPE], self::TYPE_OKP, 'Invalid OKP key. The key type does not correspond to an OKP key');
Assertion::keyExists($data, self::DATA_CURVE, 'Invalid EC2 key. The curve is missing');
Assertion::keyExists($data, self::DATA_X, 'Invalid OKP key. The x coordinate is missing');
Assertion::inArray((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES, 'The curve is not supported');
}
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
{
Assertion::true($this->isPrivate(), 'The key is not private');
return $this->get(self::DATA_D);
}
public function curve(): int
{
return (int) $this->get(self::DATA_CURVE);
}
}

View file

@ -0,0 +1,207 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Key;
use function array_key_exists;
use Assert\Assertion;
use Brick\Math\BigInteger;
use FG\ASN1\Universal\BitString;
use FG\ASN1\Universal\Integer;
use FG\ASN1\Universal\NullObject;
use FG\ASN1\Universal\ObjectIdentifier;
use FG\ASN1\Universal\Sequence;
use InvalidArgumentException;
class RsaKey extends Key
{
public const DATA_N = -1;
public const DATA_E = -2;
public const DATA_D = -3;
public const DATA_P = -4;
public const DATA_Q = -5;
public const DATA_DP = -6;
public const DATA_DQ = -7;
public const DATA_QI = -8;
public const DATA_OTHER = -9;
public const DATA_RI = -10;
public const DATA_DI = -11;
public const DATA_TI = -12;
public function __construct(array $data)
{
parent::__construct($data);
Assertion::eq($data[self::TYPE], self::TYPE_RSA, 'Invalid RSA key. The key type does not correspond to a RSA key');
Assertion::keyExists($data, self::DATA_N, 'Invalid RSA key. The modulus is missing');
Assertion::keyExists($data, self::DATA_E, 'Invalid RSA key. The exponent is missing');
}
public function n(): string
{
return $this->get(self::DATA_N);
}
public function e(): string
{
return $this->get(self::DATA_E);
}
public function d(): string
{
Assertion::true($this->isPrivate(), 'The key is not private.');
return $this->get(self::DATA_D);
}
public function p(): string
{
Assertion::true($this->isPrivate(), 'The key is not private.');
return $this->get(self::DATA_P);
}
public function q(): string
{
Assertion::true($this->isPrivate(), 'The key is not private.');
return $this->get(self::DATA_Q);
}
public function dP(): string
{
Assertion::true($this->isPrivate(), 'The key is not private.');
return $this->get(self::DATA_DP);
}
public function dQ(): string
{
Assertion::true($this->isPrivate(), 'The key is not private.');
return $this->get(self::DATA_DQ);
}
public function QInv(): string
{
Assertion::true($this->isPrivate(), 'The key is not private.');
return $this->get(self::DATA_QI);
}
public function other(): array
{
Assertion::true($this->isPrivate(), 'The key is not private.');
return $this->get(self::DATA_OTHER);
}
public function rI(): string
{
Assertion::true($this->isPrivate(), 'The key is not private.');
return $this->get(self::DATA_RI);
}
public function dI(): string
{
Assertion::true($this->isPrivate(), 'The key is not private.');
return $this->get(self::DATA_DI);
}
public function tI(): string
{
Assertion::true($this->isPrivate(), 'The key is not private.');
return $this->get(self::DATA_TI);
}
public function hasPrimes(): bool
{
return $this->has(self::DATA_P) && $this->has(self::DATA_Q);
}
public function primes(): array
{
return [
$this->p(),
$this->q(),
];
}
public function hasExponents(): bool
{
return $this->has(self::DATA_DP) && $this->has(self::DATA_DQ);
}
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
{
Assertion::false($this->isPrivate(), 'Unsupported for private keys.');
$bitSring = new Sequence(
new Integer($this->fromBase64ToInteger($this->n())),
new Integer($this->fromBase64ToInteger($this->e()))
);
$der = new Sequence(
new Sequence(
new ObjectIdentifier('1.2.840.113549.1.1.1'),
new NullObject()
),
new BitString(bin2hex($bitSring->getBinary()))
);
return $this->pem('PUBLIC KEY', $der->getBinary());
}
private function fromBase64ToInteger(string $value): string
{
$data = unpack('H*', $value);
if (false === $data) {
throw new InvalidArgumentException('Unable to convert to an integer');
}
$hex = current($data);
return BigInteger::fromBase($hex, 16)->toBase(10);
}
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,33 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose\Key;
use Assert\Assertion;
class SymmetricKey extends Key
{
public const DATA_K = -1;
public function __construct(array $data)
{
parent::__construct($data);
Assertion::eq($data[self::TYPE], self::TYPE_OCT, 'Invalid symmetric key. The key type does not correspond to a symmetric key');
Assertion::keyExists($data, self::DATA_K, 'Invalid symmetric key. The parameter "k" is missing');
}
public function k(): string
{
return $this->get(self::DATA_K);
}
}

View file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Cose;
class Verifier
{
}