// Copyright 2019 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 "util/crypto/certificate_utils.h" #include #include #include #include #include #include #include #include #include #include #include "util/crypto/openssl_util.h" #include "util/crypto/sha2.h" #include "util/osp_logging.h" namespace openscreen { namespace { // These values are bit positions from RFC 5280 4.2.1.3 and will be passed to // ASN1_BIT_STRING_set_bit. enum KeyUsageBits { kDigitalSignature = 0, kKeyCertSign = 5, }; // Returns whether or not the certificate field successfully was added. bool AddCertificateField(X509_NAME* certificate_name, absl::string_view field, absl::string_view value) { return X509_NAME_add_entry_by_txt( certificate_name, std::string(field).c_str(), MBSTRING_ASC, reinterpret_cast(value.data()), value.length(), -1, 0) == 1; } bssl::UniquePtr ToAsn1Time(std::chrono::seconds time_since_epoch) { return bssl::UniquePtr( ASN1_TIME_set(nullptr, time_since_epoch.count())); } bssl::UniquePtr CreateCertificateInternal( absl::string_view name, std::chrono::seconds certificate_duration, EVP_PKEY key_pair, std::chrono::seconds time_since_unix_epoch, bool make_ca, X509* issuer, EVP_PKEY* issuer_key) { OSP_DCHECK((!!issuer) == (!!issuer_key)); bssl::UniquePtr certificate(X509_new()); if (!issuer) { issuer = certificate.get(); } if (!issuer_key) { issuer_key = &key_pair; } // Certificate versions are zero indexed, so V1 = 0. const int kCertificateVersion3 = 2; if (X509_set_version(certificate.get(), kCertificateVersion3) != 1) { OSP_DVLOG << "Failed to set certificate version"; return nullptr; } // Serial numbers must be unique for this session. As a pretend CA, we should // not issue certificates with the same serial number in the same session. static int serial_number(1); if (ASN1_INTEGER_set(X509_get_serialNumber(certificate.get()), serial_number++) != 1) { OSP_DVLOG << "Failed to set serial number."; return nullptr; } const bssl::UniquePtr now(ToAsn1Time(time_since_unix_epoch)); const bssl::UniquePtr expiration_time( ToAsn1Time(time_since_unix_epoch + certificate_duration)); if ((X509_set_notBefore(certificate.get(), now.get()) != 1) || (X509_set_notAfter(certificate.get(), expiration_time.get()) != 1)) { OSP_DVLOG << "Failed to set before and after ranges."; return nullptr; } X509_NAME* certificate_name = X509_get_subject_name(certificate.get()); if (!AddCertificateField(certificate_name, "CN", name)) { OSP_DVLOG << "Failed to set subject name"; return nullptr; } bssl::UniquePtr x(ASN1_BIT_STRING_new()); ASN1_BIT_STRING_set_bit(x.get(), KeyUsageBits::kDigitalSignature, 1); if (make_ca) { ASN1_BIT_STRING_set_bit(x.get(), KeyUsageBits::kKeyCertSign, 1); } if (X509_add1_ext_i2d(certificate.get(), NID_key_usage, x.get(), 0, 0) != 1) { OSP_DVLOG << "Failed to set key usage extension"; return nullptr; } if (make_ca) { X509V3_CTX ctx; X509V3_set_ctx_nodb(&ctx); X509V3_set_ctx(&ctx, issuer, certificate.get(), nullptr, nullptr, 0); bssl::UniquePtr ex( X509V3_EXT_nconf_nid(nullptr, &ctx, NID_basic_constraints, const_cast("critical,CA:TRUE"))); if (!ex) { OSP_DVLOG << "Failed to set constraints extension"; return nullptr; } void* thing = X509V3_EXT_d2i(ex.get()); X509_add1_ext_i2d(certificate.get(), NID_basic_constraints, thing, 1, 0); X509V3_EXT_free(NID_basic_constraints, thing); } X509_NAME* issuer_name = X509_get_subject_name(issuer); if ((X509_set_issuer_name(certificate.get(), issuer_name) != 1) || (X509_set_pubkey(certificate.get(), &key_pair) != 1) || // Unlike all of the other BoringSSL methods here, X509_sign returns // the size of the signature in bytes. (X509_sign(certificate.get(), issuer_key, EVP_sha256()) <= 0) || (X509_verify(certificate.get(), issuer_key) != 1)) { OSP_DVLOG << "Failed to set pubkey, set issuer, sign, or verify"; return nullptr; } return certificate; } } // namespace bssl::UniquePtr GenerateRsaKeyPair(int key_bits) { bssl::UniquePtr prime(BN_new()); if (BN_set_word(prime.get(), RSA_F4) == 0) { return nullptr; } bssl::UniquePtr rsa(RSA_new()); if (RSA_generate_key_ex(rsa.get(), key_bits, prime.get(), nullptr) == 0) { return nullptr; } bssl::UniquePtr pkey(EVP_PKEY_new()); if (EVP_PKEY_set1_RSA(pkey.get(), rsa.get()) == 0) { return nullptr; } return pkey; } ErrorOr> CreateSelfSignedX509Certificate( absl::string_view name, std::chrono::seconds duration, const EVP_PKEY& key_pair, std::chrono::seconds time_since_unix_epoch, bool make_ca, X509* issuer, EVP_PKEY* issuer_key) { bssl::UniquePtr certificate = CreateCertificateInternal(name, duration, key_pair, time_since_unix_epoch, make_ca, issuer, issuer_key); if (!certificate) { return Error::Code::kCertificateCreationError; } return certificate; } ErrorOr> ExportX509CertificateToDer( const X509& certificate) { unsigned char* buffer = nullptr; // Casting-away the const because the legacy i2d_X509() function is not // const-correct. X509* const certificate_ptr = const_cast(&certificate); const int len = i2d_X509(certificate_ptr, &buffer); if (len <= 0) { return Error::Code::kCertificateValidationError; } std::vector raw_der_certificate(buffer, buffer + len); // BoringSSL doesn't free the temporary buffer. OPENSSL_free(buffer); return raw_der_certificate; } ErrorOr> ImportCertificate(const uint8_t* der_x509_cert, int der_x509_cert_length) { if (!der_x509_cert) { return Error::Code::kErrCertsMissing; } bssl::UniquePtr certificate( d2i_X509(nullptr, &der_x509_cert, der_x509_cert_length)); if (!certificate) { return Error::Code::kCertificateValidationError; } return certificate; } ErrorOr> ImportRSAPrivateKey( const uint8_t* der_rsa_private_key, int key_length) { if (!der_rsa_private_key || key_length == 0) { return Error::Code::kParameterInvalid; } RSA* rsa = RSA_private_key_from_bytes(der_rsa_private_key, key_length); if (!rsa) { return Error::Code::kRSAKeyParseError; } bssl::UniquePtr pkey(EVP_PKEY_new()); EVP_PKEY_assign_RSA(pkey.get(), rsa); return pkey; } std::string GetSpkiTlv(X509* cert) { X509_PUBKEY* key = X509_get_X509_PUBKEY(cert); int len = i2d_X509_PUBKEY(key, nullptr); if (len <= 0) { return {}; } std::string x(len, 0); uint8_t* data = reinterpret_cast(&x[0]); if (!i2d_X509_PUBKEY(key, &data)) { return {}; } return x; } ErrorOr ParseDerUint64(const ASN1_INTEGER* asn1int) { const uint8_t* data = ASN1_STRING_get0_data(asn1int); int length = ASN1_STRING_length(asn1int); if (length > 8 || length <= 0) { return Error::Code::kParameterInvalid; } uint64_t result = 0; for (int i = 0; i < length; ++i) { result = (result << 8) | data[i]; } return result; } } // namespace openscreen