Skip to content

Commit

Permalink
Merge pull request #97 from creative-commoners/pulls/4/php8
Browse files Browse the repository at this point in the history
DEP PHP 8 support
  • Loading branch information
Maxime Rainville authored Nov 9, 2021
2 parents 156e823 + 14b1b59 commit 780c98e
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 29 deletions.
2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions client/src/lib/convert.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
export const base64ToByteArray = string =>
Uint8Array.from(atob(string), c => c.charCodeAt(0));
export const base64ToByteArray = string => {
// String replace because server with encode with 'web safe' base64_encode
const b = atob(string.replace(/_/g, '/').replace(/-/g, '+'));
return Uint8Array.from(b, c => c.charCodeAt(0));
};

export const byteArrayToBase64 = byteArray =>
btoa(String.fromCharCode(...(new Uint8Array(byteArray))));
export const byteArrayToBase64 = byteArray => {
// We specifically do not want to make the 'web safe' string replacements above
// doing so will break this functionality
const uarr = new Uint8Array(byteArray);
return btoa(String.fromCharCode(...uarr));
};
28 changes: 28 additions & 0 deletions client/src/lib/tests/convert-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* global jest, describe, it, expect, window */

import { base64ToByteArray, byteArrayToBase64 } from '../convert';

describe('convert', () => {
it('converts base64ToByteArray', () => {
const string = 'abc/def+ghi';
const expected = new Uint8Array([105, 183, 63, 117, 231, 254, 130, 24]);
const actual = base64ToByteArray(string);
expect(expected).toEqual(actual);
});

it('converts base64ToByteArray when encoded web-safe', () => {
const string = 'abc_def-ghi';
const expected = new Uint8Array([105, 183, 63, 117, 231, 254, 130, 24]);
const actual = base64ToByteArray(string);
expect(expected).toEqual(actual);
});

it('converts byteArrayToBase64', () => {
const byteArray = [105, 183, 63, 117, 231, 254, 130, 24];
// this is supposed to be slightly different from string
// used in base64ToByteArray tests
const expected = 'abc/def+ghg=';
const actual = byteArrayToBase64(byteArray);
expect(expected).toEqual(actual);
});
});
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
"license": "BSD-3-Clause",
"require": {
"php": "^7.3 || ^8.0",
"ext-bcmath": "*",
"silverstripe/mfa": "^4.0",
"web-auth/webauthn-lib": "^1.0",
"web-auth/webauthn-lib": "^3.3",
"guzzlehttp/psr7": "^1.6"
},
"require-dev": {
Expand Down
8 changes: 6 additions & 2 deletions src/BaseHandlerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ trait BaseHandlerTrait
{
/**
* @return Decoder
*
* @deprecated will be removed in 5.0 - as of v3 of webauthn-lib the Decoder is now created within both:
* AttestationObjectLoader::__construct()
* AuthenticatorAssertionResponseValidator::__construct()
*/
protected function getDecoder(): Decoder
{
Expand Down Expand Up @@ -48,7 +52,7 @@ protected function getAttestationObjectLoader(
AttestationStatementSupportManager $attestationStatementSupportManager,
Decoder $decoder
): AttestationObjectLoader {
return new AttestationObjectLoader($attestationStatementSupportManager, $decoder);
return new AttestationObjectLoader($attestationStatementSupportManager);
}

/**
Expand All @@ -60,6 +64,6 @@ protected function getPublicKeyCredentialLoader(
AttestationObjectLoader $attestationObjectLoader,
Decoder $decoder
): PublicKeyCredentialLoader {
return new PublicKeyCredentialLoader($attestationObjectLoader, $decoder);
return new PublicKeyCredentialLoader($attestationObjectLoader);
}
}
19 changes: 7 additions & 12 deletions src/RegisterHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public function register(HTTPRequest $request, StoreInterface $store): Result
{
$options = $this->getCredentialCreationOptions($store);
$data = json_decode((string) $request->getBody(), true);
$publicKeyCredentialSource = null;

try {
if (empty($data['credentials'])) {
Expand All @@ -134,7 +135,8 @@ public function register(HTTPRequest $request, StoreInterface $store): Result
$psrRequest = ServerRequest::fromGlobals();

// Validate the webauthn response
$this->getAuthenticatorAttestationResponseValidator($attestationStatementSupportManager, $store)
$publicKeyCredentialSource = $this
->getAuthenticatorAttestationResponseValidator($attestationStatementSupportManager, $store)
->check($response, $options, $psrRequest);
} catch (Exception $e) {
$this->logger->error($e->getMessage());
Expand All @@ -143,17 +145,12 @@ public function register(HTTPRequest $request, StoreInterface $store): Result

$credentialRepository = $this->getCredentialRepository($store);

$source = PublicKeyCredentialSource::createFromPublicKeyCredential(
$publicKeyCredential,
$options->getUser()->getId()
);

// Clear the repository so only one key is registered at a time
// NOTE: This can be considered temporary behaviour until the UI supports managing multiple keys
$credentialRepository->reset();

// Persist the "credential source"
$credentialRepository->saveCredentialSource($source);
$credentialRepository->saveCredentialSource($publicKeyCredentialSource);

return Result::create()->setContext($credentialRepository->toArray());
}
Expand Down Expand Up @@ -259,12 +256,10 @@ protected function getCredentialCreationOptions(
$this->getUserEntity($store->getMember()),
random_bytes(32),
[new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_ES256)],
40000,
[],
$this->getAuthenticatorSelectionCriteria(),
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
new AuthenticationExtensionsClientInputs()
40000
);
$credentialOptions->setAuthenticatorSelection($this->getAuthenticatorSelectionCriteria());
$credentialOptions->setAttestation(PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE);

$store->setState(['credentialOptions' => $credentialOptions] + $state);

Expand Down
18 changes: 9 additions & 9 deletions src/VerifyHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace SilverStripe\WebAuthn;

use CBOR\Decoder;
use Cose\Algorithm\Manager;
use Exception;
use GuzzleHttp\Psr7\ServerRequest;
use InvalidArgumentException;
Expand All @@ -22,6 +23,7 @@
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialSource;
use Webauthn\TokenBinding\TokenBindingNotSupportedHandler;
use Cose\Algorithm\Signature\ECDSA\ES256;

class VerifyHandler implements VerifyHandlerInterface
{
Expand Down Expand Up @@ -160,13 +162,9 @@ protected function getCredentialRequestOptions(
return $source->getPublicKeyCredentialDescriptor();
}, $validCredentials);

$options = new PublicKeyCredentialRequestOptions(
random_bytes(32),
40000,
null,
$descriptors,
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED
);
$options = new PublicKeyCredentialRequestOptions(random_bytes(32), 40000);
$options->allowCredentials($descriptors);
$options->setUserVerification(PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED);

// Persist the options for later
$store->addState(['credentialOptions' => $options]);
Expand All @@ -183,11 +181,13 @@ protected function getAuthenticatorAssertionResponseValidator(
Decoder $decoder,
StoreInterface $store
): AuthenticatorAssertionResponseValidator {
$manager = new Manager();
$manager->add(new ES256());
return new AuthenticatorAssertionResponseValidator(
$this->getCredentialRepository($store),
$decoder,
new TokenBindingNotSupportedHandler(),
new ExtensionOutputCheckerHandler()
new ExtensionOutputCheckerHandler(),
$manager
);
}
}
3 changes: 3 additions & 0 deletions tests/RegisterHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,10 @@ public function testRegister(
->setMethods(['getPublicKeyCredentialLoader', 'getAuthenticatorAttestationResponseValidator'])
->getMock();

$publicKeyCredentialSourceMock = $this->createMock(PublicKeyCredentialSource::class);
$responseValidatorMock = $this->createMock(AuthenticatorAttestationResponseValidator::class);
$responseValidatorMock->method('check')->willReturn($publicKeyCredentialSourceMock);

// Allow the data provider to customise the validation check handling
if ($responseValidatorMockCallback) {
$responseValidatorMockCallback($responseValidatorMock);
Expand Down
4 changes: 4 additions & 0 deletions tests/VerifyHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Webauthn\AuthenticatorResponse;
use Webauthn\PublicKeyCredential;
use Webauthn\PublicKeyCredentialLoader;
use Webauthn\PublicKeyCredentialSource;

class VerifyHandlerTest extends SapphireTest
{
Expand Down Expand Up @@ -132,7 +133,10 @@ public function testVerify(
->setMethods(['getPublicKeyCredentialLoader', 'getAuthenticatorAssertionResponseValidator'])
->getMock();

$publicKeyCredentialSourceMock = $this->createMock(PublicKeyCredentialSource::class);
$responseValidatorMock = $this->createMock(AuthenticatorAssertionResponseValidator::class);
$responseValidatorMock->method('check')->willReturn($publicKeyCredentialSourceMock);

// Allow the data provider to customise the validation check handling
if ($responseValidatorMockCallback) {
$responseValidatorMockCallback($responseValidatorMock);
Expand Down

0 comments on commit 780c98e

Please sign in to comment.