Index: net/cert/internal/parse_certificate.cc |
diff --git a/net/cert/internal/parse_certificate.cc b/net/cert/internal/parse_certificate.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..cc7aa30bbb8d60c1ed69b3a661e227ab8c11cf7e |
--- /dev/null |
+++ b/net/cert/internal/parse_certificate.cc |
@@ -0,0 +1,288 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "net/cert/internal/parse_certificate.h" |
+ |
+#include "net/der/input.h" |
+#include "net/der/parse_values.h" |
+#include "net/der/parser.h" |
+ |
+namespace net { |
+ |
+namespace { |
+ |
+// Parses a Version according to RFC 5280: |
+// |
+// Version ::= INTEGER { v1(0), v2(1), v3(2) } |
+// |
+// No value other that v1, v2, or v3 is allowed (and if given will fail). RFC |
+// 5280 minimally requires the handling of v3 (and overwhelmingly these are the |
+// certificate versions in use today): |
davidben
2015/08/11 20:31:56
Do we need to handle this unspecified v4 thing?
eroman
2015/08/11 21:13:33
From what I can tell v4 is not really a thing yet
davidben
2015/08/11 22:39:20
I know nothing about what v4 is. I think Ryan said
eroman
2015/08/12 00:37:10
Acknowledged. I will try and follow-up with v4 req
|
+// |
+// Implementations SHOULD be prepared to accept any version certificate. |
+// At a minimum, conforming implementations MUST recognize version 3 |
+// certificates. |
+// |
+WARN_UNUSED_RESULT bool ParseVersion(const der::Input& in, |
+ CertificateVersion* version) { |
+ der::Parser parser(in); |
+ uint64_t version64; |
+ if (!parser.ReadUint64(&version64)) |
+ return false; |
+ |
+ switch (version64) { |
+ case 0: |
+ *version = CertificateVersion::V1; |
+ break; |
+ case 1: |
+ *version = CertificateVersion::V2; |
+ break; |
+ case 2: |
+ *version = CertificateVersion::V3; |
+ break; |
+ default: |
+ // Don't allow any other version identifier. |
+ return false; |
+ } |
+ |
+ // By definition the input to this function was a single INTEGER, so there |
+ // shouldn't be anything else after it. |
+ return !parser.HasMore(); |
+} |
+ |
+// Returns true if the given serial number (CertificateSerialNumber in RFC 5280) |
+// is valid: |
+// |
+// CertificateSerialNumber ::= INTEGER |
+// |
+// The input to this function is the (unverified) value octets of the INTEGER. |
+// This function will verify that: |
+// |
+// * The octets are a valid DER-encoding of an INTEGER (for instance, minimal |
+// encoding length). |
+// |
+// * No more than 20 octets are used. |
+// |
+// Note that it DOES NOT reject non-positive values (zero or negative). |
+// |
+// For reference, here is what RFC 5280 section 4.1.2.2 says: |
+// |
+// Given the uniqueness requirements above, serial numbers can be |
+// expected to contain long integers. Certificate users MUST be able to |
+// handle serialNumber values up to 20 octets. Conforming CAs MUST NOT |
+// use serialNumber values longer than 20 octets. |
+// |
+// Note: Non-conforming CAs may issue certificates with serial numbers |
+// that are negative or zero. Certificate users SHOULD be prepared to |
+// gracefully handle such certificates. |
+WARN_UNUSED_RESULT bool VerifySerialNumber(const der::Input& value) { |
+ der::ByteReader reader(value); |
+ |
+ if (value.Length() == 0) |
+ return false; // Not a valid DER-encoded INTEGER. |
+ |
+ // Check if the serial number is too long per RFC 5280. |
+ if (value.Length() > 20) |
+ return false; |
+ |
+ // Accept any single-byte serial number (including zero and negatives). |
+ if (value.Length() == 1) |
+ return true; |
+ |
+ // INTEGER values in DER should be minimal. They should only contain a leading |
+ // zero if the second octet has its most significant bit set to 1 (since |
+ // without the leading zero the described number would be negative). |
+ uint8_t first_byte; |
+ if (!reader.ReadByte(&first_byte)) |
+ return false; // Unexpected |
+ |
+ if (first_byte == 0) { |
+ uint8_t second_byte; |
+ if (!reader.ReadByte(&second_byte)) |
+ return false; // Unexpected |
+ |
+ if ((second_byte & 0x80) == 0) |
+ return false; // MSB must be 1. |
+ } |
+ |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+ParsedTbsCertificate::ParsedTbsCertificate() |
+ : version(CertificateVersion::V1), |
+ has_issuer_unique_id(false), |
+ has_subject_unique_id(false), |
+ has_extensions(false) {} |
+ |
+ParsedTbsCertificate::~ParsedTbsCertificate() {} |
+ |
+bool ParseCertificate(const der::Input& certificate_tlv, |
+ ParsedCertificate* out) { |
davidben
2015/08/11 20:31:56
[I probably would have written this function in Bo
eroman
2015/08/11 21:13:33
This is a good point. I found it convenient in the
davidben
2015/08/11 22:39:20
Either's fine by me. I have noticed that Chromium
|
+ der::Parser parser(certificate_tlv); |
+ |
+ // Certificate ::= SEQUENCE { |
+ der::Parser certificate_parser; |
+ if (!parser.ReadSequence(&certificate_parser)) |
+ return false; |
+ |
+ // tbsCertificate TBSCertificate, |
+ if (!certificate_parser.ReadRawTLV(&out->tbs_certificate_tlv)) |
+ return false; |
+ |
+ // signatureAlgorithm AlgorithmIdentifier, |
+ if (!certificate_parser.ReadRawTLV(&out->signature_algorithm_tlv)) |
+ return false; |
+ |
+ // signatureValue BIT STRING } |
+ if (!certificate_parser.ReadBitString(&out->signature_value)) |
+ return false; |
+ |
+ // By definition the input was a single Certificate, so there shouldn't be |
+ // unconsumed data. |
+ if (parser.HasMore()) |
+ return false; |
+ |
+ // There isn't an extension point at the end of Certificate. |
+ if (certificate_parser.HasMore()) |
+ return false; |
davidben
2015/08/11 20:31:56
Nit: It seems better to check certificate_parser b
eroman
2015/08/11 21:13:33
Will do.
eroman
2015/08/12 00:37:10
Done.
|
+ |
+ return true; |
+} |
+ |
+// From RFC 5280 section 4.1: |
+// |
+// Certificate ::= SEQUENCE { |
+// tbsCertificate TBSCertificate, |
+// signatureAlgorithm AlgorithmIdentifier, |
+// signatureValue BIT STRING } |
+// |
+// TBSCertificate ::= SEQUENCE { |
+// version [0] EXPLICIT Version DEFAULT v1, |
+// serialNumber CertificateSerialNumber, |
+// signature AlgorithmIdentifier, |
+// issuer Name, |
+// validity Validity, |
+// subject Name, |
+// subjectPublicKeyInfo SubjectPublicKeyInfo, |
+// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, |
+// -- If present, version MUST be v2 or v3 |
+// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, |
+// -- If present, version MUST be v2 or v3 |
+// extensions [3] EXPLICIT Extensions OPTIONAL |
+// -- If present, version MUST be v3 |
+// } |
+bool ParseTbsCertificate(const der::Input& tbs_tlv, ParsedTbsCertificate* out) { |
+ der::Parser parser(tbs_tlv); |
+ |
+ // Certificate ::= SEQUENCE { |
+ der::Parser tbs_parser; |
+ if (!parser.ReadSequence(&tbs_parser)) |
+ return false; |
+ |
+ // version [0] EXPLICIT Version DEFAULT v1, |
+ der::Input version; |
+ bool has_version; |
+ if (!tbs_parser.ReadOptionalTag(der::ContextSpecificConstructed(0), &version, |
+ &has_version)) { |
+ return false; |
+ } |
+ if (has_version) { |
+ if (!ParseVersion(version, &out->version)) |
+ return false; |
davidben
2015/08/11 20:31:56
Although it seems we can't enforce it, there is a
eroman
2015/08/11 21:13:33
Good point!
I should enforce that here, and will d
davidben
2015/08/11 22:39:20
I believe mozilla::pkix doesn't. Although, actuall
eroman
2015/08/12 00:37:10
Acknowledged thanks for the context!
I have added
|
+ } else { |
+ out->version = CertificateVersion::V1; |
+ } |
+ |
+ // serialNumber CertificateSerialNumber, |
+ if (!tbs_parser.ReadTag(der::kInteger, &out->serial_number)) |
+ return false; |
+ if (!VerifySerialNumber(out->serial_number)) |
+ return false; |
+ |
+ // signature AlgorithmIdentifier, |
+ if (!tbs_parser.ReadRawTLV(&out->signature_algorithm_tlv)) |
+ return false; |
+ |
+ // issuer Name, |
+ if (!tbs_parser.ReadRawTLV(&out->issuer_tlv)) |
+ return false; |
+ |
+ // validity Validity, |
+ if (!tbs_parser.ReadRawTLV(&out->validity_tlv)) |
+ return false; |
+ |
+ // subject Name, |
+ if (!tbs_parser.ReadRawTLV(&out->subject_tlv)) |
+ return false; |
+ |
+ // subjectPublicKeyInfo SubjectPublicKeyInfo, |
+ if (!tbs_parser.ReadRawTLV(&out->spki_tlv)) |
+ return false; |
+ |
+ // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, |
+ // -- If present, version MUST be v2 or v3 |
+ der::Input issuer_unique_id; |
+ if (!tbs_parser.ReadOptionalTag(der::ContextSpecificPrimitive(1), |
+ &issuer_unique_id, |
+ &out->has_issuer_unique_id)) { |
+ return false; |
+ } |
+ if (out->has_issuer_unique_id) { |
+ if (!der::ParseBitString(issuer_unique_id, &out->issuer_unique_id)) |
davidben
2015/08/11 20:31:56
[Verified that ParseBitString does NOT expect a TL
|
+ return false; |
+ if (out->version != CertificateVersion::V2 && |
+ out->version != CertificateVersion::V3) { |
+ return false; |
+ } |
+ } |
+ |
+ // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, |
+ // -- If present, version MUST be v2 or v3 |
+ der::Input subject_unique_id; |
+ if (!tbs_parser.ReadOptionalTag(der::ContextSpecificPrimitive(2), |
+ &subject_unique_id, |
+ &out->has_subject_unique_id)) { |
+ return false; |
+ } |
+ if (out->has_subject_unique_id) { |
+ if (!der::ParseBitString(subject_unique_id, &out->subject_unique_id)) |
+ return false; |
+ if (out->version != CertificateVersion::V2 && |
+ out->version != CertificateVersion::V3) { |
+ return false; |
+ } |
+ } |
+ |
+ // extensions [3] EXPLICIT Extensions OPTIONAL |
+ // -- If present, version MUST be v3 |
+ if (!tbs_parser.ReadOptionalTag(der::ContextSpecificConstructed(3), |
+ &out->extensions_tlv, &out->has_extensions)) { |
+ return false; |
+ } |
+ if (out->has_extensions) { |
+ if (out->version != CertificateVersion::V3) |
+ return false; |
+ } |
+ |
+ // By definition the input was a single TBSCertificate, so there shouldn't be |
+ // unconsumed data. |
+ if (parser.HasMore()) |
+ return false; |
+ |
+ // Note that there IS an extension point at the end of TBSCertificate |
+ // (according to RFC 5912), so from that interpretation, unconsumed data would |
+ // be allowed in |tbs_parser|. |
+ // |
+ // However because only v1, v2, and v3 certificates are supported by the |
+ // parsing, there shouldn't be any subsequent data in those versions, so |
+ // reject. |
+ if (tbs_parser.HasMore()) |
+ return false; |
davidben
2015/08/11 20:31:56
Nit: Ditto re parser vs tbs_parser order.
eroman
2015/08/12 00:37:10
Done.
|
+ |
+ return true; |
+} |
+ |
+} // namespace net |