commit cd9887977227df33462e755200ac6f6ade351bea Author: Camilo Viecco cviecco@mozilla.com Date: Thu Jun 20 10:35:43 2013 -0700
Bug #11955 Backport certificate pinning
Includes the following Mozilla patches, some modified for Tor Browser:
Bug 744204 - Allow Key pining part 1 - Built-in Pinning Service. r=keeler
Bug 744204 - Allow Certificate key pinning Part 2 - Certverifier Interface. r=keeler
--HG-- extra : rebase_source : 2f9748ba0b241c697e22b7ff72f2f5a0fad4a2ca
Bug 998057: Add test pinset to the pin generator (r=cviecco)
--HG-- rename : security/manager/ssl/tests/unit/tlsserver/default-ee.der => security/manager/boot/src/default-ee.der
Bug 998057: Add tests for certificate pinning (r=cviecco,dkeeler)
Bug 1002696 - Minimum set of changes to make genHPKPStaticPins.js productionizable. r=cviecco, dkeeler
--HG-- rename : security/manager/boot/src/PreloadedHPKPins.json => security/manager/tools/PreloadedHPKPins.json rename : security/manager/boot/src/genHPKPStaticPins.js => security/manager/tools/genHPKPStaticPins.js
Bug 951315 - Add telemetry to PK pinning. r=dkeeler
Bug 1006107 - Disable pining by default, setup pinning for *.addons.mozilla.org. r=dkeeler
Tor project: only patching two files: security/manager/ssl/src/nsNSSComponent.cpp netwerk/base/public/security-prefs.js
--HG-- extra : rebase_source : 93b1dbd5dc31490424060729a3941deffa8ee1d5
Bug 772756: Implement sha1 support, import Chrome's pinsets wholesale, add test mode (r=cviecco,keeler)
Tor project, we only patch: security/manager/ssl/tests/unit/test_pinning.js security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp security/manager/ssl/tests/unit/tlsserver/default-ee.der security/manager/ssl/tests/unit/tlsserver/generate_certs.sh security/manager/ssl/tests/unit/tlsserver/other-test-ca.der security/manager/ssl/tests/unit/tlsserver/test-ca.der
Bug 1009720: Telemetry for CERT_PINNING_TEST_RESULTS (r=keeler)
Bug 1007844: Implement per-host telemetry for pin violations for AMO and aus4 (r=keeler)
Only patching toolkit/components/telemetry/Histograms.json
Bug 1011269: Add CertVerifier::pinningEnforceTestMode (r=keeler)
Tor project, only commit: security/certverifier/CertVerifier.cpp security/certverifier/CertVerifier.h security/manager/ssl/src/nsNSSComponent.cpp
Bug 1012882: Restrict pinning to desktop (r=keeler)
Tor Bug #11955: Backport certificate pinning
Bring the following files up to date:
security/manager/boot/src/PublicKeyPinningService.cpp security/manager/boot/src/PublicKeyPinningService.h security/manager/boot/src/StaticHPKPins.h security/manager/ssl/tests/unit/test_pinning.js security/manager/tools/PreloadedHPKPins.json security/manager/tools/genHPKPStaticPins.js security/pkix/include/pkix/Time.h security/pkix/lib/pkixtime.cpp --- .gitignore | 2 +- browser/app/profile/firefox.js | 3 + modules/libpref/src/init/all.js | 3 + security/apps/AppTrustDomain.h | 2 + security/certverifier/CertVerifier.cpp | 225 +++- security/certverifier/CertVerifier.h | 17 +- security/certverifier/NSSCertDBTrustDomain.cpp | 35 +- security/certverifier/NSSCertDBTrustDomain.h | 6 +- security/certverifier/moz.build | 1 + .../manager/boot/src/PublicKeyPinningService.cpp | 306 ++++++ .../boot/src/PublicKeyPinningService.cpp.rej | 11 + .../manager/boot/src/PublicKeyPinningService.h | 35 + security/manager/boot/src/StaticHPKPins.h | 1095 ++++++++++++++++++++ security/manager/boot/src/moz.build | 6 + .../manager/ssl/src/SSLServerCertVerification.cpp | 5 +- security/manager/ssl/src/SharedCertVerifier.h | 6 +- security/manager/ssl/src/nsCMS.cpp | 5 +- security/manager/ssl/src/nsNSSCertificate.cpp | 13 +- security/manager/ssl/src/nsNSSCertificateDB.cpp | 19 +- security/manager/ssl/src/nsNSSComponent.cpp | 12 +- security/manager/ssl/src/nsUsageArrayHelper.cpp | 5 +- security/manager/ssl/tests/unit/head_psm.js | 1 + .../manager/ssl/tests/unit/test_cert_overrides.js | 2 +- security/manager/ssl/tests/unit/test_pinning.js | 182 ++++ security/manager/ssl/tests/unit/tlsserver/cert8.db | Bin 65536 -> 65536 bytes .../ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp | 11 + .../ssl/tests/unit/tlsserver/default-ee.der | Bin 527 -> 639 bytes .../ssl/tests/unit/tlsserver/generate_certs.sh | 17 +- security/manager/ssl/tests/unit/tlsserver/key3.db | Bin 49152 -> 57344 bytes .../ssl/tests/unit/tlsserver/other-test-ca.der | Bin 452 -> 452 bytes .../manager/ssl/tests/unit/tlsserver/secmod.db | Bin 16384 -> 16384 bytes .../manager/ssl/tests/unit/tlsserver/test-ca.der | Bin 440 -> 440 bytes security/manager/ssl/tests/unit/xpcshell.ini | 4 + security/manager/tools/PreloadedHPKPins.json | 247 +++++ security/manager/tools/genHPKPStaticPins.js | 576 ++++++++++ security/pkix/include/pkix/Result.h | 174 ++++ security/pkix/include/pkix/Time.h | 126 +++ security/pkix/include/pkix/pkixtypes.h | 5 + security/pkix/lib/pkixbuild.cpp | 24 + security/pkix/lib/pkixtime.cpp | 70 ++ toolkit/components/telemetry/Histograms.json | 32 + 41 files changed, 3198 insertions(+), 85 deletions(-)
diff --git a/.gitignore b/.gitignore index 8df1754..6d17511 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ ID .*.sw[a-z]
# User files that may appear at the root -/.mozconfig* +#/.mozconfig* /mozconfig /configure /config.cache diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 2ec7f9a..b61756d 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1484,6 +1484,9 @@ pref("security.csp.speccompliant", true); // Block insecure active content on https pages pref("security.mixed_content.block_active_content", true);
+// 1 = allow MITM for certificate pinning checks. +pref("security.cert_pinning.enforcement_level", 1); + // Override the Gecko-default value of false for Firefox. pref("plain_text.wrap_long_lines", true);
diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 8efad8f..8a389e3 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -1566,6 +1566,9 @@ pref("security.csp.experimentalEnabled", false); pref("security.mixed_content.block_active_content", false); pref("security.mixed_content.block_display_content", false);
+// Disable pinning checks by default. +pref("security.cert_pinning.enforcement_level", 0); + // Modifier key prefs: default to Windows settings, // menu access key = alt, accelerator key = control. // Use 17 for Ctrl, 18 for Alt, 224 for Meta, 91 for Win, 0 for none. Mac settings in macprefs.js diff --git a/security/apps/AppTrustDomain.h b/security/apps/AppTrustDomain.h index 875c1db..d7e4734 100644 --- a/security/apps/AppTrustDomain.h +++ b/security/apps/AppTrustDomain.h @@ -35,6 +35,8 @@ public: /*const*/ CERTCertificate* issuerCertToDup, PRTime time, /*optional*/ const SECItem* stapledOCSPresponse); + SECStatus IsChainValid(const CERTCertList* certChain) { return SECSuccess; } + private: void* mPinArg; // non-owning! mozilla::pkix::ScopedCERTCertificate mTrustedRoot; diff --git a/security/certverifier/CertVerifier.cpp b/security/certverifier/CertVerifier.cpp index b8b84d7..b2db9fc 100644 --- a/security/certverifier/CertVerifier.cpp +++ b/security/certverifier/CertVerifier.cpp @@ -11,9 +11,11 @@ #include "pkix/pkix.h" #include "ExtendedValidation.h" #include "NSSCertDBTrustDomain.h" +#include "PublicKeyPinningService.h" #include "cert.h" #include "ocsp.h" #include "secerr.h" +#include "pk11pub.h" #include "prerror.h" #include "sslerr.h"
@@ -38,7 +40,8 @@ CertVerifier::CertVerifier(implementation_config ic, #endif ocsp_download_config odc, ocsp_strict_config osc, - ocsp_get_config ogc) + ocsp_get_config ogc, + pinning_enforcement_config pel) : mImplementation(ic) #ifndef NSS_NO_LIBPKIX , mMissingCertDownloadEnabled(mcdc == missing_cert_download_on) @@ -47,6 +50,7 @@ CertVerifier::CertVerifier(implementation_config ic, , mOCSPDownloadEnabled(odc == ocsp_on) , mOCSPStrict(osc == ocsp_strict) , mOCSPGETEnabled(ogc == ocsp_get_enabled) + , mPinningEnforcementLevel(pel) { }
@@ -64,7 +68,6 @@ InitCertVerifierLog() #endif }
-#if 0 // Once we migrate to mozilla::pkix or change the overridable error // logic this will become unnecesary. static SECStatus @@ -95,23 +98,102 @@ insertErrorIntoVerifyLog(CERTCertificate* cert, const PRErrorCode err,
return SECSuccess; } -#endif + +SECStatus +IsCertBuiltInRoot(CERTCertificate* cert, bool& result) { + result = false; + ScopedPtr<PK11SlotList, PK11_FreeSlotList> slots; + slots = PK11_GetAllSlotsForCert(cert, nullptr); + if (!slots) { + if (PORT_GetError() == SEC_ERROR_NO_TOKEN) { + // no list + return SECSuccess; + } + return SECFailure; + } + for (PK11SlotListElement* le = slots->head; le; le = le->next) { + char* token = PK11_GetTokenName(le->slot); + PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, + ("BuiltInRoot? subject=%s token=%s",cert->subjectName, token)); + if (strcmp("Builtin Object Token", token) == 0) { + result = true; + return SECSuccess; + } + } + return SECSuccess; +} + +struct ChainValidationCallbackState +{ + const char* hostname; + const CertVerifier::pinning_enforcement_config pinningEnforcementLevel; + const SECCertificateUsage usage; + const PRTime time; +};
SECStatus chainValidationCallback(void* state, const CERTCertList* certList, PRBool* chainOK) { + ChainValidationCallbackState* callbackState = + reinterpret_cast<ChainValidationCallbackState*>(state); + *chainOK = PR_FALSE;
- PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("verifycert: Inside the Callback \n")); + PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, + ("verifycert: Inside the Callback \n"));
// On sanity failure we fail closed. if (!certList) { - PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("verifycert: Short circuit, callback, " - "sanity check failed \n")); + PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, + ("verifycert: Short circuit, callback, sanity check failed \n")); + PR_SetError(PR_INVALID_STATE_ERROR, 0); + return SECFailure; + } + if (!callbackState) { + PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, + ("verifycert: Short circuit, callback, no state! \n")); PR_SetError(PR_INVALID_STATE_ERROR, 0); return SECFailure; } - *chainOK = PR_TRUE; + + if (callbackState->usage != certificateUsageSSLServer || + callbackState->pinningEnforcementLevel == CertVerifier::pinningDisabled) { + PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, + ("verifycert: Callback shortcut pel=%d \n", + callbackState->pinningEnforcementLevel)); + *chainOK = PR_TRUE; + return SECSuccess; + } + + for (CERTCertListNode* node = CERT_LIST_HEAD(certList); + !CERT_LIST_END(node, certList); + node = CERT_LIST_NEXT(node)) { + CERTCertificate* currentCert = node->cert; + if (CERT_LIST_END(CERT_LIST_NEXT(node), certList)) { + bool isBuiltInRoot = false; + SECStatus srv = IsCertBuiltInRoot(currentCert, isBuiltInRoot); + if (srv != SECSuccess) { + PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Is BuiltInRoot failure")); + return srv; + } + // If desired, the user can enable "allow user CA MITM mode", in which + // case key pinning is not enforced for certificates that chain to trust + // anchors that are not in Mozilla's root program + if (!isBuiltInRoot && + (callbackState->pinningEnforcementLevel == + CertVerifier::pinningAllowUserCAMITM)) { + *chainOK = PR_TRUE; + return SECSuccess; + } + } + } + + const bool enforceTestMode = (callbackState->pinningEnforcementLevel == + CertVerifier::pinningEnforceTestMode); + *chainOK = PublicKeyPinningService:: + ChainHasValidPins(certList, callbackState->hostname, callbackState->time, + enforceTestMode); + return SECSuccess; }
@@ -120,42 +202,41 @@ ClassicVerifyCert(CERTCertificate* cert, const SECCertificateUsage usage, const PRTime time, void* pinArg, + ChainValidationCallbackState* callbackState, /*optional out*/ ScopedCERTCertList* validationChain, /*optional out*/ CERTVerifyLog* verifyLog) { SECStatus rv; SECCertUsage enumUsage; - if (validationChain) { - switch(usage){ - case certificateUsageSSLClient: - enumUsage = certUsageSSLClient; - break; - case certificateUsageSSLServer: - enumUsage = certUsageSSLServer; - break; - case certificateUsageSSLCA: - enumUsage = certUsageSSLCA; - break; - case certificateUsageEmailSigner: - enumUsage = certUsageEmailSigner; - break; - case certificateUsageEmailRecipient: - enumUsage = certUsageEmailRecipient; - break; - case certificateUsageObjectSigner: - enumUsage = certUsageObjectSigner; - break; - case certificateUsageVerifyCA: - enumUsage = certUsageVerifyCA; - break; - case certificateUsageStatusResponder: - enumUsage = certUsageStatusResponder; - break; - default: - PR_NOT_REACHED("unexpected usage"); - PORT_SetError(SEC_ERROR_INVALID_ARGS); - return SECFailure; - } + switch (usage) { + case certificateUsageSSLClient: + enumUsage = certUsageSSLClient; + break; + case certificateUsageSSLServer: + enumUsage = certUsageSSLServer; + break; + case certificateUsageSSLCA: + enumUsage = certUsageSSLCA; + break; + case certificateUsageEmailSigner: + enumUsage = certUsageEmailSigner; + break; + case certificateUsageEmailRecipient: + enumUsage = certUsageEmailRecipient; + break; + case certificateUsageObjectSigner: + enumUsage = certUsageObjectSigner; + break; + case certificateUsageVerifyCA: + enumUsage = certUsageVerifyCA; + break; + case certificateUsageStatusResponder: + enumUsage = certUsageStatusResponder; + break; + default: + PR_NOT_REACHED("unexpected usage"); + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; } if (usage == certificateUsageSSLServer) { // SSL server cert verification has always used CERT_VerifyCert, so we @@ -168,13 +249,38 @@ ClassicVerifyCert(CERTCertificate* cert, rv = CERT_VerifyCertificate(CERT_GetDefaultCertDB(), cert, true, usage, time, pinArg, verifyLog, nullptr); } - if (rv == SECSuccess && validationChain) { - PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("VerifyCert: getting chain in 'classic' \n")); - *validationChain = CERT_GetCertChainFromCert(cert, time, enumUsage); - if (!*validationChain) { - rv = SECFailure; + + if (rv == SECSuccess && + (validationChain || usage == certificateUsageSSLServer)) { + PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, + ("VerifyCert: getting chain in 'classic' \n")); + ScopedCERTCertList certChain(CERT_GetCertChainFromCert(cert, time, + enumUsage)); + if (!certChain) { + return SECFailure; + } + if (usage == certificateUsageSSLServer) { + PRBool chainOK = PR_FALSE; + SECStatus srv = chainValidationCallback(callbackState, certChain.get(), + &chainOK); + if (srv != SECSuccess) { + return srv; + } + if (chainOK != PR_TRUE) { + if (verifyLog) { + insertErrorIntoVerifyLog(cert, + SEC_ERROR_APPLICATION_CALLBACK_ERROR, + verifyLog); + } + PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0); // same as libpkix + return SECFailure; + } + } + if (rv == SECSuccess && validationChain) { + *validationChain = certChain.release(); } } + return rv; }
@@ -227,6 +333,7 @@ CertVerifier::MozillaPKIXVerifyCert( const PRTime time, void* pinArg, const Flags flags, + ChainValidationCallbackState* callbackState, /*optional*/ const SECItem* stapledOCSPResponse, /*optional out*/ mozilla::pkix::ScopedCERTCertList* validationChain, /*optional out*/ SECOidTag* evOidPolicy) @@ -249,6 +356,10 @@ CertVerifier::MozillaPKIXVerifyCert( return SECFailure; }
+ CERTChainVerifyCallback callbackContainer; + callbackContainer.isChainValid = chainValidationCallback; + callbackContainer.isChainValidArg = callbackState; + NSSCertDBTrustDomain::OCSPFetching ocspFetching = !mOCSPDownloadEnabled || (flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::NeverFetchOCSP @@ -295,7 +406,7 @@ CertVerifier::MozillaPKIXVerifyCert( ocspFetching == NSSCertDBTrustDomain::NeverFetchOCSP ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV : NSSCertDBTrustDomain::FetchOCSPForEV, - mOCSPCache, pinArg); + mOCSPCache, pinArg, &callbackContainer); rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time, KeyUsage::digitalSignature, // ECDHE/DHE KeyUsage::keyEncipherment, // RSA @@ -321,7 +432,7 @@ CertVerifier::MozillaPKIXVerifyCert(
// Now try non-EV. NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache, - pinArg); + pinArg, &callbackContainer); rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time, KeyUsage::digitalSignature, // (EC)DHE KeyUsage::keyEncipherment, // RSA @@ -443,19 +554,25 @@ CertVerifier::MozillaPKIXVerifyCert(
SECStatus CertVerifier::VerifyCert(CERTCertificate* cert, - /*optional*/ const SECItem* stapledOCSPResponse, const SECCertificateUsage usage, const PRTime time, void* pinArg, + const char* hostname, const Flags flags, + /*optional in*/ const SECItem* stapledOCSPResponse, /*optional out*/ ScopedCERTCertList* validationChain, /*optional out*/ SECOidTag* evOidPolicy, /*optional out*/ CERTVerifyLog* verifyLog) { + ChainValidationCallbackState callbackState = { hostname, + mPinningEnforcementLevel, + usage, + time }; + if (mImplementation == mozillapkix) { return MozillaPKIXVerifyCert(cert, usage, time, pinArg, flags, - stapledOCSPResponse, validationChain, - evOidPolicy); + &callbackState, stapledOCSPResponse, + validationChain, evOidPolicy); }
if (!cert) @@ -581,7 +698,7 @@ CertVerifier::VerifyCert(CERTCertificate* cert, CERTChainVerifyCallback callbackContainer; if (usage == certificateUsageSSLServer) { callbackContainer.isChainValid = chainValidationCallback; - callbackContainer.isChainValidArg = nullptr; + callbackContainer.isChainValidArg = &callbackState; cvin[i].type = cert_pi_chainVerifyCallback; cvin[i].value.pointer.chainVerifyCallback = &callbackContainer; ++i; @@ -685,8 +802,8 @@ CertVerifier::VerifyCert(CERTCertificate* cert, if (mImplementation == classic) { // XXX: we do not care about the localOnly flag (currently) as the // caller that wants localOnly should disable and reenable the fetching. - return ClassicVerifyCert(cert, usage, time, pinArg, validationChain, - verifyLog); + return ClassicVerifyCert(cert, usage, time, pinArg, &callbackState, + validationChain, verifyLog); }
#ifdef NSS_NO_LIBPKIX @@ -826,9 +943,9 @@ CertVerifier::VerifySSLServerCert(CERTCertificate* peerCert, // CreateCertErrorRunnable assumes that CERT_VerifyCertName is only called // if VerifyCert succeeded. ScopedCERTCertList validationChain; - SECStatus rv = VerifyCert(peerCert, stapledOCSPResponse, - certificateUsageSSLServer, time, - pinarg, 0, &validationChain, evOidPolicy); + SECStatus rv = VerifyCert(peerCert, certificateUsageSSLServer, time, pinarg, + hostname, 0, stapledOCSPResponse, &validationChain, + evOidPolicy, nullptr); if (rv != SECSuccess) { return rv; } diff --git a/security/certverifier/CertVerifier.h b/security/certverifier/CertVerifier.h index 09ed4b0..270e9a1 100644 --- a/security/certverifier/CertVerifier.h +++ b/security/certverifier/CertVerifier.h @@ -12,6 +12,8 @@
namespace mozilla { namespace psm {
+struct ChainValidationCallbackState; + class CertVerifier { public: @@ -24,11 +26,12 @@ public: // *evOidPolicy == SEC_OID_UNKNOWN means the cert is NOT EV // Only one usage per verification is supported. SECStatus VerifyCert(CERTCertificate* cert, - /*optional*/ const SECItem* stapledOCSPResponse, const SECCertificateUsage usage, const PRTime time, void* pinArg, + const char* hostname, const Flags flags = 0, + /*optional in*/ const SECItem* stapledOCSPResponse = nullptr, /*optional out*/ mozilla::pkix::ScopedCERTCertList* validationChain = nullptr, /*optional out*/ SECOidTag* evOidPolicy = nullptr , /*optional out*/ CERTVerifyLog* verifyLog = nullptr); @@ -52,6 +55,13 @@ public: mozillapkix = 2 };
+ enum pinning_enforcement_config { + pinningDisabled = 0, + pinningAllowUserCAMITM = 1, + pinningStrict = 2, + pinningEnforceTestMode = 3 + }; + enum missing_cert_download_config { missing_cert_download_off = 0, missing_cert_download_on }; enum crl_download_config { crl_local_only = 0, crl_download_allowed }; enum ocsp_download_config { ocsp_off = 0, ocsp_on }; @@ -65,7 +75,8 @@ public: missing_cert_download_config ac, crl_download_config cdc, #endif ocsp_download_config odc, ocsp_strict_config osc, - ocsp_get_config ogc); + ocsp_get_config ogc, + pinning_enforcement_config pinningEnforcementLevel); ~CertVerifier();
void ClearOCSPCache() { mOCSPCache.Clear(); } @@ -78,6 +89,7 @@ public: const bool mOCSPDownloadEnabled; const bool mOCSPStrict; const bool mOCSPGETEnabled; + const pinning_enforcement_config mPinningEnforcementLevel;
private: SECStatus MozillaPKIXVerifyCert(CERTCertificate* cert, @@ -85,6 +97,7 @@ private: const PRTime time, void* pinArg, const Flags flags, + ChainValidationCallbackState* callbackState, /*optional*/ const SECItem* stapledOCSPResponse, /*optional out*/ mozilla::pkix::ScopedCERTCertList* validationChain, /*optional out*/ SECOidTag* evOidPolicy); diff --git a/security/certverifier/NSSCertDBTrustDomain.cpp b/security/certverifier/NSSCertDBTrustDomain.cpp index 9bed2ce..fd2e363 100644 --- a/security/certverifier/NSSCertDBTrustDomain.cpp +++ b/security/certverifier/NSSCertDBTrustDomain.cpp @@ -43,11 +43,13 @@ typedef ScopedPtr<SECMODModule, SECMOD_DestroyModule> ScopedSECMODModule; NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType, OCSPFetching ocspFetching, OCSPCache& ocspCache, - void* pinArg) + void* pinArg, + CERTChainVerifyCallback* checkChainCallback) : mCertDBTrustType(certDBTrustType) , mOCSPFetching(ocspFetching) , mOCSPCache(ocspCache) , mPinArg(pinArg) + , mCheckChainCallback(checkChainCallback) { }
@@ -475,6 +477,37 @@ NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse( return rv; }
+SECStatus +NSSCertDBTrustDomain::IsChainValid(const CERTCertList* certChain) { + SECStatus rv = SECFailure; + + PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, + ("NSSCertDBTrustDomain: Top of IsChainValid mCheckCallback=%p", + mCheckChainCallback)); + + if (!mCheckChainCallback) { + return SECSuccess; + } + if (!mCheckChainCallback->isChainValid) { + PR_SetError(SEC_ERROR_INVALID_ARGS, 0); + return SECFailure; + } + PRBool chainOK; + rv = (mCheckChainCallback->isChainValid)(mCheckChainCallback->isChainValidArg, + certChain, &chainOK); + if (rv != SECSuccess) { + return rv; + } + // rv = SECSuccess only implies successful call, now is time + // to check the chain check status + // we should only return success if the chain is valid + if (chainOK) { + return SECSuccess; + } + PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0); + return SECFailure; +} + namespace {
static char* diff --git a/security/certverifier/NSSCertDBTrustDomain.h b/security/certverifier/NSSCertDBTrustDomain.h index 979c3e2..c2f211d 100644 --- a/security/certverifier/NSSCertDBTrustDomain.h +++ b/security/certverifier/NSSCertDBTrustDomain.h @@ -57,7 +57,8 @@ public: LocalOnlyOCSPForEV = 4, }; NSSCertDBTrustDomain(SECTrustType certDBTrustType, OCSPFetching ocspFetching, - OCSPCache& ocspCache, void* pinArg); + OCSPCache& ocspCache, void* pinArg, + CERTChainVerifyCallback* checkChainCallback = nullptr);
virtual SECStatus FindPotentialIssuers( const SECItem* encodedIssuerName, @@ -78,6 +79,8 @@ public: PRTime time, /*optional*/ const SECItem* stapledOCSPResponse);
+ virtual SECStatus IsChainValid(const CERTCertList* certChain); + private: enum EncodedResponseSource { ResponseIsFromNetwork = 1, @@ -93,6 +96,7 @@ private: const OCSPFetching mOCSPFetching; OCSPCache& mOCSPCache; // non-owning! void* mPinArg; // non-owning! + CERTChainVerifyCallback* mCheckChainCallback; // non-owning! };
} } // namespace mozilla::psm diff --git a/security/certverifier/moz.build b/security/certverifier/moz.build index 5e99b12..434c3cc 100644 --- a/security/certverifier/moz.build +++ b/security/certverifier/moz.build @@ -17,6 +17,7 @@ if not CONFIG['NSS_NO_EV_CERTS']: ]
LOCAL_INCLUDES += [ + '../manager/boot/src', '../manager/ssl/src', '../pkix/include', ] diff --git a/security/manager/boot/src/PublicKeyPinningService.cpp b/security/manager/boot/src/PublicKeyPinningService.cpp new file mode 100644 index 0000000..82abaaf --- /dev/null +++ b/security/manager/boot/src/PublicKeyPinningService.cpp @@ -0,0 +1,306 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PublicKeyPinningService.h" +#include "pkix/nullptr.h" +#include "StaticHPKPins.h" // autogenerated by genHPKPStaticpins.js + +#include "cert.h" +#include "mozilla/Base64.h" +#include "mozilla/Telemetry.h" +#include "nsString.h" +#include "nssb64.h" +#include "pkix/pkixtypes.h" +#include "prlog.h" +#include "ScopedNSSTypes.h" +#include "seccomon.h" +#include "sechash.h" + +using namespace mozilla; +using namespace mozilla::pkix; +using namespace mozilla::psm; + +#if defined(PR_LOGGING) +PRLogModuleInfo* gPublicKeyPinningLog = + PR_NewLogModule("PublicKeyPinningService"); +#endif + +/** + Computes in the location specified by base64Out the SHA256 digest + of the DER Encoded subject Public Key Info for the given cert +*/ +static SECStatus +GetBase64HashSPKI(const CERTCertificate* cert, SECOidTag hashType, + nsACString& hashSPKIDigest) +{ + hashSPKIDigest.Truncate(); + Digest digest; + nsresult rv = digest.DigestBuf(hashType, cert->derPublicKey.data, + cert->derPublicKey.len); + if (NS_WARN_IF(NS_FAILED(rv))) { + return SECFailure; + } + rv = Base64Encode(nsDependentCSubstring( + reinterpret_cast<const char*>(digest.get().data), + digest.get().len), + hashSPKIDigest); + if (NS_WARN_IF(NS_FAILED(rv))) { + return SECFailure; + } + return SECSuccess; +} + +/* + * Returns true if a given cert matches any hashType fingerprints from the + * given pinset, false otherwise. + */ +static bool +EvalCertWithHashType(const CERTCertificate* cert, SECOidTag hashType, + const StaticFingerprints* fingerprints) +{ + if (!fingerprints) { + PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, + ("pkpin: No hashes found for hash type: %d\n", hashType)); + return false; + } + + nsAutoCString base64Out; + SECStatus srv = GetBase64HashSPKI(cert, hashType, base64Out); + if (srv != SECSuccess) { + PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, + ("pkpin: GetBase64HashSPKI failed!\n")); + return false; + } + + for (size_t i = 0; i < fingerprints->size; i++) { + if (base64Out.Equals(fingerprints->data[i])) { + PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, + ("pkpin: found pin base_64 ='%s'\n", base64Out.get())); + return true; + } + } + return false; +} + +/* + * Returns true if a given chain matches any hashType fingerprints from the + * given pinset, false otherwise. + */ +static bool +EvalChainWithHashType(const CERTCertList* certList, SECOidTag hashType, + const StaticPinset* pinset) +{ + CERTCertificate* currentCert; + + const StaticFingerprints* fingerprints = nullptr; + if (hashType == SEC_OID_SHA256) { + fingerprints = pinset->sha256; + } else if (hashType == SEC_OID_SHA1) { + fingerprints = pinset->sha1; + } + if (!fingerprints) { + return false; + } + + CERTCertListNode* node; + for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList); + node = CERT_LIST_NEXT(node)) { + currentCert = node->cert; + PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, + ("pkpin: certArray subject: '%s'\n", + currentCert->subjectName)); + PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, + ("pkpin: certArray common_name: '%s'\n", + CERT_GetCommonName(&(currentCert->issuer)))); + if (EvalCertWithHashType(currentCert, hashType, fingerprints)) { + return true; + } + } + PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, ("pkpin: no matches found\n")); + return false; +} + +/** + * Given a pinset and certlist, return true if one of the certificates on + * the list matches a fingerprint in the pinset, false otherwise. + */ +static bool +EvalChainWithPinset(const CERTCertList* certList, + const StaticPinset* pinset) { + // SHA256 is more trustworthy, try that first. + if (EvalChainWithHashType(certList, SEC_OID_SHA256, pinset)) { + return true; + } + return EvalChainWithHashType(certList, SEC_OID_SHA1, pinset); +} + +/** + Comparator for the is public key pinned host. +*/ +static int +TransportSecurityPreloadCompare(const void *key, const void *entry) { + const char *keyStr = reinterpret_cast<const char *>(key); + const TransportSecurityPreload *preloadEntry = + reinterpret_cast<const TransportSecurityPreload *>(entry); + + return strcmp(keyStr, preloadEntry->mHost); +} + +/** + * Check PKPins on the given certlist against the specified hostname + */ +static bool +CheckPinsForHostname(const CERTCertList *certList, const char *hostname, + bool enforceTestMode) +{ + if (!certList) { + return false; + } + if (!hostname || hostname[0] == 0) { + return false; + } + + TransportSecurityPreload *foundEntry = nullptr; + char *evalHost = const_cast<char*>(hostname); + char *evalPart; + // Notice how the (xx = strchr) prevents pins for unqualified domain names. + while (!foundEntry && (evalPart = strchr(evalHost, '.'))) { + PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, + ("pkpin: Querying pinsets for host: '%s'\n", evalHost)); + foundEntry = (TransportSecurityPreload *)bsearch(evalHost, + kPublicKeyPinningPreloadList, + sizeof(kPublicKeyPinningPreloadList) / sizeof(TransportSecurityPreload), + sizeof(TransportSecurityPreload), + TransportSecurityPreloadCompare); + if (foundEntry) { + PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, + ("pkpin: Found pinset for host: '%s'\n", evalHost)); + if (evalHost != hostname) { + if (!foundEntry->mIncludeSubdomains) { + // Does not apply to this host, continue iterating + foundEntry = nullptr; + } + } + } else { + PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, + ("pkpin: Didn't find pinset for host: '%s'\n", evalHost)); + } + // Add one for '.' + evalHost = evalPart + 1; + } + + if (foundEntry && foundEntry->pinset) { + bool result = EvalChainWithPinset(certList, foundEntry->pinset); + bool retval = result; + Telemetry::ID histogram = foundEntry->mIsMoz + ? Telemetry::CERT_PINNING_MOZ_RESULTS + : Telemetry::CERT_PINNING_RESULTS; + if (foundEntry->mTestMode) { + histogram = foundEntry->mIsMoz + ? Telemetry::CERT_PINNING_MOZ_TEST_RESULTS + : Telemetry::CERT_PINNING_TEST_RESULTS; + if (!enforceTestMode) { + retval = true; + } + } + // We can collect per-host pinning violations for this host because it is + // operationally critical to Firefox. + if (foundEntry->mId != kUnknownId) { + int32_t bucket = foundEntry->mId * 2 + (result ? 1 : 0); + histogram = foundEntry->mTestMode + ? Telemetry::CERT_PINNING_MOZ_TEST_RESULTS_BY_HOST + : Telemetry::CERT_PINNING_MOZ_RESULTS_BY_HOST; + Telemetry::Accumulate(histogram, bucket); + } else { + Telemetry::Accumulate(histogram, result ? 1 : 0); + } + PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, + ("pkpin: Pin check %s for %s host '%s' (mode=%s)\n", + result ? "passed" : "failed", + foundEntry->mIsMoz ? "mozilla" : "non-mozilla", + hostname, foundEntry->mTestMode ? "test" : "production")); + return retval; + } + return true; // No pinning information for this hostname +} + +/** + * Extract all the DNS names for a host (including CN) and evaluate the + * certifiate pins against all of them (Currently is an OR so we stop + * evaluating at the first OK pin). + */ +static bool +CheckChainAgainstAllNames(const CERTCertList* certList, bool enforceTestMode) +{ + PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, + ("pkpin: top of checkChainAgainstAllNames")); + CERTCertListNode* node = CERT_LIST_HEAD(certList); + if (!node) { + return false; + } + CERTCertificate* cert = node->cert; + if (!cert) { + return false; + } + + ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + if (!arena) { + return false; + } + + bool hasValidPins = false; + CERTGeneralName* nameList; + CERTGeneralName* currentName; + nameList = CERT_GetConstrainedCertificateNames(cert, arena.get(), PR_TRUE); + if (!nameList) { + return false; + } + + currentName = nameList; + do { + if (currentName->type == certDNSName + && currentName->name.other.data[0] != 0) { + // no need to cleaup, as the arena cleanup will do + char *hostName = (char *)PORT_ArenaAlloc(arena.get(), + currentName->name.other.len + 1); + if (!hostName) { + break; + } + // We use a temporary buffer as the hostname as returned might not be + // null terminated. + hostName[currentName->name.other.len] = 0; + memcpy(hostName, currentName->name.other.data, + currentName->name.other.len); + if (!hostName[0]) { + // cannot call CheckPinsForHostname on empty or null hostname + break; + } + if (CheckPinsForHostname(certList, hostName, enforceTestMode)) { + hasValidPins = true; + break; + } + } + currentName = CERT_GetNextGeneralName(currentName); + } while (currentName != nameList); + + return hasValidPins; +} + +bool +PublicKeyPinningService::ChainHasValidPins(const CERTCertList* certList, + const char* hostname, + const PRTime time, + bool enforceTestMode) +{ + if (!certList) { + return false; + } + if (time > kPreloadPKPinsExpirationTime) { + return true; + } + if (!hostname || hostname[0] == 0) { + return CheckChainAgainstAllNames(certList, enforceTestMode); + } + return CheckPinsForHostname(certList, hostname, enforceTestMode); +} diff --git a/security/manager/boot/src/PublicKeyPinningService.cpp.rej b/security/manager/boot/src/PublicKeyPinningService.cpp.rej new file mode 100644 index 0000000..e88f91b --- /dev/null +++ b/security/manager/boot/src/PublicKeyPinningService.cpp.rej @@ -0,0 +1,11 @@ +diff a/security/manager/boot/src/PublicKeyPinningService.cpp b/security/manager/boot/src/PublicKeyPinningService.cpp (rejected hunks) +@@ -296,7 +296,8 @@ PublicKeyPinningService::ChainHasValidPins(const CERTCertList* certList, + if (!certList) { + return false; + } +- if (time > TimeFromElapsedSecondsAD(kPreloadPKPinsExpirationTime)) { ++ if (time > TimeFromEpochInSeconds(kPreloadPKPinsExpirationTime / ++ PR_USEC_PER_SEC)) { + return true; + } + if (!hostname || hostname[0] == 0) { diff --git a/security/manager/boot/src/PublicKeyPinningService.h b/security/manager/boot/src/PublicKeyPinningService.h new file mode 100644 index 0000000..978c5ec --- /dev/null +++ b/security/manager/boot/src/PublicKeyPinningService.h @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef PublicKeyPinningService_h +#define PublicKeyPinningService_h + +#include "cert.h" + +namespace mozilla { +namespace psm { + +class PublicKeyPinningService +{ +public: + /** + * Returns true if the given (host, certList) passes pinning checks, + * false otherwise. If the host is pinned, return true if one of the keys in + * the given certificate chain matches the pin set specified by the + * hostname. If the hostname is null or empty evaluate against all the + * possible names for the EE cert (Common Name (CN) plus all DNS Name: + * subject Alt Name entries). The certList's head is the EE cert and the + * tail is the trust anchor. + * Note: if an alt name is a wildcard, it won't necessarily find a pinset + * that would otherwise be valid for it + */ + static bool ChainHasValidPins(const CERTCertList* certList, + const char* hostname, + const PRTime, + bool enforceTestMode); +}; + +}} // namespace mozilla::psm + +#endif // PublicKeyPinningServiceService_h diff --git a/security/manager/boot/src/StaticHPKPins.h b/security/manager/boot/src/StaticHPKPins.h new file mode 100644 index 0000000..4506489 --- /dev/null +++ b/security/manager/boot/src/StaticHPKPins.h @@ -0,0 +1,1095 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/*****************************************************************************/ +/* This is an automatically generated file. If you're not */ +/* PublicKeyPinningService.cpp, you shouldn't be #including it. */ +/*****************************************************************************/ +#include <stdint.h> +/* AddTrust External Root */ +static const char kAddTrust_External_RootFingerprint[] = + "lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU="; + +/* AddTrust Low-Value Services Root */ +static const char kAddTrust_Low_Value_Services_RootFingerprint[] = + "BStocQfshOhzA4JFLsKidFF0XXSFpX1vRk4Np6G2ryo="; + +/* AddTrust Public Services Root */ +static const char kAddTrust_Public_Services_RootFingerprint[] = + "OGHXtpYfzbISBFb/b8LrdwSxp0G0vZM6g3b14ZFcppg="; + +/* AddTrust Qualified Certificates Root */ +static const char kAddTrust_Qualified_Certificates_RootFingerprint[] = + "xzr8Lrp3DQy8HuQfJStS6Kk9ErctzOwDHY2DnL+Bink="; + +/* AffirmTrust Commercial */ +static const char kAffirmTrust_CommercialFingerprint[] = + "bEZLmlsjOl6HTadlwm8EUBDS3c/0V5TwtMfkqvpQFJU="; + +/* AffirmTrust Networking */ +static const char kAffirmTrust_NetworkingFingerprint[] = + "lAcq0/WPcPkwmOWl9sBMlscQvYSdgxhJGa6Q64kK5AA="; + +/* AffirmTrust Premium */ +static const char kAffirmTrust_PremiumFingerprint[] = + "x/Q7TPW3FWgpT4IrU3YmBfbd0Vyt7Oc56eLDy6YenWc="; + +/* AffirmTrust Premium ECC */ +static const char kAffirmTrust_Premium_ECCFingerprint[] = + "MhmwkRT/SVo+tusAwu/qs0ACrl8KVsdnnqCHo/oDfk8="; + +/* America Online Root Certification Authority 1 */ +static const char kAmerica_Online_Root_Certification_Authority_1Fingerprint[] = + "I4SdCUkj1EpIgbY6sYXpvhWqyO8sMETZNLx/JuLSzWk="; + +/* America Online Root Certification Authority 2 */ +static const char kAmerica_Online_Root_Certification_Authority_2Fingerprint[] = + "/PfamDYD6IhiAw2WE32OEwMbrftNVsH9TKzDOfa9uyo="; + +/* Baltimore CyberTrust Root */ +static const char kBaltimore_CyberTrust_RootFingerprint[] = + "Y9mvm0exBk1JoQ57f9Vm28jKo5lFm/woKcVxrYxu80o="; + +/* COMODO Certification Authority */ +static const char kCOMODO_Certification_AuthorityFingerprint[] = + "AG1751Vd2CAmRCxPGieoDomhmJy4ezREjtIZTBgZbV4="; + +/* COMODO ECC Certification Authority */ +static const char kCOMODO_ECC_Certification_AuthorityFingerprint[] = + "58qRu/uxh4gFezqAcERupSkRYBlBAvfcw7mEjGPLnNU="; + +/* Comodo AAA Services root */ +static const char kComodo_AAA_Services_rootFingerprint[] = + "vRU+17BDT2iGsXvOi76E7TQMcTLXAqj0+jGPdW7L1vM="; + +/* Comodo Secure Services root */ +static const char kComodo_Secure_Services_rootFingerprint[] = + "RpHL/ehKa2BS3b4VK7DCFq4lqG5XR4E9vA8UfzOFcL4="; + +/* Comodo Trusted Services root */ +static const char kComodo_Trusted_Services_rootFingerprint[] = + "4tiR77c4ZpEF1TDeXtcuKyrD9KZweLU0mz/ayklvXrg="; + +/* Cybertrust Global Root */ +static const char kCybertrust_Global_RootFingerprint[] = + "foeCwVDOOVL4AuY2AjpdPpW7XWjjPoWtsroXgSXOvxU="; + +/* DigiCert Assured ID Root CA */ +static const char kDigiCert_Assured_ID_Root_CAFingerprint[] = + "I/Lt/z7ekCWanjD0Cvj5EqXls2lOaThEA0H2Bg4BT/o="; + +/* DigiCert ECC Secure Server CA */ +static const char kDigiCert_ECC_Secure_Server_CAFingerprint[] = + "PZXN3lRAy+8tBKk2Ox6F7jIlnzr2Yzmwqc3JnyfXoCw="; + +/* DigiCert Global Root CA */ +static const char kDigiCert_Global_Root_CAFingerprint[] = + "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; + +/* DigiCert High Assurance EV Root CA */ +static const char kDigiCert_High_Assurance_EV_Root_CAFingerprint[] = + "WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; + +/* End Entity Test Cert */ +static const char kEnd_Entity_Test_CertFingerprint[] = + "pVVgLk2kFI2WWRPwDMIX6YmzFhEW4DXQV/U5gP+feGA="; + +/* Entrust Root Certification Authority */ +static const char kEntrust_Root_Certification_AuthorityFingerprint[] = + "bb+uANN7nNc/j7R95lkXrwDg3d9C286sIMF8AnXuIJU="; + +/* Entrust.net Premium 2048 Secure Server CA */ +static const char kEntrust_net_Premium_2048_Secure_Server_CAFingerprint[] = + "HqPF5D7WbC2imDpCpKebHpBnhs6fG1hiFBmgBGOofTg="; + +/* Equifax Secure CA */ +static const char kEquifax_Secure_CAFingerprint[] = + "/1aAzXOlcD2gSBegdf1GJQanNQbEuBoVg+9UlHjSZHY="; + +/* Equifax Secure Global eBusiness CA */ +static const char kEquifax_Secure_Global_eBusiness_CAFingerprint[] = + "pvH5v4oKndwID7SbHvw9GhwsMtwOE2pbAMlzFvKj3BE="; + +/* Equifax Secure eBusiness CA 1 */ +static const char kEquifax_Secure_eBusiness_CA_1Fingerprint[] = + "JsGNxu6m9jL2drzrodjCtINS8pwtX82oeOCdy4Mt1uU="; + +/* GOOGLE_PIN_AlphaSSL_G2 */ +static const char kGOOGLE_PIN_AlphaSSL_G2Fingerprint[] = + "yxgiWGK++SFB9ySwt3M3qpn5HO0ZLFY5D+h+G/vcT/c="; + +/* GOOGLE_PIN_CryptoCat1 */ +static const char kGOOGLE_PIN_CryptoCat1Fingerprint[] = + "vKaqtTLWmVuXPVJE+0OqN5sRc4VCcSQHI/W3XTDVR24="; + +/* GOOGLE_PIN_EntrustRootEC1 */ +static const char kGOOGLE_PIN_EntrustRootEC1Fingerprint[] = + "/qK31kX7pz11PB7Jp4cMQOH3sMVh6Se5hb9xGGbjbyI="; + +/* GOOGLE_PIN_Entrust_G2 */ +static const char kGOOGLE_PIN_Entrust_G2Fingerprint[] = + "du6FkDdMcVQ3u8prumAo6t3i3G27uMP2EOhR8R0at/U="; + +/* GOOGLE_PIN_Entrust_SSL */ +static const char kGOOGLE_PIN_Entrust_SSLFingerprint[] = + "nsxRNo6G40YPZsKV5JQt1TCA8nseQQr/LRqp1Oa8fnw="; + +/* GOOGLE_PIN_GoDaddySecure */ +static const char kGOOGLE_PIN_GoDaddySecureFingerprint[] = + "MrZLZnJ6IGPkBm87lYywqu5Xal7O/ZUzmbuIdHMdlYc="; + +/* GOOGLE_PIN_Libertylavabitcom */ +static const char kGOOGLE_PIN_LibertylavabitcomFingerprint[] = + "WnKzsDXgqPtS1KvtImrhQPqcxfpmfssuI2cSJt4LMks="; + +/* GOOGLE_PIN_RapidSSL */ +static const char kGOOGLE_PIN_RapidSSLFingerprint[] = + "lT09gPUeQfbYrlxRtpsHrjDblj9Rpz+u7ajfCrg4qDM="; + +/* GOOGLE_PIN_Tor2web */ +static const char kGOOGLE_PIN_Tor2webFingerprint[] = + "99ogQzjMuUTBkG1ZP7FME0K4kvBEti8Buzu4nZjRItM="; + +/* GTE CyberTrust Global Root */ +static const char kGTE_CyberTrust_Global_RootFingerprint[] = + "EGn6R6CqT4z3ERscrqNl7q7RC//zJmDe9uBhS/rnCHU="; + +/* GeoTrust Global CA */ +static const char kGeoTrust_Global_CAFingerprint[] = + "h6801m+z8v3zbgkRHpq6L29Esgfzhj89C1SyUCOQmqU="; + +/* GeoTrust Global CA 2 */ +static const char kGeoTrust_Global_CA_2Fingerprint[] = + "F3VaXClfPS1y5vAxofB/QAxYi55YKyLxfq4xoVkNEYU="; + +/* GeoTrust Primary Certification Authority */ +static const char kGeoTrust_Primary_Certification_AuthorityFingerprint[] = + "SQVGZiOrQXi+kqxcvWWE96HhfydlLVqFr4lQTqI5qqo="; + +/* GeoTrust Primary Certification Authority - G2 */ +static const char kGeoTrust_Primary_Certification_Authority___G2Fingerprint[] = + "vPtEqrmtAhAVcGtBIep2HIHJ6IlnWQ9vlK50TciLePs="; + +/* GeoTrust Primary Certification Authority - G3 */ +static const char kGeoTrust_Primary_Certification_Authority___G3Fingerprint[] = + "q5hJUnat8eyv8o81xTBIeB5cFxjaucjmelBPT2pRMo8="; + +/* GeoTrust Universal CA */ +static const char kGeoTrust_Universal_CAFingerprint[] = + "lpkiXF3lLlbN0y3y6W0c/qWqPKC7Us2JM8I7XCdEOCA="; + +/* GeoTrust Universal CA 2 */ +static const char kGeoTrust_Universal_CA_2Fingerprint[] = + "fKoDRlEkWQxgHlZ+UhSOlSwM/+iQAFMP4NlbbVDqrkE="; + +/* GlobalSign Root CA */ +static const char kGlobalSign_Root_CAFingerprint[] = + "K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q="; + +/* GlobalSign Root CA - R2 */ +static const char kGlobalSign_Root_CA___R2Fingerprint[] = + "iie1VXtL7HzAMF+/PVPR9xzT80kQxdZeJ+zduCB3uj0="; + +/* GlobalSign Root CA - R3 */ +static const char kGlobalSign_Root_CA___R3Fingerprint[] = + "cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A="; + +/* Go Daddy Class 2 CA */ +static const char kGo_Daddy_Class_2_CAFingerprint[] = + "VjLZe/p3W/PJnd6lL8JVNBCGQBZynFLdZSTIqcO0SJ8="; + +/* Go Daddy Root Certificate Authority - G2 */ +static const char kGo_Daddy_Root_Certificate_Authority___G2Fingerprint[] = + "Ko8tivDrEjiY90yGasP6ZpBU4jwXvHqVvQI0GS3GNdA="; + +/* GoogleBackup2048 */ +static const char kGoogleBackup2048Fingerprint[] = + "vq7OyjSnqOco9nyMCDGdy77eijM="; + +/* GoogleG2 */ +static const char kGoogleG2Fingerprint[] = + "Q9rWMO5T+KmAym79hfRqo3mQ4Oo="; + +/* Network Solutions Certificate Authority */ +static const char kNetwork_Solutions_Certificate_AuthorityFingerprint[] = + "MtGA7THJNVieydu7ciEjuIO1/C3BD5/KOpXXfhv8tTQ="; + +/* Starfield Class 2 CA */ +static const char kStarfield_Class_2_CAFingerprint[] = + "FfFKxFycfaIz00eRZOgTf+Ne4POK6FgYPwhBDqgqxLQ="; + +/* Starfield Root Certificate Authority - G2 */ +static const char kStarfield_Root_Certificate_Authority___G2Fingerprint[] = + "gI1os/q0iEpflxrOfRBVDXqVoWN3Tz7Dav/7IT++THQ="; + +/* Starfield Services Root Certificate Authority - G2 */ +static const char kStarfield_Services_Root_Certificate_Authority___G2Fingerprint[] = + "KwccWaCgrnaw6tsrrSO61FgLacNgG2MMLq8GE6+oP5I="; + +/* StartCom Certification Authority */ +static const char kStartCom_Certification_AuthorityFingerprint[] = + "5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU="; + +/* StartCom Certification Authority G2 */ +static const char kStartCom_Certification_Authority_G2Fingerprint[] = + "FSg5faISiQqDCwuVpZlozvI0dzd531GBzxD6ZHU0u2U="; + +/* TC TrustCenter Class 2 CA II */ +static const char kTC_TrustCenter_Class_2_CA_IIFingerprint[] = + "rPZeHWLLWKK6/W/6tA+4hpnEc5fPXLSD1C1pytNM1Is="; + +/* TC TrustCenter Class 3 CA II */ +static const char kTC_TrustCenter_Class_3_CA_IIFingerprint[] = + "k5KuIUmSSt435kXbof9L3dzaKykbYJdmnSr6XHo3Jhk="; + +/* TC TrustCenter Universal CA I */ +static const char kTC_TrustCenter_Universal_CA_IFingerprint[] = + "st71NirT+s0EvSkEekOET3ZwNOpIkvgOVr7mkCQ+JQI="; + +/* TC TrustCenter Universal CA III */ +static const char kTC_TrustCenter_Universal_CA_IIIFingerprint[] = + "q1zbM1Y5c1bW5pGXPCW4YYtl12qQSG6nqKXBd2f0Zzo="; + +/* TestSPKI */ +static const char kTestSPKIFingerprint[] = + "AAAAAAAAAAAAAAAAAAAAAAAAAAA="; + +/* Thawte Premium Server CA */ +static const char kThawte_Premium_Server_CAFingerprint[] = + "9TwiBZgX3Zb0AGUWOdL4V+IQcKWavtkHlADZ9pVQaQA="; + +/* Thawte Server CA */ +static const char kThawte_Server_CAFingerprint[] = + "nG9qEjy6pO402+zu4kyX1ziHjLQj88InOQNCT10fbdU="; + +/* Tor1 */ +static const char kTor1Fingerprint[] = + "juNxSTv9UANmpC9kF5GKpmWNx3Y="; + +/* Tor2 */ +static const char kTor2Fingerprint[] = + "lia43lPolzSPVIq34Dw57uYcLD8="; + +/* Tor3 */ +static const char kTor3Fingerprint[] = + "rzEyQIKOh77j87n5bjWUNguXF8Y="; + +/* Twitter1 */ +static const char kTwitter1Fingerprint[] = + "Vv7zwhR9TtOIN/29MFI4cgHld40="; + +/* UTN DATACorp SGC Root CA */ +static const char kUTN_DATACorp_SGC_Root_CAFingerprint[] = + "QAL80xHQczFWfnG82XHkYEjI3OjRZZcRdTs9qiommvo="; + +/* UTN USERFirst Email Root CA */ +static const char kUTN_USERFirst_Email_Root_CAFingerprint[] = + "Laj56jRU0hFGRko/nQKNxMf7tXscUsc8KwVyovWZotM="; + +/* UTN USERFirst Hardware Root CA */ +static const char kUTN_USERFirst_Hardware_Root_CAFingerprint[] = + "TUDnr0MEoJ3of7+YliBMBVFB4/gJsv5zO7IxD9+YoWI="; + +/* UTN USERFirst Object Root CA */ +static const char kUTN_USERFirst_Object_Root_CAFingerprint[] = + "D+FMJksXu28NZT56cOs2Pb9UvhWAOe3a5cJXEd9IwQM="; + +/* VeriSign Class 3 Public Primary Certification Authority - G4 */ +static const char kVeriSign_Class_3_Public_Primary_Certification_Authority___G4Fingerprint[] = + "UZJDjsNp1+4M5x9cbbdflB779y5YRBcV6Z6rBMLIrO4="; + +/* VeriSign Class 3 Public Primary Certification Authority - G5 */ +static const char kVeriSign_Class_3_Public_Primary_Certification_Authority___G5Fingerprint[] = + "JbQbUG5JMJUoI6brnx0x3vZF6jilxsapbXGVfjhN8Fg="; + +/* VeriSign Universal Root Certification Authority */ +static const char kVeriSign_Universal_Root_Certification_AuthorityFingerprint[] = + "lnsM2T/O9/J84sJFdnrpsFp3awZJ+ZZbYpCWhGloaHI="; + +/* Verisign Class 1 Public Primary Certification Authority */ +static const char kVerisign_Class_1_Public_Primary_Certification_AuthorityFingerprint[] = + "LclHC+Y+9KzxvYKGCUArt7h72ZY4pkOTTohoLRvowwg="; + +/* Verisign Class 1 Public Primary Certification Authority - G3 */ +static const char kVerisign_Class_1_Public_Primary_Certification_Authority___G3Fingerprint[] = + "IgduWu9Eu5pBaii30cRDItcFn2D+/6XK9sW+hEeJEwM="; + +/* Verisign Class 2 Public Primary Certification Authority - G2 */ +static const char kVerisign_Class_2_Public_Primary_Certification_Authority___G2Fingerprint[] = + "2oALgLKofTmeZvoZ1y/fSZg7R9jPMix8eVA6DH4o/q8="; + +/* Verisign Class 2 Public Primary Certification Authority - G3 */ +static const char kVerisign_Class_2_Public_Primary_Certification_Authority___G3Fingerprint[] = + "cAajgxHlj7GTSEIzIYIQxmEloOSoJq7VOaxWHfv72QM="; + +/* Verisign Class 3 Public Primary Certification Authority */ +static const char kVerisign_Class_3_Public_Primary_Certification_AuthorityFingerprint[] = + "sRJBQqWhpaKIGcc1NA7/jJ4vgWj+47oYfyU7waOS1+I="; + +/* Verisign Class 3 Public Primary Certification Authority - G2 */ +static const char kVerisign_Class_3_Public_Primary_Certification_Authority___G2Fingerprint[] = + "AjyBzOjnxk+pQtPBUEhwfTXZu1uH9PVExb8bxWQ68vo="; + +/* Verisign Class 3 Public Primary Certification Authority - G3 */ +static const char kVerisign_Class_3_Public_Primary_Certification_Authority___G3Fingerprint[] = + "SVqWumuteCQHvVIaALrOZXuzVVVeS7f4FGxxu6V+es4="; + +/* Verisign Class 4 Public Primary Certification Authority - G3 */ +static const char kVerisign_Class_4_Public_Primary_Certification_Authority___G3Fingerprint[] = + "VnuCEf0g09KD7gzXzgZyy52ZvFtIeljJ1U7Gf3fUqPU="; + +/* XRamp Global CA Root */ +static const char kXRamp_Global_CA_RootFingerprint[] = + "BRz5+pXkDpuD7a7aaWH2Fox4ecRmAXJHnN1RqwPOpis="; + +/* thawte Primary Root CA */ +static const char kthawte_Primary_Root_CAFingerprint[] = + "HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY="; + +/* thawte Primary Root CA - G2 */ +static const char kthawte_Primary_Root_CA___G2Fingerprint[] = + "Z9xPMvoQ59AaeaBzqgyeAhLsL/w9d54Kp/nA8OHCyJM="; + +/* thawte Primary Root CA - G3 */ +static const char kthawte_Primary_Root_CA___G3Fingerprint[] = + "GQbGEk27Q4V40A4GbVBUxsN/D6YCjAVUXgmU7drshik="; + +/* Pinsets are each an ordered list by the actual value of the fingerprint */ +struct StaticFingerprints { + const size_t size; + const char* const* data; +}; + +struct StaticPinset { + const StaticFingerprints* sha1; + const StaticFingerprints* sha256; +}; + +/* PreloadedHPKPins.json pinsets */ +static const char* kPinset_facebook_sha256_Data[] = { + kDigiCert_ECC_Secure_Server_CAFingerprint, + kVerisign_Class_3_Public_Primary_Certification_Authority___G3Fingerprint, + kDigiCert_High_Assurance_EV_Root_CAFingerprint, +}; +static const StaticFingerprints kPinset_facebook_sha256 = { + sizeof(kPinset_facebook_sha256_Data) / sizeof(const char*), + kPinset_facebook_sha256_Data +}; + +static const StaticPinset kPinset_facebook = { + nullptr, + &kPinset_facebook_sha256 +}; + +static const char* kPinset_google_root_pems_sha256_Data[] = { + kEquifax_Secure_CAFingerprint, + kAmerica_Online_Root_Certification_Authority_2Fingerprint, + kComodo_Trusted_Services_rootFingerprint, + kCOMODO_ECC_Certification_AuthorityFingerprint, + kStartCom_Certification_AuthorityFingerprint, + kStartCom_Certification_AuthorityFingerprint, + kThawte_Premium_Server_CAFingerprint, + kCOMODO_Certification_AuthorityFingerprint, + kVerisign_Class_3_Public_Primary_Certification_Authority___G2Fingerprint, + kXRamp_Global_CA_RootFingerprint, + kAddTrust_Low_Value_Services_RootFingerprint, + kGeoTrust_Global_CA_2Fingerprint, + kStartCom_Certification_Authority_G2Fingerprint, + kStarfield_Class_2_CAFingerprint, + kthawte_Primary_Root_CA___G3Fingerprint, + kthawte_Primary_Root_CAFingerprint, + kEntrust_net_Premium_2048_Secure_Server_CAFingerprint, + kDigiCert_Assured_ID_Root_CAFingerprint, + kAmerica_Online_Root_Certification_Authority_1Fingerprint, + kVeriSign_Class_3_Public_Primary_Certification_Authority___G5Fingerprint, + kEquifax_Secure_eBusiness_CA_1Fingerprint, + kGlobalSign_Root_CAFingerprint, + kGo_Daddy_Root_Certificate_Authority___G2Fingerprint, + kStarfield_Services_Root_Certificate_Authority___G2Fingerprint, + kAffirmTrust_Premium_ECCFingerprint, + kNetwork_Solutions_Certificate_AuthorityFingerprint, + kAddTrust_Public_Services_RootFingerprint, + kUTN_DATACorp_SGC_Root_CAFingerprint, + kComodo_Secure_Services_rootFingerprint, + kGeoTrust_Primary_Certification_AuthorityFingerprint, + kVerisign_Class_3_Public_Primary_Certification_Authority___G3Fingerprint, + kUTN_USERFirst_Hardware_Root_CAFingerprint, + kVeriSign_Class_3_Public_Primary_Certification_Authority___G4Fingerprint, + kGo_Daddy_Class_2_CAFingerprint, + kVerisign_Class_4_Public_Primary_Certification_Authority___G3Fingerprint, + kDigiCert_High_Assurance_EV_Root_CAFingerprint, + kBaltimore_CyberTrust_RootFingerprint, + kthawte_Primary_Root_CA___G2Fingerprint, + kAffirmTrust_CommercialFingerprint, + kEntrust_Root_Certification_AuthorityFingerprint, + kGlobalSign_Root_CA___R3Fingerprint, + kGeoTrust_Universal_CA_2Fingerprint, + kCybertrust_Global_RootFingerprint, + kStarfield_Root_Certificate_Authority___G2Fingerprint, + kGeoTrust_Global_CAFingerprint, + kGlobalSign_Root_CA___R2Fingerprint, + kTC_TrustCenter_Class_3_CA_IIFingerprint, + kAffirmTrust_NetworkingFingerprint, + kAddTrust_External_RootFingerprint, + kVeriSign_Universal_Root_Certification_AuthorityFingerprint, + kGeoTrust_Universal_CAFingerprint, + kThawte_Server_CAFingerprint, + kEquifax_Secure_Global_eBusiness_CAFingerprint, + kTC_TrustCenter_Universal_CA_IIIFingerprint, + kGeoTrust_Primary_Certification_Authority___G3Fingerprint, + kDigiCert_Global_Root_CAFingerprint, + kTC_TrustCenter_Class_2_CA_IIFingerprint, + kVerisign_Class_3_Public_Primary_Certification_AuthorityFingerprint, + kVerisign_Class_3_Public_Primary_Certification_AuthorityFingerprint, + kTC_TrustCenter_Universal_CA_IFingerprint, + kGeoTrust_Primary_Certification_Authority___G2Fingerprint, + kComodo_AAA_Services_rootFingerprint, + kAffirmTrust_PremiumFingerprint, + kAddTrust_Qualified_Certificates_RootFingerprint, +}; +static const StaticFingerprints kPinset_google_root_pems_sha256 = { + sizeof(kPinset_google_root_pems_sha256_Data) / sizeof(const char*), + kPinset_google_root_pems_sha256_Data +}; + +static const StaticPinset kPinset_google_root_pems = { + nullptr, + &kPinset_google_root_pems_sha256 +}; + +static const char* kPinset_mozilla_sha256_Data[] = { + kGeoTrust_Global_CA_2Fingerprint, + kthawte_Primary_Root_CA___G3Fingerprint, + kthawte_Primary_Root_CAFingerprint, + kDigiCert_Assured_ID_Root_CAFingerprint, + kVerisign_Class_1_Public_Primary_Certification_Authority___G3Fingerprint, + kVeriSign_Class_3_Public_Primary_Certification_Authority___G5Fingerprint, + kGeoTrust_Primary_Certification_AuthorityFingerprint, + kVerisign_Class_3_Public_Primary_Certification_Authority___G3Fingerprint, + kVeriSign_Class_3_Public_Primary_Certification_Authority___G4Fingerprint, + kVerisign_Class_4_Public_Primary_Certification_Authority___G3Fingerprint, + kDigiCert_High_Assurance_EV_Root_CAFingerprint, + kBaltimore_CyberTrust_RootFingerprint, + kthawte_Primary_Root_CA___G2Fingerprint, + kVerisign_Class_2_Public_Primary_Certification_Authority___G3Fingerprint, + kGeoTrust_Universal_CA_2Fingerprint, + kGeoTrust_Global_CAFingerprint, + kVeriSign_Universal_Root_Certification_AuthorityFingerprint, + kGeoTrust_Universal_CAFingerprint, + kGeoTrust_Primary_Certification_Authority___G3Fingerprint, + kDigiCert_Global_Root_CAFingerprint, + kGeoTrust_Primary_Certification_Authority___G2Fingerprint, +}; +static const StaticFingerprints kPinset_mozilla_sha256 = { + sizeof(kPinset_mozilla_sha256_Data) / sizeof(const char*), + kPinset_mozilla_sha256_Data +}; + +static const StaticPinset kPinset_mozilla = { + nullptr, + &kPinset_mozilla_sha256 +}; + +static const char* kPinset_mozilla_services_sha256_Data[] = { + kDigiCert_Global_Root_CAFingerprint, +}; +static const StaticFingerprints kPinset_mozilla_services_sha256 = { + sizeof(kPinset_mozilla_services_sha256_Data) / sizeof(const char*), + kPinset_mozilla_services_sha256_Data +}; + +static const StaticPinset kPinset_mozilla_services = { + nullptr, + &kPinset_mozilla_services_sha256 +}; + +static const char* kPinset_mozilla_test_sha256_Data[] = { + kEnd_Entity_Test_CertFingerprint, +}; +static const StaticFingerprints kPinset_mozilla_test_sha256 = { + sizeof(kPinset_mozilla_test_sha256_Data) / sizeof(const char*), + kPinset_mozilla_test_sha256_Data +}; + +static const StaticPinset kPinset_mozilla_test = { + nullptr, + &kPinset_mozilla_test_sha256 +}; + +/* Chrome static pinsets */ +static const char* kPinset_test_sha1_Data[] = { + kTestSPKIFingerprint, +}; +static const StaticFingerprints kPinset_test_sha1 = { + sizeof(kPinset_test_sha1_Data) / sizeof(const char*), + kPinset_test_sha1_Data +}; + +static const StaticPinset kPinset_test = { + &kPinset_test_sha1, + nullptr +}; + +static const char* kPinset_google_sha1_Data[] = { + kGoogleG2Fingerprint, + kGoogleBackup2048Fingerprint, +}; +static const StaticFingerprints kPinset_google_sha1 = { + sizeof(kPinset_google_sha1_Data) / sizeof(const char*), + kPinset_google_sha1_Data +}; + +static const StaticPinset kPinset_google = { + &kPinset_google_sha1, + nullptr +}; + +static const char* kPinset_tor_sha1_Data[] = { + kTor1Fingerprint, + kTor2Fingerprint, + kTor3Fingerprint, +}; +static const StaticFingerprints kPinset_tor_sha1 = { + sizeof(kPinset_tor_sha1_Data) / sizeof(const char*), + kPinset_tor_sha1_Data +}; + +static const char* kPinset_tor_sha256_Data[] = { + kDigiCert_High_Assurance_EV_Root_CAFingerprint, + kGOOGLE_PIN_RapidSSLFingerprint, +}; +static const StaticFingerprints kPinset_tor_sha256 = { + sizeof(kPinset_tor_sha256_Data) / sizeof(const char*), + kPinset_tor_sha256_Data +}; + +static const StaticPinset kPinset_tor = { + &kPinset_tor_sha1, + &kPinset_tor_sha256 +}; + +static const char* kPinset_twitterCom_sha1_Data[] = { + kTwitter1Fingerprint, +}; +static const StaticFingerprints kPinset_twitterCom_sha1 = { + sizeof(kPinset_twitterCom_sha1_Data) / sizeof(const char*), + kPinset_twitterCom_sha1_Data +}; + +static const char* kPinset_twitterCom_sha256_Data[] = { + kVerisign_Class_2_Public_Primary_Certification_Authority___G2Fingerprint, + kVerisign_Class_3_Public_Primary_Certification_Authority___G2Fingerprint, + kGeoTrust_Global_CA_2Fingerprint, + kDigiCert_Assured_ID_Root_CAFingerprint, + kVerisign_Class_1_Public_Primary_Certification_Authority___G3Fingerprint, + kVeriSign_Class_3_Public_Primary_Certification_Authority___G5Fingerprint, + kVerisign_Class_1_Public_Primary_Certification_AuthorityFingerprint, + kGeoTrust_Primary_Certification_AuthorityFingerprint, + kVerisign_Class_3_Public_Primary_Certification_Authority___G3Fingerprint, + kVeriSign_Class_3_Public_Primary_Certification_Authority___G4Fingerprint, + kVerisign_Class_4_Public_Primary_Certification_Authority___G3Fingerprint, + kDigiCert_High_Assurance_EV_Root_CAFingerprint, + kVerisign_Class_2_Public_Primary_Certification_Authority___G3Fingerprint, + kGeoTrust_Universal_CA_2Fingerprint, + kGeoTrust_Global_CAFingerprint, + kVeriSign_Universal_Root_Certification_AuthorityFingerprint, + kGeoTrust_Universal_CAFingerprint, + kGeoTrust_Primary_Certification_Authority___G3Fingerprint, + kDigiCert_Global_Root_CAFingerprint, + kVerisign_Class_3_Public_Primary_Certification_AuthorityFingerprint, + kGeoTrust_Primary_Certification_Authority___G2Fingerprint, +}; +static const StaticFingerprints kPinset_twitterCom_sha256 = { + sizeof(kPinset_twitterCom_sha256_Data) / sizeof(const char*), + kPinset_twitterCom_sha256_Data +}; + +static const StaticPinset kPinset_twitterCom = { + &kPinset_twitterCom_sha1, + &kPinset_twitterCom_sha256 +}; + +static const char* kPinset_twitterCDN_sha1_Data[] = { + kTwitter1Fingerprint, +}; +static const StaticFingerprints kPinset_twitterCDN_sha1 = { + sizeof(kPinset_twitterCDN_sha1_Data) / sizeof(const char*), + kPinset_twitterCDN_sha1_Data +}; + +static const char* kPinset_twitterCDN_sha256_Data[] = { + kVerisign_Class_2_Public_Primary_Certification_Authority___G2Fingerprint, + kComodo_Trusted_Services_rootFingerprint, + kCOMODO_Certification_AuthorityFingerprint, + kVerisign_Class_3_Public_Primary_Certification_Authority___G2Fingerprint, + kAddTrust_Low_Value_Services_RootFingerprint, + kUTN_USERFirst_Object_Root_CAFingerprint, + kGTE_CyberTrust_Global_RootFingerprint, + kGeoTrust_Global_CA_2Fingerprint, + kEntrust_net_Premium_2048_Secure_Server_CAFingerprint, + kDigiCert_Assured_ID_Root_CAFingerprint, + kVerisign_Class_1_Public_Primary_Certification_Authority___G3Fingerprint, + kVeriSign_Class_3_Public_Primary_Certification_Authority___G5Fingerprint, + kGlobalSign_Root_CAFingerprint, + kUTN_USERFirst_Email_Root_CAFingerprint, + kVerisign_Class_1_Public_Primary_Certification_AuthorityFingerprint, + kAddTrust_Public_Services_RootFingerprint, + kUTN_DATACorp_SGC_Root_CAFingerprint, + kComodo_Secure_Services_rootFingerprint, + kGeoTrust_Primary_Certification_AuthorityFingerprint, + kVerisign_Class_3_Public_Primary_Certification_Authority___G3Fingerprint, + kUTN_USERFirst_Hardware_Root_CAFingerprint, + kVeriSign_Class_3_Public_Primary_Certification_Authority___G4Fingerprint, + kVerisign_Class_4_Public_Primary_Certification_Authority___G3Fingerprint, + kDigiCert_High_Assurance_EV_Root_CAFingerprint, + kBaltimore_CyberTrust_RootFingerprint, + kEntrust_Root_Certification_AuthorityFingerprint, + kVerisign_Class_2_Public_Primary_Certification_Authority___G3Fingerprint, + kGlobalSign_Root_CA___R3Fingerprint, + kGOOGLE_PIN_Entrust_G2Fingerprint, + kGeoTrust_Universal_CA_2Fingerprint, + kGeoTrust_Global_CAFingerprint, + kGlobalSign_Root_CA___R2Fingerprint, + kAddTrust_External_RootFingerprint, + kVeriSign_Universal_Root_Certification_AuthorityFingerprint, + kGeoTrust_Universal_CAFingerprint, + kGOOGLE_PIN_Entrust_SSLFingerprint, + kGeoTrust_Primary_Certification_Authority___G3Fingerprint, + kDigiCert_Global_Root_CAFingerprint, + kVerisign_Class_3_Public_Primary_Certification_AuthorityFingerprint, + kGeoTrust_Primary_Certification_Authority___G2Fingerprint, + kComodo_AAA_Services_rootFingerprint, + kAddTrust_Qualified_Certificates_RootFingerprint, +}; +static const StaticFingerprints kPinset_twitterCDN_sha256 = { + sizeof(kPinset_twitterCDN_sha256_Data) / sizeof(const char*), + kPinset_twitterCDN_sha256_Data +}; + +static const StaticPinset kPinset_twitterCDN = { + &kPinset_twitterCDN_sha1, + &kPinset_twitterCDN_sha256 +}; + +static const char* kPinset_tor2web_sha256_Data[] = { + kGOOGLE_PIN_Tor2webFingerprint, + kGOOGLE_PIN_AlphaSSL_G2Fingerprint, +}; +static const StaticFingerprints kPinset_tor2web_sha256 = { + sizeof(kPinset_tor2web_sha256_Data) / sizeof(const char*), + kPinset_tor2web_sha256_Data +}; + +static const StaticPinset kPinset_tor2web = { + nullptr, + &kPinset_tor2web_sha256 +}; + +static const char* kPinset_cryptoCat_sha256_Data[] = { + kDigiCert_High_Assurance_EV_Root_CAFingerprint, + kGOOGLE_PIN_CryptoCat1Fingerprint, +}; +static const StaticFingerprints kPinset_cryptoCat_sha256 = { + sizeof(kPinset_cryptoCat_sha256_Data) / sizeof(const char*), + kPinset_cryptoCat_sha256_Data +}; + +static const StaticPinset kPinset_cryptoCat = { + nullptr, + &kPinset_cryptoCat_sha256 +}; + +static const char* kPinset_lavabit_sha256_Data[] = { + kGOOGLE_PIN_LibertylavabitcomFingerprint, +}; +static const StaticFingerprints kPinset_lavabit_sha256 = { + sizeof(kPinset_lavabit_sha256_Data) / sizeof(const char*), + kPinset_lavabit_sha256_Data +}; + +static const StaticPinset kPinset_lavabit = { + nullptr, + &kPinset_lavabit_sha256 +}; + +static const char* kPinset_dropbox_sha256_Data[] = { + kGOOGLE_PIN_EntrustRootEC1Fingerprint, + kThawte_Premium_Server_CAFingerprint, + kthawte_Primary_Root_CA___G3Fingerprint, + kthawte_Primary_Root_CAFingerprint, + kEntrust_net_Premium_2048_Secure_Server_CAFingerprint, + kDigiCert_Assured_ID_Root_CAFingerprint, + kGo_Daddy_Root_Certificate_Authority___G2Fingerprint, + kGOOGLE_PIN_GoDaddySecureFingerprint, + kGeoTrust_Primary_Certification_AuthorityFingerprint, + kGo_Daddy_Class_2_CAFingerprint, + kDigiCert_High_Assurance_EV_Root_CAFingerprint, + kthawte_Primary_Root_CA___G2Fingerprint, + kEntrust_Root_Certification_AuthorityFingerprint, + kGOOGLE_PIN_Entrust_G2Fingerprint, + kGeoTrust_Global_CAFingerprint, + kGeoTrust_Primary_Certification_Authority___G3Fingerprint, + kDigiCert_Global_Root_CAFingerprint, + kGeoTrust_Primary_Certification_Authority___G2Fingerprint, +}; +static const StaticFingerprints kPinset_dropbox_sha256 = { + sizeof(kPinset_dropbox_sha256_Data) / sizeof(const char*), + kPinset_dropbox_sha256_Data +}; + +static const StaticPinset kPinset_dropbox = { + nullptr, + &kPinset_dropbox_sha256 +}; + +/* Domainlist */ +struct TransportSecurityPreload { + const char* mHost; + const bool mIncludeSubdomains; + const bool mTestMode; + const bool mIsMoz; + const int32_t mId; + const StaticPinset *pinset; +}; + +/* Sort hostnames for binary search. */ +static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = { + { "2mdn.net", true, false, false, -1, &kPinset_google_root_pems }, + { "accounts.firefox.com", true, false, false, 4, &kPinset_mozilla_services }, + { "accounts.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "addons.mozilla.net", true, false, true, 2, &kPinset_mozilla }, + { "addons.mozilla.org", true, false, true, 1, &kPinset_mozilla }, + { "admin.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "android.com", true, false, false, -1, &kPinset_google_root_pems }, + { "api.accounts.firefox.com", true, false, false, 5, &kPinset_mozilla_services }, + { "api.twitter.com", true, false, false, -1, &kPinset_twitterCDN }, + { "apis.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "appengine.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "aus4.mozilla.org", true, true, true, 3, &kPinset_mozilla }, + { "blog.torproject.org", true, false, false, -1, &kPinset_tor }, + { "business.twitter.com", true, false, false, -1, &kPinset_twitterCom }, + { "cdn.mozilla.net", true, false, true, -1, &kPinset_mozilla }, + { "cdn.mozilla.org", true, false, true, -1, &kPinset_mozilla }, + { "chart.apis.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "check.torproject.org", true, false, false, -1, &kPinset_tor }, + { "checkout.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "chrome-devtools-frontend.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "chrome.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "chromiumcodereview.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "cloud.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "code.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "codereview.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "codereview.chromium.org", true, false, false, -1, &kPinset_google_root_pems }, + { "crypto.cat", false, true, false, -1, &kPinset_cryptoCat }, + { "dev.twitter.com", true, false, false, -1, &kPinset_twitterCom }, + { "dist.torproject.org", true, false, false, -1, &kPinset_tor }, + { "dl.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "docs.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "domains.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "doubleclick.net", true, false, false, -1, &kPinset_google_root_pems }, + { "drive.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "dropbox.com", false, false, false, -1, &kPinset_dropbox }, + { "encrypted.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "exclude-subdomains.pinning.example.com", false, false, false, 0, &kPinset_mozilla_test }, + { "facebook.com", true, true, false, -1, &kPinset_facebook }, + { "g.co", true, false, false, -1, &kPinset_google_root_pems }, + { "glass.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "gmail.com", false, false, false, -1, &kPinset_google_root_pems }, + { "goo.gl", true, false, false, -1, &kPinset_google_root_pems }, + { "google-analytics.com", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ac", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ad", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ae", true, false, false, -1, &kPinset_google_root_pems }, + { "google.af", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ag", true, false, false, -1, &kPinset_google_root_pems }, + { "google.am", true, false, false, -1, &kPinset_google_root_pems }, + { "google.as", true, false, false, -1, &kPinset_google_root_pems }, + { "google.at", true, false, false, -1, &kPinset_google_root_pems }, + { "google.az", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ba", true, false, false, -1, &kPinset_google_root_pems }, + { "google.be", true, false, false, -1, &kPinset_google_root_pems }, + { "google.bf", true, false, false, -1, &kPinset_google_root_pems }, + { "google.bg", true, false, false, -1, &kPinset_google_root_pems }, + { "google.bi", true, false, false, -1, &kPinset_google_root_pems }, + { "google.bj", true, false, false, -1, &kPinset_google_root_pems }, + { "google.bs", true, false, false, -1, &kPinset_google_root_pems }, + { "google.by", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ca", true, false, false, -1, &kPinset_google_root_pems }, + { "google.cat", true, false, false, -1, &kPinset_google_root_pems }, + { "google.cc", true, false, false, -1, &kPinset_google_root_pems }, + { "google.cd", true, false, false, -1, &kPinset_google_root_pems }, + { "google.cf", true, false, false, -1, &kPinset_google_root_pems }, + { "google.cg", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ch", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ci", true, false, false, -1, &kPinset_google_root_pems }, + { "google.cl", true, false, false, -1, &kPinset_google_root_pems }, + { "google.cm", true, false, false, -1, &kPinset_google_root_pems }, + { "google.cn", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.ao", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.bw", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.ck", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.cr", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.hu", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.id", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.il", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.im", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.in", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.je", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.jp", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.ke", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.kr", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.ls", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.ma", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.mz", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.nz", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.th", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.tz", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.ug", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.uk", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.uz", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.ve", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.vi", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.za", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.zm", true, false, false, -1, &kPinset_google_root_pems }, + { "google.co.zw", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.af", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ag", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ai", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ar", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.au", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.bd", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.bh", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.bn", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.bo", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.br", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.by", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.bz", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.cn", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.co", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.cu", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.cy", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.do", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ec", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.eg", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.et", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.fj", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ge", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.gh", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.gi", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.gr", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.gt", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.hk", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.iq", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.jm", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.jo", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.kh", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.kw", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.lb", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ly", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.mt", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.mx", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.my", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.na", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.nf", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ng", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ni", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.np", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.nr", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.om", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.pa", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.pe", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ph", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.pk", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.pl", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.pr", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.py", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.qa", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ru", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.sa", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.sb", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.sg", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.sl", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.sv", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.tj", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.tn", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.tr", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.tw", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ua", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.uy", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.vc", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.ve", true, false, false, -1, &kPinset_google_root_pems }, + { "google.com.vn", true, false, false, -1, &kPinset_google_root_pems }, + { "google.cv", true, false, false, -1, &kPinset_google_root_pems }, + { "google.cz", true, false, false, -1, &kPinset_google_root_pems }, + { "google.de", true, false, false, -1, &kPinset_google_root_pems }, + { "google.dj", true, false, false, -1, &kPinset_google_root_pems }, + { "google.dk", true, false, false, -1, &kPinset_google_root_pems }, + { "google.dm", true, false, false, -1, &kPinset_google_root_pems }, + { "google.dz", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ee", true, false, false, -1, &kPinset_google_root_pems }, + { "google.es", true, false, false, -1, &kPinset_google_root_pems }, + { "google.fi", true, false, false, -1, &kPinset_google_root_pems }, + { "google.fm", true, false, false, -1, &kPinset_google_root_pems }, + { "google.fr", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ga", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ge", true, false, false, -1, &kPinset_google_root_pems }, + { "google.gg", true, false, false, -1, &kPinset_google_root_pems }, + { "google.gl", true, false, false, -1, &kPinset_google_root_pems }, + { "google.gm", true, false, false, -1, &kPinset_google_root_pems }, + { "google.gp", true, false, false, -1, &kPinset_google_root_pems }, + { "google.gr", true, false, false, -1, &kPinset_google_root_pems }, + { "google.gy", true, false, false, -1, &kPinset_google_root_pems }, + { "google.hk", true, false, false, -1, &kPinset_google_root_pems }, + { "google.hn", true, false, false, -1, &kPinset_google_root_pems }, + { "google.hr", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ht", true, false, false, -1, &kPinset_google_root_pems }, + { "google.hu", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ie", true, false, false, -1, &kPinset_google_root_pems }, + { "google.im", true, false, false, -1, &kPinset_google_root_pems }, + { "google.info", true, false, false, -1, &kPinset_google_root_pems }, + { "google.iq", true, false, false, -1, &kPinset_google_root_pems }, + { "google.is", true, false, false, -1, &kPinset_google_root_pems }, + { "google.it", true, false, false, -1, &kPinset_google_root_pems }, + { "google.it.ao", true, false, false, -1, &kPinset_google_root_pems }, + { "google.je", true, false, false, -1, &kPinset_google_root_pems }, + { "google.jo", true, false, false, -1, &kPinset_google_root_pems }, + { "google.jobs", true, false, false, -1, &kPinset_google_root_pems }, + { "google.jp", true, false, false, -1, &kPinset_google_root_pems }, + { "google.kg", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ki", true, false, false, -1, &kPinset_google_root_pems }, + { "google.kz", true, false, false, -1, &kPinset_google_root_pems }, + { "google.la", true, false, false, -1, &kPinset_google_root_pems }, + { "google.li", true, false, false, -1, &kPinset_google_root_pems }, + { "google.lk", true, false, false, -1, &kPinset_google_root_pems }, + { "google.lt", true, false, false, -1, &kPinset_google_root_pems }, + { "google.lu", true, false, false, -1, &kPinset_google_root_pems }, + { "google.lv", true, false, false, -1, &kPinset_google_root_pems }, + { "google.md", true, false, false, -1, &kPinset_google_root_pems }, + { "google.me", true, false, false, -1, &kPinset_google_root_pems }, + { "google.mg", true, false, false, -1, &kPinset_google_root_pems }, + { "google.mk", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ml", true, false, false, -1, &kPinset_google_root_pems }, + { "google.mn", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ms", true, false, false, -1, &kPinset_google_root_pems }, + { "google.mu", true, false, false, -1, &kPinset_google_root_pems }, + { "google.mv", true, false, false, -1, &kPinset_google_root_pems }, + { "google.mw", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ne", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ne.jp", true, false, false, -1, &kPinset_google_root_pems }, + { "google.net", true, false, false, -1, &kPinset_google_root_pems }, + { "google.nl", true, false, false, -1, &kPinset_google_root_pems }, + { "google.no", true, false, false, -1, &kPinset_google_root_pems }, + { "google.nr", true, false, false, -1, &kPinset_google_root_pems }, + { "google.nu", true, false, false, -1, &kPinset_google_root_pems }, + { "google.off.ai", true, false, false, -1, &kPinset_google_root_pems }, + { "google.pk", true, false, false, -1, &kPinset_google_root_pems }, + { "google.pl", true, false, false, -1, &kPinset_google_root_pems }, + { "google.pn", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ps", true, false, false, -1, &kPinset_google_root_pems }, + { "google.pt", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ro", true, false, false, -1, &kPinset_google_root_pems }, + { "google.rs", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ru", true, false, false, -1, &kPinset_google_root_pems }, + { "google.rw", true, false, false, -1, &kPinset_google_root_pems }, + { "google.sc", true, false, false, -1, &kPinset_google_root_pems }, + { "google.se", true, false, false, -1, &kPinset_google_root_pems }, + { "google.sh", true, false, false, -1, &kPinset_google_root_pems }, + { "google.si", true, false, false, -1, &kPinset_google_root_pems }, + { "google.sk", true, false, false, -1, &kPinset_google_root_pems }, + { "google.sm", true, false, false, -1, &kPinset_google_root_pems }, + { "google.sn", true, false, false, -1, &kPinset_google_root_pems }, + { "google.so", true, false, false, -1, &kPinset_google_root_pems }, + { "google.st", true, false, false, -1, &kPinset_google_root_pems }, + { "google.td", true, false, false, -1, &kPinset_google_root_pems }, + { "google.tg", true, false, false, -1, &kPinset_google_root_pems }, + { "google.tk", true, false, false, -1, &kPinset_google_root_pems }, + { "google.tl", true, false, false, -1, &kPinset_google_root_pems }, + { "google.tm", true, false, false, -1, &kPinset_google_root_pems }, + { "google.tn", true, false, false, -1, &kPinset_google_root_pems }, + { "google.to", true, false, false, -1, &kPinset_google_root_pems }, + { "google.tt", true, false, false, -1, &kPinset_google_root_pems }, + { "google.us", true, false, false, -1, &kPinset_google_root_pems }, + { "google.uz", true, false, false, -1, &kPinset_google_root_pems }, + { "google.vg", true, false, false, -1, &kPinset_google_root_pems }, + { "google.vu", true, false, false, -1, &kPinset_google_root_pems }, + { "google.ws", true, false, false, -1, &kPinset_google_root_pems }, + { "googleadservices.com", true, false, false, -1, &kPinset_google_root_pems }, + { "googleapis.com", true, false, false, -1, &kPinset_google_root_pems }, + { "googlecode.com", true, false, false, -1, &kPinset_google_root_pems }, + { "googlecommerce.com", true, false, false, -1, &kPinset_google_root_pems }, + { "googlegroups.com", true, false, false, -1, &kPinset_google_root_pems }, + { "googlemail.com", false, false, false, -1, &kPinset_google_root_pems }, + { "googleplex.com", true, false, false, -1, &kPinset_google_root_pems }, + { "googlesyndication.com", true, false, false, -1, &kPinset_google_root_pems }, + { "googletagmanager.com", true, false, false, -1, &kPinset_google_root_pems }, + { "googletagservices.com", true, false, false, -1, &kPinset_google_root_pems }, + { "googleusercontent.com", true, false, false, -1, &kPinset_google_root_pems }, + { "goto.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "groups.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "gstatic.com", true, false, false, -1, &kPinset_google_root_pems }, + { "history.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "hostedtalkgadget.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "include-subdomains.pinning.example.com", true, false, false, -1, &kPinset_mozilla_test }, + { "liberty.lavabit.com", true, true, false, -1, &kPinset_lavabit }, + { "login.corp.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "mail.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "market.android.com", true, false, false, -1, &kPinset_google_root_pems }, + { "media.mozilla.com", true, false, true, -1, &kPinset_mozilla }, + { "mobile.twitter.com", true, false, false, -1, &kPinset_twitterCom }, + { "oauth.twitter.com", true, false, false, -1, &kPinset_twitterCom }, + { "pinningtest.appspot.com", true, false, false, -1, &kPinset_test }, + { "platform.twitter.com", true, false, false, -1, &kPinset_twitterCDN }, + { "play.google.com", false, false, false, -1, &kPinset_google_root_pems }, + { "plus.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "plus.sandbox.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "profiles.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "script.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "security.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "services.mozilla.com", true, true, false, -1, &kPinset_mozilla_services }, + { "sites.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "spreadsheets.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "ssl.google-analytics.com", true, false, false, -1, &kPinset_google_root_pems }, + { "talk.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "talkgadget.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "test-mode.pinning.example.com", true, true, false, -1, &kPinset_mozilla_test }, + { "tor2web.org", true, true, false, -1, &kPinset_tor2web }, + { "torproject.org", false, false, false, -1, &kPinset_tor }, + { "translate.googleapis.com", true, false, false, -1, &kPinset_google_root_pems }, + { "twimg.com", true, false, false, -1, &kPinset_twitterCDN }, + { "twitter.com", true, false, false, -1, &kPinset_twitterCDN }, + { "urchin.com", true, false, false, -1, &kPinset_google_root_pems }, + { "w-spotlight.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "wallet.google.com", true, false, false, -1, &kPinset_google_root_pems }, + { "webfilings-eu-mirror.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "webfilings-eu.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "webfilings-mirror-hrd.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "webfilings.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "wf-bigsky-master.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "wf-demo-eu.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "wf-demo-hrd.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "wf-dogfood-hrd.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "wf-pentest.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "wf-staging-hr.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "wf-training-hrd.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "wf-training-master.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "wf-trial-hrd.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "www.dropbox.com", true, false, false, -1, &kPinset_dropbox }, + { "www.gmail.com", false, false, false, -1, &kPinset_google_root_pems }, + { "www.googlemail.com", false, false, false, -1, &kPinset_google_root_pems }, + { "www.torproject.org", true, false, false, -1, &kPinset_tor }, + { "www.twitter.com", true, false, false, -1, &kPinset_twitterCom }, + { "xbrlsuccess.appspot.com", true, false, false, -1, &kPinset_google_root_pems }, + { "youtu.be", true, false, false, -1, &kPinset_google_root_pems }, + { "youtube-nocookie.com", true, false, false, -1, &kPinset_google_root_pems }, + { "youtube.com", true, false, false, -1, &kPinset_google_root_pems }, + { "ytimg.com", true, false, false, -1, &kPinset_google_root_pems }, +}; + +// Pinning Preload List Length = 331; + +static const int32_t kUnknownId = -1; + +static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1419674828470000); diff --git a/security/manager/boot/src/moz.build b/security/manager/boot/src/moz.build index b3d2127..50a345b 100644 --- a/security/manager/boot/src/moz.build +++ b/security/manager/boot/src/moz.build @@ -10,6 +10,7 @@ UNIFIED_SOURCES += [ 'nsSecurityHeaderParser.cpp', 'nsSecurityWarningDialogs.cpp', 'nsSiteSecurityService.cpp', + 'PublicKeyPinningService.cpp', ]
# nsSecureBrowserUIImpl.cpp cannot be built in unified mode because it forces NSPR logging. @@ -17,6 +18,11 @@ SOURCES += [ 'nsSecureBrowserUIImpl.cpp', ]
+ +LOCAL_INCLUDES += [ + '../../../pkix/include', +] + FAIL_ON_WARNINGS = True
MSVC_ENABLE_PGO = True diff --git a/security/manager/ssl/src/SSLServerCertVerification.cpp b/security/manager/ssl/src/SSLServerCertVerification.cpp index cf15586..18d9f55 100644 --- a/security/manager/ssl/src/SSLServerCertVerification.cpp +++ b/security/manager/ssl/src/SSLServerCertVerification.cpp @@ -637,8 +637,9 @@ NSSDetermineCertOverrideErrors(CertVerifier& certVerifier, // possible failure. // XXX TODO: convert to VerifySSLServerCert // XXX TODO: get rid of error log - certVerifier.VerifyCert(cert, stapledOCSPResponse, certificateUsageSSLServer, - now, infoObject, 0, nullptr, nullptr, verify_log); + certVerifier.VerifyCert(cert, certificateUsageSSLServer, + now, infoObject, infoObject->GetHostNameRaw(), + 0, stapledOCSPResponse, nullptr, nullptr, verify_log);
// Check the name field against the desired hostname. if (CERT_VerifyCertName(cert, infoObject->GetHostNameRaw()) != SECSuccess) { diff --git a/security/manager/ssl/src/SharedCertVerifier.h b/security/manager/ssl/src/SharedCertVerifier.h index 32a92a3..c7c1936 100644 --- a/security/manager/ssl/src/SharedCertVerifier.h +++ b/security/manager/ssl/src/SharedCertVerifier.h @@ -24,12 +24,14 @@ public: missing_cert_download_config ac, crl_download_config cdc, #endif ocsp_download_config odc, ocsp_strict_config osc, - ocsp_get_config ogc) + ocsp_get_config ogc, + pinning_enforcement_config pinningEnforcementLevel) : mozilla::psm::CertVerifier(ic, #ifndef NSS_NO_LIBPKIX ac, cdc, #endif - odc, osc, ogc) + odc, osc, ogc, + pinningEnforcementLevel) { } }; diff --git a/security/manager/ssl/src/nsCMS.cpp b/security/manager/ssl/src/nsCMS.cpp index 70a3c49..ee76c07 100644 --- a/security/manager/ssl/src/nsCMS.cpp +++ b/security/manager/ssl/src/nsCMS.cpp @@ -264,9 +264,10 @@ nsresult nsCMSMessage::CommonVerifySignature(unsigned char* aDigestData, uint32_ NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
{ - SECStatus srv = certVerifier->VerifyCert(si->cert, nullptr, + SECStatus srv = certVerifier->VerifyCert(si->cert, certificateUsageEmailSigner, - PR_Now(), nullptr /*XXX pinarg*/); + PR_Now(), nullptr /*XXX pinarg*/, + nullptr /*hostname*/); if (srv != SECSuccess) { PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("nsCMSMessage::CommonVerifySignature - signing cert not trusted now\n")); diff --git a/security/manager/ssl/src/nsNSSCertificate.cpp b/security/manager/ssl/src/nsNSSCertificate.cpp index 88fb8ed..70b9c89 100644 --- a/security/manager/ssl/src/nsNSSCertificate.cpp +++ b/security/manager/ssl/src/nsNSSCertificate.cpp @@ -829,10 +829,12 @@ nsNSSCertificate::GetChain(nsIArray** _rvChain)
// We want to test all usages, but we start with server because most of the // time Firefox users care about server certs. - certVerifier->VerifyCert(mCert.get(), nullptr, + certVerifier->VerifyCert(mCert.get(), certificateUsageSSLServer, PR_Now(), nullptr, /*XXX fixme*/ + nullptr, /* hostname */ CertVerifier::FLAG_LOCAL_ONLY, + nullptr, /* stapledOCSPResponse */ &nssChain); // This is the whitelist of all non-SSLServer usages that are supported by // verifycert. @@ -851,10 +853,12 @@ nsNSSCertificate::GetChain(nsIArray** _rvChain) PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("pipnss: PKIX attempting chain(%d) for '%s'\n", usage, mCert->nickname)); - certVerifier->VerifyCert(mCert.get(), nullptr, + certVerifier->VerifyCert(mCert.get(), usage, PR_Now(), nullptr, /*XXX fixme*/ + nullptr, /*hostname*/ CertVerifier::FLAG_LOCAL_ONLY, + nullptr, /* stapledOCSPResponse */ &nssChain); }
@@ -1467,10 +1471,11 @@ nsNSSCertificate::hasValidEVOidTag(SECOidTag& resultOidTag, bool& validEV)
uint32_t flags = mozilla::psm::CertVerifier::FLAG_LOCAL_ONLY | mozilla::psm::CertVerifier::FLAG_MUST_BE_EV; - SECStatus rv = certVerifier->VerifyCert(mCert.get(), nullptr, + SECStatus rv = certVerifier->VerifyCert(mCert.get(), certificateUsageSSLServer, PR_Now(), nullptr /* XXX pinarg */, - flags, nullptr, &resultOidTag); + nullptr /* hostname */, + flags, nullptr /* stapledOCSPResponse */ , nullptr, &resultOidTag);
if (rv != SECSuccess) { resultOidTag = SEC_OID_UNKNOWN; diff --git a/security/manager/ssl/src/nsNSSCertificateDB.cpp b/security/manager/ssl/src/nsNSSCertificateDB.cpp index 40b03c1..c9fd8ba 100644 --- a/security/manager/ssl/src/nsNSSCertificateDB.cpp +++ b/security/manager/ssl/src/nsNSSCertificateDB.cpp @@ -634,9 +634,10 @@ nsNSSCertificateDB::ImportEmailCertificate(uint8_t * data, uint32_t length,
mozilla::pkix::ScopedCERTCertList certChain;
- SECStatus rv = certVerifier->VerifyCert(node->cert, nullptr, + SECStatus rv = certVerifier->VerifyCert(node->cert, certificateUsageEmailRecipient, - now, ctx, 0, &certChain); + now, ctx, nullptr, 0, + nullptr, &certChain);
if (rv != SECSuccess) { nsCOMPtr<nsIX509Cert> certToShow = nsNSSCertificate::Create(node->cert); @@ -801,9 +802,10 @@ nsNSSCertificateDB::ImportValidCACertsInList(CERTCertList *certList, nsIInterfac !CERT_LIST_END(node,certList); node = CERT_LIST_NEXT(node)) { mozilla::pkix::ScopedCERTCertList certChain; - SECStatus rv = certVerifier->VerifyCert(node->cert, nullptr, + SECStatus rv = certVerifier->VerifyCert(node->cert, certificateUsageVerifyCA, - PR_Now(), ctx, 0, &certChain); + PR_Now(), ctx, nullptr, 0, nullptr, + &certChain); if (rv != SECSuccess) { nsCOMPtr<nsIX509Cert> certToShow = nsNSSCertificate::Create(node->cert); DisplayCertificateAlert(ctx, "NotImportingUnverifiedCert", certToShow, proofOfLock); @@ -1381,9 +1383,10 @@ nsNSSCertificateDB::FindCertByEmailAddress(nsISupports *aToken, const char *aEma !CERT_LIST_END(node, certlist); node = CERT_LIST_NEXT(node)) {
- SECStatus srv = certVerifier->VerifyCert(node->cert, nullptr, + SECStatus srv = certVerifier->VerifyCert(node->cert, certificateUsageEmailRecipient, - PR_Now(), nullptr /*XXX pinarg*/); + PR_Now(), nullptr /*XXX pinarg*/, + nullptr /*hostname*/); if (srv == SECSuccess) { break; } @@ -1772,10 +1775,12 @@ nsNSSCertificateDB::VerifyCertNow(nsIX509Cert* aCert, SECOidTag evOidPolicy; SECStatus srv;
- srv = certVerifier->VerifyCert(nssCert, nullptr, + srv = certVerifier->VerifyCert(nssCert, aUsage, PR_Now(), nullptr, // Assume no context + nullptr, // hostname aFlags, + nullptr, // stapledOCSPResponse &resultChain, &evOidPolicy);
diff --git a/security/manager/ssl/src/nsNSSComponent.cpp b/security/manager/ssl/src/nsNSSComponent.cpp index 44b3808..c4bd566 100644 --- a/security/manager/ssl/src/nsNSSComponent.cpp +++ b/security/manager/ssl/src/nsNSSComponent.cpp @@ -996,6 +996,13 @@ void nsNSSComponent::setValidationOptions(bool isInitialSetting, } }
+ // Default pinning enforcement level is disabled. + CertVerifier::pinning_enforcement_config + pinningEnforcementLevel = + static_castCertVerifier::pinning_enforcement_config + (Preferences::GetInt("security.cert_pinning.enforcement_level", + CertVerifier::pinningDisabled)); + CertVerifier::ocsp_download_config odc; CertVerifier::ocsp_strict_config osc; CertVerifier::ocsp_get_config ogc; @@ -1009,7 +1016,7 @@ void nsNSSComponent::setValidationOptions(bool isInitialSetting, crlDownloading ? CertVerifier::crl_download_allowed : CertVerifier::crl_local_only, #endif - odc, osc, ogc); + odc, osc, ogc, pinningEnforcementLevel);
// mozilla::pkix has its own OCSP cache, so disable the NSS cache // if appropriate. @@ -1644,7 +1651,8 @@ nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic, || prefName.Equals("security.OCSP.GET.enabled") || prefName.Equals("security.ssl.enable_ocsp_stapling") || prefName.Equals("security.use_mozillapkix_verification") - || prefName.Equals("security.use_libpkix_verification")) { + || prefName.Equals("security.use_libpkix_verification") + || prefName.Equals("security.cert_pinning.enforcement_level")) { MutexAutoLock lock(mutex); setValidationOptions(false, lock); } else if (prefName.Equals("network.ntlm.send-lm-response")) { diff --git a/security/manager/ssl/src/nsUsageArrayHelper.cpp b/security/manager/ssl/src/nsUsageArrayHelper.cpp index 7f7249f..dd65ae5 100644 --- a/security/manager/ssl/src/nsUsageArrayHelper.cpp +++ b/security/manager/ssl/src/nsUsageArrayHelper.cpp @@ -105,8 +105,9 @@ nsUsageArrayHelper::check(uint32_t previousCheckResult, MOZ_CRASH("unknown cert usage passed to check()"); }
- SECStatus rv = certVerifier->VerifyCert(mCert, nullptr, aCertUsage, - time, nullptr /*XXX:wincx*/, flags); + SECStatus rv = certVerifier->VerifyCert(mCert, aCertUsage, time, + nullptr /*XXX:wincx*/, + nullptr /*hostname*/, flags);
if (rv == SECSuccess) { typestr.Append(suffix); diff --git a/security/manager/ssl/tests/unit/head_psm.js b/security/manager/ssl/tests/unit/head_psm.js index 58e838b..ae25ea9e 100644 --- a/security/manager/ssl/tests/unit/head_psm.js +++ b/security/manager/ssl/tests/unit/head_psm.js @@ -52,6 +52,7 @@ const SEC_ERROR_OCSP_INVALID_SIGNING_CERT = SEC_ERROR_BASE + 144; const SEC_ERROR_POLICY_VALIDATION_FAILED = SEC_ERROR_BASE + 160; // -8032 const SEC_ERROR_OCSP_BAD_SIGNATURE = SEC_ERROR_BASE + 157; const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = SEC_ERROR_BASE + 176; +const SEC_ERROR_APPLICATION_CALLBACK_ERROR = SEC_ERROR_BASE + 178;
const SSL_ERROR_BAD_CERT_DOMAIN = SSL_ERROR_BASE + 12;
diff --git a/security/manager/ssl/tests/unit/test_cert_overrides.js b/security/manager/ssl/tests/unit/test_cert_overrides.js index 11e14f3b..91fc77eb 100644 --- a/security/manager/ssl/tests/unit/test_cert_overrides.js +++ b/security/manager/ssl/tests/unit/test_cert_overrides.js @@ -232,7 +232,7 @@ function add_distrust_override_test(certFileName, hostName, let certToDistrust = constructCertFromFile(certFileName);
add_test(function () { - // "pu" means the cert is actively distrusted. + // Add an entry to the NSS certDB that says to distrust the cert setCertTrust(certToDistrust, "pu,,"); clearSessionCache(); run_next_test(); diff --git a/security/manager/ssl/tests/unit/test_pinning.js b/security/manager/ssl/tests/unit/test_pinning.js new file mode 100644 index 0000000..dc779dd --- /dev/null +++ b/security/manager/ssl/tests/unit/test_pinning.js @@ -0,0 +1,182 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// For all cases, the acceptable pinset includes only certificates pinned to +// Test End Entity Cert (signed by issuer testCA). Other certificates +// are issued by otherCA, which is never in the pinset but is a user-specified +// trust anchor. This test covers multiple cases: +// +// Pinned domain include-subdomains.pinning.example.com includes subdomains +// - PASS: include-subdomains.pinning.example.com serves a correct cert +// - PASS: good.include-subdomains.pinning.example.com serves a correct cert +// - FAIL (strict): bad.include-subdomains.pinning.example.com serves a cert +// not in the pinset +// - PASS (mitm): bad.include-subdomains.pinning.example.com serves a cert not +// in the pinset, but issued by a user-specified trust domain +// +// Pinned domain exclude-subdomains.pinning.example.com excludes subdomains +// - PASS: exclude-subdomains.pinning.example.com serves a correct cert +// - FAIL: exclude-subdomains.pinning.example.com services an incorrect cert +// (TODO: test using verifyCertnow) +// - PASS: sub.exclude-subdomains.pinning.example.com serves an incorrect cert + +"use strict"; + +do_get_profile(); // must be called before getting nsIX509CertDB +const certdb = Cc["@mozilla.org/security/x509certdb;1"] + .getService(Ci.nsIX509CertDB); + +function test_strict() { + // In strict mode, we always evaluate pinning data, regardless of whether the + // issuer is a built-in trust anchor. We only enforce pins that are not in + // test mode. + add_test(function() { + Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2); + run_next_test(); + }); + + // If a host should be pinned but other errors (particularly overridable + // errors) like 'unknown issuer' are encountered, the pinning error takes + // precedence. This prevents overrides for such hosts. + add_connection_test("unknownissuer.include-subdomains.pinning.example.com", + getXPCOMStatusFromNSS(MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE)); + + // Issued by otherCA, which is not in the pinset for pinning.example.com. + add_connection_test("bad.include-subdomains.pinning.example.com", + getXPCOMStatusFromNSS(MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE)); + + // These domains serve certs that match the pinset. + add_connection_test("include-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("good.include-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("exclude-subdomains.pinning.example.com", Cr.NS_OK); + + // This domain serves a cert that doesn't match the pinset, but subdomains + // are excluded. + add_connection_test("sub.exclude-subdomains.pinning.example.com", Cr.NS_OK); + + // This domain's pinset is exactly the same as + // include-subdomains.pinning.example.com, serves the same cert as + // bad.include-subdomains.pinning.example.com, but it should pass because + // it's in test_mode. + add_connection_test("test-mode.pinning.example.com", Cr.NS_OK); +} + +function test_mitm() { + // In MITM mode, we allow pinning to pass if the chain resolves to any + // user-specified trust anchor, even if it is not in the pinset. + add_test(function() { + Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 1); + run_next_test(); + }); + + add_connection_test("include-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("good.include-subdomains.pinning.example.com", Cr.NS_OK); + + add_connection_test("unknownissuer.include-subdomains.pinning.example.com", + getXPCOMStatusFromNSS(SEC_ERROR_UNKNOWN_ISSUER)); + + // In this case, even though otherCA is not in the pinset, it is a + // user-specified trust anchor and the pinning check succeeds. + add_connection_test("bad.include-subdomains.pinning.example.com", Cr.NS_OK); + + add_connection_test("exclude-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("sub.exclude-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("test-mode.pinning.example.com", Cr.NS_OK); +}; + +function test_disabled() { + // Disable pinning. + add_test(function() { + Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 0); + run_next_test(); + }); + + add_connection_test("include-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("good.include-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("bad.include-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("exclude-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("sub.exclude-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("test-mode.pinning.example.com", Cr.NS_OK); + + add_connection_test("unknownissuer.include-subdomains.pinning.example.com", + getXPCOMStatusFromNSS(SEC_ERROR_UNKNOWN_ISSUER)); +} + +function test_enforce_test_mode() { + // In enforce test mode, we always enforce all pins, even test pins. + add_test(function() { + Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 3); + run_next_test(); + }); + + add_connection_test("unknownissuer.include-subdomains.pinning.example.com", + getXPCOMStatusFromNSS(MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE)); + + // Issued by otherCA, which is not in the pinset for pinning.example.com. + add_connection_test("bad.include-subdomains.pinning.example.com", + getXPCOMStatusFromNSS(MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE)); + + // These domains serve certs that match the pinset. + add_connection_test("include-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("good.include-subdomains.pinning.example.com", Cr.NS_OK); + add_connection_test("exclude-subdomains.pinning.example.com", Cr.NS_OK); + + // This domain serves a cert that doesn't match the pinset, but subdomains + // are excluded. + add_connection_test("sub.exclude-subdomains.pinning.example.com", Cr.NS_OK); + + // This domain's pinset is exactly the same as + // include-subdomains.pinning.example.com, serves the same cert as + // bad.include-subdomains.pinning.example.com, is in test-mode, but we are + // enforcing test mode pins. + add_connection_test("test-mode.pinning.example.com", + getXPCOMStatusFromNSS(MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE)); +} + +function check_pinning_telemetry() { + let service = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + let prod_histogram = service.getHistogramById("CERT_PINNING_RESULTS") + .snapshot(); + let test_histogram = service.getHistogramById("CERT_PINNING_TEST_RESULTS") + .snapshot(); + // Because all of our test domains are pinned to user-specified trust + // anchors, effectively only strict mode and enforce test-mode get evaluated + do_check_eq(prod_histogram.counts[0], 4); // Failure count + do_check_eq(prod_histogram.counts[1], 4); // Success count + do_check_eq(test_histogram.counts[0], 2); // Failure count + do_check_eq(test_histogram.counts[1], 0); // Success count + + let moz_prod_histogram = service.getHistogramById("CERT_PINNING_MOZ_RESULTS") + .snapshot(); + let moz_test_histogram = + service.getHistogramById("CERT_PINNING_MOZ_TEST_RESULTS").snapshot(); + do_check_eq(moz_prod_histogram.counts[0], 0); // Failure count + do_check_eq(moz_prod_histogram.counts[1], 0); // Success count + do_check_eq(moz_test_histogram.counts[0], 0); // Failure count + do_check_eq(moz_test_histogram.counts[1], 0); // Success count + + let per_host_histogram = + service.getHistogramById("CERT_PINNING_MOZ_RESULTS_BY_HOST").snapshot(); + do_check_eq(per_host_histogram.counts[0], 0); // Failure count + do_check_eq(per_host_histogram.counts[1], 2); // Success count + run_next_test(); +} + +function run_test() { + add_tls_server_setup("BadCertServer"); + + // Add a user-specified trust anchor. + addCertFromFile(certdb, "tlsserver/other-test-ca.der", "CTu,u,u"); + + test_strict(); + test_mitm(); + test_disabled(); + test_enforce_test_mode(); + + add_test(function () { + check_pinning_telemetry(); + }); + run_next_test(); +} diff --git a/security/manager/ssl/tests/unit/tlsserver/cert8.db b/security/manager/ssl/tests/unit/tlsserver/cert8.db index 13c101f..7da5d7f 100644 Binary files a/security/manager/ssl/tests/unit/tlsserver/cert8.db and b/security/manager/ssl/tests/unit/tlsserver/cert8.db differ diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp index a4c59a4..5df134a 100644 --- a/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp +++ b/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp @@ -24,6 +24,7 @@ struct BadCertHost const char *mCertName; };
+// Hostname, cert nickname pairs. const BadCertHost sBadCertHosts[] = { { "expired.example.com", "expired" }, @@ -42,6 +43,16 @@ const BadCertHost sBadCertHosts[] = { "inadequatekeyusage.example.com", "inadequatekeyusage" }, { "selfsigned-inadequateEKU.example.com", "selfsigned-inadequateEKU" }, { "self-signed-end-entity-with-cA-true.example.com", "self-signed-EE-with-cA-true" }, + // All of include-subdomains.pinning.example.com is pinned to End Entity + // Test Cert with nick localhostAndExampleCom. Any other nick will only + // pass pinning when security.cert_pinning.enforcement.level != strict and + // otherCA is added as a user-specified trust anchor. See StaticHPKPins.h. + { "include-subdomains.pinning.example.com", "localhostAndExampleCom" }, + { "good.include-subdomains.pinning.example.com", "localhostAndExampleCom" }, + { "bad.include-subdomains.pinning.example.com", "otherIssuerEE" }, + { "exclude-subdomains.pinning.example.com", "localhostAndExampleCom" }, + { "sub.exclude-subdomains.pinning.example.com", "otherIssuerEE" }, + { "test-mode.pinning.example.com", "otherIssuerEE" }, { nullptr, nullptr } };
diff --git a/security/manager/ssl/tests/unit/tlsserver/default-ee.der b/security/manager/ssl/tests/unit/tlsserver/default-ee.der index ac98037..9b2359d 100644 Binary files a/security/manager/ssl/tests/unit/tlsserver/default-ee.der and b/security/manager/ssl/tests/unit/tlsserver/default-ee.der differ diff --git a/security/manager/ssl/tests/unit/tlsserver/generate_certs.sh b/security/manager/ssl/tests/unit/tlsserver/generate_certs.sh index ef388bb..6075a5c 100755 --- a/security/manager/ssl/tests/unit/tlsserver/generate_certs.sh +++ b/security/manager/ssl/tests/unit/tlsserver/generate_certs.sh @@ -12,7 +12,10 @@ # # NB: This will cause the following files to be overwritten if they are in # the output directory: -# cert8.db, key3.db, secmod.db, ocsp-ca.der, ocsp-other-ca.der +# cert8.db, key3.db, secmod.db, ocsp-ca.der, ocsp-other-ca.der, default-ee.der +# NB: You must run genHPKPStaticPins.js after running this file, since its +# output (StaticHPKPins.h) depends on default-ee.der + set -x set -e
@@ -25,11 +28,13 @@ OBJDIR=${1} OUTPUT_DIR=${2} RUN_MOZILLA="$OBJDIR/dist/bin/run-mozilla.sh" CERTUTIL="$OBJDIR/dist/bin/certutil" +# On BSD, mktemp requires either a template or a prefix. +MKTEMP="mktemp temp.XXXX"
-NOISE_FILE=`mktemp` +NOISE_FILE=`$MKTEMP` # Make a good effort at putting something unique in the noise file. date +%s%N > "$NOISE_FILE" -PASSWORD_FILE=`mktemp` +PASSWORD_FILE=`$MKTEMP`
function cleanup { rm -f "$NOISE_FILE" "$PASSWORD_FILE" @@ -134,7 +139,11 @@ function make_delegated {
make_CA testCA 'CN=Test CA' test-ca.der make_CA otherCA 'CN=Other test CA' other-test-ca.der -make_EE localhostAndExampleCom 'CN=Test End-entity' testCA "localhost,*.example.com" + +make_EE localhostAndExampleCom 'CN=Test End-entity' testCA "localhost,*.example.com,*.pinning.example.com,*.include-subdomains.pinning.example.com,*.exclude-subdomains.pinning.example.com" +# Make an EE cert issued by otherCA +make_EE otherIssuerEE 'CN=Wrong CA Pin Test End-Entity' otherCA "*.include-subdomains.pinning.example.com,*.exclude-subdomains.pinning.example.com,*.pinning.example.com" + $RUN_MOZILLA $CERTUTIL -d $OUTPUT_DIR -L -n localhostAndExampleCom -r > $OUTPUT_DIR/default-ee.der # A cert that is like localhostAndExampleCom, but with a different serial number for # testing the "OCSP response is from the right issuer, but it is for the wrong cert" diff --git a/security/manager/ssl/tests/unit/tlsserver/key3.db b/security/manager/ssl/tests/unit/tlsserver/key3.db index 283e8fb..2d4dd29 100644 Binary files a/security/manager/ssl/tests/unit/tlsserver/key3.db and b/security/manager/ssl/tests/unit/tlsserver/key3.db differ diff --git a/security/manager/ssl/tests/unit/tlsserver/other-test-ca.der b/security/manager/ssl/tests/unit/tlsserver/other-test-ca.der index 794fb9c..742bb86 100644 Binary files a/security/manager/ssl/tests/unit/tlsserver/other-test-ca.der and b/security/manager/ssl/tests/unit/tlsserver/other-test-ca.der differ diff --git a/security/manager/ssl/tests/unit/tlsserver/secmod.db b/security/manager/ssl/tests/unit/tlsserver/secmod.db index a5f2f60..7a0e2b5 100644 Binary files a/security/manager/ssl/tests/unit/tlsserver/secmod.db and b/security/manager/ssl/tests/unit/tlsserver/secmod.db differ diff --git a/security/manager/ssl/tests/unit/tlsserver/test-ca.der b/security/manager/ssl/tests/unit/tlsserver/test-ca.der index f4c4863..356de5b 100644 Binary files a/security/manager/ssl/tests/unit/tlsserver/test-ca.der and b/security/manager/ssl/tests/unit/tlsserver/test-ca.der differ diff --git a/security/manager/ssl/tests/unit/xpcshell.ini b/security/manager/ssl/tests/unit/xpcshell.ini index 7935c2d..24b0ffc 100644 --- a/security/manager/ssl/tests/unit/xpcshell.ini +++ b/security/manager/ssl/tests/unit/xpcshell.ini @@ -78,3 +78,7 @@ requesttimeoutfactor = 4 run-sequentially = hardcoded ports # Bug 676972: this test times out on Android and B2G skip-if = os == "android" || buildapp == "b2g" +[test_pinning.js] +run-sequentially = hardcoded ports +# Bug 676972: test fails consistently on Android and B2G +fail-if = os == "android" || buildapp == "b2g" \ No newline at end of file diff --git a/security/manager/tools/PreloadedHPKPins.json b/security/manager/tools/PreloadedHPKPins.json new file mode 100644 index 0000000..ed3b9b7 --- /dev/null +++ b/security/manager/tools/PreloadedHPKPins.json @@ -0,0 +1,247 @@ +// -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// The top-level element is a dictionary with two keys: "pinsets" maps details +// of certificate pinning to a name and "entries" contains the HPKP details for +// each host. +// +// "pinsets" is a list of objects. Each object has the following members: +// name: (string) the name of the pinset +// sha256_hashes: (list of strings) the set of allowed SPKIs hashes +// +// For a given pinset, a certificate is accepted if at least one of the +// Subject Public Key Infos (SPKIs) is found in the chain. SPKIs are specified +// as names, which must match up with the name given in the Mozilla root store. +// +// "entries" is a list of objects. Each object has the following members: +// name: (string) the DNS name of the host in question +// include_subdomains: (optional bool) whether subdomains of |name| are also covered +// pins: (string) the |name| member of an object in |pinsets| +// +// "extra_certs" is a list of base64-encoded certificates. These are used in +// pinsets that reference certificates not in our root program (for example, +// Facebook). + +// equifax -> aus3 +// Geotrust Primary -> www.mozilla.org +// Geotrust Global -> *. addons.mozilla.org +{ + "chromium_data" : { + "cert_file_url": "https://src.chromium.org/chrome/trunk/src/net/http/transport_security_state_...", + "json_file_url": "https://src.chromium.org/chrome/trunk/src/net/http/transport_security_state_...", + "substitute_pinsets": { + // Use the larger google_root_pems pinset instead of google + "google": "google_root_pems" + }, + "production_pinsets": [ + "google_root_pems" + ], + "production_domains": [ + // Chrome's test domain. + "pinningtest.appspot.com", + // Dropbox + "dropbox.com", + "www.dropbox.com", + // Twitter + "api.twitter.com", + "business.twitter.com", + "dev.twitter.com", + "mobile.twitter.com", + "oauth.twitter.com", + "platform.twitter.com", + "twimg.com", + "www.twitter.com", + // Tor + "torproject.org", + "blog.torproject.org", + "check.torproject.org", + "dist.torproject.org", + "www.torproject.org" + ], + "exclude_domains" : [ + // Chrome's entry for twitter.com doesn't include subdomains, so replace + // it with our own entry below which also uses an expanded pinset. + "twitter.com" + ] + }, + "pinsets": [ + { + // From bug 772756, mozilla uses GeoTrust, Digicert and Thawte. Our + // cdn sites use Verisign and Baltimore. We exclude 1024-bit root certs + // from all providers. geotrust ca info: + // http://www.geotrust.com/resources/root-certificates/index.html + "name": "mozilla", + "sha256_hashes": [ + "Baltimore CyberTrust Root", + "DigiCert Assured ID Root CA", + "DigiCert Global Root CA", + "DigiCert High Assurance EV Root CA", + "GeoTrust Global CA", + "GeoTrust Global CA 2", + "GeoTrust Primary Certification Authority", + "GeoTrust Primary Certification Authority - G2", + "GeoTrust Primary Certification Authority - G3", + "GeoTrust Universal CA", + "GeoTrust Universal CA 2", + "thawte Primary Root CA", + "thawte Primary Root CA - G2", + "thawte Primary Root CA - G3", + "Verisign Class 1 Public Primary Certification Authority - G3", + "Verisign Class 2 Public Primary Certification Authority - G3", + "Verisign Class 3 Public Primary Certification Authority - G3", + "VeriSign Class 3 Public Primary Certification Authority - G4", + "VeriSign Class 3 Public Primary Certification Authority - G5", + "Verisign Class 4 Public Primary Certification Authority - G3", + "VeriSign Universal Root Certification Authority" + ] + }, + { + "name": "mozilla_services", + "sha256_hashes": [ + "DigiCert Global Root CA" + ] + }, + // For pinning tests on pinning.example.com, the certificate must be 'End + // Entity Test Cert' + { + "name": "mozilla_test", + "sha256_hashes": [ + "End Entity Test Cert" + ] + }, + // Google's root PEMs. Chrome pins only to their intermediate certs, but + // they'd like us to be more liberal. For the initial list, we are using + // the certs from http://pki.google.com/roots.pem. + // We have no built-in for commented out CAs. + { + "name": "google_root_pems", + "sha256_hashes": [ + "AddTrust External Root", + "AddTrust Low-Value Services Root", + "AddTrust Public Services Root", + "AddTrust Qualified Certificates Root", + "AffirmTrust Commercial", + "AffirmTrust Networking", + "AffirmTrust Premium", + "AffirmTrust Premium ECC", + "America Online Root Certification Authority 1", + "America Online Root Certification Authority 2", + "Baltimore CyberTrust Root", + "Comodo AAA Services root", + "COMODO Certification Authority", + "COMODO ECC Certification Authority", + "Comodo Secure Services root", + "Comodo Trusted Services root", + "Cybertrust Global Root", + "DigiCert Assured ID Root CA", + "DigiCert Global Root CA", + "DigiCert High Assurance EV Root CA", + "Entrust.net Premium 2048 Secure Server CA", + // "Entrust.net Secure Server CA", + "Entrust Root Certification Authority", + "Equifax Secure CA", + "Equifax Secure eBusiness CA 1", + // "Equifax Secure eBusiness CA 2", + "Equifax Secure Global eBusiness CA", + "GeoTrust Global CA", + "GeoTrust Global CA 2", + "GeoTrust Primary Certification Authority", + "GeoTrust Primary Certification Authority - G2", + "GeoTrust Primary Certification Authority - G3", + "GeoTrust Universal CA", + "GeoTrust Universal CA 2", + "GlobalSign Root CA", + "GlobalSign Root CA - R2", + "GlobalSign Root CA - R3", + "Go Daddy Class 2 CA", + "Go Daddy Root Certificate Authority - G2", + // "GTE CyberTrust Global Root", + "Network Solutions Certificate Authority", + // "RSA Root Certificate 1", + "Starfield Class 2 CA", + "Starfield Root Certificate Authority - G2", + "Starfield Services Root Certificate Authority - G2", + "StartCom Certification Authority", + "StartCom Certification Authority", + "StartCom Certification Authority G2", + "TC TrustCenter Class 2 CA II", + "TC TrustCenter Class 3 CA II", + "TC TrustCenter Universal CA I", + "TC TrustCenter Universal CA III", + "Thawte Premium Server CA", + "thawte Primary Root CA", + "thawte Primary Root CA - G2", + "thawte Primary Root CA - G3", + "Thawte Server CA", + "UTN DATACorp SGC Root CA", + "UTN USERFirst Hardware Root CA", + // "ValiCert Class 1 VA", + // "ValiCert Class 2 VA", + "Verisign Class 3 Public Primary Certification Authority", + "Verisign Class 3 Public Primary Certification Authority", + "Verisign Class 3 Public Primary Certification Authority - G2", + "Verisign Class 3 Public Primary Certification Authority - G3", + "VeriSign Class 3 Public Primary Certification Authority - G4", + "VeriSign Class 3 Public Primary Certification Authority - G5", + "Verisign Class 4 Public Primary Certification Authority - G3", + "VeriSign Universal Root Certification Authority", + "XRamp Global CA Root" + ] + }, + { + "name": "facebook", + "sha256_hashes": [ + "Verisign Class 3 Public Primary Certification Authority - G3", + "DigiCert High Assurance EV Root CA", + "DigiCert ECC Secure Server CA" + ] + } + ], + + "entries": [ + // Only domains that are operationally crucial to Firefox can have per-host + // telemetry reporting (the "id") field + { "name": "addons.mozilla.org", "include_subdomains": true, + "pins": "mozilla", "test_mode": false, "id": 1 }, + { "name": "addons.mozilla.net", "include_subdomains": true, + "pins": "mozilla", "test_mode": false, "id": 2 }, + { "name": "aus4.mozilla.org", "include_subdomains": true, + "pins": "mozilla", "test_mode": true, "id": 3 }, + { "name": "accounts.firefox.com", "include_subdomains": true, + "pins": "mozilla_services", "test_mode": false, "id": 4 }, + { "name": "api.accounts.firefox.com", "include_subdomains": true, + "pins": "mozilla_services", "test_mode": false, "id": 5 }, + { "name": "cdn.mozilla.net", "include_subdomains": true, + "pins": "mozilla", "test_mode": false }, + { "name": "cdn.mozilla.org", "include_subdomains": true, + "pins": "mozilla", "test_mode": false }, + { "name": "media.mozilla.com", "include_subdomains": true, + "pins": "mozilla", "test_mode": false }, + { "name": "services.mozilla.com", "include_subdomains": true, + "pins": "mozilla_services", "test_mode": true }, + { "name": "include-subdomains.pinning.example.com", + "include_subdomains": true, "pins": "mozilla_test", + "test_mode": false }, + // Example domain to collect per-host stats for telemetry tests. + { "name": "exclude-subdomains.pinning.example.com", + "include_subdomains": false, "pins": "mozilla_test", + "test_mode": false, "id": 0 }, + { "name": "test-mode.pinning.example.com", "include_subdomains": true, + "pins": "mozilla_test", "test_mode": true }, + // Expand twitter's pinset to include all of *.twitter.com and use + // twitterCDN. More specific rules take precedence because we search for + // exact domain name first. + { "name": "twitter.com", "include_subdomains": true, + "pins": "twitterCDN", "test_mode": false }, + // Facebook (not pinned by Chrome) + { "name": "facebook.com", "include_subdomains": true, + "pins": "facebook", "test_mode": true } + ], + + "extra_certificates": [ + // DigiCert ECC Secure Server CA (for Facebook) + "MIIDrDCCApSgAwIBAgIQCssoukZe5TkIdnRw883GEjANBgkqhkiG9w0BAQwFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJjAkBgNVBAMTHURpZ2lDZXJ0IEVDQyBTZWN1cmUgU2VydmVyIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4ghC6nfYJN6gLGSkE85AnCNyqQIKDjc/ITa4jVMU9tWRlUvzlgKNcR7E2Munn17voOZ/WpIRllNv68DLP679Wz9HJOeaBy6Wvqgvu1cYr3GkvXg6HuhbPGtkESvMNCuMo4IBITCCAR0wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAdBgNVHQ4EFgQUo53mH/naOU/AbuiRy5Wl2jHiCp8wHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEMBQADggEBAMeKoENL7HTJxavVHzA1Nm6YVntIrAVjrnuaVyRXzG/63qttnMe2uuzO58pzZNvfBDcKAEmzP58 mrZGMIOgfiA4q+2Y3yDDo0sIkp0VILeoBUEoxlBPfjV/aKrtJPGHzecicZpIalir0ezZYoyxBEHQa0+1IttK7igZFcTMQMHp6mCHdJLnsnLWSB62DxsRq+HfmNb4TDydkskO/g+l3VtsIh5RHFPVfKK+jaEyDj2D3loB5hWp2Jp2VDCADjT7ueihlZGak2YPqmXTNbk19HOuNssWvFhtOyPNV6og4ETQdEa8/B6hPatJ0ES8q/HO3X8IVQwVs1n3aAr0im0/T+Xc=" + ] +} diff --git a/security/manager/tools/genHPKPStaticPins.js b/security/manager/tools/genHPKPStaticPins.js new file mode 100644 index 0000000..d16d017 --- /dev/null +++ b/security/manager/tools/genHPKPStaticPins.js @@ -0,0 +1,576 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// How to run this file: +// 1. [obtain firefox source code] +// 2. [build/obtain firefox binaries] +// 3. run `[path to]/run-mozilla.sh [path to]/xpcshell \ +// [path to]/genHPKPStaticpins.js \ +// [absolute path to]/PreloadedHPKPins.json \ +// [absolute path to]/default-ee.der \ +// [absolute path to]/StaticHPKPins.h + +if (arguments.length != 3) { + throw "Usage: genHPKPStaticPins.js " + + "<absolute path to PreloadedHPKPins.json> " + + "<absolute path to default-ee.der> " + + "<absolute path to StaticHPKPins.h>"; +} + +const { 'classes': Cc, 'interfaces': Ci, 'utils': Cu, 'results': Cr } = Components; + +let { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {}); +let { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {}); +let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); + +let gCertDB = Cc["@mozilla.org/security/x509certdb;1"] + .getService(Ci.nsIX509CertDB); + +const BUILT_IN_NICK_PREFIX = "Builtin Object Token:"; +const SHA1_PREFIX = "sha1/"; +const SHA256_PREFIX = "sha256/"; +const GOOGLE_PIN_PREFIX = "GOOGLE_PIN_"; + +// Pins expire in 14 weeks (6 weeks on Beta + 8 weeks on stable) +const PINNING_MINIMUM_REQUIRED_MAX_AGE = 60 * 60 * 24 * 7 * 14; + +const FILE_HEADER = "/* This Source Code Form is subject to the terms of the Mozilla Public\n" + +" * License, v. 2.0. If a copy of the MPL was not distributed with this\n" + +" * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n" + +"\n" + +"/*****************************************************************************/\n" + +"/* This is an automatically generated file. If you're not */\n" + +"/* PublicKeyPinningService.cpp, you shouldn't be #including it. */\n" + +"/*****************************************************************************/\n" + +"#include <stdint.h>" + +"\n"; + +const DOMAINHEADER = "/* Domainlist */\n" + + "struct TransportSecurityPreload {\n" + + " const char* mHost;\n" + + " const bool mIncludeSubdomains;\n" + + " const bool mTestMode;\n" + + " const bool mIsMoz;\n" + + " const int32_t mId;\n" + + " const StaticPinset *pinset;\n" + + "};\n\n"; + +const PINSETDEF = "/* Pinsets are each an ordered list by the actual value of the fingerprint */\n" + + "struct StaticFingerprints {\n" + + " const size_t size;\n" + + " const char* const* data;\n" + + "};\n\n" + + "struct StaticPinset {\n" + + " const StaticFingerprints* sha1;\n" + + " const StaticFingerprints* sha256;\n" + + "};\n\n"; + +// Command-line arguments +var gStaticPins = parseJson(arguments[0]); +var gTestCertFile = arguments[1]; + +// Open the output file. +let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); +file.initWithPath(arguments[2]); +let gFileOutputStream = FileUtils.openSafeFileOutputStream(file); + +function writeString(string) { + gFileOutputStream.write(string, string.length); +} + +function readFileToString(filename) { + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + file.initWithPath(filename); + let stream = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + stream.init(file, -1, 0, 0); + let buf = NetUtil.readInputStreamToString(stream, stream.available()); + return buf; +} + +function stripComments(buf) { + var lines = buf.split("\n"); + let entryRegex = /^\s*///; + let data = ""; + for (let i = 0; i < lines.length; ++i) { + let match = entryRegex.exec(lines[i]); + if (!match) { + data = data + lines[i]; + } + } + return data; +} + +function isBuiltinToken(tokenName) { + return tokenName == "Builtin Object Token"; +} + +function isCertBuiltIn(cert) { + let tokenNames = cert.getAllTokenNames({}); + if (!tokenNames) { + return false; + } + if (tokenNames.some(isBuiltinToken)) { + return true; + } + return false; +} + +function download(filename) { + var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(Ci.nsIXMLHttpRequest); + req.open("GET", filename, false); // doing the request synchronously + try { + req.send(); + } + catch (e) { + throw "ERROR: problem downloading '" + filename + "': " + e; + } + + if (req.status != 200) { + throw("ERROR: problem downloading '" + filename + "': status " + + req.status); + } + return req.responseText; +} + +function downloadAsJson(filename) { + // we have to filter out '//' comments + var result = download(filename).replace(///[^\n]*\n/g, ""); + var data = null; + try { + data = JSON.parse(result); + } + catch (e) { + throw "ERROR: could not parse data from '" + filename + "': " + e; + } + return data; +} + +// Returns a Subject Public Key Digest from the given pem, if it exists. +function getSKDFromPem(pem) { + let cert = gCertDB.constructX509FromBase64(pem, pem.length); + return cert.sha256SubjectPublicKeyInfoDigest; +} + +// Downloads the static certs file and tries to map Google Chrome nicknames +// to Mozilla nicknames, as well as storing any hashes for pins for which we +// don't have root PEMs. Each entry consists of a line containing the name of +// the pin followed either by a hash in the format "sha1/" + base64(hash), or +// a PEM encoded certificate. For certificates that we have in our database, +// return a map of Google's nickname to ours. For ones that aren't return a +// map of Google's nickname to sha1 values. This code is modeled after agl's +// https://github.com/agl/transport-security-state-generate, which doesn't +// live in the Chromium repo because go is not an official language in +// Chromium. +// For all of the entries in this file: +// - If the entry has a hash format, find the Mozilla pin name (cert nickname) +// and stick the hash into certSKDToName +// - If the entry has a PEM format, parse the PEM, find the Mozilla pin name +// and stick the hash in certSKDToName +// We MUST be able to find a corresponding cert nickname for the Chrome names, +// otherwise we skip all pinsets referring to that Chrome name. +function downloadAndParseChromeCerts(filename, certSKDToName) { + // Prefixes that we care about. + const BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; + const END_CERT = "-----END CERTIFICATE-----"; + + // Parsing states. + const PRE_NAME = 0; + const POST_NAME = 1; + const IN_CERT = 2; + let state = PRE_NAME; + + let lines = download(filename).split("\n"); + let name = ""; + let pemCert = ""; + let hash = ""; + let chromeNameToHash = {}; + let chromeNameToMozName = {} + for (let i = 0; i < lines.length; ++i) { + let line = lines[i]; + // Skip comments and newlines. + if (line.length == 0 || line[0] == '#') { + continue; + } + switch(state) { + case PRE_NAME: + chromeName = line; + state = POST_NAME; + break; + case POST_NAME: + if (line.startsWith(SHA1_PREFIX) || + line.startsWith(SHA256_PREFIX)) { + if (line.startsWith(SHA1_PREFIX)) { + hash = line.substring(SHA1_PREFIX.length); + } else if (line.startsWith(SHA256_PREFIX)) { + hash = line.substring(SHA256_PREFIX); + } + // Store the entire prefixed hash, so we can disambiguate sha1 from + // sha256 later. + chromeNameToHash[chromeName] = line; + certNameToSKD[chromeName] = hash; + certSKDToName[hash] = chromeName; + state = PRE_NAME; + } else if (line.startsWith(BEGIN_CERT)) { + state = IN_CERT; + } else { + throw "ERROR: couldn't parse Chrome certificate file " + line; + } + break; + case IN_CERT: + if (line.startsWith(END_CERT)) { + state = PRE_NAME; + hash = getSKDFromPem(pemCert); + pemCert = ""; + if (hash in certSKDToName) { + mozName = certSKDToName[hash]; + } else { + // Not one of our built-in certs. Prefix the name with + // GOOGLE_PIN_. + mozName = GOOGLE_PIN_PREFIX + chromeName; + dump("Can't find hash in builtin certs for Chrome nickname " + + chromeName + ", inserting " + mozName + "\n"); + certSKDToName[hash] = mozName; + certNameToSKD[mozName] = hash; + } + chromeNameToMozName[chromeName] = mozName; + } else { + pemCert += line; + } + break; + default: + throw "ERROR: couldn't parse Chrome certificate file " + line; + } + } + return [ chromeNameToHash, chromeNameToMozName ]; +} + +// We can only import pinsets from chrome if for every name in the pinset: +// - We have a hash from Chrome's static certificate file +// - We have a builtin cert +// If the pinset meets these requirements, we store a map array of pinset +// objects: +// { +// pinset_name : { +// // Array of names with entries in certNameToSKD +// sha1_hashes: [], +// sha256_hashes: [] +// } +// } +// and an array of imported pinset entries: +// { name: string, include_subdomains: boolean, test_mode: boolean, +// pins: pinset_name } +function downloadAndParseChromePins(filename, + chromeNameToHash, + chromeNameToMozName, + certNameToSKD, + certSKDToName) { + let chromePreloads = downloadAsJson(filename); + let chromePins = chromePreloads.pinsets; + let chromeImportedPinsets = {}; + let chromeImportedEntries = []; + + chromePins.forEach(function(pin) { + let valid = true; + let pinset = { name: pin.name, sha1_hashes: [], sha256_hashes: [] }; + // Translate the Chrome pinset format to ours + pin.static_spki_hashes.forEach(function(name) { + if (name in chromeNameToHash) { + let hash = chromeNameToHash[name]; + if (hash.startsWith(SHA1_PREFIX)) { + hash = hash.substring(SHA1_PREFIX.length); + pinset.sha1_hashes.push(certSKDToName[hash]); + } else if (hash.startsWith(SHA256_PREFIX)) { + hash = hash.substring(SHA256_PREFIX.length); + pinset.sha256_hashes.push(certSKDToName[hash]); + } else { + throw("Unsupported hash type: " + chromeNameToHash[name]); + } + // We should have already added hashes for all of these when we + // imported the certificate file. + if (!certNameToSKD[name]) { + throw("No hash for name: " + name); + } + } else if (name in chromeNameToMozName) { + pinset.sha256_hashes.push(chromeNameToMozName[name]); + } else { + dump("Skipping Chrome pinset " + pinset.name + ", couldn't find " + + "builtin " + name + " from cert file\n"); + valid = false; + } + }); + if (valid) { + chromeImportedPinsets[pinset.name] = pinset; + } + }); + + // Grab the domain entry lists. Chrome's entry format is similar to + // ours, except theirs includes a HSTS mode. + const cData = gStaticPins.chromium_data; + let entries = chromePreloads.entries; + entries.forEach(function(entry) { + let pinsetName = cData.substitute_pinsets[entry.pins]; + if (!pinsetName) { + pinsetName = entry.pins; + } + let isProductionDomain = + (cData.production_domains.indexOf(entry.name) != -1); + let isProductionPinset = + (cData.production_pinsets.indexOf(pinsetName) != -1); + let excludeDomain = + (cData.exclude_domains.indexOf(entry.name) != -1); + let isTestMode = !isProductionPinset && !isProductionDomain; + if (entry.pins && !excludeDomain && chromeImportedPinsets[entry.pins]) { + chromeImportedEntries.push({ + name: entry.name, + include_subdomains: entry.include_subdomains, + test_mode: isTestMode, + is_moz: false, + pins: pinsetName }); + } + }); + return [ chromeImportedPinsets, chromeImportedEntries ]; +} + +// Returns a pair of maps [certNameToSKD, certSKDToName] between cert +// nicknames and digests of the SPKInfo for the mozilla trust store +function loadNSSCertinfo(derTestFile, extraCertificates) { + let allCerts = gCertDB.getCerts(); + let enumerator = allCerts.getEnumerator(); + let certNameToSKD = {}; + let certSKDToName = {}; + while (enumerator.hasMoreElements()) { + let cert = enumerator.getNext().QueryInterface(Ci.nsIX509Cert); + if (!isCertBuiltIn(cert)) { + continue; + } + let name = cert.nickname.substr(BUILT_IN_NICK_PREFIX.length); + let SKD = cert.sha256SubjectPublicKeyInfoDigest; + certNameToSKD[name] = SKD; + certSKDToName[SKD] = name; + } + + for (let cert of extraCertificates) { + let name = cert.commonName; + let SKD = cert.sha256SubjectPublicKeyInfoDigest; + certNameToSKD[name] = SKD; + certSKDToName[SKD] = name; + } + + { + // A certificate for *.example.com. + let der = readFileToString(derTestFile); + let testCert = gCertDB.constructX509(der, der.length); + // We can't include this cert in the previous loop, because it skips + // non-builtin certs and the nickname is not built-in to the cert. + let name = "End Entity Test Cert"; + let SKD = testCert.sha256SubjectPublicKeyInfoDigest; + certNameToSKD[name] = SKD; + certSKDToName[SKD] = name; + } + return [certNameToSKD, certSKDToName]; +} + +function parseJson(filename) { + let json = stripComments(readFileToString(filename)); + return JSON.parse(json); +} + +function nameToAlias(certName) { + // change the name to a string valid as a c identifier + // remove non-ascii characters + certName = certName.replace( /[^[:ascii:]]/g, "_"); + // replace non word characters + certName = certName.replace(/[^A-Za-z0-9]/g ,"_"); + + return "k" + certName + "Fingerprint"; +} + +function compareByName (a, b) { + return a.name.localeCompare(b.name); +} + +function genExpirationTime() { + let now = new Date(); + let nowMillis = now.getTime(); + let expirationMillis = nowMillis + (PINNING_MINIMUM_REQUIRED_MAX_AGE * 1000); + let expirationMicros = expirationMillis * 1000; + return "static const PRTime kPreloadPKPinsExpirationTime = INT64_C(" + + expirationMicros +");\n"; +} + +function writeFullPinset(certNameToSKD, certSKDToName, pinset) { + // We aren't guaranteed to have sha1 hashes in our own imported pins. + let prefix = "kPinset_" + pinset.name; + let sha1Name = "nullptr"; + let sha256Name = "nullptr"; + if (pinset.sha1_hashes && pinset.sha1_hashes.length > 0) { + writeFingerprints(certNameToSKD, certSKDToName, pinset.name, + pinset.sha1_hashes, "sha1"); + sha1Name = "&" + prefix + "_sha1"; + } + if (pinset.sha256_hashes && pinset.sha256_hashes.length > 0) { + writeFingerprints(certNameToSKD, certSKDToName, pinset.name, + pinset.sha256_hashes, "sha256"); + sha256Name = "&" + prefix + "_sha256"; + } + writeString("static const StaticPinset " + prefix + " = {\n" + + " " + sha1Name + ",\n " + sha256Name + "\n};\n\n"); +} + +function writeFingerprints(certNameToSKD, certSKDToName, name, hashes, type) { + let varPrefix = "kPinset_" + name + "_" + type; + writeString("static const char* " + varPrefix + "_Data[] = {\n"); + let SKDList = []; + for (let certName of hashes) { + if (!(certName in certNameToSKD)) { + throw "Can't find " + certName + " in certNameToSKD"; + } + SKDList.push(certNameToSKD[certName]); + } + for (let skd of SKDList.sort()) { + writeString(" " + nameToAlias(certSKDToName[skd]) + ",\n"); + } + if (hashes.length == 0) { + // ANSI C requires that an initialiser list be non-empty. + writeString(" 0\n"); + } + writeString("};\n"); + writeString("static const StaticFingerprints " + varPrefix + " = {\n " + + "sizeof(" + varPrefix + "_Data) / sizeof(const char*),\n " + varPrefix + + "_Data\n};\n\n"); +} + +function writeEntry(entry) { + let printVal = " { "" + entry.name + "",\ "; + if (entry.include_subdomains) { + printVal += "true, "; + } else { + printVal += "false, "; + } + // Default to test mode if not specified. + let testMode = true; + if (entry.hasOwnProperty("test_mode")) { + testMode = entry.test_mode; + } + if (testMode) { + printVal += "true, "; + } else { + printVal += "false, "; + } + if (entry.is_moz || (entry.pins == "mozilla")) { + printVal += "true, "; + } else { + printVal += "false, "; + } + if (entry.id >= 256) { + throw("Not enough buckets in histogram"); + } + if (entry.id >= 0) { + printVal += entry.id + ", "; + } else { + printVal += "-1, "; + } + printVal += "&kPinset_" + entry.pins; + printVal += " },\n"; + writeString(printVal); +} + +function writeDomainList(chromeImportedEntries) { + writeString("/* Sort hostnames for binary search. */\n"); + writeString("static const TransportSecurityPreload " + + "kPublicKeyPinningPreloadList[] = {\n"); + let count = 0; + let sortedEntries = gStaticPins.entries; + sortedEntries.push.apply(sortedEntries, chromeImportedEntries); + for (let entry of sortedEntries.sort(compareByName)) { + count++; + writeEntry(entry); + } + writeString("};\n"); + + writeString("\n// Pinning Preload List Length = " + count + ";\n"); + writeString("\nstatic const int32_t kUnknownId = -1;\n"); +} + +function writeFile(certNameToSKD, certSKDToName, + chromeImportedPinsets, chromeImportedEntries) { + // Compute used pins from both Chrome's and our pinsets, so we can output + // them later. + usedFingerprints = {}; + gStaticPins.pinsets.forEach(function(pinset) { + // We aren't guaranteed to have sha1_hashes in our own JSON. + if (pinset.sha1_hashes) { + pinset.sha1_hashes.forEach(function(name) { + usedFingerprints[name] = true; + }); + } + if (pinset.sha256_hashes) { + pinset.sha256_hashes.forEach(function(name) { + usedFingerprints[name] = true; + }); + } + }); + for (let key in chromeImportedPinsets) { + let pinset = chromeImportedPinsets[key]; + pinset.sha1_hashes.forEach(function(name) { + usedFingerprints[name] = true; + }); + pinset.sha256_hashes.forEach(function(name) { + usedFingerprints[name] = true; + }); + } + + writeString(FILE_HEADER); + + // Write actual fingerprints. + Object.keys(usedFingerprints).sort().forEach(function(certName) { + if (certName) { + writeString("/* " + certName + " */\n"); + writeString("static const char " + nameToAlias(certName) + "[] =\n"); + writeString(" "" + certNameToSKD[certName] + "";\n"); + writeString("\n"); + } + }); + + // Write the pinsets + writeString(PINSETDEF); + writeString("/* PreloadedHPKPins.json pinsets */\n"); + gStaticPins.pinsets.sort(compareByName).forEach(function(pinset) { + writeFullPinset(certNameToSKD, certSKDToName, pinset); + }); + writeString("/* Chrome static pinsets */\n"); + for (let key in chromeImportedPinsets) { + writeFullPinset(certNameToSKD, certSKDToName, chromeImportedPinsets[key]); + } + + // Write the domainlist entries. + writeString(DOMAINHEADER); + writeDomainList(chromeImportedEntries); + writeString("\n"); + writeString(genExpirationTime()); +} + +function loadExtraCertificates(certStringList) { + let constructedCerts = []; + for (let certString of certStringList) { + constructedCerts.push(gCertDB.constructX509FromBase64(certString)); + } + return constructedCerts; +} + +let extraCertificates = loadExtraCertificates(gStaticPins.extra_certificates); +let [ certNameToSKD, certSKDToName ] = loadNSSCertinfo(gTestCertFile, + extraCertificates); +let [ chromeNameToHash, chromeNameToMozName ] = downloadAndParseChromeCerts( + gStaticPins.chromium_data.cert_file_url, certSKDToName); +let [ chromeImportedPinsets, chromeImportedEntries ] = + downloadAndParseChromePins(gStaticPins.chromium_data.json_file_url, + chromeNameToHash, chromeNameToMozName, certNameToSKD, certSKDToName); + +writeFile(certNameToSKD, certSKDToName, chromeImportedPinsets, + chromeImportedEntries); + +FileUtils.closeSafeFileOutputStream(gFileOutputStream); diff --git a/security/pkix/include/pkix/Result.h b/security/pkix/include/pkix/Result.h new file mode 100644 index 0000000..e82e6428 --- /dev/null +++ b/security/pkix/include/pkix/Result.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef mozilla_pkix__Result_h +#define mozilla_pkix__Result_h + +#include <cassert> + +#include "pkix/enumclass.h" + +namespace mozilla { namespace pkix { + +static const unsigned int FATAL_ERROR_FLAG = 0x800; + +// The first argument to MOZILLA_PKIX_MAP() is used for building the mapping +// from error code to error name in MapResultToName. +// +// The second argument is for defining the value for the enum literal in the +// Result enum class. +// +// The third argument to MOZILLA_PKIX_MAP() is used, along with the first +// argument, for maintaining the mapping of mozilla::pkix error codes to +// NSS/NSPR error codes in pkixnss.cpp. +#define MOZILLA_PKIX_MAP_LIST \ + MOZILLA_PKIX_MAP(Success, 0, 0) \ + MOZILLA_PKIX_MAP(ERROR_BAD_DER, 1, \ + SEC_ERROR_BAD_DER) \ + MOZILLA_PKIX_MAP(ERROR_CA_CERT_INVALID, 2, \ + SEC_ERROR_CA_CERT_INVALID) \ + MOZILLA_PKIX_MAP(ERROR_BAD_SIGNATURE, 3, \ + SEC_ERROR_BAD_SIGNATURE) \ + MOZILLA_PKIX_MAP(ERROR_CERT_BAD_ACCESS_LOCATION, 4, \ + SEC_ERROR_CERT_BAD_ACCESS_LOCATION) \ + MOZILLA_PKIX_MAP(ERROR_CERT_NOT_IN_NAME_SPACE, 5, \ + SEC_ERROR_CERT_NOT_IN_NAME_SPACE) \ + MOZILLA_PKIX_MAP(ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, 6, \ + SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED) \ + MOZILLA_PKIX_MAP(ERROR_CONNECT_REFUSED, 7, \ + PR_CONNECT_REFUSED_ERROR) \ + MOZILLA_PKIX_MAP(ERROR_EXPIRED_CERTIFICATE, 8, \ + SEC_ERROR_EXPIRED_CERTIFICATE) \ + MOZILLA_PKIX_MAP(ERROR_EXTENSION_VALUE_INVALID, 9, \ + SEC_ERROR_EXTENSION_VALUE_INVALID) \ + MOZILLA_PKIX_MAP(ERROR_INADEQUATE_CERT_TYPE, 10, \ + SEC_ERROR_INADEQUATE_CERT_TYPE) \ + MOZILLA_PKIX_MAP(ERROR_INADEQUATE_KEY_USAGE, 11, \ + SEC_ERROR_INADEQUATE_KEY_USAGE) \ + MOZILLA_PKIX_MAP(ERROR_INVALID_ALGORITHM, 12, \ + SEC_ERROR_INVALID_ALGORITHM) \ + MOZILLA_PKIX_MAP(ERROR_INVALID_TIME, 13, \ + SEC_ERROR_INVALID_TIME) \ + MOZILLA_PKIX_MAP(ERROR_KEY_PINNING_FAILURE, 14, \ + MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE) \ + MOZILLA_PKIX_MAP(ERROR_PATH_LEN_CONSTRAINT_INVALID, 15, \ + SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID) \ + MOZILLA_PKIX_MAP(ERROR_POLICY_VALIDATION_FAILED, 16, \ + SEC_ERROR_POLICY_VALIDATION_FAILED) \ + MOZILLA_PKIX_MAP(ERROR_REVOKED_CERTIFICATE, 17, \ + SEC_ERROR_REVOKED_CERTIFICATE) \ + MOZILLA_PKIX_MAP(ERROR_UNKNOWN_CRITICAL_EXTENSION, 18, \ + SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION) \ + MOZILLA_PKIX_MAP(ERROR_UNKNOWN_ERROR, 19, \ + PR_UNKNOWN_ERROR) \ + MOZILLA_PKIX_MAP(ERROR_UNKNOWN_ISSUER, 20, \ + SEC_ERROR_UNKNOWN_ISSUER) \ + MOZILLA_PKIX_MAP(ERROR_UNTRUSTED_CERT, 21, \ + SEC_ERROR_UNTRUSTED_CERT) \ + MOZILLA_PKIX_MAP(ERROR_UNTRUSTED_ISSUER, 22, \ + SEC_ERROR_UNTRUSTED_ISSUER) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_BAD_SIGNATURE, 23, \ + SEC_ERROR_OCSP_BAD_SIGNATURE) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_INVALID_SIGNING_CERT, 24, \ + SEC_ERROR_OCSP_INVALID_SIGNING_CERT) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_MALFORMED_REQUEST, 25, \ + SEC_ERROR_OCSP_MALFORMED_REQUEST) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_MALFORMED_RESPONSE, 26, \ + SEC_ERROR_OCSP_MALFORMED_RESPONSE) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_OLD_RESPONSE, 27, \ + SEC_ERROR_OCSP_OLD_RESPONSE) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_REQUEST_NEEDS_SIG, 28, \ + SEC_ERROR_OCSP_REQUEST_NEEDS_SIG) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_RESPONDER_CERT_INVALID, 29, \ + SEC_ERROR_OCSP_RESPONDER_CERT_INVALID) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_SERVER_ERROR, 30, \ + SEC_ERROR_OCSP_SERVER_ERROR) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_TRY_SERVER_LATER, 31, \ + SEC_ERROR_OCSP_TRY_SERVER_LATER) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_UNAUTHORIZED_REQUEST, 32, \ + SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_UNKNOWN_RESPONSE_STATUS, 33, \ + SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_UNKNOWN_CERT, 34, \ + SEC_ERROR_OCSP_UNKNOWN_CERT) \ + MOZILLA_PKIX_MAP(ERROR_OCSP_FUTURE_RESPONSE, 35, \ + SEC_ERROR_OCSP_FUTURE_RESPONSE) \ + MOZILLA_PKIX_MAP(ERROR_INVALID_KEY, 36, \ + SEC_ERROR_INVALID_KEY) \ + MOZILLA_PKIX_MAP(ERROR_UNSUPPORTED_KEYALG, 37, \ + SEC_ERROR_UNSUPPORTED_KEYALG) \ + MOZILLA_PKIX_MAP(ERROR_EXPIRED_ISSUER_CERTIFICATE, 38, \ + SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE) \ + MOZILLA_PKIX_MAP(ERROR_CA_CERT_USED_AS_END_ENTITY, 39, \ + MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY) \ + MOZILLA_PKIX_MAP(ERROR_INADEQUATE_KEY_SIZE, 40, \ + MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE) \ + MOZILLA_PKIX_MAP(FATAL_ERROR_INVALID_ARGS, FATAL_ERROR_FLAG | 1, \ + SEC_ERROR_INVALID_ARGS) \ + MOZILLA_PKIX_MAP(FATAL_ERROR_INVALID_STATE, FATAL_ERROR_FLAG | 2, \ + PR_INVALID_STATE_ERROR) \ + MOZILLA_PKIX_MAP(FATAL_ERROR_LIBRARY_FAILURE, FATAL_ERROR_FLAG | 3, \ + SEC_ERROR_LIBRARY_FAILURE) \ + MOZILLA_PKIX_MAP(FATAL_ERROR_NO_MEMORY, FATAL_ERROR_FLAG | 4, \ + SEC_ERROR_NO_MEMORY) \ + /* nothing here */ + +MOZILLA_PKIX_ENUM_CLASS Result +{ +#define MOZILLA_PKIX_MAP(name, value, nss_name) name = value, + MOZILLA_PKIX_MAP_LIST +#undef MOZILLA_PKIX_MAP +}; + +// Returns the stringified name of the given result, e.g. "Result::Success", +// or nullptr if result is unknown (invalid). +const char* MapResultToName(Result result); + +// We write many comparisons as (x != Success), and this shortened name makes +// those comparisons clearer, especially because the shortened name often +// results in less line wrapping. +// +// Visual Studio before VS2013 does not support "enum class," so +// Result::Success will already be visible in this scope, and compilation will +// fail if we try to define a variable with that name here. +#if !defined(_MSC_VER) || (_MSC_VER >= 1700) +static const Result Success = Result::Success; +#endif + +inline bool +IsFatalError(Result rv) +{ + return (static_cast<unsigned int>(rv) & FATAL_ERROR_FLAG) != 0; +} + +inline Result +NotReached(const char* /*explanation*/, Result result) +{ + assert(false); + return result; +} + +} } // namespace mozilla::pkix + +#endif // mozilla_pkix__Result_h diff --git a/security/pkix/include/pkix/Time.h b/security/pkix/include/pkix/Time.h new file mode 100644 index 0000000..b8d6ee9 --- /dev/null +++ b/security/pkix/include/pkix/Time.h @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef mozilla_pkix__Time_h +#define mozilla_pkix__Time_h + +#include <ctime> +#include <limits> +#include <stdint.h> + +#include "pkix/Result.h" + +namespace mozilla { namespace pkix { + +// Time with a range from the first second of year 0 (AD) through at least the +// last second of year 9999, which is the range of legal times in X.509 and +// OCSP. This type has second-level precision. The time zone is always UTC. +// +// Pass by value, not by reference. +class Time +{ +public: + // Construct an uninitilized instance. + // + // This will fail to compile because there is no default constructor: + // Time x; + // + // This will succeed, leaving the time uninitialized: + // Time x(Time::uninitialized); + enum Uninitialized { uninitialized }; + explicit Time(Uninitialized) { } + + bool operator==(const Time& other) const + { + return elapsedSecondsAD == other.elapsedSecondsAD; + } + bool operator>(const Time& other) const + { + return elapsedSecondsAD > other.elapsedSecondsAD; + } + bool operator>=(const Time& other) const + { + return elapsedSecondsAD >= other.elapsedSecondsAD; + } + bool operator<(const Time& other) const + { + return elapsedSecondsAD < other.elapsedSecondsAD; + } + bool operator<=(const Time& other) const + { + return elapsedSecondsAD <= other.elapsedSecondsAD; + } + + Result AddSeconds(uint64_t seconds) + { + if (std::numeric_limits<uint64_t>::max() - elapsedSecondsAD + < seconds) { + return Result::FATAL_ERROR_INVALID_ARGS; // integer overflow + } + elapsedSecondsAD += seconds; + return Success; + } + + Result SubtractSeconds(uint64_t seconds) + { + if (seconds > elapsedSecondsAD) { + return Result::FATAL_ERROR_INVALID_ARGS; // integer overflow + } + elapsedSecondsAD -= seconds; + return Success; + } + + static const uint64_t ONE_DAY_IN_SECONDS + = UINT64_C(24) * UINT64_C(60) * UINT64_C(60); + +private: + // This constructor is hidden to prevent accidents like this: + // + // Time foo(time_t t) + // { + // // WRONG! 1970-01-01-00:00:00 == time_t(0), but not Time(0)! + // return Time(t); + // } + explicit Time(uint64_t elapsedSecondsAD) + : elapsedSecondsAD(elapsedSecondsAD) + { + } + friend Time TimeFromElapsedSecondsAD(uint64_t); + + uint64_t elapsedSecondsAD; +}; + +inline Time TimeFromElapsedSecondsAD(uint64_t elapsedSecondsAD) +{ + return Time(elapsedSecondsAD); +} + +Time Now(); + +// Note the epoch is the unix epoch (ie 00:00:00 UTC, 1 January 1970) +Time TimeFromEpochInSeconds(uint64_t secondsSinceEpoch); + +} } // namespace mozilla::pkix + +#endif // mozilla_pkix__Time_h diff --git a/security/pkix/include/pkix/pkixtypes.h b/security/pkix/include/pkix/pkixtypes.h index 0a64b9d..d38603b 100644 --- a/security/pkix/include/pkix/pkixtypes.h +++ b/security/pkix/include/pkix/pkixtypes.h @@ -120,6 +120,11 @@ public: PRTime time, /*optional*/ const SECItem* stapledOCSPresponse) = 0;
+ // Called as soon as we think we have a valid chain but before revocation + // checks are done. Called to compute additional chain level checks, by the + // TrustDomain. + virtual SECStatus IsChainValid(const CERTCertList* certChain) = 0; + protected: TrustDomain() { }
diff --git a/security/pkix/lib/pkixbuild.cpp b/security/pkix/lib/pkixbuild.cpp index 22e3f61..c078eda 100644 --- a/security/pkix/lib/pkixbuild.cpp +++ b/security/pkix/lib/pkixbuild.cpp @@ -225,6 +225,30 @@ BuildForward(TrustDomain& trustDomain, }
if (trustLevel == TrustDomain::TrustAnchor) { + ScopedCERTCertList certChain(CERT_NewCertList()); + if (!certChain) { + PR_SetError(SEC_ERROR_NO_MEMORY, 0); + return MapSECStatus(SECFailure); + } + + rv = subject.PrependNSSCertToList(certChain.get()); + if (rv != Success) { + return rv; + } + BackCert* child = subject.childCert; + while (child) { + rv = child->PrependNSSCertToList(certChain.get()); + if (rv != Success) { + return rv; + } + child = child->childCert; + } + + SECStatus srv = trustDomain.IsChainValid(certChain.get()); + if (srv != SECSuccess) { + return MapSECStatus(srv); + } + // End of the recursion. Create the result list and add the trust anchor to // it. results = CERT_NewCertList(); diff --git a/security/pkix/lib/pkixtime.cpp b/security/pkix/lib/pkixtime.cpp new file mode 100644 index 0000000..499784e --- /dev/null +++ b/security/pkix/lib/pkixtime.cpp @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkix/Time.h" +#include "pkixutil.h" +#ifdef WIN32 +#include "windows.h" +#else +#include "sys/time.h" +#endif + +namespace mozilla { namespace pkix { + +Time +Now() +{ + uint64_t seconds; + +#ifdef WIN32 + // "Contains a 64-bit value representing the number of 100-nanosecond + // intervals since January 1, 1601 (UTC)." + // - http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).as... + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + uint64_t ft64 = (static_cast<uint64_t>(ft.dwHighDateTime) << 32) | + ft.dwLowDateTime; + seconds = (DaysBeforeYear(1601) * Time::ONE_DAY_IN_SECONDS) + + ft64 / (1000u * 1000u * 1000u / 100u); +#else + // "The gettimeofday() function shall obtain the current time, expressed as + // seconds and microseconds since the Epoch." + // - http://pubs.opengroup.org/onlinepubs/009695399/functions/gettimeofday.html + timeval tv; + (void) gettimeofday(&tv, nullptr); + seconds = (DaysBeforeYear(1970) * Time::ONE_DAY_IN_SECONDS) + tv.tv_sec; +#endif + + return TimeFromElapsedSecondsAD(seconds); +} + +Time +TimeFromEpochInSeconds(uint64_t secondsSinceEpoch) +{ + uint64_t seconds = (DaysBeforeYear(1970) * Time::ONE_DAY_IN_SECONDS) + + secondsSinceEpoch; + return TimeFromElapsedSecondsAD(seconds); +} + +} } // namespace mozilla::pkix diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 01a27a1..bf96ccd 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -5929,5 +5929,37 @@ "high": "5000", "n_buckets": 10, "extended_statistics_ok": true + }, + "CERT_PINNING_RESULTS": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Certificate pinning results (0 = failure, 1 = success)" + }, + "CERT_PINNING_TEST_RESULTS": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Certificate pinning test results (0 = failure, 1 = success)" + }, + "CERT_PINNING_MOZ_RESULTS": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Certificate pinning results for Mozilla sites (0 = failure, 1 = success)" + }, + "CERT_PINNING_MOZ_TEST_RESULTS": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Certificate pinning test results for Mozilla sites (0 = failure, 1 = success)" + }, + "CERT_PINNING_MOZ_RESULTS_BY_HOST": { + "expires_in_version": "never", + "kind": "enumerated", + "n_values": 512, + "description": "Certificate pinning results by host for Mozilla operational sites" + }, + "CERT_PINNING_MOZ_TEST_RESULTS_BY_HOST": { + "expires_in_version": "never", + "kind": "enumerated", + "n_values": 512, + "description": "Certificate pinning test results by host for Mozilla operational sites" } }
tor-commits@lists.torproject.org