From 72f638a9a279e7abb5534fa66a0ade2cf18ec1a7 Mon Sep 17 00:00:00 2001 From: Taylor Brandstetter Date: Sun, 15 Nov 2020 13:01:15 -0800 Subject: [PATCH] Use CRYPTO_BUFFER APIs instead of X509 when building with BoringSSL. Using CRYPTO_BUFFERs instead of legacy X509 objects offers memory and security gains, and will provide binary size improvements as well once the default list of built-in certificates can be removed; the code dealing with them still depends on the X509 API. Implemented by splitting openssl_identity and openssl_certificate into BoringSSL and vanilla OpenSSL implementations. Bug: webrtc:11410 Change-Id: Idc043462faac5e4ab1b75bedab2057197f80aba6 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/174120 Reviewed-by: Mirko Bonadei Reviewed-by: David Benjamin Reviewed-by: Harald Alvestrand Commit-Queue: Taylor Cr-Commit-Position: refs/heads/master@{#32811} --- pc/test/fake_rtc_certificate_generator.h | 2 +- rtc_base/BUILD.gn | 22 +- rtc_base/boringssl_certificate.cc | 410 +++++++++++++++++++++ rtc_base/boringssl_certificate.h | 80 ++++ rtc_base/boringssl_identity.cc | 215 +++++++++++ rtc_base/boringssl_identity.h | 76 ++++ rtc_base/openssl_adapter.cc | 150 ++++++-- rtc_base/openssl_adapter.h | 19 + rtc_base/openssl_identity.cc | 171 +-------- rtc_base/openssl_identity.h | 33 +- rtc_base/openssl_key_pair.cc | 192 ++++++++++ rtc_base/openssl_key_pair.h | 60 +++ rtc_base/openssl_session_cache_unittest.cc | 30 +- rtc_base/openssl_stream_adapter.cc | 80 +++- rtc_base/openssl_stream_adapter.h | 17 +- rtc_base/openssl_utility.cc | 148 +++++++- rtc_base/openssl_utility.h | 17 + rtc_base/openssl_utility_unittest.cc | 42 ++- rtc_base/rtc_certificate_generator.cc | 2 +- rtc_base/ssl_certificate.cc | 11 +- rtc_base/ssl_identity.cc | 36 +- rtc_base/ssl_identity_unittest.cc | 17 +- rtc_base/ssl_stream_adapter_unittest.cc | 63 +++- webrtc.gni | 4 + 24 files changed, 1619 insertions(+), 278 deletions(-) create mode 100644 rtc_base/boringssl_certificate.cc create mode 100644 rtc_base/boringssl_certificate.h create mode 100644 rtc_base/boringssl_identity.cc create mode 100644 rtc_base/boringssl_identity.h create mode 100644 rtc_base/openssl_key_pair.cc create mode 100644 rtc_base/openssl_key_pair.h diff --git a/pc/test/fake_rtc_certificate_generator.h b/pc/test/fake_rtc_certificate_generator.h index b726a4c0ba..b591c4c4ab 100644 --- a/pc/test/fake_rtc_certificate_generator.h +++ b/pc/test/fake_rtc_certificate_generator.h @@ -83,7 +83,7 @@ static const rtc::RTCCertificatePEM kRsaPems[] = { // ECDSA with EC_NIST_P256. // These PEM strings were created by generating an identity with -// |SSLIdentity::Generate| and invoking |identity->PrivateKeyToPEMString()|, +// |SSLIdentity::Create| and invoking |identity->PrivateKeyToPEMString()|, // |identity->PublicKeyToPEMString()| and // |identity->certificate().ToPEMString()|. static const rtc::RTCCertificatePEM kEcdsaPems[] = { diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn index 8b920908f3..8762bfbeb1 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -911,12 +911,10 @@ rtc_library("rtc_base") { "openssl.h", "openssl_adapter.cc", "openssl_adapter.h", - "openssl_certificate.cc", - "openssl_certificate.h", "openssl_digest.cc", "openssl_digest.h", - "openssl_identity.cc", - "openssl_identity.h", + "openssl_key_pair.cc", + "openssl_key_pair.h", "openssl_session_cache.cc", "openssl_session_cache.h", "openssl_stream_adapter.cc", @@ -962,6 +960,22 @@ rtc_library("rtc_base") { "unique_id_generator.h", ] + if (rtc_openssl_is_boringssl) { + sources += [ + "boringssl_certificate.cc", + "boringssl_certificate.h", + "boringssl_identity.cc", + "boringssl_identity.h", + ] + } else { + sources += [ + "openssl_certificate.cc", + "openssl_certificate.h", + "openssl_identity.cc", + "openssl_identity.h", + ] + } + if (build_with_chromium) { include_dirs = [ "../../boringssl/src/include" ] public_configs += [ ":rtc_base_chromium_config" ] diff --git a/rtc_base/boringssl_certificate.cc b/rtc_base/boringssl_certificate.cc new file mode 100644 index 0000000000..c9713c2c8c --- /dev/null +++ b/rtc_base/boringssl_certificate.cc @@ -0,0 +1,410 @@ +/* + * Copyright 2020 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/boringssl_certificate.h" + +#if defined(WEBRTC_WIN) +// Must be included first before openssl headers. +#include "rtc_base/win32.h" // NOLINT +#endif // WEBRTC_WIN + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 { + +// List of OIDs of signature algorithms accepted by WebRTC. +// Taken from openssl/nid.h. +static const uint8_t kMD5WithRSA[] = {0x2b, 0x0e, 0x03, 0x02, 0x03}; +static const uint8_t kMD5WithRSAEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x04}; +static const uint8_t kECDSAWithSHA1[] = {0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x01}; +static const uint8_t kDSAWithSHA1[] = {0x2a, 0x86, 0x48, 0xce, + 0x38, 0x04, 0x03}; +static const uint8_t kDSAWithSHA1_2[] = {0x2b, 0x0e, 0x03, 0x02, 0x1b}; +static const uint8_t kSHA1WithRSA[] = {0x2b, 0x0e, 0x03, 0x02, 0x1d}; +static const uint8_t kSHA1WithRSAEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x05}; +static const uint8_t kECDSAWithSHA224[] = {0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x01}; +static const uint8_t kSHA224WithRSAEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x0e}; +static const uint8_t kDSAWithSHA224[] = {0x60, 0x86, 0x48, 0x01, 0x65, + 0x03, 0x04, 0x03, 0x01}; +static const uint8_t kECDSAWithSHA256[] = {0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x02}; +static const uint8_t kSHA256WithRSAEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x0b}; +static const uint8_t kDSAWithSHA256[] = {0x60, 0x86, 0x48, 0x01, 0x65, + 0x03, 0x04, 0x03, 0x028}; +static const uint8_t kECDSAWithSHA384[] = {0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x03}; +static const uint8_t kSHA384WithRSAEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x0c}; +static const uint8_t kECDSAWithSHA512[] = {0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x04}; +static const uint8_t kSHA512WithRSAEncryption[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x0d}; + +#if !defined(NDEBUG) +// Print a certificate to the log, for debugging. +static void PrintCert(BoringSSLCertificate* cert) { + // Since we're using CRYPTO_BUFFER, we can't use X509_print_ex, so we'll just + // print the PEM string. + RTC_DLOG(LS_VERBOSE) << "PEM representation of certificate:\n" + << cert->ToPEMString(); +} +#endif + +bool AddSHA256SignatureAlgorithm(CBB* cbb, KeyType key_type) { + // An AlgorithmIdentifier is described in RFC 5280, 4.1.1.2. + CBB sequence, oid, params; + if (!CBB_add_asn1(cbb, &sequence, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&sequence, &oid, CBS_ASN1_OBJECT)) { + return false; + } + + switch (key_type) { + case KT_RSA: + if (!CBB_add_bytes(&oid, kSHA256WithRSAEncryption, + sizeof(kSHA256WithRSAEncryption)) || + !CBB_add_asn1(&sequence, ¶ms, CBS_ASN1_NULL)) { + return false; + } + break; + case KT_ECDSA: + if (!CBB_add_bytes(&oid, kECDSAWithSHA256, sizeof(kECDSAWithSHA256))) { + return false; + } + break; + default: + RTC_NOTREACHED(); + return false; + } + if (!CBB_flush(cbb)) { + return false; + } + return true; +} + +// Adds an X.509 Common Name to |cbb|. +bool AddCommonName(CBB* cbb, const std::string& common_name) { + // See RFC 4519. + static const uint8_t kCommonName[] = {0x55, 0x04, 0x03}; + + if (common_name.empty()) { + RTC_LOG(LS_ERROR) << "Common name cannot be empty."; + return false; + } + + // See RFC 5280, section 4.1.2.4. + CBB rdns; + if (!CBB_add_asn1(cbb, &rdns, CBS_ASN1_SEQUENCE)) { + 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, kCommonName, sizeof(kCommonName)) || + !CBB_add_asn1(&attr, &value, CBS_ASN1_UTF8STRING) || + !CBB_add_bytes(&value, + reinterpret_cast(common_name.c_str()), + common_name.size()) || + !CBB_flush(cbb)) { + return false; + } + + return true; +} + +bool AddTime(CBB* cbb, time_t time) { + bssl::UniquePtr asn1_time(ASN1_TIME_new()); + if (!asn1_time) { + return false; + } + + if (!ASN1_TIME_set(asn1_time.get(), time)) { + return false; + } + + unsigned tag; + switch (asn1_time->type) { + case V_ASN1_UTCTIME: + tag = CBS_ASN1_UTCTIME; + break; + case V_ASN1_GENERALIZEDTIME: + tag = CBS_ASN1_GENERALIZEDTIME; + break; + default: + return false; + } + + CBB child; + if (!CBB_add_asn1(cbb, &child, tag) || + !CBB_add_bytes(&child, asn1_time->data, asn1_time->length) || + !CBB_flush(cbb)) { + return false; + } + + return true; +} + +// Generate a self-signed certificate, with the public key from the +// given key pair. Caller is responsible for freeing the returned object. +static bssl::UniquePtr MakeCertificate( + EVP_PKEY* pkey, + const SSLIdentityParams& params) { + RTC_LOG(LS_INFO) << "Making certificate for " << params.common_name; + + // 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; + uint64_t serial_number; + 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) || + !RAND_bytes(reinterpret_cast(&serial_number), + sizeof(serial_number)) || + !CBB_add_asn1_uint64(&tbs_cert, serial_number) || + !AddSHA256SignatureAlgorithm(&tbs_cert, params.key_params.type()) || + !AddCommonName(&tbs_cert, params.common_name) || // issuer + !CBB_add_asn1(&tbs_cert, &validity, CBS_ASN1_SEQUENCE) || + !AddTime(&validity, params.not_before) || + !AddTime(&validity, params.not_after) || + !AddCommonName(&tbs_cert, params.common_name) || // subject + !EVP_marshal_public_key(&tbs_cert, pkey) || // subjectPublicKeyInfo + !CBB_finish(cbb.get(), &tbs_cert_bytes, &tbs_cert_len)) { + return nullptr; + } + + bssl::UniquePtr 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) || + !AddSHA256SignatureAlgorithm(&cert, params.key_params.type()) || + !CBB_add_asn1(&cert, &signature, CBS_ASN1_BITSTRING) || + !CBB_add_u8(&signature, 0 /* no unused bits */) || + !EVP_DigestSignInit(ctx.get(), nullptr, EVP_sha256(), nullptr, pkey) || + // 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 nullptr; + } + bssl::UniquePtr delete_cert_bytes(cert_bytes); + + RTC_LOG(LS_INFO) << "Returning certificate"; + return bssl::UniquePtr( + CRYPTO_BUFFER_new(cert_bytes, cert_len, openssl::GetBufferPool())); +} + +} // namespace + +BoringSSLCertificate::BoringSSLCertificate( + bssl::UniquePtr cert_buffer) + : cert_buffer_(std::move(cert_buffer)) { + RTC_DCHECK(cert_buffer_ != nullptr); +} + +std::unique_ptr BoringSSLCertificate::Generate( + OpenSSLKeyPair* key_pair, + const SSLIdentityParams& params) { + SSLIdentityParams actual_params(params); + if (actual_params.common_name.empty()) { + // Use a random string, arbitrarily 8 chars long. + actual_params.common_name = CreateRandomString(8); + } + bssl::UniquePtr cert_buffer = + MakeCertificate(key_pair->pkey(), actual_params); + if (!cert_buffer) { + openssl::LogSSLErrors("Generating certificate"); + return nullptr; + } + auto ret = std::make_unique(std::move(cert_buffer)); +#if !defined(NDEBUG) + PrintCert(ret.get()); +#endif + return ret; +} + +std::unique_ptr BoringSSLCertificate::FromPEMString( + const std::string& pem_string) { + std::string der; + if (!SSLIdentity::PemToDer(kPemTypeCertificate, pem_string, &der)) { + return nullptr; + } + bssl::UniquePtr cert_buffer( + CRYPTO_BUFFER_new(reinterpret_cast(der.c_str()), + der.length(), openssl::GetBufferPool())); + if (!cert_buffer) { + return nullptr; + } + return std::make_unique(std::move(cert_buffer)); +} + +#define OID_MATCHES(oid, oid_other) \ + (CBS_len(&oid) == sizeof(oid_other) && \ + 0 == memcmp(CBS_data(&oid), oid_other, sizeof(oid_other))) + +bool BoringSSLCertificate::GetSignatureDigestAlgorithm( + std::string* algorithm) const { + CBS oid; + if (!openssl::ParseCertificate(cert_buffer_.get(), &oid, nullptr)) { + RTC_LOG(LS_ERROR) << "Failed to parse certificate."; + return false; + } + if (OID_MATCHES(oid, kMD5WithRSA) || + OID_MATCHES(oid, kMD5WithRSAEncryption)) { + *algorithm = DIGEST_MD5; + return true; + } + if (OID_MATCHES(oid, kECDSAWithSHA1) || OID_MATCHES(oid, kDSAWithSHA1) || + OID_MATCHES(oid, kDSAWithSHA1_2) || OID_MATCHES(oid, kSHA1WithRSA) || + OID_MATCHES(oid, kSHA1WithRSAEncryption)) { + *algorithm = DIGEST_SHA_1; + return true; + } + if (OID_MATCHES(oid, kECDSAWithSHA224) || + OID_MATCHES(oid, kSHA224WithRSAEncryption) || + OID_MATCHES(oid, kDSAWithSHA224)) { + *algorithm = DIGEST_SHA_224; + return true; + } + if (OID_MATCHES(oid, kECDSAWithSHA256) || + OID_MATCHES(oid, kSHA256WithRSAEncryption) || + OID_MATCHES(oid, kDSAWithSHA256)) { + *algorithm = DIGEST_SHA_256; + return true; + } + if (OID_MATCHES(oid, kECDSAWithSHA384) || + OID_MATCHES(oid, kSHA384WithRSAEncryption)) { + *algorithm = DIGEST_SHA_384; + return true; + } + if (OID_MATCHES(oid, kECDSAWithSHA512) || + OID_MATCHES(oid, kSHA512WithRSAEncryption)) { + *algorithm = DIGEST_SHA_512; + return true; + } + // Unknown algorithm. There are several unhandled options that are less + // common and more complex. + RTC_LOG(LS_ERROR) << "Unknown signature algorithm."; + algorithm->clear(); + return false; +} + +bool BoringSSLCertificate::ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const { + return ComputeDigest(cert_buffer_.get(), algorithm, digest, size, length); +} + +bool BoringSSLCertificate::ComputeDigest(const CRYPTO_BUFFER* cert_buffer, + const std::string& 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(EVP_MD_size(md))) { + return false; + } + if (!EVP_Digest(CRYPTO_BUFFER_data(cert_buffer), + CRYPTO_BUFFER_len(cert_buffer), digest, &n, md, nullptr)) { + return false; + } + *length = n; + return true; +} + +BoringSSLCertificate::~BoringSSLCertificate() {} + +std::unique_ptr BoringSSLCertificate::Clone() const { + return std::make_unique( + bssl::UpRef(cert_buffer_.get())); +} + +std::string BoringSSLCertificate::ToPEMString() const { + return OpenSSLIdentity::DerToPem(kPemTypeCertificate, + CRYPTO_BUFFER_data(cert_buffer_.get()), + CRYPTO_BUFFER_len(cert_buffer_.get())); +} + +void BoringSSLCertificate::ToDER(Buffer* der_buffer) const { + der_buffer->SetData(CRYPTO_BUFFER_data(cert_buffer_.get()), + CRYPTO_BUFFER_len(cert_buffer_.get())); +} + +bool BoringSSLCertificate::operator==(const BoringSSLCertificate& other) const { + return CRYPTO_BUFFER_len(cert_buffer_.get()) == + CRYPTO_BUFFER_len(other.cert_buffer_.get()) && + 0 == memcmp(CRYPTO_BUFFER_data(cert_buffer_.get()), + CRYPTO_BUFFER_data(other.cert_buffer_.get()), + CRYPTO_BUFFER_len(cert_buffer_.get())); +} + +bool BoringSSLCertificate::operator!=(const BoringSSLCertificate& other) const { + return !(*this == other); +} + +int64_t BoringSSLCertificate::CertificateExpirationTime() const { + int64_t ret; + if (!openssl::ParseCertificate(cert_buffer_.get(), nullptr, &ret)) { + RTC_LOG(LS_ERROR) << "Failed to parse certificate."; + return -1; + } + return ret; +} + +} // namespace rtc diff --git a/rtc_base/boringssl_certificate.h b/rtc_base/boringssl_certificate.h new file mode 100644 index 0000000000..740763dc69 --- /dev/null +++ b/rtc_base/boringssl_certificate.h @@ -0,0 +1,80 @@ +/* + * Copyright 2020 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. + */ + +#ifndef RTC_BASE_BORINGSSL_CERTIFICATE_H_ +#define RTC_BASE_BORINGSSL_CERTIFICATE_H_ + +#include +#include +#include + +#include +#include + +#include "rtc_base/buffer.h" +#include "rtc_base/constructor_magic.h" +#include "rtc_base/ssl_certificate.h" +#include "rtc_base/ssl_identity.h" + +namespace rtc { + +class OpenSSLKeyPair; + +// BoringSSLCertificate encapsulates a BoringSSL CRYPTO_BUFFER object holding a +// certificate, which is also reference counted inside the BoringSSL library. +// This offers binary size and memory improvements over the OpenSSL X509 +// object. +class BoringSSLCertificate final : public SSLCertificate { + public: + explicit BoringSSLCertificate(bssl::UniquePtr cert_buffer); + + static std::unique_ptr Generate( + OpenSSLKeyPair* key_pair, + const SSLIdentityParams& params); + static std::unique_ptr FromPEMString( + const std::string& pem_string); + + ~BoringSSLCertificate() override; + + std::unique_ptr Clone() const override; + + CRYPTO_BUFFER* cert_buffer() const { return cert_buffer_.get(); } + + std::string ToPEMString() const override; + void ToDER(Buffer* der_buffer) const override; + bool operator==(const BoringSSLCertificate& other) const; + bool operator!=(const BoringSSLCertificate& other) const; + + // Compute the digest of the certificate given |algorithm|. + bool ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const override; + + // Compute the digest of a certificate as a CRYPTO_BUFFER. + static bool ComputeDigest(const CRYPTO_BUFFER* cert_buffer, + const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length); + + bool GetSignatureDigestAlgorithm(std::string* algorithm) const override; + + int64_t CertificateExpirationTime() const override; + + private: + // A handle to the DER encoded certificate data. + bssl::UniquePtr cert_buffer_; + RTC_DISALLOW_COPY_AND_ASSIGN(BoringSSLCertificate); +}; + +} // namespace rtc + +#endif // RTC_BASE_BORINGSSL_CERTIFICATE_H_ diff --git a/rtc_base/boringssl_identity.cc b/rtc_base/boringssl_identity.cc new file mode 100644 index 0000000000..d22c8ce529 --- /dev/null +++ b/rtc_base/boringssl_identity.cc @@ -0,0 +1,215 @@ +/* + * Copyright 2020 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/boringssl_identity.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "absl/memory/memory.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/openssl.h" +#include "rtc_base/openssl_utility.h" + +namespace rtc { + +BoringSSLIdentity::BoringSSLIdentity( + std::unique_ptr key_pair, + std::unique_ptr certificate) + : key_pair_(std::move(key_pair)) { + RTC_DCHECK(key_pair_ != nullptr); + RTC_DCHECK(certificate != nullptr); + std::vector> certs; + certs.push_back(std::move(certificate)); + cert_chain_.reset(new SSLCertChain(std::move(certs))); +} + +BoringSSLIdentity::BoringSSLIdentity(std::unique_ptr key_pair, + std::unique_ptr cert_chain) + : key_pair_(std::move(key_pair)), cert_chain_(std::move(cert_chain)) { + RTC_DCHECK(key_pair_ != nullptr); + RTC_DCHECK(cert_chain_ != nullptr); +} + +BoringSSLIdentity::~BoringSSLIdentity() = default; + +std::unique_ptr BoringSSLIdentity::CreateInternal( + const SSLIdentityParams& params) { + auto key_pair = OpenSSLKeyPair::Generate(params.key_params); + if (key_pair) { + std::unique_ptr certificate( + BoringSSLCertificate::Generate(key_pair.get(), params)); + if (certificate != nullptr) { + return absl::WrapUnique( + new BoringSSLIdentity(std::move(key_pair), std::move(certificate))); + } + } + RTC_LOG(LS_ERROR) << "Identity generation failed."; + return nullptr; +} + +// static +std::unique_ptr BoringSSLIdentity::CreateWithExpiration( + const std::string& common_name, + const KeyParams& key_params, + time_t certificate_lifetime) { + SSLIdentityParams params; + params.key_params = key_params; + params.common_name = common_name; + time_t now = time(nullptr); + params.not_before = now + kCertificateWindowInSeconds; + params.not_after = now + certificate_lifetime; + if (params.not_before > params.not_after) + return nullptr; + return CreateInternal(params); +} + +std::unique_ptr BoringSSLIdentity::CreateForTest( + const SSLIdentityParams& params) { + return CreateInternal(params); +} + +std::unique_ptr BoringSSLIdentity::CreateFromPEMStrings( + const std::string& private_key, + const std::string& certificate) { + std::unique_ptr cert( + BoringSSLCertificate::FromPEMString(certificate)); + if (!cert) { + RTC_LOG(LS_ERROR) + << "Failed to create BoringSSLCertificate from PEM string."; + return nullptr; + } + + auto key_pair = OpenSSLKeyPair::FromPrivateKeyPEMString(private_key); + if (!key_pair) { + RTC_LOG(LS_ERROR) << "Failed to create key pair from PEM string."; + return nullptr; + } + + return absl::WrapUnique( + new BoringSSLIdentity(std::move(key_pair), std::move(cert))); +} + +std::unique_ptr BoringSSLIdentity::CreateFromPEMChainStrings( + const std::string& private_key, + const std::string& certificate_chain) { + bssl::UniquePtr bio( + BIO_new_mem_buf(certificate_chain.data(), + rtc::dchecked_cast(certificate_chain.size()))); + if (!bio) { + return nullptr; + } + BIO_set_mem_eof_return(bio.get(), 0); + std::vector> certs; + while (true) { + char* name; + char* header; + unsigned char* data; + long len; // NOLINT + int ret = PEM_read_bio(bio.get(), &name, &header, &data, &len); + if (ret == 0) { + uint32_t err = ERR_peek_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PEM && + ERR_GET_REASON(err) == PEM_R_NO_START_LINE) { + break; + } + RTC_LOG(LS_ERROR) << "Failed to parse certificate from PEM string."; + return nullptr; + } + bssl::UniquePtr owned_name(name); + bssl::UniquePtr owned_header(header); + bssl::UniquePtr owned_data(data); + if (strcmp(owned_name.get(), PEM_STRING_X509) != 0) { + RTC_LOG(LS_ERROR) + << "Non-certificate found while parsing certificate chain: " + << owned_name.get(); + return nullptr; + } + bssl::UniquePtr crypto_buffer( + CRYPTO_BUFFER_new(data, len, openssl::GetBufferPool())); + if (!crypto_buffer) { + return nullptr; + } + certs.emplace_back(new BoringSSLCertificate(std::move(crypto_buffer))); + } + if (certs.empty()) { + RTC_LOG(LS_ERROR) << "Found no certificates in PEM string."; + return nullptr; + } + + auto key_pair = OpenSSLKeyPair::FromPrivateKeyPEMString(private_key); + if (!key_pair) { + RTC_LOG(LS_ERROR) << "Failed to create key pair from PEM string."; + return nullptr; + } + + return absl::WrapUnique(new BoringSSLIdentity( + std::move(key_pair), std::make_unique(std::move(certs)))); +} + +const BoringSSLCertificate& BoringSSLIdentity::certificate() const { + return *static_cast(&cert_chain_->Get(0)); +} + +const SSLCertChain& BoringSSLIdentity::cert_chain() const { + return *cert_chain_.get(); +} + +std::unique_ptr BoringSSLIdentity::CloneInternal() const { + // We cannot use std::make_unique here because the referenced + // BoringSSLIdentity constructor is private. + return absl::WrapUnique( + new BoringSSLIdentity(key_pair_->Clone(), cert_chain_->Clone())); +} + +bool BoringSSLIdentity::ConfigureIdentity(SSL_CTX* ctx) { + std::vector cert_buffers; + for (size_t i = 0; i < cert_chain_->GetSize(); ++i) { + cert_buffers.push_back( + static_cast(&cert_chain_->Get(i)) + ->cert_buffer()); + } + // 1 is the documented success return code. + if (1 != SSL_CTX_set_chain_and_key(ctx, &cert_buffers[0], cert_buffers.size(), + key_pair_->pkey(), nullptr)) { + openssl::LogSSLErrors("Configuring key and certificate"); + return false; + } + return true; +} + +std::string BoringSSLIdentity::PrivateKeyToPEMString() const { + return key_pair_->PrivateKeyToPEMString(); +} + +std::string BoringSSLIdentity::PublicKeyToPEMString() const { + return key_pair_->PublicKeyToPEMString(); +} + +bool BoringSSLIdentity::operator==(const BoringSSLIdentity& other) const { + return *this->key_pair_ == *other.key_pair_ && + this->certificate() == other.certificate(); +} + +bool BoringSSLIdentity::operator!=(const BoringSSLIdentity& other) const { + return !(*this == other); +} + +} // namespace rtc diff --git a/rtc_base/boringssl_identity.h b/rtc_base/boringssl_identity.h new file mode 100644 index 0000000000..71b29b486d --- /dev/null +++ b/rtc_base/boringssl_identity.h @@ -0,0 +1,76 @@ +/* + * Copyright 2020 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. + */ + +#ifndef RTC_BASE_BORINGSSL_IDENTITY_H_ +#define RTC_BASE_BORINGSSL_IDENTITY_H_ + +#include + +#include +#include +#include + +#include "rtc_base/boringssl_certificate.h" +#include "rtc_base/constructor_magic.h" +#include "rtc_base/openssl_key_pair.h" +#include "rtc_base/ssl_certificate.h" +#include "rtc_base/ssl_identity.h" + +namespace rtc { + +// Holds a keypair and certificate together, and a method to generate them +// consistently. Uses CRYPTO_BUFFER instead of X509, which offers binary size +// and memory improvements. +class BoringSSLIdentity final : public SSLIdentity { + public: + static std::unique_ptr CreateWithExpiration( + const std::string& common_name, + const KeyParams& key_params, + time_t certificate_lifetime); + static std::unique_ptr CreateForTest( + const SSLIdentityParams& params); + static std::unique_ptr CreateFromPEMStrings( + const std::string& private_key, + const std::string& certificate); + static std::unique_ptr CreateFromPEMChainStrings( + const std::string& private_key, + const std::string& certificate_chain); + ~BoringSSLIdentity() override; + + const BoringSSLCertificate& certificate() const override; + const SSLCertChain& cert_chain() const override; + + // Configure an SSL context object to use our key and certificate. + bool ConfigureIdentity(SSL_CTX* ctx); + + std::string PrivateKeyToPEMString() const override; + std::string PublicKeyToPEMString() const override; + bool operator==(const BoringSSLIdentity& other) const; + bool operator!=(const BoringSSLIdentity& other) const; + + private: + BoringSSLIdentity(std::unique_ptr key_pair, + std::unique_ptr certificate); + BoringSSLIdentity(std::unique_ptr key_pair, + std::unique_ptr cert_chain); + std::unique_ptr CloneInternal() const override; + + static std::unique_ptr CreateInternal( + const SSLIdentityParams& params); + + std::unique_ptr key_pair_; + std::unique_ptr cert_chain_; + + RTC_DISALLOW_COPY_AND_ASSIGN(BoringSSLIdentity); +}; + +} // namespace rtc + +#endif // RTC_BASE_BORINGSSL_IDENTITY_H_ diff --git a/rtc_base/openssl_adapter.cc b/rtc_base/openssl_adapter.cc index 8fd882c2b3..e5c2c42761 100644 --- a/rtc_base/openssl_adapter.cc +++ b/rtc_base/openssl_adapter.cc @@ -13,6 +13,9 @@ #include #include #include +#ifdef OPENSSL_IS_BORINGSSL +#include +#endif #include #include #include @@ -20,13 +23,24 @@ #include +// Use CRYPTO_BUFFER APIs if available and we have no dependency on X509 +// objects. +#if defined(OPENSSL_IS_BORINGSSL) && \ + defined(WEBRTC_EXCLUDE_BUILT_IN_SSL_ROOT_CERTS) +#define WEBRTC_USE_CRYPTO_BUFFER_CALLBACK +#endif + #include "absl/memory/memory.h" #include "rtc_base/checks.h" #include "rtc_base/location.h" #include "rtc_base/logging.h" #include "rtc_base/numerics/safe_conversions.h" #include "rtc_base/openssl.h" -#include "rtc_base/openssl_certificate.h" +#ifdef OPENSSL_IS_BORINGSSL +#include "rtc_base/boringssl_identity.h" +#else +#include "rtc_base/openssl_identity.h" +#endif #include "rtc_base/openssl_utility.h" #include "rtc_base/string_encode.h" #include "rtc_base/thread.h" @@ -223,8 +237,13 @@ void OpenSSLAdapter::SetCertVerifier( void OpenSSLAdapter::SetIdentity(std::unique_ptr identity) { RTC_DCHECK(!identity_); +#ifdef OPENSSL_IS_BORINGSSL + identity_ = + absl::WrapUnique(static_cast(identity.release())); +#else identity_ = absl::WrapUnique(static_cast(identity.release())); +#endif } void OpenSSLAdapter::SetRole(SSLRole role) { @@ -797,7 +816,70 @@ void OpenSSLAdapter::SSLInfoCallback(const SSL* s, int where, int ret) { #endif +#ifdef WEBRTC_USE_CRYPTO_BUFFER_CALLBACK +// static +enum ssl_verify_result_t OpenSSLAdapter::SSLVerifyCallback(SSL* ssl, + uint8_t* out_alert) { + // Get our stream pointer from the SSL context. + OpenSSLAdapter* stream = + reinterpret_cast(SSL_get_app_data(ssl)); + + ssl_verify_result_t ret = stream->SSLVerifyInternal(ssl, out_alert); + + // Should only be used for debugging and development. + if (ret != ssl_verify_ok && stream->ignore_bad_cert_) { + RTC_DLOG(LS_WARNING) << "Ignoring cert error while verifying cert chain"; + return ssl_verify_ok; + } + + return ret; +} + +enum ssl_verify_result_t OpenSSLAdapter::SSLVerifyInternal(SSL* ssl, + uint8_t* out_alert) { + if (ssl_cert_verifier_ == nullptr) { + RTC_LOG(LS_WARNING) << "Built-in trusted root certificates disabled but no " + "SSL verify callback provided."; + return ssl_verify_invalid; + } + + RTC_LOG(LS_INFO) << "Invoking SSL Verify Callback."; + const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl); + if (sk_CRYPTO_BUFFER_num(chain) == 0) { + RTC_LOG(LS_ERROR) << "Peer certificate chain empty?"; + return ssl_verify_invalid; + } + + BoringSSLCertificate cert(bssl::UpRef(sk_CRYPTO_BUFFER_value(chain, 0))); + if (!ssl_cert_verifier_->Verify(cert)) { + RTC_LOG(LS_WARNING) << "Failed to verify certificate using custom callback"; + return ssl_verify_invalid; + } + + custom_cert_verifier_status_ = true; + RTC_LOG(LS_INFO) << "Validated certificate using custom callback"; + return ssl_verify_ok; +} +#else // WEBRTC_USE_CRYPTO_BUFFER_CALLBACK int OpenSSLAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) { + // Get our stream pointer from the store + SSL* ssl = reinterpret_cast( + X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx())); + + OpenSSLAdapter* stream = + reinterpret_cast(SSL_get_app_data(ssl)); + ok = stream->SSLVerifyInternal(ok, ssl, store); + + // Should only be used for debugging and development. + if (!ok && stream->ignore_bad_cert_) { + RTC_DLOG(LS_WARNING) << "Ignoring cert error while verifying cert chain"; + return 1; + } + + return ok; +} + +int OpenSSLAdapter::SSLVerifyInternal(int ok, SSL* ssl, X509_STORE_CTX* store) { #if !defined(NDEBUG) if (!ok) { char data[256]; @@ -814,33 +896,40 @@ int OpenSSLAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) { << X509_verify_cert_error_string(err); } #endif - // Get our stream pointer from the store - SSL* ssl = reinterpret_cast( - X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx())); - - OpenSSLAdapter* stream = - reinterpret_cast(SSL_get_app_data(ssl)); - - if (!ok && stream->ssl_cert_verifier_ != nullptr) { - RTC_LOG(LS_INFO) << "Invoking SSL Verify Callback."; - const OpenSSLCertificate cert(X509_STORE_CTX_get_current_cert(store)); - if (stream->ssl_cert_verifier_->Verify(cert)) { - stream->custom_cert_verifier_status_ = true; - RTC_LOG(LS_INFO) << "Validated certificate using custom callback"; - ok = true; - } else { - RTC_LOG(LS_INFO) << "Failed to verify certificate using custom callback"; - } + if (ssl_cert_verifier_ == nullptr) { + return ok; } - // Should only be used for debugging and development. - if (!ok && stream->ignore_bad_cert_) { - RTC_DLOG(LS_WARNING) << "Ignoring cert error while verifying cert chain"; - ok = 1; + RTC_LOG(LS_INFO) << "Invoking SSL Verify Callback."; +#ifdef OPENSSL_IS_BORINGSSL + // Convert X509 to CRYPTO_BUFFER. + uint8_t* data = nullptr; + int length = i2d_X509(X509_STORE_CTX_get_current_cert(store), &data); + if (length < 0) { + RTC_LOG(LS_ERROR) << "Failed to encode X509."; + return ok; + } + bssl::UniquePtr owned_data(data); + bssl::UniquePtr crypto_buffer( + CRYPTO_BUFFER_new(data, length, openssl::GetBufferPool())); + if (!crypto_buffer) { + RTC_LOG(LS_ERROR) << "Failed to allocate CRYPTO_BUFFER."; + return ok; + } + const BoringSSLCertificate cert(std::move(crypto_buffer)); +#else + const OpenSSLCertificate cert(X509_STORE_CTX_get_current_cert(store)); +#endif + if (!ssl_cert_verifier_->Verify(cert)) { + RTC_LOG(LS_INFO) << "Failed to verify certificate using custom callback"; + return ok; } - return ok; + custom_cert_verifier_status_ = true; + RTC_LOG(LS_INFO) << "Validated certificate using custom callback"; + return 1; } +#endif // !defined(WEBRTC_USE_CRYPTO_BUFFER_CALLBACK) int OpenSSLAdapter::NewSSLSessionCallback(SSL* ssl, SSL_SESSION* session) { OpenSSLAdapter* stream = @@ -852,8 +941,15 @@ int OpenSSLAdapter::NewSSLSessionCallback(SSL* ssl, SSL_SESSION* session) { } SSL_CTX* OpenSSLAdapter::CreateContext(SSLMode mode, bool enable_cache) { +#ifdef WEBRTC_USE_CRYPTO_BUFFER_CALLBACK + // If X509 objects aren't used, we can use these methods to avoid + // linking the sizable crypto/x509 code. + SSL_CTX* ctx = SSL_CTX_new(mode == SSL_MODE_DTLS ? DTLS_with_buffers_method() + : TLS_with_buffers_method()); +#else SSL_CTX* ctx = SSL_CTX_new(mode == SSL_MODE_DTLS ? DTLS_method() : TLS_method()); +#endif if (ctx == nullptr) { unsigned long error = ERR_get_error(); // NOLINT: type used by OpenSSL. RTC_LOG(LS_WARNING) << "SSL_CTX creation failed: " << '"' @@ -877,8 +973,16 @@ SSL_CTX* OpenSSLAdapter::CreateContext(SSLMode mode, bool enable_cache) { SSL_CTX_set_info_callback(ctx, SSLInfoCallback); #endif +#ifdef OPENSSL_IS_BORINGSSL + SSL_CTX_set0_buffer_pool(ctx, openssl::GetBufferPool()); +#endif + +#ifdef WEBRTC_USE_CRYPTO_BUFFER_CALLBACK + SSL_CTX_set_custom_verify(ctx, SSL_VERIFY_PEER, SSLVerifyCallback); +#else SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLVerifyCallback); SSL_CTX_set_verify_depth(ctx, 4); +#endif // Use defaults, but disable HMAC-SHA256 and HMAC-SHA384 ciphers // (note that SHA256 and SHA384 only select legacy CBC ciphers). // Additionally disable HMAC-SHA1 ciphers in ECDSA. These are the remaining diff --git a/rtc_base/openssl_adapter.h b/rtc_base/openssl_adapter.h index 6f1f7dccab..76b003a7dd 100644 --- a/rtc_base/openssl_adapter.h +++ b/rtc_base/openssl_adapter.h @@ -11,6 +11,7 @@ #ifndef RTC_BASE_OPENSSL_ADAPTER_H_ #define RTC_BASE_OPENSSL_ADAPTER_H_ +#include #include #include @@ -21,7 +22,11 @@ #include "rtc_base/async_socket.h" #include "rtc_base/buffer.h" #include "rtc_base/message_handler.h" +#ifdef OPENSSL_IS_BORINGSSL +#include "rtc_base/boringssl_identity.h" +#else #include "rtc_base/openssl_identity.h" +#endif #include "rtc_base/openssl_session_cache.h" #include "rtc_base/socket.h" #include "rtc_base/socket_address.h" @@ -109,7 +114,16 @@ class OpenSSLAdapter final : public SSLAdapter, // In debug builds, logs info about the state of the SSL connection. static void SSLInfoCallback(const SSL* ssl, int where, int ret); #endif + +#if defined(OPENSSL_IS_BORINGSSL) && \ + defined(WEBRTC_EXCLUDE_BUILT_IN_SSL_ROOT_CERTS) + static enum ssl_verify_result_t SSLVerifyCallback(SSL* ssl, + uint8_t* out_alert); + enum ssl_verify_result_t SSLVerifyInternal(SSL* ssl, uint8_t* out_alert); +#else static int SSLVerifyCallback(int ok, X509_STORE_CTX* store); + int SSLVerifyInternal(int ok, SSL* ssl, X509_STORE_CTX* store); +#endif friend class OpenSSLStreamAdapter; // for custom_verify_callback_; // If the SSL_CTX was created with |enable_cache| set to true, this callback @@ -123,7 +137,12 @@ class OpenSSLAdapter final : public SSLAdapter, SSLCertificateVerifier* ssl_cert_verifier_ = nullptr; // The current connection state of the (d)TLS connection. SSLState state_; + +#ifdef OPENSSL_IS_BORINGSSL + std::unique_ptr identity_; +#else std::unique_ptr identity_; +#endif // Indicates whethere this is a client or a server. SSLRole role_; bool ssl_read_needs_write_; diff --git a/rtc_base/openssl_identity.cc b/rtc_base/openssl_identity.cc index c94df40bfb..3794d981ce 100644 --- a/rtc_base/openssl_identity.cc +++ b/rtc_base/openssl_identity.cc @@ -20,10 +20,8 @@ #endif // WEBRTC_WIN #include -#include #include #include -#include #include #include "absl/memory/memory.h" @@ -35,160 +33,6 @@ namespace rtc { -// We could have exposed a myriad of parameters for the crypto stuff, -// but keeping it simple seems best. - -// Generate a key pair. Caller is responsible for freeing the returned object. -static EVP_PKEY* MakeKey(const KeyParams& key_params) { - RTC_LOG(LS_INFO) << "Making key pair"; - EVP_PKEY* pkey = EVP_PKEY_new(); - if (key_params.type() == KT_RSA) { - int key_length = key_params.rsa_params().mod_size; - BIGNUM* exponent = BN_new(); - RSA* rsa = RSA_new(); - if (!pkey || !exponent || !rsa || - !BN_set_word(exponent, key_params.rsa_params().pub_exp) || - !RSA_generate_key_ex(rsa, key_length, exponent, nullptr) || - !EVP_PKEY_assign_RSA(pkey, rsa)) { - EVP_PKEY_free(pkey); - BN_free(exponent); - RSA_free(rsa); - RTC_LOG(LS_ERROR) << "Failed to make RSA key pair"; - return nullptr; - } - // ownership of rsa struct was assigned, don't free it. - BN_free(exponent); - } else if (key_params.type() == KT_ECDSA) { - if (key_params.ec_curve() == EC_NIST_P256) { - EC_KEY* ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - - // Ensure curve name is included when EC key is serialized. - // Without this call, OpenSSL versions before 1.1.0 will create - // certificates that don't work for TLS. - // This is a no-op for BoringSSL and OpenSSL 1.1.0+ - EC_KEY_set_asn1_flag(ec_key, OPENSSL_EC_NAMED_CURVE); - - if (!pkey || !ec_key || !EC_KEY_generate_key(ec_key) || - !EVP_PKEY_assign_EC_KEY(pkey, ec_key)) { - EVP_PKEY_free(pkey); - EC_KEY_free(ec_key); - RTC_LOG(LS_ERROR) << "Failed to make EC key pair"; - return nullptr; - } - // ownership of ec_key struct was assigned, don't free it. - } else { - // Add generation of any other curves here. - EVP_PKEY_free(pkey); - RTC_LOG(LS_ERROR) << "ECDSA key requested for unknown curve"; - return nullptr; - } - } else { - EVP_PKEY_free(pkey); - RTC_LOG(LS_ERROR) << "Key type requested not understood"; - return nullptr; - } - - RTC_LOG(LS_INFO) << "Returning key pair"; - return pkey; -} - -OpenSSLKeyPair* OpenSSLKeyPair::Generate(const KeyParams& key_params) { - EVP_PKEY* pkey = MakeKey(key_params); - if (!pkey) { - openssl::LogSSLErrors("Generating key pair"); - return nullptr; - } - return new OpenSSLKeyPair(pkey); -} - -OpenSSLKeyPair* OpenSSLKeyPair::FromPrivateKeyPEMString( - const std::string& pem_string) { - BIO* bio = BIO_new_mem_buf(const_cast(pem_string.c_str()), -1); - if (!bio) { - RTC_LOG(LS_ERROR) << "Failed to create a new BIO buffer."; - return nullptr; - } - BIO_set_mem_eof_return(bio, 0); - EVP_PKEY* pkey = - PEM_read_bio_PrivateKey(bio, nullptr, nullptr, const_cast("\0")); - BIO_free(bio); // Frees the BIO, but not the pointed-to string. - if (!pkey) { - RTC_LOG(LS_ERROR) << "Failed to create the private key from PEM string."; - return nullptr; - } - if (EVP_PKEY_missing_parameters(pkey) != 0) { - RTC_LOG(LS_ERROR) - << "The resulting key pair is missing public key parameters."; - EVP_PKEY_free(pkey); - return nullptr; - } - return new OpenSSLKeyPair(pkey); -} - -OpenSSLKeyPair::~OpenSSLKeyPair() { - EVP_PKEY_free(pkey_); -} - -OpenSSLKeyPair* OpenSSLKeyPair::GetReference() { - AddReference(); - return new OpenSSLKeyPair(pkey_); -} - -void OpenSSLKeyPair::AddReference() { - EVP_PKEY_up_ref(pkey_); -} - -std::string OpenSSLKeyPair::PrivateKeyToPEMString() const { - BIO* temp_memory_bio = BIO_new(BIO_s_mem()); - if (!temp_memory_bio) { - RTC_LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio"; - RTC_NOTREACHED(); - return ""; - } - if (!PEM_write_bio_PrivateKey(temp_memory_bio, pkey_, nullptr, nullptr, 0, - nullptr, nullptr)) { - RTC_LOG_F(LS_ERROR) << "Failed to write private key"; - BIO_free(temp_memory_bio); - RTC_NOTREACHED(); - return ""; - } - BIO_write(temp_memory_bio, "\0", 1); - char* buffer; - BIO_get_mem_data(temp_memory_bio, &buffer); - std::string priv_key_str = buffer; - BIO_free(temp_memory_bio); - return priv_key_str; -} - -std::string OpenSSLKeyPair::PublicKeyToPEMString() const { - BIO* temp_memory_bio = BIO_new(BIO_s_mem()); - if (!temp_memory_bio) { - RTC_LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio"; - RTC_NOTREACHED(); - return ""; - } - if (!PEM_write_bio_PUBKEY(temp_memory_bio, pkey_)) { - RTC_LOG_F(LS_ERROR) << "Failed to write public key"; - BIO_free(temp_memory_bio); - RTC_NOTREACHED(); - return ""; - } - BIO_write(temp_memory_bio, "\0", 1); - char* buffer; - BIO_get_mem_data(temp_memory_bio, &buffer); - std::string pub_key_str = buffer; - BIO_free(temp_memory_bio); - return pub_key_str; -} - -bool OpenSSLKeyPair::operator==(const OpenSSLKeyPair& other) const { - return EVP_PKEY_cmp(this->pkey_, other.pkey_) == 1; -} - -bool OpenSSLKeyPair::operator!=(const OpenSSLKeyPair& other) const { - return !(*this == other); -} - OpenSSLIdentity::OpenSSLIdentity( std::unique_ptr key_pair, std::unique_ptr certificate) @@ -211,8 +55,7 @@ OpenSSLIdentity::~OpenSSLIdentity() = default; std::unique_ptr OpenSSLIdentity::CreateInternal( const SSLIdentityParams& params) { - std::unique_ptr key_pair( - OpenSSLKeyPair::Generate(params.key_params)); + auto key_pair = OpenSSLKeyPair::Generate(params.key_params); if (key_pair) { std::unique_ptr certificate( OpenSSLCertificate::Generate(key_pair.get(), params)); @@ -221,7 +64,7 @@ std::unique_ptr OpenSSLIdentity::CreateInternal( new OpenSSLIdentity(std::move(key_pair), std::move(certificate))); } } - RTC_LOG(LS_INFO) << "Identity generation failed"; + RTC_LOG(LS_ERROR) << "Identity generation failed"; return nullptr; } @@ -256,8 +99,7 @@ std::unique_ptr OpenSSLIdentity::CreateFromPEMStrings( return nullptr; } - std::unique_ptr key_pair( - OpenSSLKeyPair::FromPrivateKeyPEMString(private_key)); + auto key_pair = OpenSSLKeyPair::FromPrivateKeyPEMString(private_key); if (!key_pair) { RTC_LOG(LS_ERROR) << "Failed to create key pair from PEM string."; return nullptr; @@ -298,8 +140,7 @@ std::unique_ptr OpenSSLIdentity::CreateFromPEMChainStrings( return nullptr; } - std::unique_ptr key_pair( - OpenSSLKeyPair::FromPrivateKeyPEMString(private_key)); + auto key_pair = OpenSSLKeyPair::FromPrivateKeyPEMString(private_key); if (!key_pair) { RTC_LOG(LS_ERROR) << "Failed to create key pair from PEM string."; return nullptr; @@ -320,8 +161,8 @@ const SSLCertChain& OpenSSLIdentity::cert_chain() const { std::unique_ptr OpenSSLIdentity::CloneInternal() const { // We cannot use std::make_unique here because the referenced OpenSSLIdentity // constructor is private. - return absl::WrapUnique(new OpenSSLIdentity( - absl::WrapUnique(key_pair_->GetReference()), cert_chain_->Clone())); + return absl::WrapUnique( + new OpenSSLIdentity(key_pair_->Clone(), cert_chain_->Clone())); } bool OpenSSLIdentity::ConfigureIdentity(SSL_CTX* ctx) { diff --git a/rtc_base/openssl_identity.h b/rtc_base/openssl_identity.h index a2ac87cf45..00d6c74922 100644 --- a/rtc_base/openssl_identity.h +++ b/rtc_base/openssl_identity.h @@ -17,45 +17,14 @@ #include #include -#include "rtc_base/checks.h" #include "rtc_base/constructor_magic.h" #include "rtc_base/openssl_certificate.h" +#include "rtc_base/openssl_key_pair.h" #include "rtc_base/ssl_certificate.h" #include "rtc_base/ssl_identity.h" namespace rtc { -// OpenSSLKeyPair encapsulates an OpenSSL EVP_PKEY* keypair object, -// which is reference counted inside the OpenSSL library. -class OpenSSLKeyPair final { - public: - explicit OpenSSLKeyPair(EVP_PKEY* pkey) : pkey_(pkey) { - RTC_DCHECK(pkey_ != nullptr); - } - - static OpenSSLKeyPair* Generate(const KeyParams& key_params); - // Constructs a key pair from the private key PEM string. This must not result - // in missing public key parameters. Returns null on error. - static OpenSSLKeyPair* FromPrivateKeyPEMString(const std::string& pem_string); - - virtual ~OpenSSLKeyPair(); - - virtual OpenSSLKeyPair* GetReference(); - - EVP_PKEY* pkey() const { return pkey_; } - std::string PrivateKeyToPEMString() const; - std::string PublicKeyToPEMString() const; - bool operator==(const OpenSSLKeyPair& other) const; - bool operator!=(const OpenSSLKeyPair& other) const; - - private: - void AddReference(); - - EVP_PKEY* pkey_; - - RTC_DISALLOW_COPY_AND_ASSIGN(OpenSSLKeyPair); -}; - // Holds a keypair and certificate together, and a method to generate // them consistently. class OpenSSLIdentity final : public SSLIdentity { diff --git a/rtc_base/openssl_key_pair.cc b/rtc_base/openssl_key_pair.cc new file mode 100644 index 0000000000..911a751cbe --- /dev/null +++ b/rtc_base/openssl_key_pair.cc @@ -0,0 +1,192 @@ +/* + * 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_key_pair.h" + +#include +#include + +#if defined(WEBRTC_WIN) +// Must be included first before openssl headers. +#include "rtc_base/win32.h" // NOLINT +#endif // WEBRTC_WIN + +#include +#include +#include +#include + +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/openssl.h" +#include "rtc_base/openssl_utility.h" + +namespace rtc { + +// We could have exposed a myriad of parameters for the crypto stuff, +// but keeping it simple seems best. + +// Generate a key pair. Caller is responsible for freeing the returned object. +static EVP_PKEY* MakeKey(const KeyParams& key_params) { + RTC_LOG(LS_INFO) << "Making key pair"; + EVP_PKEY* pkey = EVP_PKEY_new(); + if (key_params.type() == KT_RSA) { + int key_length = key_params.rsa_params().mod_size; + BIGNUM* exponent = BN_new(); + RSA* rsa = RSA_new(); + if (!pkey || !exponent || !rsa || + !BN_set_word(exponent, key_params.rsa_params().pub_exp) || + !RSA_generate_key_ex(rsa, key_length, exponent, nullptr) || + !EVP_PKEY_assign_RSA(pkey, rsa)) { + EVP_PKEY_free(pkey); + BN_free(exponent); + RSA_free(rsa); + RTC_LOG(LS_ERROR) << "Failed to make RSA key pair"; + return nullptr; + } + // ownership of rsa struct was assigned, don't free it. + BN_free(exponent); + } else if (key_params.type() == KT_ECDSA) { + if (key_params.ec_curve() == EC_NIST_P256) { + EC_KEY* ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (!ec_key) { + EVP_PKEY_free(pkey); + RTC_LOG(LS_ERROR) << "Failed to allocate EC key"; + return nullptr; + } + + // Ensure curve name is included when EC key is serialized. + // Without this call, OpenSSL versions before 1.1.0 will create + // certificates that don't work for TLS. + // This is a no-op for BoringSSL and OpenSSL 1.1.0+ + EC_KEY_set_asn1_flag(ec_key, OPENSSL_EC_NAMED_CURVE); + + if (!pkey || !ec_key || !EC_KEY_generate_key(ec_key) || + !EVP_PKEY_assign_EC_KEY(pkey, ec_key)) { + EVP_PKEY_free(pkey); + EC_KEY_free(ec_key); + RTC_LOG(LS_ERROR) << "Failed to make EC key pair"; + return nullptr; + } + // ownership of ec_key struct was assigned, don't free it. + } else { + // Add generation of any other curves here. + EVP_PKEY_free(pkey); + RTC_LOG(LS_ERROR) << "ECDSA key requested for unknown curve"; + return nullptr; + } + } else { + EVP_PKEY_free(pkey); + RTC_LOG(LS_ERROR) << "Key type requested not understood"; + return nullptr; + } + + RTC_LOG(LS_INFO) << "Returning key pair"; + return pkey; +} + +std::unique_ptr OpenSSLKeyPair::Generate( + const KeyParams& key_params) { + EVP_PKEY* pkey = MakeKey(key_params); + if (!pkey) { + openssl::LogSSLErrors("Generating key pair"); + return nullptr; + } + return std::make_unique(pkey); +} + +std::unique_ptr OpenSSLKeyPair::FromPrivateKeyPEMString( + const std::string& pem_string) { + BIO* bio = + BIO_new_mem_buf(const_cast(pem_string.data()), pem_string.size()); + if (!bio) { + RTC_LOG(LS_ERROR) << "Failed to create a new BIO buffer."; + return nullptr; + } + BIO_set_mem_eof_return(bio, 0); + EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); + BIO_free(bio); // Frees the BIO, but not the pointed-to string. + if (!pkey) { + RTC_LOG(LS_ERROR) << "Failed to create the private key from PEM string."; + return nullptr; + } + if (EVP_PKEY_missing_parameters(pkey) != 0) { + RTC_LOG(LS_ERROR) + << "The resulting key pair is missing public key parameters."; + EVP_PKEY_free(pkey); + return nullptr; + } + return std::make_unique(pkey); +} + +OpenSSLKeyPair::~OpenSSLKeyPair() { + EVP_PKEY_free(pkey_); +} + +std::unique_ptr OpenSSLKeyPair::Clone() { + AddReference(); + return std::make_unique(pkey_); +} + +void OpenSSLKeyPair::AddReference() { + EVP_PKEY_up_ref(pkey_); +} + +std::string OpenSSLKeyPair::PrivateKeyToPEMString() const { + BIO* temp_memory_bio = BIO_new(BIO_s_mem()); + if (!temp_memory_bio) { + RTC_LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio"; + RTC_NOTREACHED(); + return ""; + } + if (!PEM_write_bio_PrivateKey(temp_memory_bio, pkey_, nullptr, nullptr, 0, + nullptr, nullptr)) { + RTC_LOG_F(LS_ERROR) << "Failed to write private key"; + BIO_free(temp_memory_bio); + RTC_NOTREACHED(); + return ""; + } + char* buffer; + size_t len = BIO_get_mem_data(temp_memory_bio, &buffer); + std::string priv_key_str(buffer, len); + BIO_free(temp_memory_bio); + return priv_key_str; +} + +std::string OpenSSLKeyPair::PublicKeyToPEMString() const { + BIO* temp_memory_bio = BIO_new(BIO_s_mem()); + if (!temp_memory_bio) { + RTC_LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio"; + RTC_NOTREACHED(); + return ""; + } + if (!PEM_write_bio_PUBKEY(temp_memory_bio, pkey_)) { + RTC_LOG_F(LS_ERROR) << "Failed to write public key"; + BIO_free(temp_memory_bio); + RTC_NOTREACHED(); + return ""; + } + BIO_write(temp_memory_bio, "\0", 1); + char* buffer; + BIO_get_mem_data(temp_memory_bio, &buffer); + std::string pub_key_str = buffer; + BIO_free(temp_memory_bio); + return pub_key_str; +} + +bool OpenSSLKeyPair::operator==(const OpenSSLKeyPair& other) const { + return EVP_PKEY_cmp(this->pkey_, other.pkey_) == 1; +} + +bool OpenSSLKeyPair::operator!=(const OpenSSLKeyPair& other) const { + return !(*this == other); +} + +} // namespace rtc diff --git a/rtc_base/openssl_key_pair.h b/rtc_base/openssl_key_pair.h new file mode 100644 index 0000000000..a84c43b6bd --- /dev/null +++ b/rtc_base/openssl_key_pair.h @@ -0,0 +1,60 @@ +/* + * Copyright 2020 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. + */ + +#ifndef RTC_BASE_OPENSSL_KEY_PAIR_H_ +#define RTC_BASE_OPENSSL_KEY_PAIR_H_ + +#include + +#include +#include + +#include "rtc_base/checks.h" +#include "rtc_base/constructor_magic.h" +#include "rtc_base/ssl_identity.h" + +namespace rtc { + +// OpenSSLKeyPair encapsulates an OpenSSL EVP_PKEY* keypair object, +// which is reference counted inside the OpenSSL library. +class OpenSSLKeyPair final { + public: + // Takes ownership of the key. + explicit OpenSSLKeyPair(EVP_PKEY* pkey) : pkey_(pkey) { + RTC_DCHECK(pkey_ != nullptr); + } + + static std::unique_ptr Generate(const KeyParams& key_params); + // Constructs a key pair from the private key PEM string. This must not result + // in missing public key parameters. Returns null on error. + static std::unique_ptr FromPrivateKeyPEMString( + const std::string& pem_string); + + ~OpenSSLKeyPair(); + + std::unique_ptr Clone(); + + EVP_PKEY* pkey() const { return pkey_; } + std::string PrivateKeyToPEMString() const; + std::string PublicKeyToPEMString() const; + bool operator==(const OpenSSLKeyPair& other) const; + bool operator!=(const OpenSSLKeyPair& other) const; + + private: + void AddReference(); + + EVP_PKEY* pkey_; + + RTC_DISALLOW_COPY_AND_ASSIGN(OpenSSLKeyPair); +}; + +} // namespace rtc + +#endif // RTC_BASE_OPENSSL_KEY_PAIR_H_ diff --git a/rtc_base/openssl_session_cache_unittest.cc b/rtc_base/openssl_session_cache_unittest.cc index 1d3084bbc5..0441d5c012 100644 --- a/rtc_base/openssl_session_cache_unittest.cc +++ b/rtc_base/openssl_session_cache_unittest.cc @@ -19,10 +19,28 @@ #include "rtc_base/gunit.h" #include "rtc_base/openssl.h" +namespace { +// Use methods that avoid X509 objects if possible. +SSL_CTX* NewDtlsContext() { +#ifdef OPENSSL_IS_BORINGSSL + return SSL_CTX_new(DTLS_with_buffers_method()); +#else + return SSL_CTX_new(DTLS_method()); +#endif +} +SSL_CTX* NewTlsContext() { +#ifdef OPENSSL_IS_BORINGSSL + return SSL_CTX_new(TLS_with_buffers_method()); +#else + return SSL_CTX_new(TLS_method()); +#endif +} +} // namespace + namespace rtc { TEST(OpenSSLSessionCache, DTLSModeSetCorrectly) { - SSL_CTX* ssl_ctx = SSL_CTX_new(DTLSv1_2_client_method()); + SSL_CTX* ssl_ctx = NewDtlsContext(); OpenSSLSessionCache session_cache(SSL_MODE_DTLS, ssl_ctx); EXPECT_EQ(session_cache.GetSSLMode(), SSL_MODE_DTLS); @@ -31,7 +49,7 @@ TEST(OpenSSLSessionCache, DTLSModeSetCorrectly) { } TEST(OpenSSLSessionCache, TLSModeSetCorrectly) { - SSL_CTX* ssl_ctx = SSL_CTX_new(TLSv1_2_client_method()); + SSL_CTX* ssl_ctx = NewTlsContext(); OpenSSLSessionCache session_cache(SSL_MODE_TLS, ssl_ctx); EXPECT_EQ(session_cache.GetSSLMode(), SSL_MODE_TLS); @@ -40,7 +58,7 @@ TEST(OpenSSLSessionCache, TLSModeSetCorrectly) { } TEST(OpenSSLSessionCache, SSLContextSetCorrectly) { - SSL_CTX* ssl_ctx = SSL_CTX_new(DTLSv1_2_client_method()); + SSL_CTX* ssl_ctx = NewDtlsContext(); OpenSSLSessionCache session_cache(SSL_MODE_DTLS, ssl_ctx); EXPECT_EQ(session_cache.GetSSLContext(), ssl_ctx); @@ -49,7 +67,7 @@ TEST(OpenSSLSessionCache, SSLContextSetCorrectly) { } TEST(OpenSSLSessionCache, InvalidLookupReturnsNullptr) { - SSL_CTX* ssl_ctx = SSL_CTX_new(DTLSv1_2_client_method()); + SSL_CTX* ssl_ctx = NewDtlsContext(); OpenSSLSessionCache session_cache(SSL_MODE_DTLS, ssl_ctx); EXPECT_EQ(session_cache.LookupSession("Invalid"), nullptr); @@ -60,7 +78,7 @@ TEST(OpenSSLSessionCache, InvalidLookupReturnsNullptr) { } TEST(OpenSSLSessionCache, SimpleValidSessionLookup) { - SSL_CTX* ssl_ctx = SSL_CTX_new(DTLSv1_2_client_method()); + SSL_CTX* ssl_ctx = NewDtlsContext(); SSL_SESSION* ssl_session = SSL_SESSION_new(ssl_ctx); OpenSSLSessionCache session_cache(SSL_MODE_DTLS, ssl_ctx); @@ -71,7 +89,7 @@ TEST(OpenSSLSessionCache, SimpleValidSessionLookup) { } TEST(OpenSSLSessionCache, AddToExistingReplacesPrevious) { - SSL_CTX* ssl_ctx = SSL_CTX_new(DTLSv1_2_client_method()); + SSL_CTX* ssl_ctx = NewDtlsContext(); SSL_SESSION* ssl_session_1 = SSL_SESSION_new(ssl_ctx); SSL_SESSION* ssl_session_2 = SSL_SESSION_new(ssl_ctx); diff --git a/rtc_base/openssl_stream_adapter.cc b/rtc_base/openssl_stream_adapter.cc index f59b4edf18..63b8069e0e 100644 --- a/rtc_base/openssl_stream_adapter.cc +++ b/rtc_base/openssl_stream_adapter.cc @@ -32,7 +32,12 @@ #include "rtc_base/openssl.h" #include "rtc_base/openssl_adapter.h" #include "rtc_base/openssl_digest.h" +#ifdef OPENSSL_IS_BORINGSSL +#include "rtc_base/boringssl_identity.h" +#else #include "rtc_base/openssl_identity.h" +#endif +#include "rtc_base/openssl_utility.h" #include "rtc_base/ssl_certificate.h" #include "rtc_base/stream.h" #include "rtc_base/task_utils/to_queued_task.h" @@ -304,10 +309,14 @@ OpenSSLStreamAdapter::~OpenSSLStreamAdapter() { void OpenSSLStreamAdapter::SetIdentity(std::unique_ptr identity) { RTC_DCHECK(!identity_); +#ifdef OPENSSL_IS_BORINGSSL + identity_.reset(static_cast(identity.release())); +#else identity_.reset(static_cast(identity.release())); +#endif } -OpenSSLIdentity* OpenSSLStreamAdapter::GetIdentityForTesting() const { +SSLIdentity* OpenSSLStreamAdapter::GetIdentityForTesting() const { return identity_.get(); } @@ -994,8 +1003,16 @@ void OpenSSLStreamAdapter::Cleanup(uint8_t alert) { } SSL_CTX* OpenSSLStreamAdapter::SetupSSLContext() { +#ifdef OPENSSL_IS_BORINGSSL + // If X509 objects aren't used, we can use these methods to avoid + // linking the sizable crypto/x509 code, using CRYPTO_BUFFER instead. + SSL_CTX* ctx = + SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ? DTLS_with_buffers_method() + : TLS_with_buffers_method()); +#else SSL_CTX* ctx = SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ? DTLS_method() : TLS_method()); +#endif if (ctx == nullptr) { return nullptr; } @@ -1033,6 +1050,7 @@ SSL_CTX* OpenSSLStreamAdapter::SetupSSLContext() { if (g_use_time_callback_for_testing) { SSL_CTX_set_current_time_cb(ctx, &TimeCallbackForTesting); } + SSL_CTX_set0_buffer_pool(ctx, openssl::GetBufferPool()); #endif if (identity_ && !identity_->ConfigureIdentity(ctx)) { @@ -1053,11 +1071,16 @@ SSL_CTX* OpenSSLStreamAdapter::SetupSSLContext() { } // Configure a custom certificate verification callback to check the peer - // certificate digest. Note the second argument to SSL_CTX_set_verify is to - // override individual errors in the default verification logic, which is not - // what we want here. + // certificate digest. +#ifdef OPENSSL_IS_BORINGSSL + // Use CRYPTO_BUFFER version of the callback if building with BoringSSL. + SSL_CTX_set_custom_verify(ctx, mode, SSLVerifyCallback); +#else + // Note the second argument to SSL_CTX_set_verify is to override individual + // errors in the default verification logic, which is not what we want here. SSL_CTX_set_verify(ctx, mode, nullptr); SSL_CTX_set_cert_verify_callback(ctx, SSLVerifyCallback, nullptr); +#endif // Select list of available ciphers. Note that !SHA256 and !SHA384 only // remove HMAC-SHA256 and HMAC-SHA384 cipher suites, not GCM cipher suites @@ -1082,14 +1105,12 @@ bool OpenSSLStreamAdapter::VerifyPeerCertificate() { RTC_LOG(LS_WARNING) << "Missing digest or peer certificate."; return false; } - const OpenSSLCertificate* leaf_cert = - static_cast(&peer_cert_chain_->Get(0)); unsigned char digest[EVP_MAX_MD_SIZE]; size_t digest_length; - if (!OpenSSLCertificate::ComputeDigest( - leaf_cert->x509(), peer_certificate_digest_algorithm_, digest, - sizeof(digest), &digest_length)) { + if (!peer_cert_chain_->Get(0).ComputeDigest( + peer_certificate_digest_algorithm_, digest, sizeof(digest), + &digest_length)) { RTC_LOG(LS_WARNING) << "Failed to compute peer cert digest."; return false; } @@ -1113,6 +1134,36 @@ std::unique_ptr OpenSSLStreamAdapter::GetPeerSSLCertChain() return peer_cert_chain_ ? peer_cert_chain_->Clone() : nullptr; } +#ifdef OPENSSL_IS_BORINGSSL +enum ssl_verify_result_t OpenSSLStreamAdapter::SSLVerifyCallback( + SSL* ssl, + uint8_t* out_alert) { + // Get our OpenSSLStreamAdapter from the context. + OpenSSLStreamAdapter* stream = + reinterpret_cast(SSL_get_app_data(ssl)); + const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl); + // Creates certificate chain. + std::vector> cert_chain; + for (CRYPTO_BUFFER* cert : chain) { + cert_chain.emplace_back(new BoringSSLCertificate(bssl::UpRef(cert))); + } + stream->peer_cert_chain_.reset(new SSLCertChain(std::move(cert_chain))); + + // If the peer certificate digest isn't known yet, we'll wait to verify + // until it's known, and for now just return a success status. + if (stream->peer_certificate_digest_algorithm_.empty()) { + RTC_LOG(LS_INFO) << "Waiting to verify certificate until digest is known."; + // TODO(deadbeef): Use ssl_verify_retry? + return ssl_verify_ok; + } + + if (!stream->VerifyPeerCertificate()) { + return ssl_verify_invalid; + } + + return ssl_verify_ok; +} +#else // OPENSSL_IS_BORINGSSL int OpenSSLStreamAdapter::SSLVerifyCallback(X509_STORE_CTX* store, void* arg) { // Get our SSL structure and OpenSSLStreamAdapter from the store. SSL* ssl = reinterpret_cast( @@ -1120,20 +1171,10 @@ int OpenSSLStreamAdapter::SSLVerifyCallback(X509_STORE_CTX* store, void* arg) { OpenSSLStreamAdapter* stream = reinterpret_cast(SSL_get_app_data(ssl)); -#if defined(OPENSSL_IS_BORINGSSL) - STACK_OF(X509)* chain = SSL_get_peer_full_cert_chain(ssl); - // Creates certificate chain. - std::vector> cert_chain; - for (X509* cert : chain) { - cert_chain.emplace_back(new OpenSSLCertificate(cert)); - } - stream->peer_cert_chain_.reset(new SSLCertChain(std::move(cert_chain))); -#else // Record the peer's certificate. X509* cert = X509_STORE_CTX_get0_cert(store); stream->peer_cert_chain_.reset( new SSLCertChain(std::make_unique(cert))); -#endif // If the peer certificate digest isn't known yet, we'll wait to verify // until it's known, and for now just return a success status. @@ -1149,6 +1190,7 @@ int OpenSSLStreamAdapter::SSLVerifyCallback(X509_STORE_CTX* store, void* arg) { return 1; } +#endif // !OPENSSL_IS_BORINGSSL bool OpenSSLStreamAdapter::IsBoringSsl() { #ifdef OPENSSL_IS_BORINGSSL diff --git a/rtc_base/openssl_stream_adapter.h b/rtc_base/openssl_stream_adapter.h index fbfccd6844..a09737c024 100644 --- a/rtc_base/openssl_stream_adapter.h +++ b/rtc_base/openssl_stream_adapter.h @@ -21,7 +21,11 @@ #include "absl/types/optional.h" #include "rtc_base/buffer.h" +#ifdef OPENSSL_IS_BORINGSSL +#include "rtc_base/boringssl_identity.h" +#else #include "rtc_base/openssl_identity.h" +#endif #include "rtc_base/ssl_identity.h" #include "rtc_base/ssl_stream_adapter.h" #include "rtc_base/stream.h" @@ -71,7 +75,7 @@ class OpenSSLStreamAdapter final : public SSLStreamAdapter { ~OpenSSLStreamAdapter() override; void SetIdentity(std::unique_ptr identity) override; - OpenSSLIdentity* GetIdentityForTesting() const override; + SSLIdentity* GetIdentityForTesting() const override; // Default argument is for compatibility void SetServerRole(SSLRole role = SSL_SERVER) override; @@ -179,9 +183,16 @@ class OpenSSLStreamAdapter final : public SSLStreamAdapter { SSL_CTX* SetupSSLContext(); // Verify the peer certificate matches the signaled digest. bool VerifyPeerCertificate(); + +#ifdef OPENSSL_IS_BORINGSSL + // SSL certificate verification callback. See SSL_CTX_set_custom_verify. + static enum ssl_verify_result_t SSLVerifyCallback(SSL* ssl, + uint8_t* out_alert); +#else // SSL certificate verification callback. See // SSL_CTX_set_cert_verify_callback. static int SSLVerifyCallback(X509_STORE_CTX* store, void* arg); +#endif bool WaitingToVerifyPeerCertificate() const { return GetClientAuthEnabled() && !peer_certificate_verified_; @@ -208,7 +219,11 @@ class OpenSSLStreamAdapter final : public SSLStreamAdapter { SSL_CTX* ssl_ctx_; // Our key and certificate. +#ifdef OPENSSL_IS_BORINGSSL + std::unique_ptr identity_; +#else std::unique_ptr identity_; +#endif // The certificate chain that the peer presented. Initially null, until the // connection is established. std::unique_ptr peer_cert_chain_; diff --git a/rtc_base/openssl_utility.cc b/rtc_base/openssl_utility.cc index 1984eb0706..f91e8d9203 100644 --- a/rtc_base/openssl_utility.cc +++ b/rtc_base/openssl_utility.cc @@ -14,6 +14,9 @@ #include "rtc_base/win32.h" // NOLINT #endif // WEBRTC_WIN +#ifdef OPENSSL_IS_BORINGSSL +#include +#endif #include #include #include @@ -33,6 +36,10 @@ namespace openssl { // Holds various helper methods. namespace { + +// TODO(crbug.com/webrtc/11710): When OS certificate verification is available, +// and we don't need VerifyPeerCertMatchesHost, don't compile this in order to +// avoid a dependency on OpenSSL X509 objects (see crbug.com/webrtc/11410). void LogCertificates(SSL* ssl, X509* certificate) { // Logging certificates is extremely verbose. So it is disabled by default. #ifdef LOG_CERTIFICATES @@ -65,6 +72,118 @@ void LogCertificates(SSL* ssl, X509* certificate) { } } // namespace +#ifdef OPENSSL_IS_BORINGSSL +bool ParseCertificate(CRYPTO_BUFFER* cert_buffer, + CBS* signature_algorithm_oid, + int64_t* expiration_time) { + CBS cbs; + CRYPTO_BUFFER_init_CBS(cert_buffer, &cbs); + + // Certificate ::= SEQUENCE { + CBS certificate; + if (!CBS_get_asn1(&cbs, &certificate, CBS_ASN1_SEQUENCE)) { + return false; + } + // tbsCertificate TBSCertificate, + CBS tbs_certificate; + if (!CBS_get_asn1(&certificate, &tbs_certificate, CBS_ASN1_SEQUENCE)) { + return false; + } + // signatureAlgorithm AlgorithmIdentifier, + CBS signature_algorithm; + if (!CBS_get_asn1(&certificate, &signature_algorithm, CBS_ASN1_SEQUENCE)) { + return false; + } + if (!CBS_get_asn1(&signature_algorithm, signature_algorithm_oid, + CBS_ASN1_OBJECT)) { + return false; + } + // signatureValue BIT STRING } + if (!CBS_get_asn1(&certificate, nullptr, CBS_ASN1_BITSTRING)) { + return false; + } + if (CBS_len(&certificate)) { + return false; + } + + // Now parse the inner TBSCertificate. + // version [0] EXPLICIT Version DEFAULT v1, + if (!CBS_get_optional_asn1( + &tbs_certificate, nullptr, nullptr, + CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC)) { + return false; + } + // serialNumber CertificateSerialNumber, + if (!CBS_get_asn1(&tbs_certificate, nullptr, CBS_ASN1_INTEGER)) { + return false; + } + // signature AlgorithmIdentifier + if (!CBS_get_asn1(&tbs_certificate, nullptr, CBS_ASN1_SEQUENCE)) { + return false; + } + // issuer Name, + if (!CBS_get_asn1(&tbs_certificate, nullptr, CBS_ASN1_SEQUENCE)) { + return false; + } + // validity Validity, + CBS validity; + if (!CBS_get_asn1(&tbs_certificate, &validity, CBS_ASN1_SEQUENCE)) { + return false; + } + // Skip over notBefore. + if (!CBS_get_any_asn1_element(&validity, nullptr, nullptr, nullptr)) { + return false; + } + // Parse notAfter. + CBS not_after; + unsigned not_after_tag; + if (!CBS_get_any_asn1(&validity, ¬_after, ¬_after_tag)) { + return false; + } + bool long_format; + if (not_after_tag == CBS_ASN1_UTCTIME) { + long_format = false; + } else if (not_after_tag == CBS_ASN1_GENERALIZEDTIME) { + long_format = true; + } else { + return false; + } + if (expiration_time) { + *expiration_time = + ASN1TimeToSec(CBS_data(¬_after), CBS_len(¬_after), long_format); + } + // subject Name, + if (!CBS_get_asn1_element(&tbs_certificate, nullptr, CBS_ASN1_SEQUENCE)) { + return false; + } + // subjectPublicKeyInfo SubjectPublicKeyInfo, + if (!CBS_get_asn1(&tbs_certificate, nullptr, CBS_ASN1_SEQUENCE)) { + return false; + } + // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL + if (!CBS_get_optional_asn1(&tbs_certificate, nullptr, nullptr, + 0x01 | CBS_ASN1_CONTEXT_SPECIFIC)) { + return false; + } + // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL + if (!CBS_get_optional_asn1(&tbs_certificate, nullptr, nullptr, + 0x02 | CBS_ASN1_CONTEXT_SPECIFIC)) { + return false; + } + // extensions [3] EXPLICIT Extensions OPTIONAL + if (!CBS_get_optional_asn1( + &tbs_certificate, nullptr, nullptr, + 0x03 | CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC)) { + return false; + } + if (CBS_len(&tbs_certificate)) { + return false; + } + + return true; +} +#endif // OPENSSL_IS_BORINGSSL + bool VerifyPeerCertMatchesHost(SSL* ssl, const std::string& host) { if (host.empty()) { RTC_DLOG(LS_ERROR) << "Hostname is empty. Cannot verify peer certificate."; @@ -76,9 +195,28 @@ bool VerifyPeerCertMatchesHost(SSL* ssl, const std::string& host) { return false; } +#ifdef OPENSSL_IS_BORINGSSL + // We can't grab a X509 object directly, as the SSL context may have been + // initialized with TLS_with_buffers_method. + const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl); + if (chain == nullptr || sk_CRYPTO_BUFFER_num(chain) == 0) { + RTC_LOG(LS_ERROR) + << "SSL_get0_peer_certificates failed. This should never happen."; + return false; + } + CRYPTO_BUFFER* leaf = sk_CRYPTO_BUFFER_value(chain, 0); + bssl::UniquePtr x509(X509_parse_from_buffer(leaf)); + if (!x509) { + RTC_LOG(LS_ERROR) << "Failed to parse certificate to X509 object."; + return false; + } + LogCertificates(ssl, x509.get()); + return X509_check_host(x509.get(), host.c_str(), host.size(), 0, nullptr) == + 1; +#else // OPENSSL_IS_BORINGSSL X509* certificate = SSL_get_peer_certificate(ssl); if (certificate == nullptr) { - RTC_DLOG(LS_ERROR) + RTC_LOG(LS_ERROR) << "SSL_get_peer_certificate failed. This should never happen."; return false; } @@ -89,6 +227,7 @@ bool VerifyPeerCertMatchesHost(SSL* ssl, const std::string& host) { X509_check_host(certificate, host.c_str(), host.size(), 0, nullptr) == 1; X509_free(certificate); return is_valid_cert_name; +#endif // !defined(OPENSSL_IS_BORINGSSL) } void LogSSLErrors(const std::string& prefix) { @@ -123,5 +262,12 @@ bool LoadBuiltinSSLRootCertificates(SSL_CTX* ctx) { } #endif // WEBRTC_EXCLUDE_BUILT_IN_SSL_ROOT_CERTS +#ifdef OPENSSL_IS_BORINGSSL +CRYPTO_BUFFER_POOL* GetBufferPool() { + static CRYPTO_BUFFER_POOL* instance = CRYPTO_BUFFER_POOL_new(); + return instance; +} +#endif + } // namespace openssl } // namespace rtc diff --git a/rtc_base/openssl_utility.h b/rtc_base/openssl_utility.h index 022294d4bb..ee29ccd602 100644 --- a/rtc_base/openssl_utility.h +++ b/rtc_base/openssl_utility.h @@ -20,8 +20,21 @@ namespace rtc { // to OpenSSL that are commonly used and don't require global state should be // placed here. namespace openssl { + +#ifdef OPENSSL_IS_BORINGSSL +// Does minimal parsing of a certificate (only verifying the presence of major +// fields), primarily for the purpose of extracting the relevant out +// parameters. Any that the caller is uninterested in can be null. +bool ParseCertificate(CRYPTO_BUFFER* cert_buffer, + CBS* signature_algorithm_oid, + int64_t* expiration_time); +#endif + // Verifies that the hostname provided matches that in the peer certificate // attached to this SSL state. +// TODO(crbug.com/webrtc/11710): When OS certificate verification is available, +// skip compiling this as it adds a dependency on OpenSSL X509 objects, which we +// are trying to avoid in favor of CRYPTO_BUFFERs (see crbug.com/webrtc/11410). bool VerifyPeerCertMatchesHost(SSL* ssl, const std::string& host); // Logs all the errors in the OpenSSL errror queue from the current thread. A @@ -35,6 +48,10 @@ void LogSSLErrors(const std::string& prefix); bool LoadBuiltinSSLRootCertificates(SSL_CTX* ssl_ctx); #endif // WEBRTC_EXCLUDE_BUILT_IN_SSL_ROOT_CERTS +#ifdef OPENSSL_IS_BORINGSSL +CRYPTO_BUFFER_POOL* GetBufferPool(); +#endif + } // namespace openssl } // namespace rtc diff --git a/rtc_base/openssl_utility_unittest.cc b/rtc_base/openssl_utility_unittest.cc index 9c9b9717ab..d090524cde 100644 --- a/rtc_base/openssl_utility_unittest.cc +++ b/rtc_base/openssl_utility_unittest.cc @@ -24,8 +24,12 @@ #include #include #include +#ifdef OPENSSL_IS_BORINGSSL +#include +#else #include #include +#endif #include "rtc_base/arraysize.h" #include "rtc_base/checks.h" @@ -169,14 +173,17 @@ const unsigned char kFakeSSLCertificateLegacy[] = { 0x84, 0x0b, 0xc7, 0x15, 0x86, 0xc3, 0xfc, 0x48, 0x55, 0xb5, 0x81, 0x94, 0x73, 0xbd, 0x18, 0xcd, 0x9d, 0x92, 0x47, 0xaa, 0xfd, 0x18}; +#ifdef OPENSSL_IS_BORINGSSL +enum ssl_verify_result_t DummyVerifyCallback(SSL* ssl, uint8_t* out_alert) { + return ssl_verify_ok; +} +#endif + // Creates a client SSL that has completed handshaking with a server that uses // the specified certificate (which must have private key kFakeSSLPrivateKey). // The server is deallocated. This client will have a peer certificate available // and is thus suitable for testing VerifyPeerCertMatchesHost. SSL* CreateSSLWithPeerCertificate(const unsigned char* cert, size_t cert_len) { - X509* x509 = - d2i_X509(nullptr, &cert, checked_cast(cert_len)); // NOLINT - RTC_CHECK(x509); const unsigned char* key_ptr = kFakeSSLPrivateKey; EVP_PKEY* key = d2i_PrivateKey( @@ -184,14 +191,33 @@ SSL* CreateSSLWithPeerCertificate(const unsigned char* cert, size_t cert_len) { checked_cast(arraysize(kFakeSSLPrivateKey))); // NOLINT RTC_CHECK(key); - SSL_CTX* ctx = SSL_CTX_new(SSLv23_method()); +#ifdef OPENSSL_IS_BORINGSSL + SSL_CTX* ctx = SSL_CTX_new(TLS_with_buffers_method()); +#else + SSL_CTX* ctx = SSL_CTX_new(TLS_method()); +#endif SSL* client = SSL_new(ctx); SSL* server = SSL_new(ctx); SSL_set_connect_state(client); SSL_set_accept_state(server); +#ifdef OPENSSL_IS_BORINGSSL + bssl::UniquePtr cert_buffer(CRYPTO_BUFFER_new( + static_cast(cert), cert_len, openssl::GetBufferPool())); + RTC_CHECK(cert_buffer); + std::vector cert_buffers; + cert_buffers.push_back(cert_buffer.get()); + RTC_CHECK(1 == SSL_set_chain_and_key(server, cert_buffers.data(), + cert_buffers.size(), key, nullptr)); + // When using crypto buffers we don't get any built-in verification. + SSL_set_custom_verify(client, SSL_VERIFY_PEER, DummyVerifyCallback); +#else + X509* x509 = + d2i_X509(nullptr, &cert, checked_cast(cert_len)); // NOLINT + RTC_CHECK(x509); RTC_CHECK(SSL_use_certificate(server, x509)); RTC_CHECK(SSL_use_PrivateKey(server, key)); +#endif BIO* bio1; BIO* bio2; @@ -221,13 +247,19 @@ SSL* CreateSSLWithPeerCertificate(const unsigned char* cert, size_t cert_len) { SSL_free(server); SSL_CTX_free(ctx); EVP_PKEY_free(key); +#ifndef OPENSSL_IS_BORINGSSL X509_free(x509); +#endif return client; } } // namespace TEST(OpenSSLUtilityTest, VerifyPeerCertMatchesHostFailsOnNoPeerCertificate) { - SSL_CTX* ssl_ctx = SSL_CTX_new(DTLSv1_2_client_method()); +#ifdef OPENSSL_IS_BORINGSSL + SSL_CTX* ssl_ctx = SSL_CTX_new(DTLS_with_buffers_method()); +#else + SSL_CTX* ssl_ctx = SSL_CTX_new(DTLS_method()); +#endif SSL* ssl = SSL_new(ssl_ctx); EXPECT_FALSE(openssl::VerifyPeerCertMatchesHost(ssl, "webrtc.org")); diff --git a/rtc_base/rtc_certificate_generator.cc b/rtc_base/rtc_certificate_generator.cc index d95b645396..5e1fdcac30 100644 --- a/rtc_base/rtc_certificate_generator.cc +++ b/rtc_base/rtc_certificate_generator.cc @@ -51,7 +51,7 @@ scoped_refptr RTCCertificateGenerator::GenerateCertificate( expires_s = std::min(expires_s, kYearInSeconds); // TODO(torbjorng): Stop using |time_t|, its type is unspecified. It it safe // to assume it can hold up to a year's worth of seconds (and more), but - // |SSLIdentity::Generate| should stop relying on |time_t|. + // |SSLIdentity::Create| should stop relying on |time_t|. // See bugs.webrtc.org/5720. time_t cert_lifetime_s = static_cast(expires_s); identity = SSLIdentity::Create(kIdentityName, key_params, cert_lifetime_s); diff --git a/rtc_base/ssl_certificate.cc b/rtc_base/ssl_certificate.cc index db9097b9a3..3f7013ee11 100644 --- a/rtc_base/ssl_certificate.cc +++ b/rtc_base/ssl_certificate.cc @@ -16,7 +16,12 @@ #include "absl/algorithm/container.h" #include "rtc_base/checks.h" -#include "rtc_base/openssl_certificate.h" +#include "rtc_base/openssl.h" +#ifdef OPENSSL_IS_BORINGSSL +#include "rtc_base/boringssl_identity.h" +#else +#include "rtc_base/openssl_identity.h" +#endif #include "rtc_base/ssl_fingerprint.h" #include "rtc_base/third_party/base64/base64.h" @@ -117,7 +122,11 @@ std::unique_ptr SSLCertChain::GetStats() const { // static std::unique_ptr SSLCertificate::FromPEMString( const std::string& pem_string) { +#ifdef OPENSSL_IS_BORINGSSL + return BoringSSLCertificate::FromPEMString(pem_string); +#else return OpenSSLCertificate::FromPEMString(pem_string); +#endif } } // namespace rtc diff --git a/rtc_base/ssl_identity.cc b/rtc_base/ssl_identity.cc index 09d25d228e..8d93ecfe23 100644 --- a/rtc_base/ssl_identity.cc +++ b/rtc_base/ssl_identity.cc @@ -11,12 +11,16 @@ // Handling of certificates and keypairs for SSLStreamAdapter's peer mode. #include "rtc_base/ssl_identity.h" +#include #include #include -#include #include "rtc_base/checks.h" +#ifdef OPENSSL_IS_BORINGSSL +#include "rtc_base/boringssl_identity.h" +#else #include "rtc_base/openssl_identity.h" +#endif #include "rtc_base/ssl_certificate.h" #include "rtc_base/strings/string_builder.h" #include "rtc_base/third_party/base64/base64.h" @@ -213,28 +217,36 @@ std::string SSLIdentity::DerToPem(const std::string& pem_type, std::unique_ptr SSLIdentity::Create(const std::string& common_name, const KeyParams& key_param, time_t certificate_lifetime) { +#ifdef OPENSSL_IS_BORINGSSL + return BoringSSLIdentity::CreateWithExpiration(common_name, key_param, + certificate_lifetime); +#else return OpenSSLIdentity::CreateWithExpiration(common_name, key_param, certificate_lifetime); +#endif } // static std::unique_ptr SSLIdentity::Create(const std::string& common_name, const KeyParams& key_param) { - return OpenSSLIdentity::CreateWithExpiration( - common_name, key_param, kDefaultCertificateLifetimeInSeconds); + return Create(common_name, key_param, kDefaultCertificateLifetimeInSeconds); } // static std::unique_ptr SSLIdentity::Create(const std::string& common_name, KeyType key_type) { - return OpenSSLIdentity::CreateWithExpiration( - common_name, KeyParams(key_type), kDefaultCertificateLifetimeInSeconds); + return Create(common_name, KeyParams(key_type), + kDefaultCertificateLifetimeInSeconds); } // static std::unique_ptr SSLIdentity::CreateForTest( const SSLIdentityParams& params) { +#ifdef OPENSSL_IS_BORINGSSL + return BoringSSLIdentity::CreateForTest(params); +#else return OpenSSLIdentity::CreateForTest(params); +#endif } // Construct an identity from a private key and a certificate. @@ -242,7 +254,11 @@ std::unique_ptr SSLIdentity::CreateForTest( std::unique_ptr SSLIdentity::CreateFromPEMStrings( const std::string& private_key, const std::string& certificate) { +#ifdef OPENSSL_IS_BORINGSSL + return BoringSSLIdentity::CreateFromPEMStrings(private_key, certificate); +#else return OpenSSLIdentity::CreateFromPEMStrings(private_key, certificate); +#endif } // Construct an identity from a private key and a certificate chain. @@ -250,13 +266,23 @@ std::unique_ptr SSLIdentity::CreateFromPEMStrings( std::unique_ptr SSLIdentity::CreateFromPEMChainStrings( const std::string& private_key, const std::string& certificate_chain) { +#ifdef OPENSSL_IS_BORINGSSL + return BoringSSLIdentity::CreateFromPEMChainStrings(private_key, + certificate_chain); +#else return OpenSSLIdentity::CreateFromPEMChainStrings(private_key, certificate_chain); +#endif } bool operator==(const SSLIdentity& a, const SSLIdentity& b) { +#ifdef OPENSSL_IS_BORINGSSL + return static_cast(a) == + static_cast(b); +#else return static_cast(a) == static_cast(b); +#endif } bool operator!=(const SSLIdentity& a, const SSLIdentity& b) { return !(a == b); diff --git a/rtc_base/ssl_identity_unittest.cc b/rtc_base/ssl_identity_unittest.cc index 0d9d0fd859..a907bfc3ed 100644 --- a/rtc_base/ssl_identity_unittest.cc +++ b/rtc_base/ssl_identity_unittest.cc @@ -65,7 +65,7 @@ const unsigned char kTestCertSha512[] = { 0x35, 0xce, 0x26, 0x58, 0x4a, 0x33, 0x6d, 0xbc, 0xb6}; // These PEM strings were created by generating an identity with -// |SSLIdentity::Generate| and invoking |identity->PrivateKeyToPEMString()|, +// |SSLIdentity::Create| and invoking |identity->PrivateKeyToPEMString()|, // |identity->PublicKeyToPEMString()| and // |identity->certificate().ToPEMString()|. If the crypto library is updated, // and the update changes the string form of the keys, these will have to be @@ -406,6 +406,21 @@ TEST_F(SSLIdentityTest, FromPEMStringsEC) { EXPECT_EQ(kECDSA_CERT_PEM, identity->certificate().ToPEMString()); } +TEST_F(SSLIdentityTest, FromPEMChainStrings) { + // This doesn't form a valid certificate chain, but that doesn't matter for + // the purposes of the test + std::string chain(kRSA_CERT_PEM); + chain.append(kTestCertificate); + std::unique_ptr identity( + SSLIdentity::CreateFromPEMChainStrings(kRSA_PRIVATE_KEY_PEM, chain)); + EXPECT_TRUE(identity); + EXPECT_EQ(kRSA_PRIVATE_KEY_PEM, identity->PrivateKeyToPEMString()); + EXPECT_EQ(kRSA_PUBLIC_KEY_PEM, identity->PublicKeyToPEMString()); + ASSERT_EQ(2u, identity->cert_chain().GetSize()); + EXPECT_EQ(kRSA_CERT_PEM, identity->cert_chain().Get(0).ToPEMString()); + EXPECT_EQ(kTestCertificate, identity->cert_chain().Get(1).ToPEMString()); +} + TEST_F(SSLIdentityTest, CloneIdentityRSA) { TestCloningIdentity(*identity_rsa1_); TestCloningIdentity(*identity_rsa2_); diff --git a/rtc_base/ssl_stream_adapter_unittest.cc b/rtc_base/ssl_stream_adapter_unittest.cc index 379acace6e..c580d835c5 100644 --- a/rtc_base/ssl_stream_adapter_unittest.cc +++ b/rtc_base/ssl_stream_adapter_unittest.cc @@ -508,8 +508,9 @@ class SSLStreamAdapterTestBase : public ::testing::Test, } } - // This tests that the handshake can complete before the identity is - // verified, and the identity will be verified after the fact. + // This tests that the handshake can complete before the identity is verified, + // and the identity will be verified after the fact. It also verifies that + // packets can't be read or written before the identity has been verified. void TestHandshakeWithDelayedIdentity(bool valid_identity) { server_ssl_->SetMode(dtls_ ? rtc::SSL_MODE_DTLS : rtc::SSL_MODE_TLS); client_ssl_->SetMode(dtls_ ? rtc::SSL_MODE_DTLS : rtc::SSL_MODE_TLS); @@ -524,14 +525,9 @@ class SSLStreamAdapterTestBase : public ::testing::Test, } // Start the handshake - int rv; - server_ssl_->SetServerRole(); - rv = server_ssl_->StartSSL(); - ASSERT_EQ(0, rv); - - rv = client_ssl_->StartSSL(); - ASSERT_EQ(0, rv); + ASSERT_EQ(0, server_ssl_->StartSSL()); + ASSERT_EQ(0, client_ssl_->StartSSL()); // Now run the handshake. EXPECT_TRUE_WAIT( @@ -547,16 +543,57 @@ class SSLStreamAdapterTestBase : public ::testing::Test, EXPECT_EQ(rtc::SR_BLOCK, client_ssl_->Write(&packet, 1, &sent, 0)); EXPECT_EQ(rtc::SR_BLOCK, server_ssl_->Write(&packet, 1, &sent, 0)); - // If we set an invalid identity at this point, SetPeerCertificateDigest - // should return false. - SetPeerIdentitiesByDigest(valid_identity, valid_identity); + // Collect both of the certificate digests; needs to be done before calling + // SetPeerCertificateDigest as that may reset the identity. + unsigned char server_digest[20]; + size_t server_digest_len; + unsigned char client_digest[20]; + size_t client_digest_len; + bool rv; + + rv = server_identity()->certificate().ComputeDigest( + rtc::DIGEST_SHA_1, server_digest, 20, &server_digest_len); + ASSERT_TRUE(rv); + rv = client_identity()->certificate().ComputeDigest( + rtc::DIGEST_SHA_1, client_digest, 20, &client_digest_len); + ASSERT_TRUE(rv); + + if (!valid_identity) { + RTC_LOG(LS_INFO) << "Setting bogus digest for client/server certs"; + client_digest[0]++; + server_digest[0]++; + } + + // Set the peer certificate digest for the client. + rtc::SSLPeerCertificateDigestError err; + rtc::SSLPeerCertificateDigestError expected_err = + valid_identity + ? rtc::SSLPeerCertificateDigestError::NONE + : rtc::SSLPeerCertificateDigestError::VERIFICATION_FAILED; + rv = client_ssl_->SetPeerCertificateDigest(rtc::DIGEST_SHA_1, server_digest, + server_digest_len, &err); + EXPECT_EQ(expected_err, err); + EXPECT_EQ(valid_identity, rv); // State should then transition to SS_OPEN or SS_CLOSED based on validation // of the identity. if (valid_identity) { EXPECT_EQ(rtc::SS_OPEN, client_ssl_->GetState()); - EXPECT_EQ(rtc::SS_OPEN, server_ssl_->GetState()); + // If the client sends a packet while the server still hasn't verified the + // client identity, the server should continue to return SR_BLOCK. + EXPECT_EQ(rtc::SR_SUCCESS, client_ssl_->Write(&packet, 1, &sent, 0)); + EXPECT_EQ(rtc::SR_BLOCK, server_ssl_->Read(&packet, 1, 0, 0)); } else { EXPECT_EQ(rtc::SS_CLOSED, client_ssl_->GetState()); + } + + // Set the peer certificate digest for the server. + rv = server_ssl_->SetPeerCertificateDigest(rtc::DIGEST_SHA_1, client_digest, + client_digest_len, &err); + EXPECT_EQ(expected_err, err); + EXPECT_EQ(valid_identity, rv); + if (valid_identity) { + EXPECT_EQ(rtc::SS_OPEN, server_ssl_->GetState()); + } else { EXPECT_EQ(rtc::SS_CLOSED, server_ssl_->GetState()); } } diff --git a/webrtc.gni b/webrtc.gni index 05a230c4f1..2f6e77d1bf 100644 --- a/webrtc.gni +++ b/webrtc.gni @@ -227,6 +227,10 @@ declare_args() { rtc_libvpx_build_vp9 = !build_with_mozilla rtc_build_opus = !build_with_mozilla rtc_build_ssl = !build_with_mozilla + + # Can be set to true if rtc_build_ssl is false, but externally provided + # openssl library is boringssl, to enable the use of boringssl-specific code. + rtc_openssl_is_boringssl = !build_with_mozilla rtc_build_usrsctp = !build_with_mozilla # Enable libevent task queues on platforms that support it.