Bug 1293231 - Certificate Transparency - basic telemetry reports; r=Cykesiopka,keeler

MozReview-Commit-ID: EGvuZADObJo

--HG--
extra : rebase_source : 9a059c9f8e2fdf9bfc693b0b5649808b1beeb67b
This commit is contained in:
Sergei Chernov 2016-08-11 13:41:50 +03:00
Родитель 8cc8c5166b
Коммит 976d5c3f1d
24 изменённых файлов: 797 добавлений и 134 удалений

1
config/external/nss/nss.symbols поставляемый
Просмотреть файл

@ -679,6 +679,7 @@ SSL_OptionSet
SSL_OptionSetDefault
SSL_PeerCertificate
SSL_PeerCertificateChain
SSL_PeerSignedCertTimestamps
SSL_PeerStapledOCSPResponses
SSL_ResetHandshake
SSL_SendAdditionalKeyShares

Просмотреть файл

@ -86,6 +86,11 @@ pref("security.pki.netscape_step_up_policy", 1);
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);

Просмотреть файл

@ -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

Просмотреть файл

@ -509,6 +509,10 @@ DecodeSignedCertificateTimestamp(Reader& reader,
}
result.timestamp = timestamp;
result.origin = SignedCertificateTimestamp::Origin::Unknown;
result.verificationStatus =
SignedCertificateTimestamp::VerificationStatus::None;
output = Move(result);
return Success;
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -9,10 +9,13 @@
#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"
@ -24,6 +27,7 @@
#include "secmod.h"
#include "sslerr.h"
using namespace mozilla::ct;
using namespace mozilla::pkix;
using namespace mozilla::psm;
@ -42,7 +46,8 @@ CertVerifier::CertVerifier(OcspDownloadConfig odc,
PinningMode pinningMode,
SHA1Mode sha1Mode,
BRNameMatchingPolicy::Mode nameMatchingMode,
NetscapeStepUpPolicy netscapeStepUpPolicy)
NetscapeStepUpPolicy netscapeStepUpPolicy,
CertificateTransparencyMode ctMode)
: mOCSPDownloadConfig(odc)
, mOCSPStrict(osc == ocspStrict)
, mOCSPGETEnabled(ogc == ocspGetEnabled)
@ -51,7 +56,9 @@ CertVerifier::CertVerifier(OcspDownloadConfig odc,
, mSHA1Mode(sha1Mode)
, mNameMatchingMode(nameMatchingMode)
, mNetscapeStepUpPolicy(netscapeStepUpPolicy)
, mCTMode(ctMode)
{
LoadKnownCTLogs();
}
CertVerifier::~CertVerifier()
@ -121,17 +128,17 @@ BuildCertChainForOneKeyUsage(NSSCertDBTrustDomain& trustDomain, Input certDER,
/*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);
@ -146,6 +153,152 @@ BuildCertChainForOneKeyUsage(NSSCertDBTrustDomain& trustDomain, Input certDER,
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)
{
@ -175,11 +328,13 @@ CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
/*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"));
@ -255,6 +410,15 @@ CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
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
@ -368,6 +532,12 @@ CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
if (sha1ModeResult) {
*sha1ModeResult = sha1ModeResults[i];
}
rv = VerifySignedCertificateTimestamps(trustDomain, builtChain,
sctsFromTLSInput, time,
ctInfo);
if (rv != Success) {
break;
}
}
}
if (rv == Success) {
@ -449,6 +619,12 @@ CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
if (sha1ModeResult) {
*sha1ModeResult = sha1ModeResults[j];
}
rv = VerifySignedCertificateTimestamps(trustDomain, builtChain,
sctsFromTLSInput, time,
ctInfo);
if (rv != Success) {
break;
}
}
}
}
@ -631,6 +807,7 @@ CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
SECStatus
CertVerifier::VerifySSLServerCert(const UniqueCERTCertificate& peerCert,
/*optional*/ const SECItem* stapledOCSPResponse,
/*optional*/ const SECItem* sctsFromTLS,
Time time,
/*optional*/ void* pinarg,
const char* hostname,
@ -641,7 +818,8 @@ CertVerifier::VerifySSLServerCert(const UniqueCERTCertificate& peerCert,
/*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)
@ -661,9 +839,10 @@ CertVerifier::VerifySSLServerCert(const UniqueCERTCertificate& peerCert,
// 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;
}

Просмотреть файл

@ -8,11 +8,22 @@
#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.
@ -49,6 +60,21 @@ public:
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:
@ -79,15 +105,18 @@ public:
/*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,
@ -98,7 +127,8 @@ public:
/*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,
@ -126,11 +156,17 @@ public:
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(); }
@ -143,10 +179,23 @@ public:
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

Просмотреть файл

@ -8,55 +8,26 @@
#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)
{
@ -77,7 +48,7 @@ MultiLogCTVerifier::Verify(Input cert,
Input sctListFromCert,
Input sctListFromOCSPResponse,
Input sctListFromTLSExtension,
uint64_t time,
Time time,
CTVerifyResult& result)
{
MOZ_ASSERT(cert.GetLength() > 0);
@ -133,7 +104,7 @@ Result
MultiLogCTVerifier::VerifySCTs(Input encodedSctList,
const LogEntry& expectedEntry,
SignedCertificateTimestamp::Origin origin,
uint64_t time,
Time time,
CTVerifyResult& result)
{
Reader listReader;
@ -171,7 +142,7 @@ MultiLogCTVerifier::VerifySCTs(Input encodedSctList,
Result
MultiLogCTVerifier::VerifySingleSCT(SignedCertificateTimestamp&& sct,
const LogEntry& expectedEntry,
uint64_t time,
Time time,
CTVerifyResult& result)
{
CTLogVerifier* matchingLog = nullptr;
@ -184,28 +155,39 @@ MultiLogCTVerifier::VerifySingleSCT(SignedCertificateTimestamp&& sct,
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

Просмотреть файл

@ -8,45 +8,15 @@
#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
@ -80,9 +50,6 @@ public:
// |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,
@ -90,7 +57,7 @@ public:
pkix::Input sctListFromCert,
pkix::Input sctListFromOCSPResponse,
pkix::Input sctListFromTLSExtension,
uint64_t time,
pkix::Time time,
CTVerifyResult& result);
private:
@ -100,14 +67,14 @@ private:
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.

Просмотреть файл

@ -15,6 +15,7 @@
#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"
@ -73,6 +74,8 @@ NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
, mHostname(hostname)
, mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID))
, mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED)
, mSCTListFromCertificate()
, mSCTListFromOCSPStapling()
{
}
@ -963,9 +966,59 @@ NSSCertDBTrustDomain::NetscapeStepUpMatchesServerAuth(Time notBefore,
}
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

Просмотреть файл

@ -145,14 +145,22 @@ public:
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 {
@ -180,6 +188,9 @@ private:
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

Просмотреть файл

@ -78,15 +78,6 @@ struct SignedCertificateTimestamp
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,
@ -94,7 +85,32 @@ struct SignedCertificateTimestamp
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;
};

Просмотреть файл

@ -7,7 +7,10 @@
EXPORTS += [
'BRNameMatchingPolicy.h',
'CertVerifier.h',
'CTVerifyResult.h',
'OCSPCache.h',
'SignedCertificateTimestamp.h',
'SignedTreeHead.h',
]
UNIFIED_SOURCES += [
@ -16,6 +19,7 @@ UNIFIED_SOURCES += [
'CTLogVerifier.cpp',
'CTObjectsExtractor.cpp',
'CTSerialization.cpp',
'CTVerifyResult.cpp',
'MultiLogCTVerifier.cpp',
'NSSCertDBTrustDomain.cpp',
'OCSPCache.cpp',

Просмотреть файл

@ -24,6 +24,10 @@ using namespace mozilla::pkix;
class MultiLogCTVerifierTest : public ::testing::Test
{
public:
MultiLogCTVerifierTest()
: mNow(Time::uninitialized)
{}
void SetUp() override
{
// Does nothing if NSS is already initialized.
@ -39,17 +43,17 @@ public:
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|.
@ -93,7 +97,7 @@ protected:
Buffer mCaCertSPKI;
Buffer mIntermediateCert;
Buffer mIntermediateCertSPKI;
uint64_t mNow;
Time mNow;
};
// Test that an embedded SCT can be extracted and the extracted SCT contains
@ -196,7 +200,9 @@ TEST_F(MultiLogCTVerifierTest, VerifiesSCTFromMultipleSources)
// 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(
@ -218,8 +224,10 @@ TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromUnknownLog)
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

Просмотреть файл

@ -739,7 +739,8 @@ public:
nsNSSSocketInfo* infoObject,
const UniqueCERTCertificate& serverCert,
const UniqueCERTCertList& peerCertChain,
SECItem* stapledOCSPResponse,
const SECItem* stapledOCSPResponse,
const SECItem* sctsFromTLSExtension,
uint32_t providerFlags,
Time time,
PRTime prtime);
@ -752,7 +753,8 @@ private:
nsNSSSocketInfo* infoObject,
const UniqueCERTCertificate& cert,
UniqueCERTCertList peerCertChain,
SECItem* stapledOCSPResponse,
const SECItem* stapledOCSPResponse,
const SECItem* sctsFromTLSExtension,
uint32_t providerFlags,
Time time,
PRTime prtime);
@ -766,12 +768,14 @@ private:
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)
@ -783,6 +787,7 @@ SSLServerCertVerificationJob::SSLServerCertVerificationJob(
, mPRTime(prtime)
, mJobStartTime(TimeStamp::Now())
, mStapledOCSPResponse(SECITEM_DupItem(stapledOCSPResponse))
, mSCTsFromTLSExtension(SECITEM_DupItem(sctsFromTLSExtension))
{
}
@ -1214,13 +1219,88 @@ GatherSuccessfulValidationTelemetry(const UniqueCERTCertList& 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)
{
@ -1241,6 +1321,7 @@ AuthCertificate(CertVerifier& certVerifier,
KeySizeStatus keySizeStatus = KeySizeStatus::NeverChecked;
SHA1ModeResult sha1ModeResult = SHA1ModeResult::NeverChecked;
PinningTelemetryInfo pinningTelemetryInfo;
CertificateTransparencyInfo certificateTransparencyInfo;
int flags = 0;
if (!infoObject->SharedState().IsOCSPStaplingEnabled() ||
@ -1249,12 +1330,13 @@ AuthCertificate(CertVerifier& certVerifier,
}
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();
@ -1305,6 +1387,8 @@ AuthCertificate(CertVerifier& certVerifier,
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
@ -1357,7 +1441,8 @@ SSLServerCertVerificationJob::Dispatch(
nsNSSSocketInfo* infoObject,
const UniqueCERTCertificate& serverCert,
const UniqueCERTCertList& peerCertChain,
SECItem* stapledOCSPResponse,
const SECItem* stapledOCSPResponse,
const SECItem* sctsFromTLSExtension,
uint32_t providerFlags,
Time time,
PRTime prtime)
@ -1384,8 +1469,8 @@ SSLServerCertVerificationJob::Dispatch(
RefPtr<SSLServerCertVerificationJob> job(
new SSLServerCertVerificationJob(certVerifier, fdForLogging, infoObject,
serverCert, Move(peerCertChainCopy),
stapledOCSPResponse, providerFlags,
time, prtime));
stapledOCSPResponse, sctsFromTLSExtension,
providerFlags, time, prtime));
nsresult nrv;
if (!gCertVerificationThreadPool) {
@ -1436,6 +1521,7 @@ SSLServerCertVerificationJob::Run()
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");
@ -1586,6 +1672,14 @@ AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig, PRBool isServer)
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);
@ -1599,7 +1693,7 @@ AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig, PRBool isServer)
SECStatus rv = SSLServerCertVerificationJob::Dispatch(
certVerifier, static_cast<const void*>(fd), socketInfo,
serverCert, peerCertChain, stapledOCSPResponse,
providerFlags, now, prnow);
sctsFromTLSExtension, providerFlags, now, prnow);
return rv;
}
@ -1610,7 +1704,7 @@ AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig, PRBool isServer)
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) {

Просмотреть файл

@ -22,10 +22,11 @@ public:
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)
{
}
};

Просмотреть файл

@ -43,6 +43,10 @@ public:
{
mOCSPMustStapleEnabled = mustStapleEnabled;
}
void SetSignedCertTimestampsEnabled(bool signedCertTimestampsEnabled)
{
mSignedCertTimestampsEnabled = signedCertTimestampsEnabled;
}
// The following methods may be called from any thread
bool SocketCreated();
@ -50,6 +54,10 @@ public:
static void NoteCertOverrideServiceInstantiated();
bool IsOCSPStaplingEnabled() const { return mOCSPStaplingEnabled; }
bool IsOCSPMustStapleEnabled() const { return mOCSPMustStapleEnabled; }
bool IsSignedCertTimestampsEnabled() const
{
return mSignedCertTimestampsEnabled;
}
private:
~SharedSSLState();
@ -67,6 +75,7 @@ private:
bool mSocketCreated;
bool mOCSPStaplingEnabled;
bool mOCSPMustStapleEnabled;
bool mSignedCertTimestampsEnabled;
};
SharedSSLState* PublicSSLState();

Просмотреть файл

@ -1317,7 +1317,10 @@ nsNSSCertificate::hasValidEVOidTag(SECOidTag& resultOidTag, bool& validEV)
nullptr /* XXX pinarg */,
nullptr /* hostname */,
unusedBuiltChain,
flags, nullptr /* stapledOCSPResponse */, &resultOidTag);
flags,
nullptr /* stapledOCSPResponse */,
nullptr /* sctsFromTLSExtension */,
&resultOidTag);
if (rv != SECSuccess) {
resultOidTag = SEC_OID_UNKNOWN;

Просмотреть файл

@ -1493,6 +1493,7 @@ VerifyCertAtTime(nsIX509Cert* aCert,
if (aHostname && aUsage == certificateUsageSSLServer) {
srv = certVerifier->VerifySSLServerCert(nssCert,
nullptr, // stapledOCSPResponse
nullptr, // sctsFromTLSExtension
aTime,
nullptr, // Assume no context
aHostname,
@ -1507,6 +1508,7 @@ VerifyCertAtTime(nsIX509Cert* aCert,
resultChain,
aFlags,
nullptr, // stapledOCSPResponse
nullptr, // sctsFromTLSExtension
&evOidPolicy);
}

Просмотреть файл

@ -1502,6 +1502,25 @@ void nsNSSComponent::setValidationOptions(bool isInitialSetting,
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",
@ -1571,7 +1590,8 @@ void nsNSSComponent::setValidationOptions(bool isInitialSetting,
certShortLifetimeInDays,
pinningMode, sha1Mode,
nameMatchingMode,
netscapeStepUpPolicy);
netscapeStepUpPolicy,
ctMode);
}
// Enable the TLS versions given in the prefs, defaulting to TLS 1.0 (min) and
@ -1982,6 +2002,7 @@ nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic,
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") ||

Просмотреть файл

@ -443,10 +443,15 @@ nsNSSSocketInfo::IsAcceptableForHost(const nsACString& hostname, bool* _retval)
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;
}
@ -2512,6 +2517,12 @@ nsSSLIOLayerSetOptions(PRFileDesc* fd, bool forSTARTTLS,
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;
}

Просмотреть файл

@ -717,7 +717,9 @@ nsSiteSecurityService::ProcessPKPHeader(nsIURI* aSourceURI,
// 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,

Просмотреть файл

@ -8070,6 +8070,33 @@
"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",