test_session_resumption.js (9475B)
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 // Tests that PSM makes the correct determination of the security status of 7 // loads involving session resumption (i.e. when a TLS handshake bypasses the 8 // AuthCertificate callback). 9 10 do_get_profile(); 11 const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( 12 Ci.nsIX509CertDB 13 ); 14 15 registerCleanupFunction(() => { 16 Services.prefs.clearUserPref("security.OCSP.enabled"); 17 }); 18 19 Services.prefs.setIntPref("security.OCSP.enabled", 1); 20 21 addCertFromFile(certdb, "bad_certs/evroot.pem", "CTu,,"); 22 addCertFromFile(certdb, "bad_certs/ev-test-intermediate.pem", ",,"); 23 24 // For expired.example.com, the platform will make a connection that will fail. 25 // Using information gathered at that point, an override will be added and 26 // another connection will be made. This connection will succeed. At that point, 27 // as long as the session cache isn't cleared, subsequent new connections should 28 // use session resumption, thereby bypassing the AuthCertificate hook. We need 29 // to ensure that the correct security state is propagated to the new connection 30 // information object. 31 function add_resume_non_ev_with_override_test() { 32 // This adds the override and makes one successful connection. 33 add_cert_override_test("expired.example.com", SEC_ERROR_EXPIRED_CERTIFICATE); 34 35 // This connects again, using session resumption. Note that we don't clear 36 // the TLS session cache between these operations (that would defeat the 37 // purpose). 38 add_connection_test( 39 "expired.example.com", 40 PRErrorCodeSuccess, 41 null, 42 transportSecurityInfo => { 43 ok(transportSecurityInfo.resumed, "connection should be resumed"); 44 ok( 45 transportSecurityInfo.securityState & 46 Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN, 47 "expired.example.com should have STATE_CERT_USER_OVERRIDDEN flag" 48 ); 49 equal( 50 transportSecurityInfo.succeededCertChain.length, 51 0, 52 "expired.example.com should not have succeededCertChain set" 53 ); 54 equal( 55 transportSecurityInfo.handshakeCertificates.length, 56 2, 57 "expired.example.com should have handshakeCertificates set" 58 ); 59 equal( 60 transportSecurityInfo.overridableErrorCategory, 61 Ci.nsITransportSecurityInfo.ERROR_TIME, 62 "expired.example.com should have time overridable error category" 63 ); 64 ok( 65 !transportSecurityInfo.isExtendedValidation, 66 "expired.example.com should not have isExtendedValidation set" 67 ); 68 69 let certOverrideService = Cc[ 70 "@mozilla.org/security/certoverride;1" 71 ].getService(Ci.nsICertOverrideService); 72 certOverrideService.clearValidityOverride( 73 "expired.example.com", 74 8443, 75 {} 76 ); 77 } 78 ); 79 } 80 81 // Helper function that adds a test that connects to ev-test.example.com and 82 // verifies that it validates as EV (or not, if we're running a non-debug 83 // build). This assumes that an appropriate OCSP responder is running or that 84 // good responses are cached. 85 function add_one_ev_test(resumed) { 86 add_connection_test( 87 "ev-test.example.com", 88 PRErrorCodeSuccess, 89 null, 90 transportSecurityInfo => { 91 equal( 92 transportSecurityInfo.resumed, 93 resumed, 94 "connection should be resumed or not resumed as expected" 95 ); 96 ok( 97 !( 98 transportSecurityInfo.securityState & 99 Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN 100 ), 101 "ev-test.example.com should not have STATE_CERT_USER_OVERRIDDEN flag" 102 ); 103 equal( 104 transportSecurityInfo.succeededCertChain.length, 105 3, 106 "ev-test.example.com should have succeededCertChain set" 107 ); 108 equal( 109 transportSecurityInfo.handshakeCertificates.length, 110 2, 111 "ev-test.example.com should have handshakeCertificates set" 112 ); 113 equal( 114 transportSecurityInfo.overridableErrorCategory, 115 Ci.nsITransportSecurityInfo.ERROR_UNSET, 116 "ev-test.example.com should not have an overridable error category" 117 ); 118 ok( 119 !gEVExpected || transportSecurityInfo.isExtendedValidation, 120 "ev-test.example.com should have isExtendedValidation set " + 121 "(or this is a non-debug build)" 122 ); 123 } 124 ); 125 } 126 127 // This test is similar, except with extended validation. We should connect 128 // successfully, and the certificate should be EV in debug builds. Without 129 // clearing the session cache, we should connect successfully again, this time 130 // with session resumption. The certificate should again be EV in debug builds. 131 function add_resume_ev_test() { 132 const SERVER_PORT = 8888; 133 let expectedRequestPaths = ["ev-test"]; 134 let responseTypes = ["good"]; 135 // Since we cache OCSP responses, we only ever actually serve one set. 136 let ocspResponder; 137 // If we don't wrap this in an `add_test`, the OCSP responder will be running 138 // while we are actually running unrelated testcases, which can disrupt them. 139 add_test(() => { 140 ocspResponder = startOCSPResponder( 141 SERVER_PORT, 142 "localhost", 143 "bad_certs", 144 expectedRequestPaths, 145 expectedRequestPaths.slice(), 146 null, 147 responseTypes 148 ); 149 run_next_test(); 150 }); 151 // We should be able to connect and verify the certificate as EV (in debug 152 // builds). 153 add_one_ev_test(false); 154 // We should be able to connect again (using session resumption). In debug 155 // builds, the certificate should be noted as EV. Again, it's important that 156 // nothing clears the TLS cache in between these two operations. 157 add_one_ev_test(true); 158 159 add_test(() => { 160 ocspResponder.stop(run_next_test); 161 }); 162 } 163 164 const GOOD_DOMAIN = "good.include-subdomains.pinning.example.com"; 165 166 // Helper function that adds a test that connects to a domain that should 167 // succeed (but isn't EV) and verifies that its succeededCertChain gets set 168 // appropriately. 169 function add_one_non_ev_test() { 170 add_connection_test( 171 GOOD_DOMAIN, 172 PRErrorCodeSuccess, 173 null, 174 transportSecurityInfo => { 175 ok( 176 !( 177 transportSecurityInfo.securityState & 178 Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN 179 ), 180 `${GOOD_DOMAIN} should not have STATE_CERT_USER_OVERRIDDEN flag` 181 ); 182 ok( 183 transportSecurityInfo.succeededCertChain, 184 `${GOOD_DOMAIN} should have succeededCertChain set` 185 ); 186 equal( 187 transportSecurityInfo.overridableErrorCategory, 188 0, 189 `${GOOD_DOMAIN} should not have an overridable error category set` 190 ); 191 ok( 192 !transportSecurityInfo.isExtendedValidation, 193 `${GOOD_DOMAIN} should not have isExtendedValidation set` 194 ); 195 } 196 ); 197 } 198 199 // This test is similar, except with non-extended validation. We should connect 200 // successfully, and the certificate should not be EV. Without clearing the 201 // session cache, we should connect successfully again, this time with session 202 // resumption. In this case, though, we want to ensure the succeededCertChain is 203 // set. 204 function add_resume_non_ev_test() { 205 add_one_non_ev_test(); 206 add_one_non_ev_test(); 207 } 208 209 const statsPtr = getSSLStatistics(); 210 const toInt32 = ctypes.Int64.lo; 211 212 // Connect to the same domain with two origin attributes and check if any ssl 213 // session is resumed. 214 function add_origin_attributes_test( 215 originAttributes1, 216 originAttributes2, 217 resumeExpected 218 ) { 219 add_connection_test( 220 GOOD_DOMAIN, 221 PRErrorCodeSuccess, 222 clearSessionCache, 223 null, 224 null, 225 originAttributes1 226 ); 227 228 let hitsBeforeConnect; 229 let missesBeforeConnect; 230 let expectedHits = resumeExpected ? 1 : 0; 231 let expectedMisses = 1 - expectedHits; 232 233 add_connection_test( 234 GOOD_DOMAIN, 235 PRErrorCodeSuccess, 236 function () { 237 // Add the hits and misses before connection. 238 let stats = statsPtr.contents; 239 hitsBeforeConnect = toInt32(stats.sch_sid_cache_hits); 240 missesBeforeConnect = toInt32(stats.sch_sid_cache_misses); 241 }, 242 function () { 243 let stats = statsPtr.contents; 244 equal( 245 toInt32(stats.sch_sid_cache_hits), 246 hitsBeforeConnect + expectedHits, 247 "Unexpected cache hits" 248 ); 249 equal( 250 toInt32(stats.sch_sid_cache_misses), 251 missesBeforeConnect + expectedMisses, 252 "Unexpected cache misses" 253 ); 254 }, 255 null, 256 originAttributes2 257 ); 258 } 259 260 function add_resumption_tests() { 261 add_resume_ev_test(); 262 add_resume_non_ev_test(); 263 add_resume_non_ev_with_override_test(); 264 add_origin_attributes_test({}, {}, true); 265 add_origin_attributes_test({ userContextId: 1 }, { userContextId: 2 }, false); 266 add_origin_attributes_test({ userContextId: 3 }, { userContextId: 3 }, true); 267 add_origin_attributes_test( 268 { firstPartyDomain: "foo.com" }, 269 { firstPartyDomain: "bar.com" }, 270 false 271 ); 272 add_origin_attributes_test( 273 { firstPartyDomain: "baz.com" }, 274 { firstPartyDomain: "baz.com" }, 275 true 276 ); 277 } 278 279 function run_test() { 280 add_tls_server_setup("BadCertAndPinningServer", "bad_certs"); 281 add_resumption_tests(); 282 // Enable external session cache and reset the status. 283 add_test(function () { 284 Services.prefs.setBoolPref("network.ssl_tokens_cache_enabled", true); 285 certdb.clearOCSPCache(); 286 run_next_test(); 287 }); 288 // Do tests again. 289 add_resumption_tests(); 290 run_next_test(); 291 }