--- a/config/external/nss/nss.symbols
+++ b/config/external/nss/nss.symbols
@@ -674,16 +674,17 @@ SSL_HandshakeNegotiatedExtension
SSL_ImplementedCiphers @DATA@
SSL_ImportFD
SSL_NamedGroupConfig
SSL_NumImplementedCiphers @DATA@
SSL_OptionSet
SSL_OptionSetDefault
SSL_PeerCertificate
SSL_PeerCertificateChain
+SSL_PeerSignedCertTimestamps
SSL_PeerStapledOCSPResponses
SSL_ResetHandshake
SSL_SendAdditionalKeyShares
SSL_SetCanFalseStartCallback
SSL_SetDowngradeCheckVersion
SSL_SetNextProtoNego
SSL_SetPKCS11PinArg
SSL_SetSockPeerID
--- a/netwerk/base/security-prefs.js
+++ b/netwerk/base/security-prefs.js
@@ -81,16 +81,21 @@ pref("security.pki.name_matching_mode",
// 2: similarly, but for 23 August 2015
// 3: it is never considered equivalent
#ifdef RELEASE_BUILD
pref("security.pki.netscape_step_up_policy", 1);
#else
pref("security.pki.netscape_step_up_policy", 2);
#endif
+// Configures Certificate Transparency support mode:
+// 0: Fully disabled.
+// 1: Only collect telemetry. CT qualification checks are not performed.
+pref("security.pki.certificate_transparency.mode", 1);
+
pref("security.webauth.u2f", false);
pref("security.webauth.u2f_enable_softtoken", false);
pref("security.webauth.u2f_enable_usbtoken", false);
pref("security.ssl.errorReporting.enabled", true);
pref("security.ssl.errorReporting.url", "https://incoming.telemetry.mozilla.org/submit/sslreports/");
pref("security.ssl.errorReporting.automatic", false);
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTKnownLogs.h
@@ -0,0 +1,155 @@
+/* -*- 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 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 file is generated by print_log_list.py from
+ * https://github.com/google/certificate-transparency/ */
+
+#ifndef CTKnownLogs_h
+#define CTKnownLogs_h
+
+#include <stddef.h>
+
+struct CTLogInfo {
+ const char* const logName;
+ const char* const logUrl;
+ const char* const logKey;
+ const size_t logKeyLength;
+};
+
+const CTLogInfo kCTLogList[] = {
+ { "Google 'Pilot' log",
+ "https://ct.googleapis.com/pilot/",
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+ "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x7d\xa8\x4b\x12\x29\x80\xa3\x3d\xad"
+ "\xd3\x5a\x77\xb8\xcc\xe2\x88\xb3\xa5\xfd\xf1\xd3\x0c\xcd\x18\x0c\xe8\x41"
+ "\x46\xe8\x81\x01\x1b\x15\xe1\x4b\xf1\x1b\x62\xdd\x36\x0a\x08\x18\xba\xed"
+ "\x0b\x35\x84\xd0\x9e\x40\x3c\x2d\x9e\x9b\x82\x65\xbd\x1f\x04\x10\x41\x4c"
+ "\xa0",
+ 91 },
+ { "Google 'Aviator' log",
+ "https://ct.googleapis.com/aviator/",
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+ "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xd7\xf4\xcc\x69\xb2\xe4\x0e\x90\xa3"
+ "\x8a\xea\x5a\x70\x09\x4f\xef\x13\x62\xd0\x8d\x49\x60\xff\x1b\x40\x50\x07"
+ "\x0c\x6d\x71\x86\xda\x25\x49\x8d\x65\xe1\x08\x0d\x47\x34\x6b\xbd\x27\xbc"
+ "\x96\x21\x3e\x34\xf5\x87\x76\x31\xb1\x7f\x1d\xc9\x85\x3b\x0d\xf7\x1f\x3f"
+ "\xe9",
+ 91 },
+ { "DigiCert Log Server",
+ "https://ct1.digicert-ct.com/log/",
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+ "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x02\x46\xc5\xbe\x1b\xbb\x82\x40\x16"
+ "\xe8\xc1\xd2\xac\x19\x69\x13\x59\xf8\xf8\x70\x85\x46\x40\xb9\x38\xb0\x23"
+ "\x82\xa8\x64\x4c\x7f\xbf\xbb\x34\x9f\x4a\x5f\x28\x8a\xcf\x19\xc4\x00\xf6"
+ "\x36\x06\x93\x65\xed\x4c\xf5\xa9\x21\x62\x5a\xd8\x91\xeb\x38\x24\x40\xac"
+ "\xe8",
+ 91 },
+ { "Google 'Rocketeer' log",
+ "https://ct.googleapis.com/rocketeer/",
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+ "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x20\x5b\x18\xc8\x3c\xc1\x8b\xb3\x31"
+ "\x08\x00\xbf\xa0\x90\x57\x2b\xb7\x47\x8c\x6f\xb5\x68\xb0\x8e\x90\x78\xe9"
+ "\xa0\x73\xea\x4f\x28\x21\x2e\x9c\xc0\xf4\x16\x1b\xaa\xf9\xd5\xd7\xa9\x80"
+ "\xc3\x4e\x2f\x52\x3c\x98\x01\x25\x46\x24\x25\x28\x23\x77\x2d\x05\xc2\x40"
+ "\x7a",
+ 91 },
+ { "Certly.IO log",
+ "https://log.certly.io/",
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+ "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x0b\x23\xcb\x85\x62\x98\x61\x48\x04"
+ "\x73\xeb\x54\x5d\xf3\xd0\x07\x8c\x2d\x19\x2d\x8c\x36\xf5\xeb\x8f\x01\x42"
+ "\x0a\x7c\x98\x26\x27\xc1\xb5\xdd\x92\x93\xb0\xae\xf8\x9b\x3d\x0c\xd8\x4c"
+ "\x4e\x1d\xf9\x15\xfb\x47\x68\x7b\xba\x66\xb7\x25\x9c\xd0\x4a\xc2\x66\xdb"
+ "\x48",
+ 91 },
+ { "Izenpe log",
+ "https://ct.izenpe.com/",
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+ "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x27\x64\x39\x0c\x2d\xdc\x50\x18\xf8"
+ "\x21\x00\xa2\x0e\xed\x2c\xea\x3e\x75\xba\x9f\x93\x64\x09\x00\x11\xc4\x11"
+ "\x17\xab\x5c\xcf\x0f\x74\xac\xb5\x97\x90\x93\x00\x5b\xb8\xeb\xf7\x27\x3d"
+ "\xd9\xb2\x0a\x81\x5f\x2f\x0d\x75\x38\x94\x37\x99\x1e\xf6\x07\x76\xe0\xee"
+ "\xbe",
+ 91 },
+ { "Symantec log",
+ "https://ct.ws.symantec.com/",
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+ "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x96\xea\xac\x1c\x46\x0c\x1b\x55\xdc"
+ "\x0d\xfc\xb5\x94\x27\x46\x57\x42\x70\x3a\x69\x18\xe2\xbf\x3b\xc4\xdb\xab"
+ "\xa0\xf4\xb6\x6c\xc0\x53\x3f\x4d\x42\x10\x33\xf0\x58\x97\x8f\x6b\xbe\x72"
+ "\xf4\x2a\xec\x1c\x42\xaa\x03\x2f\x1a\x7e\x28\x35\x76\x99\x08\x3d\x21\x14"
+ "\x86",
+ 91 },
+ { "Venafi log",
+ "https://ctlog.api.venafi.com/",
+ "\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05"
+ "\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xa2\x5a\x48"
+ "\x1f\x17\x52\x95\x35\xcb\xa3\x5b\x3a\x1f\x53\x82\x76\x94\xa3\xff\x80\xf2"
+ "\x1c\x37\x3c\xc0\xb1\xbd\xc1\x59\x8b\xab\x2d\x65\x93\xd7\xf3\xe0\x04\xd5"
+ "\x9a\x6f\xbf\xd6\x23\x76\x36\x4f\x23\x99\xcb\x54\x28\xad\x8c\x15\x4b\x65"
+ "\x59\x76\x41\x4a\x9c\xa6\xf7\xb3\x3b\x7e\xb1\xa5\x49\xa4\x17\x51\x6c\x80"
+ "\xdc\x2a\x90\x50\x4b\x88\x24\xe9\xa5\x12\x32\x93\x04\x48\x90\x02\xfa\x5f"
+ "\x0e\x30\x87\x8e\x55\x76\x05\xee\x2a\x4c\xce\xa3\x6a\x69\x09\x6e\x25\xad"
+ "\x82\x76\x0f\x84\x92\xfa\x38\xd6\x86\x4e\x24\x8f\x9b\xb0\x72\xcb\x9e\xe2"
+ "\x6b\x3f\xe1\x6d\xc9\x25\x75\x23\x88\xa1\x18\x58\x06\x23\x33\x78\xda\x00"
+ "\xd0\x38\x91\x67\xd2\xa6\x7d\x27\x97\x67\x5a\xc1\xf3\x2f\x17\xe6\xea\xd2"
+ "\x5b\xe8\x81\xcd\xfd\x92\x68\xe7\xf3\x06\xf0\xe9\x72\x84\xee\x01\xa5\xb1"
+ "\xd8\x33\xda\xce\x83\xa5\xdb\xc7\xcf\xd6\x16\x7e\x90\x75\x18\xbf\x16\xdc"
+ "\x32\x3b\x6d\x8d\xab\x82\x17\x1f\x89\x20\x8d\x1d\x9a\xe6\x4d\x23\x08\xdf"
+ "\x78\x6f\xc6\x05\xbf\x5f\xae\x94\x97\xdb\x5f\x64\xd4\xee\x16\x8b\xa3\x84"
+ "\x6c\x71\x2b\xf1\xab\x7f\x5d\x0d\x32\xee\x04\xe2\x90\xec\x41\x9f\xfb\x39"
+ "\xc1\x02\x03\x01\x00\x01",
+ 294 },
+ { "Symantec 'Vega' log",
+ "https://vega.ws.symantec.com/",
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+ "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xea\x95\x9e\x02\xff\xee\xf1\x33\x6d"
+ "\x4b\x87\xbc\xcd\xfd\x19\x17\x62\xff\x94\xd3\xd0\x59\x07\x3f\x02\x2d\x1c"
+ "\x90\xfe\xc8\x47\x30\x3b\xf1\xdd\x0d\xb8\x11\x0c\x5d\x1d\x86\xdd\xab\xd3"
+ "\x2b\x46\x66\xfb\x6e\x65\xb7\x3b\xfd\x59\x68\xac\xdf\xa6\xf8\xce\xd2\x18"
+ "\x4d",
+ 91 },
+ { "CNNIC CT log",
+ "https://ctserver.cnnic.cn/",
+ "\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05"
+ "\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xbf\xb5\x08"
+ "\x61\x9a\x29\x32\x04\xd3\x25\x63\xe9\xd8\x85\xe1\x86\xe0\x1f\xd6\x5e\x9a"
+ "\xf7\x33\x3b\x80\x1b\xe7\xb6\x3e\x5f\x2d\xa1\x66\xf6\x95\x4a\x84\xa6\x21"
+ "\x56\x79\xe8\xf7\x85\xee\x5d\xe3\x7c\x12\xc0\xe0\x89\x22\x09\x22\x3e\xba"
+ "\x16\x95\x06\xbd\xa8\xb9\xb1\xa9\xb2\x7a\xd6\x61\x2e\x87\x11\xb9\x78\x40"
+ "\x89\x75\xdb\x0c\xdc\x90\xe0\xa4\x79\xd6\xd5\x5e\x6e\xd1\x2a\xdb\x34\xf4"
+ "\x99\x3f\x65\x89\x3b\x46\xc2\x29\x2c\x15\x07\x1c\xc9\x4b\x1a\x54\xf8\x6c"
+ "\x1e\xaf\x60\x27\x62\x0a\x65\xd5\x9a\xb9\x50\x36\x16\x6e\x71\xf6\x1f\x01"
+ "\xf7\x12\xa7\xfc\xbf\xf6\x21\xa3\x29\x90\x86\x2d\x77\xde\xbb\x4c\xd4\xcf"
+ "\xfd\xd2\xcf\x82\x2c\x4d\xd4\xf2\xc2\x2d\xac\xa9\xbe\xea\xc3\x19\x25\x43"
+ "\xb2\xe5\x9a\x6c\x0d\xc5\x1c\xa5\x8b\xf7\x3f\x30\xaf\xb9\x01\x91\xb7\x69"
+ "\x12\x12\xe5\x83\x61\xfe\x34\x00\xbe\xf6\x71\x8a\xc7\xeb\x50\x92\xe8\x59"
+ "\xfe\x15\x91\xeb\x96\x97\xf8\x23\x54\x3f\x2d\x8e\x07\xdf\xee\xda\xb3\x4f"
+ "\xc8\x3c\x9d\x6f\xdf\x3c\x2c\x43\x57\xa1\x47\x0c\x91\x04\xf4\x75\x4d\xda"
+ "\x89\x81\xa4\x14\x06\x34\xb9\x98\xc3\xda\xf1\xfd\xed\x33\x36\xd3\x16\x2d"
+ "\x35\x02\x03\x01\x00\x01",
+ 294 },
+ { "WoSign log",
+ "https://ctlog.wosign.com/",
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+ "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xcc\x11\x88\x7b\x2d\x66\xcb\xae\x8f"
+ "\x4d\x30\x66\x27\x19\x25\x22\x93\x21\x46\xb4\x2f\x01\xd3\xc6\xf9\x2b\xd5"
+ "\xc8\xba\x73\x9b\x06\xa2\xf0\x8a\x02\x9c\xd0\x6b\x46\x18\x30\x85\xba\xe9"
+ "\x24\x8b\x0e\xd1\x5b\x70\x28\x0c\x7e\xf1\x3a\x45\x7f\x5a\xf3\x82\x42\x60"
+ "\x31",
+ 91 },
+ { "StartCom log",
+ "https://ct.startssl.com/",
+ "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+ "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x48\xf3\x59\xf3\xf6\x05\x18\xd3\xdb"
+ "\xb2\xed\x46\x7e\xcf\xc8\x11\xb5\x57\xb1\xa8\xd6\x4c\xe6\x9f\xb7\x4a\x1a"
+ "\x14\x86\x43\xa9\x48\xb0\xcb\x5a\x3f\x3c\x4a\xca\xdf\xc4\x82\x14\x55\x9a"
+ "\xf8\xf7\x8e\x40\x55\xdc\xf4\xd2\xaf\xea\x75\x74\xfb\x4e\x7f\x60\x86\x2e"
+ "\x51",
+ 91 }
+};
+
+#endif // CTKnownLogs_h
--- a/security/certverifier/CTSerialization.cpp
+++ b/security/certverifier/CTSerialization.cpp
@@ -504,16 +504,20 @@ DecodeSignedCertificateTimestamp(Reader&
return rv;
}
rv = InputToBuffer(extensions, result.extensions);
if (rv != Success) {
return rv;
}
result.timestamp = timestamp;
+ result.origin = SignedCertificateTimestamp::Origin::Unknown;
+ result.verificationStatus =
+ SignedCertificateTimestamp::VerificationStatus::None;
+
output = Move(result);
return Success;
}
Result
EncodeSCTList(const Vector<pkix::Input>& scts, Buffer& output)
{
// Find out the total size of the SCT list to be written so we can
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTVerifyResult.cpp
@@ -0,0 +1,18 @@
+/* -*- 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 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 "CTVerifyResult.h"
+
+namespace mozilla { namespace ct {
+
+void
+CTVerifyResult::Reset()
+{
+ scts.clear();
+ decodingErrors = 0;
+}
+
+} } // namespace mozilla::ct
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTVerifyResult.h
@@ -0,0 +1,41 @@
+/* -*- 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 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 CTVerifyResult_h
+#define CTVerifyResult_h
+
+#include "mozilla/Vector.h"
+#include "SignedCertificateTimestamp.h"
+
+namespace mozilla { namespace ct {
+
+typedef Vector<SignedCertificateTimestamp> SCTList;
+
+// Holds Signed Certificate Timestamps verification results.
+class CTVerifyResult
+{
+public:
+ // SCTs that were processed during the verification. For each SCT,
+ // the verification result is stored in its |verificationStatus| field.
+ SCTList scts;
+
+ // The verifier makes the best effort to extract the available SCTs
+ // from the binary sources provided to it.
+ // If some SCT cannot be extracted due to encoding errors, the verifier
+ // proceeds to the next available one. In other words, decoding errors are
+ // effectively ignored.
+ // Note that a serialized SCT may fail to decode for a "legitimate" reason,
+ // e.g. if the SCT is from a future version of the Certificate Transparency
+ // standard.
+ // |decodingErrors| field counts the errors of the above kind.
+ size_t decodingErrors;
+
+ void Reset();
+};
+
+} } // namespace mozilla::ct
+
+#endif // CTVerifyResult_h
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -4,31 +4,35 @@
* 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 "CertVerifier.h"
#include <stdint.h>
#include "BRNameMatchingPolicy.h"
+#include "CTKnownLogs.h"
#include "ExtendedValidation.h"
+#include "MultiLogCTVerifier.h"
#include "NSSCertDBTrustDomain.h"
#include "NSSErrorsService.h"
#include "cert.h"
+#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "nsNSSComponent.h"
#include "nsServiceManagerUtils.h"
#include "pk11pub.h"
#include "pkix/pkix.h"
#include "pkix/pkixnss.h"
#include "prerror.h"
#include "secerr.h"
#include "secmod.h"
#include "sslerr.h"
+using namespace mozilla::ct;
using namespace mozilla::pkix;
using namespace mozilla::psm;
mozilla::LazyLogModule gCertVerifierLog("certverifier");
namespace mozilla { namespace psm {
const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
@@ -37,26 +41,29 @@ const CertVerifier::Flags CertVerifier::
CertVerifier::CertVerifier(OcspDownloadConfig odc,
OcspStrictConfig osc,
OcspGetConfig ogc,
uint32_t certShortLifetimeInDays,
PinningMode pinningMode,
SHA1Mode sha1Mode,
BRNameMatchingPolicy::Mode nameMatchingMode,
- NetscapeStepUpPolicy netscapeStepUpPolicy)
+ NetscapeStepUpPolicy netscapeStepUpPolicy,
+ CertificateTransparencyMode ctMode)
: mOCSPDownloadConfig(odc)
, mOCSPStrict(osc == ocspStrict)
, mOCSPGETEnabled(ogc == ocspGetEnabled)
, mCertShortLifetimeInDays(certShortLifetimeInDays)
, mPinningMode(pinningMode)
, mSHA1Mode(sha1Mode)
, mNameMatchingMode(nameMatchingMode)
, mNetscapeStepUpPolicy(netscapeStepUpPolicy)
+ , mCTMode(ctMode)
{
+ LoadKnownCTLogs();
}
CertVerifier::~CertVerifier()
{
}
Result
IsCertChainRootBuiltInRoot(const UniqueCERTCertList& chain, bool& result)
@@ -116,41 +123,187 @@ static Result
BuildCertChainForOneKeyUsage(NSSCertDBTrustDomain& trustDomain, Input certDER,
Time time, KeyUsage ku1, KeyUsage ku2,
KeyUsage ku3, KeyPurposeId eku,
const CertPolicyId& requiredPolicy,
const Input* stapledOCSPResponse,
/*optional out*/ CertVerifier::OCSPStaplingStatus*
ocspStaplingStatus)
{
- trustDomain.ResetOCSPStaplingStatus();
+ trustDomain.ResetAccumulatedState();
Result rv = BuildCertChain(trustDomain, certDER, time,
EndEntityOrCA::MustBeEndEntity, ku1,
eku, requiredPolicy, stapledOCSPResponse);
if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
- trustDomain.ResetOCSPStaplingStatus();
+ trustDomain.ResetAccumulatedState();
rv = BuildCertChain(trustDomain, certDER, time,
EndEntityOrCA::MustBeEndEntity, ku2,
eku, requiredPolicy, stapledOCSPResponse);
if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
- trustDomain.ResetOCSPStaplingStatus();
+ trustDomain.ResetAccumulatedState();
rv = BuildCertChain(trustDomain, certDER, time,
EndEntityOrCA::MustBeEndEntity, ku3,
eku, requiredPolicy, stapledOCSPResponse);
if (rv != Success) {
rv = Result::ERROR_INADEQUATE_KEY_USAGE;
}
}
}
if (ocspStaplingStatus) {
*ocspStaplingStatus = trustDomain.GetOCSPStaplingStatus();
}
return rv;
}
+void
+CertVerifier::LoadKnownCTLogs()
+{
+ mCTVerifier = MakeUnique<MultiLogCTVerifier>();
+ for (const CTLogInfo& log : kCTLogList) {
+ Input publicKey;
+ Result rv = publicKey.Init(
+ BitwiseCast<const uint8_t*, const char*>(log.logKey), log.logKeyLength);
+ if (rv != Success) {
+ MOZ_ASSERT_UNREACHABLE("Failed reading a log key for a known CT Log");
+ continue;
+ }
+ rv = mCTVerifier->AddLog(publicKey);
+ if (rv != Success) {
+ MOZ_ASSERT_UNREACHABLE("Failed initializing a known CT Log");
+ continue;
+ }
+ }
+}
+
+Result
+CertVerifier::VerifySignedCertificateTimestamps(
+ NSSCertDBTrustDomain& trustDomain, const UniqueCERTCertList& builtChain,
+ Input sctsFromTLS, Time time,
+ /*optional out*/ CertificateTransparencyInfo* ctInfo)
+{
+ if (ctInfo) {
+ ctInfo->Reset();
+ }
+ if (mCTMode == CertificateTransparencyMode::Disabled) {
+ return Success;
+ }
+ if (ctInfo) {
+ ctInfo->enabled = true;
+ }
+
+ if (!builtChain || CERT_LIST_EMPTY(builtChain)) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+
+ bool gotScts = false;
+ Input embeddedSCTs = trustDomain.GetSCTListFromCertificate();
+ if (embeddedSCTs.GetLength() > 0) {
+ gotScts = true;
+ MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+ ("Got embedded SCT data of length %zu\n",
+ static_cast<size_t>(embeddedSCTs.GetLength())));
+ }
+ Input sctsFromOCSP = trustDomain.GetSCTListFromOCSPStapling();
+ if (sctsFromOCSP.GetLength() > 0) {
+ gotScts = true;
+ MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+ ("Got OCSP SCT data of length %zu\n",
+ static_cast<size_t>(sctsFromOCSP.GetLength())));
+ }
+ if (sctsFromTLS.GetLength() > 0) {
+ gotScts = true;
+ MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+ ("Got TLS SCT data of length %zu\n",
+ static_cast<size_t>(sctsFromTLS.GetLength())));
+ }
+ if (!gotScts) {
+ return Success;
+ }
+
+ CERTCertListNode* endEntityNode = CERT_LIST_HEAD(builtChain);
+ if (!endEntityNode) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ CERTCertListNode* issuerNode = CERT_LIST_NEXT(endEntityNode);
+ if (!issuerNode) {
+ // Issuer certificate is required for SCT verification.
+ return Success;
+ }
+
+ CERTCertificate* endEntity = endEntityNode->cert;
+ CERTCertificate* issuer = issuerNode->cert;
+ if (!endEntity || !issuer) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+
+ Input endEntityDER;
+ Result rv = endEntityDER.Init(endEntity->derCert.data,
+ endEntity->derCert.len);
+ if (rv != Success) {
+ return rv;
+ }
+
+ Input issuerPublicKeyDER;
+ rv = issuerPublicKeyDER.Init(issuer->derPublicKey.data,
+ issuer->derPublicKey.len);
+ if (rv != Success) {
+ return rv;
+ }
+
+ CTVerifyResult result;
+ rv = mCTVerifier->Verify(endEntityDER, issuerPublicKeyDER,
+ embeddedSCTs, sctsFromOCSP, sctsFromTLS, time,
+ result);
+ if (rv != Success) {
+ MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+ ("SCT verification failed with fatal error %i\n", rv));
+ return rv;
+ }
+
+ if (MOZ_LOG_TEST(gCertVerifierLog, LogLevel::Debug)) {
+ size_t verifiedCount = 0;
+ size_t unknownLogCount = 0;
+ size_t invalidSignatureCount = 0;
+ size_t invalidTimestampCount = 0;
+ for (const SignedCertificateTimestamp& sct : result.scts) {
+ switch (sct.verificationStatus) {
+ case SignedCertificateTimestamp::VerificationStatus::OK:
+ verifiedCount++;
+ break;
+ case SignedCertificateTimestamp::VerificationStatus::UnknownLog:
+ unknownLogCount++;
+ break;
+ case SignedCertificateTimestamp::VerificationStatus::InvalidSignature:
+ invalidSignatureCount++;
+ break;
+ case SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp:
+ invalidTimestampCount++;
+ break;
+ case SignedCertificateTimestamp::VerificationStatus::None:
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected SCT verificationStatus");
+ }
+ }
+ MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+ ("SCT verification result: "
+ "verified=%zu unknownLog=%zu "
+ "invalidSignature=%zu invalidTimestamp=%zu "
+ "decodingErrors=%zu\n",
+ verifiedCount, unknownLogCount,
+ invalidSignatureCount, invalidTimestampCount,
+ result.decodingErrors));
+ }
+
+ if (ctInfo) {
+ ctInfo->processedSCTs = true;
+ ctInfo->verifyResult = Move(result);
+ }
+ return Success;
+}
+
bool
CertVerifier::SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode)
{
switch (mSHA1Mode) {
case SHA1Mode::Forbidden:
return mode != SHA1Mode::Forbidden;
case SHA1Mode::ImportedRoot:
return mode != SHA1Mode::Forbidden && mode != SHA1Mode::ImportedRoot;
@@ -170,21 +323,23 @@ static const unsigned int MIN_RSA_BITS =
static const unsigned int MIN_RSA_BITS_WEAK = 1024;
SECStatus
CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
Time time, void* pinArg, const char* hostname,
/*out*/ UniqueCERTCertList& builtChain,
/*optional*/ const Flags flags,
/*optional*/ const SECItem* stapledOCSPResponseSECItem,
+ /*optional*/ const SECItem* sctsFromTLSSECItem,
/*optional out*/ SECOidTag* evOidPolicy,
/*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
/*optional out*/ KeySizeStatus* keySizeStatus,
/*optional out*/ SHA1ModeResult* sha1ModeResult,
- /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo)
+ /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo,
+ /*optional out*/ CertificateTransparencyInfo* ctInfo)
{
MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Top of VerifyCert\n"));
PR_ASSERT(cert);
PR_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV));
PR_ASSERT(usage == certificateUsageSSLServer || !keySizeStatus);
PR_ASSERT(usage == certificateUsageSSLServer || !sha1ModeResult);
@@ -250,16 +405,25 @@ CertVerifier::VerifyCert(CERTCertificate
if (rv != Success) {
// The stapled OCSP response was too big.
PR_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE, 0);
return SECFailure;
}
stapledOCSPResponse = &stapledOCSPResponseInput;
}
+ Input sctsFromTLSInput;
+ if (sctsFromTLSSECItem) {
+ rv = sctsFromTLSInput.Init(sctsFromTLSSECItem->data,
+ sctsFromTLSSECItem->len);
+ // Silently discard the error of the extension being too big,
+ // do not fail the verification.
+ MOZ_ASSERT(rv == Success);
+ }
+
switch (usage) {
case certificateUsageSSLClient: {
// XXX: We don't really have a trust bit for SSL client authentication so
// just use trustEmail as it is the closest alternative.
NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
mOCSPCache, pinArg, ocspGETConfig,
mCertShortLifetimeInDays,
pinningDisabled, MIN_RSA_BITS_WEAK,
@@ -363,16 +527,22 @@ CertVerifier::VerifyCert(CERTCertificate
MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
("cert is EV with status %i\n", sha1ModeResults[i]));
if (evOidPolicy) {
*evOidPolicy = evPolicyOidTag;
}
if (sha1ModeResult) {
*sha1ModeResult = sha1ModeResults[i];
}
+ rv = VerifySignedCertificateTimestamps(trustDomain, builtChain,
+ sctsFromTLSInput, time,
+ ctInfo);
+ if (rv != Success) {
+ break;
+ }
}
}
if (rv == Success) {
break;
}
#endif
if (flags & FLAG_MUST_BE_EV) {
@@ -444,16 +614,22 @@ CertVerifier::VerifyCert(CERTCertificate
}
if (rv == Success) {
if (keySizeStatus) {
*keySizeStatus = keySizeStatuses[i];
}
if (sha1ModeResult) {
*sha1ModeResult = sha1ModeResults[j];
}
+ rv = VerifySignedCertificateTimestamps(trustDomain, builtChain,
+ sctsFromTLSInput, time,
+ ctInfo);
+ if (rv != Success) {
+ break;
+ }
}
}
}
if (rv == Success) {
break;
}
@@ -626,27 +802,29 @@ CertVerifier::VerifyCert(CERTCertificate
}
return SECSuccess;
}
SECStatus
CertVerifier::VerifySSLServerCert(const UniqueCERTCertificate& peerCert,
/*optional*/ const SECItem* stapledOCSPResponse,
+ /*optional*/ const SECItem* sctsFromTLS,
Time time,
/*optional*/ void* pinarg,
const char* hostname,
/*out*/ UniqueCERTCertList& builtChain,
/*optional*/ bool saveIntermediatesInPermanentDatabase,
/*optional*/ Flags flags,
/*optional out*/ SECOidTag* evOidPolicy,
/*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
/*optional out*/ KeySizeStatus* keySizeStatus,
/*optional out*/ SHA1ModeResult* sha1ModeResult,
- /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo)
+ /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo,
+ /*optional out*/ CertificateTransparencyInfo* ctInfo)
{
PR_ASSERT(peerCert);
// XXX: PR_ASSERT(pinarg)
PR_ASSERT(hostname);
PR_ASSERT(hostname[0]);
if (evOidPolicy) {
*evOidPolicy = SEC_OID_UNKNOWN;
@@ -656,19 +834,20 @@ CertVerifier::VerifySSLServerCert(const
PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
return SECFailure;
}
// CreateCertErrorRunnable assumes that CheckCertHostname is only called
// if VerifyCert succeeded.
SECStatus rv = VerifyCert(peerCert.get(), certificateUsageSSLServer, time,
pinarg, hostname, builtChain, flags,
- stapledOCSPResponse, evOidPolicy,
- ocspStaplingStatus, keySizeStatus,
- sha1ModeResult, pinningTelemetryInfo);
+ stapledOCSPResponse, sctsFromTLS,
+ evOidPolicy, ocspStaplingStatus, keySizeStatus,
+ sha1ModeResult, pinningTelemetryInfo,
+ ctInfo);
if (rv != SECSuccess) {
return rv;
}
Input peerCertInput;
Result result = peerCertInput.Init(peerCert->derCert.data,
peerCert->derCert.len);
if (result != Success) {
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -3,21 +3,32 @@
/* 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 CertVerifier_h
#define CertVerifier_h
#include "BRNameMatchingPolicy.h"
+#include "CTVerifyResult.h"
#include "OCSPCache.h"
#include "ScopedNSSTypes.h"
#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
#include "pkix/pkixtypes.h"
+namespace mozilla { namespace ct {
+
+// Including MultiLogCTVerifier.h would bring along all of its dependent
+// headers and force us to export them in moz.build. Just forward-declare
+// the class here instead.
+class MultiLogCTVerifier;
+
+} } // namespace mozilla::ct
+
namespace mozilla { namespace psm {
// These values correspond to the CERT_CHAIN_KEY_SIZE_STATUS telemetry.
enum class KeySizeStatus {
NeverChecked = 0,
LargeMinimumSucceeded = 1,
CompatibilityRisk = 2,
AlreadyBad = 3,
@@ -44,16 +55,31 @@ public:
int32_t certPinningResultBucket;
// Should we accumulate telemetry for the root?
bool accumulateForRoot;
int32_t rootBucket;
void Reset() { accumulateForRoot = false; accumulateResult = false; }
};
+class CertificateTransparencyInfo
+{
+public:
+ // Was CT enabled?
+ bool enabled;
+ // Did we receive and process any binary SCT data from the supported sources?
+ bool processedSCTs;
+ // Verification result of the processed SCTs.
+ mozilla::ct::CTVerifyResult verifyResult;
+
+ void Reset() { enabled = false; processedSCTs = false; verifyResult.Reset(); }
+};
+
+class NSSCertDBTrustDomain;
+
class CertVerifier
{
public:
typedef unsigned int Flags;
// XXX: FLAG_LOCAL_ONLY is ignored in the classic verification case
static const Flags FLAG_LOCAL_ONLY;
// Don't perform fallback DV validation on EV validation failure.
static const Flags FLAG_MUST_BE_EV;
@@ -74,36 +100,40 @@ public:
SECStatus VerifyCert(CERTCertificate* cert,
SECCertificateUsage usage,
mozilla::pkix::Time time,
void* pinArg,
const char* hostname,
/*out*/ UniqueCERTCertList& builtChain,
Flags flags = 0,
/*optional in*/ const SECItem* stapledOCSPResponse = nullptr,
+ /*optional in*/ const SECItem* sctsFromTLS = nullptr,
/*optional out*/ SECOidTag* evOidPolicy = nullptr,
/*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
/*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
/*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
- /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr);
+ /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
+ /*optional out*/ CertificateTransparencyInfo* ctInfo = nullptr);
SECStatus VerifySSLServerCert(
const UniqueCERTCertificate& peerCert,
/*optional*/ const SECItem* stapledOCSPResponse,
+ /*optional*/ const SECItem* sctsFromTLS,
mozilla::pkix::Time time,
/*optional*/ void* pinarg,
const char* hostname,
/*out*/ UniqueCERTCertList& builtChain,
/*optional*/ bool saveIntermediatesInPermanentDatabase = false,
/*optional*/ Flags flags = 0,
/*optional out*/ SECOidTag* evOidPolicy = nullptr,
/*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
/*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
/*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
- /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr);
+ /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
+ /*optional out*/ CertificateTransparencyInfo* ctInfo = nullptr);
enum PinningMode {
pinningDisabled = 0,
pinningAllowUserCAMITM = 1,
pinningStrict = 2,
pinningEnforceTestMode = 3
};
@@ -121,37 +151,56 @@ public:
enum OcspDownloadConfig {
ocspOff = 0,
ocspOn = 1,
ocspEVOnly = 2
};
enum OcspStrictConfig { ocspRelaxed = 0, ocspStrict };
enum OcspGetConfig { ocspGetDisabled = 0, ocspGetEnabled = 1 };
+ enum class CertificateTransparencyMode {
+ Disabled = 0,
+ TelemetryOnly = 1,
+ };
+
CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
PinningMode pinningMode, SHA1Mode sha1Mode,
BRNameMatchingPolicy::Mode nameMatchingMode,
- NetscapeStepUpPolicy netscapeStepUpPolicy);
+ NetscapeStepUpPolicy netscapeStepUpPolicy,
+ CertificateTransparencyMode ctMode);
~CertVerifier();
void ClearOCSPCache() { mOCSPCache.Clear(); }
const OcspDownloadConfig mOCSPDownloadConfig;
const bool mOCSPStrict;
const bool mOCSPGETEnabled;
const uint32_t mCertShortLifetimeInDays;
const PinningMode mPinningMode;
const SHA1Mode mSHA1Mode;
const BRNameMatchingPolicy::Mode mNameMatchingMode;
const NetscapeStepUpPolicy mNetscapeStepUpPolicy;
+ const CertificateTransparencyMode mCTMode;
private:
OCSPCache mOCSPCache;
+ // We only have a forward declaration of MultiLogCTVerifier (see above),
+ // so we keep a pointer to it and allocate dynamically.
+ UniquePtr<mozilla::ct::MultiLogCTVerifier> mCTVerifier;
+
+ void LoadKnownCTLogs();
+ mozilla::pkix::Result VerifySignedCertificateTimestamps(
+ NSSCertDBTrustDomain& trustDomain,
+ const UniqueCERTCertList& builtChain,
+ mozilla::pkix::Input sctsFromTLS,
+ mozilla::pkix::Time time,
+ /*optional out*/ CertificateTransparencyInfo* ctInfo);
+
// Returns true if the configured SHA1 mode is more restrictive than the given
// mode. SHA1Mode::Forbidden is more restrictive than any other mode except
// Forbidden. Next is ImportedRoot, then ImportedRootOrBefore2016, then
// Allowed. (A mode is never more restrictive than itself.)
bool SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode);
};
mozilla::pkix::Result IsCertBuiltInRoot(CERTCertificate* cert, bool& result);
--- a/security/certverifier/MultiLogCTVerifier.cpp
+++ b/security/certverifier/MultiLogCTVerifier.cpp
@@ -3,65 +3,36 @@
/* 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 "MultiLogCTVerifier.h"
#include "CTObjectsExtractor.h"
#include "CTSerialization.h"
+#include "mozilla/Assertions.h"
#include "mozilla/Move.h"
namespace mozilla { namespace ct {
using namespace mozilla::pkix;
-// The possible verification statuses for a Signed Certificate Timestamp.
-enum class SCTVerifyStatus {
- UnknownLog, // The SCT is from an unknown log and can not be verified.
- Invalid, // The SCT is from a known log, but the signature is invalid.
- OK // The SCT is from a known log, and the signature is valid.
-};
-
// Note: this moves |sct| to the target list in |result|, invalidating |sct|.
static Result
StoreVerifiedSct(CTVerifyResult& result,
SignedCertificateTimestamp&& sct,
- SCTVerifyStatus status)
+ SignedCertificateTimestamp::VerificationStatus status)
{
- SCTList* target;
- switch (status) {
- case SCTVerifyStatus::UnknownLog:
- target = &result.unknownLogsScts;
- break;
- case SCTVerifyStatus::Invalid:
- target = &result.invalidScts;
- break;
- case SCTVerifyStatus::OK:
- target = &result.verifiedScts;
- break;
- default:
- MOZ_ASSERT_UNREACHABLE("Unexpected SCTVerifyStatus type");
- return Result::FATAL_ERROR_LIBRARY_FAILURE;
- }
- if (!target->append(Move(sct))) {
+ sct.verificationStatus = status;
+ if (!result.scts.append(Move(sct))) {
return Result::FATAL_ERROR_NO_MEMORY;
}
return Success;
}
-void
-CTVerifyResult::Reset()
-{
- verifiedScts.clear();
- invalidScts.clear();
- unknownLogsScts.clear();
- decodingErrors = 0;
-}
-
Result
MultiLogCTVerifier::AddLog(Input publicKey)
{
CTLogVerifier log;
Result rv = log.Init(publicKey);
if (rv != Success) {
return rv;
}
@@ -72,17 +43,17 @@ MultiLogCTVerifier::AddLog(Input publicK
}
Result
MultiLogCTVerifier::Verify(Input cert,
Input issuerSubjectPublicKeyInfo,
Input sctListFromCert,
Input sctListFromOCSPResponse,
Input sctListFromTLSExtension,
- uint64_t time,
+ Time time,
CTVerifyResult& result)
{
MOZ_ASSERT(cert.GetLength() > 0);
result.Reset();
Result rv;
// Verify embedded SCTs
@@ -128,17 +99,17 @@ MultiLogCTVerifier::Verify(Input cert,
}
return Success;
}
Result
MultiLogCTVerifier::VerifySCTs(Input encodedSctList,
const LogEntry& expectedEntry,
SignedCertificateTimestamp::Origin origin,
- uint64_t time,
+ Time time,
CTVerifyResult& result)
{
Reader listReader;
Result rv = DecodeSCTList(encodedSctList, listReader);
if (rv != Success) {
result.decodingErrors++;
return Success;
}
@@ -166,46 +137,57 @@ MultiLogCTVerifier::VerifySCTs(Input enc
}
}
return Success;
}
Result
MultiLogCTVerifier::VerifySingleSCT(SignedCertificateTimestamp&& sct,
const LogEntry& expectedEntry,
- uint64_t time,
+ Time time,
CTVerifyResult& result)
{
CTLogVerifier* matchingLog = nullptr;
for (auto& log : mLogs) {
if (log.keyId() == sct.logId) {
matchingLog = &log;
break;
}
}
if (!matchingLog) {
// SCT does not match any known log.
- return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::UnknownLog);
+ return StoreVerifiedSct(result, Move(sct),
+ SignedCertificateTimestamp::VerificationStatus::UnknownLog);
}
if (!matchingLog->SignatureParametersMatch(sct.signature)) {
// SCT signature parameters do not match the log's.
- return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::Invalid);
+ return StoreVerifiedSct(result, Move(sct),
+ SignedCertificateTimestamp::VerificationStatus::InvalidSignature);
}
Result rv = matchingLog->Verify(expectedEntry, sct);
if (rv != Success) {
if (rv == Result::ERROR_BAD_SIGNATURE) {
- return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::Invalid);
+ return StoreVerifiedSct(result, Move(sct),
+ SignedCertificateTimestamp::VerificationStatus::InvalidSignature);
}
return rv;
}
+ // |sct.timestamp| is measured in milliseconds since the epoch,
+ // ignoring leap seconds. When converting it to a second-level precision
+ // pkix::Time, we need to round it either up or down. In our case, rounding up
+ // is more "secure", although practically it does not matter.
+ Time sctTime = TimeFromEpochInSeconds((sct.timestamp + 999u) / 1000u);
+
// SCT verified ok, just make sure the timestamp is legitimate.
- if (sct.timestamp > time) {
- return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::Invalid);
+ if (sctTime > time) {
+ return StoreVerifiedSct(result, Move(sct),
+ SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp);
}
- return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::OK);
+ return StoreVerifiedSct(result, Move(sct),
+ SignedCertificateTimestamp::VerificationStatus::OK);
}
} } // namespace mozilla::ct
--- a/security/certverifier/MultiLogCTVerifier.h
+++ b/security/certverifier/MultiLogCTVerifier.h
@@ -3,55 +3,25 @@
/* 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 MultiLogCTVerifier_h
#define MultiLogCTVerifier_h
#include "CTLogVerifier.h"
+#include "CTVerifyResult.h"
#include "mozilla/Vector.h"
#include "pkix/Input.h"
#include "pkix/Result.h"
+#include "pkix/Time.h"
#include "SignedCertificateTimestamp.h"
namespace mozilla { namespace ct {
-typedef Vector<SignedCertificateTimestamp> SCTList;
-
-// Holds Signed Certificate Timestamps, arranged by their verification results.
-class CTVerifyResult
-{
-public:
- // SCTs from known logs where the signature verified correctly.
- SCTList verifiedScts;
-
- // SCTs from known logs where the signature failed to verify.
- SCTList invalidScts;
-
- // SCTs from unknown logs and as such are unverifiable.
- SCTList unknownLogsScts;
-
- // For a certificate to pass Certificate Transparency verification, at least
- // one of the provided SCTs must validate. The verifier makes the best effort
- // to extract the available SCTs from the binary sources provided to it.
- // If some SCT cannot be extracted due to encoding errors, the verifier
- // proceeds to the next available one. In other words, decoding errors are
- // effectively ignored.
- // Note that a serialized SCT may fail to decode for a "legitimate" reason,
- // e.g. if the SCT is from a future version of the Certificate Transparency
- // standard.
- // |decodingErrors| field counts the errors of the above kind.
- // This field is purely informational; there is probably nothing to do with it
- // in release builds, but it is useful in unit tests.
- size_t decodingErrors;
-
- void Reset();
-};
-
// A Certificate Transparency verifier that can verify Signed Certificate
// Timestamps from multiple logs.
class MultiLogCTVerifier
{
public:
// Adds a new log to the list of known logs to verify against.
pkix::Result AddLog(pkix::Input publicKey);
@@ -75,44 +45,41 @@ public:
// |issuerSubjectPublicKeyInfo| SPKI of |cert|'s issuer. Can be empty,
// in which case the embedded SCT list
// won't be verified.
// |sctListFromOCSPResponse| SCT list included in a stapled OCSP response
// for |cert|. Empty if not available.
// |sctListFromTLSExtension| is the SCT list from the TLS extension. Empty
// if no extension was present.
// |time| the current time. Used to make sure SCTs are not in the future.
- // Measured in milliseconds since the epoch, ignoring leap seconds
- // (same format as used by the "timestamp" field of
- // SignedCertificateTimestamp).
// |result| will be filled with the SCTs present, divided into categories
// based on the verification result.
pkix::Result Verify(pkix::Input cert,
pkix::Input issuerSubjectPublicKeyInfo,
pkix::Input sctListFromCert,
pkix::Input sctListFromOCSPResponse,
pkix::Input sctListFromTLSExtension,
- uint64_t time,
+ pkix::Time time,
CTVerifyResult& result);
private:
// Verifies a list of SCTs from |encodedSctList| over |expectedEntry|,
// placing the verification results in |result|. The SCTs in the list
// come from |origin| (as will be reflected in the origin field of each SCT).
pkix::Result VerifySCTs(pkix::Input encodedSctList,
const LogEntry& expectedEntry,
SignedCertificateTimestamp::Origin origin,
- uint64_t time,
+ pkix::Time time,
CTVerifyResult& result);
// Verifies a single, parsed SCT against all known logs.
// Note: moves |sct| to the target list in |result|, invalidating |sct|.
pkix::Result VerifySingleSCT(SignedCertificateTimestamp&& sct,
const ct::LogEntry& expectedEntry,
- uint64_t time,
+ pkix::Time time,
CTVerifyResult& result);
// The list of known logs.
Vector<CTLogVerifier> mLogs;
};
} } // namespace mozilla::ct
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -10,16 +10,17 @@
#include "ExtendedValidation.h"
#include "NSSErrorsService.h"
#include "OCSPRequestor.h"
#include "OCSPVerificationTrustDomain.h"
#include "PublicKeyPinningService.h"
#include "cert.h"
#include "certdb.h"
+#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "nsNSSCertificate.h"
#include "nsServiceManagerUtils.h"
#include "nss.h"
#include "pk11pub.h"
#include "pkix/Result.h"
@@ -68,16 +69,18 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
, mValidityCheckingMode(validityCheckingMode)
, mSHA1Mode(sha1Mode)
, mNetscapeStepUpPolicy(netscapeStepUpPolicy)
, mBuiltChain(builtChain)
, mPinningTelemetryInfo(pinningTelemetryInfo)
, mHostname(hostname)
, mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID))
, mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED)
+ , mSCTListFromCertificate()
+ , mSCTListFromOCSPStapling()
{
}
// If useRoots is true, we only use root certificates in the candidate list.
// If useRoots is false, we only use non-root certificates in the list.
static Result
FindIssuerInner(const UniqueCERTCertList& candidates, bool useRoots,
Input encodedIssuerName, TrustDomain::IssuerChecker& checker,
@@ -958,19 +961,69 @@ NSSCertDBTrustDomain::NetscapeStepUpMatc
return Success;
default:
MOZ_ASSERT_UNREACHABLE("unhandled NetscapeStepUpPolicy type");
}
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
void
-NSSCertDBTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension /*extension*/,
- Input /*extensionData*/)
+NSSCertDBTrustDomain::ResetAccumulatedState()
+{
+ mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
+ mSCTListFromOCSPStapling = nullptr;
+ mSCTListFromCertificate = nullptr;
+}
+
+static Input
+SECItemToInput(const UniqueSECItem& item)
+{
+ Input result;
+ if (item) {
+ MOZ_ASSERT(item->type == siBuffer);
+ Result rv = result.Init(item->data, item->len);
+ // As used here, |item| originally comes from an Input,
+ // so there should be no issues converting it back.
+ MOZ_ASSERT(rv == Success);
+ Unused << rv; // suppresses warnings in release builds
+ }
+ return result;
+}
+
+Input
+NSSCertDBTrustDomain::GetSCTListFromCertificate() const
{
+ return SECItemToInput(mSCTListFromCertificate);
+}
+
+Input
+NSSCertDBTrustDomain::GetSCTListFromOCSPStapling() const
+{
+ return SECItemToInput(mSCTListFromOCSPStapling);
+}
+
+void
+NSSCertDBTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension extension,
+ Input extensionData)
+{
+ UniqueSECItem* out = nullptr;
+ switch (extension) {
+ case AuxiliaryExtension::EmbeddedSCTList:
+ out = &mSCTListFromCertificate;
+ break;
+ case AuxiliaryExtension::SCTListFromOCSPResponse:
+ out = &mSCTListFromOCSPStapling;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unhandled AuxiliaryExtension");
+ }
+ if (out) {
+ SECItem extensionDataItem = UnsafeMapInputToSECItem(extensionData);
+ out->reset(SECITEM_DupItem(&extensionDataItem));
+ }
}
SECStatus
InitializeNSS(const char* dir, bool readOnly, bool loadPKCS11Modules)
{
// The NSS_INIT_NOROOTINIT flag turns off the loading of the root certs
// module by NSS_Initialize because we will load it in InstallLoadableRoots
// later. It also allows us to work around a bug in the system NSS in
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -140,24 +140,32 @@ public:
virtual Result IsChainValid(const mozilla::pkix::DERArray& certChain,
mozilla::pkix::Time time) override;
virtual void NoteAuxiliaryExtension(
mozilla::pkix::AuxiliaryExtension extension,
mozilla::pkix::Input extensionData) override;
+ // Resets the OCSP stapling status and SCT lists accumulated during
+ // the chain building.
+ void ResetAccumulatedState();
+
CertVerifier::OCSPStaplingStatus GetOCSPStaplingStatus() const
{
return mOCSPStaplingStatus;
}
- void ResetOCSPStaplingStatus()
- {
- mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
- }
+
+ // SCT lists (see Certificate Transparency) extracted during
+ // certificate verification. Note that the returned Inputs are invalidated
+ // the next time a chain is built and by ResetAccumulatedState method
+ // (and when the TrustDomain object is destroyed).
+
+ mozilla::pkix::Input GetSCTListFromCertificate() const;
+ mozilla::pkix::Input GetSCTListFromOCSPStapling() const;
private:
enum EncodedResponseSource {
ResponseIsFromNetwork = 1,
ResponseWasStapled = 2
};
Result VerifyAndMaybeCacheEncodedOCSPResponse(
const mozilla::pkix::CertID& certID, mozilla::pkix::Time time,
@@ -175,13 +183,16 @@ private:
ValidityCheckingMode mValidityCheckingMode;
CertVerifier::SHA1Mode mSHA1Mode;
NetscapeStepUpPolicy mNetscapeStepUpPolicy;
UniqueCERTCertList& mBuiltChain; // non-owning
PinningTelemetryInfo* mPinningTelemetryInfo;
const char* mHostname; // non-owning - only used for pinning checks
nsCOMPtr<nsICertBlocklist> mCertBlocklist;
CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
+ // Certificate Transparency data extracted during certificate verification
+ UniqueSECItem mSCTListFromCertificate;
+ UniqueSECItem mSCTListFromOCSPStapling;
};
} } // namespace mozilla::psm
#endif // NSSCertDBTrustDomain_h
--- a/security/certverifier/SignedCertificateTimestamp.h
+++ b/security/certverifier/SignedCertificateTimestamp.h
@@ -73,33 +73,49 @@ struct DigitallySigned
// SignedCertificateTimestamp struct in RFC 6962, Section 3.2.
struct SignedCertificateTimestamp
{
// Version enum in RFC 6962, Section 3.2.
enum class Version {
V1 = 0,
};
- // Source of the SCT - supplementary, not defined in CT RFC.
- // Note: The numeric values are used within histograms and should not change
- // or be re-assigned.
- enum class Origin {
- Embedded = 0,
- TLSExtension = 1,
- OCSPResponse = 2,
- };
-
Version version;
Buffer logId;
// "timestamp" is the current time in milliseconds, measured since the epoch,
// ignoring leap seconds. See RFC 6962, Section 3.2.
uint64_t timestamp;
Buffer extensions;
DigitallySigned signature;
+
+ // Supplementary fields, not defined in CT RFC. Set during the various
+ // stages of processing the received SCTs.
+
+ enum class Origin {
+ Unknown,
+ Embedded,
+ TLSExtension,
+ OCSPResponse
+ };
+
+ enum class VerificationStatus {
+ None,
+ // The SCT is from a known log, and the signature is valid.
+ OK,
+ // The SCT is from an unknown log and can not be verified.
+ UnknownLog,
+ // The SCT is from a known log, but the signature is invalid.
+ InvalidSignature,
+ // The SCT signature is valid, but the timestamp is in the future.
+ // Such SCT are considered invalid (see RFC 6962, Section 5.2).
+ InvalidTimestamp
+ };
+
Origin origin;
+ VerificationStatus verificationStatus;
};
inline pkix::Result BufferToInput(const Buffer& buffer, pkix::Input& input)
{
return input.Init(buffer.begin(), buffer.length());
}
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -2,25 +2,29 @@
# vim: set filetype=python:
# 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/.
EXPORTS += [
'BRNameMatchingPolicy.h',
'CertVerifier.h',
+ 'CTVerifyResult.h',
'OCSPCache.h',
+ 'SignedCertificateTimestamp.h',
+ 'SignedTreeHead.h',
]
UNIFIED_SOURCES += [
'BRNameMatchingPolicy.cpp',
'CertVerifier.cpp',
'CTLogVerifier.cpp',
'CTObjectsExtractor.cpp',
'CTSerialization.cpp',
+ 'CTVerifyResult.cpp',
'MultiLogCTVerifier.cpp',
'NSSCertDBTrustDomain.cpp',
'OCSPCache.cpp',
'OCSPRequestor.cpp',
'OCSPVerificationTrustDomain.cpp',
'SignedCertificateTimestamp.cpp',
]
--- a/security/certverifier/tests/gtest/MultiLogCTVerifierTest.cpp
+++ b/security/certverifier/tests/gtest/MultiLogCTVerifierTest.cpp
@@ -19,42 +19,46 @@
namespace mozilla { namespace ct {
using namespace mozilla::pkix;
class MultiLogCTVerifierTest : public ::testing::Test
{
public:
+ MultiLogCTVerifierTest()
+ : mNow(Time::uninitialized)
+ {}
+
void SetUp() override
{
// Does nothing if NSS is already initialized.
MOZ_RELEASE_ASSERT(NSS_NoDB_Init(nullptr) == SECSuccess);
ASSERT_EQ(Success, mVerifier.AddLog(InputForBuffer(GetTestPublicKey())));
mTestCert = GetDEREncodedX509Cert();
mEmbeddedCert = GetDEREncodedTestEmbeddedCert();
mCaCert = GetDEREncodedCACert();
mCaCertSPKI = ExtractCertSPKI(mCaCert);
mIntermediateCert = GetDEREncodedIntermediateCert();
mIntermediateCertSPKI = ExtractCertSPKI(mIntermediateCert);
// Set the current time making sure all test timestamps are in the past.
- mNow = UINT64_MAX;
+ mNow = TimeFromEpochInSeconds(1451606400u); // Date.parse("2016-01-01")/1000
}
void CheckForSingleVerifiedSCTInResult(const CTVerifyResult& result,
SignedCertificateTimestamp::Origin origin)
{
EXPECT_EQ(0U, result.decodingErrors);
- EXPECT_TRUE(result.invalidScts.empty());
- EXPECT_TRUE(result.unknownLogsScts.empty());
- ASSERT_EQ(1U, result.verifiedScts.length());
- EXPECT_EQ(origin, result.verifiedScts[0].origin);
+ ASSERT_EQ(1U, result.scts.length());
+ EXPECT_EQ(SignedCertificateTimestamp::VerificationStatus::OK,
+ result.scts[0].verificationStatus);
+ EXPECT_EQ(origin, result.scts[0].origin);
}
// Writes an SCTList containing a single |sct| into |output|.
void EncodeSCTListForTesting(Input sct, Buffer& output)
{
Vector<Input> list;
ASSERT_TRUE(list.append(Move(sct)));
ASSERT_EQ(Success, EncodeSCTList(list, output));
@@ -88,17 +92,17 @@ public:
protected:
MultiLogCTVerifier mVerifier;
Buffer mTestCert;
Buffer mEmbeddedCert;
Buffer mCaCert;
Buffer mCaCertSPKI;
Buffer mIntermediateCert;
Buffer mIntermediateCertSPKI;
- uint64_t mNow;
+ Time mNow;
};
// Test that an embedded SCT can be extracted and the extracted SCT contains
// the expected data. This tests the ExtractEmbeddedSCTList function from
// CTTestUtils.h that other tests here rely upon.
TEST_F(MultiLogCTVerifierTest, ExtractEmbeddedSCT)
{
SignedCertificateTimestamp sct;
@@ -191,17 +195,19 @@ TEST_F(MultiLogCTVerifierTest, VerifiesS
CTVerifyResult result;
ASSERT_EQ(Success,
mVerifier.Verify(InputForBuffer(mTestCert), Input(), Input(),
InputForBuffer(sctList), InputForBuffer(sctList),
mNow, result));
// The result should contain verified SCTs from TLS and OCSP origins.
EnumSet<SignedCertificateTimestamp::Origin> origins;
- for (auto& sct : result.verifiedScts) {
+ for (const SignedCertificateTimestamp& sct : result.scts) {
+ EXPECT_EQ(SignedCertificateTimestamp::VerificationStatus::OK,
+ sct.verificationStatus);
origins += sct.origin;
}
EXPECT_FALSE(
origins.contains(SignedCertificateTimestamp::Origin::Embedded));
EXPECT_TRUE(
origins.contains(SignedCertificateTimestamp::Origin::OCSPResponse));
EXPECT_TRUE(
origins.contains(SignedCertificateTimestamp::Origin::TLSExtension));
@@ -213,13 +219,15 @@ TEST_F(MultiLogCTVerifierTest, Identifie
GetSCTListWithInvalidLogID(sctList);
CTVerifyResult result;
ASSERT_EQ(Success,
mVerifier.Verify(InputForBuffer(mTestCert), Input(),
Input(), Input(), InputForBuffer(sctList),
mNow, result));
- EXPECT_EQ(1U, result.unknownLogsScts.length());
EXPECT_EQ(0U, result.decodingErrors);
+ ASSERT_EQ(1U, result.scts.length());
+ EXPECT_EQ(SignedCertificateTimestamp::VerificationStatus::UnknownLog,
+ result.scts[0].verificationStatus);
}
} } // namespace mozilla::ct
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -734,60 +734,65 @@ class SSLServerCertVerificationJob : pub
{
public:
// Must be called only on the socket transport thread
static SECStatus Dispatch(const RefPtr<SharedCertVerifier>& certVerifier,
const void* fdForLogging,
nsNSSSocketInfo* infoObject,
const UniqueCERTCertificate& serverCert,
const UniqueCERTCertList& peerCertChain,
- SECItem* stapledOCSPResponse,
+ const SECItem* stapledOCSPResponse,
+ const SECItem* sctsFromTLSExtension,
uint32_t providerFlags,
Time time,
PRTime prtime);
private:
NS_DECL_NSIRUNNABLE
// Must be called only on the socket transport thread
SSLServerCertVerificationJob(const RefPtr<SharedCertVerifier>& certVerifier,
const void* fdForLogging,
nsNSSSocketInfo* infoObject,
const UniqueCERTCertificate& cert,
UniqueCERTCertList peerCertChain,
- SECItem* stapledOCSPResponse,
+ const SECItem* stapledOCSPResponse,
+ const SECItem* sctsFromTLSExtension,
uint32_t providerFlags,
Time time,
PRTime prtime);
const RefPtr<SharedCertVerifier> mCertVerifier;
const void* const mFdForLogging;
const RefPtr<nsNSSSocketInfo> mInfoObject;
const UniqueCERTCertificate mCert;
UniqueCERTCertList mPeerCertChain;
const uint32_t mProviderFlags;
const Time mTime;
const PRTime mPRTime;
const TimeStamp mJobStartTime;
const UniqueSECItem mStapledOCSPResponse;
+ const UniqueSECItem mSCTsFromTLSExtension;
};
SSLServerCertVerificationJob::SSLServerCertVerificationJob(
const RefPtr<SharedCertVerifier>& certVerifier, const void* fdForLogging,
nsNSSSocketInfo* infoObject, const UniqueCERTCertificate& cert,
- UniqueCERTCertList peerCertChain, SECItem* stapledOCSPResponse,
+ UniqueCERTCertList peerCertChain, const SECItem* stapledOCSPResponse,
+ const SECItem* sctsFromTLSExtension,
uint32_t providerFlags, Time time, PRTime prtime)
: mCertVerifier(certVerifier)
, mFdForLogging(fdForLogging)
, mInfoObject(infoObject)
, mCert(CERT_DupCertificate(cert.get()))
, mPeerCertChain(Move(peerCertChain))
, mProviderFlags(providerFlags)
, mTime(time)
, mPRTime(prtime)
, mJobStartTime(TimeStamp::Now())
, mStapledOCSPResponse(SECITEM_DupItem(stapledOCSPResponse))
+ , mSCTsFromTLSExtension(SECITEM_DupItem(sctsFromTLSExtension))
{
}
// This function assumes that we will only use the SPDY connection coalescing
// feature on connections where we have negotiated SPDY using NPN. If we ever
// talk SPDY without having negotiated it with SPDY, this code will give wrong
// and perhaps unsafe results.
//
@@ -1209,23 +1214,98 @@ void
GatherSuccessfulValidationTelemetry(const UniqueCERTCertList& certList)
{
GatherBaselineRequirementsTelemetry(certList);
GatherEKUTelemetry(certList);
GatherRootCATelemetry(certList);
GatherEndEntityTelemetry(certList);
}
+void
+GatherTelemetryForSingleSCT(const ct::SignedCertificateTimestamp& sct)
+{
+ // See SSL_SCTS_ORIGIN in Histograms.json.
+ uint32_t origin = 0;
+ switch (sct.origin) {
+ case ct::SignedCertificateTimestamp::Origin::Embedded:
+ origin = 1;
+ break;
+ case ct::SignedCertificateTimestamp::Origin::TLSExtension:
+ origin = 2;
+ break;
+ case ct::SignedCertificateTimestamp::Origin::OCSPResponse:
+ origin = 3;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected SCT::Origin type");
+ }
+ Telemetry::Accumulate(Telemetry::SSL_SCTS_ORIGIN, origin);
+
+ // See SSL_SCTS_VERIFICATION_STATUS in Histograms.json.
+ uint32_t verificationStatus = 0;
+ switch (sct.verificationStatus) {
+ case ct::SignedCertificateTimestamp::VerificationStatus::OK:
+ verificationStatus = 1;
+ break;
+ case ct::SignedCertificateTimestamp::VerificationStatus::UnknownLog:
+ verificationStatus = 2;
+ break;
+ case ct::SignedCertificateTimestamp::VerificationStatus::InvalidSignature:
+ verificationStatus = 3;
+ break;
+ case ct::SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp:
+ verificationStatus = 4;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected SCT::VerificationStatus type");
+ }
+ Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS,
+ verificationStatus);
+}
+
+void
+GatherCertificateTransparencyTelemetry(const UniqueCERTCertList& certList,
+ const CertificateTransparencyInfo& info)
+{
+ if (!info.enabled) {
+ // No telemetry is gathered when CT is disabled.
+ return;
+ }
+
+ if (!info.processedSCTs) {
+ // We didn't receive any SCT data for this connection.
+ Telemetry::Accumulate(Telemetry::SSL_SCTS_PER_CONNECTION, 0);
+ return;
+ }
+
+ for (const ct::SignedCertificateTimestamp& sct : info.verifyResult.scts) {
+ GatherTelemetryForSingleSCT(sct);
+ }
+
+ // Decoding errors are reported to the 0th bucket
+ // of the SSL_SCTS_VERIFICATION_STATUS enumerated probe.
+ for (size_t i = 0; i < info.verifyResult.decodingErrors; ++i) {
+ Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS, 0);
+ }
+
+ // Handle the histogram of SCTs counts.
+ uint32_t sctsCount = static_cast<uint32_t>(info.verifyResult.scts.length());
+ // Note that sctsCount can be 0 in case we've received SCT binary data,
+ // but it failed to parse (e.g. due to unsupported CT protocol version).
+ Telemetry::Accumulate(Telemetry::SSL_SCTS_PER_CONNECTION, sctsCount);
+}
+
// Note: Takes ownership of |peerCertChain| if SECSuccess is not returned.
SECStatus
AuthCertificate(CertVerifier& certVerifier,
nsNSSSocketInfo* infoObject,
const UniqueCERTCertificate& cert,
UniqueCERTCertList& peerCertChain,
- SECItem* stapledOCSPResponse,
+ const SECItem* stapledOCSPResponse,
+ const SECItem* sctsFromTLSExtension,
uint32_t providerFlags,
Time time)
{
MOZ_ASSERT(infoObject);
MOZ_ASSERT(cert);
SECStatus rv;
@@ -1236,30 +1316,32 @@ AuthCertificate(CertVerifier& certVerifi
SECOidTag evOidPolicy;
UniqueCERTCertList certList;
CertVerifier::OCSPStaplingStatus ocspStaplingStatus =
CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
KeySizeStatus keySizeStatus = KeySizeStatus::NeverChecked;
SHA1ModeResult sha1ModeResult = SHA1ModeResult::NeverChecked;
PinningTelemetryInfo pinningTelemetryInfo;
+ CertificateTransparencyInfo certificateTransparencyInfo;
int flags = 0;
if (!infoObject->SharedState().IsOCSPStaplingEnabled() ||
!infoObject->SharedState().IsOCSPMustStapleEnabled()) {
flags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
}
rv = certVerifier.VerifySSLServerCert(cert, stapledOCSPResponse,
- time, infoObject,
+ sctsFromTLSExtension, time, infoObject,
infoObject->GetHostNameRaw(),
certList, saveIntermediates, flags,
&evOidPolicy, &ocspStaplingStatus,
&keySizeStatus, &sha1ModeResult,
- &pinningTelemetryInfo);
+ &pinningTelemetryInfo,
+ &certificateTransparencyInfo);
PRErrorCode savedErrorCode;
if (rv != SECSuccess) {
savedErrorCode = PR_GetError();
}
uint32_t evStatus = (rv != SECSuccess) ? 0 // 0 = Failure
: (evOidPolicy == SEC_OID_UNKNOWN) ? 1 // 1 = DV
: 2; // 2 = EV
@@ -1300,16 +1382,18 @@ AuthCertificate(CertVerifier& certVerifi
}
else {
nsc = nsNSSCertificate::Create(cert.get());
}
}
if (rv == SECSuccess) {
GatherSuccessfulValidationTelemetry(certList);
+ GatherCertificateTransparencyTelemetry(certList,
+ certificateTransparencyInfo);
// The connection may get terminated, for example, if the server requires
// a client cert. Let's provide a minimal SSLStatus
// to the caller that contains at least the cert and its status.
if (!status) {
status = new nsSSLStatus();
infoObject->SetSSLStatus(status);
}
@@ -1352,17 +1436,18 @@ AuthCertificate(CertVerifier& certVerifi
/*static*/ SECStatus
SSLServerCertVerificationJob::Dispatch(
const RefPtr<SharedCertVerifier>& certVerifier,
const void* fdForLogging,
nsNSSSocketInfo* infoObject,
const UniqueCERTCertificate& serverCert,
const UniqueCERTCertList& peerCertChain,
- SECItem* stapledOCSPResponse,
+ const SECItem* stapledOCSPResponse,
+ const SECItem* sctsFromTLSExtension,
uint32_t providerFlags,
Time time,
PRTime prtime)
{
// Runs on the socket transport thread
if (!certVerifier || !infoObject || !serverCert) {
NS_ERROR("Invalid parameters for SSL server cert validation");
PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
@@ -1379,18 +1464,18 @@ SSLServerCertVerificationJob::Dispatch(
if (!peerCertChainCopy) {
PR_SetError(SEC_ERROR_NO_MEMORY, 0);
return SECFailure;
}
RefPtr<SSLServerCertVerificationJob> job(
new SSLServerCertVerificationJob(certVerifier, fdForLogging, infoObject,
serverCert, Move(peerCertChainCopy),
- stapledOCSPResponse, providerFlags,
- time, prtime));
+ stapledOCSPResponse, sctsFromTLSExtension,
+ providerFlags, time, prtime));
nsresult nrv;
if (!gCertVerificationThreadPool) {
nrv = NS_ERROR_NOT_INITIALIZED;
} else {
nrv = gCertVerificationThreadPool->Dispatch(job, NS_DISPATCH_NORMAL);
}
if (NS_FAILED(nrv)) {
@@ -1431,16 +1516,17 @@ SSLServerCertVerificationJob::Run()
Telemetry::ID failureTelemetry
= Telemetry::SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_MOZILLAPKIX;
// Reset the error code here so we can detect if AuthCertificate fails to
// set the error code if/when it fails.
PR_SetError(0, 0);
SECStatus rv = AuthCertificate(*mCertVerifier, mInfoObject, mCert,
mPeerCertChain, mStapledOCSPResponse.get(),
+ mSCTsFromTLSExtension.get(),
mProviderFlags, mTime);
MOZ_ASSERT(mPeerCertChain || rv != SECSuccess,
"AuthCertificate() should take ownership of chain on failure");
if (rv == SECSuccess) {
uint32_t interval = (uint32_t) ((TimeStamp::Now() - mJobStartTime).ToMilliseconds());
RefPtr<SSLServerCertVerificationResult> restart(
new SSLServerCertVerificationResult(mInfoObject, 0,
successTelemetry, interval));
@@ -1581,41 +1667,49 @@ AuthCertificateHook(void* arg, PRFileDes
// We don't own these pointers.
const SECItemArray* csa = SSL_PeerStapledOCSPResponses(fd);
SECItem* stapledOCSPResponse = nullptr;
// we currently only support single stapled responses
if (csa && csa->len == 1) {
stapledOCSPResponse = &csa->items[0];
}
+ const SECItem* sctsFromTLSExtension = SSL_PeerSignedCertTimestamps(fd);
+ if (sctsFromTLSExtension && sctsFromTLSExtension->len == 0) {
+ // SSL_PeerSignedCertTimestamps returns null on error and empty item
+ // when no extension was returned by the server. We always use null when
+ // no extension was received (for whatever reason), ignoring errors.
+ sctsFromTLSExtension = nullptr;
+ }
+
uint32_t providerFlags = 0;
socketInfo->GetProviderFlags(&providerFlags);
if (onSTSThread) {
// We *must* do certificate verification on a background thread because
// we need the socket transport thread to be free for our OCSP requests,
// and we *want* to do certificate verification on a background thread
// because of the performance benefits of doing so.
socketInfo->SetCertVerificationWaiting();
SECStatus rv = SSLServerCertVerificationJob::Dispatch(
certVerifier, static_cast<const void*>(fd), socketInfo,
serverCert, peerCertChain, stapledOCSPResponse,
- providerFlags, now, prnow);
+ sctsFromTLSExtension, providerFlags, now, prnow);
return rv;
}
// We can't do certificate verification on a background thread, because the
// thread doing the network I/O may not interrupt its network I/O on receipt
// of our SSLServerCertVerificationResult event, and/or it might not even be
// a non-blocking socket.
SECStatus rv = AuthCertificate(*certVerifier, socketInfo, serverCert,
peerCertChain, stapledOCSPResponse,
- providerFlags, now);
+ sctsFromTLSExtension, providerFlags, now);
MOZ_ASSERT(peerCertChain || rv != SECSuccess,
"AuthCertificate() should take ownership of chain on failure");
if (rv == SECSuccess) {
Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);
return SECSuccess;
}
PRErrorCode error = PR_GetError();
--- a/security/manager/ssl/SharedCertVerifier.h
+++ b/security/manager/ssl/SharedCertVerifier.h
@@ -17,19 +17,20 @@ protected:
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedCertVerifier)
SharedCertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
PinningMode pinningMode, SHA1Mode sha1Mode,
BRNameMatchingPolicy::Mode nameMatchingMode,
- NetscapeStepUpPolicy netscapeStepUpPolicy)
+ NetscapeStepUpPolicy netscapeStepUpPolicy,
+ CertificateTransparencyMode ctMode)
: mozilla::psm::CertVerifier(odc, osc, ogc, certShortLifetimeInDays,
pinningMode, sha1Mode, nameMatchingMode,
- netscapeStepUpPolicy)
+ netscapeStepUpPolicy, ctMode)
{
}
};
} } // namespace mozilla::psm
#endif // SharedCertVerifier_h
--- a/security/manager/ssl/SharedSSLState.h
+++ b/security/manager/ssl/SharedSSLState.h
@@ -38,23 +38,31 @@ public:
void SetOCSPStaplingEnabled(bool staplingEnabled)
{
mOCSPStaplingEnabled = staplingEnabled;
}
void SetOCSPMustStapleEnabled(bool mustStapleEnabled)
{
mOCSPMustStapleEnabled = mustStapleEnabled;
}
+ void SetSignedCertTimestampsEnabled(bool signedCertTimestampsEnabled)
+ {
+ mSignedCertTimestampsEnabled = signedCertTimestampsEnabled;
+ }
// The following methods may be called from any thread
bool SocketCreated();
void NoteSocketCreated();
static void NoteCertOverrideServiceInstantiated();
bool IsOCSPStaplingEnabled() const { return mOCSPStaplingEnabled; }
bool IsOCSPMustStapleEnabled() const { return mOCSPMustStapleEnabled; }
+ bool IsSignedCertTimestampsEnabled() const
+ {
+ return mSignedCertTimestampsEnabled;
+ }
private:
~SharedSSLState();
void Cleanup();
nsCOMPtr<nsIObserver> mObserver;
RefPtr<nsClientAuthRememberService> mClientAuthRemember;
@@ -62,16 +70,17 @@ private:
// True if any sockets have been created that use this shared data.
// Requires synchronization between the socket and main threads for
// reading/writing.
Mutex mMutex;
bool mSocketCreated;
bool mOCSPStaplingEnabled;
bool mOCSPMustStapleEnabled;
+ bool mSignedCertTimestampsEnabled;
};
SharedSSLState* PublicSSLState();
SharedSSLState* PrivateSSLState();
} // namespace psm
} // namespace mozilla
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -1312,17 +1312,20 @@ nsNSSCertificate::hasValidEVOidTag(SECOi
uint32_t flags = mozilla::psm::CertVerifier::FLAG_LOCAL_ONLY |
mozilla::psm::CertVerifier::FLAG_MUST_BE_EV;
UniqueCERTCertList unusedBuiltChain;
SECStatus rv = certVerifier->VerifyCert(mCert.get(),
certificateUsageSSLServer, mozilla::pkix::Now(),
nullptr /* XXX pinarg */,
nullptr /* hostname */,
unusedBuiltChain,
- flags, nullptr /* stapledOCSPResponse */, &resultOidTag);
+ flags,
+ nullptr /* stapledOCSPResponse */,
+ nullptr /* sctsFromTLSExtension */,
+ &resultOidTag);
if (rv != SECSuccess) {
resultOidTag = SEC_OID_UNKNOWN;
}
if (resultOidTag != SEC_OID_UNKNOWN) {
validEV = true;
}
return NS_OK;
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -1488,30 +1488,32 @@ VerifyCertAtTime(nsIX509Cert* aCert,
UniqueCERTCertList resultChain;
SECOidTag evOidPolicy;
SECStatus srv;
if (aHostname && aUsage == certificateUsageSSLServer) {
srv = certVerifier->VerifySSLServerCert(nssCert,
nullptr, // stapledOCSPResponse
+ nullptr, // sctsFromTLSExtension
aTime,
nullptr, // Assume no context
aHostname,
resultChain,
false, // don't save intermediates
aFlags,
&evOidPolicy);
} else {
srv = certVerifier->VerifyCert(nssCert.get(), aUsage, aTime,
nullptr, // Assume no context
aHostname,
resultChain,
aFlags,
nullptr, // stapledOCSPResponse
+ nullptr, // sctsFromTLSExtension
&evOidPolicy);
}
PRErrorCode error = PR_GetError();
nsCOMPtr<nsIX509CertList> nssCertList;
// This adopts the list
nssCertList = new nsNSSCertList(Move(resultChain), locker);
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1497,16 +1497,35 @@ void nsNSSComponent::setValidationOption
PublicSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
PrivateSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
bool ocspMustStapleEnabled = Preferences::GetBool("security.ssl.enable_ocsp_must_staple",
true);
PublicSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled);
PrivateSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled);
+ const CertVerifier::CertificateTransparencyMode defaultCTMode =
+ CertVerifier::CertificateTransparencyMode::TelemetryOnly;
+ CertVerifier::CertificateTransparencyMode ctMode =
+ static_cast<CertVerifier::CertificateTransparencyMode>
+ (Preferences::GetInt("security.pki.certificate_transparency.mode",
+ static_cast<int32_t>(defaultCTMode)));
+ switch (ctMode) {
+ case CertVerifier::CertificateTransparencyMode::Disabled:
+ case CertVerifier::CertificateTransparencyMode::TelemetryOnly:
+ break;
+ default:
+ ctMode = defaultCTMode;
+ break;
+ }
+ bool sctsEnabled =
+ ctMode != CertVerifier::CertificateTransparencyMode::Disabled;
+ PublicSSLState()->SetSignedCertTimestampsEnabled(sctsEnabled);
+ PrivateSSLState()->SetSignedCertTimestampsEnabled(sctsEnabled);
+
CertVerifier::PinningMode pinningMode =
static_cast<CertVerifier::PinningMode>
(Preferences::GetInt("security.cert_pinning.enforcement_level",
CertVerifier::pinningDisabled));
if (pinningMode > CertVerifier::pinningEnforceTestMode) {
pinningMode = CertVerifier::pinningDisabled;
}
@@ -1566,17 +1585,18 @@ void nsNSSComponent::setValidationOption
uint32_t certShortLifetimeInDays;
GetRevocationBehaviorFromPrefs(&odc, &osc, &ogc, &certShortLifetimeInDays,
lock);
mDefaultCertVerifier = new SharedCertVerifier(odc, osc, ogc,
certShortLifetimeInDays,
pinningMode, sha1Mode,
nameMatchingMode,
- netscapeStepUpPolicy);
+ netscapeStepUpPolicy,
+ ctMode);
}
// Enable the TLS versions given in the prefs, defaulting to TLS 1.0 (min) and
// TLS 1.2 (max) when the prefs aren't set or set to invalid values.
nsresult
nsNSSComponent::setEnabledTLSVersions()
{
// keep these values in sync with security-prefs.js
@@ -1977,16 +1997,17 @@ nsNSSComponent::Observe(nsISupports* aSu
} else if (prefName.Equals("security.ssl.disable_session_identifiers")) {
ConfigureTLSSessionIdentifiers();
} else if (prefName.EqualsLiteral("security.OCSP.enabled") ||
prefName.EqualsLiteral("security.OCSP.require") ||
prefName.EqualsLiteral("security.OCSP.GET.enabled") ||
prefName.EqualsLiteral("security.pki.cert_short_lifetime_in_days") ||
prefName.EqualsLiteral("security.ssl.enable_ocsp_stapling") ||
prefName.EqualsLiteral("security.ssl.enable_ocsp_must_staple") ||
+ prefName.EqualsLiteral("security.pki.certificate_transparency.mode") ||
prefName.EqualsLiteral("security.cert_pinning.enforcement_level") ||
prefName.EqualsLiteral("security.pki.sha1_enforcement_level") ||
prefName.EqualsLiteral("security.pki.name_matching_mode") ||
prefName.EqualsLiteral("security.pki.netscape_step_up_policy")) {
MutexAutoLock lock(mutex);
setValidationOptions(false, lock);
#ifdef DEBUG
} else if (prefName.EqualsLiteral("security.test.built_in_root_hash")) {
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -438,20 +438,25 @@ nsNSSSocketInfo::IsAcceptableForHost(con
// is trying to join onto this connection.
RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
if (!certVerifier) {
return NS_OK;
}
nsAutoCString hostnameFlat(PromiseFlatCString(hostname));
CertVerifier::Flags flags = CertVerifier::FLAG_LOCAL_ONLY;
UniqueCERTCertList unusedBuiltChain;
- SECStatus rv = certVerifier->VerifySSLServerCert(nssCert, nullptr,
+ SECStatus rv = certVerifier->VerifySSLServerCert(nssCert,
+ nullptr, // stapledOCSPResponse
+ nullptr, // sctsFromTLSExtension
mozilla::pkix::Now(),
- nullptr, hostnameFlat.get(),
- unusedBuiltChain, false, flags);
+ nullptr, // pinarg
+ hostnameFlat.get(),
+ unusedBuiltChain,
+ false, // save intermediates
+ flags);
if (rv != SECSuccess) {
return NS_OK;
}
// All tests pass
*_retval = true;
return NS_OK;
}
@@ -2507,16 +2512,22 @@ nsSSLIOLayerSetOptions(PRFileDesc* fd, b
return NS_ERROR_FAILURE;
}
bool enabled = infoObject->SharedState().IsOCSPStaplingEnabled();
if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_OCSP_STAPLING, enabled)) {
return NS_ERROR_FAILURE;
}
+ bool sctsEnabled = infoObject->SharedState().IsSignedCertTimestampsEnabled();
+ if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_SIGNED_CERT_TIMESTAMPS,
+ sctsEnabled)) {
+ return NS_ERROR_FAILURE;
+ }
+
if (SECSuccess != SSL_OptionSet(fd, SSL_HANDSHAKE_AS_CLIENT, true)) {
return NS_ERROR_FAILURE;
}
// Set the Peer ID so that SSL proxy connections work properly and to
// separate anonymous and/or private browsing connections.
uint32_t flags = infoObject->GetProviderFlags();
nsAutoCString peerId;
--- a/security/manager/ssl/nsSiteSecurityService.cpp
+++ b/security/manager/ssl/nsSiteSecurityService.cpp
@@ -712,17 +712,19 @@ nsSiteSecurityService::ProcessPKPHeader(
// We don't want this verification to cause any network traffic that would
// block execution. Also, since we don't have access to the original stapled
// OCSP response, we can't enforce this aspect of the TLS Feature extension.
// This is ok, because it will have been enforced when we originally connected
// to the site (or it's disabled, in which case we wouldn't want to enforce it
// anyway).
CertVerifier::Flags flags = CertVerifier::FLAG_LOCAL_ONLY |
CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
- if (certVerifier->VerifySSLServerCert(nssCert, nullptr, // stapled ocsp
+ if (certVerifier->VerifySSLServerCert(nssCert,
+ nullptr, // stapledOCSPResponse
+ nullptr, // sctsFromTLSExtension
now, nullptr, // pinarg
host.get(), // hostname
certList,
false, // don't store intermediates
flags)
!= SECSuccess) {
return NS_ERROR_FAILURE;
}
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8065,16 +8065,43 @@
"SSL_PERMANENT_CERT_ERROR_OVERRIDES": {
"alert_emails": ["seceng@mozilla.org"],
"expires_in_version": "default",
"kind": "exponential",
"high": 1024,
"n_buckets": 10,
"description": "How many permanent certificate overrides a user has stored."
},
+ "SSL_SCTS_ORIGIN": {
+ "alert_emails": ["seceng-telemetry@mozilla.com"],
+ "expires_in_version": "never",
+ "kind": "enumerated",
+ "n_values": 10,
+ "bug_numbers": [1293231],
+ "releaseChannelCollection": "opt-out",
+ "description": "Origin of Signed Certificate Timestamps received (1=Embedded, 2=TLS handshake extension, 3=Stapled OCSP response)"
+ },
+ "SSL_SCTS_PER_CONNECTION": {
+ "alert_emails": ["seceng-telemetry@mozilla.com"],
+ "expires_in_version": "never",
+ "kind": "enumerated",
+ "n_values": 10,
+ "bug_numbers": [1293231],
+ "releaseChannelCollection": "opt-out",
+ "description": "Histogram of Signed Certificate Timestamps per SSL connection, from all sources (embedded / OCSP Stapling / TLS handshake). Bucket 0 counts the cases when no SCTs were received, or none were extracted due to parsing errors."
+ },
+ "SSL_SCTS_VERIFICATION_STATUS": {
+ "alert_emails": ["seceng-telemetry@mozilla.com"],
+ "expires_in_version": "never",
+ "kind": "enumerated",
+ "n_values": 10,
+ "bug_numbers": [1293231],
+ "releaseChannelCollection": "opt-out",
+ "description": "Verification status of Signed Certificate Timestamps received (0=Decoding error, 1=SCT verified, 2=SCT from unknown log, 3=Invalid SCT signature, 4=SCT timestamp is in the future)"
+ },
"SSL_SERVER_AUTH_EKU": {
"alert_emails": ["seceng-telemetry@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 10,
"description": "Presence of of the Server Authenticaton EKU in accepted SSL server certificates (0=No EKU, 1=EKU present and has id_kp_serverAuth, 2=EKU present and has id_kp_serverAuth as well as some other EKU, 3=EKU present but does not contain id_kp_serverAuth)"
},
"TELEMETRY_TEST_EXPIRED": {