Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(171)

Unified Diff: net/cert/internal/parse_certificate.cc

Issue 1279963003: Add a function for parsing RFC 5280's "TBSCertificate". (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@cert_mapper
Patch Set: one more comment fix Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/cert/internal/parse_certificate.h ('k') | net/cert/internal/parse_certificate_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: net/cert/internal/parse_certificate.cc
diff --git a/net/cert/internal/parse_certificate.cc b/net/cert/internal/parse_certificate.cc
index c8b58c07606cfcdeb0c1240fc358598d87cc972d..e04adb89d48640716b0d7c1002b71bbb0a8fdf9f 100644
--- a/net/cert/internal/parse_certificate.cc
+++ b/net/cert/internal/parse_certificate.cc
@@ -28,8 +28,87 @@ WARN_UNUSED_RESULT bool ReadSequenceTLV(der::Parser* parser, der::Input* out) {
return parser->ReadRawTLV(out) && IsSequenceTLV(*out);
}
+// 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):
+//
+// 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) {
+ bool unused_negative;
+ if (!der::IsValidInteger(value, &unused_negative))
+ return false;
+
+ // Check if the serial number is too long per RFC 5280.
+ if (value.Length() > 20)
+ return false;
+
+ return true;
+}
+
} // namespace
+ParsedTbsCertificate::ParsedTbsCertificate() {}
+
+ParsedTbsCertificate::~ParsedTbsCertificate() {}
+
bool ParseCertificate(const der::Input& certificate_tlv,
ParsedCertificate* out) {
der::Parser parser(certificate_tlv);
@@ -63,4 +142,141 @@ bool ParseCertificate(const der::Input& certificate_tlv,
return true;
}
+// From RFC 5280 section 4.1:
+//
+// 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;
+ if (out->version == CertificateVersion::V1) {
+ // The correct way to specify v1 is to omit the version field since v1 is
+ // the DEFAULT.
+ return false;
+ }
+ } 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 (!ReadSequenceTLV(&tbs_parser, &out->signature_algorithm_tlv))
+ return false;
+
+ // issuer Name,
+ if (!ReadSequenceTLV(&tbs_parser, &out->issuer_tlv))
+ return false;
+
+ // validity Validity,
+ if (!ReadSequenceTLV(&tbs_parser, &out->validity_tlv))
+ return false;
+
+ // subject Name,
+ if (!ReadSequenceTLV(&tbs_parser, &out->subject_tlv))
+ return false;
+
+ // subjectPublicKeyInfo SubjectPublicKeyInfo,
+ if (!ReadSequenceTLV(&tbs_parser, &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))
+ 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) {
+ // extensions_tlv must be a single element. Also check that it is a
+ // SEQUENCE.
+ if (!IsSequenceTLV(out->extensions_tlv))
+ return false;
+ if (out->version != CertificateVersion::V3)
+ 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;
+
+ // By definition the input was a single TBSCertificate, so there shouldn't be
+ // unconsumed data.
+ if (parser.HasMore())
+ return false;
+
+ return true;
+}
+
} // namespace net
« no previous file with comments | « net/cert/internal/parse_certificate.h ('k') | net/cert/internal/parse_certificate_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698