1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/quic/crypto/proof_verifier_chromium.h"
8 #include "base/bind_helpers.h"
9 #include "base/callback_helpers.h"
10 #include "base/compiler_specific.h"
11 #include "base/logging.h"
12 #include "base/stl_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "crypto/signature_verifier.h"
15 #include "net/base/net_errors.h"
16 #include "net/base/net_log.h"
17 #include "net/cert/asn1_util.h"
18 #include "net/cert/cert_status_flags.h"
19 #include "net/cert/cert_verifier.h"
20 #include "net/cert/cert_verify_result.h"
21 #include "net/cert/single_request_cert_verifier.h"
22 #include "net/cert/x509_certificate.h"
23 #include "net/cert/x509_util.h"
24 #include "net/quic/crypto/crypto_protocol.h"
25 #include "net/ssl/ssl_config_service.h"
27 using base::StringPiece
;
28 using base::StringPrintf
;
34 // A Job handles the verification of a single proof. It is owned by the
35 // ProofVerifier. If the verification can not complete synchronously, it
36 // will notify the ProofVerifier upon completion.
37 class ProofVerifierChromium::Job
{
39 Job(ProofVerifierChromium
* proof_verifier
,
40 CertVerifier
* cert_verifier
,
41 const BoundNetLog
& net_log
);
43 // Starts the proof verification. If |PENDING| is returned, then |callback|
44 // will be invoked asynchronously when the verification completes.
45 Status
VerifyProof(const std::string
& hostname
,
46 const std::string
& server_config
,
47 const std::vector
<std::string
>& certs
,
48 const std::string
& signature
,
49 std::string
* error_details
,
50 scoped_ptr
<ProofVerifyDetails
>* verify_details
,
51 ProofVerifierCallback
* callback
);
57 STATE_VERIFY_CERT_COMPLETE
,
60 int DoLoop(int last_io_result
);
61 void OnIOComplete(int result
);
62 int DoVerifyCert(int result
);
63 int DoVerifyCertComplete(int result
);
65 bool VerifySignature(const std::string
& signed_data
,
66 const std::string
& signature
,
67 const std::string
& cert
);
69 // Proof verifier to notify when this jobs completes.
70 ProofVerifierChromium
* proof_verifier_
;
72 // The underlying verifier used for verifying certificates.
73 scoped_ptr
<SingleRequestCertVerifier
> verifier_
;
75 // |hostname| specifies the hostname for which |certs| is a valid chain.
76 std::string hostname_
;
78 scoped_ptr
<ProofVerifierCallback
> callback_
;
79 scoped_ptr
<ProofVerifyDetailsChromium
> verify_details_
;
80 std::string error_details_
;
82 // X509Certificate from a chain of DER encoded certificates.
83 scoped_refptr
<X509Certificate
> cert_
;
89 DISALLOW_COPY_AND_ASSIGN(Job
);
92 ProofVerifierChromium::Job::Job(ProofVerifierChromium
* proof_verifier
,
93 CertVerifier
* cert_verifier
,
94 const BoundNetLog
& net_log
)
95 : proof_verifier_(proof_verifier
),
96 verifier_(new SingleRequestCertVerifier(cert_verifier
)),
97 next_state_(STATE_NONE
),
101 ProofVerifierChromium::Status
ProofVerifierChromium::Job::VerifyProof(
102 const string
& hostname
,
103 const string
& server_config
,
104 const vector
<string
>& certs
,
105 const string
& signature
,
106 std::string
* error_details
,
107 scoped_ptr
<ProofVerifyDetails
>* verify_details
,
108 ProofVerifierCallback
* callback
) {
109 DCHECK(error_details
);
110 DCHECK(verify_details
);
113 callback_
.reset(callback
);
114 error_details
->clear();
116 if (STATE_NONE
!= next_state_
) {
117 *error_details
= "Certificate is already set and VerifyProof has begun";
118 DLOG(DFATAL
) << *error_details
;
122 verify_details_
.reset(new ProofVerifyDetailsChromium
);
125 *error_details
= "Failed to create certificate chain. Certs are empty.";
126 DLOG(WARNING
) << *error_details
;
127 verify_details_
->cert_verify_result
.cert_status
= CERT_STATUS_INVALID
;
128 verify_details
->reset(verify_details_
.release());
132 // Convert certs to X509Certificate.
133 vector
<StringPiece
> cert_pieces(certs
.size());
134 for (unsigned i
= 0; i
< certs
.size(); i
++) {
135 cert_pieces
[i
] = base::StringPiece(certs
[i
]);
137 cert_
= X509Certificate::CreateFromDERCertChain(cert_pieces
);
139 *error_details
= "Failed to create certificate chain";
140 DLOG(WARNING
) << *error_details
;
141 verify_details_
->cert_verify_result
.cert_status
= CERT_STATUS_INVALID
;
142 verify_details
->reset(verify_details_
.release());
146 // We call VerifySignature first to avoid copying of server_config and
148 if (!VerifySignature(server_config
, signature
, certs
[0])) {
149 *error_details
= "Failed to verify signature of server config";
150 DLOG(WARNING
) << *error_details
;
151 verify_details_
->cert_verify_result
.cert_status
= CERT_STATUS_INVALID
;
152 verify_details
->reset(verify_details_
.release());
156 hostname_
= hostname
;
158 next_state_
= STATE_VERIFY_CERT
;
159 switch (DoLoop(OK
)) {
161 verify_details
->reset(verify_details_
.release());
166 *error_details
= error_details_
;
167 verify_details
->reset(verify_details_
.release());
172 int ProofVerifierChromium::Job::DoLoop(int last_result
) {
173 int rv
= last_result
;
175 State state
= next_state_
;
176 next_state_
= STATE_NONE
;
178 case STATE_VERIFY_CERT
:
180 rv
= DoVerifyCert(rv
);
182 case STATE_VERIFY_CERT_COMPLETE
:
183 rv
= DoVerifyCertComplete(rv
);
188 LOG(DFATAL
) << "unexpected state " << state
;
191 } while (rv
!= ERR_IO_PENDING
&& next_state_
!= STATE_NONE
);
195 void ProofVerifierChromium::Job::OnIOComplete(int result
) {
196 int rv
= DoLoop(result
);
197 if (rv
!= ERR_IO_PENDING
) {
198 scoped_ptr
<ProofVerifierCallback
> callback(callback_
.release());
199 // Callback expects ProofVerifyDetails not ProofVerifyDetailsChromium.
200 scoped_ptr
<ProofVerifyDetails
> verify_details(verify_details_
.release());
201 callback
->Run(rv
== OK
, error_details_
, &verify_details
);
202 // Will delete |this|.
203 proof_verifier_
->OnJobComplete(this);
207 int ProofVerifierChromium::Job::DoVerifyCert(int result
) {
208 next_state_
= STATE_VERIFY_CERT_COMPLETE
;
211 return verifier_
->Verify(
215 SSLConfigService::GetCRLSet().get(),
216 &verify_details_
->cert_verify_result
,
217 base::Bind(&ProofVerifierChromium::Job::OnIOComplete
,
218 base::Unretained(this)),
222 int ProofVerifierChromium::Job::DoVerifyCertComplete(int result
) {
225 if (result
<= ERR_FAILED
) {
226 error_details_
= StringPrintf("Failed to verify certificate chain: %s",
227 ErrorToString(result
));
228 DLOG(WARNING
) << error_details_
;
232 // Exit DoLoop and return the result to the caller to VerifyProof.
233 DCHECK_EQ(STATE_NONE
, next_state_
);
237 bool ProofVerifierChromium::Job::VerifySignature(const string
& signed_data
,
238 const string
& signature
,
239 const string
& cert
) {
241 if (!asn1::ExtractSPKIFromDERCert(cert
, &spki
)) {
242 DLOG(WARNING
) << "ExtractSPKIFromDERCert failed";
246 crypto::SignatureVerifier verifier
;
249 X509Certificate::PublicKeyType type
;
250 X509Certificate::GetPublicKeyInfo(cert_
->os_cert_handle(), &size_bits
,
252 if (type
== X509Certificate::kPublicKeyTypeRSA
) {
253 crypto::SignatureVerifier::HashAlgorithm hash_alg
=
254 crypto::SignatureVerifier::SHA256
;
255 crypto::SignatureVerifier::HashAlgorithm mask_hash_alg
= hash_alg
;
256 unsigned int hash_len
= 32; // 32 is the length of a SHA-256 hash.
258 bool ok
= verifier
.VerifyInitRSAPSS(
259 hash_alg
, mask_hash_alg
, hash_len
,
260 reinterpret_cast<const uint8
*>(signature
.data()), signature
.size(),
261 reinterpret_cast<const uint8
*>(spki
.data()), spki
.size());
263 DLOG(WARNING
) << "VerifyInitRSAPSS failed";
266 } else if (type
== X509Certificate::kPublicKeyTypeECDSA
) {
267 // This is the algorithm ID for ECDSA with SHA-256. Parameters are ABSENT.
269 // ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
270 // us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 2 }
272 // When the ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-SHA384, or
273 // ecdsa-with-SHA512 algorithm identifier appears in the algorithm field
274 // as an AlgorithmIdentifier, the encoding MUST omit the parameters
275 // field. That is, the AlgorithmIdentifier SHALL be a SEQUENCE of one
276 // component, the OID ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-
277 // SHA384, or ecdsa-with-SHA512.
278 // See also RFC 5480, Appendix A.
279 static const uint8 kECDSAWithSHA256AlgorithmID
[] = {
282 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02,
285 if (!verifier
.VerifyInit(
286 kECDSAWithSHA256AlgorithmID
, sizeof(kECDSAWithSHA256AlgorithmID
),
287 reinterpret_cast<const uint8
*>(signature
.data()),
289 reinterpret_cast<const uint8
*>(spki
.data()),
291 DLOG(WARNING
) << "VerifyInit failed";
295 LOG(ERROR
) << "Unsupported public key type " << type
;
299 verifier
.VerifyUpdate(reinterpret_cast<const uint8
*>(kProofSignatureLabel
),
300 sizeof(kProofSignatureLabel
));
301 verifier
.VerifyUpdate(reinterpret_cast<const uint8
*>(signed_data
.data()),
304 if (!verifier
.VerifyFinal()) {
305 DLOG(WARNING
) << "VerifyFinal failed";
309 DVLOG(1) << "VerifyFinal success";
313 ProofVerifierChromium::ProofVerifierChromium(CertVerifier
* cert_verifier
)
314 : cert_verifier_(cert_verifier
) {}
316 ProofVerifierChromium::~ProofVerifierChromium() {
317 STLDeleteElements(&active_jobs_
);
320 ProofVerifierChromium::Status
ProofVerifierChromium::VerifyProof(
321 const std::string
& hostname
,
322 const std::string
& server_config
,
323 const std::vector
<std::string
>& certs
,
324 const std::string
& signature
,
325 const ProofVerifyContext
* verify_context
,
326 std::string
* error_details
,
327 scoped_ptr
<ProofVerifyDetails
>* verify_details
,
328 ProofVerifierCallback
* callback
) {
329 if (!verify_context
) {
330 *error_details
= "Missing context";
333 const ProofVerifyContextChromium
* chromium_context
=
334 reinterpret_cast<const ProofVerifyContextChromium
*>(verify_context
);
335 scoped_ptr
<Job
> job(new Job(this, cert_verifier_
, chromium_context
->net_log
));
336 Status status
= job
->VerifyProof(hostname
, server_config
, certs
, signature
,
337 error_details
, verify_details
, callback
);
338 if (status
== PENDING
) {
339 active_jobs_
.insert(job
.release());
344 void ProofVerifierChromium::OnJobComplete(Job
* job
) {
345 active_jobs_
.erase(job
);