From 7f3189986a7848abef474d583438535750a80697 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Fri, 9 Sep 2022 21:21:04 +0200 Subject: [PATCH 1/2] CoseSign1Tag and structure --- .github/workflows/integrate.yml | 10 +-- composer.json | 6 +- src/Signature/CoseSign1Tag.php | 110 ++++++++++++++++++++++++ src/Signature/Signature1.php | 45 ++++++++++ tests/{ => Algorithm/Mac}/HS256Test.php | 2 +- tests/Signature/CoseSign1Test.php | 106 +++++++++++++++++++++++ tests/Signature/ECSignature.php | 72 ++++++++++++++++ 7 files changed, 343 insertions(+), 8 deletions(-) create mode 100644 src/Signature/CoseSign1Tag.php create mode 100644 src/Signature/Signature1.php rename tests/{ => Algorithm/Mac}/HS256Test.php (98%) create mode 100644 tests/Signature/CoseSign1Test.php create mode 100644 tests/Signature/ECSignature.php diff --git a/.github/workflows/integrate.yml b/.github/workflows/integrate.yml index 56d7749..6d10b17 100644 --- a/.github/workflows/integrate.yml +++ b/.github/workflows/integrate.yml @@ -65,7 +65,7 @@ jobs: uses: "shivammathur/setup-php@v2" with: php-version: "${{ matrix.php-version }}" - extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter" + extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter, zlib" coverage: "xdebug" - name: "Checkout code" @@ -99,7 +99,7 @@ jobs: uses: "shivammathur/setup-php@v2" with: php-version: "8.1" - extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter" + extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter, zlib" coverage: "none" - name: "Checkout code" @@ -131,7 +131,7 @@ jobs: uses: "shivammathur/setup-php@v2" with: php-version: "8.1" - extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter" + extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter, zlib" coverage: "none" - name: "Checkout code" @@ -164,7 +164,7 @@ jobs: uses: "shivammathur/setup-php@v2" with: php-version: "8.1" - extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter" + extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter, zlib" coverage: "xdebug" - name: "Checkout code" @@ -193,7 +193,7 @@ jobs: uses: "shivammathur/setup-php@v2" with: php-version: "8.1" - extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter" + extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter, zlib" coverage: "xdebug" - name: "Checkout code" diff --git a/composer.json b/composer.json index 144b043..b648d2c 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,8 @@ "ekino/phpstan-banned-code": "^1.0", "thecodingmachine/phpstan-safe-rule": "^1.2", "php-parallel-lint/php-parallel-lint": "^1.3", - "qossmic/deptrac-shim": "^0.24.0" + "qossmic/deptrac-shim": "^0.24.0", + "spomky-labs/cbor-php": "^3.0" }, "autoload-dev": { "psr-4": { @@ -58,6 +59,7 @@ }, "suggest": { "ext-gmp": "For better performance, please install either GMP (recommended) or BCMath extension", - "ext-bcmath": "For better performance, please install either GMP (recommended) or BCMath extension" + "ext-bcmath": "For better performance, please install either GMP (recommended) or BCMath extension", + "spomky-labs/cbor-php": "For COSE Signature support" } } diff --git a/src/Signature/CoseSign1Tag.php b/src/Signature/CoseSign1Tag.php new file mode 100644 index 0000000..38863ca --- /dev/null +++ b/src/Signature/CoseSign1Tag.php @@ -0,0 +1,110 @@ +get(0); + $unprotectedHeader = $object->get(1); + $payload = $object->get(2); + $signature = $object->get(3); + + Assertion::isInstanceOf( + $protectedHeader, + ByteStringObject::class, + 'Not a valid CoseSign1 object. The item 1 shall be a ByteString object.' + ); + Assertion::isInstanceOf( + $unprotectedHeader, + MapObject::class, + 'Not a valid CoseSign1 object. The item 2 shall be a Map object.' + ); + Assertion::isInstanceOf( + $payload, + ByteStringObject::class, + 'Not a valid CoseSign1 object. The item 3 shall be a ByteString object.' + ); + Assertion::isInstanceOf( + $signature, + ByteStringObject::class, + 'Not a valid CoseSign1 object. The item 4 shall be a ByteString object.' + ); + + parent::__construct($additionalInformation, $data, $object); + $this->protectedHeader = $protectedHeader; + $this->unprotectedHeader = $unprotectedHeader; + $this->payload = $payload; + $this->signature = $signature; + } + + public static function getTagId(): int + { + return self::TAG_ID; + } + + public static function createFromLoadedData(int $additionalInformation, ?string $data, CBORObject $object): Base + { + return new self($additionalInformation, $data, $object); + } + + public static function create( + MapObject $protectedHeader, + MapObject $unprotectedHeader, + MapObject $payload, + ByteStringObject $signature + ): self { + $protectedHeaderAsBytesString = ByteStringObject::create((string) $protectedHeader); + $payloadAsBytesString = ByteStringObject::create((string) $payload); + $object = ListObject::create([ + $protectedHeaderAsBytesString, + $unprotectedHeader, + $payloadAsBytesString, + $signature, + ]); + + return new self(self::TAG_ID, null, $object); + } + + public function getProtectedHeader(): ByteStringObject + { + return $this->protectedHeader; + } + + public function getUnprotectedHeader(): MapObject + { + return $this->unprotectedHeader; + } + + public function getPayload(): ByteStringObject + { + return $this->payload; + } + + public function getSignature(): ByteStringObject + { + return $this->signature; + } +} diff --git a/src/Signature/Signature1.php b/src/Signature/Signature1.php new file mode 100644 index 0000000..4c67b9d --- /dev/null +++ b/src/Signature/Signature1.php @@ -0,0 +1,45 @@ +add(new TextStringObject('Signature1')); + $structure->add($this->protectedHeader); + $structure->add(new ByteStringObject('')); + $structure->add($this->payload); + + return (string) $structure; + } + + public static function create(ByteStringObject $protectedHeader, ByteStringObject $payload): self + { + return new self($protectedHeader, $payload); + } + + public function getProtectedHeader(): ByteStringObject + { + return $this->protectedHeader; + } + + public function getPayload(): ByteStringObject + { + return $this->payload; + } +} diff --git a/tests/HS256Test.php b/tests/Algorithm/Mac/HS256Test.php similarity index 98% rename from tests/HS256Test.php rename to tests/Algorithm/Mac/HS256Test.php index 06e8668..c10778f 100644 --- a/tests/HS256Test.php +++ b/tests/Algorithm/Mac/HS256Test.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Cose\Tests; +namespace Cose\Tests\Algorithm\Mac; use Cose\Algorithm\Mac\HS256; use Cose\Key\OkpKey; diff --git a/tests/Signature/CoseSign1Test.php b/tests/Signature/CoseSign1Test.php new file mode 100644 index 0000000..6e51e06 --- /dev/null +++ b/tests/Signature/CoseSign1Test.php @@ -0,0 +1,106 @@ +getDecoder(); + + //When + $cbor = $decoder->decode($stream); //We decode the data + + //Then + static::assertInstanceOf(CoseSign1Tag::class, $cbor, 'Invalid object'); + + return $cbor; + } + + /** + * @test + * @depends theCovidVaccinationPassCanBeLoaded + */ + public function theCovidVaccinationPassCanBeVerified(CoseSign1Tag $cbor): void + { + //Given + $structure = Signature1::create($cbor->getProtectedHeader(), $cbor->getPayload()); + $derSignature = ECSignature::toAsn1($cbor->getSignature()->normalize(), 64); + + //When + $isValid = openssl_verify((string) $structure, $derSignature, $this->getCertificate(), 'sha256'); + + //Then + static::assertSame(1, $isValid, 'Invalid signature'); + } + + private function getDecoder(): Decoder + { + $tagObjectManager = TagManager::create() + ->add(CoseSign1Tag::class) + ; + return Decoder::create($tagObjectManager, OtherObjectManager::create()); + } + + private function getCertificate(): string + { + return <<<'CODE_SAMPLE' +-----BEGIN CERTIFICATE----- +MIIGXjCCBBagAwIBAgIQQ50Ye2SIZLH9KhoLQeBFLjA9BgkqhkiG9w0BAQowMKAN +MAsGCWCGSAFlAwQCA6EaMBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgOiAwIBQDBg +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSEwHwYDVQQDExhE +LVRSVVNUIFRlc3QgQ0EgMi0yIDIwMTkxFzAVBgNVBGETDk5UUkRFLUhSQjc0MzQ2 +MB4XDTIxMDUwNjE5MjEzMFoXDTIyMDUwOTE5MjEzMFowfjELMAkGA1UEBhMCREUx +FDASBgNVBAoTC1ViaXJjaCBHbWJIMRQwEgYDVQQDEwtVYmlyY2ggR21iSDEOMAwG +A1UEBwwFS8O2bG4xHDAaBgNVBGETE0RUOkRFLVVHTk9UUFJPVklERUQxFTATBgNV +BAUTDENTTTAxNzI0OTU3MzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBAvcrr3 +ib8nS7E6vmdWJ6k7d6rqBHlD0U41OdMP2dJf9xqec4uOlwfJdOriwncgcWRpmli7 +vbFVP9w9dxX++ESjggJfMIICWzAfBgNVHSMEGDAWgBRQdpKgGuyBrpHC3agJUmg3 +3lGETzAtBggrBgEFBQcBAwQhMB8wCAYGBACORgEBMBMGBgQAjkYBBjAJBgcEAI5G +AQYCMIH+BggrBgEFBQcBAQSB8TCB7jArBggrBgEFBQcwAYYfaHR0cDovL3N0YWdp +bmcub2NzcC5kLXRydXN0Lm5ldDBHBggrBgEFBQcwAoY7aHR0cDovL3d3dy5kLXRy +dXN0Lm5ldC9jZ2ktYmluL0QtVFJVU1RfVGVzdF9DQV8yLTJfMjAxOS5jcnQwdgYI +KwYBBQUHMAKGamxkYXA6Ly9kaXJlY3RvcnkuZC10cnVzdC5uZXQvQ049RC1UUlVT +VCUyMFRlc3QlMjBDQSUyMDItMiUyMDIwMTksTz1ELVRydXN0JTIwR21iSCxDPURF +P2NBQ2VydGlmaWNhdGU/YmFzZT8wFwYDVR0gBBAwDjAMBgorBgEEAaU0AgICMIG/ +BgNVHR8EgbcwgbQwgbGgga6ggauGcGxkYXA6Ly9kaXJlY3RvcnkuZC10cnVzdC5u +ZXQvQ049RC1UUlVTVCUyMFRlc3QlMjBDQSUyMDItMiUyMDIwMTksTz1ELVRydXN0 +JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3SGN2h0dHA6Ly9j +cmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfdGVzdF9jYV8yLTJfMjAxOS5jcmww +HQYDVR0OBBYEFHgZ4+qwUzVKynAvnUl5YL6XWUK9MA4GA1UdDwEB/wQEAwIGwDA9 +BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCA6EaMBgGCSqGSIb3DQEBCDALBglg +hkgBZQMEAgOiAwIBQAOCAgEAHNnaBolwPHWiEZ6QKD6iIFFQhEiYzWvQxxvas1NQ +Sd/Xhw1Bth81aG5HRV1GCciD7Pa0yRl3wN3Dlixw2zdaU76kJlwYoXBbP6c0BQxV +lMFgWPEmG4Gt4+CrmcJ7EsrtYHeCZ7WiOuV1PJ2Pdb1Rsj1sxAhJxkv3I4eQrwlu +b3qHbQaT6uXV9X2V3qyqKPi0X12vzr9c0ca8D5GDD4+PgdGTraGU029YVeEKLe+F +qEgYVsEo0l9eSzNLp8HYuHr++5OU63pSBpTJmW7gI39VHkiEwZE87RkbuVQvFYcT +5rmqM9TIgcJVtHoUozhsitoMjL7zlx5aFTHMxnqSh7D7H0kwXgYM/wM8TQ++AV2v +gRK5q0mGp2MJPWuWRjtWrjxth71dF+pQr3Ls6hJXg1yMweVLzkd8mIzTnmtgtwP2 +pFgrSP1zW1B8ThBtb7ldXfcenP7qlOG/JyldLxy2hJjYgRST1TCPQMfeJ3yF/ONo +fxPMAefqfoadzm7BFPHBNOkaJIZ09+QJqZAS+pIoYFImrswjiykn5ZruspEYj0Tc +P5wzV01e+KTaHweT3Ii+j7ZJcUha+9OosmkhTc02g2BxzliB+PmexyY9JZkXPA8V +xF/0c/gGysbrPQtz3n09XfX/JX9Hh0cMPs4YZHk5xUpLsrKPivSCR1wJC7tCvC6J +1Xk= +-----END CERTIFICATE----- +CODE_SAMPLE; + } +} diff --git a/tests/Signature/ECSignature.php b/tests/Signature/ECSignature.php new file mode 100644 index 0000000..ebf0fd1 --- /dev/null +++ b/tests/Signature/ECSignature.php @@ -0,0 +1,72 @@ + 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 + ); + } + + private static function octetLength(string $data): int + { + return (int) (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; + } +} From f07828ee7079f2a72a2c0ce0fe763a2a3cfcfda4 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Wed, 24 May 2023 22:23:03 +0200 Subject: [PATCH 2/2] Fix composer --- composer.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 3204924..f5dda07 100644 --- a/composer.json +++ b/composer.json @@ -29,13 +29,13 @@ } }, "require-dev": { - "infection/infection": "^0.26.12", + "infection/infection": "^0.27", "phpstan/phpstan": "^1.7", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.2", "phpunit/phpunit": "^10.0", - "rector/rector": "^0.15", + "rector/rector": "^0.16", "symplify/easy-coding-standard": "^11.0", "symfony/phpunit-bridge": "^6.1", "ekino/phpstan-banned-code": "^1.0", @@ -50,7 +50,8 @@ }, "config": { "allow-plugins": { - "infection/extension-installer": false + "infection/extension-installer": true, + "phpstan/extension-installer": true } }, "suggest": {