| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/common/net/x509_certificate_model.h" |
| |
| #include <string_view> |
| #include <variant> |
| |
| #include "base/check.h" |
| #include "base/containers/adapters.h" |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/i18n/number_formatting.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "components/url_formatter/url_formatter.h" |
| #include "crypto/sha2.h" |
| #include "net/base/ip_address.h" |
| #include "net/cert/ct_objects_extractor.h" |
| #include "net/cert/qwac.h" |
| #include "net/cert/time_conversions.h" |
| #include "net/cert/x509_util.h" |
| #include "third_party/boringssl/src/include/openssl/bn.h" |
| #include "third_party/boringssl/src/include/openssl/bytestring.h" |
| #include "third_party/boringssl/src/include/openssl/mem.h" |
| #include "third_party/boringssl/src/include/openssl/rsa.h" |
| #include "third_party/boringssl/src/pki/cert_errors.h" |
| #include "third_party/boringssl/src/pki/certificate_policies.h" |
| #include "third_party/boringssl/src/pki/extended_key_usage.h" |
| #include "third_party/boringssl/src/pki/input.h" |
| #include "third_party/boringssl/src/pki/parse_certificate.h" |
| #include "third_party/boringssl/src/pki/parse_name.h" |
| #include "third_party/boringssl/src/pki/parse_values.h" |
| #include "third_party/boringssl/src/pki/parser.h" |
| #include "third_party/boringssl/src/pki/signature_algorithm.h" |
| #include "third_party/boringssl/src/pki/verify_signed_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace x509_certificate_model { |
| |
| namespace { |
| |
| // 2.5.4.46 NAME 'dnQualifier' |
| constexpr uint8_t kTypeDnQualifierOid[] = {0x55, 0x04, 0x2e}; |
| // 2.5.4.15 NAME 'businessCategory' |
| constexpr uint8_t kTypeBusinessCategory[] = {0x55, 0x04, 0x0f}; |
| // 2.5.4.17 NAME 'postalCode' |
| constexpr uint8_t kTypePostalCode[] = {0x55, 0x04, 0x11}; |
| |
| // TODO(mattm): we can probably just remove these RFC 1274 OIDs. |
| // |
| // ccitt is {0} but not explicitly defined in the RFC 1274. |
| // RFC 1274: |
| // data OBJECT IDENTIFIER ::= {ccitt 9} |
| // pss OBJECT IDENTIFIER ::= {data 2342} |
| // ucl OBJECT IDENTIFIER ::= {pss 19200300} |
| // pilot OBJECT IDENTIFIER ::= {ucl 100} |
| // pilotAttributeType OBJECT IDENTIFIER ::= {pilot 1} |
| // userid ::= {pilotAttributeType 1} |
| constexpr uint8_t kRFC1274UidOid[] = {0x09, 0x92, 0x26, 0x89, 0x93, |
| 0xf2, 0x2c, 0x64, 0x01, 0x01}; |
| // rfc822Mailbox :: = {pilotAttributeType 3} |
| constexpr uint8_t kRFC1274MailOid[] = {0x09, 0x92, 0x26, 0x89, 0x93, |
| 0xf2, 0x2c, 0x64, 0x01, 0x03}; |
| |
| // jurisdictionLocalityName (OID: 1.3.6.1.4.1.311.60.2.1.1) |
| constexpr uint8_t kEVJurisdictionLocalityName[] = { |
| 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x3c, 0x02, 0x01, 0x01}; |
| // jurisdictionStateOrProvinceName (OID: 1.3.6.1.4.1.311.60.2.1.2) |
| constexpr uint8_t kEVJurisdictionStateOrProvinceName[] = { |
| 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x3c, 0x02, 0x01, 0x02}; |
| // jurisdictionCountryName (OID: 1.3.6.1.4.1.311.60.2.1.3) |
| constexpr uint8_t kEVJurisdictionCountryName[] = { |
| 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x3c, 0x02, 0x01, 0x03}; |
| |
| // From RFC 5280: |
| // id-ce-issuerAltName OBJECT IDENTIFIER ::= { id-ce 18 } |
| // In dotted notation: 2.5.29.18 |
| constexpr uint8_t kIssuerAltNameOid[] = {0x55, 0x1d, 0x12}; |
| // From RFC 5280: |
| // id-ce-subjectDirectoryAttributes OBJECT IDENTIFIER ::= { id-ce 9 } |
| // In dotted notation: 2.5.29.9 |
| constexpr uint8_t kSubjectDirectoryAttributesOid[] = {0x55, 0x1d, 0x09}; |
| |
| // From RFC 3447: |
| // pkcs-1 OBJECT IDENTIFIER ::= { |
| // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 |
| // } |
| // rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 } |
| constexpr uint8_t kPkcs1RsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x0d, 0x01, 0x01, 0x01}; |
| // md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 } |
| constexpr uint8_t kPkcs1Md2WithRsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x0d, 0x01, 0x01, 0x02}; |
| // From RFC 2314: md4WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 3 } |
| constexpr uint8_t kPkcs1Md4WithRsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x0d, 0x01, 0x01, 0x03}; |
| // md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 } |
| constexpr uint8_t kPkcs1Md5WithRsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x0d, 0x01, 0x01, 0x04}; |
| // sha1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 } |
| constexpr uint8_t kPkcs1Sha1WithRsaEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x0d, 0x01, 0x01, 0x05}; |
| // sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 } |
| constexpr uint8_t kPkcs1Sha256WithRsaEncryption[] = { |
| 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b}; |
| // sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 } |
| constexpr uint8_t kPkcs1Sha384WithRsaEncryption[] = { |
| 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c}; |
| // sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 } |
| constexpr uint8_t kPkcs1Sha512WithRsaEncryption[] = { |
| 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d}; |
| // From RFC 3279: |
| // ansi-X9-62 OBJECT IDENTIFIER ::= { |
| // iso(1) member-body(2) us(840) 10045 } |
| // id-ecSigType OBJECT IDENTIFIER ::= { |
| // ansi-X9-62 signatures(4) } |
| // ecdsa-with-SHA1 OBJECT IDENTIFIER ::= { |
| // id-ecSigType 1 } |
| constexpr uint8_t kAnsiX962EcdsaWithSha1[] = {0x2a, 0x86, 0x48, 0xce, |
| 0x3d, 0x04, 0x01}; |
| // From RFC 5758: |
| // ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { iso(1) member-body(2) |
| // us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 2 } |
| constexpr uint8_t kAnsiX962EcdsaWithSha256[] = {0x2a, 0x86, 0x48, 0xce, |
| 0x3d, 0x04, 0x03, 0x02}; |
| // ecdsa-with-SHA384 OBJECT IDENTIFIER ::= { iso(1) member-body(2) |
| // us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 3 } |
| constexpr uint8_t kAnsiX962EcdsaWithSha384[] = {0x2a, 0x86, 0x48, 0xce, |
| 0x3d, 0x04, 0x03, 0x03}; |
| // ecdsa-with-SHA512 OBJECT IDENTIFIER ::= { iso(1) member-body(2) |
| // us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 4 } |
| constexpr uint8_t kAnsiX962EcdsaWithSha512[] = {0x2a, 0x86, 0x48, 0xce, |
| 0x3d, 0x04, 0x03, 0x04}; |
| // From RFC 3279: |
| // ansi-X9-62 OBJECT IDENTIFIER ::= |
| // { iso(1) member-body(2) us(840) 10045 } |
| // id-public-key-type OBJECT IDENTIFIER ::= { ansi-X9.62 2 } |
| // id-ecPublicKey OBJECT IDENTIFIER ::= { id-publicKeyType 1 } |
| constexpr uint8_t kAnsiX962EcPublicKey[] = {0x2a, 0x86, 0x48, 0xce, |
| 0x3d, 0x02, 0x01}; |
| // From RFC 5480: |
| // secp256r1 OBJECT IDENTIFIER ::= { |
| // iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3) |
| // prime(1) 7 } |
| constexpr uint8_t kSecgEcSecp256r1[] = {0x2a, 0x86, 0x48, 0xce, |
| 0x3d, 0x03, 0x01, 0x07}; |
| // secp384r1 OBJECT IDENTIFIER ::= { |
| // iso(1) identified-organization(3) certicom(132) curve(0) 34 } |
| constexpr uint8_t kSecgEcSecp384r1[] = {0x2b, 0x81, 0x04, 0x00, 0x22}; |
| // secp521r1 OBJECT IDENTIFIER ::= { |
| // iso(1) identified-organization(3) certicom(132) curve(0) 35 } |
| constexpr uint8_t kSecgEcSecp512r1[] = {0x2b, 0x81, 0x04, 0x00, 0x23}; |
| |
| // Old Netscape OIDs. Do we still need all these? |
| // #define NETSCAPE_OID 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42 |
| // #define NETSCAPE_CERT_EXT NETSCAPE_OID, 0x01 |
| // |
| // CONST_OID nsExtCertType[] = { NETSCAPE_CERT_EXT, 0x01 }; |
| constexpr uint8_t kNetscapeCertificateTypeOid[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x01, 0x01}; |
| |
| // CONST_OID nsExtBaseURL[] = { NETSCAPE_CERT_EXT, 0x02 }; |
| constexpr uint8_t kNetscapeBaseURLOid[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x01, 0x02}; |
| |
| // CONST_OID nsExtRevocationURL[] = { NETSCAPE_CERT_EXT, 0x03 }; |
| constexpr uint8_t kNetscapeRevocationURLOid[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x01, 0x03}; |
| |
| // CONST_OID nsExtCARevocationURL[] = { NETSCAPE_CERT_EXT, 0x04 }; |
| constexpr uint8_t kNetscapeCARevocationURLOid[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x01, 0x04}; |
| |
| // CONST_OID nsExtCACertURL[] = { NETSCAPE_CERT_EXT, 0x06 }; |
| constexpr uint8_t kNetscapeCACertURLOid[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x01, 0x06}; |
| |
| // CONST_OID nsExtCertRenewalURL[] = { NETSCAPE_CERT_EXT, 0x07 }; |
| constexpr uint8_t kNetscapeRenewalURLOid[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x01, 0x07}; |
| |
| // CONST_OID nsExtCAPolicyURL[] = { NETSCAPE_CERT_EXT, 0x08 }; |
| constexpr uint8_t kNetscapeCAPolicyURLOid[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x01, 0x08}; |
| |
| // CONST_OID nsExtSSLServerName[] = { NETSCAPE_CERT_EXT, 0x0c }; |
| constexpr uint8_t kNetscapeSSLServerNameOid[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x01, 0x0c}; |
| |
| // CONST_OID nsExtComment[] = { NETSCAPE_CERT_EXT, 0x0d }; |
| constexpr uint8_t kNetscapeCommentOid[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x01, 0x0d}; |
| |
| // CONST_OID nsExtLostPasswordURL[] = { NETSCAPE_CERT_EXT, 0x0e }; |
| constexpr uint8_t kNetscapeLostPasswordURLOid[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x01, 0x0e}; |
| |
| // CONST_OID nsExtCertRenewalTime[] = { NETSCAPE_CERT_EXT, 0x0f }; |
| constexpr uint8_t kNetscapeRenewalTimeOid[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x01, 0x0f}; |
| |
| constexpr uint8_t kNetscapeServerGatedCrypto[] = {0x60, 0x86, 0x48, 0x01, 0x86, |
| 0xf8, 0x42, 0x04, 0x01}; |
| |
| // Microsoft OIDs. Do we still need all these? |
| // |
| // 1.3.6.1.4.1.311.20.2 |
| constexpr uint8_t kMsCertExtCerttype[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x14, 0x02}; |
| |
| // 1.3.6.1.4.1.311.21.1 |
| constexpr uint8_t kMsCertsrvCaVersion[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x15, 0x01}; |
| |
| // 1.3.6.1.4.1.311.20.2.3 |
| constexpr uint8_t kMsNtPrincipalName[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x14, 0x02, 0x03}; |
| |
| // 1.3.6.1.4.1.311.25.1 |
| constexpr uint8_t kMsNtdsReplication[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x19, 0x01}; |
| |
| // 1.3.6.1.4.1.311.21.7 |
| constexpr uint8_t kMsCertTemplate[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x15, 0x07}; |
| |
| // 1.3.6.1.4.1.311.2.1.21 |
| constexpr uint8_t kEkuMsIndividualCodeSigning[] = { |
| 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x15}; |
| |
| // 1.3.6.1.4.1.311.2.1.22 |
| constexpr uint8_t kEkuMsCommercialCodeSigning[] = { |
| 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x16}; |
| |
| // 1.3.6.1.4.1.311.10.3.1 |
| constexpr uint8_t kEkuMsTrustListSigning[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x0a, 0x03, 0x01}; |
| |
| // 1.3.6.1.4.1.311.10.3.2 |
| constexpr uint8_t kEkuMsTimeStamping[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x0a, 0x03, 0x02}; |
| |
| // 1.3.6.1.4.1.311.10.3.3 |
| constexpr uint8_t kEkuMsServerGatedCrypto[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x0a, 0x03, 0x03}; |
| |
| // 1.3.6.1.4.1.311.10.3.4 |
| constexpr uint8_t kEkuMsEncryptingFileSystem[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x0a, 0x03, 0x04}; |
| |
| // 1.3.6.1.4.1.311.10.3.4.1 |
| constexpr uint8_t kEkuMsFileRecovery[] = {0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, |
| 0x37, 0x0a, 0x03, 0x04, 0x01}; |
| |
| // 1.3.6.1.4.1.311.10.3.5 |
| constexpr uint8_t kEkuMsWindowsHardwareDriverVerification[] = { |
| 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x05}; |
| |
| // 1.3.6.1.4.1.311.10.3.10 |
| constexpr uint8_t kEkuMsQualifiedSubordination[] = { |
| 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x0a}; |
| |
| // 1.3.6.1.4.1.311.10.3.11 |
| constexpr uint8_t kEkuMsKeyRecovery[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x0a, 0x03, 0x0b}; |
| |
| // 1.3.6.1.4.1.311.10.3.12 |
| constexpr uint8_t kEkuMsDocumentSigning[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x0a, 0x03, 0x0c}; |
| |
| // 1.3.6.1.4.1.311.10.3.13 |
| constexpr uint8_t kEkuMsLifetimeSigning[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x0a, 0x03, 0x0d}; |
| |
| // 1.3.6.1.4.1.311.20.2.2 |
| constexpr uint8_t kEkuMsSmartCardLogon[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x14, 0x02, 0x02}; |
| |
| // 1.3.6.1.4.1.311.21.6 |
| constexpr uint8_t kEkuMsKeyRecoveryAgent[] = {0x2b, 0x06, 0x01, 0x04, 0x01, |
| 0x82, 0x37, 0x15, 0x06}; |
| |
| // The certificate viewer may be used to view client certificates, so use the |
| // relaxed parsing mode. See crbug.com/770323 and crbug.com/788655. |
| constexpr auto kNameStringHandling = |
| bssl::X509NameAttribute::PrintableStringHandling::kAsUTF8Hack; |
| |
| std::string ProcessRawBytesWithSeparators(base::span<const unsigned char> data, |
| char hex_separator, |
| char line_separator) { |
| // Each input byte creates two output hex characters + a space or newline, |
| // except for the last byte. |
| std::string ret; |
| size_t kMin = 0U; |
| |
| if (data.empty()) { |
| return std::string(); |
| } |
| |
| ret.reserve(std::max(kMin, data.size() * 3 - 1)); |
| |
| for (size_t i = 0; i < data.size(); ++i) { |
| base::AppendHexEncodedByte(data[i], ret); |
| if (i + 1 < data.size()) { |
| ret.push_back(((i + 1) % 16) ? hex_separator : line_separator); |
| } |
| } |
| return ret; |
| } |
| |
| std::string ProcessRawBytes(base::span<const uint8_t> data) { |
| return ProcessRawBytesWithSeparators(data, ' ', '\n'); |
| } |
| |
| OptionalStringOrError FindAttributeOfType( |
| bssl::der::Input oid, |
| const bssl::RelativeDistinguishedName& rdn) { |
| // In X.509, RelativeDistinguishedName is a Set, so order has no meaning, and |
| // generally only has one element anyway. Just traverse in encoded order. |
| for (const bssl::X509NameAttribute& name_attribute : rdn) { |
| if (name_attribute.type == oid) { |
| std::string rv; |
| if (!name_attribute.ValueAsStringWithUnsafeOptions(kNameStringHandling, |
| &rv)) { |
| return Error(); |
| } |
| // TODO(mattm): do something about newlines (or other control chars)? |
| return rv; |
| } |
| } |
| return NotPresent(); |
| } |
| |
| // Returns the value of the most general name of |oid| type. |
| // Distinguished Names are specified in least to most specific. |
| OptionalStringOrError FindFirstNameOfType(bssl::der::Input oid, |
| const bssl::RDNSequence& rdns) { |
| for (const bssl::RelativeDistinguishedName& rdn : rdns) { |
| OptionalStringOrError r = FindAttributeOfType(oid, rdn); |
| if (!std::holds_alternative<NotPresent>(r)) { |
| return r; |
| } |
| } |
| return NotPresent(); |
| } |
| |
| // Returns the value of the most specific name of |oid| type. |
| // Distinguished Names are specified in least to most specific. |
| OptionalStringOrError FindLastNameOfType(bssl::der::Input oid, |
| const bssl::RDNSequence& rdns) { |
| for (const bssl::RelativeDistinguishedName& rdn : base::Reversed(rdns)) { |
| OptionalStringOrError r = FindAttributeOfType(oid, rdn); |
| if (!std::holds_alternative<NotPresent>(r)) { |
| return r; |
| } |
| } |
| return NotPresent(); |
| } |
| |
| // Returns a string containing the dotted numeric form of |oid| prefixed by |
| // "OID.", or an empty string on error. |
| std::string OidToNumericString(bssl::der::Input oid) { |
| CBS cbs; |
| CBS_init(&cbs, oid.data(), oid.size()); |
| bssl::UniquePtr<char> text(CBS_asn1_oid_to_text(&cbs)); |
| if (!text) |
| return std::string(); |
| return std::string("OID.") + text.get(); |
| } |
| |
| constexpr auto kOidStringMap = base::MakeFixedFlatMap<bssl::der::Input, int>({ |
| // Distinguished Name fields: |
| {bssl::der::Input(bssl::kTypeCommonNameOid), IDS_CERT_OID_AVA_COMMON_NAME}, |
| {bssl::der::Input(bssl::kTypeStateOrProvinceNameOid), |
| IDS_CERT_OID_AVA_STATE_OR_PROVINCE}, |
| {bssl::der::Input(bssl::kTypeOrganizationNameOid), |
| IDS_CERT_OID_AVA_ORGANIZATION_NAME}, |
| {bssl::der::Input(bssl::kTypeOrganizationUnitNameOid), |
| IDS_CERT_OID_AVA_ORGANIZATIONAL_UNIT_NAME}, |
| {bssl::der::Input(kTypeDnQualifierOid), IDS_CERT_OID_AVA_DN_QUALIFIER}, |
| {bssl::der::Input(bssl::kTypeCountryNameOid), |
| IDS_CERT_OID_AVA_COUNTRY_NAME}, |
| {bssl::der::Input(bssl::kTypeSerialNumberOid), |
| IDS_CERT_OID_AVA_SERIAL_NUMBER}, |
| {bssl::der::Input(bssl::kTypeLocalityNameOid), IDS_CERT_OID_AVA_LOCALITY}, |
| {bssl::der::Input(bssl::kTypeDomainComponentOid), IDS_CERT_OID_AVA_DC}, |
| {bssl::der::Input(kRFC1274MailOid), IDS_CERT_OID_RFC1274_MAIL}, |
| {bssl::der::Input(kRFC1274UidOid), IDS_CERT_OID_RFC1274_UID}, |
| {bssl::der::Input(bssl::kTypeEmailAddressOid), |
| IDS_CERT_OID_PKCS9_EMAIL_ADDRESS}, |
| |
| // Extended Validation (EV) name fields: |
| {bssl::der::Input(kTypeBusinessCategory), IDS_CERT_OID_BUSINESS_CATEGORY}, |
| {bssl::der::Input(kEVJurisdictionLocalityName), |
| IDS_CERT_OID_EV_INCORPORATION_LOCALITY}, |
| {bssl::der::Input(kEVJurisdictionStateOrProvinceName), |
| IDS_CERT_OID_EV_INCORPORATION_STATE}, |
| {bssl::der::Input(kEVJurisdictionCountryName), |
| IDS_CERT_OID_EV_INCORPORATION_COUNTRY}, |
| {bssl::der::Input(bssl::kTypeStreetAddressOid), |
| IDS_CERT_OID_AVA_STREET_ADDRESS}, |
| {bssl::der::Input(kTypePostalCode), IDS_CERT_OID_AVA_POSTAL_CODE}, |
| |
| // Algorithm fields: |
| {bssl::der::Input(kPkcs1RsaEncryption), IDS_CERT_OID_PKCS1_RSA_ENCRYPTION}, |
| {bssl::der::Input(kPkcs1Md2WithRsaEncryption), |
| IDS_CERT_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION}, |
| {bssl::der::Input(kPkcs1Md4WithRsaEncryption), |
| IDS_CERT_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION}, |
| {bssl::der::Input(kPkcs1Md5WithRsaEncryption), |
| IDS_CERT_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION}, |
| {bssl::der::Input(kPkcs1Sha1WithRsaEncryption), |
| IDS_CERT_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION}, |
| {bssl::der::Input(kPkcs1Sha256WithRsaEncryption), |
| IDS_CERT_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION}, |
| {bssl::der::Input(kPkcs1Sha384WithRsaEncryption), |
| IDS_CERT_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION}, |
| {bssl::der::Input(kPkcs1Sha512WithRsaEncryption), |
| IDS_CERT_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION}, |
| {bssl::der::Input(kAnsiX962EcdsaWithSha1), |
| IDS_CERT_OID_ANSIX962_ECDSA_SHA1_SIGNATURE}, |
| {bssl::der::Input(kAnsiX962EcdsaWithSha256), |
| IDS_CERT_OID_ANSIX962_ECDSA_SHA256_SIGNATURE}, |
| {bssl::der::Input(kAnsiX962EcdsaWithSha384), |
| IDS_CERT_OID_ANSIX962_ECDSA_SHA384_SIGNATURE}, |
| {bssl::der::Input(kAnsiX962EcdsaWithSha512), |
| IDS_CERT_OID_ANSIX962_ECDSA_SHA512_SIGNATURE}, |
| {bssl::der::Input(kAnsiX962EcPublicKey), |
| IDS_CERT_OID_ANSIX962_EC_PUBLIC_KEY}, |
| {bssl::der::Input(kSecgEcSecp256r1), IDS_CERT_OID_SECG_EC_SECP256R1}, |
| {bssl::der::Input(kSecgEcSecp384r1), IDS_CERT_OID_SECG_EC_SECP384R1}, |
| {bssl::der::Input(kSecgEcSecp512r1), IDS_CERT_OID_SECG_EC_SECP521R1}, |
| |
| // Extension fields (including details of extensions): |
| {bssl::der::Input(kNetscapeCertificateTypeOid), IDS_CERT_EXT_NS_CERT_TYPE}, |
| {bssl::der::Input(kNetscapeBaseURLOid), IDS_CERT_EXT_NS_CERT_BASE_URL}, |
| {bssl::der::Input(kNetscapeRevocationURLOid), |
| IDS_CERT_EXT_NS_CERT_REVOCATION_URL}, |
| {bssl::der::Input(kNetscapeCARevocationURLOid), |
| IDS_CERT_EXT_NS_CA_REVOCATION_URL}, |
| {bssl::der::Input(kNetscapeRenewalURLOid), |
| IDS_CERT_EXT_NS_CERT_RENEWAL_URL}, |
| {bssl::der::Input(kNetscapeCAPolicyURLOid), IDS_CERT_EXT_NS_CA_POLICY_URL}, |
| {bssl::der::Input(kNetscapeSSLServerNameOid), |
| IDS_CERT_EXT_NS_SSL_SERVER_NAME}, |
| {bssl::der::Input(kNetscapeCommentOid), IDS_CERT_EXT_NS_COMMENT}, |
| {bssl::der::Input(kNetscapeLostPasswordURLOid), |
| IDS_CERT_EXT_NS_LOST_PASSWORD_URL}, |
| {bssl::der::Input(kNetscapeRenewalTimeOid), |
| IDS_CERT_EXT_NS_CERT_RENEWAL_TIME}, |
| {bssl::der::Input(kSubjectDirectoryAttributesOid), |
| IDS_CERT_X509_SUBJECT_DIRECTORY_ATTR}, |
| {bssl::der::Input(bssl::kSubjectKeyIdentifierOid), |
| IDS_CERT_X509_SUBJECT_KEYID}, |
| {bssl::der::Input(bssl::kAuthorityKeyIdentifierOid), |
| IDS_CERT_X509_AUTH_KEYID}, |
| {bssl::der::Input(bssl::kKeyUsageOid), IDS_CERT_X509_KEY_USAGE}, |
| {bssl::der::Input(bssl::kSubjectAltNameOid), |
| IDS_CERT_X509_SUBJECT_ALT_NAME}, |
| {bssl::der::Input(kIssuerAltNameOid), IDS_CERT_X509_ISSUER_ALT_NAME}, |
| {bssl::der::Input(bssl::kBasicConstraintsOid), |
| IDS_CERT_X509_BASIC_CONSTRAINTS}, |
| {bssl::der::Input(bssl::kNameConstraintsOid), |
| IDS_CERT_X509_NAME_CONSTRAINTS}, |
| {bssl::der::Input(bssl::kCrlDistributionPointsOid), |
| IDS_CERT_X509_CRL_DIST_POINTS}, |
| {bssl::der::Input(bssl::kCertificatePoliciesOid), |
| IDS_CERT_X509_CERT_POLICIES}, |
| {bssl::der::Input(bssl::kPolicyMappingsOid), IDS_CERT_X509_POLICY_MAPPINGS}, |
| {bssl::der::Input(bssl::kPolicyConstraintsOid), |
| IDS_CERT_X509_POLICY_CONSTRAINTS}, |
| {bssl::der::Input(bssl::kExtKeyUsageOid), IDS_CERT_X509_EXT_KEY_USAGE}, |
| {bssl::der::Input(bssl::kAuthorityInfoAccessOid), |
| IDS_CERT_X509_AUTH_INFO_ACCESS}, |
| {bssl::der::Input(bssl::kCpsPointerId), |
| IDS_CERT_PKIX_CPS_POINTER_QUALIFIER}, |
| {bssl::der::Input(bssl::kUserNoticeId), |
| IDS_CERT_PKIX_USER_NOTICE_QUALIFIER}, |
| {bssl::der::Input(net::ct::kEmbeddedSCTOid), IDS_CERT_X509_SCT_LIST}, |
| {bssl::der::Input(net::kQcStatementsOid), IDS_CERT_QC_STATEMENTS}, |
| |
| // Extended Key Usages: |
| {bssl::der::Input(bssl::kAnyEKU), IDS_CERT_EKU_ANY_EKU}, |
| {bssl::der::Input(bssl::kServerAuth), |
| IDS_CERT_EKU_TLS_WEB_SERVER_AUTHENTICATION}, |
| {bssl::der::Input(bssl::kClientAuth), |
| IDS_CERT_EKU_TLS_WEB_CLIENT_AUTHENTICATION}, |
| {bssl::der::Input(bssl::kCodeSigning), IDS_CERT_EKU_CODE_SIGNING}, |
| {bssl::der::Input(bssl::kEmailProtection), IDS_CERT_EKU_EMAIL_PROTECTION}, |
| {bssl::der::Input(bssl::kTimeStamping), IDS_CERT_EKU_TIME_STAMPING}, |
| {bssl::der::Input(bssl::kOCSPSigning), IDS_CERT_EKU_OCSP_SIGNING}, |
| {bssl::der::Input(kNetscapeServerGatedCrypto), |
| IDS_CERT_EKU_NETSCAPE_INTERNATIONAL_STEP_UP}, |
| |
| // Policies: |
| {bssl::der::Input(net::kQevcpwOid), IDS_CERT_POLICY_ETSI_QEVCP_W}, |
| {bssl::der::Input(net::kQncpwOid), IDS_CERT_POLICY_ETSI_QNCP_W}, |
| |
| // QcStatements: |
| {bssl::der::Input(net::kEtsiQcsQcComplianceOid), |
| IDS_CERT_QC_ETSI_QCS_QCCOMPLIANCE}, |
| {bssl::der::Input(net::kEtsiQcsQcTypeOid), IDS_CERT_QC_ETSI_QCS_QCTYPE}, |
| {bssl::der::Input(net::kEtsiQctWebOid), IDS_CERT_QC_ETSI_QCT_WEB}, |
| |
| // Microsoft oids: |
| {bssl::der::Input(kMsCertExtCerttype), IDS_CERT_EXT_MS_CERT_TYPE}, |
| {bssl::der::Input(kMsCertsrvCaVersion), IDS_CERT_EXT_MS_CA_VERSION}, |
| {bssl::der::Input(kMsNtPrincipalName), IDS_CERT_EXT_MS_NT_PRINCIPAL_NAME}, |
| {bssl::der::Input(kMsNtdsReplication), IDS_CERT_EXT_MS_NTDS_REPLICATION}, |
| {bssl::der::Input(bssl::kMSApplicationPoliciesOid), |
| IDS_CERT_EXT_MS_APP_POLICIES}, |
| {bssl::der::Input(kMsCertTemplate), IDS_CERT_EXT_MS_CERT_TEMPLATE}, |
| {bssl::der::Input(kEkuMsIndividualCodeSigning), |
| IDS_CERT_EKU_MS_INDIVIDUAL_CODE_SIGNING}, |
| {bssl::der::Input(kEkuMsCommercialCodeSigning), |
| IDS_CERT_EKU_MS_COMMERCIAL_CODE_SIGNING}, |
| {bssl::der::Input(kEkuMsTrustListSigning), |
| IDS_CERT_EKU_MS_TRUST_LIST_SIGNING}, |
| {bssl::der::Input(kEkuMsTimeStamping), IDS_CERT_EKU_MS_TIME_STAMPING}, |
| {bssl::der::Input(kEkuMsServerGatedCrypto), |
| IDS_CERT_EKU_MS_SERVER_GATED_CRYPTO}, |
| {bssl::der::Input(kEkuMsEncryptingFileSystem), |
| IDS_CERT_EKU_MS_ENCRYPTING_FILE_SYSTEM}, |
| {bssl::der::Input(kEkuMsFileRecovery), IDS_CERT_EKU_MS_FILE_RECOVERY}, |
| {bssl::der::Input(kEkuMsWindowsHardwareDriverVerification), |
| IDS_CERT_EKU_MS_WINDOWS_HARDWARE_DRIVER_VERIFICATION}, |
| {bssl::der::Input(kEkuMsQualifiedSubordination), |
| IDS_CERT_EKU_MS_QUALIFIED_SUBORDINATION}, |
| {bssl::der::Input(kEkuMsKeyRecovery), IDS_CERT_EKU_MS_KEY_RECOVERY}, |
| {bssl::der::Input(kEkuMsDocumentSigning), IDS_CERT_EKU_MS_DOCUMENT_SIGNING}, |
| {bssl::der::Input(kEkuMsLifetimeSigning), IDS_CERT_EKU_MS_LIFETIME_SIGNING}, |
| {bssl::der::Input(kEkuMsSmartCardLogon), IDS_CERT_EKU_MS_SMART_CARD_LOGON}, |
| {bssl::der::Input(kEkuMsKeyRecoveryAgent), |
| IDS_CERT_EKU_MS_KEY_RECOVERY_AGENT}, |
| }); |
| |
| std::optional<std::string> GetOidText(bssl::der::Input oid) { |
| const auto i = kOidStringMap.find(oid); |
| if (i != kOidStringMap.end()) |
| return l10n_util::GetStringUTF8(i->second); |
| return std::nullopt; |
| } |
| |
| std::string GetOidTextOrNumeric(bssl::der::Input oid) { |
| std::optional<std::string> oid_text = GetOidText(oid); |
| return oid_text ? *oid_text : OidToNumericString(oid); |
| } |
| |
| std::string ProcessRDN(const bssl::RelativeDistinguishedName& rdn) { |
| std::string rv; |
| // In X.509, RelativeDistinguishedName is a Set, so "last" has no meaning, |
| // and generally only has one element anyway. Just traverse in encoded |
| // order. |
| for (const bssl::X509NameAttribute& name_attribute : rdn) { |
| std::string oid_text = GetOidTextOrNumeric(name_attribute.type); |
| if (oid_text.empty()) |
| return std::string(); |
| rv += oid_text; |
| std::string value; |
| if (!name_attribute.ValueAsStringWithUnsafeOptions(kNameStringHandling, |
| &value)) { |
| return std::string(); |
| } |
| rv += " = "; |
| if (name_attribute.type == bssl::der::Input(bssl::kTypeCommonNameOid)) { |
| value = ProcessIDN(value); |
| } |
| // TODO(mattm): do something about newlines (or other control chars)? |
| rv += value; |
| rv += "\n"; |
| } |
| return rv; |
| } |
| |
| // Note: This was called ProcessName in the x509_certificate_model_nss impl. |
| OptionalStringOrError RDNSequenceToStringMultiLine( |
| const bssl::RDNSequence& rdns) { |
| if (rdns.empty()) |
| return NotPresent(); |
| |
| std::string rv; |
| // Note: this has high level similarity to net::ConvertToRFC2253, but |
| // this one is multi-line, and prints in reverse order, and has a different |
| // set of oids that it has printable names for, and different handling of |
| // unprintable values, and IDN processing... |
| for (const bssl::RelativeDistinguishedName& rdn : base::Reversed(rdns)) { |
| std::string rdn_value = ProcessRDN(rdn); |
| if (rdn_value.empty()) |
| return Error(); |
| rv += rdn_value; |
| } |
| return rv; |
| } |
| |
| std::optional<std::string> ProcessIA5String(bssl::der::Input extension_data) { |
| bssl::der::Input value; |
| bssl::der::Parser parser(extension_data); |
| std::string rv; |
| if (!parser.ReadTag(CBS_ASN1_IA5STRING, &value) || parser.HasMore() || |
| !bssl::der::ParseIA5String(value, &rv)) { |
| return std::nullopt; |
| } |
| // TODO(mattm): do something about newlines (or other control chars)? |
| return rv; |
| } |
| |
| // Returns a comma-separated string of the strings in |string_map| for the bits |
| // in |bitfield| that are set. |
| // string_map may contain -1 for reserved positions that should not be set. |
| std::optional<std::string> ProcessBitField(bssl::der::BitString bitfield, |
| base::span<const int> string_map, |
| char separator) { |
| std::string rv; |
| for (size_t i = 0; i < string_map.size(); ++i) { |
| if (bitfield.AssertsBit(i)) { |
| int string_id = string_map[i]; |
| // TODO(mattm): is returning an error here correct? Or should it encode |
| // some generic string like "reserved bit N set"? |
| if (string_id < 0) |
| return std::nullopt; |
| if (!rv.empty()) |
| rv += separator; |
| rv += l10n_util::GetStringUTF8(string_id); |
| } |
| } |
| // TODO(mattm): should it be an error if bitfield asserts bits beyond |len|? |
| // Or encode them with some generic string like "bit N set"? |
| return rv; |
| } |
| |
| // Returns nullopt on error, or empty string if no bits were set. |
| std::optional<std::string> ProcessBitStringValue( |
| bssl::der::Input value, |
| base::span<const int> string_map, |
| char separator) { |
| std::optional<bssl::der::BitString> decoded = |
| bssl::der::ParseBitString(value); |
| if (!decoded) { |
| return std::nullopt; |
| } |
| return ProcessBitField(decoded.value(), string_map, separator); |
| } |
| |
| std::optional<std::string> ProcessBitStringExtension( |
| bssl::der::Input extension_data, |
| base::span<const int> string_map, |
| char separator) { |
| bssl::der::Input value; |
| bssl::der::Parser parser(extension_data); |
| if (!parser.ReadTag(CBS_ASN1_BITSTRING, &value) || parser.HasMore()) { |
| return std::nullopt; |
| } |
| |
| return ProcessBitStringValue(value, string_map, separator); |
| } |
| |
| std::optional<std::string> ProcessNSCertTypeExtension( |
| bssl::der::Input extension_data) { |
| static const int usage_strings[] = { |
| IDS_CERT_USAGE_SSL_CLIENT, |
| IDS_CERT_USAGE_SSL_SERVER, |
| IDS_CERT_EXT_NS_CERT_TYPE_EMAIL, |
| IDS_CERT_USAGE_OBJECT_SIGNER, |
| -1, // reserved |
| IDS_CERT_USAGE_SSL_CA, |
| IDS_CERT_EXT_NS_CERT_TYPE_EMAIL_CA, |
| IDS_CERT_USAGE_OBJECT_SIGNER, |
| }; |
| return ProcessBitStringExtension(extension_data, usage_strings, '\n'); |
| } |
| |
| std::optional<std::string> ProcessKeyUsageExtension( |
| bssl::der::Input extension_data) { |
| static const int usage_strings[] = { |
| IDS_CERT_X509_KEY_USAGE_SIGNING, |
| IDS_CERT_X509_KEY_USAGE_NONREP, |
| IDS_CERT_X509_KEY_USAGE_ENCIPHERMENT, |
| IDS_CERT_X509_KEY_USAGE_DATA_ENCIPHERMENT, |
| IDS_CERT_X509_KEY_USAGE_KEY_AGREEMENT, |
| IDS_CERT_X509_KEY_USAGE_CERT_SIGNER, |
| IDS_CERT_X509_KEY_USAGE_CRL_SIGNER, |
| IDS_CERT_X509_KEY_USAGE_ENCIPHER_ONLY, |
| IDS_CERT_X509_KEY_USAGE_DECIPHER_ONLY, |
| }; |
| std::optional<std::string> rv = |
| ProcessBitStringExtension(extension_data, usage_strings, '\n'); |
| if (rv && rv->empty()) { |
| // RFC 5280 4.2.1.3: |
| // When the keyUsage extension appears in a certificate, at least one of |
| // the bits MUST be set to 1. |
| return std::nullopt; |
| } |
| return rv; |
| } |
| |
| std::optional<std::string> ProcessBasicConstraints( |
| bssl::der::Input extension_data) { |
| bssl::ParsedBasicConstraints basic_constraints; |
| if (!bssl::ParseBasicConstraints(extension_data, &basic_constraints)) { |
| return std::nullopt; |
| } |
| |
| std::string rv; |
| if (basic_constraints.is_ca) |
| rv = l10n_util::GetStringUTF8(IDS_CERT_X509_BASIC_CONSTRAINT_IS_CA); |
| else |
| rv = l10n_util::GetStringUTF8(IDS_CERT_X509_BASIC_CONSTRAINT_IS_NOT_CA); |
| rv += '\n'; |
| if (basic_constraints.is_ca) { |
| std::u16string depth; |
| if (!basic_constraints.has_path_len) { |
| depth = l10n_util::GetStringUTF16( |
| IDS_CERT_X509_BASIC_CONSTRAINT_PATH_LEN_UNLIMITED); |
| } else { |
| depth = base::FormatNumber(basic_constraints.path_len); |
| } |
| rv += l10n_util::GetStringFUTF8(IDS_CERT_X509_BASIC_CONSTRAINT_PATH_LEN, |
| depth); |
| } |
| return rv; |
| } |
| |
| std::optional<std::string> ProcessExtKeyUsage(bssl::der::Input extension_data) { |
| std::vector<bssl::der::Input> extended_key_usage; |
| if (!bssl::ParseEKUExtension(extension_data, &extended_key_usage)) { |
| return std::nullopt; |
| } |
| |
| std::string rv; |
| for (const auto& oid : extended_key_usage) { |
| std::string numeric_oid = OidToNumericString(oid); |
| std::optional<std::string> oid_text = GetOidText(oid); |
| |
| // If oid is one that is recognized, display the text description along |
| // with the numeric_oid. If we don't recognize the OID just display the |
| // numeric OID alone. |
| if (!oid_text) { |
| rv += numeric_oid; |
| } else { |
| rv += l10n_util::GetStringFUTF8(IDS_CERT_EXT_KEY_USAGE_FORMAT, |
| base::UTF8ToUTF16(*oid_text), |
| base::UTF8ToUTF16(numeric_oid)); |
| } |
| rv += '\n'; |
| } |
| return rv; |
| } |
| |
| OptionalStringOrError ProcessNameValue(bssl::der::Input name_value) { |
| bssl::RDNSequence rdns; |
| if (!bssl::ParseNameValue(name_value, &rdns)) { |
| return Error(); |
| } |
| return RDNSequenceToStringMultiLine(rdns); |
| } |
| |
| std::string FormatGeneralName(std::u16string key, std::string_view value) { |
| return l10n_util::GetStringFUTF8(IDS_CERT_UNKNOWN_OID_INFO_FORMAT, key, |
| base::UTF8ToUTF16(value)) + |
| '\n'; |
| } |
| |
| std::string FormatGeneralName(int key_string_id, std::string_view value) { |
| return FormatGeneralName(l10n_util::GetStringUTF16(key_string_id), value); |
| } |
| |
| bool ParseOtherName(bssl::der::Input other_name, |
| bssl::der::Input* type, |
| bssl::der::Input* value) { |
| // OtherName ::= SEQUENCE { |
| // type-id OBJECT IDENTIFIER, |
| // value [0] EXPLICIT ANY DEFINED BY type-id } |
| bssl::der::Parser sequence_parser(other_name); |
| return sequence_parser.ReadTag(CBS_ASN1_OBJECT, type) && |
| sequence_parser.ReadTag( |
| CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0, value) && |
| !sequence_parser.HasMore(); |
| } |
| |
| std::optional<std::string> ProcessGeneralNames( |
| const bssl::GeneralNames& names) { |
| // Note: The old x509_certificate_model_nss impl would process names in the |
| // order they appeared in the certificate, whereas this impl parses names |
| // into different lists by each type and then processes those in order. |
| // Probably doesn't matter. |
| std::string rv; |
| for (const auto& other_name : names.other_names) { |
| bssl::der::Input type; |
| bssl::der::Input value; |
| if (!ParseOtherName(other_name, &type, &value)) { |
| return std::nullopt; |
| } |
| // x509_certificate_model_nss went a bit further in parsing certain |
| // otherName types, but it probably isn't worth bothering. |
| rv += FormatGeneralName(base::UTF8ToUTF16(GetOidTextOrNumeric(type)), |
| ProcessRawBytes(value)); |
| } |
| for (const auto& rfc822_name : names.rfc822_names) { |
| // TODO(mattm): do something about newlines (or other control chars)? |
| rv += FormatGeneralName(IDS_CERT_GENERAL_NAME_RFC822_NAME, rfc822_name); |
| } |
| for (const auto& dns_name : names.dns_names) { |
| // TODO(mattm): Should probably do ProcessIDN on dnsNames from |
| // subjectAltName like we do on subject commonName? |
| // TODO(mattm): do something about newlines (or other control chars)? |
| rv += FormatGeneralName(IDS_CERT_GENERAL_NAME_DNS_NAME, dns_name); |
| } |
| for (const auto& x400_address : names.x400_addresses) { |
| rv += FormatGeneralName(IDS_CERT_GENERAL_NAME_X400_ADDRESS, |
| ProcessRawBytes(x400_address)); |
| } |
| for (const auto& directory_name : names.directory_names) { |
| OptionalStringOrError name = ProcessNameValue(directory_name); |
| if (!std::holds_alternative<std::string>(name)) { |
| return std::nullopt; |
| } |
| rv += FormatGeneralName(IDS_CERT_GENERAL_NAME_DIRECTORY_NAME, |
| std::get<std::string>(name)); |
| } |
| for (const auto& edi_party_name : names.edi_party_names) { |
| rv += FormatGeneralName(IDS_CERT_GENERAL_NAME_EDI_PARTY_NAME, |
| ProcessRawBytes(edi_party_name)); |
| } |
| for (const auto& uniform_resource_identifier : |
| names.uniform_resource_identifiers) { |
| // TODO(mattm): do something about newlines (or other control chars)? |
| rv += FormatGeneralName(IDS_CERT_GENERAL_NAME_URI, |
| uniform_resource_identifier); |
| } |
| for (const auto& ip_address_bytes : names.ip_addresses) { |
| net::IPAddress ip_address(ip_address_bytes); |
| // The `GeneralNames` parser should guarantee this. |
| CHECK(ip_address.IsValid()); |
| rv += FormatGeneralName(IDS_CERT_GENERAL_NAME_IP_ADDRESS, |
| ip_address.ToString()); |
| } |
| for (const auto& ip_address_range : names.ip_address_ranges) { |
| net::IPAddress ip_address(ip_address_range.first); |
| net::IPAddress mask(ip_address_range.second); |
| // The `GeneralNames` parser should guarantee this. |
| CHECK(ip_address.IsValid()); |
| CHECK(mask.IsValid()); |
| rv += FormatGeneralName( |
| IDS_CERT_GENERAL_NAME_IP_ADDRESS, |
| ip_address.ToString() + '/' + |
| base::NumberToString(net::MaskPrefixLength(mask))); |
| } |
| for (const auto& registered_id : names.registered_ids) { |
| rv += FormatGeneralName(IDS_CERT_GENERAL_NAME_REGISTERED_ID, |
| GetOidTextOrNumeric(registered_id)); |
| } |
| |
| return rv; |
| } |
| |
| std::optional<std::string> ProcessGeneralNamesTlv( |
| bssl::der::Input extension_data) { |
| bssl::CertErrors unused_errors; |
| std::unique_ptr<bssl::GeneralNames> alt_names = |
| bssl::GeneralNames::Create(extension_data, &unused_errors); |
| if (!alt_names) |
| return std::nullopt; |
| return ProcessGeneralNames(*alt_names); |
| } |
| |
| std::optional<std::string> ProcessGeneralNamesValue( |
| bssl::der::Input general_names_value) { |
| bssl::CertErrors unused_errors; |
| std::unique_ptr<bssl::GeneralNames> alt_names = |
| bssl::GeneralNames::CreateFromValue(general_names_value, &unused_errors); |
| if (!alt_names) |
| return std::nullopt; |
| return ProcessGeneralNames(*alt_names); |
| } |
| |
| std::optional<std::string> ProcessSubjectKeyId( |
| bssl::der::Input extension_data) { |
| bssl::der::Input subject_key_identifier; |
| if (!bssl::ParseSubjectKeyIdentifier(extension_data, |
| &subject_key_identifier)) { |
| return std::nullopt; |
| } |
| return l10n_util::GetStringFUTF8( |
| IDS_CERT_KEYID_FORMAT, |
| base::ASCIIToUTF16(ProcessRawBytes(subject_key_identifier))); |
| } |
| |
| std::optional<std::string> ProcessAuthorityKeyId( |
| bssl::der::Input extension_data) { |
| bssl::ParsedAuthorityKeyIdentifier authority_key_id; |
| if (!bssl::ParseAuthorityKeyIdentifier(extension_data, &authority_key_id)) { |
| return std::nullopt; |
| } |
| |
| std::string rv; |
| if (authority_key_id.key_identifier) { |
| rv += l10n_util::GetStringFUTF8( |
| IDS_CERT_KEYID_FORMAT, |
| base::ASCIIToUTF16(ProcessRawBytes(*authority_key_id.key_identifier))); |
| rv += '\n'; |
| } |
| if (authority_key_id.authority_cert_issuer) { |
| std::optional<std::string> s = |
| ProcessGeneralNamesValue(*authority_key_id.authority_cert_issuer); |
| if (!s) |
| return std::nullopt; |
| rv += l10n_util::GetStringFUTF8(IDS_CERT_ISSUER_FORMAT, |
| base::UTF8ToUTF16(*s)); |
| rv += '\n'; |
| } |
| if (authority_key_id.authority_cert_serial_number) { |
| rv += l10n_util::GetStringFUTF8( |
| IDS_CERT_SERIAL_NUMBER_FORMAT, |
| base::ASCIIToUTF16( |
| ProcessRawBytes(*authority_key_id.authority_cert_serial_number))); |
| rv += '\n'; |
| } |
| |
| return rv; |
| } |
| |
| std::optional<std::string> ProcessUserNoticeDisplayText( |
| CBS_ASN1_TAG tag, |
| bssl::der::Input value) { |
| std::string display_text; |
| switch (tag) { |
| case CBS_ASN1_IA5STRING: |
| if (!bssl::der::ParseIA5String(value, &display_text)) { |
| return std::nullopt; |
| } |
| break; |
| case CBS_ASN1_VISIBLESTRING: |
| if (!bssl::der::ParseVisibleString(value, &display_text)) { |
| return std::nullopt; |
| } |
| break; |
| case CBS_ASN1_BMPSTRING: |
| if (!bssl::der::ParseBmpString(value, &display_text)) { |
| return std::nullopt; |
| } |
| break; |
| case CBS_ASN1_UTF8STRING: |
| if (!base::IsStringUTF8AllowingNoncharacters(value.AsStringView())) { |
| return std::nullopt; |
| } |
| display_text = value.AsString(); |
| break; |
| default: |
| return std::nullopt; |
| } |
| // TODO(mattm): do something about newlines (or other control chars)? |
| return display_text; |
| } |
| |
| std::optional<std::string> ProcessUserNotice(bssl::der::Input qualifier) { |
| // RFC 5280 section 4.2.1.4: |
| // |
| // UserNotice ::= SEQUENCE { |
| // noticeRef NoticeReference OPTIONAL, |
| // explicitText DisplayText OPTIONAL } |
| // |
| // NoticeReference ::= SEQUENCE { |
| // organization DisplayText, |
| // noticeNumbers SEQUENCE OF INTEGER } |
| // |
| // DisplayText ::= CHOICE { |
| // ia5String IA5String (SIZE (1..200)), |
| // visibleString VisibleString (SIZE (1..200)), |
| // bmpString BMPString (SIZE (1..200)), |
| // utf8String UTF8String (SIZE (1..200)) } |
| |
| bssl::der::Parser outer_parser(qualifier); |
| bssl::der::Parser parser; |
| if (!outer_parser.ReadSequence(&parser) || outer_parser.HasMore()) |
| return std::nullopt; |
| |
| std::optional<bssl::der::Input> notice_ref_value; |
| if (!parser.ReadOptionalTag(CBS_ASN1_SEQUENCE, ¬ice_ref_value)) { |
| return std::nullopt; |
| } |
| |
| std::string rv; |
| if (notice_ref_value) { |
| bssl::der::Parser notice_ref_parser(*notice_ref_value); |
| CBS_ASN1_TAG organization_tag; |
| bssl::der::Input organization_value; |
| if (!notice_ref_parser.ReadTagAndValue(&organization_tag, |
| &organization_value)) { |
| return std::nullopt; |
| } |
| std::optional<std::string> s = |
| ProcessUserNoticeDisplayText(organization_tag, organization_value); |
| if (!s) |
| return std::nullopt; |
| rv += *s; |
| rv += " - "; |
| |
| bssl::der::Parser notice_numbers_parser; |
| if (!notice_ref_parser.ReadSequence(¬ice_numbers_parser)) |
| return std::nullopt; |
| bool first = true; |
| while (notice_numbers_parser.HasMore()) { |
| bssl::der::Input notice_number; |
| if (!notice_numbers_parser.ReadTag(CBS_ASN1_INTEGER, ¬ice_number)) { |
| return std::nullopt; |
| } |
| if (!first) |
| rv += ", "; |
| rv += '#'; |
| uint64_t number; |
| if (bssl::der::ParseUint64(notice_number, &number)) { |
| rv += base::NumberToString(number); |
| } else { |
| rv += ProcessRawBytes(notice_number); |
| } |
| first = false; |
| } |
| } |
| |
| if (parser.HasMore()) { |
| CBS_ASN1_TAG explicit_text_tag; |
| bssl::der::Input explicit_text_value; |
| if (!parser.ReadTagAndValue(&explicit_text_tag, &explicit_text_value)) |
| return std::nullopt; |
| rv += "\n "; |
| std::optional<std::string> s = |
| ProcessUserNoticeDisplayText(explicit_text_tag, explicit_text_value); |
| if (!s) |
| return std::nullopt; |
| rv += *s; |
| } |
| |
| if (parser.HasMore()) |
| return std::nullopt; |
| |
| return rv; |
| } |
| |
| std::optional<std::string> ProcessCertificatePolicies( |
| bssl::der::Input extension_data) { |
| std::vector<bssl::PolicyInformation> policies; |
| bssl::CertErrors errors; |
| if (!bssl::ParseCertificatePoliciesExtension(extension_data, &policies, |
| &errors)) { |
| return std::nullopt; |
| } |
| std::string rv; |
| for (const auto& policy_info : policies) { |
| std::string key = GetOidTextOrNumeric(policy_info.policy_oid); |
| // If there are policy qualifiers, display the oid text |
| // with a ':', otherwise just put the oid text and a newline. |
| if (policy_info.policy_qualifiers.empty()) { |
| rv += key; |
| } else { |
| rv += l10n_util::GetStringFUTF8(IDS_CERT_MULTILINE_INFO_START_FORMAT, |
| base::UTF8ToUTF16(key)); |
| } |
| rv += '\n'; |
| |
| if (!policy_info.policy_qualifiers.empty()) { |
| for (const auto& qualifier_info : policy_info.policy_qualifiers) { |
| rv += " "; |
| rv += l10n_util::GetStringFUTF8(IDS_CERT_MULTILINE_INFO_START_FORMAT, |
| base::UTF8ToUTF16(GetOidTextOrNumeric( |
| qualifier_info.qualifier_oid))); |
| if (qualifier_info.qualifier_oid == |
| bssl::der::Input(bssl::kCpsPointerId)) { |
| std::optional<std::string> s = |
| ProcessIA5String(qualifier_info.qualifier); |
| if (!s) |
| return std::nullopt; |
| rv += " "; |
| rv += *s; |
| } else if (qualifier_info.qualifier_oid == |
| bssl::der::Input(bssl::kUserNoticeId)) { |
| std::optional<std::string> s = |
| ProcessUserNotice(qualifier_info.qualifier); |
| if (!s) |
| return std::nullopt; |
| rv += *s; |
| } else { |
| rv += ProcessRawBytes(qualifier_info.qualifier); |
| } |
| rv += '\n'; |
| } |
| } |
| } |
| return rv; |
| } |
| |
| std::optional<std::string> ProcessCrlDistributionPoints( |
| bssl::der::Input extension_data) { |
| std::vector<bssl::ParsedDistributionPoint> distribution_points; |
| if (!ParseCrlDistributionPoints(extension_data, &distribution_points)) |
| return std::nullopt; |
| |
| // ReasonFlags ::= BIT STRING { |
| static const int kReasonStrings[] = { |
| // unused (0), |
| IDS_CERT_REVOCATION_REASON_UNUSED, |
| // keyCompromise (1), |
| IDS_CERT_REVOCATION_REASON_KEY_COMPROMISE, |
| // cACompromise (2), |
| IDS_CERT_REVOCATION_REASON_CA_COMPROMISE, |
| // affiliationChanged (3), |
| IDS_CERT_REVOCATION_REASON_AFFILIATION_CHANGED, |
| // superseded (4), |
| IDS_CERT_REVOCATION_REASON_SUPERSEDED, |
| // cessationOfOperation (5), |
| IDS_CERT_REVOCATION_REASON_CESSATION_OF_OPERATION, |
| // certificateHold (6), |
| IDS_CERT_REVOCATION_REASON_CERTIFICATE_HOLD, |
| // These aren't included as they would be challenging to translate and |
| // are irrelevant for a web browser. (Actually all of these are |
| // kinda irrelevant as we don't support CRL reasons.) |
| // privilegeWithdrawn (7), |
| // aACompromise (8) } |
| }; |
| |
| std::string rv; |
| for (const auto& dp : distribution_points) { |
| if (dp.distribution_point_fullname) { |
| std::optional<std::string> s = |
| ProcessGeneralNames(*dp.distribution_point_fullname); |
| if (!s) |
| return std::nullopt; |
| rv += *s; |
| } |
| |
| if (dp.distribution_point_name_relative_to_crl_issuer) { |
| bssl::RelativeDistinguishedName name_relative_to_crl_issuer; |
| bssl::der::Parser rdnParser( |
| *dp.distribution_point_name_relative_to_crl_issuer); |
| if (!bssl::ReadRdn(&rdnParser, &name_relative_to_crl_issuer)) { |
| return std::nullopt; |
| } |
| std::string s = ProcessRDN(name_relative_to_crl_issuer); |
| if (s.empty()) |
| return std::nullopt; |
| rv += s; |
| } |
| |
| if (dp.reasons) { |
| std::optional<std::string> s = |
| ProcessBitStringValue(*dp.reasons, kReasonStrings, ','); |
| if (!s) |
| return std::nullopt; |
| rv += *s + '\n'; |
| } |
| |
| if (dp.crl_issuer) { |
| bssl::CertErrors unused_errors; |
| auto crl_issuer = |
| bssl::GeneralNames::CreateFromValue(*dp.crl_issuer, &unused_errors); |
| if (!crl_issuer) |
| return std::nullopt; |
| std::optional<std::string> s = ProcessGeneralNames(*crl_issuer); |
| if (!s) |
| return std::nullopt; |
| rv += l10n_util::GetStringFUTF8(IDS_CERT_ISSUER_FORMAT, |
| base::UTF8ToUTF16(*s)); |
| } |
| } |
| |
| return rv; |
| } |
| |
| std::optional<std::string> ProcessAuthorityInfoAccess( |
| bssl::der::Input extension_data) { |
| std::vector<bssl::AuthorityInfoAccessDescription> access_descriptions; |
| if (!bssl::ParseAuthorityInfoAccess(extension_data, &access_descriptions)) { |
| return std::nullopt; |
| } |
| |
| std::string rv; |
| for (const auto& access_description : access_descriptions) { |
| bssl::GeneralNames name; |
| bssl::CertErrors unused_errors; |
| if (!bssl::ParseGeneralName(access_description.access_location, |
| bssl::GeneralNames::IP_ADDRESS_ONLY, &name, |
| &unused_errors)) { |
| return std::nullopt; |
| } |
| |
| std::optional<std::string> s = ProcessGeneralNames(name); |
| if (!s) |
| return std::nullopt; |
| std::u16string location_str = base::UTF8ToUTF16(*s); |
| if (access_description.access_method_oid == |
| bssl::der::Input(bssl::kAdOcspOid)) { |
| rv += l10n_util::GetStringFUTF8(IDS_CERT_OCSP_RESPONDER_FORMAT, |
| location_str); |
| } else if (access_description.access_method_oid == |
| bssl::der::Input(bssl::kAdCaIssuersOid)) { |
| rv += l10n_util::GetStringFUTF8(IDS_CERT_CA_ISSUERS_FORMAT, location_str); |
| } else { |
| rv += l10n_util::GetStringFUTF8( |
| IDS_CERT_UNKNOWN_OID_INFO_FORMAT, |
| base::UTF8ToUTF16( |
| GetOidTextOrNumeric(access_description.access_method_oid)), |
| location_str); |
| } |
| } |
| |
| return rv; |
| } |
| |
| std::string ProcessAlgorithmIdentifier(bssl::der::Input algorithm_tlv) { |
| bssl::der::Input oid; |
| bssl::der::Input params; |
| if (!bssl::ParseAlgorithmIdentifier(algorithm_tlv, &oid, ¶ms)) { |
| return std::string(); |
| } |
| return GetOidTextOrNumeric(oid); |
| } |
| |
| bool ParseSubjectPublicKeyInfo(bssl::der::Input spki_tlv, |
| bssl::der::Input* algorithm_tlv, |
| bssl::der::Input* subject_public_key_value) { |
| bssl::der::Parser spki_parser(spki_tlv); |
| |
| // SubjectPublicKeyInfo ::= SEQUENCE { |
| // algorithm AlgorithmIdentifier, |
| // subjectPublicKey BIT STRING } |
| bssl::der::Parser sequence_parser; |
| if (!spki_parser.ReadSequence(&sequence_parser)) |
| return false; |
| |
| if (!sequence_parser.ReadRawTLV(algorithm_tlv)) |
| return false; |
| |
| if (!sequence_parser.ReadTag(CBS_ASN1_BITSTRING, subject_public_key_value)) { |
| return false; |
| } |
| |
| if (sequence_parser.HasMore()) |
| return false; |
| |
| return true; |
| } |
| |
| std::vector<uint8_t> BIGNUMBytes(const BIGNUM* bn) { |
| std::vector<uint8_t> ret(BN_num_bytes(bn)); |
| BN_bn2bin(bn, ret.data()); |
| return ret; |
| } |
| |
| std::optional<std::string> ProcessQcStatements( |
| bssl::der::Input extension_data) { |
| std::optional<std::vector<net::QcStatement>> qc_statements = |
| net::ParseQcStatements(extension_data); |
| if (!qc_statements.has_value()) { |
| return std::nullopt; |
| } |
| |
| std::string rv; |
| for (const auto& statement : qc_statements.value()) { |
| rv += GetOidTextOrNumeric(statement.id); |
| if (!statement.info.empty()) { |
| rv += " = "; |
| // The `statement.info` is dependent on the `id`, so `info` can only be |
| // processed for known ids. Otherwise the raw bytes of `info` are shown. |
| if (statement.id == bssl::der::Input(net::kEtsiQcsQcTypeOid)) { |
| std::optional<std::vector<bssl::der::Input>> qc_types = |
| net::ParseQcTypeInfo(statement.info); |
| if (!qc_types.has_value()) { |
| return std::nullopt; |
| } |
| std::string sep; |
| for (const auto& qc_type_id : qc_types.value()) { |
| rv += sep; |
| rv += GetOidTextOrNumeric(qc_type_id); |
| sep = ", "; |
| } |
| } else { |
| rv += ProcessRawBytes(statement.info); |
| } |
| } |
| rv += "\n"; |
| } |
| return rv; |
| } |
| |
| } // namespace |
| |
| X509CertificateModel::X509CertificateModel( |
| bssl::UniquePtr<CRYPTO_BUFFER> cert_data) |
| : cert_data_(std::move(cert_data)) { |
| DCHECK(cert_data_); |
| |
| bssl::ParseCertificateOptions options; |
| options.allow_invalid_serial_numbers = true; |
| bssl::CertErrors unused_errors; |
| if (!bssl::ParseCertificate( |
| bssl::der::Input( |
| net::x509_util::CryptoBufferAsSpan(cert_data_.get())), |
| &tbs_certificate_tlv_, &signature_algorithm_tlv_, &signature_value_, |
| &unused_errors) || |
| !ParseTbsCertificate(tbs_certificate_tlv_, options, &tbs_, |
| &unused_errors) || |
| !bssl::ParseName(tbs_.subject_tlv, &subject_rdns_) || |
| !bssl::ParseName(tbs_.issuer_tlv, &issuer_rdns_)) { |
| return; |
| } |
| if (tbs_.extensions_tlv && !ParseExtensions(tbs_.extensions_tlv.value())) { |
| return; |
| } |
| parsed_successfully_ = true; |
| } |
| |
| X509CertificateModel::X509CertificateModel(X509CertificateModel&& other) = |
| default; |
| |
| X509CertificateModel::~X509CertificateModel() = default; |
| |
| std::string X509CertificateModel::HashCertSHA256() const { |
| auto hash = |
| crypto::SHA256Hash(net::x509_util::CryptoBufferAsSpan(cert_data_.get())); |
| return base::HexEncodeLower(hash); |
| } |
| |
| std::string X509CertificateModel::GetTitle() const { |
| if (!parsed_successfully_) |
| return HashCertSHA256(); |
| |
| if (!subject_rdns_.empty()) { |
| OptionalStringOrError common_name = FindLastNameOfType( |
| bssl::der::Input(bssl::kTypeCommonNameOid), subject_rdns_); |
| if (auto* str = std::get_if<std::string>(&common_name); str) { |
| return std::move(*str); |
| } |
| if (std::holds_alternative<Error>(common_name)) { |
| return HashCertSHA256(); |
| } |
| |
| std::string rv; |
| if (!bssl::ConvertToRFC2253(subject_rdns_, &rv)) { |
| return HashCertSHA256(); |
| } |
| return rv; |
| } |
| |
| if (subject_alt_names_) { |
| // TODO(mattm): do something about newlines (or other control chars)? |
| if (!subject_alt_names_->dns_names.empty()) |
| return std::string(subject_alt_names_->dns_names[0]); |
| if (!subject_alt_names_->rfc822_names.empty()) |
| return std::string(subject_alt_names_->rfc822_names[0]); |
| } |
| |
| return HashCertSHA256(); |
| } |
| |
| std::string X509CertificateModel::GetVersion() const { |
| DCHECK(parsed_successfully_); |
| switch (tbs_.version) { |
| case bssl::CertificateVersion::V1: |
| return "1"; |
| case bssl::CertificateVersion::V2: |
| return "2"; |
| case bssl::CertificateVersion::V3: |
| return "3"; |
| } |
| } |
| |
| std::string X509CertificateModel::GetSerialNumberHexified() const { |
| DCHECK(parsed_successfully_); |
| return ProcessRawBytesWithSeparators(tbs_.serial_number, ':', ':'); |
| } |
| |
| bool X509CertificateModel::GetTimes(base::Time* not_before, |
| base::Time* not_after) const { |
| DCHECK(parsed_successfully_); |
| return net::GeneralizedTimeToTime(tbs_.validity_not_before, not_before) && |
| net::GeneralizedTimeToTime(tbs_.validity_not_after, not_after); |
| } |
| |
| OptionalStringOrError X509CertificateModel::GetIssuerCommonName() const { |
| DCHECK(parsed_successfully_); |
| // Return the last (most specific) commonName. This matches NSS |
| // CERT_GetCommonName. |
| return FindLastNameOfType(bssl::der::Input(bssl::kTypeCommonNameOid), |
| issuer_rdns_); |
| } |
| |
| OptionalStringOrError X509CertificateModel::GetIssuerOrgName() const { |
| DCHECK(parsed_successfully_); |
| // Return the first (most general) orgName. This matches NSS CERT_GetOrgName. |
| return FindFirstNameOfType(bssl::der::Input(bssl::kTypeOrganizationNameOid), |
| issuer_rdns_); |
| } |
| |
| OptionalStringOrError X509CertificateModel::GetIssuerOrgUnitName() const { |
| DCHECK(parsed_successfully_); |
| // Return the first (most general) orgUnitName. This matches NSS |
| // CERT_GetOrgUnitName. |
| return FindFirstNameOfType( |
| bssl::der::Input(bssl::kTypeOrganizationUnitNameOid), issuer_rdns_); |
| } |
| |
| OptionalStringOrError X509CertificateModel::GetSubjectCommonName() const { |
| DCHECK(parsed_successfully_); |
| // Return the last (most specific) commonName. This matches NSS |
| // CERT_GetCommonName. |
| return FindLastNameOfType(bssl::der::Input(bssl::kTypeCommonNameOid), |
| subject_rdns_); |
| } |
| |
| OptionalStringOrError X509CertificateModel::GetSubjectOrgName() const { |
| DCHECK(parsed_successfully_); |
| // Return the first (most general) orgName. This matches NSS CERT_GetOrgName. |
| return FindFirstNameOfType(bssl::der::Input(bssl::kTypeOrganizationNameOid), |
| subject_rdns_); |
| } |
| |
| OptionalStringOrError X509CertificateModel::GetSubjectOrgUnitName() const { |
| DCHECK(parsed_successfully_); |
| // Return the first (most general) orgUnitName. This matches NSS |
| // CERT_GetOrgUnitName. |
| return FindFirstNameOfType( |
| bssl::der::Input(bssl::kTypeOrganizationUnitNameOid), subject_rdns_); |
| } |
| |
| OptionalStringOrError X509CertificateModel::GetIssuerName() const { |
| DCHECK(parsed_successfully_); |
| return RDNSequenceToStringMultiLine(issuer_rdns_); |
| } |
| |
| OptionalStringOrError X509CertificateModel::GetSubjectName() const { |
| DCHECK(parsed_successfully_); |
| return RDNSequenceToStringMultiLine(subject_rdns_); |
| } |
| |
| std::vector<Extension> X509CertificateModel::GetExtensions( |
| std::string_view critical_label, |
| std::string_view non_critical_label) const { |
| DCHECK(parsed_successfully_); |
| std::vector<Extension> extensions; |
| for (const auto& extension : extensions_) { |
| Extension processed_extension; |
| processed_extension.name = GetOidTextOrNumeric(extension.oid); |
| processed_extension.value = |
| ProcessExtension(critical_label, non_critical_label, extension); |
| extensions.push_back(processed_extension); |
| } |
| return extensions; |
| } |
| |
| bool X509CertificateModel::ParseExtensions( |
| const bssl::der::Input& extensions_tlv) { |
| bssl::CertErrors unused_errors; |
| bssl::der::Parser parser(extensions_tlv); |
| |
| // Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension |
| bssl::der::Parser extensions_parser; |
| if (!parser.ReadSequence(&extensions_parser)) |
| return false; |
| |
| // The Extensions SEQUENCE must contains at least 1 element (otherwise it |
| // should have been omitted). |
| if (!extensions_parser.HasMore()) |
| return false; |
| |
| while (extensions_parser.HasMore()) { |
| bssl::ParsedExtension extension; |
| |
| bssl::der::Input extension_tlv; |
| if (!extensions_parser.ReadRawTLV(&extension_tlv)) |
| return false; |
| |
| if (!ParseExtension(extension_tlv, &extension)) |
| return false; |
| |
| extensions_.push_back(extension); |
| |
| if (extension.oid == bssl::der::Input(bssl::kSubjectAltNameOid)) { |
| subject_alt_names_ = |
| bssl::GeneralNames::Create(extension.value, &unused_errors); |
| if (!subject_alt_names_) |
| return false; |
| } |
| } |
| |
| // By definition the input was a single Extensions sequence, so there |
| // shouldn't be unconsumed data. |
| if (parser.HasMore()) |
| return false; |
| |
| return true; |
| } |
| |
| std::string X509CertificateModel::ProcessExtension( |
| std::string_view critical_label, |
| std::string_view non_critical_label, |
| const bssl::ParsedExtension& extension) const { |
| std::string_view criticality = |
| extension.critical ? critical_label : non_critical_label; |
| std::optional<std::string> processed_extension = |
| ProcessExtensionData(extension); |
| return base::StrCat( |
| {criticality, "\n", |
| (processed_extension |
| ? *processed_extension |
| : l10n_util::GetStringUTF8(IDS_CERT_EXTENSION_DUMP_ERROR))}); |
| } |
| |
| std::optional<std::string> X509CertificateModel::ProcessExtensionData( |
| const bssl::ParsedExtension& extension) const { |
| if (extension.oid == bssl::der::Input(kNetscapeCertificateTypeOid)) { |
| return ProcessNSCertTypeExtension(extension.value); |
| } |
| if (extension.oid == bssl::der::Input(bssl::kKeyUsageOid)) { |
| return ProcessKeyUsageExtension(extension.value); |
| } |
| if (extension.oid == bssl::der::Input(bssl::kBasicConstraintsOid)) { |
| return ProcessBasicConstraints(extension.value); |
| } |
| if (extension.oid == bssl::der::Input(bssl::kExtKeyUsageOid)) { |
| return ProcessExtKeyUsage(extension.value); |
| } |
| if (extension.oid == bssl::der::Input(bssl::kSubjectAltNameOid)) { |
| // The subjectAltName extension was already parsed in the constructor, use |
| // that rather than parse it again. |
| DCHECK(subject_alt_names_); |
| return ProcessGeneralNames(*subject_alt_names_); |
| } |
| if (extension.oid == bssl::der::Input(kIssuerAltNameOid)) { |
| return ProcessGeneralNamesTlv(extension.value); |
| } |
| if (extension.oid == bssl::der::Input(bssl::kSubjectKeyIdentifierOid)) { |
| return ProcessSubjectKeyId(extension.value); |
| } |
| if (extension.oid == bssl::der::Input(bssl::kAuthorityKeyIdentifierOid)) { |
| return ProcessAuthorityKeyId(extension.value); |
| } |
| if (extension.oid == bssl::der::Input(bssl::kCertificatePoliciesOid)) { |
| return ProcessCertificatePolicies(extension.value); |
| } |
| if (extension.oid == bssl::der::Input(bssl::kCrlDistributionPointsOid)) { |
| return ProcessCrlDistributionPoints(extension.value); |
| } |
| if (extension.oid == bssl::der::Input(bssl::kAuthorityInfoAccessOid)) { |
| return ProcessAuthorityInfoAccess(extension.value); |
| } |
| if (extension.oid == bssl::der::Input(kNetscapeBaseURLOid) || |
| extension.oid == bssl::der::Input(kNetscapeRevocationURLOid) || |
| extension.oid == bssl::der::Input(kNetscapeCARevocationURLOid) || |
| extension.oid == bssl::der::Input(kNetscapeCACertURLOid) || |
| extension.oid == bssl::der::Input(kNetscapeRenewalURLOid) || |
| extension.oid == bssl::der::Input(kNetscapeCAPolicyURLOid) || |
| extension.oid == bssl::der::Input(kNetscapeSSLServerNameOid) || |
| extension.oid == bssl::der::Input(kNetscapeCommentOid) || |
| extension.oid == bssl::der::Input(kNetscapeLostPasswordURLOid)) { |
| return ProcessIA5String(extension.value); |
| } |
| if (extension.oid == bssl::der::Input(net::kQcStatementsOid)) { |
| return ProcessQcStatements(extension.value); |
| } |
| // TODO(crbug.com/41395047): SCT |
| // TODO(mattm): name constraints |
| // TODO(mattm): policy mappings |
| // TODO(mattm): policy constraints |
| return ProcessRawBytes(extension.value); |
| } |
| |
| std::string X509CertificateModel::ProcessSecAlgorithmSignature() const { |
| DCHECK(parsed_successfully_); |
| return ProcessAlgorithmIdentifier(signature_algorithm_tlv_); |
| } |
| |
| std::string X509CertificateModel::ProcessSecAlgorithmSubjectPublicKey() const { |
| DCHECK(parsed_successfully_); |
| |
| bssl::der::Input algorithm_tlv; |
| bssl::der::Input unused_spk_value; |
| if (!ParseSubjectPublicKeyInfo(tbs_.spki_tlv, &algorithm_tlv, |
| &unused_spk_value)) { |
| return std::string(); |
| } |
| |
| return ProcessAlgorithmIdentifier(algorithm_tlv); |
| } |
| |
| std::string X509CertificateModel::ProcessSecAlgorithmSignatureWrap() const { |
| DCHECK(parsed_successfully_); |
| return ProcessAlgorithmIdentifier(tbs_.signature_algorithm_tlv); |
| } |
| |
| std::string X509CertificateModel::ProcessSubjectPublicKeyInfo() const { |
| DCHECK(parsed_successfully_); |
| std::string rv = ProcessRawSubjectPublicKeyInfo(tbs_.spki_tlv); |
| if (rv.empty()) |
| return std::string(); |
| return rv; |
| } |
| |
| std::string X509CertificateModel::HashSpkiSHA256() const { |
| DCHECK(parsed_successfully_); |
| auto hash = crypto::SHA256Hash(tbs_.spki_tlv); |
| return base::HexEncodeLower(hash); |
| } |
| |
| std::string X509CertificateModel::ProcessRawBitsSignatureWrap() const { |
| DCHECK(parsed_successfully_); |
| return ProcessRawBytes(signature_value_.bytes()); |
| } |
| |
| // TODO(crbug.com/41453265): move to anonymous namespace once |
| // x509_certificate_model_nss is removed. |
| std::string ProcessIDN(const std::string& input) { |
| if (!base::IsStringASCII(input)) |
| return input; |
| |
| // Convert the ASCII input to a string16 for ICU. |
| std::u16string input16; |
| input16.reserve(input.length()); |
| input16.insert(input16.end(), input.begin(), input.end()); |
| |
| std::u16string output16 = url_formatter::IDNToUnicode(input); |
| if (input16 == output16) |
| return input; // Input did not contain any encoded data. |
| |
| // Input contained encoded data, return formatted string showing original and |
| // decoded forms. |
| return l10n_util::GetStringFUTF8(IDS_CERT_INFO_IDN_VALUE_FORMAT, input16, |
| output16); |
| } |
| |
| std::string ProcessRawSubjectPublicKeyInfo(base::span<const uint8_t> spki_der) { |
| bssl::UniquePtr<EVP_PKEY> public_key; |
| if (!bssl::ParsePublicKey(bssl::der::Input(spki_der), &public_key)) { |
| return std::string(); |
| } |
| switch (EVP_PKEY_id(public_key.get())) { |
| case EVP_PKEY_RSA: { |
| RSA* rsa = EVP_PKEY_get0_RSA(public_key.get()); |
| // EVP_PKEY_get0_RSA can only fail if the type was wrong, which was just |
| // checked in the switch. |
| DCHECK(rsa); |
| const BIGNUM* modulus = RSA_get0_n(rsa); |
| const BIGNUM* public_exponent = RSA_get0_e(rsa); |
| DCHECK(modulus); |
| DCHECK(public_exponent); |
| |
| return l10n_util::GetStringFUTF8( |
| IDS_CERT_RSA_PUBLIC_KEY_DUMP_FORMAT, |
| base::NumberToString16(BN_num_bits(modulus)), |
| base::UTF8ToUTF16(ProcessRawBytes(BIGNUMBytes(modulus))), |
| base::NumberToString16(BN_num_bits(public_exponent)), |
| base::UTF8ToUTF16(ProcessRawBytes(BIGNUMBytes(public_exponent)))); |
| } |
| // TODO(mattm): handle other key types? (eg EVP_PKEY_EC) |
| } |
| |
| bssl::der::Input unused_algorithm_tlv; |
| bssl::der::Input subject_public_key_value; |
| if (!ParseSubjectPublicKeyInfo(bssl::der::Input(spki_der), |
| &unused_algorithm_tlv, |
| &subject_public_key_value)) { |
| return std::string(); |
| } |
| return ProcessRawBytes(subject_public_key_value); |
| } |
| |
| } // namespace x509_certificate_model |