2016-07-05 08:35:06 +03:00
|
|
|
/* -*- 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 "MultiLogCTVerifier.h"
|
|
|
|
|
|
|
|
#include "CTObjectsExtractor.h"
|
|
|
|
#include "CTSerialization.h"
|
2016-08-11 13:41:50 +03:00
|
|
|
#include "mozilla/Assertions.h"
|
2016-07-05 08:35:06 +03:00
|
|
|
#include "mozilla/Move.h"
|
|
|
|
|
|
|
|
namespace mozilla { namespace ct {
|
|
|
|
|
|
|
|
using namespace mozilla::pkix;
|
|
|
|
|
2016-11-23 16:37:31 +03:00
|
|
|
// Note: this moves |verifiedSct| to the target list in |result|.
|
2016-07-05 08:35:06 +03:00
|
|
|
static Result
|
|
|
|
StoreVerifiedSct(CTVerifyResult& result,
|
2016-11-23 16:37:31 +03:00
|
|
|
VerifiedSCT&& verifiedSct,
|
|
|
|
VerifiedSCT::Status status)
|
2016-07-05 08:35:06 +03:00
|
|
|
{
|
2016-11-23 16:37:31 +03:00
|
|
|
verifiedSct.status = status;
|
|
|
|
if (!result.verifiedScts.append(Move(verifiedSct))) {
|
2016-07-05 08:35:06 +03:00
|
|
|
return Result::FATAL_ERROR_NO_MEMORY;
|
|
|
|
}
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
|
|
|
Result
|
2016-11-23 16:37:31 +03:00
|
|
|
MultiLogCTVerifier::AddLog(CTLogVerifier&& log)
|
2016-07-05 08:35:06 +03:00
|
|
|
{
|
|
|
|
if (!mLogs.append(Move(log))) {
|
|
|
|
return Result::FATAL_ERROR_NO_MEMORY;
|
|
|
|
}
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
|
|
|
Result
|
|
|
|
MultiLogCTVerifier::Verify(Input cert,
|
|
|
|
Input issuerSubjectPublicKeyInfo,
|
|
|
|
Input sctListFromCert,
|
|
|
|
Input sctListFromOCSPResponse,
|
|
|
|
Input sctListFromTLSExtension,
|
2016-08-11 13:41:50 +03:00
|
|
|
Time time,
|
2016-07-05 08:35:06 +03:00
|
|
|
CTVerifyResult& result)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(cert.GetLength() > 0);
|
|
|
|
result.Reset();
|
|
|
|
|
|
|
|
Result rv;
|
|
|
|
|
|
|
|
// Verify embedded SCTs
|
|
|
|
if (issuerSubjectPublicKeyInfo.GetLength() > 0 &&
|
|
|
|
sctListFromCert.GetLength() > 0) {
|
|
|
|
LogEntry precertEntry;
|
|
|
|
rv = GetPrecertLogEntry(cert, issuerSubjectPublicKeyInfo, precertEntry);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
rv = VerifySCTs(sctListFromCert, precertEntry,
|
2016-11-23 16:37:31 +03:00
|
|
|
VerifiedSCT::Origin::Embedded, time, result);
|
2016-07-05 08:35:06 +03:00
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
LogEntry x509Entry;
|
|
|
|
rv = GetX509LogEntry(cert, x509Entry);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify SCTs from a stapled OCSP response
|
|
|
|
if (sctListFromOCSPResponse.GetLength() > 0) {
|
|
|
|
rv = VerifySCTs(sctListFromOCSPResponse, x509Entry,
|
2016-11-23 16:37:31 +03:00
|
|
|
VerifiedSCT::Origin::OCSPResponse, time, result);
|
2016-07-05 08:35:06 +03:00
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify SCTs from a TLS extension
|
|
|
|
if (sctListFromTLSExtension.GetLength() > 0) {
|
|
|
|
rv = VerifySCTs(sctListFromTLSExtension, x509Entry,
|
2016-11-23 16:37:31 +03:00
|
|
|
VerifiedSCT::Origin::TLSExtension, time, result);
|
2016-07-05 08:35:06 +03:00
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
|
|
|
Result
|
|
|
|
MultiLogCTVerifier::VerifySCTs(Input encodedSctList,
|
|
|
|
const LogEntry& expectedEntry,
|
2016-11-23 16:37:31 +03:00
|
|
|
VerifiedSCT::Origin origin,
|
2016-08-11 13:41:50 +03:00
|
|
|
Time time,
|
2016-07-05 08:35:06 +03:00
|
|
|
CTVerifyResult& result)
|
|
|
|
{
|
|
|
|
Reader listReader;
|
|
|
|
Result rv = DecodeSCTList(encodedSctList, listReader);
|
|
|
|
if (rv != Success) {
|
|
|
|
result.decodingErrors++;
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!listReader.AtEnd()) {
|
|
|
|
Input encodedSct;
|
|
|
|
rv = ReadSCTListItem(listReader, encodedSct);
|
|
|
|
if (rv != Success) {
|
|
|
|
result.decodingErrors++;
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
|
|
|
Reader encodedSctReader(encodedSct);
|
|
|
|
SignedCertificateTimestamp sct;
|
|
|
|
rv = DecodeSignedCertificateTimestamp(encodedSctReader, sct);
|
|
|
|
if (rv != Success) {
|
|
|
|
result.decodingErrors++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-11-23 16:37:31 +03:00
|
|
|
rv = VerifySingleSCT(Move(sct), expectedEntry, origin, time, result);
|
2016-07-05 08:35:06 +03:00
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
|
|
|
Result
|
|
|
|
MultiLogCTVerifier::VerifySingleSCT(SignedCertificateTimestamp&& sct,
|
|
|
|
const LogEntry& expectedEntry,
|
2016-11-23 16:37:31 +03:00
|
|
|
VerifiedSCT::Origin origin,
|
2016-08-11 13:41:50 +03:00
|
|
|
Time time,
|
2016-07-05 08:35:06 +03:00
|
|
|
CTVerifyResult& result)
|
|
|
|
{
|
2016-11-23 16:37:31 +03:00
|
|
|
VerifiedSCT verifiedSct;
|
|
|
|
verifiedSct.origin = origin;
|
|
|
|
verifiedSct.sct = Move(sct);
|
|
|
|
|
2016-07-05 08:35:06 +03:00
|
|
|
CTLogVerifier* matchingLog = nullptr;
|
|
|
|
for (auto& log : mLogs) {
|
2016-11-23 16:37:31 +03:00
|
|
|
if (log.keyId() == verifiedSct.sct.logId) {
|
2016-07-05 08:35:06 +03:00
|
|
|
matchingLog = &log;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!matchingLog) {
|
|
|
|
// SCT does not match any known log.
|
2016-11-23 16:37:31 +03:00
|
|
|
return StoreVerifiedSct(result, Move(verifiedSct),
|
|
|
|
VerifiedSCT::Status::UnknownLog);
|
2016-07-05 08:35:06 +03:00
|
|
|
}
|
|
|
|
|
2016-11-29 23:51:46 +03:00
|
|
|
verifiedSct.logOperatorId = matchingLog->operatorId();
|
|
|
|
|
2016-11-23 16:37:31 +03:00
|
|
|
if (!matchingLog->SignatureParametersMatch(verifiedSct.sct.signature)) {
|
2016-07-05 08:35:06 +03:00
|
|
|
// SCT signature parameters do not match the log's.
|
2016-11-23 16:37:31 +03:00
|
|
|
return StoreVerifiedSct(result, Move(verifiedSct),
|
|
|
|
VerifiedSCT::Status::InvalidSignature);
|
2016-07-05 08:35:06 +03:00
|
|
|
}
|
|
|
|
|
2016-11-23 16:37:31 +03:00
|
|
|
Result rv = matchingLog->Verify(expectedEntry, verifiedSct.sct);
|
2016-07-05 08:35:06 +03:00
|
|
|
if (rv != Success) {
|
|
|
|
if (rv == Result::ERROR_BAD_SIGNATURE) {
|
2016-11-23 16:37:31 +03:00
|
|
|
return StoreVerifiedSct(result, Move(verifiedSct),
|
|
|
|
VerifiedSCT::Status::InvalidSignature);
|
2016-07-05 08:35:06 +03:00
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2016-11-23 16:37:31 +03:00
|
|
|
// Make sure the timestamp is legitimate (not in the future).
|
|
|
|
// SCT's |timestamp| is measured in milliseconds since the epoch,
|
2016-08-11 13:41:50 +03:00
|
|
|
// 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
|
2016-11-23 16:37:31 +03:00
|
|
|
// (towards the future) is more "secure", although practically
|
|
|
|
// it does not matter.
|
|
|
|
Time sctTime =
|
|
|
|
TimeFromEpochInSeconds((verifiedSct.sct.timestamp + 999u) / 1000u);
|
2016-08-11 13:41:50 +03:00
|
|
|
if (sctTime > time) {
|
2016-11-23 16:37:31 +03:00
|
|
|
return StoreVerifiedSct(result, Move(verifiedSct),
|
|
|
|
VerifiedSCT::Status::InvalidTimestamp);
|
2016-07-05 08:35:06 +03:00
|
|
|
}
|
|
|
|
|
2016-11-29 23:51:46 +03:00
|
|
|
// SCT verified ok, see if the log is qualified. Since SCTs from
|
|
|
|
// disqualified logs are treated as valid under certain circumstances (see
|
|
|
|
// the CT Policy), the log qualification check must be the last one we do.
|
|
|
|
if (matchingLog->isDisqualified()) {
|
|
|
|
verifiedSct.logDisqualificationTime = matchingLog->disqualificationTime();
|
|
|
|
return StoreVerifiedSct(result, Move(verifiedSct),
|
|
|
|
VerifiedSCT::Status::ValidFromDisqualifiedLog);
|
|
|
|
}
|
|
|
|
|
2016-11-23 16:37:31 +03:00
|
|
|
return StoreVerifiedSct(result, Move(verifiedSct),
|
|
|
|
VerifiedSCT::Status::Valid);
|
2016-07-05 08:35:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
} } // namespace mozilla::ct
|