From 970ebed5c9430086b7e325c132c13467af943abc Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Sun, 11 Feb 2018 22:44:55 -0500 Subject: [PATCH 01/17] Remove unreachable line --- src/Server.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Server.php b/src/Server.php index d3e892e..b1eeeba 100644 --- a/src/Server.php +++ b/src/Server.php @@ -166,7 +166,6 @@ public function authenticate(SignResponse $response): Registration { ->setKeyHandle($registration->getKeyHandleBinary()) ->setPublicKey($registration->getPublicKey()) ->setCounter($response->getCounter()); - return true; } /** From 748dce46f8a3106135e2ad7c87be9f7788c7151c Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Sun, 29 Apr 2018 17:27:24 -0700 Subject: [PATCH 02/17] Build on 7.0, 7.1, and 7.2, fix test issue in >=7.1 (#7) --- .travis.yml | 2 ++ tests/ServerTest.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9bb78c4..e33d957 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: php php: - '7.0' + - '7.1' + - '7.2' # From PHPUnit's config install: diff --git a/tests/ServerTest.php b/tests/ServerTest.php index aeba2cc..4297478 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -282,7 +282,7 @@ public function testRegisterThrowsWithChangedKeyHandle() { $json = file_get_contents(__DIR__.'/register_response.json'); $data = json_decode($json, true); $reg = $data['registrationData']; - $reg[70] = $reg[70] ^ 0xFF; // Invert a byte in the key handle + $reg[70] = chr(ord($reg[70]) + 1); // Change a byte in the key handle $data['registrationData'] = $reg; $response = RegisterResponse::fromJson(json_encode($data)); @@ -302,7 +302,7 @@ public function testRegisterThrowsWithChangedPubkey() { $json = file_get_contents(__DIR__.'/register_response.json'); $data = json_decode($json, true); $reg = $data['registrationData']; - $reg[3] = $reg[3] ^ 0xFF; // Invert a byte in the public key + $reg[3] = chr(ord($reg[3]) + 1); // Change a byte in the public key $data['registrationData'] = $reg; $response = RegisterResponse::fromJson(json_encode($data)); From 9ceabe0b5a7dadf57233738514e07e9db0f70ad3 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Sun, 29 Apr 2018 17:51:56 -0700 Subject: [PATCH 03/17] Add build status to README (#6) --- .gitignore | 2 + .travis.yml | 3 + README.md | 3 + composer.json | 4 +- composer.lock | 1150 --------------------- phpunit.xml | 24 + tests/AppIdTraitTest.php | 2 +- tests/AttestationCertificateTraitTest.php | 2 +- tests/ChallengeTraitTest.php | 2 +- tests/ClientDataTest.php | 2 +- tests/ClientErrorExceptionTest.php | 2 +- tests/ECPublicKeyTraitTest.php | 2 +- tests/InvalidDataExceptionTest.php | 2 +- tests/KeyHandleTraitTest.php | 2 +- tests/RegisterRequestTest.php | 2 +- tests/RegisterResponseTest.php | 2 +- tests/RegistrationTest.php | 2 +- tests/ResponseTraitTest.php | 2 +- tests/SecurityExceptionTest.php | 2 +- tests/ServerTest.php | 6 +- tests/SignRequestTest.php | 2 +- tests/SignResponseTest.php | 2 +- tests/VersionTraitTest.php | 2 +- tests/functionsTest.php | 2 +- 24 files changed, 54 insertions(+), 1172 deletions(-) delete mode 100644 composer.lock create mode 100644 phpunit.xml diff --git a/.gitignore b/.gitignore index 38d8e6d..237c7bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /examples/users/* /examples/vendor/ /vendor/ +# Suggested for libraries +composer.lock diff --git a/.travis.yml b/.travis.yml index e33d957..d75f4bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,3 +9,6 @@ install: - travis_retry composer install --no-interaction --prefer-source script: php -d mbstring.func_overload=7 vendor/bin/phpunit --coverage-text --whitelist src/ tests/ + +after_success: + - travis_retry php vendor/bin/php-coveralls diff --git a/README.md b/README.md index 0d30fcc..95abd56 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ A PHP implementation of the FIDO U2F authentication standard +[![Build Status](https://travis-ci.org/Firehed/u2f-php.svg?branch=master)](https://travis-ci.org/Firehed/u2f-php) +[![Coverage Status](https://coveralls.io/repos/github/Firehed/u2f-php/badge.svg)](https://coveralls.io/github/Firehed/u2f-php) + ## Introduction U2F, or Universal Second Factor, is a new authentication protocol designed "to augment the security of their existing password infrastructure by adding a strong second factor to user login"[1](https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-overview.html#background). It allows websites to replace the need for a companion app (such as Google Authenticator) with a single hardware token that will work across any website supporting the U2F protocol. diff --git a/composer.json b/composer.json index 7214806..1f6b310 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,8 @@ "php": ">=7.0" }, "require-dev": { - "firehed/arctools": "^1", - "phpunit/phpunit": "^5.2" + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^6.0 || ^7.0" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock deleted file mode 100644 index a821be3..0000000 --- a/composer.lock +++ /dev/null @@ -1,1150 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "hash": "619e41093d2fdb704a016f75703d1297", - "content-hash": "6206adc173e7b48e1071edb7819d70a8", - "packages": [], - "packages-dev": [ - { - "name": "doctrine/instantiator", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", - "shasum": "" - }, - "require": { - "php": ">=5.3,<8.0-DEV" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2015-06-14 21:17:01" - }, - { - "name": "firehed/arctools", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/Firehed/arctools.git", - "reference": "033cc82e777dd10a5416f544b37afb84877332ca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Firehed/arctools/zipball/033cc82e777dd10a5416f544b37afb84877332ca", - "reference": "033cc82e777dd10a5416f544b37afb84877332ca", - "shasum": "" - }, - "require-dev": { - "phpunit/phpunit": "~5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Firehed\\Arctools\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eric Stern", - "email": "eric@ericstern.com" - } - ], - "description": "Easily integrate with Arcanist and libphutil", - "time": "2015-10-16 22:30:55" - }, - { - "name": "myclabs/deep-copy", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e3abefcd7f106677fd352cd7c187d6c969aa9ddc", - "reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "time": "2015-11-07 22:20:37" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "time": "2015-02-03 12:10:50" - }, - { - "name": "phpspec/prophecy", - "version": "v1.6.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972", - "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "~2.0", - "sebastian/comparator": "~1.1", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "phpspec/phpspec": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5.x-dev" - } - }, - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2016-02-15 07:46:21" - }, - { - "name": "phpunit/php-code-coverage", - "version": "3.3.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "fe33716763b604ade4cb442c0794f5bd5ad73004" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe33716763b604ade4cb442c0794f5bd5ad73004", - "reference": "fe33716763b604ade4cb442c0794f5bd5ad73004", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "^1.4.2", - "sebastian/code-unit-reverse-lookup": "~1.0", - "sebastian/environment": "^1.3.2", - "sebastian/version": "~1.0|~2.0" - }, - "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "~5" - }, - "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.2.1", - "ext-xmlwriter": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2016-03-03 08:49:08" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2015-06-21 13:08:43" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21 13:50:34" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.7", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2015-06-21 08:01:12" - }, - { - "name": "phpunit/php-token-stream", - "version": "1.4.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2015-09-15 10:49:45" - }, - { - "name": "phpunit/phpunit", - "version": "5.2.12", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "6f0948bab32270352f97d1913d82a49338dcb0da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6f0948bab32270352f97d1913d82a49338dcb0da", - "reference": "6f0948bab32270352f97d1913d82a49338dcb0da", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", - "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "^3.3.0", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": ">=1.0.6", - "phpunit/phpunit-mock-objects": ">=3.0.5", - "sebastian/comparator": "~1.1", - "sebastian/diff": "~1.2", - "sebastian/environment": "~1.3", - "sebastian/exporter": "~1.2", - "sebastian/global-state": "~1.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0|~2.0", - "symfony/yaml": "~2.1|~3.0" - }, - "suggest": { - "phpunit/php-invoker": "~1.1" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2016-03-15 05:59:58" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "3.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "49bc700750196c04dd6bc2c4c99cb632b893836b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/49bc700750196c04dd6bc2c4c99cb632b893836b", - "reference": "49bc700750196c04dd6bc2c4c99cb632b893836b", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": ">=5.6", - "phpunit/php-text-template": "~1.2", - "sebastian/exporter": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "~5" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2015-12-08 08:47:06" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "~5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2016-02-13 06:45:14" - }, - { - "name": "sebastian/comparator", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2015-07-26 15:48:44" - }, - { - "name": "sebastian/diff", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff" - ], - "time": "2015-12-08 07:14:41" - }, - { - "name": "sebastian/environment", - "version": "1.3.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", - "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2016-02-26 18:40:46" - }, - { - "name": "sebastian/exporter", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "7ae5513327cb536431847bcc0c10edba2701064e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", - "reference": "7ae5513327cb536431847bcc0c10edba2701064e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2015-06-21 07:55:53" - }, - { - "name": "sebastian/global-state", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2015-10-12 03:26:01" - }, - { - "name": "sebastian/recursion-context", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-11-11 19:50:13" - }, - { - "name": "sebastian/resource-operations", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "shasum": "" - }, - "require": { - "php": ">=5.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28 20:34:47" - }, - { - "name": "sebastian/version", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-02-04 12:56:52" - }, - { - "name": "symfony/yaml", - "version": "v3.0.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/b5ba64cd67ecd6887f63868fa781ca094bd1377c", - "reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2016-02-23 15:16:06" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=7.0" - }, - "platform-dev": [] -} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..6d0d482 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,24 @@ + + + + + tests + + + + + + src + + + + + + diff --git a/tests/AppIdTraitTest.php b/tests/AppIdTraitTest.php index 25ca4e4..ee840f3 100644 --- a/tests/AppIdTraitTest.php +++ b/tests/AppIdTraitTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class AppIdTraitTest extends \PHPUnit_Framework_TestCase +class AppIdTraitTest extends \PHPUnit\Framework\TestCase { /** diff --git a/tests/AttestationCertificateTraitTest.php b/tests/AttestationCertificateTraitTest.php index bd5466a..ea58e5e 100644 --- a/tests/AttestationCertificateTraitTest.php +++ b/tests/AttestationCertificateTraitTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class AttestationCertificateTraitTest extends \PHPUnit_Framework_TestCase +class AttestationCertificateTraitTest extends \PHPUnit\Framework\TestCase { /** * @covers ::getAttestationCertificateBinary diff --git a/tests/ChallengeTraitTest.php b/tests/ChallengeTraitTest.php index f026e84..eb027be 100644 --- a/tests/ChallengeTraitTest.php +++ b/tests/ChallengeTraitTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class ChallengeTraitTest extends \PHPUnit_Framework_TestCase +class ChallengeTraitTest extends \PHPUnit\Framework\TestCase { /** diff --git a/tests/ClientDataTest.php b/tests/ClientDataTest.php index b9c5f1a..3617f04 100644 --- a/tests/ClientDataTest.php +++ b/tests/ClientDataTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class ClientDataTest extends \PHPUnit_Framework_TestCase +class ClientDataTest extends \PHPUnit\Framework\TestCase { /** diff --git a/tests/ClientErrorExceptionTest.php b/tests/ClientErrorExceptionTest.php index eecbe1f..dcc95b3 100644 --- a/tests/ClientErrorExceptionTest.php +++ b/tests/ClientErrorExceptionTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class ClientErrorExceptionTest extends \PHPUnit_Framework_TestCase +class ClientErrorExceptionTest extends \PHPUnit\Framework\TestCase { /** * @covers ::__construct diff --git a/tests/ECPublicKeyTraitTest.php b/tests/ECPublicKeyTraitTest.php index 9daacc0..a5e5010 100644 --- a/tests/ECPublicKeyTraitTest.php +++ b/tests/ECPublicKeyTraitTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class ECPublicKeyTraitTest extends \PHPUnit_Framework_TestCase +class ECPublicKeyTraitTest extends \PHPUnit\Framework\TestCase { /** diff --git a/tests/InvalidDataExceptionTest.php b/tests/InvalidDataExceptionTest.php index 933b9d9..5ff9e46 100644 --- a/tests/InvalidDataExceptionTest.php +++ b/tests/InvalidDataExceptionTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class InvalidDataExceptionTest extends \PHPUnit_Framework_TestCase +class InvalidDataExceptionTest extends \PHPUnit\Framework\TestCase { /** * @covers ::__construct diff --git a/tests/KeyHandleTraitTest.php b/tests/KeyHandleTraitTest.php index 6723e44..af543d3 100644 --- a/tests/KeyHandleTraitTest.php +++ b/tests/KeyHandleTraitTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class KeyHandleTraitTest extends \PHPUnit_Framework_TestCase +class KeyHandleTraitTest extends \PHPUnit\Framework\TestCase { /** * @covers ::getKeyHandleBinary diff --git a/tests/RegisterRequestTest.php b/tests/RegisterRequestTest.php index 9eb3a13..6ad8a8e 100644 --- a/tests/RegisterRequestTest.php +++ b/tests/RegisterRequestTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class RegisterRequestTest extends \PHPUnit_Framework_TestCase +class RegisterRequestTest extends \PHPUnit\Framework\TestCase { /** diff --git a/tests/RegisterResponseTest.php b/tests/RegisterResponseTest.php index 4e09a50..8003f2c 100644 --- a/tests/RegisterResponseTest.php +++ b/tests/RegisterResponseTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class RegisterResponseTest extends \PHPUnit_Framework_TestCase +class RegisterResponseTest extends \PHPUnit\Framework\TestCase { private $validClientData = 'eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCIsImNoYWxsZW5nZSI6IkJyQWN4dGIxOWFYNTRoN0Y2T0NKWVptQ3prZHlHV0Nib3NEcHpNMUh2MkUiLCJvcmlnaW4iOiJodHRwczovL3UyZi5lcmljc3Rlcm4uY29tIiwiY2lkX3B1YmtleSI6IiJ9'; diff --git a/tests/RegistrationTest.php b/tests/RegistrationTest.php index 7a73dea..08fcd89 100644 --- a/tests/RegistrationTest.php +++ b/tests/RegistrationTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class RegistrationTest extends \PHPUnit_Framework_TestCase +class RegistrationTest extends \PHPUnit\Framework\TestCase { /** diff --git a/tests/ResponseTraitTest.php b/tests/ResponseTraitTest.php index 7a81aad..d5db1cb 100644 --- a/tests/ResponseTraitTest.php +++ b/tests/ResponseTraitTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class ResponseTraitTest extends \PHPUnit_Framework_TestCase +class ResponseTraitTest extends \PHPUnit\Framework\TestCase { private $trait; diff --git a/tests/SecurityExceptionTest.php b/tests/SecurityExceptionTest.php index 0ed935b..1c5c675 100644 --- a/tests/SecurityExceptionTest.php +++ b/tests/SecurityExceptionTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class SecurityExceptionTest extends \PHPUnit_Framework_TestCase +class SecurityExceptionTest extends \PHPUnit\Framework\TestCase { /** * @covers ::__construct diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 4297478..2a844eb 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -10,7 +10,7 @@ * @covers :: * @covers :: */ -class ServerTest extends \PHPUnit_Framework_TestCase +class ServerTest extends \PHPUnit\Framework\TestCase { const APP_ID = 'https://u2f.example.com'; const ENCODED_KEY_HANDLE = 'JUnVTStPn-V2-bCu0RlvPbukBpHTD5Mi1ZGglDOcN0vD45rnTD0BXdkRt78huTwJ7tVaxTqSetHjr22tCjmYLQ'; @@ -222,7 +222,7 @@ public function testRegisterWorksWithCAList() { $this->server->setTrustedCAs($CAs); try { - $this->server + $reg = $this->server ->setRegisterRequest($request) ->register($response); } catch (SecurityException $e) { @@ -231,7 +231,7 @@ public function testRegisterWorksWithCAList() { } throw $e; } - // Implicit pass - no exceptions should be thrown at all + $this->assertInstanceOf(Registration::class, $reg); } /** diff --git a/tests/SignRequestTest.php b/tests/SignRequestTest.php index 1959c75..d9ef577 100644 --- a/tests/SignRequestTest.php +++ b/tests/SignRequestTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class SignRequestTest extends \PHPUnit_Framework_TestCase +class SignRequestTest extends \PHPUnit\Framework\TestCase { /** * @covers ::jsonSerialize diff --git a/tests/SignResponseTest.php b/tests/SignResponseTest.php index cee3fb8..0efba66 100644 --- a/tests/SignResponseTest.php +++ b/tests/SignResponseTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class SignResponseTest extends \PHPUnit_Framework_TestCase +class SignResponseTest extends \PHPUnit\Framework\TestCase { const JSON_FORMAT = '{"keyHandle":"%s","clientData":"%s","signatureData":"%s"}'; diff --git a/tests/VersionTraitTest.php b/tests/VersionTraitTest.php index 154f415..db859ce 100644 --- a/tests/VersionTraitTest.php +++ b/tests/VersionTraitTest.php @@ -8,7 +8,7 @@ * @covers :: * @covers :: */ -class VersionTraitTest extends \PHPUnit_Framework_TestCase +class VersionTraitTest extends \PHPUnit\Framework\TestCase { /** diff --git a/tests/functionsTest.php b/tests/functionsTest.php index 85b9e2a..d9044ea 100644 --- a/tests/functionsTest.php +++ b/tests/functionsTest.php @@ -3,7 +3,7 @@ namespace Firehed\U2F; -class functionsTest extends \PHPUnit_Framework_TestCase +class functionsTest extends \PHPUnit\Framework\TestCase { From f52ad0b04dc15189a9f8a3421dfd04cc1ab2adf3 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Sun, 29 Apr 2018 19:01:09 -0700 Subject: [PATCH 04/17] Add static analysis tools into CI process (#8) Fixes a ton of code style issues along the way. --- .travis.yml | 5 +- composer.json | 4 +- phpcs.xml | 4 + src/AppIdTrait.php | 10 +- src/AttestationCertificateTrait.php | 16 +- src/ChallengeProvider.php | 1 - src/ChallengeTrait.php | 6 +- src/ClientData.php | 32 +- src/ClientErrorException.php | 4 +- src/ECPublicKeyTrait.php | 19 +- src/InvalidDataException.php | 5 +- src/KeyHandleTrait.php | 10 +- src/RegisterRequest.php | 3 +- src/RegisterResponse.php | 41 ++- src/Registration.php | 8 +- src/ResponseTrait.php | 17 +- src/SecurityException.php | 11 +- src/Server.php | 94 +++--- src/SignRequest.php | 4 +- src/SignResponse.php | 10 +- src/VersionTrait.php | 3 +- src/functions.php | 28 +- tests/AppIdTraitTest.php | 31 +- tests/AttestationCertificateTraitTest.php | 42 ++- tests/ChallengeTraitTest.php | 17 +- tests/ClientDataTest.php | 51 +++- tests/ClientErrorExceptionTest.php | 20 +- tests/ECPublicKeyTraitTest.php | 32 +- .../{functionsTest.php => FunctionsTest.php} | 25 +- tests/InvalidDataExceptionTest.php | 28 +- tests/KeyHandleTraitTest.php | 28 +- tests/RegisterRequestTest.php | 48 ++- tests/RegisterResponseTest.php | 100 +++++-- tests/RegistrationTest.php | 20 +- tests/ResponseTraitTest.php | 51 +++- tests/SecurityExceptionTest.php | 19 +- tests/ServerTest.php | 276 ++++++++++++------ tests/SignRequestTest.php | 69 +++-- tests/SignResponseTest.php | 142 ++++++--- tests/VersionTraitTest.php | 12 +- 40 files changed, 911 insertions(+), 435 deletions(-) create mode 100644 phpcs.xml rename tests/{functionsTest.php => FunctionsTest.php} (62%) diff --git a/.travis.yml b/.travis.yml index d75f4bc..b8d8ca8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,10 @@ php: install: - travis_retry composer install --no-interaction --prefer-source -script: php -d mbstring.func_overload=7 vendor/bin/phpunit --coverage-text --whitelist src/ tests/ +script: + - php -d mbstring.func_overload=7 vendor/bin/phpunit --coverage-text --whitelist src/ tests/ + - vendor/bin/phpcs src tests + - vendor/bin/phpstan analyse --no-progress -l7 src tests after_success: - travis_retry php vendor/bin/php-coveralls diff --git a/composer.json b/composer.json index 1f6b310..aa526cd 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,9 @@ }, "require-dev": { "php-coveralls/php-coveralls": "^2.0", - "phpunit/phpunit": "^6.0 || ^7.0" + "phpstan/phpstan": "^0.9.2", + "phpunit/phpunit": "^6.0 || ^7.0", + "squizlabs/php_codesniffer": "^3.2" }, "autoload": { "psr-4": { diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..bb7e60c --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/AppIdTrait.php b/src/AppIdTrait.php index 8a25558..8bf52cf 100644 --- a/src/AppIdTrait.php +++ b/src/AppIdTrait.php @@ -7,11 +7,13 @@ trait AppIdTrait { private $appId; - public function getAppId(): string { + public function getAppId(): string + { return $this->appId; } - public function setAppId(string $appId): self { + public function setAppId(string $appId): self + { $this->appId = $appId; return $this; } @@ -19,8 +21,8 @@ public function setAppId(string $appId): self { /** * @return the raw SHA-256 hash of the App ID */ - public function getApplicationParameter(): string { + public function getApplicationParameter(): string + { return hash('sha256', $this->appId, true); } - } diff --git a/src/AttestationCertificateTrait.php b/src/AttestationCertificateTrait.php index e3787b0..8b448f8 100644 --- a/src/AttestationCertificateTrait.php +++ b/src/AttestationCertificateTrait.php @@ -10,34 +10,38 @@ trait AttestationCertificateTrait private $attest = ''; // Binary string of attestation certificate (from device issuer) - public function getAttestationCertificateBinary(): string { + public function getAttestationCertificateBinary(): string + { return base64_decode($this->attest); } // PEM formatted cert - public function getAttestationCertificatePem(): string { + public function getAttestationCertificatePem(): string + { $pem = "-----BEGIN CERTIFICATE-----\r\n"; $pem .= chunk_split($this->attest, 64); $pem .= "-----END CERTIFICATE-----"; return $pem; } - public function setAttestationCertificate(string $cert): self { + public function setAttestationCertificate(string $cert): self + { // In the future, this may make assertions about the cert formatting; // right now, we're going to leave it be. $this->attest = base64_encode($cert); return $this; } - public function verifyIssuerAgainstTrustedCAs(array $trusted_cas): bool { + public function verifyIssuerAgainstTrustedCAs(array $trusted_cas): bool + { $result = openssl_x509_checkpurpose( $this->getAttestationCertificatePem(), \X509_PURPOSE_ANY, - $trusted_cas); + $trusted_cas + ); if ($result !== true) { throw new SecurityException(SecurityException::NO_TRUSTED_CA); } return $result; } - } diff --git a/src/ChallengeProvider.php b/src/ChallengeProvider.php index 23df621..9aa5681 100644 --- a/src/ChallengeProvider.php +++ b/src/ChallengeProvider.php @@ -7,5 +7,4 @@ interface ChallengeProvider { public function getChallenge(): string; - } diff --git a/src/ChallengeTrait.php b/src/ChallengeTrait.php index a0bdb2d..3906634 100644 --- a/src/ChallengeTrait.php +++ b/src/ChallengeTrait.php @@ -7,11 +7,13 @@ trait ChallengeTrait private $challenge; // B64-websafe value (at no point is the binary version used) - public function getChallenge(): string { + public function getChallenge(): string + { return ($this->challenge); } - public function setChallenge(string $challenge): self { + public function setChallenge(string $challenge): self + { // TODO: make immutable $this->challenge = $challenge; return $this; diff --git a/src/ClientData.php b/src/ClientData.php index 027269b..5713eca 100644 --- a/src/ClientData.php +++ b/src/ClientData.php @@ -14,7 +14,8 @@ class ClientData implements JsonSerializable, ChallengeProvider private $origin; private $typ; - public static function fromJson(string $json) { + public static function fromJson(string $json) + { $data = json_decode($json, true); if (json_last_error() !== \JSON_ERROR_NONE) { throw new IDE(IDE::MALFORMED_DATA, 'json'); @@ -30,17 +31,18 @@ public static function fromJson(string $json) { /** * Checks the 'typ' field against the allowed types in the U2F spec (sec. * 7.1) - * @param $type the 'typ' value + * @param string $type the 'typ' value * @return $this * @throws InvalidDataException if a non-conforming value is provided */ - private function setType(string $type): self { + private function setType(string $type): self + { switch ($type) { - case 'navigator.id.getAssertion': // fall through - case 'navigator.id.finishEnrollment': - break; - default: - throw new IDE(IDE::MALFORMED_DATA, 'typ'); + case 'navigator.id.getAssertion': // fall through + case 'navigator.id.finishEnrollment': + break; + default: + throw new IDE(IDE::MALFORMED_DATA, 'typ'); } $this->typ = $type; return $this; @@ -49,12 +51,13 @@ private function setType(string $type): self { /** * Checks for the presence of $key in $data. Returns the value if found, * throws an InvalidDataException if missing - * @param $key The array key to check - * @param $data The array to check in + * @param string $key The array key to check + * @param array $data The array to check in * @return mixed The data, if present * @throws InvalidDataException if not prsent */ - private function validateKey(string $key, array $data) { + private function validateKey(string $key, array $data) + { if (!array_key_exists($key, $data)) { throw new IDE(IDE::MISSING_KEY, $key); } @@ -62,12 +65,14 @@ private function validateKey(string $key, array $data) { } // Returns the SHA256 hash of this object per the raw message formats spec - public function getChallengeParameter(): string { + public function getChallengeParameter(): string + { $json = json_encode($this, \JSON_UNESCAPED_SLASHES); return hash('sha256', $json, true); } - public function jsonSerialize() { + public function jsonSerialize() + { return [ 'typ' => $this->typ, 'challenge' => $this->getChallenge(), @@ -75,5 +80,4 @@ public function jsonSerialize() { 'cid_pubkey' => $this->cid_pubkey, ]; } - } diff --git a/src/ClientErrorException.php b/src/ClientErrorException.php index 9944045..a4f082c 100644 --- a/src/ClientErrorException.php +++ b/src/ClientErrorException.php @@ -2,11 +2,13 @@ declare(strict_types=1); namespace Firehed\U2F; + use Exception; class ClientErrorException extends Exception { - public function __construct(int $code) { + public function __construct(int $code) + { parent::__construct(ClientError::MESSAGES[$code] ?? '', $code); } } diff --git a/src/ECPublicKeyTrait.php b/src/ECPublicKeyTrait.php index ee5f0a3..527beeb 100644 --- a/src/ECPublicKeyTrait.php +++ b/src/ECPublicKeyTrait.php @@ -2,6 +2,7 @@ declare(strict_types=1); namespace Firehed\U2F; + use Firehed\U2F\InvalidDataException as IDE; trait ECPublicKeyTrait @@ -10,19 +11,21 @@ trait ECPublicKeyTrait private $pubKey = ''; // Binary string of public key - public function getPublicKey(): string { + public function getPublicKey(): string + { return base64_decode($this->pubKey); } // Prepends the pubkey format headers and builds a pem file from the raw // public key component - public function getPublicKeyPem(): string { + public function getPublicKeyPem(): string + { $key = $this->getPublicKey(); // Described in RFC 5480 // Just use an OID calculator to figure out *that* encoding $der = hex2bin( - '3059' // SEQUENCE, length 89 + '3059' // SEQUENCE, length 89 .'3013' // SEQUENCE, length 19 .'0607' // OID, length 7 .'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key @@ -39,11 +42,14 @@ public function getPublicKeyPem(): string { return $pem; } - public function setPublicKey(string $key): self { + public function setPublicKey(string $key): self + { // RFC5480 2.2 - must be uncompressed value if ($key[0] !== "\x04") { - throw new IDE(IDE::MALFORMED_DATA, - 'public key: first byte not x04 (uncompressed)'); + throw new IDE( + IDE::MALFORMED_DATA, + 'public key: first byte not x04 (uncompressed)' + ); } if (strlen($key) !== 65) { throw new IDE(IDE::PUBLIC_KEY_LENGTH, '65'); @@ -51,5 +57,4 @@ public function setPublicKey(string $key): self { $this->pubKey = base64_encode($key); return $this; } - } diff --git a/src/InvalidDataException.php b/src/InvalidDataException.php index 948ca1d..24d3544 100644 --- a/src/InvalidDataException.php +++ b/src/InvalidDataException.php @@ -2,6 +2,7 @@ declare(strict_types=1); namespace Firehed\U2F; + use Exception; class InvalidDataException extends Exception @@ -16,11 +17,11 @@ class InvalidDataException extends Exception self::PUBLIC_KEY_LENGTH => 'Public key length invalid, must be %s bytes', ]; - public function __construct(int $code, string ...$args) { + public function __construct(int $code, string ...$args) + { $format = self::MESSAGES[$code] ?? 'Default message'; $message = sprintf($format, ...$args); parent::__construct($message, $code); } - } diff --git a/src/KeyHandleTrait.php b/src/KeyHandleTrait.php index 4e03537..2111fec 100644 --- a/src/KeyHandleTrait.php +++ b/src/KeyHandleTrait.php @@ -7,18 +7,20 @@ trait KeyHandleTrait private $keyHandle; // Binary string of key handle - public function getKeyHandleBinary(): string { + public function getKeyHandleBinary(): string + { return base64_decode($this->keyHandle); } // B64-websafe value - public function getKeyHandleWeb(): string { + public function getKeyHandleWeb(): string + { return toBase64Web($this->getKeyHandleBinary()); } // Binary value - public function setKeyHandle(string $keyHandle): self { + public function setKeyHandle(string $keyHandle): self + { // TODO: make immutable $this->keyHandle = base64_encode($keyHandle); return $this; } - } diff --git a/src/RegisterRequest.php b/src/RegisterRequest.php index 6112817..494f374 100644 --- a/src/RegisterRequest.php +++ b/src/RegisterRequest.php @@ -10,7 +10,8 @@ class RegisterRequest implements JsonSerializable, ChallengeProvider use ChallengeTrait; use VersionTrait; - public function jsonSerialize() { + public function jsonSerialize() + { return [ "version" => $this->version, "challenge" => $this->getChallenge(), diff --git a/src/RegisterResponse.php b/src/RegisterResponse.php index 4fda0d3..be0b6df 100644 --- a/src/RegisterResponse.php +++ b/src/RegisterResponse.php @@ -2,6 +2,7 @@ declare(strict_types=1); namespace Firehed\U2F; + use Firehed\U2F\InvalidDataException as IDE; class RegisterResponse @@ -10,7 +11,8 @@ class RegisterResponse use ECPublicKeyTrait; use ResponseTrait; - protected function parseResponse(array $response): self { + protected function parseResponse(array $response): self + { $this->validateKeyInArray('registrationData', $response); // Binary string as defined by // U2F 1.0 Raw Message Format Sec. 4.3 @@ -19,16 +21,20 @@ protected function parseResponse(array $response): self { // Basic fixed length check if (strlen($regData) < 67) { - throw new IDE(IDE::MALFORMED_DATA, - 'registrationData is missing information'); + throw new IDE( + IDE::MALFORMED_DATA, + 'registrationData is missing information' + ); } $offset = 0; // Number of bytes read so far (think fread/fseek) $reserved = ord($regData[$offset]); if ($reserved !== 5) { - throw new IDE(IDE::MALFORMED_DATA, - 'reserved byte'); + throw new IDE( + IDE::MALFORMED_DATA, + 'reserved byte' + ); } $offset += 1; @@ -40,8 +46,10 @@ protected function parseResponse(array $response): self { // Dynamic length check through key handle if (strlen($regData) < $offset+$keyHandleLength) { - throw new IDE(IDE::MALFORMED_DATA, - 'key handle length'); + throw new IDE( + IDE::MALFORMED_DATA, + 'key handle length' + ); } $this->setKeyHandle(substr($regData, $offset, $keyHandleLength)); $offset += $keyHandleLength; @@ -59,16 +67,20 @@ protected function parseResponse(array $response): self { $remain = substr($regData, $offset); $b0 = ord($remain[0]); if (($b0 & 0x1F) != 0x10) { - throw new IDE(IDE::MALFORMED_DATA, - 'starting byte of attestation certificate'); + throw new IDE( + IDE::MALFORMED_DATA, + 'starting byte of attestation certificate' + ); } $length = ord($remain[1]); if (($length & 0x80) == 0x80) { $needed = $length ^ 0x80; if ($needed > 4) { // This would be a >4GB cert, reject it out of hand - throw new IDE(IDE::MALFORMED_DATA, - 'certificate length'); + throw new IDE( + IDE::MALFORMED_DATA, + 'certificate length' + ); } $bytes = 0; // Start 2 bytes in, for SEQUENCE and its LENGTH @@ -83,8 +95,10 @@ protected function parseResponse(array $response): self { // data, in case a malformed cert was provided to trigger an overflow // during parsing if ($length + $offset > strlen($regData)) { - throw new IDE(IDE::MALFORMED_DATA, - 'certificate and sigature length'); + throw new IDE( + IDE::MALFORMED_DATA, + 'certificate and sigature length' + ); } $this->setAttestationCertificate(substr($regData, $offset, $length)); $offset += $length; @@ -94,5 +108,4 @@ protected function parseResponse(array $response): self { return $this; } - } diff --git a/src/Registration.php b/src/Registration.php index 546c6d1..e3d786f 100644 --- a/src/Registration.php +++ b/src/Registration.php @@ -2,6 +2,7 @@ declare(strict_types=1); namespace Firehed\U2F; + use OutOfBoundsException; class Registration @@ -12,15 +13,16 @@ class Registration private $counter = -1; - public function getCounter(): int { + public function getCounter(): int + { return $this->counter; } - public function setCounter(int $counter): self { + public function setCounter(int $counter): self + { if ($counter < 0) { throw new OutOfBoundsException('Counter may not be negative'); } $this->counter = $counter; return $this; } - } diff --git a/src/ResponseTrait.php b/src/ResponseTrait.php index 17eef71..77ffc1b 100644 --- a/src/ResponseTrait.php +++ b/src/ResponseTrait.php @@ -2,6 +2,7 @@ declare(strict_types=1); namespace Firehed\U2F; + use Firehed\U2F\InvalidDataException as IDE; trait ResponseTrait @@ -14,20 +15,24 @@ trait ResponseTrait private $signature = ''; // Binary string of signature - public function getSignature(): string { + public function getSignature(): string + { return base64_decode($this->signature); } - public function getClientData(): ClientData { + public function getClientData(): ClientData + { return $this->clientData; } - protected function setSignature(string $signature): self { + protected function setSignature(string $signature): self + { $this->signature = base64_encode($signature); return $this; } - public static function fromJson(string $json): self { + public static function fromJson(string $json): self + { $data = json_decode($json, true); if (json_last_error() !== \JSON_ERROR_NONE) { throw new IDE(IDE::MALFORMED_DATA, 'JSON'); @@ -44,7 +49,8 @@ public static function fromJson(string $json): self { abstract protected function parseResponse(array $response): self; - private function validateKeyInArray(string $key, array $data): bool { + private function validateKeyInArray(string $key, array $data): bool + { if (!isset($data[$key])) { throw new IDE(IDE::MISSING_KEY, $key); } @@ -53,5 +59,4 @@ private function validateKeyInArray(string $key, array $data): bool { } return true; } - } diff --git a/src/SecurityException.php b/src/SecurityException.php index 01f9ed1..d31431d 100644 --- a/src/SecurityException.php +++ b/src/SecurityException.php @@ -2,6 +2,7 @@ declare(strict_types=1); namespace Firehed\U2F; + use Exception; class SecurityException extends Exception @@ -14,14 +15,18 @@ class SecurityException extends Exception const MESSAGES = [ self::SIGNATURE_INVALID => 'Signature verification failed', - self::COUNTER_USED => 'Response counter value is too low, indicating a possible replay attack or cloned token. It is also possible but unlikely that the token\'s internal counter wrapped around. This token should be invalidated or flagged for review.', + self::COUNTER_USED => + 'Response counter value is too low, indicating a possible replay '. + 'attack or cloned token. It is also possible but unlikely that '. + 'the token\'s internal counter wrapped around. This token should '. + 'be invalidated or flagged for review.', self::CHALLENGE_MISMATCH => 'Response challenge does not match request', self::KEY_HANDLE_UNRECOGNIZED => 'Key handle has not been registered', self::NO_TRUSTED_CA => 'The attestation certificate was not signed by any trusted Certificate Authority', ]; - public function __construct(int $code) { + public function __construct(int $code) + { parent::__construct(self::MESSAGES[$code] ?? '', $code); } - } diff --git a/src/Server.php b/src/Server.php index b1eeeba..39d3742 100644 --- a/src/Server.php +++ b/src/Server.php @@ -56,26 +56,31 @@ class Server * value, which *must* be persisted for the next authentication. If any * verification component fails, a `SE` will be thrown. * - * @param SignResponse the parsed response from the user + * @param SignResponse $response the parsed response from the user * @return Registration if authentication succeeds * @throws SE if authentication fails * @throws BadMethodCallException if a precondition is not met */ - public function authenticate(SignResponse $response): Registration { + public function authenticate(SignResponse $response): Registration + { if (!$this->registrations) { throw new BadMethodCallException( 'Before calling authenticate(), provide `Registration`s with '. - 'setRegistrations()'); + 'setRegistrations()' + ); } if (!$this->signRequests) { throw new BadMethodCallException( 'Before calling authenticate(), provide `SignRequest`s with '. - 'setSignRequests()'); + 'setSignRequests()' + ); } // Search for the registration to use based on the Key Handle - $registration = $this->findObjectWithKeyHandle($this->registrations, - $response->getKeyHandleBinary()); + $registration = $this->findObjectWithKeyHandle( + $this->registrations, + $response->getKeyHandleBinary() + ); if (!$registration) { // This would suggest either some sort of forgery attempt or // a hilariously-broken token responding to handles it doesn't @@ -84,8 +89,10 @@ public function authenticate(SignResponse $response): Registration { } // Search for the Signing Request to use based on the Key Handle - $request = $this->findObjectWithKeyHandle($this->signRequests, - $registration->getKeyHandleBinary()); + $request = $this->findObjectWithKeyHandle( + $this->signRequests, + $registration->getKeyHandleBinary() + ); if (!$request) { // Similar to above, there is a bizarre mismatch between the known // possible sign requests and the key handle determined above. This @@ -104,7 +111,8 @@ public function authenticate(SignResponse $response): Registration { // U2F Spec: // https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#authentication-response-message-success - $to_verify = sprintf('%s%s%s%s', + $to_verify = sprintf( + '%s%s%s%s', $request->getApplicationParameter(), chr($response->getUserPresenceByte()), pack('N', $response->getCounter()), @@ -174,22 +182,25 @@ public function authenticate(SignResponse $response): Registration { * returns a Registration object; if not, a SE will be * thrown and attempt to register the key must be aborted. * - * @param The response to verify + * @param RegisterResponse $resp The response to verify * @return Registration if the response is proven authentic * @throws SE if the response cannot be proven authentic * @throws BadMethodCallException if a precondition is not met */ - public function register(RegisterResponse $resp): Registration { + public function register(RegisterResponse $resp): Registration + { if (!$this->registerRequest) { throw new BadMethodCallException( 'Before calling register(), provide a RegisterRequest '. - 'with setRegisterRequest()'); + 'with setRegisterRequest()' + ); } $this->validateChallenge($resp->getClientData(), $this->registerRequest); // Check the Application Parameter? // https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#registration-response-message-success - $signed_data = sprintf('%s%s%s%s%s', + $signed_data = sprintf( + '%s%s%s%s%s', chr(0), $this->registerRequest->getApplicationParameter(), $resp->getClientData()->getChallengeParameter(), @@ -230,7 +241,8 @@ public function register(RegisterResponse $resp): Registration { * * @return self */ - public function disableCAVerification(): self { + public function disableCAVerification(): self + { $this->verifyCA = false; return $this; } @@ -242,10 +254,11 @@ public function disableCAVerification(): self { * This method or disableCAVerification must be called before register() or * a SecurityException will always be thrown. * - * @param array A list of file paths to device issuer CA certs + * @param string[] $CAs A list of file paths to device issuer CA certs * @return self */ - public function setTrustedCAs(array $CAs): self { + public function setTrustedCAs(array $CAs): self + { $this->verifyCA = true; $this->trustedCAs = $CAs; return $this; @@ -255,10 +268,11 @@ public function setTrustedCAs(array $CAs): self { * Provide the previously-generated RegisterRequest to be used when * verifying a RegisterResponse during register() * - * @param RegisterRequest + * @param RegisterRequest $request * @return self */ - public function setRegisterRequest(RegisterRequest $request): self { + public function setRegisterRequest(RegisterRequest $request): self + { $this->registerRequest = $request; return $this; } @@ -266,11 +280,13 @@ public function setRegisterRequest(RegisterRequest $request): self { /** * Provide a user's existing Registrations to be used during authentication * - * @param array + * @param Registration[] $registrations * @return self */ - public function setRegistrations(array $registrations): self { - array_map(function(Registration $r){}, $registrations); // type check + public function setRegistrations(array $registrations): self + { + array_map(function (Registration $r) { + }, $registrations); // type check $this->registrations = $registrations; return $this; } @@ -280,11 +296,13 @@ public function setRegistrations(array $registrations): self { * existing Registrations, of of which should be signed and will be * verified during authenticate() * - * @param array + * @param SignRequest[] $signRequests * @return self */ - public function setSignRequests(array $signRequests): self { - array_map(function(SignRequest $s){}, $signRequests); // type check + public function setSignRequests(array $signRequests): self + { + array_map(function (SignRequest $s) { + }, $signRequests); // type check $this->signRequests = $signRequests; return $this; } @@ -295,7 +313,8 @@ public function setSignRequests(array $signRequests): self { * * @return RegisterRequest */ - public function generateRegisterRequest(): RegisterRequest { + public function generateRegisterRequest(): RegisterRequest + { return (new RegisterRequest()) ->setAppId($this->getAppId()) ->setChallenge($this->generateChallenge()); @@ -305,10 +324,11 @@ public function generateRegisterRequest(): RegisterRequest { * Creates a new SignRequest for an existing Registration for an * authenticating user, used by the `u2f.sign` API. * - * @param Registration one of the user's existing Registrations + * @param Registration $reg one of the user's existing Registrations * @return SignRequest */ - public function generateSignRequest(Registration $reg): SignRequest { + public function generateSignRequest(Registration $reg): SignRequest + { return (new SignRequest()) ->setAppId($this->getAppId()) ->setChallenge($this->generateChallenge()) @@ -318,10 +338,11 @@ public function generateSignRequest(Registration $reg): SignRequest { /** * Wraps generateSignRequest for multiple Registrations * - * @param array - * @return array + * @param Registration[] $registrations + * @return SignRequest[] */ - public function generateSignRequests(array $registrations): array { + public function generateSignRequests(array $registrations): array + { return array_values(array_map([$this, 'generateSignRequest'], $registrations)); } @@ -330,8 +351,10 @@ public function generateSignRequests(array $registrations): array { * key handle value. If one is found, it is returned; if not, this returns * null. * - * @param array<> haystack to search - * @param string key handle to find in haystack + * TODO: create and implement a HasKeyHandle interface of sorts to type + * this better + * @param array $objects haystack to search + * @param string $keyHandle key handle to find in haystack * @return mixed element from haystack * @return null if no element matches */ @@ -352,7 +375,8 @@ private function findObjectWithKeyHandle( * * @return string */ - private function generateChallenge(): string { + private function generateChallenge(): string + { // FIDO Alliance spec suggests a minimum of 8 random bytes return toBase64Web(\random_bytes(16)); } @@ -362,8 +386,8 @@ private function generateChallenge(): string { * user-provided value. A mismatch will throw a SE. Future * versions may also enforce a timing window. * - * @param ChallengeProvider source of known challenge - * @param ChallengeProvider user-provided value + * @param ChallengeProvider $from source of known challenge + * @param ChallengeProvider $to user-provided value * @return true on success * @throws SE on failure */ diff --git a/src/SignRequest.php b/src/SignRequest.php index 4dd3f3c..f0e8a73 100644 --- a/src/SignRequest.php +++ b/src/SignRequest.php @@ -11,7 +11,8 @@ class SignRequest implements JsonSerializable, ChallengeProvider use KeyHandleTrait; use VersionTrait; - public function jsonSerialize() { + public function jsonSerialize() + { return [ "version" => $this->version, "challenge" => $this->getChallenge(), @@ -19,5 +20,4 @@ public function jsonSerialize() { "appId" => $this->getAppId(), ]; } - } diff --git a/src/SignResponse.php b/src/SignResponse.php index e28c14b..6a63f56 100644 --- a/src/SignResponse.php +++ b/src/SignResponse.php @@ -13,14 +13,17 @@ class SignResponse private $counter = -1; private $user_presence = 0; - public function getCounter(): int { + public function getCounter(): int + { return $this->counter; } - public function getUserPresenceByte(): int { + public function getUserPresenceByte(): int + { return $this->user_presence; } - protected function parseResponse(array $response): self { + protected function parseResponse(array $response): self + { $this->validateKeyInArray('keyHandle', $response); $this->setKeyHandle(fromBase64Web($response['keyHandle'])); @@ -39,5 +42,4 @@ protected function parseResponse(array $response): self { $this->setSignature($decoded['signature']); return $this; } - } diff --git a/src/VersionTrait.php b/src/VersionTrait.php index a65f66a..d7d4da1 100644 --- a/src/VersionTrait.php +++ b/src/VersionTrait.php @@ -7,7 +7,8 @@ trait VersionTrait { private $version = 'U2F_V2'; - public function getVersion(): string { + public function getVersion(): string + { return $this->version; } } diff --git a/src/functions.php b/src/functions.php index ddbbb3b..8c31c3b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -4,19 +4,21 @@ /** * Takes a web-safe base64 encoded string and returns the decoded version - * @param the encoded string - * @return the raw binary string + * @param string $base64 the encoded string + * @return string the raw binary string */ -function fromBase64Web(string $base64): string { +function fromBase64Web(string $base64): string +{ return base64_decode(strtr($base64, '-_', '+/')); } /** * Encodes any string to web-safe base64 - * @param the raw binary string - * @return the encoded string + * @param string $binary the raw binary string + * @return string the encoded string */ -function toBase64Web(string $binary): string { +function toBase64Web(string $binary): string +{ return rtrim(strtr(base64_encode($binary), '+/', '-_'), '='); } @@ -29,10 +31,11 @@ function toBase64Web(string $binary): string { * set to a multi-byte character set, in which case it retains the * non-overloaded behavior. * - * @param string The string being measured + * @param string $string The string being measured * @return int The length of the string, in bytes */ -function strlen(string $string): int { +function strlen(string $string): int +{ if (function_exists('mb_strlen')) { return \mb_strlen($string, '8bit'); } @@ -44,12 +47,13 @@ function strlen(string $string): int { * set to a multi-byte character set, in which case it retains the * non-overloaded behavior. * - * @param string The input string - * @param int The starting point, in bytes - * @param int The length, in bytes + * @param string $string The input string + * @param int $start The starting point, in bytes + * @param int $length The length, in bytes * @return string The extracted part of the string */ -function substr(string $string, int $start, int $length = null): string { +function substr(string $string, int $start, int $length = null): string +{ if (function_exists('mb_substr')) { return \mb_substr($string, $start, $length, '8bit'); } diff --git a/tests/AppIdTraitTest.php b/tests/AppIdTraitTest.php index ee840f3..e6c85fe 100644 --- a/tests/AppIdTraitTest.php +++ b/tests/AppIdTraitTest.php @@ -15,28 +15,39 @@ class AppIdTraitTest extends \PHPUnit\Framework\TestCase * @covers ::getAppId * @covers ::setAppId */ - public function testAccessors() { + public function testAccessors() + { $obj = new class { use AppIdTrait; }; $appId = 'https://u2f.example.com'; - $this->assertSame($obj, $obj->setAppId($appId), - 'setAppId should return $this'); - $this->assertSame($appId, $obj->getAppId(), - 'getAppId should return the set value'); + $this->assertSame( + $obj, + $obj->setAppId($appId), + 'setAppId should return $this' + ); + $this->assertSame( + $appId, + $obj->getAppId(), + 'getAppId should return the set value' + ); } /** * @covers ::getApplicationParameter */ - public function testGetApplicationParameter() { - $obj = new class { use AppIdTrait; }; + public function testGetApplicationParameter() + { + $obj = new class { + use AppIdTrait; + }; $appId = 'https://u2f.example.com'; $obj->setAppId($appId); - $this->assertSame(hash('sha256', $appId, true), + $this->assertSame( + hash('sha256', $appId, true), $obj->getApplicationParameter(), - 'getApplicationParamter should return the raw SHA256 hash of the application id'); + 'getApplicationParamter should return the raw SHA256 hash of the application id' + ); } - } diff --git a/tests/AttestationCertificateTraitTest.php b/tests/AttestationCertificateTraitTest.php index ea58e5e..db59d57 100644 --- a/tests/AttestationCertificateTraitTest.php +++ b/tests/AttestationCertificateTraitTest.php @@ -14,21 +14,29 @@ class AttestationCertificateTraitTest extends \PHPUnit\Framework\TestCase * @covers ::getAttestationCertificateBinary * @covers ::setAttestationCertificate */ - public function testAccessors() { + public function testAccessors() + { $obj = new class { use AttestationCertificateTrait; }; $cert = bin2hex(random_bytes(35)); - $this->assertSame($obj, $obj->setAttestationCertificate($cert), - 'setAttestationCertificate should return $this'); - $this->assertSame($cert, $obj->getAttestationCertificateBinary(), - 'getAttestationCertificate should return the challenge that was set'); + $this->assertSame( + $obj, + $obj->setAttestationCertificate($cert), + 'setAttestationCertificate should return $this' + ); + $this->assertSame( + $cert, + $obj->getAttestationCertificateBinary(), + 'getAttestationCertificate should return the challenge that was set' + ); } /** * @covers ::getAttestationCertificatePem */ - public function testGetAttestationCertificatePem() { + public function testGetAttestationCertificatePem() + { $obj = new class { use AttestationCertificateTrait; }; @@ -37,14 +45,18 @@ public function testGetAttestationCertificatePem() { $expected .= chunk_split(base64_encode($raw), 64); $expected .= "-----END CERTIFICATE-----"; $obj->setAttestationCertificate($raw); - $this->assertSame($expected, $obj->getAttestationCertificatePem(), - 'PEM-formatted certificate was incorrect'); + $this->assertSame( + $expected, + $obj->getAttestationCertificatePem(), + 'PEM-formatted certificate was incorrect' + ); } /** * @covers ::verifyIssuerAgainstTrustedCAs */ - public function testSuccessfulCAVerification() { + public function testSuccessfulCAVerification() + { $class = $this->getObjectWithYubicoCert(); $certs = [dirname(__DIR__).'/CAcerts/yubico.pem']; $this->assertTrue($class->verifyIssuerAgainstTrustedCAs($certs)); @@ -53,7 +65,8 @@ public function testSuccessfulCAVerification() { /** * @covers ::verifyIssuerAgainstTrustedCAs */ - public function testFailedCAVerification() { + public function testFailedCAVerification() + { $class = $this->getObjectWithYubicoCert(); $certs = [__DIR__.'/verisign_only_for_unit_tests.pem']; $this->expectException(SecurityException::class); @@ -64,7 +77,8 @@ public function testFailedCAVerification() { /** * @covers ::verifyIssuerAgainstTrustedCAs */ - public function testFailedCAVerificationFromNoCAs() { + public function testFailedCAVerificationFromNoCAs() + { $class = $this->getObjectWithYubicoCert(); $certs = []; $this->expectException(SecurityException::class); @@ -80,9 +94,11 @@ public function testFailedCAVerificationFromNoCAs() { * * @return mixed Some class using AttestationCertificateTrait */ - private function getObjectWithYubicoCert() { + private function getObjectWithYubicoCert() + { $response = RegisterResponse::fromJson( - file_get_contents(__DIR__.'/register_response.json')); + file_get_contents(__DIR__.'/register_response.json') + ); // Sanity check that the response actually imlements this trait, rather // than doing all sorts of magic $check = AttestationCertificateTrait::class; diff --git a/tests/ChallengeTraitTest.php b/tests/ChallengeTraitTest.php index eb027be..10f438b 100644 --- a/tests/ChallengeTraitTest.php +++ b/tests/ChallengeTraitTest.php @@ -15,14 +15,21 @@ class ChallengeTraitTest extends \PHPUnit\Framework\TestCase * @covers ::getChallenge * @covers ::setChallenge */ - public function testAccessors() { + public function testAccessors() + { $obj = new class { use ChallengeTrait; }; $challenge = bin2hex(random_bytes(15)); - $this->assertSame($obj, $obj->setChallenge($challenge), - 'setChallenge should return $this'); - $this->assertSame($challenge, $obj->getChallenge(), - 'getChallenge should return the challenge that was set'); + $this->assertSame( + $obj, + $obj->setChallenge($challenge), + 'setChallenge should return $this' + ); + $this->assertSame( + $challenge, + $obj->getChallenge(), + 'getChallenge should return the challenge that was set' + ); } } diff --git a/tests/ClientDataTest.php b/tests/ClientDataTest.php index 3617f04..fc0b378 100644 --- a/tests/ClientDataTest.php +++ b/tests/ClientDataTest.php @@ -14,8 +14,15 @@ class ClientDataTest extends \PHPUnit\Framework\TestCase /** * @covers ::fromJson */ - public function testFromValidJson() { - $goodJson = '{"typ":"navigator.id.finishEnrollment","challenge":"PfsWR1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo","origin":"https://u2f.ericstern.com","cid_pubkey":""}'; + public function testFromValidJson() + { + $goodData = [ + 'typ' => 'navigator.id.finishEnrollment', + 'challenge' => 'PfsWR1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo', + 'origin' => 'https://u2f.ericstern.com', + 'cid_pubkey' => '', + ]; + $goodJson = json_encode($goodData); $clientData = ClientData::fromJson($goodJson); $this->assertInstanceOf(ClientData::class, $clientData); } @@ -24,23 +31,35 @@ public function testFromValidJson() { * @covers ::getChallengeParameter * @covers ::jsonSerialize */ - public function testGetChallengeParameter() { + public function testGetChallengeParameter() + { $expected_param = base64_decode('exDPjyyKbizXMAAUNLpv0QYJNyXClbUqewUWojPtp0g='); // Sanity check - $this->assertSame(32, + $this->assertSame( + 32, strlen($expected_param), - 'Test vector should have been 32 bytes'); + 'Test vector should have been 32 bytes' + ); - $goodJson = '{"typ":"navigator.id.finishEnrollment","challenge":"PfsWR1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo","origin":"https://u2f.ericstern.com","cid_pubkey":""}'; + $goodData = [ + 'typ' => 'navigator.id.finishEnrollment', + 'challenge' => 'PfsWR1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo', + 'origin' => 'https://u2f.ericstern.com', + 'cid_pubkey' => '', + ]; + $goodJson = json_encode($goodData); $clientData = ClientData::fromJson($goodJson); - $this->assertTrue(hash_equals($expected_param, $clientData->getChallengeParameter()), - 'Challenge parameter did not match expected value'); + $this->assertTrue( + hash_equals($expected_param, $clientData->getChallengeParameter()), + 'Challenge parameter did not match expected value' + ); } /** * @covers ::fromJson */ - public function testBadJson() { + public function testBadJson() + { $json = 'this is not json'; $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MALFORMED_DATA); @@ -51,7 +70,8 @@ public function testBadJson() { * @covers ::fromJson * @dataProvider missingData */ - public function testDataValidation($json) { + public function testDataValidation($json) + { $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MISSING_KEY); ClientData::fromJson($json); @@ -60,7 +80,8 @@ public function testDataValidation($json) { /** * @dataProvider types */ - public function testTypes(string $type, bool $allowed) { + public function testTypes(string $type, bool $allowed) + { $all = [ 'typ' => $type, 'challenge' => 'SOMECHALLENGE', @@ -79,14 +100,15 @@ public function testTypes(string $type, bool $allowed) { // -( DataProviders )------------------------------------------------------ - public function missingData(): array { + public function missingData(): array + { $all = [ 'typ' => 'navigator.id.finishEnrollment', 'challenge' => 'SOMECHALLENGE', 'origin' => 'https://u2f.example.com', 'cid_pubkey' => '', ]; - $without = function(string $i) use ($all): array { + $without = function (string $i) use ($all): array { unset($all[$i]); return [json_encode($all)]; }; @@ -98,7 +120,8 @@ public function missingData(): array { ]; } - public function types(): array { + public function types(): array + { return [ ['navigator.id.getAssertion', true], ['navigator.id.finishEnrollment', true], diff --git a/tests/ClientErrorExceptionTest.php b/tests/ClientErrorExceptionTest.php index dcc95b3..1d59b72 100644 --- a/tests/ClientErrorExceptionTest.php +++ b/tests/ClientErrorExceptionTest.php @@ -14,17 +14,23 @@ class ClientErrorExceptionTest extends \PHPUnit\Framework\TestCase * @covers ::__construct * @dataProvider clientErrors */ - public function testClientError(int $code) { + public function testClientError(int $code) + { $ex = new ClientErrorException($code); - $this->assertInstanceOf(ClientErrorException::class, + $this->assertInstanceOf( + ClientErrorException::class, $ex, - '__construct failed'); - $this->assertNotEmpty($ex->getMessage(), - 'A predefined message should have been used'); + '__construct failed' + ); + $this->assertNotEmpty( + $ex->getMessage(), + 'A predefined message should have been used' + ); } // -( DataProviders )------------------------------------------------------ - public function clientErrors() { + public function clientErrors() + { return [ [ClientError::OTHER_ERROR], [ClientError::BAD_REQUEST], @@ -33,6 +39,4 @@ public function clientErrors() { [ClientError::TIMEOUT], ]; } - - } diff --git a/tests/ECPublicKeyTraitTest.php b/tests/ECPublicKeyTraitTest.php index a5e5010..c737021 100644 --- a/tests/ECPublicKeyTraitTest.php +++ b/tests/ECPublicKeyTraitTest.php @@ -15,21 +15,29 @@ class ECPublicKeyTraitTest extends \PHPUnit\Framework\TestCase * @covers ::setPublicKey * @covers ::getPublicKey */ - public function testAccessors() { + public function testAccessors() + { $obj = new class { use ECPublicKeyTrait; }; $key = "\x04".\random_bytes(64); - $this->assertSame($obj, $obj->setPublicKey($key), - 'setPublicKey should return $this'); - $this->assertSame($key, $obj->getPublicKey(), - 'getPublicKey should return the set value'); + $this->assertSame( + $obj, + $obj->setPublicKey($key), + 'setPublicKey should return $this' + ); + $this->assertSame( + $key, + $obj->getPublicKey(), + 'getPublicKey should return the set value' + ); } /** * @covers ::getPublicKeyPem */ - public function testGetPublicKeyPem() { + public function testGetPublicKeyPem() + { $obj = new class { use ECPublicKeyTrait; }; @@ -51,7 +59,8 @@ public function testGetPublicKeyPem() { /** * @covers ::setPublicKey */ - public function testSetPublicKeyThrowsWithBadFirstByte() { + public function testSetPublicKeyThrowsWithBadFirstByte() + { $obj = new class { use ECPublicKeyTrait; }; @@ -65,7 +74,8 @@ public function testSetPublicKeyThrowsWithBadFirstByte() { /** * @covers ::setPublicKey */ - public function testSetPublicKeyThrowsWhenTooShort() { + public function testSetPublicKeyThrowsWhenTooShort() + { $obj = new class { use ECPublicKeyTrait; }; @@ -78,7 +88,8 @@ public function testSetPublicKeyThrowsWhenTooShort() { /** * @covers ::setPublicKey */ - public function testSetPublicKeyThrowsWhenTooLong() { + public function testSetPublicKeyThrowsWhenTooLong() + { $obj = new class { use ECPublicKeyTrait; }; @@ -87,7 +98,4 @@ public function testSetPublicKeyThrowsWhenTooLong() { $this->expectExceptionCode(InvalidDataException::PUBLIC_KEY_LENGTH); $obj->setPublicKey($key); } - - - } diff --git a/tests/functionsTest.php b/tests/FunctionsTest.php similarity index 62% rename from tests/functionsTest.php rename to tests/FunctionsTest.php index d9044ea..0e35dac 100644 --- a/tests/functionsTest.php +++ b/tests/FunctionsTest.php @@ -3,7 +3,7 @@ namespace Firehed\U2F; -class functionsTest extends \PHPUnit\Framework\TestCase +class FunctionsTest extends \PHPUnit\Framework\TestCase { @@ -11,21 +11,30 @@ class functionsTest extends \PHPUnit\Framework\TestCase * @covers Firehed\U2F\fromBase64Web * @dataProvider vectors */ - public function testFromBase64Web($plain, $encoded) { - $this->assertSame($plain, fromBase64Web($encoded), - 'Decoded vector did not match known plaintext version'); + public function testFromBase64Web($plain, $encoded) + { + $this->assertSame( + $plain, + fromBase64Web($encoded), + 'Decoded vector did not match known plaintext version' + ); } /** * @covers Firehed\U2F\toBase64Web * @dataProvider vectors */ - public function testToBase64Web($plain, $encoded) { - $this->assertSame($encoded, toBase64Web($plain), - 'Encoded vector did not match known version'); + public function testToBase64Web($plain, $encoded) + { + $this->assertSame( + $encoded, + toBase64Web($plain), + 'Encoded vector did not match known version' + ); } - public function vectors(): array { + public function vectors(): array + { return [ // Plain Encoded // Adapted test vctors from RFC4648 Sec. 5 (padding trimmed) diff --git a/tests/InvalidDataExceptionTest.php b/tests/InvalidDataExceptionTest.php index 5ff9e46..bfc4667 100644 --- a/tests/InvalidDataExceptionTest.php +++ b/tests/InvalidDataExceptionTest.php @@ -12,26 +12,34 @@ class InvalidDataExceptionTest extends \PHPUnit\Framework\TestCase { /** * @covers ::__construct - * @dataProvider invalidDataExceptionCodes + * @dataProvider invalidDataExceptionCodes */ - public function testInvalidDataException(int $code) { + public function testInvalidDataException(int $code) + { $ex = new InvalidDataException($code, 'Prefill'); - $this->assertInstanceOf(InvalidDataException::class, + $this->assertInstanceOf( + InvalidDataException::class, $ex, - '__construct failed'); - $this->assertNotEmpty($ex->getMessage(), - 'A predefined message should have been used'); - $this->assertRegExp('/Prefill/', $ex->getMessage(), - 'The sprintf args were not added to the exception message'); + '__construct failed' + ); + $this->assertNotEmpty( + $ex->getMessage(), + 'A predefined message should have been used' + ); + $this->assertRegExp( + '/Prefill/', + $ex->getMessage(), + 'The sprintf args were not added to the exception message' + ); } // -( DataProviders )------------------------------------------------------ - public function invalidDataExceptionCodes() { + public function invalidDataExceptionCodes() + { return [ [InvalidDataException::MISSING_KEY], [InvalidDataException::MALFORMED_DATA], [InvalidDataException::PUBLIC_KEY_LENGTH], ]; } - } diff --git a/tests/KeyHandleTraitTest.php b/tests/KeyHandleTraitTest.php index af543d3..73c3287 100644 --- a/tests/KeyHandleTraitTest.php +++ b/tests/KeyHandleTraitTest.php @@ -14,29 +14,39 @@ class KeyHandleTraitTest extends \PHPUnit\Framework\TestCase * @covers ::getKeyHandleBinary * @covers ::setKeyHandle */ - public function testAccessors() { + public function testAccessors() + { $obj = new class { use KeyHandleTrait; }; $kh = random_bytes(14)."\x00 "; - $this->assertSame($obj, $obj->setKeyHandle($kh), - 'setKeyHandle should return $this'); - $this->assertSame($kh, $obj->getKeyHandleBinary(), - 'getKeyHandleBinary should return the set value'); + $this->assertSame( + $obj, + $obj->setKeyHandle($kh), + 'setKeyHandle should return $this' + ); + $this->assertSame( + $kh, + $obj->getKeyHandleBinary(), + 'getKeyHandleBinary should return the set value' + ); } /** * @covers ::getKeyHandleWeb */ - public function testGetKeyHandleWeb() { + public function testGetKeyHandleWeb() + { $obj = new class { use KeyHandleTrait; }; $kh = random_bytes(14)."\x00 "; $webKh = toBase64Web($kh); $obj->setKeyHandle($kh); - $this->assertSame($webKh, $obj->getKeyHandleWeb(), - 'getKeyHandleWeb was encoded wrong'); + $this->assertSame( + $webKh, + $obj->getKeyHandleWeb(), + 'getKeyHandleWeb was encoded wrong' + ); } - } diff --git a/tests/RegisterRequestTest.php b/tests/RegisterRequestTest.php index 6ad8a8e..448a6b3 100644 --- a/tests/RegisterRequestTest.php +++ b/tests/RegisterRequestTest.php @@ -14,7 +14,8 @@ class RegisterRequestTest extends \PHPUnit\Framework\TestCase /** * @covers ::jsonSerialize */ - public function testJsonSerialize() { + public function testJsonSerialize() + { $appId = 'https://u2f.example.com'; $challenge = 'some-random-string'; @@ -24,20 +25,35 @@ public function testJsonSerialize() { ->setChallenge($challenge); $json = json_encode($request); $decoded = json_decode($json, true); - $this->assertSame($appId, $request->getAppId(), - 'getAppId returned the wrong value'); - $this->assertSame($appId, $decoded['appId'], - 'json appId property did not match'); - $this->assertSame($challenge, $request->getChallenge(), - 'getChallenge returned the wrong value'); - $this->assertSame($challenge, $decoded['challenge'], - 'json challenge property did not match'); - $this->assertSame('U2F_V2', $request->getVersion(), - 'getVersion returned the wrong value'); - $this->assertSame('U2F_V2', $decoded['version'], - 'json version was incorrect'); - - + $this->assertSame( + $appId, + $request->getAppId(), + 'getAppId returned the wrong value' + ); + $this->assertSame( + $appId, + $decoded['appId'], + 'json appId property did not match' + ); + $this->assertSame( + $challenge, + $request->getChallenge(), + 'getChallenge returned the wrong value' + ); + $this->assertSame( + $challenge, + $decoded['challenge'], + 'json challenge property did not match' + ); + $this->assertSame( + 'U2F_V2', + $request->getVersion(), + 'getVersion returned the wrong value' + ); + $this->assertSame( + 'U2F_V2', + $decoded['version'], + 'json version was incorrect' + ); } - } diff --git a/tests/RegisterResponseTest.php b/tests/RegisterResponseTest.php index 8003f2c..b88c65a 100644 --- a/tests/RegisterResponseTest.php +++ b/tests/RegisterResponseTest.php @@ -11,14 +11,40 @@ class RegisterResponseTest extends \PHPUnit\Framework\TestCase { - private $validClientData = 'eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCIsImNoYWxsZW5nZSI6IkJyQWN4dGIxOWFYNTRoN0Y2T0NKWVptQ3prZHlHV0Nib3NEcHpNMUh2MkUiLCJvcmlnaW4iOiJodHRwczovL3UyZi5lcmljc3Rlcm4uY29tIiwiY2lkX3B1YmtleSI6IiJ9'; - private $validRegistrationData = "BQS55FfGvxbgmcNO1cpNhdr4r-CMSbMtuhiMMJbXqd_3FD8Aah2X_n4ZiyBlgBqbbe4RdyksR7ZXoqPYT47-tmeWQJhf7xs1T8ObBRpkFi_VWG5oFJe499mQYxcj9BR0G8B5fjkYbUuPCwNRiscOP8P18ep6V1OOulT3tq6kBC-94xQwggItMIIBF6ADAgECAgQFtgV5MAsGCSqGSIb3DQEBCzAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKDEmMCQGA1UEAwwdWXViaWNvIFUyRiBFRSBTZXJpYWwgOTU4MTUwMzMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT9uN6zoe1w62NsBm62AGmWpflw_LXbiPw7MF1B5ZZvDBtUuFL-8KCQftF_O__CnU0yG5z4qEos6qA4yr011ZjeoyYwJDAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgyLjEuMTALBgkqhkiG9w0BAQsDggEBAH7T-2zMJSAT-C8hjCo32mAx0g5_MIHa_K6xKPx_myM5FL-2TWE18XziIfp2T0U-8Sc6jOlllWRCuy8eR0g_c33LyYtYU3f-9QsnDgKJ-IQ28a3PSbJiHuXjAt9VW5q3QnLgafkYFJs97E8SIosQwPiN42r1inS7RCuFrgBTZL2mcCBY_B8th5tTARHqYOhsY_F_pZRMyD8KommEiz7jiKbAnmsFlT_LuPR-g6J-AHKmPDKtZIZOkm1xEvoZl_eDllb7syvo94idDwFFUZonr92ORrBMpCkNhUC2NLiGFh51iMhimdzdZDXRZ4o6bwp0gpxN0_cMNSTR3fFteK3SG2QwRAIgFTLJPY9_a0ZPujRfLufS-9ANCWemIWPHqs3icavMJIgCIFH5MSGDFkuY_NWhKa4mbLdbP6r7wMwspwHPG5_Xf48V"; + private $validClientData = + 'eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCIsImNoYWxsZW5nZSI6I'. + 'kJyQWN4dGIxOWFYNTRoN0Y2T0NKWVptQ3prZHlHV0Nib3NEcHpNMUh2MkUiLCJvcmlnaW'. + '4iOiJodHRwczovL3UyZi5lcmljc3Rlcm4uY29tIiwiY2lkX3B1YmtleSI6IiJ9'; + + private $validRegistrationData = + 'BQS55FfGvxbgmcNO1cpNhdr4r-CMSbMtuhiMMJbXqd_3FD8Aah2X_n4ZiyBlgBqbbe4Rd'. + 'yksR7ZXoqPYT47-tmeWQJhf7xs1T8ObBRpkFi_VWG5oFJe499mQYxcj9BR0G8B5fjkYbU'. + 'uPCwNRiscOP8P18ep6V1OOulT3tq6kBC-94xQwggItMIIBF6ADAgECAgQFtgV5MAsGCSq'. + 'GSIb3DQEBCzAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIw'. + 'MDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKDEmMCQGA1UEAwwdW'. + 'XViaWNvIFUyRiBFRSBTZXJpYWwgOTU4MTUwMzMwWTATBgcqhkjOPQIBBggqhkjOPQMBBw'. + 'NCAAT9uN6zoe1w62NsBm62AGmWpflw_LXbiPw7MF1B5ZZvDBtUuFL-8KCQftF_O__CnU0'. + 'yG5z4qEos6qA4yr011ZjeoyYwJDAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgy'. + 'LjEuMTALBgkqhkiG9w0BAQsDggEBAH7T-2zMJSAT-C8hjCo32mAx0g5_MIHa_K6xKPx_m'. + 'yM5FL-2TWE18XziIfp2T0U-8Sc6jOlllWRCuy8eR0g_c33LyYtYU3f-9QsnDgKJ-IQ28a'. + '3PSbJiHuXjAt9VW5q3QnLgafkYFJs97E8SIosQwPiN42r1inS7RCuFrgBTZL2mcCBY_B8'. + 'th5tTARHqYOhsY_F_pZRMyD8KommEiz7jiKbAnmsFlT_LuPR-g6J-AHKmPDKtZIZOkm1x'. + 'EvoZl_eDllb7syvo94idDwFFUZonr92ORrBMpCkNhUC2NLiGFh51iMhimdzdZDXRZ4o6b'. + 'wp0gpxN0_cMNSTR3fFteK3SG2QwRAIgFTLJPY9_a0ZPujRfLufS-9ANCWemIWPHqs3ica'. + 'vMJIgCIFH5MSGDFkuY_NWhKa4mbLdbP6r7wMwspwHPG5_Xf48V'; /** * @covers ::fromJson */ - public function testFromJson() { - $json = sprintf('{"registrationData":"%s","version":"U2F_V2","challenge":"BrAcxtb19aX54h7F6OCJYZmCzkdyGWCbosDpzM1Hv2E","appId":"https://u2f.ericstern.com","clientData":"%s"}', $this->validRegistrationData, $this->validClientData);; + public function testFromJson() + { + $json = json_encode([ + 'registrationData' => $this->validRegistrationData, + 'version' => 'U2F_V2', + 'challenge' => 'BrAcxtb19aX54h7F6OCJYZmCzkdyGWCbosDpzM1Hv2E', + 'appId' => 'https://u2f.ericstern.com', + 'clientData' => $this->validClientData, + ]); $response = RegisterResponse::fromJson($json); $this->assertInstanceOf(RegisterResponse::class, $response); } @@ -26,21 +52,24 @@ public function testFromJson() { /** * @dataProvider clientErrors */ - public function testErrorResponse(int $code) { + public function testErrorResponse(int $code) + { $json = sprintf('{"errorCode":%d}', $code); $this->expectException(ClientErrorException::class); $this->expectExceptionCode($code); RegisterResponse::fromJson($json); } - public function testFromJsonBadJson() { + public function testFromJsonBadJson() + { $json = 'this is not json'; $this->expectException(InvalidDataException::class); // FIXME: code RegisterResponse::fromJson($json); } - public function testFromJsonMissingClientData() { + public function testFromJsonMissingClientData() + { $json = sprintf('{"registrationData":"%s"}', $this->validRegistrationData); $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MISSING_KEY); @@ -48,7 +77,8 @@ public function testFromJsonMissingClientData() { RegisterResponse::fromJson($json); } - public function testFromJsonMissingRegistrationData() { + public function testFromJsonMissingRegistrationData() + { $json = sprintf('{"clientData":"%s"}', $this->validClientData); $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MISSING_KEY); @@ -59,7 +89,8 @@ public function testFromJsonMissingRegistrationData() { /** * @dataProvider invalidRegistrationData */ - public function testBadRegistrationData(string $registrationData) { + public function testBadRegistrationData(string $registrationData) + { $json = $this->buildJson($this->validClientData, $registrationData); $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MALFORMED_DATA); @@ -72,7 +103,8 @@ public function testBadRegistrationData(string $registrationData) { * @covers ::getPublicKey * @covers ::getSignature */ - public function testDataAccuracyAfterSuccessfulParsing() { + public function testDataAccuracyAfterSuccessfulParsing() + { $pubkey = "\x04".random_bytes(64); $handle = random_bytes(32); $st = "\x05".$pubkey."\x20".$handle; @@ -83,19 +115,32 @@ public function testDataAccuracyAfterSuccessfulParsing() { $json = $this->buildJson($this->validClientData, $reg); $response = RegisterResponse::fromJson($json); - $this->assertSame($pubkey, $response->getPublicKey(), - 'Public key was not parsed correctly'); - $this->assertSame($handle, $response->getKeyHandleBinary(), - 'Key Handle was not parsed correctly'); - $this->assertSame($cert, $response->getAttestationCertificateBinary(), - 'Cert was not parsed correctly'); - $this->assertSame($sig, $response->getSignature(), - 'Signature was not parsed correctly'); + $this->assertSame( + $pubkey, + $response->getPublicKey(), + 'Public key was not parsed correctly' + ); + $this->assertSame( + $handle, + $response->getKeyHandleBinary(), + 'Key Handle was not parsed correctly' + ); + $this->assertSame( + $cert, + $response->getAttestationCertificateBinary(), + 'Cert was not parsed correctly' + ); + $this->assertSame( + $sig, + $response->getSignature(), + 'Signature was not parsed correctly' + ); } // -( DataProviders )------------------------------------------------------ - public function clientErrors() { + public function clientErrors() + { return [ [ClientError::OTHER_ERROR], [ClientError::BAD_REQUEST], @@ -105,9 +150,10 @@ public function clientErrors() { ]; } - public function invalidRegistrationData(): array { - $bad_reserved_byte = "\x01".str_repeat('a',200); - $bad_pubkey_start = "\x05\x99".str_repeat('a',200); + public function invalidRegistrationData(): array + { + $bad_reserved_byte = "\x01".str_repeat('a', 200); + $bad_pubkey_start = "\x05\x99".str_repeat('a', 200); $pubkey_too_short = "\x05\x04".random_bytes(5); $handle_too_short = "\x05\x04".random_bytes(64)."\x20".random_bytes(16); @@ -116,8 +162,8 @@ public function invalidRegistrationData(): array { $valid_start = "\x05\x04".random_bytes(64)."\x20".random_bytes(32); $bad_cert_start = "\x40".str_repeat('a', 100); // Must start with bxxx10000 $crazy_long_cert = "\x30\x85".str_repeat('a', 100); - $too_short_cert = "\x30\x82\x01\x00".str_repeat('a',50); // x0100 bytes long - return array_map(function(string $s) { + $too_short_cert = "\x30\x82\x01\x00".str_repeat('a', 50); // x0100 bytes long + return array_map(function (string $s) { return [toBase64Web($s)]; }, [ $bad_reserved_byte, @@ -132,8 +178,10 @@ public function invalidRegistrationData(): array { // -( Helpers )------------------------------------------------------------ - protected function buildJson($clientData, $registrationData): string { - return sprintf('{"clientData":"%s","registrationData":"%s"}', + protected function buildJson($clientData, $registrationData): string + { + return sprintf( + '{"clientData":"%s","registrationData":"%s"}', $clientData, $registrationData ); diff --git a/tests/RegistrationTest.php b/tests/RegistrationTest.php index 08fcd89..4c319c1 100644 --- a/tests/RegistrationTest.php +++ b/tests/RegistrationTest.php @@ -15,18 +15,26 @@ class RegistrationTest extends \PHPUnit\Framework\TestCase * @covers ::setCounter * @covers ::getCounter */ - public function testCounter() { + public function testCounter() + { $obj = new Registration(); - $this->assertSame($obj, $obj->setCounter(1833), - 'setCounter should return $this'); - $this->assertSame(1833, $obj->getCounter(), - 'getCounter should return the set value'); + $this->assertSame( + $obj, + $obj->setCounter(1833), + 'setCounter should return $this' + ); + $this->assertSame( + 1833, + $obj->getCounter(), + 'getCounter should return the set value' + ); } /** * @covers ::setCounter */ - public function testSetCounterRejectsNegativeNumbers() { + public function testSetCounterRejectsNegativeNumbers() + { $obj = new Registration(); $this->expectException(\OutOfBoundsException::class); $obj->setCounter(-1); diff --git a/tests/ResponseTraitTest.php b/tests/ResponseTraitTest.php index d5db1cb..1b0da62 100644 --- a/tests/ResponseTraitTest.php +++ b/tests/ResponseTraitTest.php @@ -12,10 +12,12 @@ class ResponseTraitTest extends \PHPUnit\Framework\TestCase { private $trait; - public function setUp() { + public function setUp() + { $this->trait = new class { use ResponseTrait; - protected function parseResponse(array $response): self { + protected function parseResponse(array $response): self + { $this->setSignature($response['signature']); return $this; } @@ -27,10 +29,14 @@ protected function parseResponse(array $response): self { * @covers ::getSignature * @covers ::getClientData */ - public function testValidJson() { + public function testValidJson() + { $signature = __METHOD__; $json = json_encode([ - 'clientData' => 'eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoid3QyemU4SXNrY1RPM25Jc08yRDJoRmpFNXRWRDA0MU5wblllc0xwSndlZyIsIm9yaWdpbiI6Imh0dHBzOi8vdTJmLmVyaWNzdGVybi5jb20iLCJjaWRfcHVia2V5IjoiIn0', + 'clientData' => 'eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwi'. + 'Y2hhbGxlbmdlIjoid3QyemU4SXNrY1RPM25Jc08yRDJoRmpFNXRWRDA0MU5w'. + 'blllc0xwSndlZyIsIm9yaWdpbiI6Imh0dHBzOi8vdTJmLmVyaWNzdGVybi5j'. + 'b20iLCJjaWRfcHVia2V5IjoiIn0', 'signature' => $signature, ]); @@ -38,20 +44,30 @@ public function testValidJson() { // This is a little goofy because it's an anonymous class, but seems // preferable to declaring a one-off class in the test to implement the // trait instead. - $this->assertInstanceOf(get_class($this->trait), $response, - 'Parsed response was the wrong type'); + $this->assertInstanceOf( + get_class($this->trait), + $response, + 'Parsed response was the wrong type' + ); - $this->assertInstanceOf(ClientData::class, $response->getClientData(), - 'ClientData was not parsed correctly'); + $this->assertInstanceOf( + ClientData::class, + $response->getClientData(), + 'ClientData was not parsed correctly' + ); - $this->assertSame(__METHOD__, $response->getSignature(), - 'Signature was not parsed correctly'); + $this->assertSame( + __METHOD__, + $response->getSignature(), + 'Signature was not parsed correctly' + ); } /** * @covers ::fromJson */ - public function testFromJsonWithNonJson() { + public function testFromJsonWithNonJson() + { $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MALFORMED_DATA); $this->trait::fromJson('this is not json'); @@ -61,7 +77,8 @@ public function testFromJsonWithNonJson() { * @covers ::fromJson * @dataProvider clientErrors */ - public function testErrorResponse(int $code) { + public function testErrorResponse(int $code) + { $json = sprintf('{"errorCode":%d}', $code); $this->expectException(ClientErrorException::class); $this->expectExceptionCode($code); @@ -72,7 +89,8 @@ public function testErrorResponse(int $code) { * @covers ::fromJson * @dataProvider badClientData */ - public function testClientDataValidation(string $json, int $code) { + public function testClientDataValidation(string $json, int $code) + { $this->expectException(InvalidDataException::class); $this->expectExceptionCode($code); $this->trait::fromJson($json); @@ -80,7 +98,8 @@ public function testClientDataValidation(string $json, int $code) { // -( DataProviders )------------------------------------------------------ - public function clientErrors() { + public function clientErrors() + { return [ [ClientError::OTHER_ERROR], [ClientError::BAD_REQUEST], @@ -90,11 +109,11 @@ public function clientErrors() { ]; } - public function badClientData() { + public function badClientData() + { return [ ['{}', InvalidDataException::MISSING_KEY], ['{"clientData":25}', InvalidDataException::MALFORMED_DATA], ]; } - } diff --git a/tests/SecurityExceptionTest.php b/tests/SecurityExceptionTest.php index 1c5c675..36aabc4 100644 --- a/tests/SecurityExceptionTest.php +++ b/tests/SecurityExceptionTest.php @@ -14,17 +14,23 @@ class SecurityExceptionTest extends \PHPUnit\Framework\TestCase * @covers ::__construct * @dataProvider securityExceptionCodes */ - public function testSecurityException(int $code) { + public function testSecurityException(int $code) + { $ex = new SecurityException($code); - $this->assertInstanceOf(SecurityException::class, + $this->assertInstanceOf( + SecurityException::class, $ex, - '__construct failed'); - $this->assertNotEmpty($ex->getMessage(), - 'A predefined message should have been used'); + '__construct failed' + ); + $this->assertNotEmpty( + $ex->getMessage(), + 'A predefined message should have been used' + ); } // -( DataProviders )------------------------------------------------------ - public function securityExceptionCodes() { + public function securityExceptionCodes() + { return [ [SecurityException::SIGNATURE_INVALID], [SecurityException::COUNTER_USED], @@ -33,5 +39,4 @@ public function securityExceptionCodes() { [SecurityException::NO_TRUSTED_CA], ]; } - } diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 2a844eb..805e554 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -13,12 +13,19 @@ class ServerTest extends \PHPUnit\Framework\TestCase { const APP_ID = 'https://u2f.example.com'; - const ENCODED_KEY_HANDLE = 'JUnVTStPn-V2-bCu0RlvPbukBpHTD5Mi1ZGglDOcN0vD45rnTD0BXdkRt78huTwJ7tVaxTqSetHjr22tCjmYLQ'; - const ENCODED_PUBLIC_KEY = 'BEyIn4ldTViNAgceMA/YgRX1DlJR3bSF39drG44Fx1E2LaF9Md9RUN2CHyfzSokIjjCHP8jMsTYwdt0tKe6qLzc='; + + const ENCODED_KEY_HANDLE = + 'JUnVTStPn-V2-bCu0RlvPbukBpHTD5Mi1ZGglDOcN0vD45rnTD0BXdkRt78huTwJ7tVax'. + 'TqSetHjr22tCjmYLQ'; + + const ENCODED_PUBLIC_KEY = + 'BEyIn4ldTViNAgceMA/YgRX1DlJR3bSF39drG44Fx1E2LaF9Md9RUN2CHyfzSokIjjCHP'. + '8jMsTYwdt0tKe6qLzc='; private $server; - public function setUp() { + public function setUp() + { $this->server = (new Server()) ->disableCAVerification() ->setAppId(self::APP_ID); @@ -27,70 +34,119 @@ public function setUp() { /** * @covers ::disableCAVerification */ - public function testDisableCAVerificationReturnsSelf() { + public function testDisableCAVerificationReturnsSelf() + { $server = new Server(); - $this->assertSame($server, $server->disableCAVerification(), - 'disableCAVerification did not return $this'); + $this->assertSame( + $server, + $server->disableCAVerification(), + 'disableCAVerification did not return $this' + ); } /** * @covers ::generateRegisterRequest */ - public function testGenerateRegisterRequest() { + public function testGenerateRegisterRequest() + { $req = $this->server->generateRegisterRequest(); $this->assertInstanceOf(RegisterRequest::class, $req); - $this->assertSame(self::APP_ID, $req->getAppId(), - 'RegisterRequest App ID was not the value from the server'); - $this->assertNotEmpty($req->getChallenge(), - 'No challenge value was set'); - $this->assertTrue(strlen($req->getChallenge()) >= 8, - 'Challenge was less than 8 bytes long, violating the spec'); + $this->assertSame( + self::APP_ID, + $req->getAppId(), + 'RegisterRequest App ID was not the value from the server' + ); + $this->assertNotEmpty( + $req->getChallenge(), + 'No challenge value was set' + ); + $this->assertTrue( + strlen($req->getChallenge()) >= 8, + 'Challenge was less than 8 bytes long, violating the spec' + ); } /** * @covers ::generateSignRequest */ - public function testGenerateSignRequest() { + public function testGenerateSignRequest() + { $kh = \random_bytes(16); $registration = (new Registration()) ->setKeyHandle($kh); $req = $this->server->generateSignRequest($registration); $this->assertInstanceOf(SignRequest::class, $req); - $this->assertSame($kh, $req->getKeyHandleBinary(), - 'Key handle was not set correctly'); - $this->assertSame(self::APP_ID, $req->getAppId(), - 'SignRequest App ID was not the value form the server'); - $this->assertNotEmpty($req->getChallenge(), - 'No challenge value was set'); - $this->assertTrue(strlen($req->getChallenge()) >= 8, - 'Challenge was less than 8 bytes long, violating the spec'); + $this->assertSame( + $kh, + $req->getKeyHandleBinary(), + 'Key handle was not set correctly' + ); + $this->assertSame( + self::APP_ID, + $req->getAppId(), + 'SignRequest App ID was not the value form the server' + ); + $this->assertNotEmpty( + $req->getChallenge(), + 'No challenge value was set' + ); + $this->assertTrue( + strlen($req->getChallenge()) >= 8, + 'Challenge was less than 8 bytes long, violating the spec' + ); + } + + /** + * @covers ::generateSignRequests + */ + public function testGenerateSignRequests() + { + $registrations = [ + (new Registration())->setKeyHandle(\random_bytes(16)), + (new Registration())->setKeyHandle(\random_bytes(16)), + ]; + $signRequests = $this->server->generateSignRequests($registrations); + + $this->assertInternalType('array', $signRequests); + foreach ($signRequests as $signRequest) { + $this->assertInstanceOf(SignRequest::class, $signRequest); + } + // This method is a simple map operation, so testGenerateSignRequest + // does the heavy lifting. } /** * @covers ::setRegisterRequest */ - public function testSetRegisterRequestReturnsSelf() { + public function testSetRegisterRequestReturnsSelf() + { $req = $this->getDefaultRegisterRequest(); - $this->assertSame($this->server, + $this->assertSame( + $this->server, $this->server->setRegisterRequest($req), - 'setRegisterRequest did not return $this'); + 'setRegisterRequest did not return $this' + ); } /** * @covers ::setRegistrations */ - public function testSetRegistrationsReturnsSelf() { + public function testSetRegistrationsReturnsSelf() + { $reg = $this->getDefaultRegistration(); - $this->assertSame($this->server, + $this->assertSame( + $this->server, $this->server->setRegistrations([$reg]), - 'setRegistrations did not return $this'); + 'setRegistrations did not return $this' + ); } /** * @covers ::setRegistrations */ - public function testSetRegistrationsEnforcesTypeCheck() { + public function testSetRegistrationsEnforcesTypeCheck() + { $wrong = true; $this->expectException(TypeError::class); $this->server->setRegistrations([$wrong]); @@ -99,17 +155,21 @@ public function testSetRegistrationsEnforcesTypeCheck() { /** * @covers ::setSignRequests */ - public function testSetSignRequestsReturnsSelf() { + public function testSetSignRequestsReturnsSelf() + { $req = $this->getDefaultSignRequest(); - $this->assertSame($this->server, + $this->assertSame( + $this->server, $this->server->setSignRequests([$req]), - 'setSignRequests did not return $this'); + 'setSignRequests did not return $this' + ); } /** * @covers ::setSignRequests */ - public function testSetSignRequestsEnforcesTypeCheck() { + public function testSetSignRequestsEnforcesTypeCheck() + { $wrong = true; $this->expectException(TypeError::class); $this->server->setSignRequests([$wrong]); @@ -120,7 +180,8 @@ public function testSetSignRequestsEnforcesTypeCheck() { /** * @covers ::register */ - public function testRegisterThrowsIfNoRegistrationRequestProvided() { + public function testRegisterThrowsIfNoRegistrationRequestProvided() + { $this->expectException(BadMethodCallException::class); $this->server->register($this->getDefaultRegisterResponse()); } @@ -128,35 +189,49 @@ public function testRegisterThrowsIfNoRegistrationRequestProvided() { /** * @covers ::register */ - public function testRegistration() { + public function testRegistration() + { $request = $this->getDefaultRegisterRequest(); $response = $this->getDefaultRegisterResponse(); $registration = $this->server ->setRegisterRequest($request) ->register($response); - $this->assertInstanceOf(Registration::class, $registration, - 'Server->register did not return a registration'); - $this->assertSame(0, $registration->getCounter(), - 'Counter should start at 0'); - - $this->assertSame($response->getAttestationCertificateBinary(), + $this->assertInstanceOf( + Registration::class, + $registration, + 'Server->register did not return a registration' + ); + $this->assertSame( + 0, + $registration->getCounter(), + 'Counter should start at 0' + ); + + $this->assertSame( + $response->getAttestationCertificateBinary(), $registration->getAttestationCertificateBinary(), - 'Attestation cert was not copied from response'); + 'Attestation cert was not copied from response' + ); - $this->assertSame($response->getKeyHandleBinary(), + $this->assertSame( + $response->getKeyHandleBinary(), $registration->getKeyHandleBinary(), - 'Key handle was not copied from response'); + 'Key handle was not copied from response' + ); - $this->assertSame($response->getPublicKey(), + $this->assertSame( + $response->getPublicKey(), $registration->getPublicKey(), - 'Public key was not copied from response'); + 'Public key was not copied from response' + ); } /** * @covers ::register */ - public function testRegisterDefaultsToTryingEmptyCAList() { + public function testRegisterDefaultsToTryingEmptyCAList() + { $request = $this->getDefaultRegisterRequest(); $response = $this->getDefaultRegisterResponse(); @@ -174,7 +249,8 @@ public function testRegisterDefaultsToTryingEmptyCAList() { /** * @covers ::register */ - public function testRegisterThrowsIfChallengeDoesNotMatch() { + public function testRegisterThrowsIfChallengeDoesNotMatch() + { // This would have come from a session, database, etc. $request = (new RegisterRequest()) ->setAppId('https://u2f.ericstern.com') @@ -191,7 +267,8 @@ public function testRegisterThrowsIfChallengeDoesNotMatch() { /** * @covers ::register */ - public function testRegisterThrowsWithUntrustedDeviceIssuerCertificate() { + public function testRegisterThrowsWithUntrustedDeviceIssuerCertificate() + { $request = $this->getDefaultRegisterRequest(); $response = $this->getDefaultRegisterResponse(); @@ -211,7 +288,8 @@ public function testRegisterThrowsWithUntrustedDeviceIssuerCertificate() { * @covers ::register * @covers ::setTrustedCAs */ - public function testRegisterWorksWithCAList() { + public function testRegisterWorksWithCAList() + { $request = $this->getDefaultRegisterRequest(); $response = $this->getDefaultRegisterResponse(); // This contains the actual trusted + verified certificates which are @@ -237,7 +315,8 @@ public function testRegisterWorksWithCAList() { /** * @covers ::register */ - public function testRegisterThrowsWithChangedApplicationParameter() { + public function testRegisterThrowsWithChangedApplicationParameter() + { $request = (new RegisterRequest()) ->setAppId('https://not.my.u2f.example.com') ->setChallenge('PfsWR1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo'); @@ -254,7 +333,8 @@ public function testRegisterThrowsWithChangedApplicationParameter() { /** * @covers ::register */ - public function testRegisterThrowsWithChangedChallengeParameter() { + public function testRegisterThrowsWithChangedChallengeParameter() + { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: challenge parameter $json = file_get_contents(__DIR__.'/register_response.json'); @@ -276,7 +356,8 @@ public function testRegisterThrowsWithChangedChallengeParameter() { /** * @covers ::register */ - public function testRegisterThrowsWithChangedKeyHandle() { + public function testRegisterThrowsWithChangedKeyHandle() + { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: key handle $json = file_get_contents(__DIR__.'/register_response.json'); @@ -296,7 +377,8 @@ public function testRegisterThrowsWithChangedKeyHandle() { /** * @covers ::register */ - public function testRegisterThrowsWithChangedPubkey() { + public function testRegisterThrowsWithChangedPubkey() + { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: public key $json = file_get_contents(__DIR__.'/register_response.json'); @@ -316,14 +398,15 @@ public function testRegisterThrowsWithChangedPubkey() { /** * @covers ::register */ - public function testRegisterThrowsWithBadSignature() { + public function testRegisterThrowsWithBadSignature() + { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: signature $json = file_get_contents(__DIR__.'/register_response.json'); $data = json_decode($json, true); $reg = $data['registrationData']; $last = str_rot13(substr($reg, -5)); // rot13 a few chars in signature - $data['registrationData'] = substr($reg,0,-5).$last; + $data['registrationData'] = substr($reg, 0, -5).$last; $response = RegisterResponse::fromJson(json_encode($data)); $this->expectException(SecurityException::class); @@ -338,7 +421,8 @@ public function testRegisterThrowsWithBadSignature() { /** * @covers ::authenticate */ - public function testAuthenticateThrowsIfNoRegistrationsPresent() { + public function testAuthenticateThrowsIfNoRegistrationsPresent() + { $this->server->setSignRequests([$this->getDefaultSignRequest()]); $this->expectException(BadMethodCallException::class); $this->server->authenticate($this->getDefaultSignResponse()); @@ -347,7 +431,8 @@ public function testAuthenticateThrowsIfNoRegistrationsPresent() { /** * @covers ::authenticate */ - public function testAuthenticateThrowsIfNoSignRequestsPresent() { + public function testAuthenticateThrowsIfNoSignRequestsPresent() + { $this->server->setRegistrations([$this->getDefaultRegistration()]); $this->expectException(BadMethodCallException::class); $this->server->authenticate($this->getDefaultSignResponse()); @@ -356,7 +441,8 @@ public function testAuthenticateThrowsIfNoSignRequestsPresent() { /** * @covers ::authenticate */ - public function testAuthenticate() { + public function testAuthenticate() + { // All normal $registration = $this->getDefaultRegistration(); $request = $this->getDefaultSignRequest(); @@ -366,12 +452,21 @@ public function testAuthenticate() { ->setRegistrations([$registration]) ->setSignRequests([$request]) ->authenticate($response); - $this->assertInstanceOf(Registration::class, $return, - 'A successful authentication should have returned a Registration'); - $this->assertNotSame($registration, $return, - 'A new instance of Registration should have been returned'); - $this->assertSame($response->getCounter(), $return->getCounter(), - 'The new Registration\'s counter did not match the Response'); + $this->assertInstanceOf( + Registration::class, + $return, + 'A successful authentication should have returned a Registration' + ); + $this->assertNotSame( + $registration, + $return, + 'A new instance of Registration should have been returned' + ); + $this->assertSame( + $response->getCounter(), + $return->getCounter(), + 'The new Registration\'s counter did not match the Response' + ); } /** @@ -389,7 +484,8 @@ public function testAuthenticateWithMultibyteSettings() $this->markTestSkipped(sprintf( "mbstring.func_overload cannot be changed at runtime. Re-run ". "this test with the following command:\n\n%s", - $cmd)); + $cmd + )); } $this->testAuthenticate(); @@ -401,7 +497,8 @@ public function testAuthenticateWithMultibyteSettings() * * @covers ::authenticate */ - public function testAuthenticateThrowsWithObviousReplayAttack() { + public function testAuthenticateThrowsWithObviousReplayAttack() + { // All normal $registration = $this->getDefaultRegistration(); $request = $this->getDefaultSignRequest(); @@ -428,7 +525,8 @@ public function testAuthenticateThrowsWithObviousReplayAttack() { /** * @covers ::authenticate */ - public function testAuthenticateThrowsWhenCounterGoesBackwards() { + public function testAuthenticateThrowsWhenCounterGoesBackwards() + { // Counter from "DB" bumped, suggesting response was cloned $registration = (new Registration()) ->setKeyHandle(fromBase64Web(self::ENCODED_KEY_HANDLE)) @@ -449,7 +547,8 @@ public function testAuthenticateThrowsWhenCounterGoesBackwards() { /** * @covers ::authenticate */ - public function testAuthenticateThrowsWhenChallengeDoesNotMatch() { + public function testAuthenticateThrowsWhenChallengeDoesNotMatch() + { $registration = $this->getDefaultRegistration(); // Change request challenge $request = (new SignRequest()) @@ -470,7 +569,8 @@ public function testAuthenticateThrowsWhenChallengeDoesNotMatch() { /** * @covers ::authenticate */ - public function testAuthenticateThrowsIfNoRegistrationMatchesKeyHandle() { + public function testAuthenticateThrowsIfNoRegistrationMatchesKeyHandle() + { // Change registration KH $registration = (new Registration()) ->setKeyHandle(fromBase64Web('some-other-key-handle')) @@ -491,7 +591,8 @@ public function testAuthenticateThrowsIfNoRegistrationMatchesKeyHandle() { /** * @covers ::authenticate */ - public function testAuthenticateThrowsIfNoRequestMatchesKeyHandle() { + public function testAuthenticateThrowsIfNoRequestMatchesKeyHandle() + { $registration = $this->getDefaultRegistration(); // Change request KH $request = (new SignRequest()) @@ -512,13 +613,15 @@ public function testAuthenticateThrowsIfNoRequestMatchesKeyHandle() { /** * @covers ::authenticate */ - public function testAuthenticateThrowsIfSignatureIsInvalid() { + public function testAuthenticateThrowsIfSignatureIsInvalid() + { $registration = $this->getDefaultRegistration(); $request = $this->getDefaultSignRequest(); // Trimming a byte off the signature to cause a mismatch $data = json_decode( file_get_contents(__DIR__.'/sign_response.json'), - true); + true + ); $data['signatureData'] = substr($data['signatureData'], 0, -1); $response = SignResponse::fromJson(json_encode($data)); @@ -537,7 +640,8 @@ public function testAuthenticateThrowsIfSignatureIsInvalid() { * * @covers ::authenticate */ - public function testAuthenticateThrowsIfRequestIsSignedWithWrongKey() { + public function testAuthenticateThrowsIfRequestIsSignedWithWrongKey() + { // This was a different key genearated with: // $ openssl ecparam -name prime256v1 -genkey -out private.pem // $ openssl ec -in private.pem -pubout -out public.pem @@ -547,7 +651,8 @@ public function testAuthenticateThrowsIfRequestIsSignedWithWrongKey() { ->setKeyHandle(fromBase64Web(self::ENCODED_KEY_HANDLE)) ->setPublicKey(base64_decode( 'BCXk9bGiuzLRJaX6pFONm+twgIrDkOSNDdXgltt+KhOD'. - '9OxeRv2zYiz7SrVa8eb4LbGR9IDUE7gJySiiuQYWt1w=')) + '9OxeRv2zYiz7SrVa8eb4LbGR9IDUE7gJySiiuQYWt1w=' + )) ->setCounter(2) ; $request = $this->getDefaultSignRequest(); @@ -562,19 +667,23 @@ public function testAuthenticateThrowsIfRequestIsSignedWithWrongKey() { // -( Helpers )------------------------------------------------------------ - private function getDefaultRegisterRequest(): RegisterRequest { + private function getDefaultRegisterRequest(): RegisterRequest + { // This would have come from a session, database, etc. return (new RegisterRequest()) ->setAppId('https://u2f.ericstern.com') ->setChallenge('PfsWR1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo'); } - private function getDefaultRegisterResponse(): RegisterResponse { + private function getDefaultRegisterResponse(): RegisterResponse + { return RegisterResponse::fromJson( - file_get_contents(__DIR__.'/register_response.json')); + file_get_contents(__DIR__.'/register_response.json') + ); } - private function getDefaultSignRequest(): SignRequest { + private function getDefaultSignRequest(): SignRequest + { // This would have come from a session, database, etc return (new SignRequest()) ->setAppId('https://u2f.ericstern.com') @@ -583,7 +692,8 @@ private function getDefaultSignRequest(): SignRequest { ; } - private function getDefaultRegistration(): Registration { + private function getDefaultRegistration(): Registration + { // From database attached to the authenticating user return (new Registration()) ->setKeyHandle(fromBase64Web(self::ENCODED_KEY_HANDLE)) @@ -592,9 +702,11 @@ private function getDefaultRegistration(): Registration { ; } - private function getDefaultSignResponse(): SignResponse { + private function getDefaultSignResponse(): SignResponse + { // Value from user return SignResponse::fromJson( - file_get_contents(__DIR__.'/sign_response.json')); + file_get_contents(__DIR__.'/sign_response.json') + ); } } diff --git a/tests/SignRequestTest.php b/tests/SignRequestTest.php index d9ef577..5dabe49 100644 --- a/tests/SignRequestTest.php +++ b/tests/SignRequestTest.php @@ -13,7 +13,8 @@ class SignRequestTest extends \PHPUnit\Framework\TestCase /** * @covers ::jsonSerialize */ - public function testJsonSerialize() { + public function testJsonSerialize() + { $appId = 'https://u2f.example.com'; $challenge = 'some-random-string'; $keyHandle = random_bytes(20); @@ -22,30 +23,56 @@ public function testJsonSerialize() { $request ->setAppId($appId) ->setChallenge($challenge) - ->setKeyHandle($keyHandle);; + ->setKeyHandle($keyHandle); $json = json_encode($request); $decoded = json_decode($json, true); - $this->assertSame($appId, $request->getAppId(), - 'getAppId returned the wrong value'); - $this->assertSame($appId, $decoded['appId'], - 'json appId property did not match'); + $this->assertSame( + $appId, + $request->getAppId(), + 'getAppId returned the wrong value' + ); + $this->assertSame( + $appId, + $decoded['appId'], + 'json appId property did not match' + ); - $this->assertSame($challenge, $request->getChallenge(), - 'getChallenge returned the wrong value'); - $this->assertSame($challenge, $decoded['challenge'], - 'json challenge property did not match'); + $this->assertSame( + $challenge, + $request->getChallenge(), + 'getChallenge returned the wrong value' + ); + $this->assertSame( + $challenge, + $decoded['challenge'], + 'json challenge property did not match' + ); - $this->assertSame($keyHandle, $request->getKeyHandleBinary(), - 'getKeyHandleBinary returned the wrong value'); - $this->assertSame(toBase64Web($keyHandle), $request->getKeyHandleWeb(), - 'getKeyHandleWeb returned the wrong value'); - $this->assertSame(toBase64Web($keyHandle), $decoded['keyHandle'], - 'json keyHandle property did not match'); + $this->assertSame( + $keyHandle, + $request->getKeyHandleBinary(), + 'getKeyHandleBinary returned the wrong value' + ); + $this->assertSame( + toBase64Web($keyHandle), + $request->getKeyHandleWeb(), + 'getKeyHandleWeb returned the wrong value' + ); + $this->assertSame( + toBase64Web($keyHandle), + $decoded['keyHandle'], + 'json keyHandle property did not match' + ); - $this->assertSame('U2F_V2', $request->getVersion(), - 'getVersion returned the wrong value'); - $this->assertSame('U2F_V2', $decoded['version'], - 'json version was incorrect'); + $this->assertSame( + 'U2F_V2', + $request->getVersion(), + 'getVersion returned the wrong value' + ); + $this->assertSame( + 'U2F_V2', + $decoded['version'], + 'json version was incorrect' + ); } - } diff --git a/tests/SignResponseTest.php b/tests/SignResponseTest.php index 0efba66..428392b 100644 --- a/tests/SignResponseTest.php +++ b/tests/SignResponseTest.php @@ -12,18 +12,31 @@ class SignResponseTest extends \PHPUnit\Framework\TestCase { const JSON_FORMAT = '{"keyHandle":"%s","clientData":"%s","signatureData":"%s"}'; - private $valid_key_handle = 'JUnVTStPn-V2-bCu0RlvPbukBpHTD5Mi1ZGglDOcN0vD45rnTD0BXdkRt78huTwJ7tVaxTqSetHjr22tCjmYLQ'; - private $valid_client_data = 'eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoid3QyemU4SXNrY1RPM25Jc08yRDJoRmpFNXRWRDA0MU5wblllc0xwSndlZyIsIm9yaWdpbiI6Imh0dHBzOi8vdTJmLmVyaWNzdGVybi5jb20iLCJjaWRfcHVia2V5IjoiIn0'; - private $valid_signature_data = 'AQAAAC0wRgIhAJPy1RvD1WCw1XZX53BXydX_Kyf_XZQueFSIPigRF-D2AiEAx3bJr5ixrXGdUX1XooAfhz15ZIY8rC5H4qaW7gQspJ4'; + + private $valid_key_handle = + 'JUnVTStPn-V2-bCu0RlvPbukBpHTD5Mi1ZGglDOcN0vD45rnTD0BXdkRt78huTwJ7tVax'. + 'TqSetHjr22tCjmYLQ'; + + private $valid_client_data = + 'eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoid3Qye'. + 'mU4SXNrY1RPM25Jc08yRDJoRmpFNXRWRDA0MU5wblllc0xwSndlZyIsIm9yaWdpbiI6Im'. + 'h0dHBzOi8vdTJmLmVyaWNzdGVybi5jb20iLCJjaWRfcHVia2V5IjoiIn0'; + + private $valid_signature_data = + 'AQAAAC0wRgIhAJPy1RvD1WCw1XZX53BXydX_Kyf_XZQueFSIPigRF-D2AiEAx3bJr5ixr'. + 'XGdUX1XooAfhz15ZIY8rC5H4qaW7gQspJ4'; /** * @covers ::fromJson */ - public function testFromJsonWorks() { - $json = sprintf(self::JSON_FORMAT, + public function testFromJsonWorks() + { + $json = sprintf( + self::JSON_FORMAT, $this->valid_key_handle, $this->valid_client_data, - $this->valid_signature_data); + $this->valid_signature_data + ); $response = SignResponse::fromJson($json); $this->assertInstanceOf(SignResponse::class, $response); } @@ -33,9 +46,10 @@ public function testFromJsonWorks() { * @covers ::getSignature * @covers ::getUserPresenceByte */ - public function testDataAccuracyAfterSuccessfulParsing() { + public function testDataAccuracyAfterSuccessfulParsing() + { $sig = random_bytes(16); - $counter = random_int(0, pow(2,32)); + $counter = random_int(0, pow(2, 32)); $signature_data = toBase64Web(pack('CNA*', 1, $counter, $sig)); $challenge = toBase64Web(random_bytes(32)); @@ -48,76 +62,117 @@ public function testDataAccuracyAfterSuccessfulParsing() { "cid_pubkey" => "" ])); - $json = sprintf(self::JSON_FORMAT, + $json = sprintf( + self::JSON_FORMAT, $key_handle, $client_data, - $signature_data); + $signature_data + ); $response = SignResponse::fromJson($json); - $this->assertSame($key_handle, $response->getKeyHandleWeb(), - 'Key Handle was parsed incorrectly'); - $this->assertSame($counter, $response->getCounter(), - 'Counter was parsed incorrectly'); - $this->assertSame(1, $response->getUserPresenceByte(), - 'User presence byte was parsed incorrectly'); - $this->assertSame($sig, $response->getSignature(), - 'Signature was parsed incorrectly'); + $this->assertSame( + $key_handle, + $response->getKeyHandleWeb(), + 'Key Handle was parsed incorrectly' + ); + $this->assertSame( + $counter, + $response->getCounter(), + 'Counter was parsed incorrectly' + ); + $this->assertSame( + 1, + $response->getUserPresenceByte(), + 'User presence byte was parsed incorrectly' + ); + $this->assertSame( + $sig, + $response->getSignature(), + 'Signature was parsed incorrectly' + ); } - public function testSignatureWithNullRemainsIntact() { + public function testSignatureWithNullRemainsIntact() + { $sig = "\x00\x00\x00".random_bytes(10)."\x00\x00\x00"; $sigData = toBase64Web("\x01\x00\x00\x00\x45".$sig); - $json = sprintf(self::JSON_FORMAT, + $json = sprintf( + self::JSON_FORMAT, $this->valid_key_handle, $this->valid_client_data, - $sigData); + $sigData + ); $response = SignResponse::fromJson($json); - $this->assertSame($sig, $response->getSignature(), - 'Signature trimmed a trailing NUL byte'); + $this->assertSame( + $sig, + $response->getSignature(), + 'Signature trimmed a trailing NUL byte' + ); } - public function testSignatureWithSpaceRemainsIntact() { + public function testSignatureWithSpaceRemainsIntact() + { $sig = ' '.random_bytes(10).' '; $sigData = toBase64Web("\x01\x00\x00\x00\x45".$sig); - $json = sprintf(self::JSON_FORMAT, + $json = sprintf( + self::JSON_FORMAT, $this->valid_key_handle, $this->valid_client_data, - $sigData); + $sigData + ); $response = SignResponse::fromJson($json); - $this->assertSame($sig, $response->getSignature(), - 'Signature trimmed a trailing space'); + $this->assertSame( + $sig, + $response->getSignature(), + 'Signature trimmed a trailing space' + ); } - public function testFromJsonWithMissingKeyHandle() { - $json = sprintf('{"clientData":"%s","signatureData":"%s"}', - $this->valid_client_data, $this->valid_signature_data); + public function testFromJsonWithMissingKeyHandle() + { + $json = sprintf( + '{"clientData":"%s","signatureData":"%s"}', + $this->valid_client_data, + $this->valid_signature_data + ); $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MISSING_KEY); SignResponse::fromJson($json); } - public function testFromJsonWithMissingClientData() { - $json = sprintf('{"keyHandle":"%s","signatureData":"%s"}', - $this->valid_key_handle, $this->valid_signature_data); + public function testFromJsonWithMissingClientData() + { + $json = sprintf( + '{"keyHandle":"%s","signatureData":"%s"}', + $this->valid_key_handle, + $this->valid_signature_data + ); $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MISSING_KEY); SignResponse::fromJson($json); } - public function testFromJsonWithMissingSignatureData() { - $json = sprintf('{"keyHandle":"%s","clientData":"%s"}', - $this->valid_key_handle, $this->valid_client_data); + public function testFromJsonWithMissingSignatureData() + { + $json = sprintf( + '{"keyHandle":"%s","clientData":"%s"}', + $this->valid_key_handle, + $this->valid_client_data + ); $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MISSING_KEY); SignResponse::fromJson($json); } - public function testFromJsonWithInvalidSignatureData() { - $json = sprintf('{"keyHandle":"%s","clientData":"%s","signatureData":"%s"}', + public function testFromJsonWithInvalidSignatureData() + { + $json = sprintf( + '{"keyHandle":"%s","clientData":"%s","signatureData":"%s"}', $this->valid_key_handle, $this->valid_client_data, - '0000000'); + '0000000' + ); $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MALFORMED_DATA); SignResponse::fromJson($json); @@ -126,14 +181,16 @@ public function testFromJsonWithInvalidSignatureData() { /** * @dataProvider clientErrors */ - public function testErrorResponse(int $code) { + public function testErrorResponse(int $code) + { $json = sprintf('{"errorCode":%d}', $code); $this->expectException(ClientErrorException::class); $this->expectExceptionCode($code); SignResponse::fromJson($json); } - public function clientErrors() { + public function clientErrors() + { return [ [ClientError::OTHER_ERROR], [ClientError::BAD_REQUEST], @@ -142,5 +199,4 @@ public function clientErrors() { [ClientError::TIMEOUT], ]; } - } diff --git a/tests/VersionTraitTest.php b/tests/VersionTraitTest.php index db859ce..dbeed1b 100644 --- a/tests/VersionTraitTest.php +++ b/tests/VersionTraitTest.php @@ -14,13 +14,15 @@ class VersionTraitTest extends \PHPUnit\Framework\TestCase /** * @covers ::getVersion */ - public function testGetVersion() { + public function testGetVersion() + { $obj = new class { use VersionTrait; }; - $this->assertSame('U2F_V2', $obj->getVersion(), - 'getVersion should always return the string "U2F_V2" per the spec'); + $this->assertSame( + 'U2F_V2', + $obj->getVersion(), + 'getVersion should always return the string "U2F_V2" per the spec' + ); } - - } From 9c06b4b61517741f8b7e5ed43b5c780dc2bd080c Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Sun, 29 Apr 2018 20:08:41 -0700 Subject: [PATCH 05/17] Add test coverage for multibyte string un-overloading functions (#9) --- tests/FunctionsTest.php | 62 ++++++++++++++++++++++++++++++++- tests/MultibyteWarningTrait.php | 21 +++++++++++ tests/ServerTest.php | 14 ++------ 3 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 tests/MultibyteWarningTrait.php diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 0e35dac..3fb8206 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -5,7 +5,7 @@ class FunctionsTest extends \PHPUnit\Framework\TestCase { - + use MultibyteWarningTrait; /** * @covers Firehed\U2F\fromBase64Web @@ -33,6 +33,32 @@ public function testToBase64Web($plain, $encoded) ); } + /** + * @covers Firehed\U2F\strlen + * @dataProvider strlenVectors + */ + public function testStrlen(string $string, int $length) + { + $this->assertSame(strlen($string), $length, 'Wrong length returned'); + } + + /** + * @covers Firehed\U2F\substr + * @dataProvider substrVectors + */ + public function testSubstr(string $string, int $start, int $length = null, string $result) + { + $this->assertSame( + $result, + substr($string, $start, $length), + sprintf( + 'Wrong substring returned: got bytes %s instead of %s', + bin2hex(substr($string, $start, $length)), + bin2hex($result) + ) + ); + } + public function vectors(): array { return [ @@ -49,4 +75,38 @@ public function vectors(): array [hex2bin('000fc107e71c'), "AA_BB-cc"], ]; } + + public function strlenVectors(): array + { + $this->skipIfNotMultibyte(); + // Strlen should be un-overloaded to just count bytes + return [ + ['ascii text', 10], + ['texte français', 15], + ['русский текст', 25], + ['日本語テキスト', 21], + ['✨emoji❤️text🐰', 22], + ['🐰', 4], + ]; + } + + public function substrVectors(): array + { + $this->skipIfNotMultibyte(); + return [ + // Substr should be un-overloaded to just work on bytes, resulting + // in multibyte characters potentially getting cut in half + ['ascii text', 5, null, ' text'], + ['ascii text', 0, 5, 'ascii'], + ['texte français', 5, null, ' français'], + ['texte français', 0, 5, 'texte'], + ['русский текст', 5, null, chr(0x81).'ский текст'], + ['русский текст', 0, 5, 'ру'.chr(0xD1)], + ['日本語テキスト', 5, null, chr(0xAC).'語テキスト'], + ['日本語テキスト', 0, 5, '日'.chr(0xE6).chr(0x9C)], + ['✨emoji❤️text🐰', 5, null, 'oji❤️text🐰'], + ['✨emoji❤️text🐰', 0, 5, '✨em'], + ['🐰', 1, 2, chr(0x9F).chr(0x90)], + ]; + } } diff --git a/tests/MultibyteWarningTrait.php b/tests/MultibyteWarningTrait.php new file mode 100644 index 0000000..d9033fc --- /dev/null +++ b/tests/MultibyteWarningTrait.php @@ -0,0 +1,21 @@ +markTestSkipped(sprintf( + "mbstring.func_overload cannot be changed at runtime. Re-run ". + "this test with the following command:\n\n%s", + $cmd + )); + } + } +} diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 805e554..110749a 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -12,6 +12,8 @@ */ class ServerTest extends \PHPUnit\Framework\TestCase { + use MultibyteWarningTrait; + const APP_ID = 'https://u2f.example.com'; const ENCODED_KEY_HANDLE = @@ -477,17 +479,7 @@ public function testAuthenticate() */ public function testAuthenticateWithMultibyteSettings() { - $overload = ini_get('mbstring.func_overload'); - if ($overload != 7) { - $cmd = 'php -d mbstring.func_overload=7 '. - implode(' ', $_SERVER['argv']); - $this->markTestSkipped(sprintf( - "mbstring.func_overload cannot be changed at runtime. Re-run ". - "this test with the following command:\n\n%s", - $cmd - )); - } - + $this->skipIfNotMultibyte(); $this->testAuthenticate(); } From 65bb799b1f709192ff7fed1af814ca43b03a64c7 Mon Sep 17 00:00:00 2001 From: Louis-Marie Matthews Date: Tue, 22 May 2018 17:27:00 +0100 Subject: [PATCH 06/17] Add RegistrationInterface (#10) --- src/ECPublicKeyTrait.php | 17 ++++++++++-- src/Registration.php | 2 +- src/RegistrationInterface.php | 32 +++++++++++++++++++++++ src/Server.php | 47 +++++++++++++++++----------------- tests/ECPublicKeyTraitTest.php | 2 +- tests/RegisterResponseTest.php | 2 +- tests/ServerTest.php | 20 ++++++++------- 7 files changed, 85 insertions(+), 37 deletions(-) create mode 100644 src/RegistrationInterface.php diff --git a/src/ECPublicKeyTrait.php b/src/ECPublicKeyTrait.php index 527beeb..dbdbbdf 100644 --- a/src/ECPublicKeyTrait.php +++ b/src/ECPublicKeyTrait.php @@ -10,8 +10,21 @@ trait ECPublicKeyTrait // Stored base64-encoded private $pubKey = ''; - // Binary string of public key + /** + * @deprecated Methods that return binary values are suffixed with "binary". + * @return string Binary string of public key + */ public function getPublicKey(): string + { + trigger_error('Please use getPublicKeyBinary().', E_USER_DEPRECATED); + + return base64_decode($this->pubKey); + } + + /** + * @return string The decoded public key. + */ + public function getPublicKeyBinary(): string { return base64_decode($this->pubKey); } @@ -20,7 +33,7 @@ public function getPublicKey(): string // public key component public function getPublicKeyPem(): string { - $key = $this->getPublicKey(); + $key = $this->getPublicKeyBinary(); // Described in RFC 5480 // Just use an OID calculator to figure out *that* encoding diff --git a/src/Registration.php b/src/Registration.php index e3d786f..05c3d41 100644 --- a/src/Registration.php +++ b/src/Registration.php @@ -5,7 +5,7 @@ use OutOfBoundsException; -class Registration +class Registration implements RegistrationInterface { use AttestationCertificateTrait; use ECPublicKeyTrait; diff --git a/src/RegistrationInterface.php b/src/RegistrationInterface.php new file mode 100644 index 0000000..c440b56 --- /dev/null +++ b/src/RegistrationInterface.php @@ -0,0 +1,32 @@ +registrations) { throw new BadMethodCallException( - 'Before calling authenticate(), provide `Registration`s with '. - 'setRegistrations()' + 'Before calling authenticate(), provide objects implementing'. + 'RegistrationInterface with setRegistrations()' ); } if (!$this->signRequests) { @@ -172,22 +172,22 @@ public function authenticate(SignResponse $response): Registration return (new Registration()) ->setAttestationCertificate($registration->getAttestationCertificateBinary()) ->setKeyHandle($registration->getKeyHandleBinary()) - ->setPublicKey($registration->getPublicKey()) + ->setPublicKey($registration->getPublicKeyBinary()) ->setCounter($response->getCounter()); } /** * This method authenticates a RegisterResponse against its corresponding * RegisterRequest by verifying the certificate and signature. If valid, it - * returns a Registration object; if not, a SE will be - * thrown and attempt to register the key must be aborted. + * returns a registration; if not, a SE will be thrown and attempt to + * register the key must be aborted. * * @param RegisterResponse $resp The response to verify - * @return Registration if the response is proven authentic + * @return RegistrationInterface if the response is proven authentic * @throws SE if the response cannot be proven authentic * @throws BadMethodCallException if a precondition is not met */ - public function register(RegisterResponse $resp): Registration + public function register(RegisterResponse $resp): RegistrationInterface { if (!$this->registerRequest) { throw new BadMethodCallException( @@ -205,7 +205,7 @@ public function register(RegisterResponse $resp): Registration $this->registerRequest->getApplicationParameter(), $resp->getClientData()->getChallengeParameter(), $resp->getKeyHandleBinary(), - $resp->getPublicKey() + $resp->getPublicKeyBinary() ); $pem = $resp->getAttestationCertificatePem(); @@ -228,7 +228,7 @@ public function register(RegisterResponse $resp): Registration ->setAttestationCertificate($resp->getAttestationCertificateBinary()) ->setCounter(0) // The response does not include this ->setKeyHandle($resp->getKeyHandleBinary()) - ->setPublicKey($resp->getPublicKey()); + ->setPublicKey($resp->getPublicKeyBinary()); } /** @@ -278,9 +278,10 @@ public function setRegisterRequest(RegisterRequest $request): self } /** - * Provide a user's existing Registrations to be used during authentication + * Provide a user's existing registration to be used during + * authentication * - * @param Registration[] $registrations + * @param RegistrationInterface[] $registrations * @return self */ public function setRegistrations(array $registrations): self @@ -321,13 +322,13 @@ public function generateRegisterRequest(): RegisterRequest } /** - * Creates a new SignRequest for an existing Registration for an + * Creates a new SignRequest for an existing registration for an * authenticating user, used by the `u2f.sign` API. * - * @param Registration $reg one of the user's existing Registrations + * @param RegistrationInterface $reg one of the user's existing Registrations * @return SignRequest */ - public function generateSignRequest(Registration $reg): SignRequest + public function generateSignRequest(RegistrationInterface $reg): SignRequest { return (new SignRequest()) ->setAppId($this->getAppId()) @@ -336,9 +337,9 @@ public function generateSignRequest(Registration $reg): SignRequest } /** - * Wraps generateSignRequest for multiple Registrations + * Wraps generateSignRequest for multiple registrations * - * @param Registration[] $registrations + * @param RegistrationInterface[] $registrations * @return SignRequest[] */ public function generateSignRequests(array $registrations): array diff --git a/tests/ECPublicKeyTraitTest.php b/tests/ECPublicKeyTraitTest.php index c737021..2770541 100644 --- a/tests/ECPublicKeyTraitTest.php +++ b/tests/ECPublicKeyTraitTest.php @@ -28,7 +28,7 @@ public function testAccessors() ); $this->assertSame( $key, - $obj->getPublicKey(), + $obj->getPublicKeyBinary(), 'getPublicKey should return the set value' ); } diff --git a/tests/RegisterResponseTest.php b/tests/RegisterResponseTest.php index b88c65a..80cfd25 100644 --- a/tests/RegisterResponseTest.php +++ b/tests/RegisterResponseTest.php @@ -117,7 +117,7 @@ public function testDataAccuracyAfterSuccessfulParsing() $this->assertSame( $pubkey, - $response->getPublicKey(), + $response->getPublicKeyBinary(), 'Public key was not parsed correctly' ); $this->assertSame( diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 110749a..4a31564 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -200,7 +200,7 @@ public function testRegistration() ->setRegisterRequest($request) ->register($response); $this->assertInstanceOf( - Registration::class, + RegistrationInterface::class, $registration, 'Server->register did not return a registration' ); @@ -223,8 +223,8 @@ public function testRegistration() ); $this->assertSame( - $response->getPublicKey(), - $registration->getPublicKey(), + $response->getPublicKeyBinary(), + $registration->getPublicKeyBinary(), 'Public key was not copied from response' ); } @@ -311,7 +311,7 @@ public function testRegisterWorksWithCAList() } throw $e; } - $this->assertInstanceOf(Registration::class, $reg); + $this->assertInstanceOf(RegistrationInterface::class, $reg); } /** @@ -455,19 +455,21 @@ public function testAuthenticate() ->setSignRequests([$request]) ->authenticate($response); $this->assertInstanceOf( - Registration::class, + RegistrationInterface::class, $return, - 'A successful authentication should have returned a Registration' + 'A successful authentication should have returned an object '. + 'implementing RegistrationInterface' ); $this->assertNotSame( $registration, $return, - 'A new instance of Registration should have been returned' + 'A new object implementing RegistrationInterface should have been '. + 'returned' ); $this->assertSame( $response->getCounter(), $return->getCounter(), - 'The new Registration\'s counter did not match the Response' + 'The new registration\'s counter did not match the Response' ); } @@ -684,7 +686,7 @@ private function getDefaultSignRequest(): SignRequest ; } - private function getDefaultRegistration(): Registration + private function getDefaultRegistration(): RegistrationInterface { // From database attached to the authenticating user return (new Registration()) From 37656ada4c290f17a7adc6100c63b029ee73d39d Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 27 May 2019 11:09:10 -0700 Subject: [PATCH 07/17] Update dependencies, cleanup (#12) --- .gitignore | 1 + .travis.yml | 8 +- README.md | 7 ++ composer.json | 23 ++++- phpstan.neon | 6 ++ src/AppIdTrait.php | 2 +- src/AttestationCertificateTrait.php | 10 +- src/ClientData.php | 1 + src/ECPublicKeyTrait.php | 8 +- src/KeyHandleTrait.php | 5 +- src/ResponseTrait.php | 9 +- src/Server.php | 12 +++ src/functions.php | 42 +------- tests/AttestationCertificateTraitTest.php | 6 +- tests/ClientDataTest.php | 4 + tests/ECPublicKeyTraitTest.php | 3 +- tests/FunctionsTest.php | 62 ------------ tests/MultibyteWarningTrait.php | 21 ---- tests/RegisterRequestTest.php | 1 + tests/RegisterResponseTest.php | 1 + tests/ResponseTraitTest.php | 2 +- tests/ServerTest.php | 112 +++++++++++++--------- tests/SignRequestTest.php | 1 + tests/SignResponseTest.php | 6 +- 24 files changed, 151 insertions(+), 202 deletions(-) create mode 100644 phpstan.neon delete mode 100644 tests/MultibyteWarningTrait.php diff --git a/.gitignore b/.gitignore index 237c7bf..010fb9a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /vendor/ # Suggested for libraries composer.lock +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml index b8d8ca8..fb8aea0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,17 @@ language: php php: - - '7.0' - - '7.1' - '7.2' + - '7.3' + - '7.4snapshot' # From PHPUnit's config install: - travis_retry composer install --no-interaction --prefer-source script: - - php -d mbstring.func_overload=7 vendor/bin/phpunit --coverage-text --whitelist src/ tests/ + - php vendor/bin/phpunit --coverage-text --whitelist src/ tests/ - vendor/bin/phpcs src tests - - vendor/bin/phpstan analyse --no-progress -l7 src tests + - vendor/bin/phpstan analyse . after_success: - travis_retry php vendor/bin/php-coveralls diff --git a/README.md b/README.md index 95abd56..546dff9 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,13 @@ Additional resources: * [FIDO U2F Overview](https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-overview.html) * [FIDO U2F Javascript API](https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-javascript-api.html) +## Installation + +`composer require firehed/u2f` + +Note: you **must not** be using the deprecated `mbstring.func_overload` functionality, which can completely break working on binary data. +The library will immediately throw an exception if you have it enabled. + ## Usage Usage will be described in three parts: setup, registration, and authentication. diff --git a/composer.json b/composer.json index aa526cd..4c6a3f7 100644 --- a/composer.json +++ b/composer.json @@ -13,13 +13,14 @@ ], "homepage": "https://github.com/Firehed/u2f-php", "require": { - "php": ">=7.0" + "php": ">=7.2" }, "require-dev": { "php-coveralls/php-coveralls": "^2.0", - "phpstan/phpstan": "^0.9.2", - "phpunit/phpunit": "^6.0 || ^7.0", - "squizlabs/php_codesniffer": "^3.2" + "phpstan/phpstan": "^0.11", + "phpunit/phpunit": "^8.0", + "squizlabs/php_codesniffer": "^3.2", + "spatie/phpunit-watcher": "^1.8" }, "autoload": { "psr-4": { @@ -39,5 +40,17 @@ "name": "Eric Stern", "email": "eric@ericstern.com" } - ] + ], + "scripts": { + "test": [ + "@phpunit", + "@phpstan", + "@phpcs" + ], + "coverage": "phpunit --coverage-html build; open build/index.html", + "autofix": "phpcbf src lib tests db", + "phpunit": "phpunit", + "phpstan": "phpstan analyse --no-progress .", + "phpcs": "phpcs ." + } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..82b5f09 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + excludes_analyse: + - %rootDir%/../../../vendor + level: 7 + includes: + - vendor/phpstan/phpstan/conf/bleedingEdge.neon diff --git a/src/AppIdTrait.php b/src/AppIdTrait.php index 8bf52cf..c54e828 100644 --- a/src/AppIdTrait.php +++ b/src/AppIdTrait.php @@ -19,7 +19,7 @@ public function setAppId(string $appId): self } /** - * @return the raw SHA-256 hash of the App ID + * @return string The raw SHA-256 hash of the App ID */ public function getApplicationParameter(): string { diff --git a/src/AttestationCertificateTrait.php b/src/AttestationCertificateTrait.php index 8b448f8..6620773 100644 --- a/src/AttestationCertificateTrait.php +++ b/src/AttestationCertificateTrait.php @@ -5,21 +5,21 @@ trait AttestationCertificateTrait { - - // Stored base64-encoded + /** @var string (binary) */ private $attest = ''; // Binary string of attestation certificate (from device issuer) public function getAttestationCertificateBinary(): string { - return base64_decode($this->attest); + return $this->attest; } // PEM formatted cert public function getAttestationCertificatePem(): string { + $data = base64_encode($this->getAttestationCertificateBinary()); $pem = "-----BEGIN CERTIFICATE-----\r\n"; - $pem .= chunk_split($this->attest, 64); + $pem .= chunk_split($data, 64); $pem .= "-----END CERTIFICATE-----"; return $pem; } @@ -28,7 +28,7 @@ public function setAttestationCertificate(string $cert): self { // In the future, this may make assertions about the cert formatting; // right now, we're going to leave it be. - $this->attest = base64_encode($cert); + $this->attest = $cert; return $this; } diff --git a/src/ClientData.php b/src/ClientData.php index 5713eca..73b1ee9 100644 --- a/src/ClientData.php +++ b/src/ClientData.php @@ -68,6 +68,7 @@ private function validateKey(string $key, array $data) public function getChallengeParameter(): string { $json = json_encode($this, \JSON_UNESCAPED_SLASHES); + assert($json !== false); return hash('sha256', $json, true); } diff --git a/src/ECPublicKeyTrait.php b/src/ECPublicKeyTrait.php index dbdbbdf..101fc5a 100644 --- a/src/ECPublicKeyTrait.php +++ b/src/ECPublicKeyTrait.php @@ -7,7 +7,7 @@ trait ECPublicKeyTrait { - // Stored base64-encoded + /** @var string (binary) */ private $pubKey = ''; /** @@ -18,7 +18,7 @@ public function getPublicKey(): string { trigger_error('Please use getPublicKeyBinary().', E_USER_DEPRECATED); - return base64_decode($this->pubKey); + return $this->getPublicKeyBinary(); } /** @@ -26,7 +26,7 @@ public function getPublicKey(): string */ public function getPublicKeyBinary(): string { - return base64_decode($this->pubKey); + return $this->pubKey; } // Prepends the pubkey format headers and builds a pem file from the raw @@ -67,7 +67,7 @@ public function setPublicKey(string $key): self if (strlen($key) !== 65) { throw new IDE(IDE::PUBLIC_KEY_LENGTH, '65'); } - $this->pubKey = base64_encode($key); + $this->pubKey = $key; return $this; } } diff --git a/src/KeyHandleTrait.php b/src/KeyHandleTrait.php index 2111fec..8ab31b9 100644 --- a/src/KeyHandleTrait.php +++ b/src/KeyHandleTrait.php @@ -4,12 +4,13 @@ trait KeyHandleTrait { + /** @var string (binary) */ private $keyHandle; // Binary string of key handle public function getKeyHandleBinary(): string { - return base64_decode($this->keyHandle); + return $this->keyHandle; } // B64-websafe value public function getKeyHandleWeb(): string @@ -20,7 +21,7 @@ public function getKeyHandleWeb(): string public function setKeyHandle(string $keyHandle): self { // TODO: make immutable - $this->keyHandle = base64_encode($keyHandle); + $this->keyHandle = $keyHandle; return $this; } } diff --git a/src/ResponseTrait.php b/src/ResponseTrait.php index 77ffc1b..ba78b8a 100644 --- a/src/ResponseTrait.php +++ b/src/ResponseTrait.php @@ -7,17 +7,16 @@ trait ResponseTrait { - use KeyHandleTrait; private $clientData; - // Stored base64-encoded + + /** @var string (binary) */ private $signature = ''; - // Binary string of signature public function getSignature(): string { - return base64_decode($this->signature); + return $this->signature; } public function getClientData(): ClientData @@ -27,7 +26,7 @@ public function getClientData(): ClientData protected function setSignature(string $signature): self { - $this->signature = base64_encode($signature); + $this->signature = $signature; return $this; } diff --git a/src/Server.php b/src/Server.php index 363616a..92d0f02 100644 --- a/src/Server.php +++ b/src/Server.php @@ -5,6 +5,7 @@ use BadMethodCallException; use Firehed\U2F\SecurityException as SE; +use RuntimeException; class Server { @@ -48,6 +49,17 @@ class Server */ private $signRequests = []; + public function __construct() + { + $overload = ini_get('mbstring.func_overload'); + // @codeCoverageIgnoreStart + if ($overload > 0) { + throw new RuntimeException( + 'The deprecated "mbstring.func_overload" directive must be disabled' + ); + } + // @codeCoverageIgnoreEnd + } /** * This method authenticates a `SignResponse` against outstanding * registrations and their corresponding `SignRequest`s. If the response's diff --git a/src/functions.php b/src/functions.php index 8c31c3b..8fe26e7 100644 --- a/src/functions.php +++ b/src/functions.php @@ -9,7 +9,9 @@ */ function fromBase64Web(string $base64): string { - return base64_decode(strtr($base64, '-_', '+/')); + $decoded = base64_decode(strtr($base64, '-_', '+/')); + assert($decoded !== false); + return $decoded; } /** @@ -21,41 +23,3 @@ function toBase64Web(string $binary): string { return rtrim(strtr(base64_encode($binary), '+/', '-_'), '='); } - -// Multibyte string wrappers: hijack calls to `strlen` and `substr` to force -// 8-bit encoding in the event that `mbstring.func_overload` parameter is -// non-zero and the mbstring default charset is not 8bit. - -/** - * Identical to `\strlen` except when `mbstring.func_overload` is enabled and - * set to a multi-byte character set, in which case it retains the - * non-overloaded behavior. - * - * @param string $string The string being measured - * @return int The length of the string, in bytes - */ -function strlen(string $string): int -{ - if (function_exists('mb_strlen')) { - return \mb_strlen($string, '8bit'); - } - return \strlen($string); -} - -/** - * Identical to `\substr` except when `mbstring.func_overload` is enabled and - * set to a multi-byte character set, in which case it retains the - * non-overloaded behavior. - * - * @param string $string The input string - * @param int $start The starting point, in bytes - * @param int $length The length, in bytes - * @return string The extracted part of the string - */ -function substr(string $string, int $start, int $length = null): string -{ - if (function_exists('mb_substr')) { - return \mb_substr($string, $start, $length, '8bit'); - } - return \substr($string, $start, $length); -} diff --git a/tests/AttestationCertificateTraitTest.php b/tests/AttestationCertificateTraitTest.php index db59d57..15f4d89 100644 --- a/tests/AttestationCertificateTraitTest.php +++ b/tests/AttestationCertificateTraitTest.php @@ -96,9 +96,9 @@ public function testFailedCAVerificationFromNoCAs() */ private function getObjectWithYubicoCert() { - $response = RegisterResponse::fromJson( - file_get_contents(__DIR__.'/register_response.json') - ); + $data = file_get_contents(__DIR__.'/register_response.json'); + assert($data !== false); + $response = RegisterResponse::fromJson($data); // Sanity check that the response actually imlements this trait, rather // than doing all sorts of magic $check = AttestationCertificateTrait::class; diff --git a/tests/ClientDataTest.php b/tests/ClientDataTest.php index fc0b378..cbf2f89 100644 --- a/tests/ClientDataTest.php +++ b/tests/ClientDataTest.php @@ -23,6 +23,7 @@ public function testFromValidJson() 'cid_pubkey' => '', ]; $goodJson = json_encode($goodData); + assert($goodJson !== false); $clientData = ClientData::fromJson($goodJson); $this->assertInstanceOf(ClientData::class, $clientData); } @@ -34,6 +35,7 @@ public function testFromValidJson() public function testGetChallengeParameter() { $expected_param = base64_decode('exDPjyyKbizXMAAUNLpv0QYJNyXClbUqewUWojPtp0g='); + assert($expected_param !== false); // Sanity check $this->assertSame( 32, @@ -48,6 +50,7 @@ public function testGetChallengeParameter() 'cid_pubkey' => '', ]; $goodJson = json_encode($goodData); + assert($goodJson !== false); $clientData = ClientData::fromJson($goodJson); $this->assertTrue( hash_equals($expected_param, $clientData->getChallengeParameter()), @@ -89,6 +92,7 @@ public function testTypes(string $type, bool $allowed) 'cid_pubkey' => '', ]; $json = json_encode($all); + assert($json !== false); if (!$allowed) { $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MALFORMED_DATA); diff --git a/tests/ECPublicKeyTraitTest.php b/tests/ECPublicKeyTraitTest.php index 2770541..f84c0d3 100644 --- a/tests/ECPublicKeyTraitTest.php +++ b/tests/ECPublicKeyTraitTest.php @@ -13,7 +13,7 @@ class ECPublicKeyTraitTest extends \PHPUnit\Framework\TestCase /** * @covers ::setPublicKey - * @covers ::getPublicKey + * @covers ::getPublicKeyBinary */ public function testAccessors() { @@ -46,6 +46,7 @@ public function testGetPublicKeyPem() '04b4960ae0fa301033fbedc85c33ac30408dffd6098bc8580d8b66159959d89b9'. '31daf1d43a1949b07b7d47eea25efcac478bb5cd6ead0a3c3f7b7cb2a7bc1e3be' ); + assert($key !== false); $pem = "-----BEGIN PUBLIC KEY-----\r\n". "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtJYK4PowEDP77chcM6wwQI3/1gmL\r\n". diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 3fb8206..ee9f727 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -5,8 +5,6 @@ class FunctionsTest extends \PHPUnit\Framework\TestCase { - use MultibyteWarningTrait; - /** * @covers Firehed\U2F\fromBase64Web * @dataProvider vectors @@ -33,32 +31,6 @@ public function testToBase64Web($plain, $encoded) ); } - /** - * @covers Firehed\U2F\strlen - * @dataProvider strlenVectors - */ - public function testStrlen(string $string, int $length) - { - $this->assertSame(strlen($string), $length, 'Wrong length returned'); - } - - /** - * @covers Firehed\U2F\substr - * @dataProvider substrVectors - */ - public function testSubstr(string $string, int $start, int $length = null, string $result) - { - $this->assertSame( - $result, - substr($string, $start, $length), - sprintf( - 'Wrong substring returned: got bytes %s instead of %s', - bin2hex(substr($string, $start, $length)), - bin2hex($result) - ) - ); - } - public function vectors(): array { return [ @@ -75,38 +47,4 @@ public function vectors(): array [hex2bin('000fc107e71c'), "AA_BB-cc"], ]; } - - public function strlenVectors(): array - { - $this->skipIfNotMultibyte(); - // Strlen should be un-overloaded to just count bytes - return [ - ['ascii text', 10], - ['texte français', 15], - ['русский текст', 25], - ['日本語テキスト', 21], - ['✨emoji❤️text🐰', 22], - ['🐰', 4], - ]; - } - - public function substrVectors(): array - { - $this->skipIfNotMultibyte(); - return [ - // Substr should be un-overloaded to just work on bytes, resulting - // in multibyte characters potentially getting cut in half - ['ascii text', 5, null, ' text'], - ['ascii text', 0, 5, 'ascii'], - ['texte français', 5, null, ' français'], - ['texte français', 0, 5, 'texte'], - ['русский текст', 5, null, chr(0x81).'ский текст'], - ['русский текст', 0, 5, 'ру'.chr(0xD1)], - ['日本語テキスト', 5, null, chr(0xAC).'語テキスト'], - ['日本語テキスト', 0, 5, '日'.chr(0xE6).chr(0x9C)], - ['✨emoji❤️text🐰', 5, null, 'oji❤️text🐰'], - ['✨emoji❤️text🐰', 0, 5, '✨em'], - ['🐰', 1, 2, chr(0x9F).chr(0x90)], - ]; - } } diff --git a/tests/MultibyteWarningTrait.php b/tests/MultibyteWarningTrait.php deleted file mode 100644 index d9033fc..0000000 --- a/tests/MultibyteWarningTrait.php +++ /dev/null @@ -1,21 +0,0 @@ -markTestSkipped(sprintf( - "mbstring.func_overload cannot be changed at runtime. Re-run ". - "this test with the following command:\n\n%s", - $cmd - )); - } - } -} diff --git a/tests/RegisterRequestTest.php b/tests/RegisterRequestTest.php index 448a6b3..7b03da9 100644 --- a/tests/RegisterRequestTest.php +++ b/tests/RegisterRequestTest.php @@ -24,6 +24,7 @@ public function testJsonSerialize() ->setAppId($appId) ->setChallenge($challenge); $json = json_encode($request); + assert($json !== false); $decoded = json_decode($json, true); $this->assertSame( $appId, diff --git a/tests/RegisterResponseTest.php b/tests/RegisterResponseTest.php index 80cfd25..2137d43 100644 --- a/tests/RegisterResponseTest.php +++ b/tests/RegisterResponseTest.php @@ -45,6 +45,7 @@ public function testFromJson() 'appId' => 'https://u2f.ericstern.com', 'clientData' => $this->validClientData, ]); + assert($json !== false); $response = RegisterResponse::fromJson($json); $this->assertInstanceOf(RegisterResponse::class, $response); } diff --git a/tests/ResponseTraitTest.php b/tests/ResponseTraitTest.php index 1b0da62..f7bd109 100644 --- a/tests/ResponseTraitTest.php +++ b/tests/ResponseTraitTest.php @@ -12,7 +12,7 @@ class ResponseTraitTest extends \PHPUnit\Framework\TestCase { private $trait; - public function setUp() + public function setUp(): void { $this->trait = new class { use ResponseTrait; diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 4a31564..76248fa 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -12,8 +12,6 @@ */ class ServerTest extends \PHPUnit\Framework\TestCase { - use MultibyteWarningTrait; - const APP_ID = 'https://u2f.example.com'; const ENCODED_KEY_HANDLE = @@ -26,13 +24,22 @@ class ServerTest extends \PHPUnit\Framework\TestCase private $server; - public function setUp() + public function setUp(): void { $this->server = (new Server()) ->disableCAVerification() ->setAppId(self::APP_ID); } + /** + * @covers ::__construct + */ + public function testConstruct() + { + $server = new Server(); + $this->assertInstanceOf(Server::class, $server); + } + /** * @covers ::disableCAVerification */ @@ -110,7 +117,7 @@ public function testGenerateSignRequests() ]; $signRequests = $this->server->generateSignRequests($registrations); - $this->assertInternalType('array', $signRequests); + $this->assertIsArray($signRequests); foreach ($signRequests as $signRequest) { $this->assertInstanceOf(SignRequest::class, $signRequest); } @@ -339,14 +346,13 @@ public function testRegisterThrowsWithChangedChallengeParameter() { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: challenge parameter - $json = file_get_contents(__DIR__.'/register_response.json'); - $data = json_decode($json, true); + $data = $this->readJsonFile('register_response.json'); $cli = fromBase64Web($data['clientData']); $obj = json_decode($cli, true); $obj['origin'] = 'https://not.my.u2f.example.com'; - $cli = toBase64Web(json_encode($obj)); + $cli = toBase64Web($this->safeEncode($obj)); $data['clientData'] = $cli; - $response = RegisterResponse::fromJson(json_encode($data)); + $response = RegisterResponse::fromJson($this->safeEncode($data)); $this->expectException(SecurityException::class); $this->expectExceptionCode(SecurityException::SIGNATURE_INVALID); @@ -362,12 +368,11 @@ public function testRegisterThrowsWithChangedKeyHandle() { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: key handle - $json = file_get_contents(__DIR__.'/register_response.json'); - $data = json_decode($json, true); + $data = $this->readJsonFile('register_response.json'); $reg = $data['registrationData']; $reg[70] = chr(ord($reg[70]) + 1); // Change a byte in the key handle $data['registrationData'] = $reg; - $response = RegisterResponse::fromJson(json_encode($data)); + $response = RegisterResponse::fromJson($this->safeEncode($data)); $this->expectException(SecurityException::class); $this->expectExceptionCode(SecurityException::SIGNATURE_INVALID); @@ -383,12 +388,11 @@ public function testRegisterThrowsWithChangedPubkey() { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: public key - $json = file_get_contents(__DIR__.'/register_response.json'); - $data = json_decode($json, true); + $data = $this->readJsonFile('register_response.json'); $reg = $data['registrationData']; $reg[3] = chr(ord($reg[3]) + 1); // Change a byte in the public key $data['registrationData'] = $reg; - $response = RegisterResponse::fromJson(json_encode($data)); + $response = RegisterResponse::fromJson($this->safeEncode($data)); $this->expectException(SecurityException::class); $this->expectExceptionCode(SecurityException::SIGNATURE_INVALID); @@ -404,12 +408,11 @@ public function testRegisterThrowsWithBadSignature() { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: signature - $json = file_get_contents(__DIR__.'/register_response.json'); - $data = json_decode($json, true); + $data = $this->readJsonFile('register_response.json'); $reg = $data['registrationData']; $last = str_rot13(substr($reg, -5)); // rot13 a few chars in signature $data['registrationData'] = substr($reg, 0, -5).$last; - $response = RegisterResponse::fromJson(json_encode($data)); + $response = RegisterResponse::fromJson($this->safeEncode($data)); $this->expectException(SecurityException::class); $this->expectExceptionCode(SecurityException::SIGNATURE_INVALID); @@ -473,18 +476,6 @@ public function testAuthenticate() ); } - /** - * Re-run testAuthenticate after ensuring that mbstring.func_overload is - * being used - * - * @coversNothing - */ - public function testAuthenticateWithMultibyteSettings() - { - $this->skipIfNotMultibyte(); - $this->testAuthenticate(); - } - /** * This tries to authenticate with a used response immediately following * its successful use. @@ -521,10 +512,12 @@ public function testAuthenticateThrowsWithObviousReplayAttack() */ public function testAuthenticateThrowsWhenCounterGoesBackwards() { + $pk = base64_decode(self::ENCODED_PUBLIC_KEY); + assert($pk !== false); // Counter from "DB" bumped, suggesting response was cloned $registration = (new Registration()) ->setKeyHandle(fromBase64Web(self::ENCODED_KEY_HANDLE)) - ->setPublicKey(base64_decode(self::ENCODED_PUBLIC_KEY)) + ->setPublicKey($pk) ->setCounter(82) ; $request = $this->getDefaultSignRequest(); @@ -565,10 +558,12 @@ public function testAuthenticateThrowsWhenChallengeDoesNotMatch() */ public function testAuthenticateThrowsIfNoRegistrationMatchesKeyHandle() { + $pk = base64_decode(self::ENCODED_PUBLIC_KEY); + assert($pk !== false); // Change registration KH $registration = (new Registration()) ->setKeyHandle(fromBase64Web('some-other-key-handle')) - ->setPublicKey(base64_decode(self::ENCODED_PUBLIC_KEY)) + ->setPublicKey($pk) ->setCounter(2) ; $request = $this->getDefaultSignRequest(); @@ -612,12 +607,9 @@ public function testAuthenticateThrowsIfSignatureIsInvalid() $registration = $this->getDefaultRegistration(); $request = $this->getDefaultSignRequest(); // Trimming a byte off the signature to cause a mismatch - $data = json_decode( - file_get_contents(__DIR__.'/sign_response.json'), - true - ); + $data = $this->readJsonFile('sign_response.json'); $data['signatureData'] = substr($data['signatureData'], 0, -1); - $response = SignResponse::fromJson(json_encode($data)); + $response = SignResponse::fromJson($this->safeEncode($data)); $this->expectException(SecurityException::class); $this->expectExceptionCode(SecurityException::SIGNATURE_INVALID); @@ -636,6 +628,11 @@ public function testAuthenticateThrowsIfSignatureIsInvalid() */ public function testAuthenticateThrowsIfRequestIsSignedWithWrongKey() { + $pk = base64_decode( + 'BCXk9bGiuzLRJaX6pFONm+twgIrDkOSNDdXgltt+KhOD'. + '9OxeRv2zYiz7SrVa8eb4LbGR9IDUE7gJySiiuQYWt1w=' + ); + assert($pk !== false); // This was a different key genearated with: // $ openssl ecparam -name prime256v1 -genkey -out private.pem // $ openssl ec -in private.pem -pubout -out public.pem @@ -643,10 +640,7 @@ public function testAuthenticateThrowsIfRequestIsSignedWithWrongKey() // leading bytes are formatting; see ECPublicKeyTrait) $registration = (new Registration()) ->setKeyHandle(fromBase64Web(self::ENCODED_KEY_HANDLE)) - ->setPublicKey(base64_decode( - 'BCXk9bGiuzLRJaX6pFONm+twgIrDkOSNDdXgltt+KhOD'. - '9OxeRv2zYiz7SrVa8eb4LbGR9IDUE7gJySiiuQYWt1w=' - )) + ->setPublicKey($pk) ->setCounter(2) ; $request = $this->getDefaultSignRequest(); @@ -671,9 +665,7 @@ private function getDefaultRegisterRequest(): RegisterRequest private function getDefaultRegisterResponse(): RegisterResponse { - return RegisterResponse::fromJson( - file_get_contents(__DIR__.'/register_response.json') - ); + return RegisterResponse::fromJson($this->safeReadFile('register_response.json')); } private function getDefaultSignRequest(): SignRequest @@ -688,10 +680,12 @@ private function getDefaultSignRequest(): SignRequest private function getDefaultRegistration(): RegistrationInterface { + $pk = base64_decode(self::ENCODED_PUBLIC_KEY); + assert($pk !== false); // From database attached to the authenticating user return (new Registration()) ->setKeyHandle(fromBase64Web(self::ENCODED_KEY_HANDLE)) - ->setPublicKey(base64_decode(self::ENCODED_PUBLIC_KEY)) + ->setPublicKey($pk) ->setCounter(2) ; } @@ -699,8 +693,32 @@ private function getDefaultRegistration(): RegistrationInterface private function getDefaultSignResponse(): SignResponse { // Value from user - return SignResponse::fromJson( - file_get_contents(__DIR__.'/sign_response.json') - ); + return SignResponse::fromJson($this->safeReadFile('sign_response.json')); + } + + private function readJsonFile(string $file): array + { + return $this->safeDecode($this->safeReadFile($file)); + } + + private function safeReadFile(string $file): string + { + $body = file_get_contents(__DIR__.'/'.$file); + assert($body !== false); + return $body; + } + + private function safeDecode(string $json): array + { + $data = json_decode($json, true); + assert($data !== false); + return $data; + } + + private function safeEncode(array $data): string + { + $json = json_encode($data); + assert($json !== false); + return $json; } } diff --git a/tests/SignRequestTest.php b/tests/SignRequestTest.php index 5dabe49..dfcbd49 100644 --- a/tests/SignRequestTest.php +++ b/tests/SignRequestTest.php @@ -25,6 +25,7 @@ public function testJsonSerialize() ->setChallenge($challenge) ->setKeyHandle($keyHandle); $json = json_encode($request); + assert($json !== false); $decoded = json_decode($json, true); $this->assertSame( $appId, diff --git a/tests/SignResponseTest.php b/tests/SignResponseTest.php index 428392b..76cf0c9 100644 --- a/tests/SignResponseTest.php +++ b/tests/SignResponseTest.php @@ -55,12 +55,14 @@ public function testDataAccuracyAfterSuccessfulParsing() $challenge = toBase64Web(random_bytes(32)); $key_handle = toBase64Web(random_bytes(16)); - $client_data = toBase64Web(json_encode([ + $json = json_encode([ "typ" => "navigator.id.getAssertion", "challenge" => $challenge, "origin" => "https://u2f.example.com", "cid_pubkey" => "" - ])); + ]); + assert($json !== false); + $client_data = toBase64Web($json); $json = sprintf( self::JSON_FORMAT, From e83f4dda737d6d70aec772e27ed685d1307da41c Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 29 May 2019 20:20:46 -0700 Subject: [PATCH 08/17] Add interfaces for data structures (#13) --- src/AppIdTrait.php | 8 ++ src/AttestationCertificateTrait.php | 13 --- src/ChallengeProvider.php | 1 - src/ClientData.php | 5 ++ src/LoginResponseInterface.php | 17 ++++ src/RegisterResponse.php | 20 ++++- src/RegistrationResponseInterface.php | 23 +++++ src/ResponseTrait.php | 5 ++ src/SecurityException.php | 2 + src/Server.php | 101 ++++++++++++---------- src/SignResponse.php | 21 ++++- tests/AppIdTraitTest.php | 17 ++++ tests/AttestationCertificateTraitTest.php | 34 -------- tests/ClientDataTest.php | 21 ++++- tests/RegisterResponseTest.php | 49 +++++++++++ tests/ResponseTraitTest.php | 6 ++ tests/ServerTest.php | 19 ++-- tests/SignResponseTest.php | 33 +++++++ 18 files changed, 291 insertions(+), 104 deletions(-) create mode 100644 src/LoginResponseInterface.php create mode 100644 src/RegistrationResponseInterface.php diff --git a/src/AppIdTrait.php b/src/AppIdTrait.php index c54e828..eb10a2d 100644 --- a/src/AppIdTrait.php +++ b/src/AppIdTrait.php @@ -25,4 +25,12 @@ public function getApplicationParameter(): string { return hash('sha256', $this->appId, true); } + + /** + * @return string The raw SHA-256 hash of the Relying Party ID + */ + public function getRpIdHash(): string + { + return hash('sha256', $this->appId, true); + } } diff --git a/src/AttestationCertificateTrait.php b/src/AttestationCertificateTrait.php index 6620773..3acb9b8 100644 --- a/src/AttestationCertificateTrait.php +++ b/src/AttestationCertificateTrait.php @@ -31,17 +31,4 @@ public function setAttestationCertificate(string $cert): self $this->attest = $cert; return $this; } - - public function verifyIssuerAgainstTrustedCAs(array $trusted_cas): bool - { - $result = openssl_x509_checkpurpose( - $this->getAttestationCertificatePem(), - \X509_PURPOSE_ANY, - $trusted_cas - ); - if ($result !== true) { - throw new SecurityException(SecurityException::NO_TRUSTED_CA); - } - return $result; - } } diff --git a/src/ChallengeProvider.php b/src/ChallengeProvider.php index 9aa5681..334b117 100644 --- a/src/ChallengeProvider.php +++ b/src/ChallengeProvider.php @@ -5,6 +5,5 @@ interface ChallengeProvider { - public function getChallenge(): string; } diff --git a/src/ClientData.php b/src/ClientData.php index 73b1ee9..58ea494 100644 --- a/src/ClientData.php +++ b/src/ClientData.php @@ -28,6 +28,11 @@ public static function fromJson(string $json) return $ret; } + public function getApplicationParameter(): string + { + return hash('sha256', $this->origin, true); + } + /** * Checks the 'typ' field against the allowed types in the U2F spec (sec. * 7.1) diff --git a/src/LoginResponseInterface.php b/src/LoginResponseInterface.php new file mode 100644 index 0000000..b092f6d --- /dev/null +++ b/src/LoginResponseInterface.php @@ -0,0 +1,17 @@ +getClientData()->getApplicationParameter(), + $this->getClientData()->getChallengeParameter(), + $this->getKeyHandleBinary(), + $this->getPublicKeyBinary() + ); + } + + public function getRpIdHash(): string + { + return $this->getClientData()->getApplicationParameter(); + } } diff --git a/src/RegistrationResponseInterface.php b/src/RegistrationResponseInterface.php new file mode 100644 index 0000000..c9f20a9 --- /dev/null +++ b/src/RegistrationResponseInterface.php @@ -0,0 +1,23 @@ +clientData; } + public function getChallengeProvider(): ChallengeProvider + { + return $this->clientData; + } + protected function setSignature(string $signature): self { $this->signature = $signature; diff --git a/src/SecurityException.php b/src/SecurityException.php index d31431d..2d2ed73 100644 --- a/src/SecurityException.php +++ b/src/SecurityException.php @@ -12,6 +12,7 @@ class SecurityException extends Exception const CHALLENGE_MISMATCH = 3; const KEY_HANDLE_UNRECOGNIZED = 4; const NO_TRUSTED_CA = 5; + const WRONG_RELYING_PARTY = 6; const MESSAGES = [ self::SIGNATURE_INVALID => 'Signature verification failed', @@ -23,6 +24,7 @@ class SecurityException extends Exception self::CHALLENGE_MISMATCH => 'Response challenge does not match request', self::KEY_HANDLE_UNRECOGNIZED => 'Key handle has not been registered', self::NO_TRUSTED_CA => 'The attestation certificate was not signed by any trusted Certificate Authority', + self::WRONG_RELYING_PARTY => 'Relying party invalid for this server', ]; public function __construct(int $code) diff --git a/src/Server.php b/src/Server.php index 92d0f02..c4b7975 100644 --- a/src/Server.php +++ b/src/Server.php @@ -15,8 +15,8 @@ class Server /** * Holds a list of paths to PEM-formatted CA certificates. Unless * verification has been explicitly disabled with `disableCAVerification()`, - * the Attestation Certificate in the `RegisterResponse` will be validated - * against the provided CAs. + * the Attestation Certificate in the `RegistrationResponseInterface` will + * be validated against the provided CAs. * * This means that you *must* either a) provide a list of trusted * certificates, or b) explicitly disable verifiation. By default, it will @@ -61,19 +61,19 @@ public function __construct() // @codeCoverageIgnoreEnd } /** - * This method authenticates a `SignResponse` against outstanding + * This method authenticates a `LoginResponseInterface` against outstanding * registrations and their corresponding `SignRequest`s. If the response's * signature validates and the counter hasn't done anything strange, the * registration will be returned with an updated counter value, which *must* * be persisted for the next authentication. If any verification component * fails, a `SE` will be thrown. * - * @param SignResponse $response the parsed response from the user + * @param LoginResponseInterface $response the parsed response from the user * @return RegistrationInterface if authentication succeeds * @throws SE if authentication fails * @throws BadMethodCallException if a precondition is not met */ - public function authenticate(SignResponse $response): RegistrationInterface + public function authenticate(LoginResponseInterface $response): RegistrationInterface { if (!$this->registrations) { throw new BadMethodCallException( @@ -117,28 +117,15 @@ public function authenticate(SignResponse $response): RegistrationInterface // match the one in the signing request, the client signed the // wrong thing. This could possibly be an attempt at a replay // attack. - $this->validateChallenge($response->getClientData(), $request); + $this->validateChallenge($response->getChallengeProvider(), $request); $pem = $registration->getPublicKeyPem(); - // U2F Spec: - // https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#authentication-response-message-success - $to_verify = sprintf( - '%s%s%s%s', - $request->getApplicationParameter(), - chr($response->getUserPresenceByte()), - pack('N', $response->getCounter()), - // Note: Spec says this should be from the request, but that's not - // actually available via the JS API. Because we assert the - // challenge *value* from the Client Data matches the trusted one - // from the SignRequest and that value is included in the Challenge - // Parameter, this is safe unless/until SHA-256 is broken. - $response->getClientData()->getChallengeParameter() - ); + $toVerify = $response->getSignedData(); // Signature must validate against $sig_check = openssl_verify( - $to_verify, + $toVerify, $response->getSignature(), $pem, \OPENSSL_ALGO_SHA256 @@ -189,17 +176,17 @@ public function authenticate(SignResponse $response): RegistrationInterface } /** - * This method authenticates a RegisterResponse against its corresponding - * RegisterRequest by verifying the certificate and signature. If valid, it - * returns a registration; if not, a SE will be thrown and attempt to - * register the key must be aborted. + * This method authenticates a RegistrationResponseInterface against its + * corresponding RegisterRequest by verifying the certificate and signature. + * If valid, it returns a registration; if not, a SE will be thrown and + * attempt to register the key must be aborted. * - * @param RegisterResponse $resp The response to verify + * @param RegistrationResponseInterface $response The response to verify * @return RegistrationInterface if the response is proven authentic * @throws SE if the response cannot be proven authentic * @throws BadMethodCallException if a precondition is not met */ - public function register(RegisterResponse $resp): RegistrationInterface + public function register(RegistrationResponseInterface $response): RegistrationInterface { if (!$this->registerRequest) { throw new BadMethodCallException( @@ -207,28 +194,19 @@ public function register(RegisterResponse $resp): RegistrationInterface 'with setRegisterRequest()' ); } - $this->validateChallenge($resp->getClientData(), $this->registerRequest); - // Check the Application Parameter? - - // https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#registration-response-message-success - $signed_data = sprintf( - '%s%s%s%s%s', - chr(0), - $this->registerRequest->getApplicationParameter(), - $resp->getClientData()->getChallengeParameter(), - $resp->getKeyHandleBinary(), - $resp->getPublicKeyBinary() - ); + $this->validateChallenge($response->getChallengeProvider(), $this->registerRequest); + // Check the Application Parameter + $this->validateRelyingParty($response->getRpIdHash()); - $pem = $resp->getAttestationCertificatePem(); if ($this->verifyCA) { - $resp->verifyIssuerAgainstTrustedCAs($this->trustedCAs); + $this->verifyAttestationCertAgainstTrustedCAs($response); } // Signature must validate against device issuer's public key + $pem = $response->getAttestationCertificatePem(); $sig_check = openssl_verify( - $signed_data, - $resp->getSignature(), + $response->getSignedData(), + $response->getSignature(), $pem, \OPENSSL_ALGO_SHA256 ); @@ -237,10 +215,10 @@ public function register(RegisterResponse $resp): RegistrationInterface } return (new Registration()) - ->setAttestationCertificate($resp->getAttestationCertificateBinary()) + ->setAttestationCertificate($response->getAttestationCertificateBinary()) ->setCounter(0) // The response does not include this - ->setKeyHandle($resp->getKeyHandleBinary()) - ->setPublicKey($resp->getPublicKeyBinary()); + ->setKeyHandle($response->getKeyHandleBinary()) + ->setPublicKey($response->getPublicKeyBinary()); } /** @@ -394,6 +372,15 @@ private function generateChallenge(): string return toBase64Web(\random_bytes(16)); } + private function validateRelyingParty(string $rpIdHash): void + { + // Note: this is a bit delicate at the moment, since different + // protocols have different rules around the handling of Relying Party + // verification. Expect this to be revised. + if (!hash_equals($this->getRpIdHash(), $rpIdHash)) { + throw new SE(SE::WRONG_RELYING_PARTY); + } + } /** * Compares the Challenge value from a known source against the * user-provided value. A mismatch will throw a SE. Future @@ -418,4 +405,26 @@ private function validateChallenge( // TOOD: generate and compare timestamps return true; } + + /** + * Asserts that the attestation cert provided by the registration is issued + * by the set of trusted CAs. + * + * @param RegistrationResponseInterface $response The response to validate + * @throws SecurityException upon failure + * @return void + */ + private function verifyAttestationCertAgainstTrustedCAs(RegistrationResponseInterface $response): void + { + $pem = $response->getAttestationCertificatePem(); + + $result = openssl_x509_checkpurpose( + $pem, + \X509_PURPOSE_ANY, + $this->trustedCAs + ); + if ($result !== true) { + throw new SE(SE::NO_TRUSTED_CA); + } + } } diff --git a/src/SignResponse.php b/src/SignResponse.php index 6a63f56..f642332 100644 --- a/src/SignResponse.php +++ b/src/SignResponse.php @@ -5,7 +5,7 @@ use Firehed\U2F\InvalidDataException as IDE; -class SignResponse +class SignResponse implements LoginResponseInterface { use ResponseTrait; @@ -17,11 +17,30 @@ public function getCounter(): int { return $this->counter; } + public function getUserPresenceByte(): int { return $this->user_presence; } + public function getSignedData(): string + { + // U2F Spec: + // https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#authentication-response-message-success + return sprintf( + '%s%s%s%s', + $this->getClientData()->getApplicationParameter(), + chr($this->getUserPresenceByte()), + pack('N', $this->getCounter()), + // Note: Spec says this should be from the request, but that's not + // actually available via the JS API. Because we assert the + // challenge *value* from the Client Data matches the trusted one + // from the SignRequest and that value is included in the Challenge + // Parameter, this is safe unless/until SHA-256 is broken. + $this->getClientData()->getChallengeParameter() + ); + } + protected function parseResponse(array $response): self { $this->validateKeyInArray('keyHandle', $response); diff --git a/tests/AppIdTraitTest.php b/tests/AppIdTraitTest.php index e6c85fe..1f5a710 100644 --- a/tests/AppIdTraitTest.php +++ b/tests/AppIdTraitTest.php @@ -50,4 +50,21 @@ public function testGetApplicationParameter() 'getApplicationParamter should return the raw SHA256 hash of the application id' ); } + + /** + * @covers ::getRpIdHash + */ + public function testGetRpIdHash() + { + $obj = new class { + use AppIdTrait; + }; + $appId = 'https://u2f.example.com'; + $obj->setAppId($appId); + $this->assertSame( + hash('sha256', $appId, true), + $obj->getRpIdHash(), + 'getRpIdHash should return the raw SHA256 hash of the application id' + ); + } } diff --git a/tests/AttestationCertificateTraitTest.php b/tests/AttestationCertificateTraitTest.php index 15f4d89..63860b2 100644 --- a/tests/AttestationCertificateTraitTest.php +++ b/tests/AttestationCertificateTraitTest.php @@ -52,40 +52,6 @@ public function testGetAttestationCertificatePem() ); } - /** - * @covers ::verifyIssuerAgainstTrustedCAs - */ - public function testSuccessfulCAVerification() - { - $class = $this->getObjectWithYubicoCert(); - $certs = [dirname(__DIR__).'/CAcerts/yubico.pem']; - $this->assertTrue($class->verifyIssuerAgainstTrustedCAs($certs)); - } - - /** - * @covers ::verifyIssuerAgainstTrustedCAs - */ - public function testFailedCAVerification() - { - $class = $this->getObjectWithYubicoCert(); - $certs = [__DIR__.'/verisign_only_for_unit_tests.pem']; - $this->expectException(SecurityException::class); - $this->expectExceptionCode(SecurityException::NO_TRUSTED_CA); - $class->verifyIssuerAgainstTrustedCAs($certs); - } - - /** - * @covers ::verifyIssuerAgainstTrustedCAs - */ - public function testFailedCAVerificationFromNoCAs() - { - $class = $this->getObjectWithYubicoCert(); - $certs = []; - $this->expectException(SecurityException::class); - $this->expectExceptionCode(SecurityException::NO_TRUSTED_CA); - $class->verifyIssuerAgainstTrustedCAs($certs); - } - // -(Helper methods)------------------------------------------------------- /** diff --git a/tests/ClientDataTest.php b/tests/ClientDataTest.php index cbf2f89..b369220 100644 --- a/tests/ClientDataTest.php +++ b/tests/ClientDataTest.php @@ -10,7 +10,6 @@ */ class ClientDataTest extends \PHPUnit\Framework\TestCase { - /** * @covers ::fromJson */ @@ -58,6 +57,26 @@ public function testGetChallengeParameter() ); } + /** + * @covers ::getApplicationParameter + */ + public function testGetApplicationParameter() + { + $goodData = [ + 'typ' => 'navigator.id.finishEnrollment', + 'challenge' => 'PfsWR1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo', + 'origin' => 'https://u2f.ericstern.com', + 'cid_pubkey' => '', + ]; + $goodJson = json_encode($goodData); + assert($goodJson !== false); + $clientData = ClientData::fromJson($goodJson); + $this->assertSame( + hash('sha256', 'https://u2f.ericstern.com', true), + $clientData->getApplicationParameter() + ); + } + /** * @covers ::fromJson */ diff --git a/tests/RegisterResponseTest.php b/tests/RegisterResponseTest.php index 2137d43..77f3137 100644 --- a/tests/RegisterResponseTest.php +++ b/tests/RegisterResponseTest.php @@ -138,6 +138,55 @@ public function testDataAccuracyAfterSuccessfulParsing() ); } + /** + * @covers ::getSignedData + */ + public function testGetSignedData() + { + $json = file_get_contents(__DIR__ . '/register_response.json'); + assert($json !== false); + $response = RegisterResponse::fromJson($json); + + $expectedSignedData = sprintf( + '%s%s%s%s%s', + "\x00", + hash('sha256', 'https://u2f.ericstern.com', true), + hash( + 'sha256', + '{'. + '"typ":"navigator.id.finishEnrollment",'. + '"challenge":"PfsWR1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo",'. + '"origin":"https://u2f.ericstern.com",'. + '"cid_pubkey":""'. + '}', + true + ), + $response->getKeyHandleBinary(), + $response->getPublicKeyBinary() + ); + + $this->assertSame( + $expectedSignedData, + $response->getSignedData(), + 'Wrong signed data' + ); + } + + /** + * @covers ::getRpIdHash + */ + public function testGetRpIdHash() + { + $json = file_get_contents(__DIR__ . '/register_response.json'); + assert($json !== false); + $response = RegisterResponse::fromJson($json); + + $this->assertSame( + hash('sha256', 'https://u2f.ericstern.com', true), + $response->getRpIdHash() + ); + } + // -( DataProviders )------------------------------------------------------ public function clientErrors() diff --git a/tests/ResponseTraitTest.php b/tests/ResponseTraitTest.php index f7bd109..fd696d7 100644 --- a/tests/ResponseTraitTest.php +++ b/tests/ResponseTraitTest.php @@ -27,6 +27,7 @@ protected function parseResponse(array $response): self /** * @covers ::fromJson * @covers ::getSignature + * @covers ::getChallengeProvider * @covers ::getClientData */ public function testValidJson() @@ -55,6 +56,11 @@ public function testValidJson() $response->getClientData(), 'ClientData was not parsed correctly' ); + $this->assertInstanceOf( + ChallengeProvider::class, + $response->getChallengeProvider(), + 'Did not get a challenge provider' + ); $this->assertSame( __METHOD__, diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 76248fa..b81dbc6 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -12,7 +12,7 @@ */ class ServerTest extends \PHPUnit\Framework\TestCase { - const APP_ID = 'https://u2f.example.com'; + const APP_ID = 'https://u2f.ericstern.com'; const ENCODED_KEY_HANDLE = 'JUnVTStPn-V2-bCu0RlvPbukBpHTD5Mi1ZGglDOcN0vD45rnTD0BXdkRt78huTwJ7tVax'. @@ -326,14 +326,19 @@ public function testRegisterWorksWithCAList() */ public function testRegisterThrowsWithChangedApplicationParameter() { - $request = (new RegisterRequest()) - ->setAppId('https://not.my.u2f.example.com') - ->setChallenge('PfsWR1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo'); + $request = $this->getDefaultRegisterRequest(); - $response = $this->getDefaultRegisterResponse(); + $challengeProvider = $this->createMock(ChallengeProvider::class); + $challengeProvider->method('getChallenge') + ->willReturn($request->getChallenge()); + $response = $this->createMock(RegistrationResponseInterface::class); + $response->method('getChallengeProvider') + ->willReturn($challengeProvider); + $response->method('getRpIdHash') + ->willReturn(hash('sha256', 'https://some.otherdomain.com', true)); $this->expectException(SecurityException::class); - $this->expectExceptionCode(SecurityException::SIGNATURE_INVALID); + $this->expectExceptionCode(SecurityException::WRONG_RELYING_PARTY); $this->server ->setRegisterRequest($request) ->register($response); @@ -349,7 +354,7 @@ public function testRegisterThrowsWithChangedChallengeParameter() $data = $this->readJsonFile('register_response.json'); $cli = fromBase64Web($data['clientData']); $obj = json_decode($cli, true); - $obj['origin'] = 'https://not.my.u2f.example.com'; + $obj['cid_pubkey'] = 'nonsense'; $cli = toBase64Web($this->safeEncode($obj)); $data['clientData'] = $cli; $response = RegisterResponse::fromJson($this->safeEncode($data)); diff --git a/tests/SignResponseTest.php b/tests/SignResponseTest.php index 76cf0c9..1d3e7c3 100644 --- a/tests/SignResponseTest.php +++ b/tests/SignResponseTest.php @@ -180,6 +180,39 @@ public function testFromJsonWithInvalidSignatureData() SignResponse::fromJson($json); } + /** + * @covers ::getSignedData + */ + public function testGetSignedData() + { + $json = file_get_contents(__DIR__ . '/sign_response.json'); + assert($json !== false); + $response = SignResponse::fromJson($json); + + $expectedSignedData = sprintf( + '%s%s%s%s', + hash('sha256', 'https://u2f.ericstern.com', true), + "\x01", // user presence + "\x00\x00\x00\x2d", // counter (int(45)) + hash( + 'sha256', + '{'. + '"typ":"navigator.id.getAssertion",'. + '"challenge":"wt2ze8IskcTO3nIsO2D2hFjE5tVD041NpnYesLpJweg",'. + '"origin":"https://u2f.ericstern.com",'. + '"cid_pubkey":""'. + '}', + true + ) + ); + + $this->assertSame( + $expectedSignedData, + $response->getSignedData(), + 'Wrong signed data' + ); + } + /** * @dataProvider clientErrors */ From 6c4fe033ef80adcc4e72ac4f6cb05fa87f7ed0a8 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 29 May 2019 21:21:22 -0700 Subject: [PATCH 09/17] Hide ClientData from public formats (#15) --- src/RegisterResponse.php | 6 +++--- src/ResponseTrait.php | 8 ++------ src/SignResponse.php | 4 ++-- tests/ResponseTraitTest.php | 6 ------ 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/RegisterResponse.php b/src/RegisterResponse.php index df4f891..d3cb00b 100644 --- a/src/RegisterResponse.php +++ b/src/RegisterResponse.php @@ -115,8 +115,8 @@ public function getSignedData(): string return sprintf( '%s%s%s%s%s', chr(0), - $this->getClientData()->getApplicationParameter(), - $this->getClientData()->getChallengeParameter(), + $this->clientData->getApplicationParameter(), + $this->clientData->getChallengeParameter(), $this->getKeyHandleBinary(), $this->getPublicKeyBinary() ); @@ -124,6 +124,6 @@ public function getSignedData(): string public function getRpIdHash(): string { - return $this->getClientData()->getApplicationParameter(); + return $this->clientData->getApplicationParameter(); } } diff --git a/src/ResponseTrait.php b/src/ResponseTrait.php index a2296d5..e82eb23 100644 --- a/src/ResponseTrait.php +++ b/src/ResponseTrait.php @@ -9,7 +9,8 @@ trait ResponseTrait { use KeyHandleTrait; - private $clientData; + /** @var ClientData */ + protected $clientData; /** @var string (binary) */ private $signature = ''; @@ -19,11 +20,6 @@ public function getSignature(): string return $this->signature; } - public function getClientData(): ClientData - { - return $this->clientData; - } - public function getChallengeProvider(): ChallengeProvider { return $this->clientData; diff --git a/src/SignResponse.php b/src/SignResponse.php index f642332..76b14d3 100644 --- a/src/SignResponse.php +++ b/src/SignResponse.php @@ -29,7 +29,7 @@ public function getSignedData(): string // https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#authentication-response-message-success return sprintf( '%s%s%s%s', - $this->getClientData()->getApplicationParameter(), + $this->clientData->getApplicationParameter(), chr($this->getUserPresenceByte()), pack('N', $this->getCounter()), // Note: Spec says this should be from the request, but that's not @@ -37,7 +37,7 @@ public function getSignedData(): string // challenge *value* from the Client Data matches the trusted one // from the SignRequest and that value is included in the Challenge // Parameter, this is safe unless/until SHA-256 is broken. - $this->getClientData()->getChallengeParameter() + $this->clientData->getChallengeParameter() ); } diff --git a/tests/ResponseTraitTest.php b/tests/ResponseTraitTest.php index fd696d7..bbf766d 100644 --- a/tests/ResponseTraitTest.php +++ b/tests/ResponseTraitTest.php @@ -28,7 +28,6 @@ protected function parseResponse(array $response): self * @covers ::fromJson * @covers ::getSignature * @covers ::getChallengeProvider - * @covers ::getClientData */ public function testValidJson() { @@ -51,11 +50,6 @@ public function testValidJson() 'Parsed response was the wrong type' ); - $this->assertInstanceOf( - ClientData::class, - $response->getClientData(), - 'ClientData was not parsed correctly' - ); $this->assertInstanceOf( ChallengeProvider::class, $response->getChallengeProvider(), From e6c340fc5699de34a3cf967a4818e9c2e530ace6 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 29 May 2019 23:12:54 -0700 Subject: [PATCH 10/17] Convert certs and PKs to data structures (#16) --- composer.json | 3 +- phpstan.neon | 2 + src/AttestationCertificate.php | 34 ++++++ src/AttestationCertificateInterface.php | 11 ++ src/AttestationCertificateTrait.php | 34 ------ src/{ECPublicKeyTrait.php => ECPublicKey.php} | 57 +++++----- src/PublicKeyInterface.php | 11 ++ src/RegisterResponse.php | 25 ++++- src/Registration.php | 32 +++++- src/RegistrationInterface.php | 9 +- src/RegistrationResponseInterface.php | 6 +- src/Server.php | 15 +-- tests/AttestationCertificateTest.php | 67 +++++++++++ tests/AttestationCertificateTraitTest.php | 74 ------------- tests/ECPublicKeyTest.php | 104 ++++++++++++++++++ tests/ECPublicKeyTraitTest.php | 102 ----------------- tests/RegisterResponseTest.php | 8 +- tests/RegistrationTest.php | 24 ++++ tests/ServerTest.php | 56 +++++++--- 19 files changed, 391 insertions(+), 283 deletions(-) create mode 100644 src/AttestationCertificate.php create mode 100644 src/AttestationCertificateInterface.php delete mode 100644 src/AttestationCertificateTrait.php rename src/{ECPublicKeyTrait.php => ECPublicKey.php} (61%) create mode 100644 src/PublicKeyInterface.php create mode 100644 tests/AttestationCertificateTest.php delete mode 100644 tests/AttestationCertificateTraitTest.php create mode 100644 tests/ECPublicKeyTest.php delete mode 100644 tests/ECPublicKeyTraitTest.php diff --git a/composer.json b/composer.json index 4c6a3f7..d8b2e71 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "phpstan/phpstan": "^0.11", "phpunit/phpunit": "^8.0", "squizlabs/php_codesniffer": "^3.2", - "spatie/phpunit-watcher": "^1.8" + "spatie/phpunit-watcher": "^1.8", + "phpstan/phpstan-phpunit": "^0.11.2" }, "autoload": { "psr-4": { diff --git a/phpstan.neon b/phpstan.neon index 82b5f09..626379f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,3 +1,5 @@ +includes: + - vendor/phpstan/phpstan-phpunit/extension.neon parameters: excludes_analyse: - %rootDir%/../../../vendor diff --git a/src/AttestationCertificate.php b/src/AttestationCertificate.php new file mode 100644 index 0000000..42d5e2e --- /dev/null +++ b/src/AttestationCertificate.php @@ -0,0 +1,34 @@ +binary = $binary; + } + + public function getBinary(): string + { + return $this->binary; + } + + public function getPemFormatted(): string + { + $data = base64_encode($this->binary); + $pem = "-----BEGIN CERTIFICATE-----\r\n"; + $pem .= chunk_split($data, 64); + $pem .= "-----END CERTIFICATE-----"; + return $pem; + } + + public function __debugInfo(): array + { + return ['binary' => '0x' . bin2hex($this->binary)]; + } +} diff --git a/src/AttestationCertificateInterface.php b/src/AttestationCertificateInterface.php new file mode 100644 index 0000000..38f3f18 --- /dev/null +++ b/src/AttestationCertificateInterface.php @@ -0,0 +1,11 @@ +attest; - } - - // PEM formatted cert - public function getAttestationCertificatePem(): string - { - $data = base64_encode($this->getAttestationCertificateBinary()); - $pem = "-----BEGIN CERTIFICATE-----\r\n"; - $pem .= chunk_split($data, 64); - $pem .= "-----END CERTIFICATE-----"; - return $pem; - } - - public function setAttestationCertificate(string $cert): self - { - // In the future, this may make assertions about the cert formatting; - // right now, we're going to leave it be. - $this->attest = $cert; - return $this; - } -} diff --git a/src/ECPublicKeyTrait.php b/src/ECPublicKey.php similarity index 61% rename from src/ECPublicKeyTrait.php rename to src/ECPublicKey.php index 101fc5a..bb7eaf4 100644 --- a/src/ECPublicKeyTrait.php +++ b/src/ECPublicKey.php @@ -3,38 +3,41 @@ namespace Firehed\U2F; -use Firehed\U2F\InvalidDataException as IDE; - -trait ECPublicKeyTrait +class ECPublicKey implements PublicKeyInterface { /** @var string (binary) */ - private $pubKey = ''; + private $binary = ''; - /** - * @deprecated Methods that return binary values are suffixed with "binary". - * @return string Binary string of public key - */ - public function getPublicKey(): string + public function __construct(string $key) { - trigger_error('Please use getPublicKeyBinary().', E_USER_DEPRECATED); - - return $this->getPublicKeyBinary(); + // RFC5480 2.2 - must be uncompressed value + if ($key[0] !== "\x04") { + throw new InvalidDataException( + InvalidDataException::MALFORMED_DATA, + 'public key: first byte not x04 (uncompressed)' + ); + } + if (strlen($key) !== 65) { + throw new InvalidDataException( + InvalidDataException::PUBLIC_KEY_LENGTH, + '65' + ); + } + $this->binary = $key; } /** * @return string The decoded public key. */ - public function getPublicKeyBinary(): string + public function getBinary(): string { - return $this->pubKey; + return $this->binary; } // Prepends the pubkey format headers and builds a pem file from the raw // public key component - public function getPublicKeyPem(): string + public function getPemFormatted(): string { - $key = $this->getPublicKeyBinary(); - // Described in RFC 5480 // Just use an OID calculator to figure out *that* encoding $der = hex2bin( @@ -47,27 +50,19 @@ public function getPublicKeyPem(): string .'0342' // BIT STRING, length 66 .'00' // prepend with NUL - pubkey will follow ); + $der .= $this->binary; - $der .= $key; $pem = "-----BEGIN PUBLIC KEY-----\r\n"; $pem .= chunk_split(base64_encode($der), 64); $pem .= "-----END PUBLIC KEY-----"; return $pem; } - public function setPublicKey(string $key): self + public function __debugInfo(): array { - // RFC5480 2.2 - must be uncompressed value - if ($key[0] !== "\x04") { - throw new IDE( - IDE::MALFORMED_DATA, - 'public key: first byte not x04 (uncompressed)' - ); - } - if (strlen($key) !== 65) { - throw new IDE(IDE::PUBLIC_KEY_LENGTH, '65'); - } - $this->pubKey = $key; - return $this; + return [ + 'x' => '0x' . bin2hex(substr($this->binary, 1, 32)), + 'y' => '0x' . bin2hex(substr($this->binary, 33)), + ]; } } diff --git a/src/PublicKeyInterface.php b/src/PublicKeyInterface.php new file mode 100644 index 0000000..0e5998d --- /dev/null +++ b/src/PublicKeyInterface.php @@ -0,0 +1,11 @@ +validateKeyInArray('registrationData', $response); @@ -38,7 +42,7 @@ protected function parseResponse(array $response): self } $offset += 1; - $this->setPublicKey(substr($regData, $offset, 65)); + $this->pubKey = new ECPublicKey(substr($regData, $offset, 65)); $offset += 65; $keyHandleLength = ord($regData[$offset]); @@ -100,7 +104,8 @@ protected function parseResponse(array $response): self 'certificate and sigature length' ); } - $this->setAttestationCertificate(substr($regData, $offset, $length)); + $cert = new AttestationCertificate(substr($regData, $offset, $length)); + $this->cert = $cert; $offset += $length; // All remaining data is the signature @@ -118,7 +123,7 @@ public function getSignedData(): string $this->clientData->getApplicationParameter(), $this->clientData->getChallengeParameter(), $this->getKeyHandleBinary(), - $this->getPublicKeyBinary() + $this->pubKey->getBinary() ); } @@ -126,4 +131,14 @@ public function getRpIdHash(): string { return $this->clientData->getApplicationParameter(); } + + public function getAttestationCertificate(): AttestationCertificate + { + return $this->cert; + } + + public function getPublicKey(): PublicKeyInterface + { + return $this->pubKey; + } } diff --git a/src/Registration.php b/src/Registration.php index 05c3d41..b39a81e 100644 --- a/src/Registration.php +++ b/src/Registration.php @@ -7,16 +7,33 @@ class Registration implements RegistrationInterface { - use AttestationCertificateTrait; - use ECPublicKeyTrait; use KeyHandleTrait; + /** @var AttestationCertificateInterface */ + private $cert; + + /** @var int */ private $counter = -1; + /** @var PublicKeyInterface */ + private $pubKey; + + public function getAttestationCertificate(): AttestationCertificateInterface + { + return $this->cert; + } + public function getCounter(): int { return $this->counter; } + + public function setAttestationCertificate(AttestationCertificateInterface $cert): self + { + $this->cert = $cert; + return $this; + } + public function setCounter(int $counter): self { if ($counter < 0) { @@ -25,4 +42,15 @@ public function setCounter(int $counter): self $this->counter = $counter; return $this; } + + public function getPublicKey(): PublicKeyInterface + { + return $this->pubKey; + } + + public function setPublicKey(PublicKeyInterface $pubKey): self + { + $this->pubKey = $pubKey; + return $this; + } } diff --git a/src/RegistrationInterface.php b/src/RegistrationInterface.php index c440b56..fc87ce6 100644 --- a/src/RegistrationInterface.php +++ b/src/RegistrationInterface.php @@ -10,9 +10,9 @@ interface RegistrationInterface { /** - * @return string The decoded attestation of the U2F token. + * @return AttestationCertificateInterface The decoded attestation of the U2F token. */ - public function getAttestationCertificateBinary(): string; + public function getAttestationCertificate(): AttestationCertificateInterface; /** * @return int The counter of the U2F registration. @@ -25,8 +25,7 @@ public function getCounter(): int; public function getKeyHandleBinary(): string; /** - * @return string The decoded public key of the U2F - * registration. + * @return PublicKeyInterface The public key of the registration. */ - public function getPublicKeyBinary(): string; + public function getPublicKey(): PublicKeyInterface; } diff --git a/src/RegistrationResponseInterface.php b/src/RegistrationResponseInterface.php index c9f20a9..fce1181 100644 --- a/src/RegistrationResponseInterface.php +++ b/src/RegistrationResponseInterface.php @@ -5,15 +5,13 @@ interface RegistrationResponseInterface { - public function getAttestationCertificateBinary(): string; - - public function getAttestationCertificatePem(): string; + public function getAttestationCertificate(): AttestationCertificate; public function getChallengeProvider(): ChallengeProvider; public function getKeyHandleBinary(): string; - public function getPublicKeyBinary(): string; + public function getPublicKey(): PublicKeyInterface; public function getRpIdHash(): string; diff --git a/src/Server.php b/src/Server.php index c4b7975..eeea89e 100644 --- a/src/Server.php +++ b/src/Server.php @@ -89,6 +89,7 @@ public function authenticate(LoginResponseInterface $response): RegistrationInte } // Search for the registration to use based on the Key Handle + /** @var ?Registration */ $registration = $this->findObjectWithKeyHandle( $this->registrations, $response->getKeyHandleBinary() @@ -119,7 +120,7 @@ public function authenticate(LoginResponseInterface $response): RegistrationInte // attack. $this->validateChallenge($response->getChallengeProvider(), $request); - $pem = $registration->getPublicKeyPem(); + $pem = $registration->getPublicKey()->getPemFormatted(); $toVerify = $response->getSignedData(); @@ -169,9 +170,9 @@ public function authenticate(LoginResponseInterface $response): RegistrationInte // again. There's no perfect way to handle this since return (new Registration()) - ->setAttestationCertificate($registration->getAttestationCertificateBinary()) + ->setAttestationCertificate($registration->getAttestationCertificate()) ->setKeyHandle($registration->getKeyHandleBinary()) - ->setPublicKey($registration->getPublicKeyBinary()) + ->setPublicKey($registration->getPublicKey()) ->setCounter($response->getCounter()); } @@ -203,7 +204,7 @@ public function register(RegistrationResponseInterface $response): RegistrationI } // Signature must validate against device issuer's public key - $pem = $response->getAttestationCertificatePem(); + $pem = $response->getAttestationCertificate()->getPemFormatted(); $sig_check = openssl_verify( $response->getSignedData(), $response->getSignature(), @@ -215,10 +216,10 @@ public function register(RegistrationResponseInterface $response): RegistrationI } return (new Registration()) - ->setAttestationCertificate($response->getAttestationCertificateBinary()) + ->setAttestationCertificate($response->getAttestationCertificate()) ->setCounter(0) // The response does not include this ->setKeyHandle($response->getKeyHandleBinary()) - ->setPublicKey($response->getPublicKeyBinary()); + ->setPublicKey($response->getPublicKey()); } /** @@ -416,7 +417,7 @@ private function validateChallenge( */ private function verifyAttestationCertAgainstTrustedCAs(RegistrationResponseInterface $response): void { - $pem = $response->getAttestationCertificatePem(); + $pem = $response->getAttestationCertificate()->getPemFormatted(); $result = openssl_x509_checkpurpose( $pem, diff --git a/tests/AttestationCertificateTest.php b/tests/AttestationCertificateTest.php new file mode 100644 index 0000000..8565c30 --- /dev/null +++ b/tests/AttestationCertificateTest.php @@ -0,0 +1,67 @@ + + * @covers :: + */ +class AttestationCertificateTest extends \PHPUnit\Framework\TestCase +{ + /** + * @covers ::__construct + */ + public function testConstruct() + { + // Note: a future, stricter implementation which actually parses and + // examines the ASN.1 format should fail on this. For now it's just + // a simple bag of bytes. + $raw = random_bytes(128); + $cert = new AttestationCertificate($raw); + $this->assertInstanceOf(AttestationCertificateInterface::class, $cert); + } + + /** + * @covers ::getBinary + */ + public function testGetBinary() + { + $raw = random_bytes(128); + $cert = new AttestationCertificate($raw); + $this->assertSame( + $raw, + $cert->getBinary(), + 'getBinary should return the untouched raw data' + ); + } + + /** + * @covers ::getPemFormatted + */ + public function testGetPemFormatted() + { + $raw = random_bytes(128); + $cert = new AttestationCertificate($raw); + $expected = "-----BEGIN CERTIFICATE-----\r\n"; + $expected .= chunk_split(base64_encode($raw), 64); + $expected .= "-----END CERTIFICATE-----"; + $this->assertSame( + $expected, + $cert->getPemFormatted(), + 'PEM-formatted certificate was incorrect' + ); + } + + /** + * @covers ::__debugInfo + */ + public function testDebugInfoEncodesBinary() + { + $cert = new AttestationCertificate(random_bytes(128)); + $debugInfo = $cert->__debugInfo(); + $this->assertArrayHasKey('binary', $debugInfo); + $this->assertRegExp('/^0x[0-9a-f]{256}$/', $debugInfo['binary']); + } +} diff --git a/tests/AttestationCertificateTraitTest.php b/tests/AttestationCertificateTraitTest.php deleted file mode 100644 index 63860b2..0000000 --- a/tests/AttestationCertificateTraitTest.php +++ /dev/null @@ -1,74 +0,0 @@ - - * @covers :: - */ -class AttestationCertificateTraitTest extends \PHPUnit\Framework\TestCase -{ - /** - * @covers ::getAttestationCertificateBinary - * @covers ::setAttestationCertificate - */ - public function testAccessors() - { - $obj = new class { - use AttestationCertificateTrait; - }; - $cert = bin2hex(random_bytes(35)); - $this->assertSame( - $obj, - $obj->setAttestationCertificate($cert), - 'setAttestationCertificate should return $this' - ); - $this->assertSame( - $cert, - $obj->getAttestationCertificateBinary(), - 'getAttestationCertificate should return the challenge that was set' - ); - } - - /** - * @covers ::getAttestationCertificatePem - */ - public function testGetAttestationCertificatePem() - { - $obj = new class { - use AttestationCertificateTrait; - }; - $raw = random_bytes(128); - $expected = "-----BEGIN CERTIFICATE-----\r\n"; - $expected .= chunk_split(base64_encode($raw), 64); - $expected .= "-----END CERTIFICATE-----"; - $obj->setAttestationCertificate($raw); - $this->assertSame( - $expected, - $obj->getAttestationCertificatePem(), - 'PEM-formatted certificate was incorrect' - ); - } - - // -(Helper methods)------------------------------------------------------- - - /** - * Returns some concrete implementation of a class using - * AttestationCertificateTrait - * - * @return mixed Some class using AttestationCertificateTrait - */ - private function getObjectWithYubicoCert() - { - $data = file_get_contents(__DIR__.'/register_response.json'); - assert($data !== false); - $response = RegisterResponse::fromJson($data); - // Sanity check that the response actually imlements this trait, rather - // than doing all sorts of magic - $check = AttestationCertificateTrait::class; - $this->assertContains($check, class_uses($response)); - return $response; - } -} diff --git a/tests/ECPublicKeyTest.php b/tests/ECPublicKeyTest.php new file mode 100644 index 0000000..7a58706 --- /dev/null +++ b/tests/ECPublicKeyTest.php @@ -0,0 +1,104 @@ + + * @covers :: + */ +class ECPublicKeyTest extends \PHPUnit\Framework\TestCase +{ + /** + * @covers ::__construct + */ + public function testConstruct() + { + $key = "\x04".\random_bytes(64); + $obj = new ECPublicKey($key); + $this->assertInstanceOf(PublicKeyInterface::class, $obj); + } + + /** + * @covers ::__construct + */ + public function testConstructThrowsWithBadFirstByte() + { + $key = "\x01".\random_bytes(64); + $this->expectException(InvalidDataException::class); + $this->expectExceptionCode(InvalidDataException::MALFORMED_DATA); + new ECPublicKey($key); + } + + + /** + * @covers ::__construct + */ + public function testConstructThrowsWhenTooShort() + { + $key = "\x04".random_bytes(63); + $this->expectException(InvalidDataException::class); + $this->expectExceptionCode(InvalidDataException::PUBLIC_KEY_LENGTH); + new ECPublicKey($key); + } + + /** + * @covers ::__construct + */ + public function testConstructThrowsWhenTooLong() + { + $key = "\x04".random_bytes(65); + $this->expectException(InvalidDataException::class); + $this->expectExceptionCode(InvalidDataException::PUBLIC_KEY_LENGTH); + new ECPublicKey($key); + } + + /** + * @covers ::getBinary + */ + public function testGetBinary() + { + $key = "\x04".\random_bytes(64); + $obj = new ECPublicKey($key); + $this->assertSame( + $key, + $obj->getBinary(), + 'getBinary should return the set value' + ); + } + + /** + * @covers ::getPemFormatted + */ + public function testGetPublicKeyPem() + { + $key = hex2bin( + '04b4960ae0fa301033fbedc85c33ac30408dffd6098bc8580d8b66159959d89b9'. + '31daf1d43a1949b07b7d47eea25efcac478bb5cd6ead0a3c3f7b7cb2a7bc1e3be' + ); + assert($key !== false); + $obj = new ECPublicKey($key); + $pem = + "-----BEGIN PUBLIC KEY-----\r\n". + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtJYK4PowEDP77chcM6wwQI3/1gmL\r\n". + "yFgNi2YVmVnYm5Mdrx1DoZSbB7fUfuol78rEeLtc1urQo8P3t8sqe8Hjvg==\r\n". + "-----END PUBLIC KEY-----"; + + $this->assertSame($pem, $obj->getPemFormatted()); + } + + /** + * @covers ::__debugInfo + */ + public function testDebugInfoEncodesBinary() + { + $x = random_bytes(32); + $y = random_bytes(32); + $pk = new ECPublicKey("\x04$x$y"); + $debugInfo = $pk->__debugInfo(); + $this->assertArrayHasKey('x', $debugInfo); + $this->assertSame('0x'.bin2hex($x), $debugInfo['x'], 'x-coordinate wrong'); + $this->assertSame('0x'.bin2hex($y), $debugInfo['y'], 'y-coordinate wrong'); + } +} diff --git a/tests/ECPublicKeyTraitTest.php b/tests/ECPublicKeyTraitTest.php deleted file mode 100644 index f84c0d3..0000000 --- a/tests/ECPublicKeyTraitTest.php +++ /dev/null @@ -1,102 +0,0 @@ - - * @covers :: - */ -class ECPublicKeyTraitTest extends \PHPUnit\Framework\TestCase -{ - - /** - * @covers ::setPublicKey - * @covers ::getPublicKeyBinary - */ - public function testAccessors() - { - $obj = new class { - use ECPublicKeyTrait; - }; - $key = "\x04".\random_bytes(64); - $this->assertSame( - $obj, - $obj->setPublicKey($key), - 'setPublicKey should return $this' - ); - $this->assertSame( - $key, - $obj->getPublicKeyBinary(), - 'getPublicKey should return the set value' - ); - } - - /** - * @covers ::getPublicKeyPem - */ - public function testGetPublicKeyPem() - { - $obj = new class { - use ECPublicKeyTrait; - }; - - $key = hex2bin( - '04b4960ae0fa301033fbedc85c33ac30408dffd6098bc8580d8b66159959d89b9'. - '31daf1d43a1949b07b7d47eea25efcac478bb5cd6ead0a3c3f7b7cb2a7bc1e3be' - ); - assert($key !== false); - $pem = - "-----BEGIN PUBLIC KEY-----\r\n". - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtJYK4PowEDP77chcM6wwQI3/1gmL\r\n". - "yFgNi2YVmVnYm5Mdrx1DoZSbB7fUfuol78rEeLtc1urQo8P3t8sqe8Hjvg==\r\n". - "-----END PUBLIC KEY-----"; - - $obj->setPublicKey($key); - $this->assertSame($pem, $obj->getPublicKeyPem()); - } - - /** - * @covers ::setPublicKey - */ - public function testSetPublicKeyThrowsWithBadFirstByte() - { - $obj = new class { - use ECPublicKeyTrait; - }; - $key = "\x01".\random_bytes(64); - $this->expectException(InvalidDataException::class); - $this->expectExceptionCode(InvalidDataException::MALFORMED_DATA); - $obj->setPublicKey($key); - } - - - /** - * @covers ::setPublicKey - */ - public function testSetPublicKeyThrowsWhenTooShort() - { - $obj = new class { - use ECPublicKeyTrait; - }; - $key = "\x04".random_bytes(63); - $this->expectException(InvalidDataException::class); - $this->expectExceptionCode(InvalidDataException::PUBLIC_KEY_LENGTH); - $obj->setPublicKey($key); - } - - /** - * @covers ::setPublicKey - */ - public function testSetPublicKeyThrowsWhenTooLong() - { - $obj = new class { - use ECPublicKeyTrait; - }; - $key = "\x04".random_bytes(65); - $this->expectException(InvalidDataException::class); - $this->expectExceptionCode(InvalidDataException::PUBLIC_KEY_LENGTH); - $obj->setPublicKey($key); - } -} diff --git a/tests/RegisterResponseTest.php b/tests/RegisterResponseTest.php index 77f3137..fecf587 100644 --- a/tests/RegisterResponseTest.php +++ b/tests/RegisterResponseTest.php @@ -99,7 +99,7 @@ public function testBadRegistrationData(string $registrationData) } /** - * @covers ::getAttestationCertificateBinary + * @covers ::getAttestationCertificate * @covers ::getKeyHandleBinary * @covers ::getPublicKey * @covers ::getSignature @@ -118,7 +118,7 @@ public function testDataAccuracyAfterSuccessfulParsing() $this->assertSame( $pubkey, - $response->getPublicKeyBinary(), + $response->getPublicKey()->getBinary(), 'Public key was not parsed correctly' ); $this->assertSame( @@ -128,7 +128,7 @@ public function testDataAccuracyAfterSuccessfulParsing() ); $this->assertSame( $cert, - $response->getAttestationCertificateBinary(), + $response->getAttestationCertificate()->getBinary(), 'Cert was not parsed correctly' ); $this->assertSame( @@ -162,7 +162,7 @@ public function testGetSignedData() true ), $response->getKeyHandleBinary(), - $response->getPublicKeyBinary() + $response->getPublicKey()->getBinary() ); $this->assertSame( diff --git a/tests/RegistrationTest.php b/tests/RegistrationTest.php index 4c319c1..c9ec9c1 100644 --- a/tests/RegistrationTest.php +++ b/tests/RegistrationTest.php @@ -39,4 +39,28 @@ public function testSetCounterRejectsNegativeNumbers() $this->expectException(\OutOfBoundsException::class); $obj->setCounter(-1); } + + /** + * @covers ::getPublicKey + * @covers ::setPublicKey + */ + public function testPublicKey() + { + $pk = $this->createMock(PublicKeyInterface::class); + $reg = new Registration(); + $reg->setPublicKey($pk); + $this->assertSame($pk, $reg->getPublicKey()); + } + + /** + * @covers ::getAttestationCertificate + * @covers ::setAttestationCertificate + */ + public function testAttestationCertificate() + { + $pk = $this->createMock(AttestationCertificateInterface::class); + $reg = new Registration(); + $reg->setAttestationCertificate($pk); + $this->assertSame($pk, $reg->getAttestationCertificate()); + } } diff --git a/tests/ServerTest.php b/tests/ServerTest.php index b81dbc6..d49225b 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -218,8 +218,8 @@ public function testRegistration() ); $this->assertSame( - $response->getAttestationCertificateBinary(), - $registration->getAttestationCertificateBinary(), + $response->getAttestationCertificate()->getBinary(), + $registration->getAttestationCertificate()->getBinary(), 'Attestation cert was not copied from response' ); @@ -230,8 +230,8 @@ public function testRegistration() ); $this->assertSame( - $response->getPublicKeyBinary(), - $registration->getPublicKeyBinary(), + $response->getPublicKey()->getBinary(), + $registration->getPublicKey()->getBinary(), 'Public key was not copied from response' ); } @@ -517,12 +517,10 @@ public function testAuthenticateThrowsWithObviousReplayAttack() */ public function testAuthenticateThrowsWhenCounterGoesBackwards() { - $pk = base64_decode(self::ENCODED_PUBLIC_KEY); - assert($pk !== false); // Counter from "DB" bumped, suggesting response was cloned $registration = (new Registration()) ->setKeyHandle(fromBase64Web(self::ENCODED_KEY_HANDLE)) - ->setPublicKey($pk) + ->setPublicKey($this->getDefaultPublicKey()) ->setCounter(82) ; $request = $this->getDefaultSignRequest(); @@ -563,12 +561,10 @@ public function testAuthenticateThrowsWhenChallengeDoesNotMatch() */ public function testAuthenticateThrowsIfNoRegistrationMatchesKeyHandle() { - $pk = base64_decode(self::ENCODED_PUBLIC_KEY); - assert($pk !== false); // Change registration KH $registration = (new Registration()) ->setKeyHandle(fromBase64Web('some-other-key-handle')) - ->setPublicKey($pk) + ->setPublicKey($this->getDefaultPublicKey()) ->setCounter(2) ; $request = $this->getDefaultSignRequest(); @@ -645,7 +641,7 @@ public function testAuthenticateThrowsIfRequestIsSignedWithWrongKey() // leading bytes are formatting; see ECPublicKeyTrait) $registration = (new Registration()) ->setKeyHandle(fromBase64Web(self::ENCODED_KEY_HANDLE)) - ->setPublicKey($pk) + ->setPublicKey(new ECPublicKey($pk)) ->setCounter(2) ; $request = $this->getDefaultSignRequest(); @@ -685,12 +681,11 @@ private function getDefaultSignRequest(): SignRequest private function getDefaultRegistration(): RegistrationInterface { - $pk = base64_decode(self::ENCODED_PUBLIC_KEY); - assert($pk !== false); // From database attached to the authenticating user return (new Registration()) ->setKeyHandle(fromBase64Web(self::ENCODED_KEY_HANDLE)) - ->setPublicKey($pk) + ->setAttestationCertificate($this->getDefaultAttestationCertificate()) + ->setPublicKey($this->getDefaultPublicKey()) ->setCounter(2) ; } @@ -701,6 +696,39 @@ private function getDefaultSignResponse(): SignResponse return SignResponse::fromJson($this->safeReadFile('sign_response.json')); } + private function getDefaultAttestationCertificate(): AttestationCertificate + { + $attest = hex2bin( + '3082022d30820117a003020102020405b60579300b06092a864886f70d01010b'. + '302e312c302a0603550403132359756269636f2055324620526f6f7420434120'. + '53657269616c203435373230303633313020170d313430383031303030303030'. + '5a180f32303530303930343030303030305a30283126302406035504030c1d59'. + '756269636f205532462045452053657269616c20393538313530333330593013'. + '06072a8648ce3d020106082a8648ce3d03010703420004fdb8deb3a1ed70eb63'. + '6c066eb6006996a5f970fcb5db88fc3b305d41e5966f0c1b54b852fef0a0907e'. + 'd17f3bffc29d4d321b9cf8a84a2ceaa038cabd35d598dea3263024302206092b'. + '0601040182c40a020415312e332e362e312e342e312e34313438322e312e3130'. + '0b06092a864886f70d01010b03820101007ed3fb6ccc252013f82f218c2a37da'. + '6031d20e7f3081dafcaeb128fc7f9b233914bfb64d6135f17ce221fa764f453e'. + 'f1273a8ce965956442bb2f1e47483f737dcbc98b585377fef50b270e0289f884'. + '36f1adcf49b2621ee5e302df555b9ab74272e069f918149b3dec4f12228b10c0'. + 'f88de36af58a74bb442b85ae005364bda6702058fc1f2d879b530111ea60e86c'. + '63f17fa5944cc83f0aa269848b3ee388a6c09e6b05953fcbb8f47e83a27e0072'. + 'a63c32ad64864e926d7112fa1997f7839656fbb32be8f7889d0f0145519a27af'. + 'dd8e46b04ca4290d8540b634b886161e7588c86299dcdd6435d1678a3a6f0a74'. + '829c4dd3f70c3524d1ddf16d78add21b64' + ); + assert($attest !== false); + return new AttestationCertificate($attest); + } + + public function getDefaultPublicKey(): PublicKeyInterface + { + $pk = base64_decode(self::ENCODED_PUBLIC_KEY); + assert($pk !== false); + return new ECPublicKey($pk); + } + private function readJsonFile(string $file): array { return $this->safeDecode($this->safeReadFile($file)); From 15a255e03029909e68dc28f0068b5bd1cd887639 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 29 May 2019 23:43:39 -0700 Subject: [PATCH 11/17] Fix registration interface (#17) --- src/RegisterResponse.php | 2 +- src/RegistrationResponseInterface.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RegisterResponse.php b/src/RegisterResponse.php index e794b43..a911f69 100644 --- a/src/RegisterResponse.php +++ b/src/RegisterResponse.php @@ -132,7 +132,7 @@ public function getRpIdHash(): string return $this->clientData->getApplicationParameter(); } - public function getAttestationCertificate(): AttestationCertificate + public function getAttestationCertificate(): AttestationCertificateInterface { return $this->cert; } diff --git a/src/RegistrationResponseInterface.php b/src/RegistrationResponseInterface.php index fce1181..9a1cbc5 100644 --- a/src/RegistrationResponseInterface.php +++ b/src/RegistrationResponseInterface.php @@ -5,7 +5,7 @@ interface RegistrationResponseInterface { - public function getAttestationCertificate(): AttestationCertificate; + public function getAttestationCertificate(): AttestationCertificateInterface; public function getChallengeProvider(): ChallengeProvider; From 6526b8a39d5fde96ad0b2ec6edb7f2adba6410fa Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 29 May 2019 23:45:45 -0700 Subject: [PATCH 12/17] Remove ChallengeProvider interface (#18) --- src/ClientData.php | 2 +- src/LoginResponseInterface.php | 4 +--- src/RegisterResponse.php | 5 +++++ src/RegistrationResponseInterface.php | 4 +--- src/ResponseTrait.php | 5 ----- src/Server.php | 13 ++++--------- src/SignResponse.php | 5 +++++ tests/RegisterResponseTest.php | 14 ++++++++++++++ tests/ResponseTraitTest.php | 7 ------- tests/ServerTest.php | 7 ++----- tests/SignResponseTest.php | 15 +++++++++++++++ 11 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/ClientData.php b/src/ClientData.php index 58ea494..55db533 100644 --- a/src/ClientData.php +++ b/src/ClientData.php @@ -6,7 +6,7 @@ use JsonSerializable; use Firehed\U2F\InvalidDataException as IDE; -class ClientData implements JsonSerializable, ChallengeProvider +class ClientData implements JsonSerializable { use ChallengeTrait; diff --git a/src/LoginResponseInterface.php b/src/LoginResponseInterface.php index b092f6d..f514cb3 100644 --- a/src/LoginResponseInterface.php +++ b/src/LoginResponseInterface.php @@ -3,10 +3,8 @@ namespace Firehed\U2F; -interface LoginResponseInterface +interface LoginResponseInterface extends ChallengeProvider { - public function getChallengeProvider(): ChallengeProvider; - public function getCounter(): int; public function getKeyHandleBinary(): string; diff --git a/src/RegisterResponse.php b/src/RegisterResponse.php index a911f69..76d160f 100644 --- a/src/RegisterResponse.php +++ b/src/RegisterResponse.php @@ -137,6 +137,11 @@ public function getAttestationCertificate(): AttestationCertificateInterface return $this->cert; } + public function getChallenge(): string + { + return $this->clientData->getChallenge(); + } + public function getPublicKey(): PublicKeyInterface { return $this->pubKey; diff --git a/src/RegistrationResponseInterface.php b/src/RegistrationResponseInterface.php index 9a1cbc5..861e493 100644 --- a/src/RegistrationResponseInterface.php +++ b/src/RegistrationResponseInterface.php @@ -3,12 +3,10 @@ namespace Firehed\U2F; -interface RegistrationResponseInterface +interface RegistrationResponseInterface extends ChallengeProvider { public function getAttestationCertificate(): AttestationCertificateInterface; - public function getChallengeProvider(): ChallengeProvider; - public function getKeyHandleBinary(): string; public function getPublicKey(): PublicKeyInterface; diff --git a/src/ResponseTrait.php b/src/ResponseTrait.php index e82eb23..367054c 100644 --- a/src/ResponseTrait.php +++ b/src/ResponseTrait.php @@ -20,11 +20,6 @@ public function getSignature(): string return $this->signature; } - public function getChallengeProvider(): ChallengeProvider - { - return $this->clientData; - } - protected function setSignature(string $signature): self { $this->signature = $signature; diff --git a/src/Server.php b/src/Server.php index eeea89e..293552e 100644 --- a/src/Server.php +++ b/src/Server.php @@ -118,7 +118,7 @@ public function authenticate(LoginResponseInterface $response): RegistrationInte // match the one in the signing request, the client signed the // wrong thing. This could possibly be an attempt at a replay // attack. - $this->validateChallenge($response->getChallengeProvider(), $request); + $this->validateChallenge($request, $response); $pem = $registration->getPublicKey()->getPemFormatted(); @@ -195,7 +195,7 @@ public function register(RegistrationResponseInterface $response): RegistrationI 'with setRegisterRequest()' ); } - $this->validateChallenge($response->getChallengeProvider(), $this->registerRequest); + $this->validateChallenge($this->registerRequest, $response); // Check the Application Parameter $this->validateRelyingParty($response->getRpIdHash()); @@ -389,13 +389,10 @@ private function validateRelyingParty(string $rpIdHash): void * * @param ChallengeProvider $from source of known challenge * @param ChallengeProvider $to user-provided value - * @return true on success * @throws SE on failure */ - private function validateChallenge( - ChallengeProvider $from, - ChallengeProvider $to - ): bool { + private function validateChallenge(ChallengeProvider $from, ChallengeProvider $to) + { // Note: strictly speaking, this shouldn't even be targetable as // a timing attack. However, this opts to be proactive, and also // ensures that no weird PHP-isms in string handling cause mismatched @@ -403,8 +400,6 @@ private function validateChallenge( if (!hash_equals($from->getChallenge(), $to->getChallenge())) { throw new SE(SE::CHALLENGE_MISMATCH); } - // TOOD: generate and compare timestamps - return true; } /** diff --git a/src/SignResponse.php b/src/SignResponse.php index 76b14d3..1cf3fe2 100644 --- a/src/SignResponse.php +++ b/src/SignResponse.php @@ -61,4 +61,9 @@ protected function parseResponse(array $response): self $this->setSignature($decoded['signature']); return $this; } + + public function getChallenge(): string + { + return $this->clientData->getChallenge(); + } } diff --git a/tests/RegisterResponseTest.php b/tests/RegisterResponseTest.php index fecf587..310e765 100644 --- a/tests/RegisterResponseTest.php +++ b/tests/RegisterResponseTest.php @@ -172,6 +172,20 @@ public function testGetSignedData() ); } + /** + * @covers ::getChallenge + */ + public function testGetChallenge() + { + $json = file_get_contents(__DIR__ . '/register_response.json'); + assert($json !== false); + $response = RegisterResponse::fromJson($json); + + $this->assertSame( + 'PfsWR1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo', + $response->getChallenge() + ); + } /** * @covers ::getRpIdHash */ diff --git a/tests/ResponseTraitTest.php b/tests/ResponseTraitTest.php index bbf766d..aedc385 100644 --- a/tests/ResponseTraitTest.php +++ b/tests/ResponseTraitTest.php @@ -27,7 +27,6 @@ protected function parseResponse(array $response): self /** * @covers ::fromJson * @covers ::getSignature - * @covers ::getChallengeProvider */ public function testValidJson() { @@ -50,12 +49,6 @@ public function testValidJson() 'Parsed response was the wrong type' ); - $this->assertInstanceOf( - ChallengeProvider::class, - $response->getChallengeProvider(), - 'Did not get a challenge provider' - ); - $this->assertSame( __METHOD__, $response->getSignature(), diff --git a/tests/ServerTest.php b/tests/ServerTest.php index d49225b..38426df 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -328,12 +328,9 @@ public function testRegisterThrowsWithChangedApplicationParameter() { $request = $this->getDefaultRegisterRequest(); - $challengeProvider = $this->createMock(ChallengeProvider::class); - $challengeProvider->method('getChallenge') - ->willReturn($request->getChallenge()); $response = $this->createMock(RegistrationResponseInterface::class); - $response->method('getChallengeProvider') - ->willReturn($challengeProvider); + $response->method('getChallenge') + ->willReturn($request->getChallenge()); $response->method('getRpIdHash') ->willReturn(hash('sha256', 'https://some.otherdomain.com', true)); diff --git a/tests/SignResponseTest.php b/tests/SignResponseTest.php index 1d3e7c3..5d0f7d4 100644 --- a/tests/SignResponseTest.php +++ b/tests/SignResponseTest.php @@ -213,6 +213,21 @@ public function testGetSignedData() ); } + /** + * @covers ::getChallenge + */ + public function testGetChallenge() + { + $json = file_get_contents(__DIR__ . '/sign_response.json'); + assert($json !== false); + $response = SignResponse::fromJson($json); + + $this->assertSame( + 'wt2ze8IskcTO3nIsO2D2hFjE5tVD041NpnYesLpJweg', + $response->getChallenge() + ); + } + /** * @dataProvider clientErrors */ From a8c8734f40314c4d5a8f5b2ac3d532102f1e286f Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Thu, 13 Jun 2019 16:55:47 -0700 Subject: [PATCH 13/17] Backport cid_pubkey fix to 2.0/master (#21) --- src/ClientData.php | 25 ++++++--------- tests/ClientDataTest.php | 13 +++----- tests/ServerTest.php | 66 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 25 deletions(-) diff --git a/src/ClientData.php b/src/ClientData.php index 55db533..1bdaae6 100644 --- a/src/ClientData.php +++ b/src/ClientData.php @@ -3,13 +3,14 @@ namespace Firehed\U2F; -use JsonSerializable; use Firehed\U2F\InvalidDataException as IDE; -class ClientData implements JsonSerializable +class ClientData { use ChallengeTrait; + /** @var string */ + private $originalJson; private $cid_pubkey; private $origin; private $typ; @@ -24,7 +25,11 @@ public static function fromJson(string $json) $ret->setType($ret->validateKey('typ', $data)); $ret->setChallenge($ret->validateKey('challenge', $data)); $ret->origin = $ret->validateKey('origin', $data); - $ret->cid_pubkey = $ret->validateKey('cid_pubkey', $data); + // This field is optional + if (isset($data['cid_pubkey'])) { + $ret->cid_pubkey = $data['cid_pubkey']; + } + $ret->originalJson = $json; return $ret; } @@ -72,18 +77,6 @@ private function validateKey(string $key, array $data) // Returns the SHA256 hash of this object per the raw message formats spec public function getChallengeParameter(): string { - $json = json_encode($this, \JSON_UNESCAPED_SLASHES); - assert($json !== false); - return hash('sha256', $json, true); - } - - public function jsonSerialize() - { - return [ - 'typ' => $this->typ, - 'challenge' => $this->getChallenge(), - 'origin' => $this->origin, - 'cid_pubkey' => $this->cid_pubkey, - ]; + return hash('sha256', $this->originalJson, true); } } diff --git a/tests/ClientDataTest.php b/tests/ClientDataTest.php index b369220..343feda 100644 --- a/tests/ClientDataTest.php +++ b/tests/ClientDataTest.php @@ -29,7 +29,6 @@ public function testFromValidJson() /** * @covers ::getChallengeParameter - * @covers ::jsonSerialize */ public function testGetChallengeParameter() { @@ -42,13 +41,10 @@ public function testGetChallengeParameter() 'Test vector should have been 32 bytes' ); - $goodData = [ - 'typ' => 'navigator.id.finishEnrollment', - 'challenge' => 'PfsWR1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo', - 'origin' => 'https://u2f.ericstern.com', - 'cid_pubkey' => '', - ]; - $goodJson = json_encode($goodData); + $goodJson = '{"typ":"navigator.id.finishEnrollment","challenge":"PfsWR'. + '1Umy2V5Al1Bam2tG0yfPLeJElfwRzzAzkYPgzo","origin":"https://u2f.eri'. + 'cstern.com","cid_pubkey":""}'; + assert($goodJson !== false); $clientData = ClientData::fromJson($goodJson); $this->assertTrue( @@ -139,7 +135,6 @@ public function missingData(): array $without('typ'), $without('challenge'), $without('origin'), - $without('cid_pubkey'), ]; } diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 38426df..4fbbf5f 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -651,6 +651,72 @@ public function testAuthenticateThrowsIfRequestIsSignedWithWrongKey() ->authenticate($response); } + // -( Alternate formats (see #14) )---------------------------------------- + + public function testRegistrationWithoutCidPubkeyBug14Case1() + { + $server = (new Server()) + ->disableCAVerification() + ->setAppId('https://u2f.ericstern.com'); + + $registerRequest = new RegisterRequest(); + $registerRequest->setAppId($server->getAppId()) + ->setChallenge('dNqjowssvlxx9zBhvsy03A'); + $server->setRegisterRequest($registerRequest); + + $json = '{"registrationData":"BQSFDYsZaHlRBQcdLyu4jZ-Bukb1vw6QtSfmvTQO'. + 'IXpjZpfqYptdtpBznuNBslzlZdodspfqRkqwJIt3a0W2P_HlQImHG1FoSkYdPwSzp'. + '3WvlDisShW5fveiaaI4Zk8oZBkyWoQ6v1c2ypcd5OWPX6rAH-N7cPjw1Vg_w1q_YL'. + 'c3mR8wggE0MIHboAMCAQICCjJ1rwmwx867ew8wCgYIKoZIzj0EAwIwFTETMBEGA1U'. + 'EAxMKVTJGIElzc3VlcjAaFwswMDAxMDEwMDAwWhcLMDAwMTAxMDAwMFowFTETMBEG'. + 'A1UEAxMKVTJGIERldmljZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCLzJT4vt'. + 'kl-799Ks5wINHdVRIKCLq-kX6oIajh_2Dv4Sk0cBVteQt1xdGau1XzEaGYIOvU5hU'. + 'm2J2pxVBQIzaajFzAVMBMGCysGAQQBguUcAgEBBAQDAgUgMAoGCCqGSM49BAMCA0g'. + 'AMEUCIQDBo6aOLxanIUYnBX9iu3KMngPnobpi0EZSTkVtLC8_cwIgC1945RGqGBKf'. + 'byNtkhMifZK05n7fU-gW37Bdnci5D94wRQIgEPJVWZ7zgVQUctG3xpWBv77s3u2R7'. + 'OJP-UjkWdcUs2QCIQC1fqlZIrl4kIEsSQTRMauvcaoeunV-I24WYnp3rgC_Dg","v'. + 'ersion":"U2F_V2","challenge":"dNqjowssvlxx9zBhvsy03A","appId":"ht'. + 'tps://u2f.ericstern.com","clientData":"eyJjaGFsbGVuZ2UiOiJkTnFqb3'. + 'dzc3ZseHg5ekJodnN5MDNBIiwib3JpZ2luIjoiaHR0cHM6Ly91MmYuZXJpY3N0ZXJ'. + 'uLmNvbSIsInR5cCI6Im5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50In0"}'; + $registerResponse = RegisterResponse::fromJson($json); + + $registration = $server->register($registerResponse); + $this->assertInstanceOf(Registration::class, $registration); + } + + public function testRegistrationWithoutCidPubkeyBug14Case2() + { + $server = (new Server()) + ->disableCAVerification() + ->setAppId('https://u2f.ericstern.com'); + + $registerRequest = new RegisterRequest(); + $registerRequest->setAppId($server->getAppId()) + ->setChallenge('E23usdC7VkxjN1mwRAeyjg'); + $server->setRegisterRequest($registerRequest); + + $json = '{"registrationData":"BQSTffB-e9hdFwhsfb2t-2ppwyxZAltnDf6TYwv4'. + '1VtleEO4488JwNFGr_bks_4EzA4DoluDBCgfmULGpZpXykTZQMOMz9DfbESHnuBY9'. + 'cmTxVTVtrsTFTQA-IPETCYJ2dYACULXRN7_qLq_2WnDQJaME7zWyZEB0NFu-hosav'. + 'uqjncwggEbMIHCoAMCAQICCiIygbKxS2KpYY8wCgYIKoZIzj0EAwIwFTETMBEGA1U'. + 'EAxMKVTJGIElzc3VlcjAaFwswMDAxMDEwMDAwWhcLMDAwMTAxMDAwMFowFTETMBEG'. + 'A1UEAxMKVTJGIERldmljZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCdqjfpHR'. + '9L8a6-pVRv9PWu-pORC9sO9eDk6ZlFIXaclyfxbLJqAehvIWJuzij_BxJOLbQPD_9'. + 'fX5uKh9tDv8nowCgYIKoZIzj0EAwIDSAAwRQIhAMGjpo4vFqchRicFf2K7coyeA-e'. + 'humLQRlJORW0sLz9zAiALX3jlEaoYEp9vI22SEyJ9krTmft9T6BbfsF2dyLkP3jBE'. + 'AiAHD70-wA4f3SZk6s0RocHAA4nDCGaVFvTBG4gZXcZTnQIge2joenpQxVP0r1o9E'. + 'zL9C3aR-HEKhSHr86MX4eUTMlw","version":"U2F_V2","challenge":"E23us'. + 'dC7VkxjN1mwRAeyjg","appId":"https://u2f.ericstern.com","clientDat'. + 'a":"eyJjaGFsbGVuZ2UiOiJFMjN1c2RDN1ZreGpOMW13UkFleWpnIiwib3JpZ2luI'. + 'joiaHR0cHM6Ly91MmYuZXJpY3N0ZXJuLmNvbSIsInR5cCI6Im5hdmlnYXRvci5pZC'. + '5maW5pc2hFbnJvbGxtZW50In0"}'; + $registerResponse = RegisterResponse::fromJson($json); + + $registration = $server->register($registerResponse); + $this->assertInstanceOf(Registration::class, $registration); + } + // -( Helpers )------------------------------------------------------------ private function getDefaultRegisterRequest(): RegisterRequest From 0b03072833c3d5f0d0158ac749110a9700340d77 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 22 Oct 2021 16:05:23 -0400 Subject: [PATCH 14/17] Update CI tooling (#24) --- .gitattributes | 6 + .github/dependabot.yml | 11 + .github/workflows/lint.yml | 41 ++ .github/workflows/static-analysis.yml | 41 ++ .github/workflows/test.yml | 80 +++ .travis.yml | 17 - README.md | 6 +- composer.json | 10 +- phpcs.xml | 3 + phpstan-baseline.neon | 822 ++++++++++++++++++++++++++ phpstan.neon | 11 +- 11 files changed, 1019 insertions(+), 29 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/static-analysis.yml create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml create mode 100644 phpstan-baseline.neon diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3a1617e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +/tests export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore + +*.php diff=php diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a839255 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..ce4db10 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,41 @@ +name: Lint + +on: + push: + branches: + - master + pull_request: + # Run on all PRs + +env: + CI: "true" + +jobs: + phpcs: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.json') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer update + --no-ansi + --no-interaction + --no-progress + --no-suggest + --prefer-dist + + - name: PHPCS + run: composer phpcs diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..d1fd5c3 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,41 @@ +name: Static analysis + +on: + push: + branches: + - master + pull_request: + # Run on all PRs + +env: + CI: "true" + +jobs: + phpstan: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.json') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer update + --no-ansi + --no-interaction + --no-progress + --no-suggest + --prefer-dist + + - name: PHPStan + run: composer phpstan diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..1ca349a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,80 @@ +name: Test + +on: + push: + branches: + - master + pull_request: + # Run on all PRs + +env: + CI: "true" + +jobs: + phpunit: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + dependencies: + - 'high' + - 'low' + php: + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + exclude: + - php: '8.1' + dependencies: 'low' + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + coverage: pcov + ini-values: zend.assertions=1, assert.exception=1, error_reporting=-1 + php-version: ${{ matrix.php }} + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ matrix.dependencies }}-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} + restore-keys: | + ${{ runner.os }}-php-${{ matrix.dependencies }}-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} + ${{ runner.os }}-php-${{ matrix.dependencies }}-${{ matrix.php }}- + ${{ runner.os }}-php-${{ matrix.dependencies }}- + ${{ runner.os }}-php- + + - name: Install highest dependencies + if: ${{ matrix.dependencies == 'high' }} + run: composer update + --no-ansi + --no-interaction + --no-progress + --no-suggest + --prefer-dist + + - name: Install lowest dependencies + if: ${{ matrix.dependencies == 'low' }} + run: composer update + --no-ansi + --no-interaction + --no-progress + --no-suggest + --prefer-dist + --prefer-lowest + + - name: PHPUnit + run: vendor/bin/phpunit + --coverage-clover coverage.xml + + - name: Submit code coverage + if: ${{ always() }} + uses: codecov/codecov-action@v2 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fb8aea0..0000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: php -php: - - '7.2' - - '7.3' - - '7.4snapshot' - -# From PHPUnit's config -install: - - travis_retry composer install --no-interaction --prefer-source - -script: - - php vendor/bin/phpunit --coverage-text --whitelist src/ tests/ - - vendor/bin/phpcs src tests - - vendor/bin/phpstan analyse . - -after_success: - - travis_retry php vendor/bin/php-coveralls diff --git a/README.md b/README.md index 546dff9..2473531 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,10 @@ A PHP implementation of the FIDO U2F authentication standard -[![Build Status](https://travis-ci.org/Firehed/u2f-php.svg?branch=master)](https://travis-ci.org/Firehed/u2f-php) -[![Coverage Status](https://coveralls.io/repos/github/Firehed/u2f-php/badge.svg)](https://coveralls.io/github/Firehed/u2f-php) +[![Lint](https://github.com/Firehed/u2f-php/actions/workflows/lint.yml/badge.svg)](https://github.com/Firehed/u2f-php/actions/workflows/lint.yml) +[![Static analysis](https://github.com/Firehed/u2f-php/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/Firehed/u2f-php/actions/workflows/static-analysis.yml) +[![Test](https://github.com/Firehed/u2f-php/actions/workflows/test.yml/badge.svg)](https://github.com/Firehed/u2f-php/actions/workflows/test.yml) +[![codecov](https://codecov.io/gh/Firehed/u2f-php/branch/master/graph/badge.svg?token=8VxRoJxmNL)](https://codecov.io/gh/Firehed/u2f-php) ## Introduction diff --git a/composer.json b/composer.json index d8b2e71..0b6f555 100644 --- a/composer.json +++ b/composer.json @@ -17,11 +17,11 @@ }, "require-dev": { "php-coveralls/php-coveralls": "^2.0", - "phpstan/phpstan": "^0.11", - "phpunit/phpunit": "^8.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5", "squizlabs/php_codesniffer": "^3.2", "spatie/phpunit-watcher": "^1.8", - "phpstan/phpstan-phpunit": "^0.11.2" + "phpstan/phpstan-phpunit": "^0.12" }, "autoload": { "psr-4": { @@ -51,7 +51,7 @@ "coverage": "phpunit --coverage-html build; open build/index.html", "autofix": "phpcbf src lib tests db", "phpunit": "phpunit", - "phpstan": "phpstan analyse --no-progress .", - "phpcs": "phpcs ." + "phpstan": "phpstan analyse --no-progress", + "phpcs": "phpcs" } } diff --git a/phpcs.xml b/phpcs.xml index bb7e60c..3a54078 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,4 +1,7 @@ + src + tests + diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..bb025ff --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,822 @@ +parameters: + ignoreErrors: + - + message: "#^Method Firehed\\\\U2F\\\\AttestationCertificate\\:\\:__debugInfo\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/AttestationCertificate.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientData\\:\\:fromJson\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/ClientData.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientData\\:\\:validateKey\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: src/ClientData.php + + - + message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$challenge has no typehint specified\\.$#" + count: 1 + path: src/ClientData.php + + - + message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$cid_pubkey has no typehint specified\\.$#" + count: 1 + path: src/ClientData.php + + - + message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$cid_pubkey is never read, only written\\.$#" + count: 1 + path: src/ClientData.php + + - + message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$origin has no typehint specified\\.$#" + count: 1 + path: src/ClientData.php + + - + message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$typ has no typehint specified\\.$#" + count: 1 + path: src/ClientData.php + + - + message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$typ is never read, only written\\.$#" + count: 1 + path: src/ClientData.php + + - + message: "#^Method Firehed\\\\U2F\\\\ECPublicKey\\:\\:__debugInfo\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/ECPublicKey.php + + - + message: "#^Property Firehed\\\\U2F\\\\RegisterRequest\\:\\:\\$appId has no typehint specified\\.$#" + count: 1 + path: src/RegisterRequest.php + + - + message: "#^Property Firehed\\\\U2F\\\\RegisterRequest\\:\\:\\$challenge has no typehint specified\\.$#" + count: 1 + path: src/RegisterRequest.php + + - + message: "#^Property Firehed\\\\U2F\\\\RegisterRequest\\:\\:\\$version has no typehint specified\\.$#" + count: 1 + path: src/RegisterRequest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponse\\:\\:parseResponse\\(\\) has parameter \\$response with no value type specified in iterable type array\\.$#" + count: 1 + path: src/RegisterResponse.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponse\\:\\:validateKeyInArray\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: src/RegisterResponse.php + + - + message: "#^Method Firehed\\\\U2F\\\\Server\\:\\:findObjectWithKeyHandle\\(\\) has parameter \\$objects with no value type specified in iterable type array\\.$#" + count: 1 + path: src/Server.php + + - + message: "#^Method Firehed\\\\U2F\\\\Server\\:\\:validateChallenge\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/Server.php + + - + message: "#^Negated boolean expression is always true\\.$#" + count: 1 + path: src/Server.php + + - + message: "#^Parameter \\#1 \\$callback of function array_map expects callable\\(Firehed\\\\U2F\\\\RegistrationInterface\\)\\: mixed, Closure\\(Firehed\\\\U2F\\\\Registration\\)\\: void given\\.$#" + count: 1 + path: src/Server.php + + - + message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$appId has no typehint specified\\.$#" + count: 1 + path: src/Server.php + + - + message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$registerRequest has no typehint specified\\.$#" + count: 1 + path: src/Server.php + + - + message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$registrations has no typehint specified\\.$#" + count: 1 + path: src/Server.php + + - + message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$signRequests has no typehint specified\\.$#" + count: 1 + path: src/Server.php + + - + message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$trustedCAs has no typehint specified\\.$#" + count: 1 + path: src/Server.php + + - + message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$verifyCA has no typehint specified\\.$#" + count: 1 + path: src/Server.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Server.php + + - + message: "#^Property Firehed\\\\U2F\\\\SignRequest\\:\\:\\$appId has no typehint specified\\.$#" + count: 1 + path: src/SignRequest.php + + - + message: "#^Property Firehed\\\\U2F\\\\SignRequest\\:\\:\\$challenge has no typehint specified\\.$#" + count: 1 + path: src/SignRequest.php + + - + message: "#^Property Firehed\\\\U2F\\\\SignRequest\\:\\:\\$version has no typehint specified\\.$#" + count: 1 + path: src/SignRequest.php + + - + message: "#^Cannot access offset 'counter' on array\\|false\\.$#" + count: 1 + path: src/SignResponse.php + + - + message: "#^Cannot access offset 'presence' on array\\|false\\.$#" + count: 1 + path: src/SignResponse.php + + - + message: "#^Cannot access offset 'signature' on array\\|false\\.$#" + count: 1 + path: src/SignResponse.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponse\\:\\:parseResponse\\(\\) has parameter \\$response with no value type specified in iterable type array\\.$#" + count: 1 + path: src/SignResponse.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponse\\:\\:validateKeyInArray\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: src/SignResponse.php + + - + message: "#^Property Firehed\\\\U2F\\\\SignResponse\\:\\:\\$counter has no typehint specified\\.$#" + count: 1 + path: src/SignResponse.php + + - + message: "#^Property Firehed\\\\U2F\\\\SignResponse\\:\\:\\$user_presence has no typehint specified\\.$#" + count: 1 + path: src/SignResponse.php + + - + message: "#^Method Firehed\\\\U2F\\\\AppIdTraitTest\\:\\:testAccessors\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/AppIdTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\AppIdTraitTest\\:\\:testGetApplicationParameter\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/AppIdTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\AppIdTraitTest\\:\\:testGetRpIdHash\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/AppIdTraitTest.php + + - + message: "#^Property class@anonymous/tests/AppIdTraitTest\\.php\\:20\\:\\:\\$appId has no typehint specified\\.$#" + count: 1 + path: tests/AppIdTraitTest.php + + - + message: "#^Property class@anonymous/tests/AppIdTraitTest\\.php\\:42\\:\\:\\$appId has no typehint specified\\.$#" + count: 1 + path: tests/AppIdTraitTest.php + + - + message: "#^Property class@anonymous/tests/AppIdTraitTest\\.php\\:59\\:\\:\\$appId has no typehint specified\\.$#" + count: 1 + path: tests/AppIdTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\AttestationCertificateTest\\:\\:testConstruct\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/AttestationCertificateTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\AttestationCertificateTest\\:\\:testDebugInfoEncodesBinary\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/AttestationCertificateTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\AttestationCertificateTest\\:\\:testGetBinary\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/AttestationCertificateTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\AttestationCertificateTest\\:\\:testGetPemFormatted\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/AttestationCertificateTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ChallengeTraitTest\\:\\:testAccessors\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ChallengeTraitTest.php + + - + message: "#^Property class@anonymous/tests/ChallengeTraitTest\\.php\\:20\\:\\:\\$challenge has no typehint specified\\.$#" + count: 1 + path: tests/ChallengeTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:missingData\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/ClientDataTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testBadJson\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ClientDataTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testDataValidation\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ClientDataTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testDataValidation\\(\\) has parameter \\$json with no typehint specified\\.$#" + count: 1 + path: tests/ClientDataTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testFromValidJson\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ClientDataTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testGetApplicationParameter\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ClientDataTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testGetChallengeParameter\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ClientDataTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testTypes\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ClientDataTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:types\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/ClientDataTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientErrorExceptionTest\\:\\:clientErrors\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ClientErrorExceptionTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ClientErrorExceptionTest\\:\\:testClientError\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ClientErrorExceptionTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testConstruct\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ECPublicKeyTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testConstructThrowsWhenTooLong\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ECPublicKeyTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testConstructThrowsWhenTooShort\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ECPublicKeyTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testConstructThrowsWithBadFirstByte\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ECPublicKeyTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testDebugInfoEncodesBinary\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ECPublicKeyTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testGetBinary\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ECPublicKeyTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testGetPublicKeyPem\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ECPublicKeyTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testFromBase64Web\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/FunctionsTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testFromBase64Web\\(\\) has parameter \\$encoded with no typehint specified\\.$#" + count: 1 + path: tests/FunctionsTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testFromBase64Web\\(\\) has parameter \\$plain with no typehint specified\\.$#" + count: 1 + path: tests/FunctionsTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testToBase64Web\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/FunctionsTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testToBase64Web\\(\\) has parameter \\$encoded with no typehint specified\\.$#" + count: 1 + path: tests/FunctionsTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testToBase64Web\\(\\) has parameter \\$plain with no typehint specified\\.$#" + count: 1 + path: tests/FunctionsTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:vectors\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/FunctionsTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\InvalidDataExceptionTest\\:\\:invalidDataExceptionCodes\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/InvalidDataExceptionTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\InvalidDataExceptionTest\\:\\:testInvalidDataException\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/InvalidDataExceptionTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\KeyHandleTraitTest\\:\\:testAccessors\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/KeyHandleTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\KeyHandleTraitTest\\:\\:testGetKeyHandleWeb\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/KeyHandleTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterRequestTest\\:\\:testJsonSerialize\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterRequestTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:buildJson\\(\\) has parameter \\$clientData with no typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:buildJson\\(\\) has parameter \\$registrationData with no typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:clientErrors\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:invalidRegistrationData\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testBadRegistrationData\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testDataAccuracyAfterSuccessfulParsing\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testErrorResponse\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testFromJson\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testFromJsonBadJson\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testFromJsonMissingClientData\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testFromJsonMissingRegistrationData\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testGetChallenge\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testGetRpIdHash\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testGetSignedData\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Property Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:\\$validClientData has no typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Property Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:\\$validRegistrationData has no typehint specified\\.$#" + count: 1 + path: tests/RegisterResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegistrationTest\\:\\:testAttestationCertificate\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegistrationTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegistrationTest\\:\\:testCounter\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegistrationTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegistrationTest\\:\\:testPublicKey\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegistrationTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\RegistrationTest\\:\\:testSetCounterRejectsNegativeNumbers\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/RegistrationTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:badClientData\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ResponseTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:clientErrors\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ResponseTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:testClientDataValidation\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ResponseTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:testErrorResponse\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ResponseTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:testFromJsonWithNonJson\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ResponseTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:testValidJson\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ResponseTraitTest.php + + - + message: "#^Method class@anonymous/tests/ResponseTraitTest\\.php\\:17\\:\\:parseResponse\\(\\) has parameter \\$response with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/ResponseTraitTest.php + + - + message: "#^Method class@anonymous/tests/ResponseTraitTest\\.php\\:17\\:\\:validateKeyInArray\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/ResponseTraitTest.php + + - + message: "#^Property Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:\\$trait has no typehint specified\\.$#" + count: 1 + path: tests/ResponseTraitTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SecurityExceptionTest\\:\\:securityExceptionCodes\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SecurityExceptionTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SecurityExceptionTest\\:\\:testSecurityException\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SecurityExceptionTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:readJsonFile\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:safeDecode\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:safeEncode\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticate\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfNoRegistrationMatchesKeyHandle\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfNoRegistrationsPresent\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfNoRequestMatchesKeyHandle\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfNoSignRequestsPresent\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfRequestIsSignedWithWrongKey\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfSignatureIsInvalid\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsWhenChallengeDoesNotMatch\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsWhenCounterGoesBackwards\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsWithObviousReplayAttack\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testConstruct\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testDisableCAVerificationReturnsSelf\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testGenerateRegisterRequest\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testGenerateSignRequest\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testGenerateSignRequests\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterDefaultsToTryingEmptyCAList\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsIfChallengeDoesNotMatch\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsIfNoRegistrationRequestProvided\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithBadSignature\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithChangedApplicationParameter\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithChangedChallengeParameter\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithChangedKeyHandle\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithChangedPubkey\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithUntrustedDeviceIssuerCertificate\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterWorksWithCAList\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegistration\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegistrationWithoutCidPubkeyBug14Case1\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegistrationWithoutCidPubkeyBug14Case2\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testSetRegisterRequestReturnsSelf\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testSetRegistrationsEnforcesTypeCheck\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testSetRegistrationsReturnsSelf\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testSetSignRequestsEnforcesTypeCheck\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testSetSignRequestsReturnsSelf\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Property Firehed\\\\U2F\\\\ServerTest\\:\\:\\$server has no typehint specified\\.$#" + count: 1 + path: tests/ServerTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignRequestTest\\:\\:testJsonSerialize\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignRequestTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:clientErrors\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testDataAccuracyAfterSuccessfulParsing\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testErrorResponse\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testFromJsonWithInvalidSignatureData\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testFromJsonWithMissingClientData\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testFromJsonWithMissingKeyHandle\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testFromJsonWithMissingSignatureData\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testFromJsonWorks\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testGetChallenge\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testGetSignedData\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testSignatureWithNullRemainsIntact\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testSignatureWithSpaceRemainsIntact\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Property Firehed\\\\U2F\\\\SignResponseTest\\:\\:\\$valid_client_data has no typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Property Firehed\\\\U2F\\\\SignResponseTest\\:\\:\\$valid_key_handle has no typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Property Firehed\\\\U2F\\\\SignResponseTest\\:\\:\\$valid_signature_data has no typehint specified\\.$#" + count: 1 + path: tests/SignResponseTest.php + + - + message: "#^Method Firehed\\\\U2F\\\\VersionTraitTest\\:\\:testGetVersion\\(\\) has no return typehint specified\\.$#" + count: 1 + path: tests/VersionTraitTest.php + + - + message: "#^Property class@anonymous/tests/VersionTraitTest\\.php\\:19\\:\\:\\$version has no typehint specified\\.$#" + count: 1 + path: tests/VersionTraitTest.php + diff --git a/phpstan.neon b/phpstan.neon index 626379f..71f50cc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,8 +1,9 @@ includes: + - phpstan-baseline.neon - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan/conf/bleedingEdge.neon parameters: - excludes_analyse: - - %rootDir%/../../../vendor - level: 7 - includes: - - vendor/phpstan/phpstan/conf/bleedingEdge.neon + level: max + paths: + - src + - tests From 82529e294812bd9f149916ef2cef27eaecd5ff2b Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 22 Oct 2021 16:49:46 -0400 Subject: [PATCH 15/17] Add additional type information (#25) --- phpstan-baseline.neon | 803 +-------------------------- src/AppIdTrait.php | 1 + src/AttestationCertificate.php | 1 + src/ChallengeTrait.php | 3 +- src/ClientData.php | 14 +- src/ECPublicKey.php | 1 + src/KeyHandleInterface.php | 13 + src/RegisterResponse.php | 9 + src/RegistrationInterface.php | 7 +- src/ResponseTrait.php | 1 + src/Server.php | 25 +- src/SignRequest.php | 2 +- src/SignResponse.php | 11 + src/VersionTrait.php | 1 + tests/AppIdTraitTest.php | 6 +- tests/AttestationCertificateTest.php | 8 +- tests/ChallengeTraitTest.php | 2 +- tests/ClientDataTest.php | 20 +- tests/ClientErrorExceptionTest.php | 3 +- tests/ECPublicKeyTest.php | 14 +- tests/FunctionsTest.php | 5 +- tests/InvalidDataExceptionTest.php | 3 +- tests/KeyHandleTraitTest.php | 4 +- tests/RegisterRequestTest.php | 2 +- tests/RegisterResponseTest.php | 26 +- tests/RegistrationTest.php | 8 +- tests/ResponseTraitTest.php | 15 +- tests/SecurityExceptionTest.php | 3 +- tests/ServerTest.php | 73 +-- tests/SignRequestTest.php | 2 +- tests/SignResponseTest.php | 26 +- tests/VersionTraitTest.php | 2 +- 32 files changed, 198 insertions(+), 916 deletions(-) create mode 100644 src/KeyHandleInterface.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index bb025ff..2c27d18 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,822 +1,27 @@ parameters: ignoreErrors: - - - message: "#^Method Firehed\\\\U2F\\\\AttestationCertificate\\:\\:__debugInfo\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/AttestationCertificate.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientData\\:\\:fromJson\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/ClientData.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientData\\:\\:validateKey\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" - count: 1 - path: src/ClientData.php - - - - message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$challenge has no typehint specified\\.$#" - count: 1 - path: src/ClientData.php - - - - message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$cid_pubkey has no typehint specified\\.$#" - count: 1 - path: src/ClientData.php - - message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$cid_pubkey is never read, only written\\.$#" count: 1 path: src/ClientData.php - - - message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$origin has no typehint specified\\.$#" - count: 1 - path: src/ClientData.php - - - - message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$typ has no typehint specified\\.$#" - count: 1 - path: src/ClientData.php - - message: "#^Property Firehed\\\\U2F\\\\ClientData\\:\\:\\$typ is never read, only written\\.$#" count: 1 path: src/ClientData.php - - message: "#^Method Firehed\\\\U2F\\\\ECPublicKey\\:\\:__debugInfo\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/ECPublicKey.php - - - - message: "#^Property Firehed\\\\U2F\\\\RegisterRequest\\:\\:\\$appId has no typehint specified\\.$#" - count: 1 - path: src/RegisterRequest.php - - - - message: "#^Property Firehed\\\\U2F\\\\RegisterRequest\\:\\:\\$challenge has no typehint specified\\.$#" - count: 1 - path: src/RegisterRequest.php - - - - message: "#^Property Firehed\\\\U2F\\\\RegisterRequest\\:\\:\\$version has no typehint specified\\.$#" - count: 1 - path: src/RegisterRequest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponse\\:\\:parseResponse\\(\\) has parameter \\$response with no value type specified in iterable type array\\.$#" - count: 1 - path: src/RegisterResponse.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponse\\:\\:validateKeyInArray\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" - count: 1 - path: src/RegisterResponse.php - - - - message: "#^Method Firehed\\\\U2F\\\\Server\\:\\:findObjectWithKeyHandle\\(\\) has parameter \\$objects with no value type specified in iterable type array\\.$#" - count: 1 - path: src/Server.php - - - - message: "#^Method Firehed\\\\U2F\\\\Server\\:\\:validateChallenge\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/Server.php - - - - message: "#^Negated boolean expression is always true\\.$#" - count: 1 - path: src/Server.php - - - - message: "#^Parameter \\#1 \\$callback of function array_map expects callable\\(Firehed\\\\U2F\\\\RegistrationInterface\\)\\: mixed, Closure\\(Firehed\\\\U2F\\\\Registration\\)\\: void given\\.$#" - count: 1 - path: src/Server.php - - - - message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$appId has no typehint specified\\.$#" - count: 1 - path: src/Server.php - - - - message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$registerRequest has no typehint specified\\.$#" - count: 1 - path: src/Server.php - - - - message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$registrations has no typehint specified\\.$#" - count: 1 - path: src/Server.php - - - - message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$signRequests has no typehint specified\\.$#" - count: 1 - path: src/Server.php - - - - message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$trustedCAs has no typehint specified\\.$#" - count: 1 - path: src/Server.php - - - - message: "#^Property Firehed\\\\U2F\\\\Server\\:\\:\\$verifyCA has no typehint specified\\.$#" - count: 1 - path: src/Server.php - - - - message: "#^Unreachable statement \\- code above always terminates\\.$#" - count: 1 - path: src/Server.php - - - - message: "#^Property Firehed\\\\U2F\\\\SignRequest\\:\\:\\$appId has no typehint specified\\.$#" - count: 1 - path: src/SignRequest.php - - - - message: "#^Property Firehed\\\\U2F\\\\SignRequest\\:\\:\\$challenge has no typehint specified\\.$#" - count: 1 - path: src/SignRequest.php - - - - message: "#^Property Firehed\\\\U2F\\\\SignRequest\\:\\:\\$version has no typehint specified\\.$#" - count: 1 - path: src/SignRequest.php - - - - message: "#^Cannot access offset 'counter' on array\\|false\\.$#" - count: 1 - path: src/SignResponse.php - - - - message: "#^Cannot access offset 'presence' on array\\|false\\.$#" - count: 1 - path: src/SignResponse.php - - - - message: "#^Cannot access offset 'signature' on array\\|false\\.$#" - count: 1 - path: src/SignResponse.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponse\\:\\:parseResponse\\(\\) has parameter \\$response with no value type specified in iterable type array\\.$#" - count: 1 - path: src/SignResponse.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponse\\:\\:validateKeyInArray\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" - count: 1 - path: src/SignResponse.php - - - - message: "#^Property Firehed\\\\U2F\\\\SignResponse\\:\\:\\$counter has no typehint specified\\.$#" - count: 1 - path: src/SignResponse.php - - - - message: "#^Property Firehed\\\\U2F\\\\SignResponse\\:\\:\\$user_presence has no typehint specified\\.$#" - count: 1 - path: src/SignResponse.php - - - - message: "#^Method Firehed\\\\U2F\\\\AppIdTraitTest\\:\\:testAccessors\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/AppIdTraitTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\AppIdTraitTest\\:\\:testGetApplicationParameter\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/AppIdTraitTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\AppIdTraitTest\\:\\:testGetRpIdHash\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/AppIdTraitTest.php - - - - message: "#^Property class@anonymous/tests/AppIdTraitTest\\.php\\:20\\:\\:\\$appId has no typehint specified\\.$#" - count: 1 - path: tests/AppIdTraitTest.php - - - - message: "#^Property class@anonymous/tests/AppIdTraitTest\\.php\\:42\\:\\:\\$appId has no typehint specified\\.$#" - count: 1 - path: tests/AppIdTraitTest.php - - - - message: "#^Property class@anonymous/tests/AppIdTraitTest\\.php\\:59\\:\\:\\$appId has no typehint specified\\.$#" - count: 1 - path: tests/AppIdTraitTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\AttestationCertificateTest\\:\\:testConstruct\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/AttestationCertificateTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\AttestationCertificateTest\\:\\:testDebugInfoEncodesBinary\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/AttestationCertificateTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\AttestationCertificateTest\\:\\:testGetBinary\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/AttestationCertificateTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\AttestationCertificateTest\\:\\:testGetPemFormatted\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/AttestationCertificateTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ChallengeTraitTest\\:\\:testAccessors\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ChallengeTraitTest.php - - - - message: "#^Property class@anonymous/tests/ChallengeTraitTest\\.php\\:20\\:\\:\\$challenge has no typehint specified\\.$#" - count: 1 - path: tests/ChallengeTraitTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:missingData\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: tests/ClientDataTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testBadJson\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ClientDataTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testDataValidation\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ClientDataTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testDataValidation\\(\\) has parameter \\$json with no typehint specified\\.$#" - count: 1 - path: tests/ClientDataTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testFromValidJson\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ClientDataTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testGetApplicationParameter\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ClientDataTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testGetChallengeParameter\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ClientDataTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:testTypes\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ClientDataTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientDataTest\\:\\:types\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: tests/ClientDataTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientErrorExceptionTest\\:\\:clientErrors\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ClientErrorExceptionTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ClientErrorExceptionTest\\:\\:testClientError\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ClientErrorExceptionTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testConstruct\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ECPublicKeyTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testConstructThrowsWhenTooLong\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ECPublicKeyTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testConstructThrowsWhenTooShort\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ECPublicKeyTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testConstructThrowsWithBadFirstByte\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ECPublicKeyTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testDebugInfoEncodesBinary\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ECPublicKeyTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testGetBinary\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ECPublicKeyTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ECPublicKeyTest\\:\\:testGetPublicKeyPem\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ECPublicKeyTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testFromBase64Web\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/FunctionsTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testFromBase64Web\\(\\) has parameter \\$encoded with no typehint specified\\.$#" - count: 1 - path: tests/FunctionsTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testFromBase64Web\\(\\) has parameter \\$plain with no typehint specified\\.$#" - count: 1 - path: tests/FunctionsTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testToBase64Web\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/FunctionsTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testToBase64Web\\(\\) has parameter \\$encoded with no typehint specified\\.$#" - count: 1 - path: tests/FunctionsTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:testToBase64Web\\(\\) has parameter \\$plain with no typehint specified\\.$#" + message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:vectors\\(\\) should return array\\ but returns array\\(array\\('', ''\\), array\\('f', 'Zg'\\), array\\('fo', 'Zm8'\\), array\\('foo', 'Zm9v'\\), array\\('foob', 'Zm9vYg'\\), array\\('fooba', 'Zm9vYmE'\\), array\\('foobar', 'Zm9vYmFy'\\), array\\(string\\|false, 'AA_BB\\-cc'\\)\\)\\.$#" count: 1 path: tests/FunctionsTest.php - - message: "#^Method Firehed\\\\U2F\\\\FunctionsTest\\:\\:vectors\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: tests/FunctionsTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\InvalidDataExceptionTest\\:\\:invalidDataExceptionCodes\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/InvalidDataExceptionTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\InvalidDataExceptionTest\\:\\:testInvalidDataException\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/InvalidDataExceptionTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\KeyHandleTraitTest\\:\\:testAccessors\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/KeyHandleTraitTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\KeyHandleTraitTest\\:\\:testGetKeyHandleWeb\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/KeyHandleTraitTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterRequestTest\\:\\:testJsonSerialize\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterRequestTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:buildJson\\(\\) has parameter \\$clientData with no typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:buildJson\\(\\) has parameter \\$registrationData with no typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:clientErrors\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:invalidRegistrationData\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testBadRegistrationData\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testDataAccuracyAfterSuccessfulParsing\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testErrorResponse\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testFromJson\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testFromJsonBadJson\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testFromJsonMissingClientData\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testFromJsonMissingRegistrationData\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testGetChallenge\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testGetRpIdHash\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:testGetSignedData\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Property Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:\\$validClientData has no typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Property Firehed\\\\U2F\\\\RegisterResponseTest\\:\\:\\$validRegistrationData has no typehint specified\\.$#" - count: 1 - path: tests/RegisterResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegistrationTest\\:\\:testAttestationCertificate\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegistrationTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegistrationTest\\:\\:testCounter\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegistrationTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegistrationTest\\:\\:testPublicKey\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegistrationTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\RegistrationTest\\:\\:testSetCounterRejectsNegativeNumbers\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/RegistrationTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:badClientData\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ResponseTraitTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:clientErrors\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ResponseTraitTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:testClientDataValidation\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ResponseTraitTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:testErrorResponse\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ResponseTraitTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:testFromJsonWithNonJson\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ResponseTraitTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:testValidJson\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ResponseTraitTest.php - - - - message: "#^Method class@anonymous/tests/ResponseTraitTest\\.php\\:17\\:\\:parseResponse\\(\\) has parameter \\$response with no value type specified in iterable type array\\.$#" - count: 1 - path: tests/ResponseTraitTest.php - - - - message: "#^Method class@anonymous/tests/ResponseTraitTest\\.php\\:17\\:\\:validateKeyInArray\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" - count: 1 + message: "#^Call to an undefined static method object\\:\\:fromJson\\(\\)\\.$#" + count: 4 path: tests/ResponseTraitTest.php - - message: "#^Property Firehed\\\\U2F\\\\ResponseTraitTest\\:\\:\\$trait has no typehint specified\\.$#" + message: "#^Method class@anonymous/tests/ResponseTraitTest\\.php\\:18\\:\\:parseResponse\\(\\) has parameter \\$response with no value type specified in iterable type array\\.$#" count: 1 path: tests/ResponseTraitTest.php - - - message: "#^Method Firehed\\\\U2F\\\\SecurityExceptionTest\\:\\:securityExceptionCodes\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SecurityExceptionTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SecurityExceptionTest\\:\\:testSecurityException\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SecurityExceptionTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:readJsonFile\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:safeDecode\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:safeEncode\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticate\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfNoRegistrationMatchesKeyHandle\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfNoRegistrationsPresent\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfNoRequestMatchesKeyHandle\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfNoSignRequestsPresent\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfRequestIsSignedWithWrongKey\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsIfSignatureIsInvalid\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsWhenChallengeDoesNotMatch\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsWhenCounterGoesBackwards\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testAuthenticateThrowsWithObviousReplayAttack\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testConstruct\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testDisableCAVerificationReturnsSelf\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testGenerateRegisterRequest\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testGenerateSignRequest\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testGenerateSignRequests\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterDefaultsToTryingEmptyCAList\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsIfChallengeDoesNotMatch\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsIfNoRegistrationRequestProvided\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithBadSignature\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithChangedApplicationParameter\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithChangedChallengeParameter\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithChangedKeyHandle\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithChangedPubkey\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterThrowsWithUntrustedDeviceIssuerCertificate\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegisterWorksWithCAList\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegistration\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegistrationWithoutCidPubkeyBug14Case1\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testRegistrationWithoutCidPubkeyBug14Case2\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testSetRegisterRequestReturnsSelf\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testSetRegistrationsEnforcesTypeCheck\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testSetRegistrationsReturnsSelf\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testSetSignRequestsEnforcesTypeCheck\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\ServerTest\\:\\:testSetSignRequestsReturnsSelf\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Property Firehed\\\\U2F\\\\ServerTest\\:\\:\\$server has no typehint specified\\.$#" - count: 1 - path: tests/ServerTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignRequestTest\\:\\:testJsonSerialize\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignRequestTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:clientErrors\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testDataAccuracyAfterSuccessfulParsing\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testErrorResponse\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testFromJsonWithInvalidSignatureData\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testFromJsonWithMissingClientData\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testFromJsonWithMissingKeyHandle\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testFromJsonWithMissingSignatureData\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testFromJsonWorks\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testGetChallenge\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testGetSignedData\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testSignatureWithNullRemainsIntact\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\SignResponseTest\\:\\:testSignatureWithSpaceRemainsIntact\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Property Firehed\\\\U2F\\\\SignResponseTest\\:\\:\\$valid_client_data has no typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Property Firehed\\\\U2F\\\\SignResponseTest\\:\\:\\$valid_key_handle has no typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Property Firehed\\\\U2F\\\\SignResponseTest\\:\\:\\$valid_signature_data has no typehint specified\\.$#" - count: 1 - path: tests/SignResponseTest.php - - - - message: "#^Method Firehed\\\\U2F\\\\VersionTraitTest\\:\\:testGetVersion\\(\\) has no return typehint specified\\.$#" - count: 1 - path: tests/VersionTraitTest.php - - - - message: "#^Property class@anonymous/tests/VersionTraitTest\\.php\\:19\\:\\:\\$version has no typehint specified\\.$#" - count: 1 - path: tests/VersionTraitTest.php - diff --git a/src/AppIdTrait.php b/src/AppIdTrait.php index eb10a2d..ee38d01 100644 --- a/src/AppIdTrait.php +++ b/src/AppIdTrait.php @@ -5,6 +5,7 @@ trait AppIdTrait { + /** @var string */ private $appId; public function getAppId(): string diff --git a/src/AttestationCertificate.php b/src/AttestationCertificate.php index 42d5e2e..e0f8a29 100644 --- a/src/AttestationCertificate.php +++ b/src/AttestationCertificate.php @@ -27,6 +27,7 @@ public function getPemFormatted(): string return $pem; } + /** @return array{binary: string} */ public function __debugInfo(): array { return ['binary' => '0x' . bin2hex($this->binary)]; diff --git a/src/ChallengeTrait.php b/src/ChallengeTrait.php index 3906634..767cb23 100644 --- a/src/ChallengeTrait.php +++ b/src/ChallengeTrait.php @@ -4,12 +4,13 @@ trait ChallengeTrait { + /** @var string */ private $challenge; // B64-websafe value (at no point is the binary version used) public function getChallenge(): string { - return ($this->challenge); + return $this->challenge; } public function setChallenge(string $challenge): self diff --git a/src/ClientData.php b/src/ClientData.php index 1bdaae6..6034105 100644 --- a/src/ClientData.php +++ b/src/ClientData.php @@ -11,11 +11,17 @@ class ClientData /** @var string */ private $originalJson; + + /** @var string */ private $cid_pubkey; + + /** @var string */ private $origin; + + /** @var string */ private $typ; - public static function fromJson(string $json) + public static function fromJson(string $json): ClientData { $data = json_decode($json, true); if (json_last_error() !== \JSON_ERROR_NONE) { @@ -62,11 +68,11 @@ private function setType(string $type): self * Checks for the presence of $key in $data. Returns the value if found, * throws an InvalidDataException if missing * @param string $key The array key to check - * @param array $data The array to check in - * @return mixed The data, if present + * @param array $data The array to check in + * @return string The data, if present * @throws InvalidDataException if not prsent */ - private function validateKey(string $key, array $data) + private function validateKey(string $key, array $data): string { if (!array_key_exists($key, $data)) { throw new IDE(IDE::MISSING_KEY, $key); diff --git a/src/ECPublicKey.php b/src/ECPublicKey.php index bb7eaf4..2ee1755 100644 --- a/src/ECPublicKey.php +++ b/src/ECPublicKey.php @@ -58,6 +58,7 @@ public function getPemFormatted(): string return $pem; } + /** @return array{x: string, y: string} */ public function __debugInfo(): array { return [ diff --git a/src/KeyHandleInterface.php b/src/KeyHandleInterface.php new file mode 100644 index 0000000..bef8c67 --- /dev/null +++ b/src/KeyHandleInterface.php @@ -0,0 +1,13 @@ +validateKeyInArray('registrationData', $response); diff --git a/src/RegistrationInterface.php b/src/RegistrationInterface.php index fc87ce6..df3557d 100644 --- a/src/RegistrationInterface.php +++ b/src/RegistrationInterface.php @@ -7,7 +7,7 @@ /** * A U2F registration. */ -interface RegistrationInterface +interface RegistrationInterface extends KeyHandleInterface { /** * @return AttestationCertificateInterface The decoded attestation of the U2F token. @@ -19,11 +19,6 @@ public function getAttestationCertificate(): AttestationCertificateInterface; */ public function getCounter(): int; - /** - * @return string The decoded key handle of the U2F registration. - */ - public function getKeyHandleBinary(): string; - /** * @return PublicKeyInterface The public key of the registration. */ diff --git a/src/ResponseTrait.php b/src/ResponseTrait.php index 367054c..ad0f4e4 100644 --- a/src/ResponseTrait.php +++ b/src/ResponseTrait.php @@ -44,6 +44,7 @@ public static function fromJson(string $json): self abstract protected function parseResponse(array $response): self; + /** @param array $data */ private function validateKeyInArray(string $key, array $data): bool { if (!isset($data[$key])) { diff --git a/src/Server.php b/src/Server.php index 293552e..a3274df 100644 --- a/src/Server.php +++ b/src/Server.php @@ -9,7 +9,6 @@ class Server { - use AppIdTrait; /** @@ -22,30 +21,40 @@ class Server * certificates, or b) explicitly disable verifiation. By default, it will * attempt to validate against an empty list which will always fail. This * is by design. + * + * @var string[] */ private $trustedCAs = []; /** * Indicates whether to verify against `$trustedCAs`. Must be explicitly * disabled with `disableCAVerification()`. + * + * @var bool */ private $verifyCA = true; /** * Holds a RegisterRequest used by `register()`, which contains the * challenge in the signature. + * + * @var ?RegisterRequest */ private $registerRequest; /** * Holds Registrations that were previously established by the * authenticating party during `authenticate()` + * + * @var RegistrationInterface[] */ private $registrations = []; /** * Holds SignRequests used by `authenticate` which contain the challenge * that's part of the signed response. + * + * @var SignRequest[] */ private $signRequests = []; @@ -277,7 +286,7 @@ public function setRegisterRequest(RegisterRequest $request): self */ public function setRegistrations(array $registrations): self { - array_map(function (Registration $r) { + array_map(function (RegistrationInterface $r) { }, $registrations); // type check $this->registrations = $registrations; return $this; @@ -343,12 +352,12 @@ public function generateSignRequests(array $registrations): array * key handle value. If one is found, it is returned; if not, this returns * null. * - * TODO: create and implement a HasKeyHandle interface of sorts to type - * this better - * @param array $objects haystack to search + * @template T of KeyHandleInterface + * + * @param T[] $objects haystack to search * @param string $keyHandle key handle to find in haystack - * @return mixed element from haystack - * @return null if no element matches + * + * @return ?T element from haystack if match found, otherwise null */ private function findObjectWithKeyHandle( array $objects, @@ -391,7 +400,7 @@ private function validateRelyingParty(string $rpIdHash): void * @param ChallengeProvider $to user-provided value * @throws SE on failure */ - private function validateChallenge(ChallengeProvider $from, ChallengeProvider $to) + private function validateChallenge(ChallengeProvider $from, ChallengeProvider $to): void { // Note: strictly speaking, this shouldn't even be targetable as // a timing attack. However, this opts to be proactive, and also diff --git a/src/SignRequest.php b/src/SignRequest.php index f0e8a73..26509f7 100644 --- a/src/SignRequest.php +++ b/src/SignRequest.php @@ -4,7 +4,7 @@ use JsonSerializable; -class SignRequest implements JsonSerializable, ChallengeProvider +class SignRequest implements JsonSerializable, ChallengeProvider, KeyHandleInterface { use AppIdTrait; use ChallengeTrait; diff --git a/src/SignResponse.php b/src/SignResponse.php index 1cf3fe2..ecf2a04 100644 --- a/src/SignResponse.php +++ b/src/SignResponse.php @@ -10,7 +10,10 @@ class SignResponse implements LoginResponseInterface use ResponseTrait; // Decoded SignatureData + /** @var int */ private $counter = -1; + + /** @var int */ private $user_presence = 0; public function getCounter(): int @@ -41,6 +44,13 @@ public function getSignedData(): string ); } + /** + * @param array{ + * keyHandle: string, + * clientData: string, + * signatureData: string, + * } $response + */ protected function parseResponse(array $response): self { $this->validateKeyInArray('keyHandle', $response); @@ -56,6 +66,7 @@ protected function parseResponse(array $response): self throw new IDE(IDE::MALFORMED_DATA, 'signatureData'); } $decoded = unpack('cpresence/Ncounter/a*signature', $sig_raw); + assert($decoded !== false); $this->user_presence = $decoded['presence']; $this->counter = $decoded['counter']; $this->setSignature($decoded['signature']); diff --git a/src/VersionTrait.php b/src/VersionTrait.php index d7d4da1..6318686 100644 --- a/src/VersionTrait.php +++ b/src/VersionTrait.php @@ -5,6 +5,7 @@ trait VersionTrait { + /** @var 'U2F_V2' */ private $version = 'U2F_V2'; public function getVersion(): string diff --git a/tests/AppIdTraitTest.php b/tests/AppIdTraitTest.php index 1f5a710..8feaafa 100644 --- a/tests/AppIdTraitTest.php +++ b/tests/AppIdTraitTest.php @@ -15,7 +15,7 @@ class AppIdTraitTest extends \PHPUnit\Framework\TestCase * @covers ::getAppId * @covers ::setAppId */ - public function testAccessors() + public function testAccessors(): void { $obj = new class { use AppIdTrait; @@ -37,7 +37,7 @@ public function testAccessors() /** * @covers ::getApplicationParameter */ - public function testGetApplicationParameter() + public function testGetApplicationParameter(): void { $obj = new class { use AppIdTrait; @@ -54,7 +54,7 @@ public function testGetApplicationParameter() /** * @covers ::getRpIdHash */ - public function testGetRpIdHash() + public function testGetRpIdHash(): void { $obj = new class { use AppIdTrait; diff --git a/tests/AttestationCertificateTest.php b/tests/AttestationCertificateTest.php index 8565c30..7c7b235 100644 --- a/tests/AttestationCertificateTest.php +++ b/tests/AttestationCertificateTest.php @@ -13,7 +13,7 @@ class AttestationCertificateTest extends \PHPUnit\Framework\TestCase /** * @covers ::__construct */ - public function testConstruct() + public function testConstruct(): void { // Note: a future, stricter implementation which actually parses and // examines the ASN.1 format should fail on this. For now it's just @@ -26,7 +26,7 @@ public function testConstruct() /** * @covers ::getBinary */ - public function testGetBinary() + public function testGetBinary(): void { $raw = random_bytes(128); $cert = new AttestationCertificate($raw); @@ -40,7 +40,7 @@ public function testGetBinary() /** * @covers ::getPemFormatted */ - public function testGetPemFormatted() + public function testGetPemFormatted(): void { $raw = random_bytes(128); $cert = new AttestationCertificate($raw); @@ -57,7 +57,7 @@ public function testGetPemFormatted() /** * @covers ::__debugInfo */ - public function testDebugInfoEncodesBinary() + public function testDebugInfoEncodesBinary(): void { $cert = new AttestationCertificate(random_bytes(128)); $debugInfo = $cert->__debugInfo(); diff --git a/tests/ChallengeTraitTest.php b/tests/ChallengeTraitTest.php index 10f438b..a2a8a37 100644 --- a/tests/ChallengeTraitTest.php +++ b/tests/ChallengeTraitTest.php @@ -15,7 +15,7 @@ class ChallengeTraitTest extends \PHPUnit\Framework\TestCase * @covers ::getChallenge * @covers ::setChallenge */ - public function testAccessors() + public function testAccessors(): void { $obj = new class { use ChallengeTrait; diff --git a/tests/ClientDataTest.php b/tests/ClientDataTest.php index 343feda..2ffc3b2 100644 --- a/tests/ClientDataTest.php +++ b/tests/ClientDataTest.php @@ -13,7 +13,7 @@ class ClientDataTest extends \PHPUnit\Framework\TestCase /** * @covers ::fromJson */ - public function testFromValidJson() + public function testFromValidJson(): void { $goodData = [ 'typ' => 'navigator.id.finishEnrollment', @@ -30,7 +30,7 @@ public function testFromValidJson() /** * @covers ::getChallengeParameter */ - public function testGetChallengeParameter() + public function testGetChallengeParameter(): void { $expected_param = base64_decode('exDPjyyKbizXMAAUNLpv0QYJNyXClbUqewUWojPtp0g='); assert($expected_param !== false); @@ -56,7 +56,7 @@ public function testGetChallengeParameter() /** * @covers ::getApplicationParameter */ - public function testGetApplicationParameter() + public function testGetApplicationParameter(): void { $goodData = [ 'typ' => 'navigator.id.finishEnrollment', @@ -76,7 +76,7 @@ public function testGetApplicationParameter() /** * @covers ::fromJson */ - public function testBadJson() + public function testBadJson(): void { $json = 'this is not json'; $this->expectException(InvalidDataException::class); @@ -88,7 +88,7 @@ public function testBadJson() * @covers ::fromJson * @dataProvider missingData */ - public function testDataValidation($json) + public function testDataValidation(string $json): void { $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MISSING_KEY); @@ -98,7 +98,7 @@ public function testDataValidation($json) /** * @dataProvider types */ - public function testTypes(string $type, bool $allowed) + public function testTypes(string $type, bool $allowed): void { $all = [ 'typ' => $type, @@ -119,6 +119,9 @@ public function testTypes(string $type, bool $allowed) // -( DataProviders )------------------------------------------------------ + /** + * @return array{string}[] + */ public function missingData(): array { $all = [ @@ -129,7 +132,7 @@ public function missingData(): array ]; $without = function (string $i) use ($all): array { unset($all[$i]); - return [json_encode($all)]; + return [json_encode($all, JSON_THROW_ON_ERROR)]; }; return [ $without('typ'), @@ -138,6 +141,9 @@ public function missingData(): array ]; } + /** + * @return array{string, bool}[] + */ public function types(): array { return [ diff --git a/tests/ClientErrorExceptionTest.php b/tests/ClientErrorExceptionTest.php index 1d59b72..a32ee94 100644 --- a/tests/ClientErrorExceptionTest.php +++ b/tests/ClientErrorExceptionTest.php @@ -14,7 +14,7 @@ class ClientErrorExceptionTest extends \PHPUnit\Framework\TestCase * @covers ::__construct * @dataProvider clientErrors */ - public function testClientError(int $code) + public function testClientError(int $code): void { $ex = new ClientErrorException($code); $this->assertInstanceOf( @@ -29,6 +29,7 @@ public function testClientError(int $code) } // -( DataProviders )------------------------------------------------------ + /** @return array{int}[] */ public function clientErrors() { return [ diff --git a/tests/ECPublicKeyTest.php b/tests/ECPublicKeyTest.php index 7a58706..99b569d 100644 --- a/tests/ECPublicKeyTest.php +++ b/tests/ECPublicKeyTest.php @@ -13,7 +13,7 @@ class ECPublicKeyTest extends \PHPUnit\Framework\TestCase /** * @covers ::__construct */ - public function testConstruct() + public function testConstruct(): void { $key = "\x04".\random_bytes(64); $obj = new ECPublicKey($key); @@ -23,7 +23,7 @@ public function testConstruct() /** * @covers ::__construct */ - public function testConstructThrowsWithBadFirstByte() + public function testConstructThrowsWithBadFirstByte(): void { $key = "\x01".\random_bytes(64); $this->expectException(InvalidDataException::class); @@ -35,7 +35,7 @@ public function testConstructThrowsWithBadFirstByte() /** * @covers ::__construct */ - public function testConstructThrowsWhenTooShort() + public function testConstructThrowsWhenTooShort(): void { $key = "\x04".random_bytes(63); $this->expectException(InvalidDataException::class); @@ -46,7 +46,7 @@ public function testConstructThrowsWhenTooShort() /** * @covers ::__construct */ - public function testConstructThrowsWhenTooLong() + public function testConstructThrowsWhenTooLong(): void { $key = "\x04".random_bytes(65); $this->expectException(InvalidDataException::class); @@ -57,7 +57,7 @@ public function testConstructThrowsWhenTooLong() /** * @covers ::getBinary */ - public function testGetBinary() + public function testGetBinary(): void { $key = "\x04".\random_bytes(64); $obj = new ECPublicKey($key); @@ -71,7 +71,7 @@ public function testGetBinary() /** * @covers ::getPemFormatted */ - public function testGetPublicKeyPem() + public function testGetPublicKeyPem(): void { $key = hex2bin( '04b4960ae0fa301033fbedc85c33ac30408dffd6098bc8580d8b66159959d89b9'. @@ -91,7 +91,7 @@ public function testGetPublicKeyPem() /** * @covers ::__debugInfo */ - public function testDebugInfoEncodesBinary() + public function testDebugInfoEncodesBinary(): void { $x = random_bytes(32); $y = random_bytes(32); diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index ee9f727..da61992 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -9,7 +9,7 @@ class FunctionsTest extends \PHPUnit\Framework\TestCase * @covers Firehed\U2F\fromBase64Web * @dataProvider vectors */ - public function testFromBase64Web($plain, $encoded) + public function testFromBase64Web(string $plain, string $encoded): void { $this->assertSame( $plain, @@ -22,7 +22,7 @@ public function testFromBase64Web($plain, $encoded) * @covers Firehed\U2F\toBase64Web * @dataProvider vectors */ - public function testToBase64Web($plain, $encoded) + public function testToBase64Web(string $plain, string $encoded): void { $this->assertSame( $encoded, @@ -31,6 +31,7 @@ public function testToBase64Web($plain, $encoded) ); } + /** @return array{string, string}[] */ public function vectors(): array { return [ diff --git a/tests/InvalidDataExceptionTest.php b/tests/InvalidDataExceptionTest.php index bfc4667..012d8fb 100644 --- a/tests/InvalidDataExceptionTest.php +++ b/tests/InvalidDataExceptionTest.php @@ -14,7 +14,7 @@ class InvalidDataExceptionTest extends \PHPUnit\Framework\TestCase * @covers ::__construct * @dataProvider invalidDataExceptionCodes */ - public function testInvalidDataException(int $code) + public function testInvalidDataException(int $code): void { $ex = new InvalidDataException($code, 'Prefill'); $this->assertInstanceOf( @@ -34,6 +34,7 @@ public function testInvalidDataException(int $code) } // -( DataProviders )------------------------------------------------------ + /** @return array{int}[] */ public function invalidDataExceptionCodes() { return [ diff --git a/tests/KeyHandleTraitTest.php b/tests/KeyHandleTraitTest.php index 73c3287..26c1aa9 100644 --- a/tests/KeyHandleTraitTest.php +++ b/tests/KeyHandleTraitTest.php @@ -14,7 +14,7 @@ class KeyHandleTraitTest extends \PHPUnit\Framework\TestCase * @covers ::getKeyHandleBinary * @covers ::setKeyHandle */ - public function testAccessors() + public function testAccessors(): void { $obj = new class { use KeyHandleTrait; @@ -35,7 +35,7 @@ public function testAccessors() /** * @covers ::getKeyHandleWeb */ - public function testGetKeyHandleWeb() + public function testGetKeyHandleWeb(): void { $obj = new class { use KeyHandleTrait; diff --git a/tests/RegisterRequestTest.php b/tests/RegisterRequestTest.php index 7b03da9..54eb2d8 100644 --- a/tests/RegisterRequestTest.php +++ b/tests/RegisterRequestTest.php @@ -14,7 +14,7 @@ class RegisterRequestTest extends \PHPUnit\Framework\TestCase /** * @covers ::jsonSerialize */ - public function testJsonSerialize() + public function testJsonSerialize(): void { $appId = 'https://u2f.example.com'; $challenge = 'some-random-string'; diff --git a/tests/RegisterResponseTest.php b/tests/RegisterResponseTest.php index 310e765..c8d6feb 100644 --- a/tests/RegisterResponseTest.php +++ b/tests/RegisterResponseTest.php @@ -11,11 +11,13 @@ class RegisterResponseTest extends \PHPUnit\Framework\TestCase { + /** @var string */ private $validClientData = 'eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCIsImNoYWxsZW5nZSI6I'. 'kJyQWN4dGIxOWFYNTRoN0Y2T0NKWVptQ3prZHlHV0Nib3NEcHpNMUh2MkUiLCJvcmlnaW'. '4iOiJodHRwczovL3UyZi5lcmljc3Rlcm4uY29tIiwiY2lkX3B1YmtleSI6IiJ9'; + /** @var string */ private $validRegistrationData = 'BQS55FfGvxbgmcNO1cpNhdr4r-CMSbMtuhiMMJbXqd_3FD8Aah2X_n4ZiyBlgBqbbe4Rd'. 'yksR7ZXoqPYT47-tmeWQJhf7xs1T8ObBRpkFi_VWG5oFJe499mQYxcj9BR0G8B5fjkYbU'. @@ -36,7 +38,7 @@ class RegisterResponseTest extends \PHPUnit\Framework\TestCase /** * @covers ::fromJson */ - public function testFromJson() + public function testFromJson(): void { $json = json_encode([ 'registrationData' => $this->validRegistrationData, @@ -53,7 +55,7 @@ public function testFromJson() /** * @dataProvider clientErrors */ - public function testErrorResponse(int $code) + public function testErrorResponse(int $code): void { $json = sprintf('{"errorCode":%d}', $code); $this->expectException(ClientErrorException::class); @@ -61,7 +63,7 @@ public function testErrorResponse(int $code) RegisterResponse::fromJson($json); } - public function testFromJsonBadJson() + public function testFromJsonBadJson(): void { $json = 'this is not json'; $this->expectException(InvalidDataException::class); @@ -69,7 +71,7 @@ public function testFromJsonBadJson() RegisterResponse::fromJson($json); } - public function testFromJsonMissingClientData() + public function testFromJsonMissingClientData(): void { $json = sprintf('{"registrationData":"%s"}', $this->validRegistrationData); $this->expectException(InvalidDataException::class); @@ -78,7 +80,7 @@ public function testFromJsonMissingClientData() RegisterResponse::fromJson($json); } - public function testFromJsonMissingRegistrationData() + public function testFromJsonMissingRegistrationData(): void { $json = sprintf('{"clientData":"%s"}', $this->validClientData); $this->expectException(InvalidDataException::class); @@ -90,7 +92,7 @@ public function testFromJsonMissingRegistrationData() /** * @dataProvider invalidRegistrationData */ - public function testBadRegistrationData(string $registrationData) + public function testBadRegistrationData(string $registrationData): void { $json = $this->buildJson($this->validClientData, $registrationData); $this->expectException(InvalidDataException::class); @@ -104,7 +106,7 @@ public function testBadRegistrationData(string $registrationData) * @covers ::getPublicKey * @covers ::getSignature */ - public function testDataAccuracyAfterSuccessfulParsing() + public function testDataAccuracyAfterSuccessfulParsing(): void { $pubkey = "\x04".random_bytes(64); $handle = random_bytes(32); @@ -141,7 +143,7 @@ public function testDataAccuracyAfterSuccessfulParsing() /** * @covers ::getSignedData */ - public function testGetSignedData() + public function testGetSignedData(): void { $json = file_get_contents(__DIR__ . '/register_response.json'); assert($json !== false); @@ -175,7 +177,7 @@ public function testGetSignedData() /** * @covers ::getChallenge */ - public function testGetChallenge() + public function testGetChallenge(): void { $json = file_get_contents(__DIR__ . '/register_response.json'); assert($json !== false); @@ -189,7 +191,7 @@ public function testGetChallenge() /** * @covers ::getRpIdHash */ - public function testGetRpIdHash() + public function testGetRpIdHash(): void { $json = file_get_contents(__DIR__ . '/register_response.json'); assert($json !== false); @@ -203,6 +205,7 @@ public function testGetRpIdHash() // -( DataProviders )------------------------------------------------------ + /** @return array{int}[] */ public function clientErrors() { return [ @@ -214,6 +217,7 @@ public function clientErrors() ]; } + /** @return array{string}[] */ public function invalidRegistrationData(): array { $bad_reserved_byte = "\x01".str_repeat('a', 200); @@ -242,7 +246,7 @@ public function invalidRegistrationData(): array // -( Helpers )------------------------------------------------------------ - protected function buildJson($clientData, $registrationData): string + protected function buildJson(string $clientData, string $registrationData): string { return sprintf( '{"clientData":"%s","registrationData":"%s"}', diff --git a/tests/RegistrationTest.php b/tests/RegistrationTest.php index c9ec9c1..9daed90 100644 --- a/tests/RegistrationTest.php +++ b/tests/RegistrationTest.php @@ -15,7 +15,7 @@ class RegistrationTest extends \PHPUnit\Framework\TestCase * @covers ::setCounter * @covers ::getCounter */ - public function testCounter() + public function testCounter(): void { $obj = new Registration(); $this->assertSame( @@ -33,7 +33,7 @@ public function testCounter() /** * @covers ::setCounter */ - public function testSetCounterRejectsNegativeNumbers() + public function testSetCounterRejectsNegativeNumbers(): void { $obj = new Registration(); $this->expectException(\OutOfBoundsException::class); @@ -44,7 +44,7 @@ public function testSetCounterRejectsNegativeNumbers() * @covers ::getPublicKey * @covers ::setPublicKey */ - public function testPublicKey() + public function testPublicKey(): void { $pk = $this->createMock(PublicKeyInterface::class); $reg = new Registration(); @@ -56,7 +56,7 @@ public function testPublicKey() * @covers ::getAttestationCertificate * @covers ::setAttestationCertificate */ - public function testAttestationCertificate() + public function testAttestationCertificate(): void { $pk = $this->createMock(AttestationCertificateInterface::class); $reg = new Registration(); diff --git a/tests/ResponseTraitTest.php b/tests/ResponseTraitTest.php index aedc385..903b32d 100644 --- a/tests/ResponseTraitTest.php +++ b/tests/ResponseTraitTest.php @@ -10,6 +10,7 @@ */ class ResponseTraitTest extends \PHPUnit\Framework\TestCase { + /** @var object */ private $trait; public function setUp(): void @@ -28,7 +29,7 @@ protected function parseResponse(array $response): self * @covers ::fromJson * @covers ::getSignature */ - public function testValidJson() + public function testValidJson(): void { $signature = __METHOD__; $json = json_encode([ @@ -59,7 +60,7 @@ public function testValidJson() /** * @covers ::fromJson */ - public function testFromJsonWithNonJson() + public function testFromJsonWithNonJson(): void { $this->expectException(InvalidDataException::class); $this->expectExceptionCode(InvalidDataException::MALFORMED_DATA); @@ -70,7 +71,7 @@ public function testFromJsonWithNonJson() * @covers ::fromJson * @dataProvider clientErrors */ - public function testErrorResponse(int $code) + public function testErrorResponse(int $code): void { $json = sprintf('{"errorCode":%d}', $code); $this->expectException(ClientErrorException::class); @@ -82,7 +83,7 @@ public function testErrorResponse(int $code) * @covers ::fromJson * @dataProvider badClientData */ - public function testClientDataValidation(string $json, int $code) + public function testClientDataValidation(string $json, int $code): void { $this->expectException(InvalidDataException::class); $this->expectExceptionCode($code); @@ -91,7 +92,8 @@ public function testClientDataValidation(string $json, int $code) // -( DataProviders )------------------------------------------------------ - public function clientErrors() + /** @return array{int}[] */ + public function clientErrors(): array { return [ [ClientError::OTHER_ERROR], @@ -102,7 +104,8 @@ public function clientErrors() ]; } - public function badClientData() + /** @return array{string, int}[] */ + public function badClientData(): array { return [ ['{}', InvalidDataException::MISSING_KEY], diff --git a/tests/SecurityExceptionTest.php b/tests/SecurityExceptionTest.php index 36aabc4..6e551a8 100644 --- a/tests/SecurityExceptionTest.php +++ b/tests/SecurityExceptionTest.php @@ -14,7 +14,7 @@ class SecurityExceptionTest extends \PHPUnit\Framework\TestCase * @covers ::__construct * @dataProvider securityExceptionCodes */ - public function testSecurityException(int $code) + public function testSecurityException(int $code): void { $ex = new SecurityException($code); $this->assertInstanceOf( @@ -29,6 +29,7 @@ public function testSecurityException(int $code) } // -( DataProviders )------------------------------------------------------ + /** @return array{int}[] */ public function securityExceptionCodes() { return [ diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 4fbbf5f..4185cdc 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -22,6 +22,7 @@ class ServerTest extends \PHPUnit\Framework\TestCase 'BEyIn4ldTViNAgceMA/YgRX1DlJR3bSF39drG44Fx1E2LaF9Md9RUN2CHyfzSokIjjCHP'. '8jMsTYwdt0tKe6qLzc='; + /** @var Server */ private $server; public function setUp(): void @@ -34,7 +35,7 @@ public function setUp(): void /** * @covers ::__construct */ - public function testConstruct() + public function testConstruct(): void { $server = new Server(); $this->assertInstanceOf(Server::class, $server); @@ -43,7 +44,7 @@ public function testConstruct() /** * @covers ::disableCAVerification */ - public function testDisableCAVerificationReturnsSelf() + public function testDisableCAVerificationReturnsSelf(): void { $server = new Server(); $this->assertSame( @@ -56,7 +57,7 @@ public function testDisableCAVerificationReturnsSelf() /** * @covers ::generateRegisterRequest */ - public function testGenerateRegisterRequest() + public function testGenerateRegisterRequest(): void { $req = $this->server->generateRegisterRequest(); $this->assertInstanceOf(RegisterRequest::class, $req); @@ -78,7 +79,7 @@ public function testGenerateRegisterRequest() /** * @covers ::generateSignRequest */ - public function testGenerateSignRequest() + public function testGenerateSignRequest(): void { $kh = \random_bytes(16); $registration = (new Registration()) @@ -109,7 +110,7 @@ public function testGenerateSignRequest() /** * @covers ::generateSignRequests */ - public function testGenerateSignRequests() + public function testGenerateSignRequests(): void { $registrations = [ (new Registration())->setKeyHandle(\random_bytes(16)), @@ -128,7 +129,7 @@ public function testGenerateSignRequests() /** * @covers ::setRegisterRequest */ - public function testSetRegisterRequestReturnsSelf() + public function testSetRegisterRequestReturnsSelf(): void { $req = $this->getDefaultRegisterRequest(); $this->assertSame( @@ -141,7 +142,7 @@ public function testSetRegisterRequestReturnsSelf() /** * @covers ::setRegistrations */ - public function testSetRegistrationsReturnsSelf() + public function testSetRegistrationsReturnsSelf(): void { $reg = $this->getDefaultRegistration(); $this->assertSame( @@ -154,17 +155,18 @@ public function testSetRegistrationsReturnsSelf() /** * @covers ::setRegistrations */ - public function testSetRegistrationsEnforcesTypeCheck() + public function testSetRegistrationsEnforcesTypeCheck(): void { $wrong = true; $this->expectException(TypeError::class); + // @phpstan-ignore-next-line $this->server->setRegistrations([$wrong]); } /** * @covers ::setSignRequests */ - public function testSetSignRequestsReturnsSelf() + public function testSetSignRequestsReturnsSelf(): void { $req = $this->getDefaultSignRequest(); $this->assertSame( @@ -177,10 +179,11 @@ public function testSetSignRequestsReturnsSelf() /** * @covers ::setSignRequests */ - public function testSetSignRequestsEnforcesTypeCheck() + public function testSetSignRequestsEnforcesTypeCheck(): void { $wrong = true; $this->expectException(TypeError::class); + // @phpstan-ignore-next-line $this->server->setSignRequests([$wrong]); } @@ -189,7 +192,7 @@ public function testSetSignRequestsEnforcesTypeCheck() /** * @covers ::register */ - public function testRegisterThrowsIfNoRegistrationRequestProvided() + public function testRegisterThrowsIfNoRegistrationRequestProvided(): void { $this->expectException(BadMethodCallException::class); $this->server->register($this->getDefaultRegisterResponse()); @@ -198,7 +201,7 @@ public function testRegisterThrowsIfNoRegistrationRequestProvided() /** * @covers ::register */ - public function testRegistration() + public function testRegistration(): void { $request = $this->getDefaultRegisterRequest(); $response = $this->getDefaultRegisterResponse(); @@ -239,7 +242,7 @@ public function testRegistration() /** * @covers ::register */ - public function testRegisterDefaultsToTryingEmptyCAList() + public function testRegisterDefaultsToTryingEmptyCAList(): void { $request = $this->getDefaultRegisterRequest(); $response = $this->getDefaultRegisterResponse(); @@ -258,7 +261,7 @@ public function testRegisterDefaultsToTryingEmptyCAList() /** * @covers ::register */ - public function testRegisterThrowsIfChallengeDoesNotMatch() + public function testRegisterThrowsIfChallengeDoesNotMatch(): void { // This would have come from a session, database, etc. $request = (new RegisterRequest()) @@ -276,7 +279,7 @@ public function testRegisterThrowsIfChallengeDoesNotMatch() /** * @covers ::register */ - public function testRegisterThrowsWithUntrustedDeviceIssuerCertificate() + public function testRegisterThrowsWithUntrustedDeviceIssuerCertificate(): void { $request = $this->getDefaultRegisterRequest(); $response = $this->getDefaultRegisterResponse(); @@ -297,7 +300,7 @@ public function testRegisterThrowsWithUntrustedDeviceIssuerCertificate() * @covers ::register * @covers ::setTrustedCAs */ - public function testRegisterWorksWithCAList() + public function testRegisterWorksWithCAList(): void { $request = $this->getDefaultRegisterRequest(); $response = $this->getDefaultRegisterResponse(); @@ -306,6 +309,7 @@ public function testRegisterWorksWithCAList() // generated with a YubiCo device and separately tested against // a different reference implementation. $CAs = glob(dirname(__DIR__).'/CAcerts/*.pem'); + assert($CAs !== false); $this->server->setTrustedCAs($CAs); try { @@ -324,7 +328,7 @@ public function testRegisterWorksWithCAList() /** * @covers ::register */ - public function testRegisterThrowsWithChangedApplicationParameter() + public function testRegisterThrowsWithChangedApplicationParameter(): void { $request = $this->getDefaultRegisterRequest(); @@ -344,7 +348,7 @@ public function testRegisterThrowsWithChangedApplicationParameter() /** * @covers ::register */ - public function testRegisterThrowsWithChangedChallengeParameter() + public function testRegisterThrowsWithChangedChallengeParameter(): void { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: challenge parameter @@ -366,7 +370,7 @@ public function testRegisterThrowsWithChangedChallengeParameter() /** * @covers ::register */ - public function testRegisterThrowsWithChangedKeyHandle() + public function testRegisterThrowsWithChangedKeyHandle(): void { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: key handle @@ -386,7 +390,7 @@ public function testRegisterThrowsWithChangedKeyHandle() /** * @covers ::register */ - public function testRegisterThrowsWithChangedPubkey() + public function testRegisterThrowsWithChangedPubkey(): void { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: public key @@ -406,7 +410,7 @@ public function testRegisterThrowsWithChangedPubkey() /** * @covers ::register */ - public function testRegisterThrowsWithBadSignature() + public function testRegisterThrowsWithBadSignature(): void { $request = $this->getDefaultRegisterRequest(); // Mess up some known-good data: signature @@ -428,7 +432,7 @@ public function testRegisterThrowsWithBadSignature() /** * @covers ::authenticate */ - public function testAuthenticateThrowsIfNoRegistrationsPresent() + public function testAuthenticateThrowsIfNoRegistrationsPresent(): void { $this->server->setSignRequests([$this->getDefaultSignRequest()]); $this->expectException(BadMethodCallException::class); @@ -438,7 +442,7 @@ public function testAuthenticateThrowsIfNoRegistrationsPresent() /** * @covers ::authenticate */ - public function testAuthenticateThrowsIfNoSignRequestsPresent() + public function testAuthenticateThrowsIfNoSignRequestsPresent(): void { $this->server->setRegistrations([$this->getDefaultRegistration()]); $this->expectException(BadMethodCallException::class); @@ -448,7 +452,7 @@ public function testAuthenticateThrowsIfNoSignRequestsPresent() /** * @covers ::authenticate */ - public function testAuthenticate() + public function testAuthenticate(): void { // All normal $registration = $this->getDefaultRegistration(); @@ -484,7 +488,7 @@ public function testAuthenticate() * * @covers ::authenticate */ - public function testAuthenticateThrowsWithObviousReplayAttack() + public function testAuthenticateThrowsWithObviousReplayAttack(): void { // All normal $registration = $this->getDefaultRegistration(); @@ -512,7 +516,7 @@ public function testAuthenticateThrowsWithObviousReplayAttack() /** * @covers ::authenticate */ - public function testAuthenticateThrowsWhenCounterGoesBackwards() + public function testAuthenticateThrowsWhenCounterGoesBackwards(): void { // Counter from "DB" bumped, suggesting response was cloned $registration = (new Registration()) @@ -534,7 +538,7 @@ public function testAuthenticateThrowsWhenCounterGoesBackwards() /** * @covers ::authenticate */ - public function testAuthenticateThrowsWhenChallengeDoesNotMatch() + public function testAuthenticateThrowsWhenChallengeDoesNotMatch(): void { $registration = $this->getDefaultRegistration(); // Change request challenge @@ -556,7 +560,7 @@ public function testAuthenticateThrowsWhenChallengeDoesNotMatch() /** * @covers ::authenticate */ - public function testAuthenticateThrowsIfNoRegistrationMatchesKeyHandle() + public function testAuthenticateThrowsIfNoRegistrationMatchesKeyHandle(): void { // Change registration KH $registration = (new Registration()) @@ -578,7 +582,7 @@ public function testAuthenticateThrowsIfNoRegistrationMatchesKeyHandle() /** * @covers ::authenticate */ - public function testAuthenticateThrowsIfNoRequestMatchesKeyHandle() + public function testAuthenticateThrowsIfNoRequestMatchesKeyHandle(): void { $registration = $this->getDefaultRegistration(); // Change request KH @@ -600,7 +604,7 @@ public function testAuthenticateThrowsIfNoRequestMatchesKeyHandle() /** * @covers ::authenticate */ - public function testAuthenticateThrowsIfSignatureIsInvalid() + public function testAuthenticateThrowsIfSignatureIsInvalid(): void { $registration = $this->getDefaultRegistration(); $request = $this->getDefaultSignRequest(); @@ -624,7 +628,7 @@ public function testAuthenticateThrowsIfSignatureIsInvalid() * * @covers ::authenticate */ - public function testAuthenticateThrowsIfRequestIsSignedWithWrongKey() + public function testAuthenticateThrowsIfRequestIsSignedWithWrongKey(): void { $pk = base64_decode( 'BCXk9bGiuzLRJaX6pFONm+twgIrDkOSNDdXgltt+KhOD'. @@ -653,7 +657,7 @@ public function testAuthenticateThrowsIfRequestIsSignedWithWrongKey() // -( Alternate formats (see #14) )---------------------------------------- - public function testRegistrationWithoutCidPubkeyBug14Case1() + public function testRegistrationWithoutCidPubkeyBug14Case1(): void { $server = (new Server()) ->disableCAVerification() @@ -685,7 +689,7 @@ public function testRegistrationWithoutCidPubkeyBug14Case1() $this->assertInstanceOf(Registration::class, $registration); } - public function testRegistrationWithoutCidPubkeyBug14Case2() + public function testRegistrationWithoutCidPubkeyBug14Case2(): void { $server = (new Server()) ->disableCAVerification() @@ -792,6 +796,7 @@ public function getDefaultPublicKey(): PublicKeyInterface return new ECPublicKey($pk); } + /** @return mixed[] */ private function readJsonFile(string $file): array { return $this->safeDecode($this->safeReadFile($file)); @@ -804,6 +809,7 @@ private function safeReadFile(string $file): string return $body; } + /** @return mixed[] */ private function safeDecode(string $json): array { $data = json_decode($json, true); @@ -811,6 +817,7 @@ private function safeDecode(string $json): array return $data; } + /** @param mixed[] $data */ private function safeEncode(array $data): string { $json = json_encode($data); diff --git a/tests/SignRequestTest.php b/tests/SignRequestTest.php index dfcbd49..c2997c2 100644 --- a/tests/SignRequestTest.php +++ b/tests/SignRequestTest.php @@ -13,7 +13,7 @@ class SignRequestTest extends \PHPUnit\Framework\TestCase /** * @covers ::jsonSerialize */ - public function testJsonSerialize() + public function testJsonSerialize(): void { $appId = 'https://u2f.example.com'; $challenge = 'some-random-string'; diff --git a/tests/SignResponseTest.php b/tests/SignResponseTest.php index 5d0f7d4..58f0839 100644 --- a/tests/SignResponseTest.php +++ b/tests/SignResponseTest.php @@ -13,15 +13,18 @@ class SignResponseTest extends \PHPUnit\Framework\TestCase const JSON_FORMAT = '{"keyHandle":"%s","clientData":"%s","signatureData":"%s"}'; + /** @var string */ private $valid_key_handle = 'JUnVTStPn-V2-bCu0RlvPbukBpHTD5Mi1ZGglDOcN0vD45rnTD0BXdkRt78huTwJ7tVax'. 'TqSetHjr22tCjmYLQ'; + /** @var string */ private $valid_client_data = 'eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoid3Qye'. 'mU4SXNrY1RPM25Jc08yRDJoRmpFNXRWRDA0MU5wblllc0xwSndlZyIsIm9yaWdpbiI6Im'. 'h0dHBzOi8vdTJmLmVyaWNzdGVybi5jb20iLCJjaWRfcHVia2V5IjoiIn0'; + /** @var string */ private $valid_signature_data = 'AQAAAC0wRgIhAJPy1RvD1WCw1XZX53BXydX_Kyf_XZQueFSIPigRF-D2AiEAx3bJr5ixr'. 'XGdUX1XooAfhz15ZIY8rC5H4qaW7gQspJ4'; @@ -29,7 +32,7 @@ class SignResponseTest extends \PHPUnit\Framework\TestCase /** * @covers ::fromJson */ - public function testFromJsonWorks() + public function testFromJsonWorks(): void { $json = sprintf( self::JSON_FORMAT, @@ -46,7 +49,7 @@ public function testFromJsonWorks() * @covers ::getSignature * @covers ::getUserPresenceByte */ - public function testDataAccuracyAfterSuccessfulParsing() + public function testDataAccuracyAfterSuccessfulParsing(): void { $sig = random_bytes(16); $counter = random_int(0, pow(2, 32)); @@ -94,7 +97,7 @@ public function testDataAccuracyAfterSuccessfulParsing() ); } - public function testSignatureWithNullRemainsIntact() + public function testSignatureWithNullRemainsIntact(): void { $sig = "\x00\x00\x00".random_bytes(10)."\x00\x00\x00"; $sigData = toBase64Web("\x01\x00\x00\x00\x45".$sig); @@ -112,7 +115,7 @@ public function testSignatureWithNullRemainsIntact() ); } - public function testSignatureWithSpaceRemainsIntact() + public function testSignatureWithSpaceRemainsIntact(): void { $sig = ' '.random_bytes(10).' '; $sigData = toBase64Web("\x01\x00\x00\x00\x45".$sig); @@ -131,7 +134,7 @@ public function testSignatureWithSpaceRemainsIntact() } - public function testFromJsonWithMissingKeyHandle() + public function testFromJsonWithMissingKeyHandle(): void { $json = sprintf( '{"clientData":"%s","signatureData":"%s"}', @@ -143,7 +146,7 @@ public function testFromJsonWithMissingKeyHandle() SignResponse::fromJson($json); } - public function testFromJsonWithMissingClientData() + public function testFromJsonWithMissingClientData(): void { $json = sprintf( '{"keyHandle":"%s","signatureData":"%s"}', @@ -155,7 +158,7 @@ public function testFromJsonWithMissingClientData() SignResponse::fromJson($json); } - public function testFromJsonWithMissingSignatureData() + public function testFromJsonWithMissingSignatureData(): void { $json = sprintf( '{"keyHandle":"%s","clientData":"%s"}', @@ -167,7 +170,7 @@ public function testFromJsonWithMissingSignatureData() SignResponse::fromJson($json); } - public function testFromJsonWithInvalidSignatureData() + public function testFromJsonWithInvalidSignatureData(): void { $json = sprintf( '{"keyHandle":"%s","clientData":"%s","signatureData":"%s"}', @@ -183,7 +186,7 @@ public function testFromJsonWithInvalidSignatureData() /** * @covers ::getSignedData */ - public function testGetSignedData() + public function testGetSignedData(): void { $json = file_get_contents(__DIR__ . '/sign_response.json'); assert($json !== false); @@ -216,7 +219,7 @@ public function testGetSignedData() /** * @covers ::getChallenge */ - public function testGetChallenge() + public function testGetChallenge(): void { $json = file_get_contents(__DIR__ . '/sign_response.json'); assert($json !== false); @@ -231,7 +234,7 @@ public function testGetChallenge() /** * @dataProvider clientErrors */ - public function testErrorResponse(int $code) + public function testErrorResponse(int $code): void { $json = sprintf('{"errorCode":%d}', $code); $this->expectException(ClientErrorException::class); @@ -239,6 +242,7 @@ public function testErrorResponse(int $code) SignResponse::fromJson($json); } + /** @return array{int}[] */ public function clientErrors() { return [ diff --git a/tests/VersionTraitTest.php b/tests/VersionTraitTest.php index dbeed1b..03ba974 100644 --- a/tests/VersionTraitTest.php +++ b/tests/VersionTraitTest.php @@ -14,7 +14,7 @@ class VersionTraitTest extends \PHPUnit\Framework\TestCase /** * @covers ::getVersion */ - public function testGetVersion() + public function testGetVersion(): void { $obj = new class { use VersionTrait; From 5c3ad2ab6db105543c6d2ffc249aa855635128e5 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 25 Oct 2021 17:00:34 -0400 Subject: [PATCH 16/17] Tidy up registration class (#26) --- src/Registration.php | 30 ++++++++++++++++++++++++++---- tests/RegistrationTest.php | 17 +++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/Registration.php b/src/Registration.php index b39a81e..4eaca59 100644 --- a/src/Registration.php +++ b/src/Registration.php @@ -16,7 +16,7 @@ class Registration implements RegistrationInterface private $counter = -1; /** @var PublicKeyInterface */ - private $pubKey; + private $publicKey; public function getAttestationCertificate(): AttestationCertificateInterface { @@ -45,12 +45,34 @@ public function setCounter(int $counter): self public function getPublicKey(): PublicKeyInterface { - return $this->pubKey; + return $this->publicKey; } - public function setPublicKey(PublicKeyInterface $pubKey): self + public function setPublicKey(PublicKeyInterface $publicKey): self { - $this->pubKey = $pubKey; + $this->publicKey = $publicKey; return $this; } + + /** + * @return array{ + * cert: AttestationCertificateInterface, + * counter: int, + * publicKey: PublicKeyInterface, + * keyHandle: string, + * } + */ + public function __debugInfo(): array + { + $hex = function (string $binary): string { + return '0x' . bin2hex($binary); + }; + + return [ + 'cert' => $this->cert, + 'counter' => $this->counter, + 'publicKey' => $this->publicKey, + 'keyHandle' => $hex($this->keyHandle), + ]; + } } diff --git a/tests/RegistrationTest.php b/tests/RegistrationTest.php index 9daed90..63eaf52 100644 --- a/tests/RegistrationTest.php +++ b/tests/RegistrationTest.php @@ -63,4 +63,21 @@ public function testAttestationCertificate(): void $reg->setAttestationCertificate($pk); $this->assertSame($pk, $reg->getAttestationCertificate()); } + + /** + * @covers ::__debugInfo + */ + public function testDebugInfoEncodesBinary(): void + { + $reg = new Registration(); + $reg->setAttestationCertificate($this->createMock(AttestationCertificateInterface::class)); + $reg->setPublicKey($this->createMock(PublicKeyInterface::class)); + $kh = random_bytes(20); + $reg->setKeyHandle($kh); + $reg->setCounter(50); + + $debugInfo = $reg->__debugInfo(); + $this->assertNotSame($kh, $debugInfo['keyHandle']); + $this->assertRegExp('/^0x[0-9a-f]{40}$/', $debugInfo['keyHandle']); + } } From 31535c615c588594f06b7afe19c4c9c57dca3622 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Mon, 25 Oct 2021 19:10:47 -0400 Subject: [PATCH 17/17] Remove outdated dev-dependencies (#27) --- .arcconfig | 6 ------ README.md | 4 +--- composer.json | 2 -- 3 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 .arcconfig diff --git a/.arcconfig b/.arcconfig deleted file mode 100644 index ba871f4..0000000 --- a/.arcconfig +++ /dev/null @@ -1,6 +0,0 @@ -{ - "unit.engine": "Firehed\\Arctools\\Unit\\PHPUnitTestEngine", - "load": [ - "vendor/firehed/arctools/src" - ] -} diff --git a/README.md b/README.md index 2473531..781206d 100644 --- a/README.md +++ b/README.md @@ -161,9 +161,7 @@ See its README for more information. ## Tests -If you are using Arcanist, `arc unit` workflows will work as expected. - -Otherwise, all tests are in the `tests/` directory and can be run with `vendor/bin/phpunit tests/`. +All tests are in the `tests/` directory and can be run with `vendor/bin/phpunit`. ## License diff --git a/composer.json b/composer.json index 0b6f555..4c0da2f 100644 --- a/composer.json +++ b/composer.json @@ -16,11 +16,9 @@ "php": ">=7.2" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.0", "phpstan/phpstan": "^0.12", "phpunit/phpunit": "^8.5", "squizlabs/php_codesniffer": "^3.2", - "spatie/phpunit-watcher": "^1.8", "phpstan/phpstan-phpunit": "^0.12" }, "autoload": {