browser_clientAuthRememberService.js (8271B)
1 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*- 2 // Any copyright is dedicated to the Public Domain. 3 // http://creativecommons.org/publicdomain/zero/1.0/ 4 "use strict"; 5 6 /** 7 * Test certificate (i.e. build/pgo/certs/mochitest.client). 8 * 9 * @type {nsIX509Cert} 10 */ 11 var cert; 12 var cert2; 13 var cert3; 14 15 var sdr = Cc["@mozilla.org/security/sdr;1"].getService(Ci.nsISecretDecoderRing); 16 var certDB = Cc["@mozilla.org/security/x509certdb;1"].getService( 17 Ci.nsIX509CertDB 18 ); 19 20 var deleted = false; 21 22 const { MockRegistrar } = ChromeUtils.importESModule( 23 "resource://testing-common/MockRegistrar.sys.mjs" 24 ); 25 26 function findCertByCommonName(commonName) { 27 for (let cert of certDB.getCerts()) { 28 if (cert.commonName == commonName) { 29 return cert; 30 } 31 } 32 return null; 33 } 34 35 async function testHelper(connectURL, expectedURL) { 36 let win = await BrowserTestUtils.openNewBrowserWindow(); 37 38 await SpecialPowers.pushPrefEnv({ 39 set: [["security.default_personal_cert", "Ask Every Time"]], 40 }); 41 42 BrowserTestUtils.startLoadingURIString( 43 win.gBrowser.selectedBrowser, 44 connectURL 45 ); 46 47 await BrowserTestUtils.browserLoaded( 48 win.gBrowser.selectedBrowser, 49 false, 50 expectedURL, 51 true 52 ); 53 let loadedURL = win.gBrowser.selectedBrowser.documentURI.spec; 54 Assert.ok( 55 loadedURL.startsWith(expectedURL), 56 `Expected and actual URLs should match (got '${loadedURL}', expected '${expectedURL}')` 57 ); 58 59 await win.close(); 60 61 // This clears the TLS session cache so we don't use a previously-established 62 // ticket to connect and bypass selecting a client auth certificate in 63 // subsequent tests. 64 sdr.logout(); 65 } 66 67 async function openRequireClientCert() { 68 gClientAuthDialogService.chooseCertificateCalled = false; 69 await testHelper( 70 "https://requireclientcert.example.com:443", 71 "https://requireclientcert.example.com/" 72 ); 73 } 74 75 async function openRequireClientCert2() { 76 gClientAuthDialogService.chooseCertificateCalled = false; 77 await testHelper( 78 "https://requireclientcert-2.example.com:443", 79 "https://requireclientcert-2.example.com/" 80 ); 81 } 82 83 // Mock implementation of nsIClientAuthRememberService 84 const gClientAuthRememberService = { 85 forgetRememberedDecision(key) { 86 deleted = true; 87 Assert.equal( 88 key, 89 "exampleKey2", 90 "Expected to get the same key that was passed in getDecisions()" 91 ); 92 }, 93 94 getDecisions() { 95 return [ 96 { 97 asciiHost: "example.com", 98 dbKey: cert.dbKey, 99 entryKey: "exampleKey1", 100 }, 101 { 102 asciiHost: "example.org", 103 dbKey: cert2.dbKey, 104 entryKey: "exampleKey2", 105 }, 106 { 107 asciiHost: "example.test", 108 dbKey: cert3.dbKey, 109 entryKey: "exampleKey3", 110 }, 111 { 112 asciiHost: "unavailable.example.com", 113 // This dbKey should not correspond to any real certificate. The first 114 // 8 bytes have to be 0, followed by the lengths of the serial number 115 // and issuer distinguished name, respectively, and then followed by 116 // the bytes of the serial number and finally the encoded issuer 117 // distinguished name. In this case, the serial number is a single 0 118 // byte and the issuer distinguished name is a DER SEQUENCE of length 0 119 // (the bytes 0x30 and 0). 120 // See also the documentation in nsNSSCertificateDB::FindCertByDBKey. 121 dbKey: "AAAAAAAAAAAAAAABAAAAAgAeAA==", 122 entryKey: "exampleKey4", 123 }, 124 ]; 125 }, 126 127 QueryInterface: ChromeUtils.generateQI(["nsIClientAuthRememberService"]), 128 }; 129 130 const gClientAuthDialogService = { 131 _chooseCertificateCalled: false, 132 133 get chooseCertificateCalled() { 134 return this._chooseCertificateCalled; 135 }, 136 137 set chooseCertificateCalled(value) { 138 this._chooseCertificateCalled = value; 139 }, 140 141 chooseCertificate(hostname, certArray, loadContext, caNames, callback) { 142 this.chooseCertificateCalled = true; 143 callback.certificateChosen(certArray[0], true); 144 }, 145 146 QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthDialogService]), 147 }; 148 149 add_task(async function testRememberedDecisionsUI() { 150 cert = findCertByCommonName("Mochitest client"); 151 cert2 = await readCertificate("pgo-ca-all-usages.pem", ",,"); 152 cert3 = await readCertificate("client-cert-via-intermediate.pem", ",,"); 153 isnot(cert, null, "Should be able to find the test client cert"); 154 isnot(cert2, null, "Should be able to find pgo-ca-all-usages.pem"); 155 isnot(cert3, null, "Should be able to find client-cert-via-intermediate.pem"); 156 157 let clientAuthRememberServiceCID = MockRegistrar.register( 158 "@mozilla.org/security/clientAuthRememberService;1", 159 gClientAuthRememberService 160 ); 161 162 let win = await openCertManager(); 163 164 let listItems = win.document 165 .getElementById("rememberedList") 166 .querySelectorAll("richlistitem"); 167 168 Assert.equal( 169 listItems.length, 170 4, 171 "rememberedList has expected number of items" 172 ); 173 174 let labels = win.document 175 .getElementById("rememberedList") 176 .querySelectorAll("label"); 177 178 Assert.equal( 179 labels.length, 180 12, 181 "rememberedList has expected number of labels" 182 ); 183 184 await BrowserTestUtils.waitForCondition( 185 () => !!labels[10].textContent.length, 186 "Localized label is populated" 187 ); 188 189 let expectedHosts = [ 190 "example.com", 191 "example.org", 192 "example.test", 193 "unavailable.example.com", 194 ]; 195 let hosts = [ 196 labels[0].value, 197 labels[3].value, 198 labels[6].value, 199 labels[9].value, 200 ]; 201 let expectedNames = [ 202 cert.commonName, 203 cert2.commonName, 204 cert3.commonName, 205 "(Unavailable)", 206 ]; 207 let names = [ 208 labels[1].value, 209 labels[4].value, 210 labels[7].value, 211 labels[10].textContent, 212 ]; 213 let expectedSerialNumbers = [ 214 cert.serialNumber, 215 cert2.serialNumber, 216 cert3.serialNumber, 217 "(Unavailable)", 218 ]; 219 let serialNumbers = [ 220 labels[2].value, 221 labels[5].value, 222 labels[8].value, 223 labels[11].textContent, 224 ]; 225 226 for (let i = 0; i < listItems.length; i++) { 227 Assert.equal(hosts[i], expectedHosts[i], "got expected asciiHost"); 228 Assert.equal(names[i], expectedNames[i], "got expected commonName"); 229 Assert.equal( 230 serialNumbers[i], 231 expectedSerialNumbers[i], 232 "got expected serialNumber" 233 ); 234 } 235 236 win.document.getElementById("rememberedList").selectedIndex = 1; 237 win.document.getElementById("remembered_deleteButton").click(); 238 239 Assert.ok(deleted, "Expected forgetRememberedDecision() to get called"); 240 241 win.document.getElementById("certmanager").acceptDialog(); 242 await BrowserTestUtils.windowClosed(win); 243 244 MockRegistrar.unregister(clientAuthRememberServiceCID); 245 }); 246 247 add_task(async function testDeletingRememberedDecisions() { 248 let clientAuthDialogServiceCID = MockRegistrar.register( 249 "@mozilla.org/security/ClientAuthDialogService;1", 250 gClientAuthDialogService 251 ); 252 let cars = Cc["@mozilla.org/security/clientAuthRememberService;1"].getService( 253 Ci.nsIClientAuthRememberService 254 ); 255 256 await openRequireClientCert(); 257 Assert.ok( 258 gClientAuthDialogService.chooseCertificateCalled, 259 "chooseCertificate should have been called if visiting 'requireclientcert.example.com' for the first time" 260 ); 261 262 await openRequireClientCert(); 263 Assert.ok( 264 !gClientAuthDialogService.chooseCertificateCalled, 265 "chooseCertificate should not have been called if visiting 'requireclientcert.example.com' for the second time" 266 ); 267 268 await openRequireClientCert2(); 269 Assert.ok( 270 gClientAuthDialogService.chooseCertificateCalled, 271 "chooseCertificate should have been called if visiting 'requireclientcert-2.example.com' for the first time" 272 ); 273 274 let originAttributes = { privateBrowsingId: 0 }; 275 cars.deleteDecisionsByHost("requireclientcert.example.com", originAttributes); 276 277 await openRequireClientCert(); 278 Assert.ok( 279 gClientAuthDialogService.chooseCertificateCalled, 280 "chooseCertificate should have been called after removing all remembered decisions for 'requireclientcert.example.com'" 281 ); 282 283 await openRequireClientCert2(); 284 Assert.ok( 285 !gClientAuthDialogService.chooseCertificateCalled, 286 "chooseCertificate should not have been called if visiting 'requireclientcert-2.example.com' for the second time" 287 ); 288 289 MockRegistrar.unregister(clientAuthDialogServiceCID); 290 });