1 // Copyright 2020 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.
4
5 #include "net/test/revocation_builder.h"
6
7 #include "base/hash/sha1.h"
8 #include "base/strings/string_util.h"
9 #include "net/cert/asn1_util.h"
10 #include "net/cert/x509_util.h"
11 #include "net/der/encode_values.h"
12 #include "net/der/input.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14 #include "third_party/boringssl/src/include/openssl/bytestring.h"
15 #include "third_party/boringssl/src/include/openssl/mem.h"
16
17 namespace net {
18
19 namespace {
20
Sha256WithRSAEncryption()21 std::string Sha256WithRSAEncryption() {
22 const uint8_t kSha256WithRSAEncryption[] = {0x30, 0x0D, 0x06, 0x09, 0x2a,
23 0x86, 0x48, 0x86, 0xf7, 0x0d,
24 0x01, 0x01, 0x0b, 0x05, 0x00};
25 return std::string(std::begin(kSha256WithRSAEncryption),
26 std::end(kSha256WithRSAEncryption));
27 }
28
Sha1WithRSAEncryption()29 std::string Sha1WithRSAEncryption() {
30 const uint8_t kSha1WithRSAEncryption[] = {0x30, 0x0D, 0x06, 0x09, 0x2a,
31 0x86, 0x48, 0x86, 0xf7, 0x0d,
32 0x01, 0x01, 0x05, 0x05, 0x00};
33 return std::string(std::begin(kSha1WithRSAEncryption),
34 std::end(kSha1WithRSAEncryption));
35 }
36
Md5WithRSAEncryption()37 std::string Md5WithRSAEncryption() {
38 const uint8_t kMd5WithRSAEncryption[] = {0x30, 0x0d, 0x06, 0x09, 0x2a,
39 0x86, 0x48, 0x86, 0xf7, 0x0d,
40 0x01, 0x01, 0x04, 0x05, 0x00};
41 return std::string(std::begin(kMd5WithRSAEncryption),
42 std::end(kMd5WithRSAEncryption));
43 }
44
Sha1()45 std::string Sha1() {
46 // SEQUENCE { OBJECT_IDENTIFIER { 1.3.14.3.2.26 } }
47 const uint8_t kSHA1[] = {0x30, 0x07, 0x06, 0x05, 0x2b,
48 0x0e, 0x03, 0x02, 0x1a};
49 return std::string(std::begin(kSHA1), std::end(kSHA1));
50 }
51
52 // Adds bytes (specified as a StringPiece) to the given CBB.
53 // The argument ordering follows the boringssl CBB_* api style.
CBBAddBytes(CBB * cbb,base::StringPiece bytes)54 bool CBBAddBytes(CBB* cbb, base::StringPiece bytes) {
55 return CBB_add_bytes(cbb, reinterpret_cast<const uint8_t*>(bytes.data()),
56 bytes.size());
57 }
58
59 // Adds bytes (from fixed size array) to the given CBB.
60 // The argument ordering follows the boringssl CBB_* api style.
61 template <size_t N>
CBBAddBytes(CBB * cbb,const uint8_t (& data)[N])62 bool CBBAddBytes(CBB* cbb, const uint8_t (&data)[N]) {
63 return CBB_add_bytes(cbb, data, N);
64 }
65
66 // Adds a GeneralizedTime value to the given CBB.
67 // The argument ordering follows the boringssl CBB_* api style.
CBBAddGeneralizedTime(CBB * cbb,const base::Time & time)68 bool CBBAddGeneralizedTime(CBB* cbb, const base::Time& time) {
69 der::GeneralizedTime generalized_time;
70 if (!der::EncodeTimeAsGeneralizedTime(time, &generalized_time))
71 return false;
72 CBB time_cbb;
73 uint8_t out[der::kGeneralizedTimeLength];
74 if (!der::EncodeGeneralizedTime(generalized_time, out) ||
75 !CBB_add_asn1(cbb, &time_cbb, CBS_ASN1_GENERALIZEDTIME) ||
76 !CBBAddBytes(&time_cbb, out) || !CBB_flush(cbb))
77 return false;
78 return true;
79 }
80
81 // Finalizes the CBB to a std::string.
FinishCBB(CBB * cbb)82 std::string FinishCBB(CBB* cbb) {
83 size_t cbb_len;
84 uint8_t* cbb_bytes;
85
86 if (!CBB_finish(cbb, &cbb_bytes, &cbb_len)) {
87 ADD_FAILURE() << "CBB_finish() failed";
88 return std::string();
89 }
90
91 bssl::UniquePtr<uint8_t> delete_bytes(cbb_bytes);
92 return std::string(reinterpret_cast<char*>(cbb_bytes), cbb_len);
93 }
94
PKeyToSPK(const EVP_PKEY * pkey)95 std::string PKeyToSPK(const EVP_PKEY* pkey) {
96 bssl::ScopedCBB cbb;
97 if (!CBB_init(cbb.get(), 64) || !EVP_marshal_public_key(cbb.get(), pkey)) {
98 ADD_FAILURE();
99 return std::string();
100 }
101 std::string spki = FinishCBB(cbb.get());
102
103 base::StringPiece spk;
104 if (!asn1::ExtractSubjectPublicKeyFromSPKI(spki, &spk)) {
105 ADD_FAILURE();
106 return std::string();
107 }
108
109 // ExtractSubjectPublicKeyFromSPKI() includes the unused bit count. For this
110 // application, the unused bit count must be zero, and is not included in the
111 // result.
112 if (!base::StartsWith(spk, "\0")) {
113 ADD_FAILURE();
114 return std::string();
115 }
116 spk.remove_prefix(1);
117
118 return spk.as_string();
119 }
120
121 // Returns a DER-encoded OCSPResponse with the given |response_status|.
122 // |response_type| and |response| are optional and may be empty.
EncodeOCSPResponse(OCSPResponse::ResponseStatus response_status,der::Input response_type,std::string response)123 std::string EncodeOCSPResponse(OCSPResponse::ResponseStatus response_status,
124 der::Input response_type,
125 std::string response) {
126 // RFC 6960 section 4.2.1:
127 //
128 // OCSPResponse ::= SEQUENCE {
129 // responseStatus OCSPResponseStatus,
130 // responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
131 //
132 // OCSPResponseStatus ::= ENUMERATED {
133 // successful (0), -- Response has valid confirmations
134 // malformedRequest (1), -- Illegal confirmation request
135 // internalError (2), -- Internal error in issuer
136 // tryLater (3), -- Try again later
137 // -- (4) is not used
138 // sigRequired (5), -- Must sign the request
139 // unauthorized (6) -- Request unauthorized
140 // }
141 //
142 // The value for responseBytes consists of an OBJECT IDENTIFIER and a
143 // response syntax identified by that OID encoded as an OCTET STRING.
144 //
145 // ResponseBytes ::= SEQUENCE {
146 // responseType OBJECT IDENTIFIER,
147 // response OCTET STRING }
148 bssl::ScopedCBB cbb;
149 CBB ocsp_response, ocsp_response_status, ocsp_response_bytes,
150 ocsp_response_bytes_sequence, ocsp_response_type,
151 ocsp_response_octet_string;
152
153 if (!CBB_init(cbb.get(), 64 + response_type.Length() + response.size()) ||
154 !CBB_add_asn1(cbb.get(), &ocsp_response, CBS_ASN1_SEQUENCE) ||
155 !CBB_add_asn1(&ocsp_response, &ocsp_response_status,
156 CBS_ASN1_ENUMERATED) ||
157 !CBB_add_u8(&ocsp_response_status,
158 static_cast<uint8_t>(response_status))) {
159 ADD_FAILURE();
160 return std::string();
161 }
162
163 if (response_type.Length()) {
164 if (!CBB_add_asn1(&ocsp_response, &ocsp_response_bytes,
165 CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
166 !CBB_add_asn1(&ocsp_response_bytes, &ocsp_response_bytes_sequence,
167 CBS_ASN1_SEQUENCE) ||
168 !CBB_add_asn1(&ocsp_response_bytes_sequence, &ocsp_response_type,
169 CBS_ASN1_OBJECT) ||
170 !CBBAddBytes(&ocsp_response_type, response_type.AsStringPiece()) ||
171 !CBB_add_asn1(&ocsp_response_bytes_sequence,
172 &ocsp_response_octet_string, CBS_ASN1_OCTETSTRING) ||
173 !CBBAddBytes(&ocsp_response_octet_string, response)) {
174 ADD_FAILURE();
175 return std::string();
176 }
177 }
178
179 return FinishCBB(cbb.get());
180 }
181
182 // Adds a DER-encoded OCSP SingleResponse to |responses_cbb|.
183 // |issuer_name_hash| and |issuer_key_hash| should be binary SHA1 hashes.
AddOCSPSingleResponse(CBB * responses_cbb,const OCSPBuilderSingleResponse & response,const std::string & issuer_name_hash,const std::string & issuer_key_hash)184 bool AddOCSPSingleResponse(CBB* responses_cbb,
185 const OCSPBuilderSingleResponse& response,
186 const std::string& issuer_name_hash,
187 const std::string& issuer_key_hash) {
188 // RFC 6960 section 4.2.1:
189 //
190 // SingleResponse ::= SEQUENCE {
191 // certID CertID,
192 // certStatus CertStatus,
193 // thisUpdate GeneralizedTime,
194 // nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
195 // singleExtensions [1] EXPLICIT Extensions OPTIONAL }
196 //
197 // CertStatus ::= CHOICE {
198 // good [0] IMPLICIT NULL,
199 // revoked [1] IMPLICIT RevokedInfo,
200 // unknown [2] IMPLICIT UnknownInfo }
201 //
202 // RevokedInfo ::= SEQUENCE {
203 // revocationTime GeneralizedTime,
204 // revocationReason [0] EXPLICIT CRLReason OPTIONAL }
205 //
206 // UnknownInfo ::= NULL
207 //
208 // RFC 6960 section 4.1.1:
209 // CertID ::= SEQUENCE {
210 // hashAlgorithm AlgorithmIdentifier,
211 // issuerNameHash OCTET STRING, -- Hash of issuer's DN
212 // issuerKeyHash OCTET STRING, -- Hash of issuer's public key
213 // serialNumber CertificateSerialNumber }
214 //
215 // The contents of CertID include the following fields:
216 //
217 // o hashAlgorithm is the hash algorithm used to generate the
218 // issuerNameHash and issuerKeyHash values.
219 //
220 // o issuerNameHash is the hash of the issuer's distinguished name
221 // (DN). The hash shall be calculated over the DER encoding of the
222 // issuer's name field in the certificate being checked.
223 //
224 // o issuerKeyHash is the hash of the issuer's public key. The hash
225 // shall be calculated over the value (excluding tag and length) of
226 // the subject public key field in the issuer's certificate.
227 //
228 // o serialNumber is the serial number of the certificate for which
229 // status is being requested.
230
231 CBB single_response, issuer_name_hash_cbb, issuer_key_hash_cbb, cert_id;
232 if (!CBB_add_asn1(responses_cbb, &single_response, CBS_ASN1_SEQUENCE) ||
233 !CBB_add_asn1(&single_response, &cert_id, CBS_ASN1_SEQUENCE) ||
234 !CBBAddBytes(&cert_id, Sha1()) ||
235 !CBB_add_asn1(&cert_id, &issuer_name_hash_cbb, CBS_ASN1_OCTETSTRING) ||
236 !CBBAddBytes(&issuer_name_hash_cbb, issuer_name_hash) ||
237 !CBB_add_asn1(&cert_id, &issuer_key_hash_cbb, CBS_ASN1_OCTETSTRING) ||
238 !CBBAddBytes(&issuer_key_hash_cbb, issuer_key_hash) ||
239 !CBB_add_asn1_uint64(&cert_id, response.serial)) {
240 ADD_FAILURE();
241 return false;
242 }
243
244 unsigned int cert_status_tag_number;
245 switch (response.cert_status) {
246 case OCSPRevocationStatus::GOOD:
247 cert_status_tag_number = CBS_ASN1_CONTEXT_SPECIFIC | 0;
248 break;
249 case OCSPRevocationStatus::REVOKED:
250 cert_status_tag_number =
251 CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1;
252 break;
253 case OCSPRevocationStatus::UNKNOWN:
254 cert_status_tag_number = CBS_ASN1_CONTEXT_SPECIFIC | 2;
255 break;
256 }
257
258 CBB cert_status_cbb;
259 if (!CBB_add_asn1(&single_response, &cert_status_cbb,
260 cert_status_tag_number)) {
261 ADD_FAILURE();
262 return false;
263 }
264 if (response.cert_status == OCSPRevocationStatus::REVOKED &&
265 !CBBAddGeneralizedTime(&cert_status_cbb, response.revocation_time)) {
266 ADD_FAILURE();
267 return false;
268 }
269
270 CBB next_update_cbb;
271 if (!CBBAddGeneralizedTime(&single_response, response.this_update) ||
272 !CBB_add_asn1(&single_response, &next_update_cbb,
273 CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
274 !CBBAddGeneralizedTime(&next_update_cbb, response.next_update)) {
275 ADD_FAILURE();
276 return false;
277 }
278
279 return CBB_flush(responses_cbb);
280 }
281
282 } // namespace
283
BuildOCSPResponseError(OCSPResponse::ResponseStatus response_status)284 std::string BuildOCSPResponseError(
285 OCSPResponse::ResponseStatus response_status) {
286 DCHECK_NE(response_status, OCSPResponse::ResponseStatus::SUCCESSFUL);
287 return EncodeOCSPResponse(response_status, der::Input(), std::string());
288 }
289
BuildOCSPResponse(const std::string & responder_subject,EVP_PKEY * responder_key,base::Time produced_at,const std::vector<OCSPBuilderSingleResponse> & responses)290 std::string BuildOCSPResponse(
291 const std::string& responder_subject,
292 EVP_PKEY* responder_key,
293 base::Time produced_at,
294 const std::vector<OCSPBuilderSingleResponse>& responses) {
295 std::string responder_name_hash = base::SHA1HashString(responder_subject);
296 std::string responder_key_hash =
297 base::SHA1HashString(PKeyToSPK(responder_key));
298
299 // RFC 6960 section 4.2.1:
300 //
301 // ResponseData ::= SEQUENCE {
302 // version [0] EXPLICIT Version DEFAULT v1,
303 // responderID ResponderID,
304 // producedAt GeneralizedTime,
305 // responses SEQUENCE OF SingleResponse,
306 // responseExtensions [1] EXPLICIT Extensions OPTIONAL }
307 //
308 // ResponderID ::= CHOICE {
309 // byName [1] Name,
310 // byKey [2] KeyHash }
311 //
312 // KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
313 // (excluding the tag and length fields)
314 bssl::ScopedCBB tbs_cbb;
315 CBB response_data, responder_id, responder_id_by_key, responses_cbb;
316 if (!CBB_init(tbs_cbb.get(), 64) ||
317 !CBB_add_asn1(tbs_cbb.get(), &response_data, CBS_ASN1_SEQUENCE) ||
318 // Version is the default v1, so it is not encoded.
319 !CBB_add_asn1(&response_data, &responder_id,
320 CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 2) ||
321 !CBB_add_asn1(&responder_id, &responder_id_by_key,
322 CBS_ASN1_OCTETSTRING) ||
323 !CBBAddBytes(&responder_id_by_key, responder_key_hash) ||
324 !CBBAddGeneralizedTime(&response_data, produced_at) ||
325 !CBB_add_asn1(&response_data, &responses_cbb, CBS_ASN1_SEQUENCE)) {
326 ADD_FAILURE();
327 return std::string();
328 }
329
330 for (const auto& response : responses) {
331 if (!AddOCSPSingleResponse(&responses_cbb, response, responder_name_hash,
332 responder_key_hash)) {
333 return std::string();
334 }
335 }
336
337 // responseExtensions not currently supported.
338
339 return BuildOCSPResponseWithResponseData(responder_key,
340 FinishCBB(tbs_cbb.get()));
341 }
342
BuildOCSPResponseWithResponseData(EVP_PKEY * responder_key,const std::string & tbs_response_data)343 std::string BuildOCSPResponseWithResponseData(
344 EVP_PKEY* responder_key,
345 const std::string& tbs_response_data) {
346 // For a basic OCSP responder, responseType will be id-pkix-ocsp-basic.
347 //
348 // id-pkix-ocsp OBJECT IDENTIFIER ::= { id-ad-ocsp }
349 // id-pkix-ocsp-basic OBJECT IDENTIFIER ::= { id-pkix-ocsp 1 }
350 //
351 // The value for response SHALL be the DER encoding of
352 // BasicOCSPResponse.
353 //
354 // BasicOCSPResponse ::= SEQUENCE {
355 // tbsResponseData ResponseData,
356 // signatureAlgorithm AlgorithmIdentifier,
357 // signature BIT STRING,
358 // certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
359 //
360 // The value for signature SHALL be computed on the hash of the DER
361 // encoding of ResponseData. The responder MAY include certificates in
362 // the certs field of BasicOCSPResponse that help the OCSP client verify
363 // the responder's signature. If no certificates are included, then
364 // certs SHOULD be absent.
365 //
366 bssl::ScopedCBB basic_ocsp_response_cbb;
367 CBB basic_ocsp_response, signature;
368 bssl::ScopedEVP_MD_CTX ctx;
369 uint8_t* sig_out;
370 size_t sig_len;
371 if (!CBB_init(basic_ocsp_response_cbb.get(), 64 + tbs_response_data.size()) ||
372 !CBB_add_asn1(basic_ocsp_response_cbb.get(), &basic_ocsp_response,
373 CBS_ASN1_SEQUENCE) ||
374 !CBBAddBytes(&basic_ocsp_response, tbs_response_data) ||
375 !CBBAddBytes(&basic_ocsp_response, Sha256WithRSAEncryption()) ||
376 !CBB_add_asn1(&basic_ocsp_response, &signature, CBS_ASN1_BITSTRING) ||
377 !CBB_add_u8(&signature, 0 /* no unused bits */) ||
378 !EVP_DigestSignInit(ctx.get(), nullptr, EVP_sha256(), nullptr,
379 responder_key) ||
380 !EVP_DigestSign(
381 ctx.get(), nullptr, &sig_len,
382 reinterpret_cast<const uint8_t*>(tbs_response_data.data()),
383 tbs_response_data.size()) ||
384 !CBB_reserve(&signature, &sig_out, sig_len) ||
385 !EVP_DigestSign(
386 ctx.get(), sig_out, &sig_len,
387 reinterpret_cast<const uint8_t*>(tbs_response_data.data()),
388 tbs_response_data.size()) ||
389 !CBB_did_write(&signature, sig_len)) {
390 ADD_FAILURE();
391 return std::string();
392 }
393
394 // certs field not currently supported.
395
396 return EncodeOCSPResponse(OCSPResponse::ResponseStatus::SUCCESSFUL,
397 BasicOCSPResponseOid(),
398 FinishCBB(basic_ocsp_response_cbb.get()));
399 }
400
BuildCrl(const std::string & crl_issuer_subject,EVP_PKEY * crl_issuer_key,const std::vector<uint64_t> & revoked_serials,DigestAlgorithm digest)401 std::string BuildCrl(const std::string& crl_issuer_subject,
402 EVP_PKEY* crl_issuer_key,
403 const std::vector<uint64_t>& revoked_serials,
404 DigestAlgorithm digest) {
405 std::string signature_algorithm;
406 const EVP_MD* md = nullptr;
407 switch (digest) {
408 case DigestAlgorithm::Sha256: {
409 signature_algorithm = Sha256WithRSAEncryption();
410 md = EVP_sha256();
411 break;
412 }
413
414 case DigestAlgorithm::Sha1: {
415 signature_algorithm = Sha1WithRSAEncryption();
416 md = EVP_sha1();
417 break;
418 }
419
420 case DigestAlgorithm::Md5: {
421 signature_algorithm = Md5WithRSAEncryption();
422 md = EVP_md5();
423 break;
424 }
425
426 default:
427 ADD_FAILURE();
428 return std::string();
429 }
430 // TBSCertList ::= SEQUENCE {
431 // version Version OPTIONAL,
432 // -- if present, MUST be v2
433 // signature AlgorithmIdentifier,
434 // issuer Name,
435 // thisUpdate Time,
436 // nextUpdate Time OPTIONAL,
437 // revokedCertificates SEQUENCE OF SEQUENCE {
438 // userCertificate CertificateSerialNumber,
439 // revocationDate Time,
440 // crlEntryExtensions Extensions OPTIONAL
441 // -- if present, version MUST be v2
442 // } OPTIONAL,
443 // crlExtensions [0] EXPLICIT Extensions OPTIONAL
444 // -- if present, version MUST be v2
445 // }
446 bssl::ScopedCBB tbs_cbb;
447 CBB tbs_cert_list, revoked_serials_cbb;
448 if (!CBB_init(tbs_cbb.get(), 10) ||
449 !CBB_add_asn1(tbs_cbb.get(), &tbs_cert_list, CBS_ASN1_SEQUENCE) ||
450 !CBB_add_asn1_uint64(&tbs_cert_list, 1 /* V2 */) ||
451 !CBBAddBytes(&tbs_cert_list, signature_algorithm) ||
452 !CBBAddBytes(&tbs_cert_list, crl_issuer_subject) ||
453 !x509_util::CBBAddTime(
454 &tbs_cert_list, base::Time::Now() - base::TimeDelta::FromDays(1)) ||
455 !x509_util::CBBAddTime(
456 &tbs_cert_list, base::Time::Now() + base::TimeDelta::FromDays(6))) {
457 ADD_FAILURE();
458 return std::string();
459 }
460 if (!revoked_serials.empty()) {
461 if (!CBB_add_asn1(&tbs_cert_list, &revoked_serials_cbb,
462 CBS_ASN1_SEQUENCE)) {
463 ADD_FAILURE();
464 return std::string();
465 }
466 for (const int64_t revoked_serial : revoked_serials) {
467 CBB revoked_serial_cbb;
468 if (!CBB_add_asn1(&revoked_serials_cbb, &revoked_serial_cbb,
469 CBS_ASN1_SEQUENCE) ||
470 !CBB_add_asn1_uint64(&revoked_serial_cbb, revoked_serial) ||
471 !x509_util::CBBAddTime(
472 &revoked_serial_cbb,
473 base::Time::Now() - base::TimeDelta::FromDays(1)) ||
474 !CBB_flush(&revoked_serials_cbb)) {
475 ADD_FAILURE();
476 return std::string();
477 }
478 }
479 }
480
481 std::string tbs_tlv = FinishCBB(tbs_cbb.get());
482
483 // CertificateList ::= SEQUENCE {
484 // tbsCertList TBSCertList,
485 // signatureAlgorithm AlgorithmIdentifier,
486 // signatureValue BIT STRING }
487 bssl::ScopedCBB crl_cbb;
488 CBB cert_list, signature;
489 bssl::ScopedEVP_MD_CTX ctx;
490 uint8_t* sig_out;
491 size_t sig_len;
492 if (!CBB_init(crl_cbb.get(), 10) ||
493 !CBB_add_asn1(crl_cbb.get(), &cert_list, CBS_ASN1_SEQUENCE) ||
494 !CBBAddBytes(&cert_list, tbs_tlv) ||
495 !CBBAddBytes(&cert_list, signature_algorithm) ||
496 !CBB_add_asn1(&cert_list, &signature, CBS_ASN1_BITSTRING) ||
497 !CBB_add_u8(&signature, 0 /* no unused bits */) ||
498 !EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, crl_issuer_key) ||
499 !EVP_DigestSign(ctx.get(), nullptr, &sig_len,
500 reinterpret_cast<const uint8_t*>(tbs_tlv.data()),
501 tbs_tlv.size()) ||
502 !CBB_reserve(&signature, &sig_out, sig_len) ||
503 !EVP_DigestSign(ctx.get(), sig_out, &sig_len,
504 reinterpret_cast<const uint8_t*>(tbs_tlv.data()),
505 tbs_tlv.size()) ||
506 !CBB_did_write(&signature, sig_len)) {
507 ADD_FAILURE();
508 return std::string();
509 }
510 return FinishCBB(crl_cbb.get());
511 }
512 } // namespace net
513