|  | /* | 
|  | *  Copyright 2004 The WebRTC Project Authors. All rights reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #include "rtc_base/openssl_certificate.h" | 
|  |  | 
|  | #if defined(WEBRTC_WIN) | 
|  | // Must be included first before openssl headers. | 
|  | #include "rtc_base/win32.h"  // NOLINT | 
|  | #endif                       // WEBRTC_WIN | 
|  |  | 
|  | #include <openssl/bio.h> | 
|  | #include <openssl/bn.h> | 
|  | #include <openssl/pem.h> | 
|  | #include <time.h> | 
|  |  | 
|  | #include <memory> | 
|  |  | 
|  | #include "rtc_base/checks.h" | 
|  | #include "rtc_base/helpers.h" | 
|  | #include "rtc_base/logging.h" | 
|  | #include "rtc_base/message_digest.h" | 
|  | #include "rtc_base/openssl_digest.h" | 
|  | #include "rtc_base/openssl_identity.h" | 
|  | #include "rtc_base/openssl_utility.h" | 
|  |  | 
|  | namespace rtc { | 
|  | namespace { | 
|  |  | 
|  | // Random bits for certificate serial number | 
|  | static const int SERIAL_RAND_BITS = 64; | 
|  |  | 
|  | #if !defined(NDEBUG) | 
|  | // Print a certificate to the log, for debugging. | 
|  | static void PrintCert(X509* x509) { | 
|  | BIO* temp_memory_bio = BIO_new(BIO_s_mem()); | 
|  | if (!temp_memory_bio) { | 
|  | RTC_DLOG_F(LS_ERROR) << "Failed to allocate temporary memory bio"; | 
|  | return; | 
|  | } | 
|  | X509_print_ex(temp_memory_bio, x509, XN_FLAG_SEP_CPLUS_SPC, 0); | 
|  | BIO_write(temp_memory_bio, "\0", 1); | 
|  | char* buffer; | 
|  | BIO_get_mem_data(temp_memory_bio, &buffer); | 
|  | RTC_DLOG(LS_VERBOSE) << buffer; | 
|  | BIO_free(temp_memory_bio); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | // Generate a self-signed certificate, with the public key from the | 
|  | // given key pair. Caller is responsible for freeing the returned object. | 
|  | static X509* MakeCertificate(EVP_PKEY* pkey, const SSLIdentityParams& params) { | 
|  | RTC_LOG(LS_INFO) << "Making certificate for " << params.common_name; | 
|  |  | 
|  | ASN1_INTEGER* asn1_serial_number = nullptr; | 
|  | std::unique_ptr<BIGNUM, decltype(&::BN_free)> serial_number{nullptr, | 
|  | ::BN_free}; | 
|  | std::unique_ptr<X509, decltype(&::X509_free)> x509{nullptr, ::X509_free}; | 
|  | std::unique_ptr<X509_NAME, decltype(&::X509_NAME_free)> name{ | 
|  | nullptr, ::X509_NAME_free}; | 
|  | time_t epoch_off = 0;  // Time offset since epoch. | 
|  | x509.reset(X509_new()); | 
|  | if (x509 == nullptr) { | 
|  | return nullptr; | 
|  | } | 
|  | if (!X509_set_pubkey(x509.get(), pkey)) { | 
|  | return nullptr; | 
|  | } | 
|  | // serial number - temporary reference to serial number inside x509 struct | 
|  | serial_number.reset(BN_new()); | 
|  | if (serial_number == nullptr || | 
|  | !BN_pseudo_rand(serial_number.get(), SERIAL_RAND_BITS, 0, 0) || | 
|  | (asn1_serial_number = X509_get_serialNumber(x509.get())) == nullptr || | 
|  | !BN_to_ASN1_INTEGER(serial_number.get(), asn1_serial_number)) { | 
|  | return nullptr; | 
|  | } | 
|  | // Set version to X509.V3 | 
|  | if (!X509_set_version(x509.get(), 2L)) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // There are a lot of possible components for the name entries. In | 
|  | // our P2P SSL mode however, the certificates are pre-exchanged | 
|  | // (through the secure XMPP channel), and so the certificate | 
|  | // identification is arbitrary. It can't be empty, so we set some | 
|  | // arbitrary common_name. Note that this certificate goes out in | 
|  | // clear during SSL negotiation, so there may be a privacy issue in | 
|  | // putting anything recognizable here. | 
|  | name.reset(X509_NAME_new()); | 
|  | if (name == nullptr || | 
|  | !X509_NAME_add_entry_by_NID(name.get(), NID_commonName, MBSTRING_UTF8, | 
|  | (unsigned char*)params.common_name.c_str(), | 
|  | -1, -1, 0) || | 
|  | !X509_set_subject_name(x509.get(), name.get()) || | 
|  | !X509_set_issuer_name(x509.get(), name.get())) { | 
|  | return nullptr; | 
|  | } | 
|  | if (!X509_time_adj(X509_get_notBefore(x509.get()), params.not_before, | 
|  | &epoch_off) || | 
|  | !X509_time_adj(X509_get_notAfter(x509.get()), params.not_after, | 
|  | &epoch_off)) { | 
|  | return nullptr; | 
|  | } | 
|  | if (!X509_sign(x509.get(), pkey, EVP_sha256())) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | RTC_LOG(LS_INFO) << "Returning certificate"; | 
|  | return x509.release(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | OpenSSLCertificate::OpenSSLCertificate(X509* x509) : x509_(x509) { | 
|  | RTC_DCHECK(x509_ != nullptr); | 
|  | X509_up_ref(x509_); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<OpenSSLCertificate> OpenSSLCertificate::Generate( | 
|  | OpenSSLKeyPair* key_pair, | 
|  | const SSLIdentityParams& params) { | 
|  | SSLIdentityParams actual_params(params); | 
|  | if (actual_params.common_name.empty()) { | 
|  | // Use a random string, arbitrarily 8chars long. | 
|  | actual_params.common_name = CreateRandomString(8); | 
|  | } | 
|  | X509* x509 = MakeCertificate(key_pair->pkey(), actual_params); | 
|  | if (!x509) { | 
|  | openssl::LogSSLErrors("Generating certificate"); | 
|  | return nullptr; | 
|  | } | 
|  | #if !defined(NDEBUG) | 
|  | PrintCert(x509); | 
|  | #endif | 
|  | auto ret = std::make_unique<OpenSSLCertificate>(x509); | 
|  | X509_free(x509); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<OpenSSLCertificate> OpenSSLCertificate::FromPEMString( | 
|  | absl::string_view pem_string) { | 
|  | BIO* bio = BIO_new_mem_buf(const_cast<char*>(pem_string.data()), -1); | 
|  | if (!bio) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | BIO_set_mem_eof_return(bio, 0); | 
|  | X509* x509 = | 
|  | PEM_read_bio_X509(bio, nullptr, nullptr, const_cast<char*>("\0")); | 
|  | BIO_free(bio);  // Frees the BIO, but not the pointed-to string. | 
|  |  | 
|  | if (!x509) { | 
|  | return nullptr; | 
|  | } | 
|  | auto ret = std::make_unique<OpenSSLCertificate>(x509); | 
|  | X509_free(x509); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | // NOTE: This implementation only functions correctly after InitializeSSL | 
|  | // and before CleanupSSL. | 
|  | bool OpenSSLCertificate::GetSignatureDigestAlgorithm( | 
|  | std::string* algorithm) const { | 
|  | int nid = X509_get_signature_nid(x509_); | 
|  | switch (nid) { | 
|  | case NID_md5WithRSA: | 
|  | case NID_md5WithRSAEncryption: | 
|  | *algorithm = DIGEST_MD5; | 
|  | break; | 
|  | case NID_ecdsa_with_SHA1: | 
|  | case NID_dsaWithSHA1: | 
|  | case NID_dsaWithSHA1_2: | 
|  | case NID_sha1WithRSA: | 
|  | case NID_sha1WithRSAEncryption: | 
|  | *algorithm = DIGEST_SHA_1; | 
|  | break; | 
|  | case NID_ecdsa_with_SHA224: | 
|  | case NID_sha224WithRSAEncryption: | 
|  | case NID_dsa_with_SHA224: | 
|  | *algorithm = DIGEST_SHA_224; | 
|  | break; | 
|  | case NID_ecdsa_with_SHA256: | 
|  | case NID_sha256WithRSAEncryption: | 
|  | case NID_dsa_with_SHA256: | 
|  | *algorithm = DIGEST_SHA_256; | 
|  | break; | 
|  | case NID_ecdsa_with_SHA384: | 
|  | case NID_sha384WithRSAEncryption: | 
|  | *algorithm = DIGEST_SHA_384; | 
|  | break; | 
|  | case NID_ecdsa_with_SHA512: | 
|  | case NID_sha512WithRSAEncryption: | 
|  | *algorithm = DIGEST_SHA_512; | 
|  | break; | 
|  | default: | 
|  | // Unknown algorithm.  There are several unhandled options that are less | 
|  | // common and more complex. | 
|  | RTC_LOG(LS_ERROR) << "Unknown signature algorithm NID: " << nid; | 
|  | algorithm->clear(); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool OpenSSLCertificate::ComputeDigest(absl::string_view algorithm, | 
|  | unsigned char* digest, | 
|  | size_t size, | 
|  | size_t* length) const { | 
|  | return ComputeDigest(x509_, algorithm, digest, size, length); | 
|  | } | 
|  |  | 
|  | bool OpenSSLCertificate::ComputeDigest(const X509* x509, | 
|  | absl::string_view algorithm, | 
|  | unsigned char* digest, | 
|  | size_t size, | 
|  | size_t* length) { | 
|  | const EVP_MD* md = nullptr; | 
|  | unsigned int n = 0; | 
|  | if (!OpenSSLDigest::GetDigestEVP(algorithm, &md)) { | 
|  | return false; | 
|  | } | 
|  | if (size < static_cast<size_t>(EVP_MD_size(md))) { | 
|  | return false; | 
|  | } | 
|  | X509_digest(x509, md, digest, &n); | 
|  | *length = n; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | OpenSSLCertificate::~OpenSSLCertificate() { | 
|  | X509_free(x509_); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<SSLCertificate> OpenSSLCertificate::Clone() const { | 
|  | return std::make_unique<OpenSSLCertificate>(x509_); | 
|  | } | 
|  |  | 
|  | std::string OpenSSLCertificate::ToPEMString() const { | 
|  | BIO* bio = BIO_new(BIO_s_mem()); | 
|  | RTC_CHECK(bio); | 
|  | RTC_CHECK(PEM_write_bio_X509(bio, x509_)); | 
|  | BIO_write(bio, "\0", 1); | 
|  | char* buffer; | 
|  | BIO_get_mem_data(bio, &buffer); | 
|  | std::string ret(buffer); | 
|  | BIO_free(bio); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void OpenSSLCertificate::ToDER(Buffer* der_buffer) const { | 
|  | // In case of failure, make sure to leave the buffer empty. | 
|  | der_buffer->SetSize(0); | 
|  | // Calculates the DER representation of the certificate, from scratch. | 
|  | BIO* bio = BIO_new(BIO_s_mem()); | 
|  | RTC_CHECK(bio); | 
|  | RTC_CHECK(i2d_X509_bio(bio, x509_)); | 
|  | char* data = nullptr; | 
|  | size_t length = BIO_get_mem_data(bio, &data); | 
|  | der_buffer->SetData(data, length); | 
|  | BIO_free(bio); | 
|  | } | 
|  |  | 
|  | bool OpenSSLCertificate::operator==(const OpenSSLCertificate& other) const { | 
|  | return X509_cmp(x509_, other.x509_) == 0; | 
|  | } | 
|  |  | 
|  | bool OpenSSLCertificate::operator!=(const OpenSSLCertificate& other) const { | 
|  | return !(*this == other); | 
|  | } | 
|  |  | 
|  | int64_t OpenSSLCertificate::CertificateExpirationTime() const { | 
|  | ASN1_TIME* expire_time = X509_get_notAfter(x509_); | 
|  | bool long_format; | 
|  | if (expire_time->type == V_ASN1_UTCTIME) { | 
|  | long_format = false; | 
|  | } else if (expire_time->type == V_ASN1_GENERALIZEDTIME) { | 
|  | long_format = true; | 
|  | } else { | 
|  | return -1; | 
|  | } | 
|  | return ASN1TimeToSec(expire_time->data, expire_time->length, long_format); | 
|  | } | 
|  |  | 
|  | }  // namespace rtc |