/src/mozilla-central/security/certverifier/MultiLogCTVerifier.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "MultiLogCTVerifier.h" |
8 | | |
9 | | #include "CTObjectsExtractor.h" |
10 | | #include "CTSerialization.h" |
11 | | #include "mozilla/Assertions.h" |
12 | | #include "mozilla/Move.h" |
13 | | |
14 | | namespace mozilla { namespace ct { |
15 | | |
16 | | using namespace mozilla::pkix; |
17 | | |
18 | | // Note: this moves |verifiedSct| to the target list in |result|. |
19 | | static Result |
20 | | StoreVerifiedSct(CTVerifyResult& result, |
21 | | VerifiedSCT&& verifiedSct, |
22 | | VerifiedSCT::Status status) |
23 | 0 | { |
24 | 0 | verifiedSct.status = status; |
25 | 0 | if (!result.verifiedScts.append(std::move(verifiedSct))) { |
26 | 0 | return Result::FATAL_ERROR_NO_MEMORY; |
27 | 0 | } |
28 | 0 | return Success; |
29 | 0 | } |
30 | | |
31 | | Result |
32 | | MultiLogCTVerifier::AddLog(CTLogVerifier&& log) |
33 | 0 | { |
34 | 0 | if (!mLogs.append(std::move(log))) { |
35 | 0 | return Result::FATAL_ERROR_NO_MEMORY; |
36 | 0 | } |
37 | 0 | return Success; |
38 | 0 | } |
39 | | |
40 | | Result |
41 | | MultiLogCTVerifier::Verify(Input cert, |
42 | | Input issuerSubjectPublicKeyInfo, |
43 | | Input sctListFromCert, |
44 | | Input sctListFromOCSPResponse, |
45 | | Input sctListFromTLSExtension, |
46 | | Time time, |
47 | | CTVerifyResult& result) |
48 | 0 | { |
49 | 0 | MOZ_ASSERT(cert.GetLength() > 0); |
50 | 0 | result.Reset(); |
51 | 0 |
|
52 | 0 | Result rv; |
53 | 0 |
|
54 | 0 | // Verify embedded SCTs |
55 | 0 | if (issuerSubjectPublicKeyInfo.GetLength() > 0 && |
56 | 0 | sctListFromCert.GetLength() > 0) { |
57 | 0 | LogEntry precertEntry; |
58 | 0 | rv = GetPrecertLogEntry(cert, issuerSubjectPublicKeyInfo, precertEntry); |
59 | 0 | if (rv != Success) { |
60 | 0 | return rv; |
61 | 0 | } |
62 | 0 | rv = VerifySCTs(sctListFromCert, precertEntry, |
63 | 0 | VerifiedSCT::Origin::Embedded, time, result); |
64 | 0 | if (rv != Success) { |
65 | 0 | return rv; |
66 | 0 | } |
67 | 0 | } |
68 | 0 | |
69 | 0 | LogEntry x509Entry; |
70 | 0 | rv = GetX509LogEntry(cert, x509Entry); |
71 | 0 | if (rv != Success) { |
72 | 0 | return rv; |
73 | 0 | } |
74 | 0 | |
75 | 0 | // Verify SCTs from a stapled OCSP response |
76 | 0 | if (sctListFromOCSPResponse.GetLength() > 0) { |
77 | 0 | rv = VerifySCTs(sctListFromOCSPResponse, x509Entry, |
78 | 0 | VerifiedSCT::Origin::OCSPResponse, time, result); |
79 | 0 | if (rv != Success) { |
80 | 0 | return rv; |
81 | 0 | } |
82 | 0 | } |
83 | 0 | |
84 | 0 | // Verify SCTs from a TLS extension |
85 | 0 | if (sctListFromTLSExtension.GetLength() > 0) { |
86 | 0 | rv = VerifySCTs(sctListFromTLSExtension, x509Entry, |
87 | 0 | VerifiedSCT::Origin::TLSExtension, time, result); |
88 | 0 | if (rv != Success) { |
89 | 0 | return rv; |
90 | 0 | } |
91 | 0 | } |
92 | 0 | return Success; |
93 | 0 | } |
94 | | |
95 | | Result |
96 | | MultiLogCTVerifier::VerifySCTs(Input encodedSctList, |
97 | | const LogEntry& expectedEntry, |
98 | | VerifiedSCT::Origin origin, |
99 | | Time time, |
100 | | CTVerifyResult& result) |
101 | 0 | { |
102 | 0 | Reader listReader; |
103 | 0 | Result rv = DecodeSCTList(encodedSctList, listReader); |
104 | 0 | if (rv != Success) { |
105 | 0 | result.decodingErrors++; |
106 | 0 | return Success; |
107 | 0 | } |
108 | 0 | |
109 | 0 | while (!listReader.AtEnd()) { |
110 | 0 | Input encodedSct; |
111 | 0 | rv = ReadSCTListItem(listReader, encodedSct); |
112 | 0 | if (rv != Success) { |
113 | 0 | result.decodingErrors++; |
114 | 0 | return Success; |
115 | 0 | } |
116 | 0 | |
117 | 0 | Reader encodedSctReader(encodedSct); |
118 | 0 | SignedCertificateTimestamp sct; |
119 | 0 | rv = DecodeSignedCertificateTimestamp(encodedSctReader, sct); |
120 | 0 | if (rv != Success) { |
121 | 0 | result.decodingErrors++; |
122 | 0 | continue; |
123 | 0 | } |
124 | 0 | |
125 | 0 | rv = VerifySingleSCT(std::move(sct), expectedEntry, origin, time, result); |
126 | 0 | if (rv != Success) { |
127 | 0 | return rv; |
128 | 0 | } |
129 | 0 | } |
130 | 0 | return Success; |
131 | 0 | } |
132 | | |
133 | | Result |
134 | | MultiLogCTVerifier::VerifySingleSCT(SignedCertificateTimestamp&& sct, |
135 | | const LogEntry& expectedEntry, |
136 | | VerifiedSCT::Origin origin, |
137 | | Time time, |
138 | | CTVerifyResult& result) |
139 | 0 | { |
140 | 0 | VerifiedSCT verifiedSct; |
141 | 0 | verifiedSct.origin = origin; |
142 | 0 | verifiedSct.sct = std::move(sct); |
143 | 0 |
|
144 | 0 | CTLogVerifier* matchingLog = nullptr; |
145 | 0 | for (auto& log : mLogs) { |
146 | 0 | if (log.keyId() == verifiedSct.sct.logId) { |
147 | 0 | matchingLog = &log; |
148 | 0 | break; |
149 | 0 | } |
150 | 0 | } |
151 | 0 |
|
152 | 0 | if (!matchingLog) { |
153 | 0 | // SCT does not match any known log. |
154 | 0 | return StoreVerifiedSct(result, std::move(verifiedSct), |
155 | 0 | VerifiedSCT::Status::UnknownLog); |
156 | 0 | } |
157 | 0 | |
158 | 0 | verifiedSct.logOperatorId = matchingLog->operatorId(); |
159 | 0 |
|
160 | 0 | if (!matchingLog->SignatureParametersMatch(verifiedSct.sct.signature)) { |
161 | 0 | // SCT signature parameters do not match the log's. |
162 | 0 | return StoreVerifiedSct(result, std::move(verifiedSct), |
163 | 0 | VerifiedSCT::Status::InvalidSignature); |
164 | 0 | } |
165 | 0 | |
166 | 0 | Result rv = matchingLog->Verify(expectedEntry, verifiedSct.sct); |
167 | 0 | if (rv != Success) { |
168 | 0 | if (rv == Result::ERROR_BAD_SIGNATURE) { |
169 | 0 | return StoreVerifiedSct(result, std::move(verifiedSct), |
170 | 0 | VerifiedSCT::Status::InvalidSignature); |
171 | 0 | } |
172 | 0 | return rv; |
173 | 0 | } |
174 | 0 | |
175 | 0 | // Make sure the timestamp is legitimate (not in the future). |
176 | 0 | // SCT's |timestamp| is measured in milliseconds since the epoch, |
177 | 0 | // ignoring leap seconds. When converting it to a second-level precision |
178 | 0 | // pkix::Time, we need to round it either up or down. In our case, rounding up |
179 | 0 | // (towards the future) is more "secure", although practically |
180 | 0 | // it does not matter. |
181 | 0 | Time sctTime = |
182 | 0 | TimeFromEpochInSeconds((verifiedSct.sct.timestamp + 999u) / 1000u); |
183 | 0 | if (sctTime > time) { |
184 | 0 | return StoreVerifiedSct(result, std::move(verifiedSct), |
185 | 0 | VerifiedSCT::Status::InvalidTimestamp); |
186 | 0 | } |
187 | 0 | |
188 | 0 | // SCT verified ok, see if the log is qualified. Since SCTs from |
189 | 0 | // disqualified logs are treated as valid under certain circumstances (see |
190 | 0 | // the CT Policy), the log qualification check must be the last one we do. |
191 | 0 | if (matchingLog->isDisqualified()) { |
192 | 0 | verifiedSct.logDisqualificationTime = matchingLog->disqualificationTime(); |
193 | 0 | return StoreVerifiedSct(result, std::move(verifiedSct), |
194 | 0 | VerifiedSCT::Status::ValidFromDisqualifiedLog); |
195 | 0 | } |
196 | 0 | |
197 | 0 | return StoreVerifiedSct(result, std::move(verifiedSct), |
198 | 0 | VerifiedSCT::Status::Valid); |
199 | 0 | } |
200 | | |
201 | | } } // namespace mozilla::ct |