// Copyright (c) 2012 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/asn1_util.h" #include "net/der/input.h" #include "net/der/parser.h" namespace net { namespace asn1 { namespace { // Parses input |in| which should point to the beginning of a Certificate, and // sets |*tbs_certificate| ready to parse the SubjectPublicKeyInfo. If parsing // fails, this function returns false and |*tbs_certificate| is left in an // undefined state. bool SeekToSPKI(der::Input in, der::Parser* tbs_certificate) { // 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, // ... } der::Parser parser(in); der::Parser certificate; if (!parser.ReadSequence(&certificate)) return false; // We don't allow junk after the certificate. if (parser.HasMore()) return false; if (!certificate.ReadSequence(tbs_certificate)) return false; bool unused; if (!tbs_certificate->SkipOptionalTag( der::kTagConstructed | der::kTagContextSpecific | 0, &unused)) { return false; } // serialNumber if (!tbs_certificate->SkipTag(der::kInteger)) return false; // signature if (!tbs_certificate->SkipTag(der::kSequence)) return false; // issuer if (!tbs_certificate->SkipTag(der::kSequence)) return false; // validity if (!tbs_certificate->SkipTag(der::kSequence)) return false; // subject if (!tbs_certificate->SkipTag(der::kSequence)) return false; return true; } } // namespace bool ExtractSPKIFromDERCert(base::StringPiece cert, base::StringPiece* spki_out) { der::Parser parser; if (!SeekToSPKI(der::Input(cert), &parser)) return false; der::Input spki; if (!parser.ReadRawTLV(&spki)) return false; *spki_out = spki.AsStringPiece(); return true; } bool ExtractSubjectPublicKeyFromSPKI(base::StringPiece spki, base::StringPiece* spk_out) { // From RFC 5280, Section 4.1 // SubjectPublicKeyInfo ::= SEQUENCE { // algorithm AlgorithmIdentifier, // subjectPublicKey BIT STRING } // // AlgorithmIdentifier ::= SEQUENCE { // algorithm OBJECT IDENTIFIER, // parameters ANY DEFINED BY algorithm OPTIONAL } // Step into SubjectPublicKeyInfo sequence. der::Parser parser((der::Input(spki))); der::Parser spki_parser; if (!parser.ReadSequence(&spki_parser)) return false; // Step over algorithm field (a SEQUENCE). if (!spki_parser.SkipTag(der::kSequence)) return false; // Extract the subjectPublicKey field. der::Input spk; if (!spki_parser.ReadTag(der::kBitString, &spk)) return false; *spk_out = spk.AsStringPiece(); return true; } bool ExtractCRLURLsFromDERCert(base::StringPiece cert, std::vector<base::StringPiece>* urls_out) { urls_out->clear(); std::vector<base::StringPiece> tmp_urls_out; bool present; der::Parser tbs_cert_parser; if (!SeekToSPKI(der::Input(cert), &tbs_cert_parser)) return false; // From RFC 5280, section 4.1 // TBSCertificate ::= SEQUENCE { // ... // subjectPublicKeyInfo SubjectPublicKeyInfo, // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, // extensions [3] EXPLICIT Extensions OPTIONAL } // subjectPublicKeyInfo if (!tbs_cert_parser.SkipTag(der::kSequence)) return false; // issuerUniqueID if (!tbs_cert_parser.SkipOptionalTag( der::kTagConstructed | der::kTagContextSpecific | 1, &present)) { return false; } // subjectUniqueID if (!tbs_cert_parser.SkipOptionalTag( der::kTagConstructed | der::kTagContextSpecific | 2, &present)) { return false; } der::Input extensions; if (!tbs_cert_parser.ReadOptionalTag( der::kTagConstructed | der::kTagContextSpecific | 3, &extensions, &present)) { return false; } if (!present) return true; // Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension // Extension ::= SEQUENCE { // extnID OBJECT IDENTIFIER, // critical BOOLEAN DEFAULT FALSE, // extnValue OCTET STRING } // |extensions| was EXPLICITly tagged, so we still need to remove the // ASN.1 SEQUENCE header. der::Parser explicit_extensions_parser(extensions); der::Parser extensions_parser; if (!explicit_extensions_parser.ReadSequence(&extensions_parser)) return false; if (explicit_extensions_parser.HasMore()) return false; while (extensions_parser.HasMore()) { der::Parser extension_parser; if (!extensions_parser.ReadSequence(&extension_parser)) return false; der::Input oid; if (!extension_parser.ReadTag(der::kOid, &oid)) return false; // kCRLDistributionPointsOID is the DER encoding of the OID for the X.509 // CRL Distribution Points extension. static const uint8_t kCRLDistributionPointsOID[] = {0x55, 0x1d, 0x1f}; if (oid != der::Input(kCRLDistributionPointsOID)) continue; // critical if (!extension_parser.SkipOptionalTag(der::kBool, &present)) return false; // extnValue der::Input extension_value; if (!extension_parser.ReadTag(der::kOctetString, &extension_value)) return false; // RFC 5280, section 4.2.1.13. // // CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint // // DistributionPoint ::= SEQUENCE { // distributionPoint [0] DistributionPointName OPTIONAL, // reasons [1] ReasonFlags OPTIONAL, // cRLIssuer [2] GeneralNames OPTIONAL } der::Parser extension_value_parser(extension_value); der::Parser distribution_points_parser; if (!extension_value_parser.ReadSequence(&distribution_points_parser)) return false; if (extension_value_parser.HasMore()) return false; while (distribution_points_parser.HasMore()) { der::Parser distrib_point_parser; if (!distribution_points_parser.ReadSequence(&distrib_point_parser)) return false; der::Input name; if (!distrib_point_parser.ReadOptionalTag( der::kTagContextSpecific | der::kTagConstructed | 0, &name, &present)) { return false; } // If it doesn't contain a name then we skip it. if (!present) continue; if (!distrib_point_parser.SkipOptionalTag(der::kTagContextSpecific | 1, &present)) { return false; } // If it contains a subset of reasons then we skip it. We aren't // interested in subsets of CRLs and the RFC states that there MUST be // a CRL that covers all reasons. if (present) continue; if (!distrib_point_parser.SkipOptionalTag( der::kTagContextSpecific | der::kTagConstructed | 2, &present)) { return false; } // If it contains a alternative issuer, then we skip it. if (present) continue; // DistributionPointName ::= CHOICE { // fullName [0] GeneralNames, // nameRelativeToCRLIssuer [1] RelativeDistinguishedName } der::Input general_names; if (!der::Parser(name).ReadOptionalTag( der::kTagContextSpecific | der::kTagConstructed | 0, &general_names, &present)) { return false; } if (!present) continue; // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName // GeneralName ::= CHOICE { // ... // uniformResourceIdentifier [6] IA5String, // ... } der::Parser general_names_parser(general_names); while (general_names_parser.HasMore()) { der::Input url; if (!general_names_parser.ReadOptionalTag(der::kTagContextSpecific | 6, &url, &present)) { return false; } if (present) { // This does not validate that |url| is a valid IA5String. tmp_urls_out.push_back(url.AsStringPiece()); } else { der::Tag unused_tag; der::Input unused_value; if (!general_names_parser.ReadTagAndValue(&unused_tag, &unused_value)) { return false; } } } } } urls_out->swap(tmp_urls_out); return true; } } // namespace asn1 } // namespace net