| 'use strict'; | 
 |  | 
 | // See https://www.iana.org/assignments/cose/cose.xhtml#key-type | 
 | 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. | 
 | } |