head.js (6510B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 let exports = this; 8 9 const scripts = [ 10 "pkijs/common.js", 11 "pkijs/asn1.js", 12 "pkijs/x509_schema.js", 13 "pkijs/x509_simpl.js", 14 "browser/cbor.js", 15 "browser/u2futil.js", 16 ]; 17 18 for (let script of scripts) { 19 Services.scriptloader.loadSubScript( 20 `chrome://mochitests/content/browser/dom/webauthn/tests/${script}`, 21 this 22 ); 23 } 24 25 function add_virtual_authenticator(autoremove = true) { 26 let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService( 27 Ci.nsIWebAuthnService 28 ); 29 let id = webauthnService.addVirtualAuthenticator( 30 "ctap2_1", 31 "internal", 32 true, 33 true, 34 true, 35 true 36 ); 37 if (autoremove) { 38 registerCleanupFunction(() => { 39 webauthnService.removeVirtualAuthenticator(id); 40 }); 41 } 42 return id; 43 } 44 45 function remove_virtual_authenticator(authenticatorId) { 46 let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService( 47 Ci.nsIWebAuthnService 48 ); 49 webauthnService.removeVirtualAuthenticator(authenticatorId); 50 } 51 52 async function addCredential(authenticatorId, rpId) { 53 let keyPair = await crypto.subtle.generateKey( 54 { 55 name: "ECDSA", 56 namedCurve: "P-256", 57 }, 58 true, 59 ["sign"] 60 ); 61 62 let credId = new Uint8Array(32); 63 crypto.getRandomValues(credId); 64 credId = bytesToBase64UrlSafe(credId); 65 66 let privateKey = await crypto.subtle 67 .exportKey("pkcs8", keyPair.privateKey) 68 .then(privateKey => bytesToBase64UrlSafe(privateKey)); 69 70 let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService( 71 Ci.nsIWebAuthnService 72 ); 73 74 webauthnService.addCredential( 75 authenticatorId, 76 credId, 77 true, // resident key 78 rpId, 79 privateKey, 80 "VGVzdCBVc2Vy", // "Test User" 81 0 // sign count 82 ); 83 84 return credId; 85 } 86 87 async function removeCredential(authenticatorId, credId) { 88 let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService( 89 Ci.nsIWebAuthnService 90 ); 91 92 webauthnService.removeCredential(authenticatorId, credId); 93 } 94 95 function memcmp(x, y) { 96 let xb = new Uint8Array(x); 97 let yb = new Uint8Array(y); 98 99 if (x.byteLength != y.byteLength) { 100 return false; 101 } 102 103 for (let i = 0; i < xb.byteLength; ++i) { 104 if (xb[i] != yb[i]) { 105 return false; 106 } 107 } 108 109 return true; 110 } 111 112 function arrivingHereIsBad(aResult) { 113 ok(false, "Bad result! Received a: " + aResult); 114 } 115 116 function expectError(aType) { 117 let expected = `${aType}Error`; 118 return function (aResult) { 119 is( 120 aResult.slice(0, expected.length), 121 expected, 122 `Expecting a ${aType}Error` 123 ); 124 }; 125 } 126 127 /* eslint-disable no-shadow */ 128 function promiseWebAuthnMakeCredential( 129 tab, 130 attestation = "none", 131 residentKey = "discouraged", 132 extensions = {} 133 ) { 134 return ContentTask.spawn( 135 tab.linkedBrowser, 136 [attestation, residentKey, extensions], 137 ([attestation, residentKey, extensions]) => { 138 const cose_alg_ECDSA_w_SHA256 = -7; 139 140 let challenge = content.crypto.getRandomValues(new Uint8Array(16)); 141 142 let pubKeyCredParams = [ 143 { 144 type: "public-key", 145 alg: cose_alg_ECDSA_w_SHA256, 146 }, 147 ]; 148 149 let publicKey = { 150 rp: { id: content.document.domain, name: "none" }, 151 user: { 152 id: new Uint8Array(), 153 name: "none", 154 displayName: "none", 155 }, 156 pubKeyCredParams, 157 authenticatorSelection: { 158 authenticatorAttachment: "cross-platform", 159 residentKey, 160 }, 161 extensions, 162 attestation, 163 challenge, 164 }; 165 166 return content.navigator.credentials 167 .create({ publicKey }) 168 .then(credential => { 169 return { 170 clientDataJSON: credential.response.clientDataJSON, 171 attObj: credential.response.attestationObject, 172 rawId: credential.rawId, 173 }; 174 }); 175 } 176 ); 177 } 178 179 function promiseWebAuthnGetAssertion(tab, key_handle = null, extensions = {}) { 180 return ContentTask.spawn( 181 tab.linkedBrowser, 182 [key_handle, extensions], 183 ([key_handle, extensions]) => { 184 let challenge = content.crypto.getRandomValues(new Uint8Array(16)); 185 if (key_handle == null) { 186 key_handle = content.crypto.getRandomValues(new Uint8Array(16)); 187 } 188 189 let credential = { 190 id: key_handle, 191 type: "public-key", 192 transports: ["usb"], 193 }; 194 195 let publicKey = { 196 challenge, 197 extensions, 198 rpId: content.document.domain, 199 allowCredentials: [credential], 200 }; 201 202 return content.navigator.credentials 203 .get({ publicKey }) 204 .then(assertion => { 205 return { 206 authenticatorData: assertion.response.authenticatorData, 207 clientDataJSON: assertion.response.clientDataJSON, 208 extensions: assertion.getClientExtensionResults(), 209 signature: assertion.response.signature, 210 }; 211 }); 212 } 213 ); 214 } 215 216 function promiseWebAuthnGetAssertionDiscoverable( 217 tab, 218 mediation = "optional", 219 extensions = {} 220 ) { 221 return ContentTask.spawn( 222 tab.linkedBrowser, 223 [extensions, mediation], 224 ([extensions, mediation]) => { 225 let challenge = content.crypto.getRandomValues(new Uint8Array(16)); 226 227 let publicKey = { 228 challenge, 229 extensions, 230 rpId: content.document.domain, 231 allowCredentials: [], 232 }; 233 234 return content.navigator.credentials.get({ publicKey, mediation }); 235 } 236 ); 237 } 238 239 function checkRpIdHash(rpIdHash, hostname) { 240 return crypto.subtle 241 .digest("SHA-256", string2buffer(hostname)) 242 .then(calculatedRpIdHash => { 243 let calcHashStr = bytesToBase64UrlSafe( 244 new Uint8Array(calculatedRpIdHash) 245 ); 246 let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(rpIdHash)); 247 248 if (calcHashStr != providedHashStr) { 249 throw new Error("Calculated RP ID hash doesn't match."); 250 } 251 }); 252 } 253 254 function promiseNotification(id) { 255 return new Promise(resolve => { 256 PopupNotifications.panel.addEventListener("popupshown", function shown() { 257 let notification = PopupNotifications.getNotification(id); 258 if (notification) { 259 ok(true, `${id} prompt visible`); 260 PopupNotifications.panel.removeEventListener("popupshown", shown); 261 resolve(); 262 } 263 }); 264 }); 265 } 266 /* eslint-enable no-shadow */