Handle serial numbers permissively in OCSP

Since libpki's serial number parser is itself sometimes permissive, it
would be prudent for us to ensure all accepted serial numbers are
representable in OCSP and CRL. This isn't critical because the real
factor is whether the caller treats revocation as hardfail or not.

This also fixes a bug where OCSPParseCertificate forgot to pass
parse_options anywhere.

In reality, this is all just just downstream of libpki handling serial
numbers badly. https://crbug.com/533048005 tracks fixing this.

Fixed: 518241439
Change-Id: Id73102bfc56d8c27ec08b1f76c411bdee3ab0551
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/98647
Presubmit-BoringSSL-Verified: boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Matt Mueller <mattm@google.com>
Commit-Queue: Matt Mueller <mattm@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
This commit is contained in:
David Benjamin
2026-07-09 16:52:16 -04:00
committed by boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 43c18e2d71
commit d4864a6c40
9 changed files with 208 additions and 13 deletions
+1
View File
@@ -2180,6 +2180,7 @@ pki_test_data = [
"pki/testdata/ocsp_unittest/bad_status.pem",
"pki/testdata/ocsp_unittest/future_response.pem",
"pki/testdata/ocsp_unittest/good_response.pem",
"pki/testdata/ocsp_unittest/good_response_invalid_serial.pem",
"pki/testdata/ocsp_unittest/good_response_next_update.pem",
"pki/testdata/ocsp_unittest/good_response_sha256.pem",
"pki/testdata/ocsp_unittest/has_critical_ct_extension.pem",
+1
View File
@@ -2238,6 +2238,7 @@ set(
pki/testdata/ocsp_unittest/bad_status.pem
pki/testdata/ocsp_unittest/future_response.pem
pki/testdata/ocsp_unittest/good_response.pem
pki/testdata/ocsp_unittest/good_response_invalid_serial.pem
pki/testdata/ocsp_unittest/good_response_next_update.pem
pki/testdata/ocsp_unittest/good_response_sha256.pem
pki/testdata/ocsp_unittest/has_critical_ct_extension.pem
+1
View File
@@ -2180,6 +2180,7 @@ pki_test_data = [
"pki/testdata/ocsp_unittest/bad_status.pem",
"pki/testdata/ocsp_unittest/future_response.pem",
"pki/testdata/ocsp_unittest/good_response.pem",
"pki/testdata/ocsp_unittest/good_response_invalid_serial.pem",
"pki/testdata/ocsp_unittest/good_response_next_update.pem",
"pki/testdata/ocsp_unittest/good_response_sha256.pem",
"pki/testdata/ocsp_unittest/has_critical_ct_extension.pem",
+1
View File
@@ -2160,6 +2160,7 @@
"pki/testdata/ocsp_unittest/bad_status.pem",
"pki/testdata/ocsp_unittest/future_response.pem",
"pki/testdata/ocsp_unittest/good_response.pem",
"pki/testdata/ocsp_unittest/good_response_invalid_serial.pem",
"pki/testdata/ocsp_unittest/good_response_next_update.pem",
"pki/testdata/ocsp_unittest/good_response_sha256.pem",
"pki/testdata/ocsp_unittest/has_critical_ct_extension.pem",
+1
View File
@@ -2152,6 +2152,7 @@ boringssl_pki_test_data := \
pki/testdata/ocsp_unittest/bad_status.pem \
pki/testdata/ocsp_unittest/future_response.pem \
pki/testdata/ocsp_unittest/good_response.pem \
pki/testdata/ocsp_unittest/good_response_invalid_serial.pem \
pki/testdata/ocsp_unittest/good_response_next_update.pem \
pki/testdata/ocsp_unittest/good_response_sha256.pem \
pki/testdata/ocsp_unittest/has_critical_ct_extension.pem \
+5
View File
@@ -360,6 +360,11 @@ CRLRevocationStatus GetCRLStatusForCert(
return CRLRevocationStatus::UNKNOWN;
}
// TODO(crbug.com/533048005): We do not check `revoked_cert_serial_number`
// is a valid INTEGER because our certificate parser does not yet check for
// valid INTEGERs. This ensures those non-integers can be represented in
// CRLs.
// revocationDate Time,
der::GeneralizedTime unused_revocation_date;
if (!ReadUTCOrGeneralizedTime(&crl_entry_parser, &unused_revocation_date)) {
+11 -12
View File
@@ -75,11 +75,9 @@ bool ParseOCSPCertID(der::Input raw_tlv, OCSPCertID *out) {
if (!parser.ReadTag(CBS_ASN1_INTEGER, &out->serial_number)) {
return false;
}
CertErrors errors;
if (!VerifySerialNumber(out->serial_number, false /*warnings_only*/,
&errors)) {
return false;
}
// TODO(crbug.com/533048005): We do not check `out->serial_number` is a valid
// INTEGER because our certificate serial parser does not yet check for valid
// INTEGERs. This ensures those non-integers can be represented in OCSP.
return !parser.HasMore();
}
@@ -601,7 +599,7 @@ bool CheckCertIDMatchesCertificate(
// would either pass in the parsed bits, or have a better abstraction for lazily
// parsing.
std::shared_ptr<const ParsedCertificate> OCSPParseCertificate(
std::string_view der) {
Span<const uint8_t> der) {
ParseCertificateOptions parse_options;
parse_options.allow_invalid_serial_numbers = true;
@@ -613,9 +611,9 @@ std::shared_ptr<const ParsedCertificate> OCSPParseCertificate(
// parsing model.
CertErrors errors;
return ParsedCertificate::Create(
bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new(
reinterpret_cast<const uint8_t *>(der.data()), der.size(), nullptr)),
{}, &errors);
bssl::UniquePtr<CRYPTO_BUFFER>(
CRYPTO_BUFFER_new(der.data(), der.size(), nullptr)),
parse_options, &errors);
}
// Checks that the ResponderID `id` matches the certificate `cert` either
@@ -716,7 +714,7 @@ std::shared_ptr<const ParsedCertificate> OCSPParseCertificate(
// (3) Has signed the OCSP response using its public key.
for (const auto &responder_cert_tlv : response.certs) {
std::shared_ptr<const ParsedCertificate> cur_responder_certificate =
OCSPParseCertificate(BytesAsStringView(responder_cert_tlv));
OCSPParseCertificate(responder_cert_tlv);
// If failed parsing the certificate, keep looking.
if (!cur_responder_certificate) {
@@ -936,11 +934,12 @@ OCSPRevocationStatus CheckOCSP(
std::shared_ptr<const ParsedCertificate> parsed_certificate;
std::shared_ptr<const ParsedCertificate> parsed_issuer_certificate;
if (!certificate) {
parsed_certificate = OCSPParseCertificate(certificate_der);
parsed_certificate = OCSPParseCertificate(StringAsBytes(certificate_der));
certificate = parsed_certificate.get();
}
if (!issuer_certificate) {
parsed_issuer_certificate = OCSPParseCertificate(issuer_certificate_der);
parsed_issuer_certificate =
OCSPParseCertificate(StringAsBytes(issuer_certificate_der));
issuer_certificate = parsed_issuer_certificate.get();
}
+8 -1
View File
@@ -21,6 +21,7 @@
#include <openssl/span.h>
#include "encode_values.h"
#include "parsed_certificate.h"
#include "string_util.h"
#include "test_helpers.h"
@@ -38,10 +39,14 @@ std::shared_ptr<const ParsedCertificate> ParseCertificate(
std::string_view data) {
CertErrors errors;
auto bytes = StringAsBytes(data);
// TODO(crbug.com/533048005): Remove this option when
// good_response_invalid_serial is removed.
ParseCertificateOptions options;
options.allow_invalid_serial_numbers = true;
return ParsedCertificate::Create(
bssl::UniquePtr<CRYPTO_BUFFER>(
CRYPTO_BUFFER_new(bytes.data(), bytes.size(), nullptr)),
{}, &errors);
options, &errors);
}
struct TestParams {
@@ -57,6 +62,8 @@ const TestParams kTestParams[] = {
OCSPVerifyResult::PROVIDED},
{"good_response_sha256.pem", OCSPRevocationStatus::GOOD,
OCSPVerifyResult::PROVIDED},
{"good_response_invalid_serial.pem", OCSPRevocationStatus::GOOD,
OCSPVerifyResult::PROVIDED},
{"no_response.pem", OCSPRevocationStatus::UNKNOWN,
OCSPVerifyResult::NO_MATCHING_RESPONSE},
{"malformed_request.pem", OCSPRevocationStatus::UNKNOWN,
@@ -0,0 +1,179 @@
Is a valid response for the cert, but the cert has an incorrectly encoded
serial number. Until we fix the cert parser (https://crbug.com/533048005), it
is prudent (though not critical) for every acceptable serial number to be
representable in OCSP.
This input is not generated by make_ocsp.py because it needs to output an
invalid INTEGER. Instead, it was modified from good_response.pem with der-ascii
and then the signature manually refreshed.
$ openssl asn1parse -i < [OCSP RESPONSE]
0:d=0 hl=4 l= 441 cons: SEQUENCE
4:d=1 hl=2 l= 1 prim: ENUMERATED :00
7:d=1 hl=4 l= 434 cons: cont [ 0 ]
11:d=2 hl=4 l= 430 cons: SEQUENCE
15:d=3 hl=2 l= 9 prim: OBJECT :Basic OCSP Response
26:d=3 hl=4 l= 415 prim: OCTET STRING
0:d=0 hl=4 l= 411 cons: SEQUENCE
4:d=1 hl=3 l= 134 cons: SEQUENCE
7:d=2 hl=2 l= 33 cons: cont [ 1 ]
9:d=3 hl=2 l= 31 cons: SEQUENCE
11:d=4 hl=2 l= 29 cons: SET
13:d=5 hl=2 l= 27 cons: SEQUENCE
15:d=6 hl=2 l= 3 prim: OBJECT :commonName
20:d=6 hl=2 l= 20 prim: UTF8STRING :Test Intermediate CA
42:d=2 hl=2 l= 15 prim: GENERALIZEDTIME :20170302000000Z
59:d=2 hl=2 l= 80 cons: SEQUENCE
61:d=3 hl=2 l= 78 cons: SEQUENCE
63:d=4 hl=2 l= 57 cons: SEQUENCE
65:d=5 hl=2 l= 7 cons: SEQUENCE
67:d=6 hl=2 l= 5 prim: OBJECT :sha1
74:d=5 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:449B1C5B31C6E9990966523E49C3F773C024190A
96:d=5 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:345CB28B1D8CDD6CBFF8F5CCF46521E87A8DF391
118:d=5 hl=2 l= 2 prim: INTEGER :BAD INTEGER:[0005]
122:d=4 hl=2 l= 0 prim: cont [ 0 ]
124:d=4 hl=2 l= 15 prim: GENERALIZEDTIME :20170301000000Z
141:d=1 hl=2 l= 11 cons: SEQUENCE
143:d=2 hl=2 l= 9 prim: OBJECT :sha1WithRSAEncryption
154:d=1 hl=4 l= 257 prim: BIT STRING
-----BEGIN OCSP RESPONSE-----
MIIBuQoBAKCCAbIwggGuBgkrBgEFBQcwAQEEggGfMIIBmzCBhqEhMB8xHTAbBgNVBAMMFFRlc3Q
gSW50ZXJtZWRpYXRlIENBGA8yMDE3MDMwMjAwMDAwMFowUDBOMDkwBwYFKw4DAhoEFESbHFsxxu
mZCWZSPknD93PAJBkKBBQ0XLKLHYzdbL/49cz0ZSHoeo3zkQICAAWAABgPMjAxNzAzMDEwMDAwM
DBaMAsGCSqGSIb3DQEBBQOCAQEAKo1IH2jKiU1EMMDxo8YQ9qPKbVmdvt5EkQ5qzOfNoVKPE9oW
GvTN78vxP0imCnGsZboIDRHCmufqxcQ9qLIWK09BkwbsBlVRxhFQsciqsxuRTgMBUtsgV0aRpCT
jJBb5BBJDk7KsWO7CnpUABsfSpVsJM0AfqpRc/E0f08WXPqaT5OhjyajzfY0xdqdmBISpUYsBdi
HC8lewfLkKZGzTEMOExIFWblbXXdWTvOP+Imt04y1DjUeO3eaGlQ5GgM4LZNmFGJtM6TGoJalPj
MqGwI1a1cuEVrm5MbsYJxlYM6b4dq/T1izhaqZtF4ORZQrkrWoLpq+VAy+4pNFpdNnAtQ==
-----END OCSP RESPONSE-----
$ openssl x509 -text < [CA CERTIFICATE]
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 2 (0x2)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=Test CA
Validity
Not Before: Jan 1 00:00:00 2017 GMT
Not After : Jan 1 00:00:00 2018 GMT
Subject: CN=Test Intermediate CA
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:b6:34:d0:4e:c4:a4:f7:e0:90:a6:1f:54:37:d7:
02:5e:33:3d:39:99:9c:15:f5:d6:7a:e5:59:87:bf:
52:53:2d:8b:f6:31:ef:54:a2:e6:74:29:d5:52:54:
6f:25:9e:24:30:29:15:f1:71:68:cd:d9:77:48:94:
e5:96:a3:43:8b:35:2e:5f:da:00:a7:d3:ef:37:e8:
ef:e9:6e:b6:c9:dc:2b:90:cb:64:90:70:32:69:d9:
39:dd:9a:a8:51:98:61:99:0b:36:92:5e:f4:57:54:
8f:c7:9f:b2:28:df:0b:73:bb:2c:c8:96:26:6d:a0:
04:a9:93:a8:77:98:f9:ea:26:e0:92:6f:1b:34:03:
31:bd:fe:36:cc:86:02:21:f9:f3:41:c4:0d:c4:6e:
86:09:35:4b:f1:d6:4c:49:8a:bb:e5:d2:96:fa:fe:
ae:5c:95:8a:83:41:e6:36:9a:e8:3d:f2:73:e1:a6:
93:2d:ef:8f:35:95:67:f2:2b:b7:5e:72:24:de:1b:
c0:8d:cc:8f:2f:b3:94:74:15:cb:cc:5f:e4:dd:ee:
9b:fb:b2:86:64:1d:c3:ae:ac:58:eb:5e:5a:58:28:
4f:ea:49:f8:d3:ce:3a:f8:f1:a5:44:d4:ff:89:71:
dd:88:d5:64:b7:39:02:4b:d7:0e:87:55:9a:be:c3:
a2:6b
Exponent: 65537 (0x10001)
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
48:77:56:03:36:fd:be:c2:46:c7:b8:88:b1:58:a0:e9:34:77:
91:0c:ec:ad:f5:9c:c7:49:0e:d7:f4:22:b1:3b:6a:b5:1a:e1:
ed:16:1e:43:25:8c:2a:27:3a:66:4e:a0:af:6e:e1:d0:4f:f3:
9c:63:08:07:2f:55:c1:af:55:c3:13:ec:a9:62:fc:d7:bb:4f:
6b:6c:09:7e:ce:b5:0f:54:2c:33:94:fe:33:44:d4:db:d3:7c:
ad:7a:62:fb:b7:c5:03:52:20:38:d0:b1:27:81:9f:e2:4f:02:
fb:26:a9:69:16:7b:56:f7:2f:f6:44:7b:31:39:71:40:72:ac:
3b:f7:09:c5:c8:0c:73:c2:2f:9d:5d:bb:e9:fd:66:40:a9:dd:
dc:c9:85:81:d4:0f:6a:9c:01:ff:60:d5:26:5d:d3:af:54:af:
38:44:44:7f:c0:e1:1e:1f:37:f2:df:25:cb:96:91:6a:2f:33:
4f:42:73:05:ee:85:82:3f:33:79:8f:85:be:92:73:dd:49:c7:
da:45:6d:1c:10:ba:d9:29:06:83:c3:c2:4b:70:8d:65:f5:1e:
56:6c:bc:d4:e7:71:0e:13:63:a6:88:41:36:66:a9:6c:92:d1:
de:c5:e6:c4:0d:a5:17:d1:9d:ec:3e:b5:d8:7b:bd:2d:ae:69:
70:1c:13:0c
-----BEGIN CA CERTIFICATE-----
MIICqjCCAZKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdUZXN0IENBMB4XDTE
3MDEwMTAwMDAwMFoXDTE4MDEwMTAwMDAwMFowHzEdMBsGA1UEAwwUVGVzdCBJbnRlcm1lZGlhdG
UgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2NNBOxKT34JCmH1Q31wJeMz05m
ZwV9dZ65VmHv1JTLYv2Me9UouZ0KdVSVG8lniQwKRXxcWjN2XdIlOWWo0OLNS5f2gCn0+836O/p
brbJ3CuQy2SQcDJp2TndmqhRmGGZCzaSXvRXVI/Hn7Io3wtzuyzIliZtoASpk6h3mPnqJuCSbxs
0AzG9/jbMhgIh+fNBxA3EboYJNUvx1kxJirvl0pb6/q5clYqDQeY2mug98nPhppMt7481lWfyK7
deciTeG8CNzI8vs5R0FcvMX+Td7pv7soZkHcOurFjrXlpYKE/qSfjTzjr48aVE1P+Jcd2I1WS3O
QJL1w6HVZq+w6JrAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEh3VgM2/b7CRse4iLFYoOk0d5EM
7K31nMdJDtf0IrE7arUa4e0WHkMljConOmZOoK9u4dBP85xjCAcvVcGvVcMT7Kli/Ne7T2tsCX7
OtQ9ULDOU/jNE1NvTfK16Yvu3xQNSIDjQsSeBn+JPAvsmqWkWe1b3L/ZEezE5cUByrDv3CcXIDH
PCL51du+n9ZkCp3dzJhYHUD2qcAf9g1SZd069UrzhERH/A4R4fN/LfJcuWkWovM09CcwXuhYI/M
3mPhb6Sc91Jx9pFbRwQutkpBoPDwktwjWX1HlZsvNTncQ4TY6aIQTZmqWyS0d7F5sQNpRfRnew+
tdh7vS2uaXAcEww=
-----END CA CERTIFICATE-----
$ openssl asn1parse -i < [CERTIFICATE]
0:d=0 hl=4 l= 685 cons: SEQUENCE
4:d=1 hl=4 l= 405 cons: SEQUENCE
8:d=2 hl=2 l= 3 cons: cont [ 0 ]
10:d=3 hl=2 l= 1 prim: INTEGER :02
13:d=2 hl=2 l= 2 prim: INTEGER :BAD INTEGER:[0005]
17:d=2 hl=2 l= 13 cons: SEQUENCE
19:d=3 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption
30:d=3 hl=2 l= 0 prim: NULL
32:d=2 hl=2 l= 31 cons: SEQUENCE
34:d=3 hl=2 l= 29 cons: SET
36:d=4 hl=2 l= 27 cons: SEQUENCE
38:d=5 hl=2 l= 3 prim: OBJECT :commonName
43:d=5 hl=2 l= 20 prim: UTF8STRING :Test Intermediate CA
65:d=2 hl=2 l= 30 cons: SEQUENCE
67:d=3 hl=2 l= 13 prim: UTCTIME :170101000000Z
82:d=3 hl=2 l= 13 prim: UTCTIME :180101000000Z
97:d=2 hl=2 l= 20 cons: SEQUENCE
99:d=3 hl=2 l= 18 cons: SET
101:d=4 hl=2 l= 16 cons: SEQUENCE
103:d=5 hl=2 l= 3 prim: OBJECT :commonName
108:d=5 hl=2 l= 9 prim: UTF8STRING :Test Cert
119:d=2 hl=4 l= 290 cons: SEQUENCE
123:d=3 hl=2 l= 13 cons: SEQUENCE
125:d=4 hl=2 l= 9 prim: OBJECT :rsaEncryption
136:d=4 hl=2 l= 0 prim: NULL
138:d=3 hl=4 l= 271 prim: BIT STRING
413:d=1 hl=2 l= 13 cons: SEQUENCE
415:d=2 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption
426:d=2 hl=2 l= 0 prim: NULL
428:d=1 hl=4 l= 257 prim: BIT STRING
-----BEGIN CERTIFICATE-----
MIICrTCCAZWgAwIBAgICAAUwDQYJKoZIhvcNAQELBQAwHzEdMBsGA1UEAwwUVGVzdCBJbnRlcm1
lZGlhdGUgQ0EwHhcNMTcwMTAxMDAwMDAwWhcNMTgwMTAxMDAwMDAwWjAUMRIwEAYDVQQDDAlUZX
N0IENlcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8xQEfHCA7qzXLNBPOsCdAd
FJFawmHFAESDDnD+2mQLtRKSxZC25x3kAQs6lrfmCmv5OvopVSbopsEj/7G81AWlRJoAcyZx3XP
oGR3UOii4lFvxrCLQwtOMaVo8X22HWarzxW6l/IoHwDHSluKJIS7YS7afpBOo4k1GeG+FJ3Ek57
mcgCAZ/dcAtUv5K7wiey0lHZvx5W7bpfHzlkBdryyhcp8sOepApmkNnOPJCAM1H1ntosD4uXcty
sz2ChlkVVhJUnqWRydIew2vqaSZA/5IrF51qf+U3xDglNPj14zFZxzMLEMglaFukQhyb8O8MJKr
XF+NfX4GOZGZj0XTjuJAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACiJwPZNBsVdgjB91Ez0qCUR
OoB8QQ6LIoC59MSxxDw8afXttGEybTecZ7kNCbzP18u23VHWDmyhDDjluW2tnQzIilmnPkwz5Pk
9vLJyF8OXbV+yRZaM/KvXPLaSJFf8mhUMkQ+p2BhFMZJqzifZWHtDJ34wei3m84CLNdRJA6jT6y
5RjKdGUmlsJwu9fuKoxEoeY8lLXo9Oa+g1L/q1ifJ32UpT+lgqZmwxbeYvlzk8xqKyeFSJznM/p
/bpAkLcWwXWByhlobDs6cxSlXdGkn0i7hnadHOvAKkBmTraYn7dFXDx3Xom3HckUQNzy3EoLnZR
7OaTMjUqihkVes+H1fE=
-----END CERTIFICATE-----
$ openssl asn1parse -i < [OCSP REQUEST]
0:d=0 hl=2 l= 67 cons: SEQUENCE
2:d=1 hl=2 l= 65 cons: SEQUENCE
4:d=2 hl=2 l= 63 cons: SEQUENCE
6:d=3 hl=2 l= 61 cons: SEQUENCE
8:d=4 hl=2 l= 59 cons: SEQUENCE
10:d=5 hl=2 l= 9 cons: SEQUENCE
12:d=6 hl=2 l= 5 prim: OBJECT :sha1
19:d=6 hl=2 l= 0 prim: NULL
21:d=5 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:449B1C5B31C6E9990966523E49C3F773C024190A
43:d=5 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:345CB28B1D8CDD6CBFF8F5CCF46521E87A8DF391
65:d=5 hl=2 l= 2 prim: INTEGER :BAD INTEGER:[0005]
-----BEGIN OCSP REQUEST-----
MEMwQTA/MD0wOzAJBgUrDgMCGgUABBREmxxbMcbpmQlmUj5Jw/dzwCQZCgQUNFyyix2M3Wy/+PX
M9GUh6HqN85ECAgAF
-----END OCSP REQUEST-----