Source code
Revision control
Copy as Markdown
Other Tools
'use strict';
const cose_key_type_ec2 = 2;
const cose_key_type_rsa = 3;
// Decode |encoded| using a base64url decoding.
function base64urlToUint8Array(encoded) {
return Uint8Array.from(base64urlDecode(encoded), c => c.charCodeAt(0));
}
// The result of a browser bound key verification.
const BrowserBoundKeyVerificationResult = Object.freeze({
// No browser bound key was included.
NoBrowserBoundKey: 'NoBrowserBoundKey',
// A browser bound key was included and the cryptographic signature verifies.
BrowserBoundKeySignatureVerified: 'BrowserBoundKeySignatureVerified',
});
// This function takes a credential and verifies either that no BBK was
// included (no browser bound public key, and no browser bound signature)
// or that the BBK was included (browser bound public key, browser bound
// signature, and the signature cryptographically verifies).
//
// Returns a BrowserBoundKeyVerificationResult informing the conditions of
// successful verification.
async function verifyBrowserBoundKey(credential, expectedKeyTypes) {
const clientExtensionResults = credential.getClientExtensionResults();
const signatureArray =
clientExtensionResults?.payment?.browserBoundSignature?.signature;
const clientData = JSON.parse(String.fromCharCode.apply(
null, new Uint8Array(credential.response.clientDataJSON)));
const publicKeyCoseKeyEncoded = clientData?.payment?.browserBoundPublicKey;
assert_equals(
signatureArray !== undefined, publicKeyCoseKeyEncoded !== undefined,
'Either both or none of the browser bound public key and signature must ' +
'be present, but only one was present.')
if (signatureArray == undefined) {
return BrowserBoundKeyVerificationResult.NoBrowserBoundKey;
}
assertBrowserBoundSignatureInClientExtensionResults(clientExtensionResults);
await assertBrowserBoundKeySignature(
credential.response.clientDataJSON, signatureArray, expectedKeyTypes);
return BrowserBoundKeyVerificationResult.BrowserBoundKeySignatureVerified;
}
function getBrowserBoundPublicKeyFromCredential(credential) {
const clientData = JSON.parse(String.fromCharCode.apply(
null, new Uint8Array(credential.response.clientDataJSON)));
return clientData?.payment?.browserBoundPublicKey;
}
function assertNoBrowserBoundPublicKeyInCredential(credential, message) {
const clientData = JSON.parse(String.fromCharCode.apply(
null, new Uint8Array(credential.response.clientDataJSON)));
assert_equals(clientData?.payment?.browserBoundPublicKey, undefined, message);
}
function assertBrowserBoundSignatureInClientExtensionResults(
clientExtensionResults) {
assert_not_equals(
clientExtensionResults.payment, undefined,
'getClientExtensionResults().payment is not undefined');
assert_not_equals(
clientExtensionResults.payment.browserBoundSignature, undefined,
'getClientExtensionResults().payment is not undefined');
assert_not_equals(
clientExtensionResults.payment.browserBoundSignature.signature, undefined,
'getClientExtensionResults().payment.signature is not undefined');
}
async function assertBrowserBoundKeySignature(
clientDataJSON, signatureArray, expectedKeyTypes) {
const clientData = JSON.parse(
String.fromCharCode.apply(null, new Uint8Array(clientDataJSON)));
assert_not_equals(
clientData.payment, undefined,
`Deserialized clientData, ${
JSON.stringify(clientData)}, should contain a 'payment' member`);
assert_not_equals(
clientData.payment.browserBoundPublicKey, undefined,
`ClientData['payment'] should contain a 'browserBoundPublicKey' member.`);
const browserBoundPublicKeyCoseKeyBase64 =
clientData.payment.browserBoundPublicKey;
const browserBoundPublicKeyCoseKeyEncoded =
base64urlToUint8Array(browserBoundPublicKeyCoseKeyBase64);
const keyType = getCoseKeyType(browserBoundPublicKeyCoseKeyEncoded);
assert_true(
expectedKeyTypes.includes(keyType),
`KeyType, ${keyType}, was not one of the expected key types, ${
expectedKeyTypes}`);
if (keyType == cose_key_type_ec2) {
// Verify the signature for a ES256 signature scheme.
const browserBoundPublicKeyCoseKey =
parseCosePublicKey(browserBoundPublicKeyCoseKeyEncoded);
const jwkPublicKey = coseObjectToJWK(browserBoundPublicKeyCoseKey);
const key = await crypto.subtle.importKey(
'jwk', jwkPublicKey, {name: 'ECDSA', namedCurve: 'P-256'},
/*extractable=*/ false, ['verify']);
const signature =
convertDERSignatureToSubtle(new Uint8Array(signatureArray));
assert_true(await crypto.subtle.verify(
{name: 'ECDSA', hash: 'SHA-256'}, key, signature, clientDataJSON));
}
// TODO: Verify the signature in case of an RS256 signature scheme.
}