utils-bbk.js (4864B)
1 'use strict'; 2 3 // See https://www.iana.org/assignments/cose/cose.xhtml#key-type 4 const cose_key_type_ec2 = 2; 5 const cose_key_type_rsa = 3; 6 7 // Decode |encoded| using a base64url decoding. 8 function base64urlToUint8Array(encoded) { 9 return Uint8Array.from(base64urlDecode(encoded), c => c.charCodeAt(0)); 10 } 11 12 // The result of a browser bound key verification. 13 const BrowserBoundKeyVerificationResult = Object.freeze({ 14 // No browser bound key was included. 15 NoBrowserBoundKey: 'NoBrowserBoundKey', 16 // A browser bound key was included and the cryptographic signature verifies. 17 BrowserBoundKeySignatureVerified: 'BrowserBoundKeySignatureVerified', 18 }); 19 20 // This function takes a credential and verifies either that no BBK was 21 // included (no browser bound public key, and no browser bound signature) 22 // or that the BBK was included (browser bound public key, browser bound 23 // signature, and the signature cryptographically verifies). 24 // 25 // Returns a BrowserBoundKeyVerificationResult informing the conditions of 26 // successful verification. 27 async function verifyBrowserBoundKey(credential, expectedKeyTypes) { 28 const clientExtensionResults = credential.getClientExtensionResults(); 29 const signatureArray = 30 clientExtensionResults?.payment?.browserBoundSignature?.signature; 31 const clientData = JSON.parse(String.fromCharCode.apply( 32 null, new Uint8Array(credential.response.clientDataJSON))); 33 const publicKeyCoseKeyEncoded = clientData?.payment?.browserBoundPublicKey; 34 assert_equals( 35 signatureArray !== undefined, publicKeyCoseKeyEncoded !== undefined, 36 'Either both or none of the browser bound public key and signature must ' + 37 'be present, but only one was present.') 38 if (signatureArray == undefined) { 39 return BrowserBoundKeyVerificationResult.NoBrowserBoundKey; 40 } 41 assertBrowserBoundSignatureInClientExtensionResults(clientExtensionResults); 42 await assertBrowserBoundKeySignature( 43 credential.response.clientDataJSON, signatureArray, expectedKeyTypes); 44 return BrowserBoundKeyVerificationResult.BrowserBoundKeySignatureVerified; 45 } 46 47 function getBrowserBoundPublicKeyFromCredential(credential) { 48 const clientData = JSON.parse(String.fromCharCode.apply( 49 null, new Uint8Array(credential.response.clientDataJSON))); 50 return clientData?.payment?.browserBoundPublicKey; 51 } 52 53 function assertNoBrowserBoundPublicKeyInCredential(credential, message) { 54 const clientData = JSON.parse(String.fromCharCode.apply( 55 null, new Uint8Array(credential.response.clientDataJSON))); 56 assert_equals(clientData?.payment?.browserBoundPublicKey, undefined, message); 57 } 58 59 function assertBrowserBoundSignatureInClientExtensionResults( 60 clientExtensionResults) { 61 assert_not_equals( 62 clientExtensionResults.payment, undefined, 63 'getClientExtensionResults().payment is not undefined'); 64 assert_not_equals( 65 clientExtensionResults.payment.browserBoundSignature, undefined, 66 'getClientExtensionResults().payment is not undefined'); 67 assert_not_equals( 68 clientExtensionResults.payment.browserBoundSignature.signature, undefined, 69 'getClientExtensionResults().payment.signature is not undefined'); 70 } 71 72 async function assertBrowserBoundKeySignature( 73 clientDataJSON, signatureArray, expectedKeyTypes) { 74 const clientData = JSON.parse( 75 String.fromCharCode.apply(null, new Uint8Array(clientDataJSON))); 76 assert_not_equals( 77 clientData.payment, undefined, 78 `Deserialized clientData, ${ 79 JSON.stringify(clientData)}, should contain a 'payment' member`); 80 assert_not_equals( 81 clientData.payment.browserBoundPublicKey, undefined, 82 `ClientData['payment'] should contain a 'browserBoundPublicKey' member.`); 83 const browserBoundPublicKeyCoseKeyBase64 = 84 clientData.payment.browserBoundPublicKey; 85 const browserBoundPublicKeyCoseKeyEncoded = 86 base64urlToUint8Array(browserBoundPublicKeyCoseKeyBase64); 87 const keyType = getCoseKeyType(browserBoundPublicKeyCoseKeyEncoded); 88 assert_true( 89 expectedKeyTypes.includes(keyType), 90 `KeyType, ${keyType}, was not one of the expected key types, ${ 91 expectedKeyTypes}`); 92 if (keyType == cose_key_type_ec2) { 93 // Verify the signature for a ES256 signature scheme. 94 const browserBoundPublicKeyCoseKey = 95 parseCosePublicKey(browserBoundPublicKeyCoseKeyEncoded); 96 const jwkPublicKey = coseObjectToJWK(browserBoundPublicKeyCoseKey); 97 const key = await crypto.subtle.importKey( 98 'jwk', jwkPublicKey, {name: 'ECDSA', namedCurve: 'P-256'}, 99 /*extractable=*/ false, ['verify']); 100 const signature = 101 convertDERSignatureToSubtle(new Uint8Array(signatureArray)); 102 assert_true(await crypto.subtle.verify( 103 {name: 'ECDSA', hash: 'SHA-256'}, key, signature, clientDataJSON)); 104 } 105 // TODO: Verify the signature in case of an RS256 signature scheme. 106 }