diff options
Diffstat (limited to 'security/ct/MultiLogCTVerifier.cpp')
-rw-r--r-- | security/ct/MultiLogCTVerifier.cpp | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/security/ct/MultiLogCTVerifier.cpp b/security/ct/MultiLogCTVerifier.cpp new file mode 100644 index 0000000000..78f7abb7a5 --- /dev/null +++ b/security/ct/MultiLogCTVerifier.cpp @@ -0,0 +1,194 @@ +/* -*- 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" + +namespace mozilla { +namespace ct { + +using namespace mozilla::pkix; + +// Note: this moves |verifiedSct| to the target list in |result|. +static void StoreVerifiedSct(CTVerifyResult& result, VerifiedSCT&& verifiedSct, + VerifiedSCT::Status status) { + verifiedSct.status = status; + result.verifiedScts.push_back(std::move(verifiedSct)); +} + +void MultiLogCTVerifier::AddLog(CTLogVerifier&& log) { + mLogs.push_back(std::move(log)); +} + +Result MultiLogCTVerifier::Verify(Input cert, Input issuerSubjectPublicKeyInfo, + Input sctListFromCert, + Input sctListFromOCSPResponse, + Input sctListFromTLSExtension, Time time, + CTVerifyResult& result) { + 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, + VerifiedSCT::Origin::Embedded, time, result); + if (rv != Success) { + return rv; + } + } + + LogEntry x509Entry; + GetX509LogEntry(cert, x509Entry); + + // Verify SCTs from a stapled OCSP response + if (sctListFromOCSPResponse.GetLength() > 0) { + rv = VerifySCTs(sctListFromOCSPResponse, x509Entry, + VerifiedSCT::Origin::OCSPResponse, time, result); + if (rv != Success) { + return rv; + } + } + + // Verify SCTs from a TLS extension + if (sctListFromTLSExtension.GetLength() > 0) { + rv = VerifySCTs(sctListFromTLSExtension, x509Entry, + VerifiedSCT::Origin::TLSExtension, time, result); + if (rv != Success) { + return rv; + } + } + return Success; +} + +void DecodeSCTs(Input encodedSctList, + std::vector<SignedCertificateTimestamp>& decodedSCTs, + size_t& decodingErrors) { + decodedSCTs.clear(); + + Reader listReader; + Result rv = DecodeSCTList(encodedSctList, listReader); + if (rv != Success) { + decodingErrors++; + return; + } + + while (!listReader.AtEnd()) { + Input encodedSct; + rv = ReadSCTListItem(listReader, encodedSct); + if (rv != Success) { + decodingErrors++; + return; + } + + Reader encodedSctReader(encodedSct); + SignedCertificateTimestamp sct; + rv = DecodeSignedCertificateTimestamp(encodedSctReader, sct); + if (rv != Success) { + decodingErrors++; + continue; + } + decodedSCTs.push_back(std::move(sct)); + } +} + +Result MultiLogCTVerifier::VerifySCTs(Input encodedSctList, + const LogEntry& expectedEntry, + VerifiedSCT::Origin origin, Time time, + CTVerifyResult& result) { + std::vector<SignedCertificateTimestamp> decodedSCTs; + DecodeSCTs(encodedSctList, decodedSCTs, result.decodingErrors); + for (auto sct : decodedSCTs) { + Result rv = + VerifySingleSCT(std::move(sct), expectedEntry, origin, time, result); + if (rv != Success) { + return rv; + } + } + return Success; +} + +Result MultiLogCTVerifier::VerifySingleSCT(SignedCertificateTimestamp&& sct, + const LogEntry& expectedEntry, + VerifiedSCT::Origin origin, + Time time, CTVerifyResult& result) { + VerifiedSCT verifiedSct; + verifiedSct.origin = origin; + verifiedSct.sct = std::move(sct); + + CTLogVerifier* matchingLog = nullptr; + for (auto& log : mLogs) { + if (log.keyId() == verifiedSct.sct.logId) { + matchingLog = &log; + break; + } + } + + if (!matchingLog) { + // SCT does not match any known log. + StoreVerifiedSct(result, std::move(verifiedSct), + VerifiedSCT::Status::UnknownLog); + return Success; + } + + verifiedSct.logOperatorId = matchingLog->operatorId(); + + if (!matchingLog->SignatureParametersMatch(verifiedSct.sct.signature)) { + // SCT signature parameters do not match the log's. + StoreVerifiedSct(result, std::move(verifiedSct), + VerifiedSCT::Status::InvalidSignature); + return Success; + } + + Result rv = matchingLog->Verify(expectedEntry, verifiedSct.sct); + if (rv != Success) { + if (rv == Result::ERROR_BAD_SIGNATURE) { + StoreVerifiedSct(result, std::move(verifiedSct), + VerifiedSCT::Status::InvalidSignature); + return Success; + } + return rv; + } + + // Make sure the timestamp is legitimate (not in the future). + // SCT's |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 + // (towards the future) is more "secure", although practically + // it does not matter. + Time sctTime = + TimeFromEpochInSeconds((verifiedSct.sct.timestamp + 999u) / 1000u); + if (sctTime > time) { + StoreVerifiedSct(result, std::move(verifiedSct), + VerifiedSCT::Status::InvalidTimestamp); + return Success; + } + + // 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(); + StoreVerifiedSct(result, std::move(verifiedSct), + VerifiedSCT::Status::ValidFromDisqualifiedLog); + return Success; + } + + StoreVerifiedSct(result, std::move(verifiedSct), VerifiedSCT::Status::Valid); + return Success; +} + +} // namespace ct +} // namespace mozilla |