|  | // Copyright 2021 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 "quic/core/crypto/certificate_util.h" | 
|  |  | 
|  | #include "absl/strings/str_format.h" | 
|  | #include "absl/strings/str_split.h" | 
|  | #include "absl/strings/string_view.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/digest.h" | 
|  | #include "third_party/boringssl/src/include/openssl/ec_key.h" | 
|  | #include "third_party/boringssl/src/include/openssl/mem.h" | 
|  | #include "third_party/boringssl/src/include/openssl/pkcs7.h" | 
|  | #include "third_party/boringssl/src/include/openssl/pool.h" | 
|  | #include "third_party/boringssl/src/include/openssl/rsa.h" | 
|  | #include "third_party/boringssl/src/include/openssl/stack.h" | 
|  | #include "quic/core/crypto/boring_utils.h" | 
|  | #include "quic/platform/api/quic_logging.h" | 
|  |  | 
|  | namespace quic { | 
|  | namespace { | 
|  | bool AddEcdsa256SignatureAlgorithm(CBB* cbb) { | 
|  | // See RFC 5758. This is the encoding of OID 1.2.840.10045.4.3.2. | 
|  | static const uint8_t kEcdsaWithSha256[] = {0x2a, 0x86, 0x48, 0xce, | 
|  | 0x3d, 0x04, 0x03, 0x02}; | 
|  |  | 
|  | // An AlgorithmIdentifier is described in RFC 5280, 4.1.1.2. | 
|  | CBB sequence, oid; | 
|  | if (!CBB_add_asn1(cbb, &sequence, CBS_ASN1_SEQUENCE) || | 
|  | !CBB_add_asn1(&sequence, &oid, CBS_ASN1_OBJECT)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!CBB_add_bytes(&oid, kEcdsaWithSha256, sizeof(kEcdsaWithSha256))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // RFC 5758, section 3.2: ecdsa-with-sha256 MUST omit the parameters field. | 
|  | return CBB_flush(cbb); | 
|  | } | 
|  |  | 
|  | // Adds an X.509 Name with the specified distinguished name to |cbb|. | 
|  | bool AddName(CBB* cbb, absl::string_view name) { | 
|  | // See RFC 4519. | 
|  | static const uint8_t kCommonName[] = {0x55, 0x04, 0x03}; | 
|  | static const uint8_t kCountryName[] = {0x55, 0x04, 0x06}; | 
|  | static const uint8_t kOrganizationName[] = {0x55, 0x04, 0x0a}; | 
|  | static const uint8_t kOrganizationalUnitName[] = {0x55, 0x04, 0x0b}; | 
|  |  | 
|  | std::vector<std::string> attributes = | 
|  | absl::StrSplit(name, ',', absl::SkipEmpty()); | 
|  |  | 
|  | if (attributes.empty()) { | 
|  | QUIC_LOG(ERROR) << "Missing DN or wrong format"; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // See RFC 5280, section 4.1.2.4. | 
|  | CBB rdns; | 
|  | if (!CBB_add_asn1(cbb, &rdns, CBS_ASN1_SEQUENCE)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (const std::string& attribute : attributes) { | 
|  | std::vector<std::string> parts = | 
|  | absl::StrSplit(absl::StripAsciiWhitespace(attribute), '='); | 
|  | if (parts.size() != 2) { | 
|  | QUIC_LOG(ERROR) << "Wrong DN format at " + attribute; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const std::string& type_string = parts[0]; | 
|  | const std::string& value_string = parts[1]; | 
|  | absl::Span<const uint8_t> type_bytes; | 
|  | if (type_string == "CN") { | 
|  | type_bytes = kCommonName; | 
|  | } else if (type_string == "C") { | 
|  | type_bytes = kCountryName; | 
|  | } else if (type_string == "O") { | 
|  | type_bytes = kOrganizationName; | 
|  | } else if (type_string == "OU") { | 
|  | type_bytes = kOrganizationalUnitName; | 
|  | } else { | 
|  | QUIC_LOG(ERROR) << "Unrecognized type " + type_string; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | CBB rdn, attr, type, value; | 
|  | if (!CBB_add_asn1(&rdns, &rdn, CBS_ASN1_SET) || | 
|  | !CBB_add_asn1(&rdn, &attr, CBS_ASN1_SEQUENCE) || | 
|  | !CBB_add_asn1(&attr, &type, CBS_ASN1_OBJECT) || | 
|  | !CBB_add_bytes(&type, type_bytes.data(), type_bytes.size()) || | 
|  | !CBB_add_asn1(&attr, &value, | 
|  | type_string == "C" ? CBS_ASN1_PRINTABLESTRING | 
|  | : CBS_ASN1_UTF8STRING) || | 
|  | !AddStringToCbb(&value, value_string) || !CBB_flush(&rdns)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | if (!CBB_flush(cbb)) { | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CBBAddTime(CBB* cbb, const CertificateTimestamp& timestamp) { | 
|  | CBB child; | 
|  | std::string formatted_time; | 
|  |  | 
|  | // Per RFC 5280, 4.1.2.5, times which fit in UTCTime must be encoded as | 
|  | // UTCTime rather than GeneralizedTime. | 
|  | const bool is_utc_time = (1950 <= timestamp.year && timestamp.year < 2050); | 
|  | if (is_utc_time) { | 
|  | uint16_t year = timestamp.year - 1900; | 
|  | if (year >= 100) { | 
|  | year -= 100; | 
|  | } | 
|  | formatted_time = absl::StrFormat("%02d", year); | 
|  | if (!CBB_add_asn1(cbb, &child, CBS_ASN1_UTCTIME)) { | 
|  | return false; | 
|  | } | 
|  | } else { | 
|  | formatted_time = absl::StrFormat("%04d", timestamp.year); | 
|  | if (!CBB_add_asn1(cbb, &child, CBS_ASN1_GENERALIZEDTIME)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | absl::StrAppendFormat(&formatted_time, "%02d%02d%02d%02d%02dZ", | 
|  | timestamp.month, timestamp.day, timestamp.hour, | 
|  | timestamp.minute, timestamp.second); | 
|  |  | 
|  | static const size_t kGeneralizedTimeLength = 15; | 
|  | static const size_t kUTCTimeLength = 13; | 
|  | QUICHE_DCHECK_EQ(formatted_time.size(), | 
|  | is_utc_time ? kUTCTimeLength : kGeneralizedTimeLength); | 
|  |  | 
|  | return AddStringToCbb(&child, formatted_time) && CBB_flush(cbb); | 
|  | } | 
|  |  | 
|  | bool CBBAddExtension(CBB* extensions, absl::Span<const uint8_t> oid, | 
|  | bool critical, absl::Span<const uint8_t> contents) { | 
|  | CBB extension, cbb_oid, cbb_contents; | 
|  | if (!CBB_add_asn1(extensions, &extension, CBS_ASN1_SEQUENCE) || | 
|  | !CBB_add_asn1(&extension, &cbb_oid, CBS_ASN1_OBJECT) || | 
|  | !CBB_add_bytes(&cbb_oid, oid.data(), oid.size()) || | 
|  | (critical && !CBB_add_asn1_bool(&extension, 1)) || | 
|  | !CBB_add_asn1(&extension, &cbb_contents, CBS_ASN1_OCTETSTRING) || | 
|  | !CBB_add_bytes(&cbb_contents, contents.data(), contents.size()) || | 
|  | !CBB_flush(extensions)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool IsEcdsa256Key(const EVP_PKEY& evp_key) { | 
|  | if (EVP_PKEY_id(&evp_key) != EVP_PKEY_EC) { | 
|  | return false; | 
|  | } | 
|  | const EC_KEY* key = EVP_PKEY_get0_EC_KEY(&evp_key); | 
|  | if (key == nullptr) { | 
|  | return false; | 
|  | } | 
|  | const EC_GROUP* group = EC_KEY_get0_group(key); | 
|  | if (group == nullptr) { | 
|  | return false; | 
|  | } | 
|  | return EC_GROUP_get_curve_name(group) == NID_X9_62_prime256v1; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | bssl::UniquePtr<EVP_PKEY> MakeKeyPairForSelfSignedCertificate() { | 
|  | bssl::UniquePtr<EVP_PKEY_CTX> context( | 
|  | EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr)); | 
|  | if (!context) { | 
|  | return nullptr; | 
|  | } | 
|  | if (EVP_PKEY_keygen_init(context.get()) != 1) { | 
|  | return nullptr; | 
|  | } | 
|  | if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(context.get(), | 
|  | NID_X9_62_prime256v1) != 1) { | 
|  | return nullptr; | 
|  | } | 
|  | EVP_PKEY* raw_key = nullptr; | 
|  | if (EVP_PKEY_keygen(context.get(), &raw_key) != 1) { | 
|  | return nullptr; | 
|  | } | 
|  | return bssl::UniquePtr<EVP_PKEY>(raw_key); | 
|  | } | 
|  |  | 
|  | std::string CreateSelfSignedCertificate(EVP_PKEY& key, | 
|  | const CertificateOptions& options) { | 
|  | std::string error; | 
|  | if (!IsEcdsa256Key(key)) { | 
|  | QUIC_LOG(ERROR) << "CreateSelfSignedCert only accepts ECDSA P-256 keys"; | 
|  | return error; | 
|  | } | 
|  |  | 
|  | // See RFC 5280, section 4.1. First, construct the TBSCertificate. | 
|  | bssl::ScopedCBB cbb; | 
|  | CBB tbs_cert, version, validity; | 
|  | uint8_t* tbs_cert_bytes; | 
|  | size_t tbs_cert_len; | 
|  |  | 
|  | if (!CBB_init(cbb.get(), 64) || | 
|  | !CBB_add_asn1(cbb.get(), &tbs_cert, CBS_ASN1_SEQUENCE) || | 
|  | !CBB_add_asn1(&tbs_cert, &version, | 
|  | CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) || | 
|  | !CBB_add_asn1_uint64(&version, 2) ||  // X.509 version 3 | 
|  | !CBB_add_asn1_uint64(&tbs_cert, options.serial_number) || | 
|  | !AddEcdsa256SignatureAlgorithm(&tbs_cert) ||  // signature algorithm | 
|  | !AddName(&tbs_cert, options.subject) ||       // issuer | 
|  | !CBB_add_asn1(&tbs_cert, &validity, CBS_ASN1_SEQUENCE) || | 
|  | !CBBAddTime(&validity, options.validity_start) || | 
|  | !CBBAddTime(&validity, options.validity_end) || | 
|  | !AddName(&tbs_cert, options.subject) ||      // subject | 
|  | !EVP_marshal_public_key(&tbs_cert, &key)) {  // subjectPublicKeyInfo | 
|  | return error; | 
|  | } | 
|  |  | 
|  | CBB outer_extensions, extensions; | 
|  | if (!CBB_add_asn1(&tbs_cert, &outer_extensions, | 
|  | 3 | CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED) || | 
|  | !CBB_add_asn1(&outer_extensions, &extensions, CBS_ASN1_SEQUENCE)) { | 
|  | return error; | 
|  | } | 
|  |  | 
|  | // Key Usage | 
|  | constexpr uint8_t kKeyUsageOid[] = {0x55, 0x1d, 0x0f}; | 
|  | constexpr uint8_t kKeyUsageContent[] = { | 
|  | 0x3,   // BIT STRING | 
|  | 0x2,   // Length | 
|  | 0x0,   // Unused bits | 
|  | 0x80,  // bit(0): digitalSignature | 
|  | }; | 
|  | CBBAddExtension(&extensions, kKeyUsageOid, true, kKeyUsageContent); | 
|  |  | 
|  | // TODO(wub): Add more extensions here if needed. | 
|  |  | 
|  | if (!CBB_finish(cbb.get(), &tbs_cert_bytes, &tbs_cert_len)) { | 
|  | return error; | 
|  | } | 
|  |  | 
|  | bssl::UniquePtr<uint8_t> delete_tbs_cert_bytes(tbs_cert_bytes); | 
|  |  | 
|  | // Sign the TBSCertificate and write the entire certificate. | 
|  | CBB cert, signature; | 
|  | bssl::ScopedEVP_MD_CTX ctx; | 
|  | uint8_t* sig_out; | 
|  | size_t sig_len; | 
|  | uint8_t* cert_bytes; | 
|  | size_t cert_len; | 
|  | if (!CBB_init(cbb.get(), tbs_cert_len) || | 
|  | !CBB_add_asn1(cbb.get(), &cert, CBS_ASN1_SEQUENCE) || | 
|  | !CBB_add_bytes(&cert, tbs_cert_bytes, tbs_cert_len) || | 
|  | !AddEcdsa256SignatureAlgorithm(&cert) || | 
|  | !CBB_add_asn1(&cert, &signature, CBS_ASN1_BITSTRING) || | 
|  | !CBB_add_u8(&signature, 0 /* no unused bits */) || | 
|  | !EVP_DigestSignInit(ctx.get(), nullptr, EVP_sha256(), nullptr, &key) || | 
|  | // Compute the maximum signature length. | 
|  | !EVP_DigestSign(ctx.get(), nullptr, &sig_len, tbs_cert_bytes, | 
|  | tbs_cert_len) || | 
|  | !CBB_reserve(&signature, &sig_out, sig_len) || | 
|  | // Actually sign the TBSCertificate. | 
|  | !EVP_DigestSign(ctx.get(), sig_out, &sig_len, tbs_cert_bytes, | 
|  | tbs_cert_len) || | 
|  | !CBB_did_write(&signature, sig_len) || | 
|  | !CBB_finish(cbb.get(), &cert_bytes, &cert_len)) { | 
|  | return error; | 
|  | } | 
|  | bssl::UniquePtr<uint8_t> delete_cert_bytes(cert_bytes); | 
|  | return std::string(reinterpret_cast<char*>(cert_bytes), cert_len); | 
|  | } | 
|  |  | 
|  | }  // namespace quic |