diff options
author | Yuri Wiitala <miu@chromium.org> | 2019-11-26 16:10:29 -0800 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2019-11-27 00:20:32 +0000 |
commit | fddca10f23f5d483e2768dea6e3e920abb28898c (patch) | |
tree | f1783cfc7bc65df183156ecd52487048767dc26d | |
parent | f9d1fe4a538ab7003addcdf8592c77519a55b91a (diff) | |
download | openscreen-fddca10f23f5d483e2768dea6e3e920abb28898c.tar.gz |
Remove dependencies on openssl from platform/api.
Moves all certificate utilities out of TlsCredentials (in platform/base)
to a new util/crypto/certificate_utilities.* library.
Then, all remaning boringssl dependencies are removed from platform/api
by modifying the TlsConnectionFactory API to provide DER-encoded X509
certificates (i.e., a serialized form) instead of the boringssl X509
struct.
Bug: openscreen:89
Change-Id: Iaaeec687d81770bb8e7e2bab4837880c77a37aa9
Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/1932181
Reviewed-by: Yuri Wiitala <miu@chromium.org>
Reviewed-by: Jordan Bayles <jophba@chromium.org>
Commit-Queue: Yuri Wiitala <miu@chromium.org>
-rw-r--r-- | cast/sender/BUILD.gn | 2 | ||||
-rw-r--r-- | cast/sender/channel/sender_socket_factory.cc | 17 | ||||
-rw-r--r-- | cast/sender/channel/sender_socket_factory.h | 8 | ||||
-rw-r--r-- | platform/BUILD.gn | 1 | ||||
-rw-r--r-- | platform/api/tls_connection_factory.h | 15 | ||||
-rw-r--r-- | platform/base/DEPS | 4 | ||||
-rw-r--r-- | platform/base/tls_credentials.cc | 151 | ||||
-rw-r--r-- | platform/base/tls_credentials.h | 80 | ||||
-rw-r--r-- | platform/base/tls_credentials_unittest.cc | 89 | ||||
-rw-r--r-- | platform/impl/tls_connection_factory_posix.cc | 46 | ||||
-rw-r--r-- | platform/impl/tls_connection_factory_posix.h | 2 | ||||
-rw-r--r-- | util/BUILD.gn | 3 | ||||
-rw-r--r-- | util/crypto/certificate_utils.cc | 124 | ||||
-rw-r--r-- | util/crypto/certificate_utils.h | 40 | ||||
-rw-r--r-- | util/crypto/certificate_utils_unittest.cc | 70 |
15 files changed, 326 insertions, 326 deletions
diff --git a/cast/sender/BUILD.gn b/cast/sender/BUILD.gn index 86d67ade..31c21215 100644 --- a/cast/sender/BUILD.gn +++ b/cast/sender/BUILD.gn @@ -13,12 +13,14 @@ source_set("channel") { ] deps = [ + "../../util", "../common/certificate/proto:certificate_proto", "../common/channel/proto:channel_proto", ] public_deps = [ "../../platform", + "../../third_party/boringssl", ] } diff --git a/cast/sender/channel/sender_socket_factory.cc b/cast/sender/channel/sender_socket_factory.cc index 811f07df..3e75c977 100644 --- a/cast/sender/channel/sender_socket_factory.cc +++ b/cast/sender/channel/sender_socket_factory.cc @@ -7,6 +7,7 @@ #include "cast/common/channel/cast_socket.h" #include "cast/sender/channel/message_util.h" #include "platform/base/tls_connect_options.h" +#include "util/crypto/certificate_utils.h" namespace cast { namespace channel { @@ -43,14 +44,14 @@ void SenderSocketFactory::Connect(const IPEndpoint& endpoint, void SenderSocketFactory::OnAccepted( TlsConnectionFactory* factory, - X509* peer_cert, + std::vector<uint8_t> der_x509_peer_cert, std::unique_ptr<TlsConnection> connection) { OSP_NOTREACHED() << "This factory is connect-only."; } void SenderSocketFactory::OnConnected( TlsConnectionFactory* factory, - X509* peer_cert, + std::vector<uint8_t> der_x509_peer_cert, std::unique_ptr<TlsConnection> connection) { const IPEndpoint& endpoint = connection->GetRemoteEndpoint(); auto it = FindPendingConnection(endpoint); @@ -63,16 +64,18 @@ void SenderSocketFactory::OnConnected( CastSocket::Client* client = it->client; pending_connections_.erase(it); + ErrorOr<bssl::UniquePtr<X509>> peer_cert = openscreen::ImportCertificate( + der_x509_peer_cert.data(), der_x509_peer_cert.size()); if (!peer_cert) { - client_->OnError(this, endpoint, Error::Code::kErrCertsMissing); + client_->OnError(this, endpoint, peer_cert.error()); return; } auto socket = std::make_unique<CastSocket>(std::move(connection), this, GetNextSocketId()); - pending_auth_.emplace_back(new PendingAuth{endpoint, media_policy, - std::move(socket), client, - AuthContext::Create(), peer_cert}); + pending_auth_.emplace_back( + new PendingAuth{endpoint, media_policy, std::move(socket), client, + AuthContext::Create(), std::move(peer_cert.value())}); PendingAuth& pending = *pending_auth_.back(); CastMessage auth_challenge = CreateAuthChallengeMessage(pending.auth_context); @@ -146,7 +149,7 @@ void SenderSocketFactory::OnMessage(CastSocket* socket, CastMessage message) { } ErrorOr<CastDeviceCertPolicy> policy_or_error = AuthenticateChallengeReply( - message, (*it)->peer_cert, (*it)->auth_context); + message, (*it)->peer_cert.get(), (*it)->auth_context); if (policy_or_error.is_error()) { client_->OnError(this, pending->endpoint, policy_or_error.error()); return; diff --git a/cast/sender/channel/sender_socket_factory.h b/cast/sender/channel/sender_socket_factory.h index 62fa6d97..63998674 100644 --- a/cast/sender/channel/sender_socket_factory.h +++ b/cast/sender/channel/sender_socket_factory.h @@ -5,6 +5,8 @@ #ifndef CAST_SENDER_CHANNEL_SENDER_SOCKET_FACTORY_H_ #define CAST_SENDER_CHANNEL_SENDER_SOCKET_FACTORY_H_ +#include <openssl/x509.h> + #include <set> #include <utility> #include <vector> @@ -57,10 +59,10 @@ class SenderSocketFactory final : public TlsConnectionFactory::Client, // TlsConnectionFactory::Client overrides. void OnAccepted(TlsConnectionFactory* factory, - X509* peer_cert, + std::vector<uint8_t> der_x509_peer_cert, std::unique_ptr<TlsConnection> connection) override; void OnConnected(TlsConnectionFactory* factory, - X509* peer_cert, + std::vector<uint8_t> der_x509_peer_cert, std::unique_ptr<TlsConnection> connection) override; void OnConnectionFailed(TlsConnectionFactory* factory, const IPEndpoint& remote_address) override; @@ -79,7 +81,7 @@ class SenderSocketFactory final : public TlsConnectionFactory::Client, std::unique_ptr<CastSocket> socket; CastSocket::Client* client; AuthContext auth_context; - X509* peer_cert; + bssl::UniquePtr<X509> peer_cert; }; friend bool operator<(const std::unique_ptr<PendingAuth>& a, uint32_t b); diff --git a/platform/BUILD.gn b/platform/BUILD.gn index 12d849fa..66c7f0dc 100644 --- a/platform/BUILD.gn +++ b/platform/BUILD.gn @@ -149,7 +149,6 @@ source_set("unittests") { "base/ip_address_unittest.cc", "base/location_unittest.cc", "base/serial_delete_ptr_unittest.cc", - "base/tls_credentials_unittest.cc", ] # The unit tests in impl/ assume the standalone implementation is being used. diff --git a/platform/api/tls_connection_factory.h b/platform/api/tls_connection_factory.h index e68d2fc6..eed6893f 100644 --- a/platform/api/tls_connection_factory.h +++ b/platform/api/tls_connection_factory.h @@ -5,9 +5,10 @@ #ifndef PLATFORM_API_TLS_CONNECTION_FACTORY_H_ #define PLATFORM_API_TLS_CONNECTION_FACTORY_H_ -#include <openssl/crypto.h> +#include <stdint.h> #include <memory> +#include <vector> #include "platform/base/ip_address.h" @@ -17,7 +18,7 @@ namespace platform { class TaskRunner; class TlsConnection; struct TlsConnectOptions; -class TlsCredentials; +struct TlsCredentials; struct TlsListenOptions; // We expect a single factory to be able to handle an arbitrary number of @@ -27,12 +28,18 @@ class TlsConnectionFactory { // Client callbacks are ran on the provided TaskRunner. class Client { public: + // Provides a new |connection| that resulted from listening on the local + // socket. |der_x509_peer_cert| is the DER-encoded X509 certificate from the + // peer. virtual void OnAccepted(TlsConnectionFactory* factory, - X509* peer_cert, + std::vector<uint8_t> der_x509_peer_cert, std::unique_ptr<TlsConnection> connection) = 0; + // Provides a new |connection| that resulted from connecting to a remote + // endpoint. |der_x509_peer_cert| is the DER-encoded X509 certificate from + // the peer. virtual void OnConnected(TlsConnectionFactory* factory, - X509* peer_cert, + std::vector<uint8_t> der_x509_peer_cert, std::unique_ptr<TlsConnection> connection) = 0; virtual void OnConnectionFailed(TlsConnectionFactory* factory, diff --git a/platform/base/DEPS b/platform/base/DEPS index 70cdf0d0..b8e2da73 100644 --- a/platform/base/DEPS +++ b/platform/base/DEPS @@ -19,8 +19,4 @@ specific_include_rules = { "serial_delete_ptr\.h": [ '+platform/api', ], - "tls_credentials\.(cc|h)": [ - '+platform/api', - '+util', - ], } diff --git a/platform/base/tls_credentials.cc b/platform/base/tls_credentials.cc index 8c7723b8..9493f597 100644 --- a/platform/base/tls_credentials.cc +++ b/platform/base/tls_credentials.cc @@ -4,154 +4,19 @@ #include "platform/base/tls_credentials.h" -#include <openssl/asn1.h> -#include <openssl/bio.h> -#include <openssl/evp.h> -#include <openssl/x509.h> -#include <time.h> - -#include <atomic> -#include <chrono> -#include <utility> - -#include "absl/strings/str_cat.h" -#include "util/crypto/openssl_util.h" -#include "util/crypto/sha2.h" - namespace openscreen { namespace platform { -namespace { - -// Returns whether or not the certificate field successfully was added. -bool AddCertificateField(X509_NAME* certificate_name, - absl::string_view field, - absl::string_view value) { - return X509_NAME_add_entry_by_txt( - certificate_name, std::string(field).c_str(), MBSTRING_ASC, - reinterpret_cast<const unsigned char*>(value.data()), - value.length(), -1, 0) == 1; -} - -bssl::UniquePtr<ASN1_TIME> ToAsn1Time(const Clock::time_point time) { - // We don't have access to system_clock::to_time_t. - const std::time_t timestamp_seconds = - std::chrono::duration_cast<std::chrono::seconds>(time.time_since_epoch()) - .count(); - - return bssl::UniquePtr<ASN1_TIME>(ASN1_TIME_set(nullptr, timestamp_seconds)); -} - -bssl::UniquePtr<X509> CreateCertificate( - absl::string_view name, - std::chrono::seconds certificate_duration, - EVP_PKEY key_pair, - const Clock::time_point now_time_point) { - bssl::UniquePtr<X509> certificate(X509_new()); - - // Serial numbers must be unique for this session. As a pretend CA, we should - // not issue certificates with the same serial number in the same session. - static std::atomic_int serial_number(1); - if (ASN1_INTEGER_set(X509_get_serialNumber(certificate.get()), - serial_number++) != 1) { - return nullptr; - } - - const bssl::UniquePtr<ASN1_TIME> now(ToAsn1Time(now_time_point)); - const bssl::UniquePtr<ASN1_TIME> expiration_time( - ToAsn1Time(now_time_point + certificate_duration)); - - if ((X509_set_notBefore(certificate.get(), now.get()) != 1) || - (X509_set_notAfter(certificate.get(), expiration_time.get()) != 1)) { - return nullptr; - } - - X509_NAME* certificate_name = X509_get_subject_name(certificate.get()); - if (!AddCertificateField(certificate_name, "CN", name)) { - return nullptr; - } - - if ((X509_set_issuer_name(certificate.get(), certificate_name) != 1) || - (X509_set_pubkey(certificate.get(), &key_pair) != 1) || - // Unlike all of the other BoringSSL methods here, X509_sign returns - // the size of the signature in bytes. - (X509_sign(certificate.get(), &key_pair, EVP_sha256()) <= 0) || - (X509_verify(certificate.get(), &key_pair) != 1)) { - return nullptr; - } - - return certificate; -} - -} // namespace - -ErrorOr<TlsCredentials> TlsCredentials::Create( - absl::string_view name, - std::chrono::seconds certificate_duration, - ClockNowFunctionPtr now_function, - EVP_PKEY* key_pair) { - EnsureOpenSSLInit(); - - bssl::UniquePtr<X509> certificate = - CreateCertificate(name, certificate_duration, *key_pair, now_function()); - - if (!certificate) { - return Error::Code::kCertificateCreationError; - } - - unsigned char* buffer = nullptr; - const int len = i2d_X509(certificate.get(), &buffer); - if (len <= 0) { - return Error::Code::kCertificateValidationError; - } - - std::vector<uint8_t> raw_der_certificate(buffer, buffer + len); - // BoringSSL doesn't free the temporary buffer. - OPENSSL_free(buffer); - - ErrorOr<RSAPrivateKey> rsa_private_key = - RSAPrivateKey::CreateFromKey(key_pair); - if (rsa_private_key.is_error()) { - return rsa_private_key.error(); - } - - ErrorOr<std::vector<uint8_t>> private_key_export = - rsa_private_key.value().ExportPrivateKey(); - if (private_key_export.is_error()) { - return private_key_export.error(); - } - - ErrorOr<std::vector<uint8_t>> public_key_export = - rsa_private_key.value().ExportPublicKey(); - if (public_key_export.is_error()) { - return public_key_export.error(); - } - - std::vector<uint8_t> public_key_hash(SHA256_DIGEST_LENGTH); - absl::string_view key_view( - reinterpret_cast<const char*>(public_key_export.value().data()), - public_key_export.value().size()); - SHA256HashString(key_view, public_key_hash.data()); +TlsCredentials::TlsCredentials() = default; - return TlsCredentials( - std::move(certificate), std::move(rsa_private_key.value()), - std::move(private_key_export.value()), - std::move(public_key_export.value()), std::move(public_key_hash), - std::move(raw_der_certificate)); -} +TlsCredentials::TlsCredentials(std::vector<uint8_t> der_rsa_private_key, + std::vector<uint8_t> der_rsa_public_key, + std::vector<uint8_t> der_x509_cert) + : der_rsa_private_key(std::move(der_rsa_private_key)), + der_rsa_public_key(std::move(der_rsa_public_key)), + der_x509_cert(std::move(der_x509_cert)) {} -TlsCredentials::TlsCredentials(bssl::UniquePtr<X509> certificate, - RSAPrivateKey key_pair, - std::vector<uint8_t> private_key_base64, - std::vector<uint8_t> public_key_base64, - std::vector<uint8_t> public_key_hash, - std::vector<uint8_t> raw_der_certificate) - : certificate_(std::move(certificate)), - key_pair_(std::move(key_pair)), - private_key_base64_(std::move(private_key_base64)), - public_key_base64_(std::move(public_key_base64)), - public_key_hash_(std::move(public_key_hash)), - raw_der_certificate_(std::move(raw_der_certificate)) {} +TlsCredentials::~TlsCredentials() = default; } // namespace platform } // namespace openscreen diff --git a/platform/base/tls_credentials.h b/platform/base/tls_credentials.h index 715c7847..8c5162dc 100644 --- a/platform/base/tls_credentials.h +++ b/platform/base/tls_credentials.h @@ -5,82 +5,28 @@ #ifndef PLATFORM_BASE_TLS_CREDENTIALS_H_ #define PLATFORM_BASE_TLS_CREDENTIALS_H_ -#include <openssl/crypto.h> -#include <openssl/rsa.h> -#include <openssl/ssl.h> +#include <stdint.h> -#include <chrono> -#include <memory> -#include <string> #include <vector> -#include "absl/strings/string_view.h" -#include "platform/api/time.h" -#include "platform/base/error.h" -#include "platform/base/macros.h" -#include "util/crypto/rsa_private_key.h" - namespace openscreen { namespace platform { -class TlsCredentials { - public: - // We are move only due to unique pointers. - TlsCredentials(TlsCredentials&&) noexcept = default; - TlsCredentials& operator=(TlsCredentials&&) noexcept = default; - - // TlsCredentials generates a self signed certificate given (1) the name - // to use for the certificate, (2) the length of time the certificate will - // be valid, and (3) a private/public key pair. - // - // NOTE: the ownership of EVP_PKEY is automatically managed by OpenSSL using - // ref counting, even if you store it in a bssl::UniquePtr. - static ErrorOr<TlsCredentials> Create( - absl::string_view name, - std::chrono::seconds certificate_duration, - ClockNowFunctionPtr now_function, - EVP_PKEY* key_pair); - - // The OpenSSL encoded self signed certificate. - const X509& certificate() const { return *certificate_; } - - // The original key pair provided on construction. - const RSAPrivateKey& key_pair() const { return key_pair_; } - - // A base64 encoded version of the private key provided on construction. - const std::vector<uint8_t>& private_key_base64() const { - return private_key_base64_; - } - - // A base64 encoded version of the public key provided on construction. - const std::vector<uint8_t>& public_key_base64() const { - return public_key_base64_; - } - - // A SHA-256 digest of the public key provided on construction. - const std::vector<uint8_t>& public_key_hash() const { - return public_key_hash_; - } +struct TlsCredentials { + TlsCredentials(); + TlsCredentials(std::vector<uint8_t> der_rsa_private_key, + std::vector<uint8_t> der_rsa_public_key, + std::vector<uint8_t> der_x509_cert); + ~TlsCredentials(); - // The DER-encoded raw bytes of the generated self signed certficate. - const std::vector<uint8_t>& raw_der_certificate() const { - return raw_der_certificate_; - } + // DER-encoded RSA private key. + std::vector<uint8_t> der_rsa_private_key; - private: - TlsCredentials(bssl::UniquePtr<X509> certificate, - RSAPrivateKey key_pair, - std::vector<uint8_t> private_key_base64, - std::vector<uint8_t> public_key_base64, - std::vector<uint8_t> public_key_hash, - std::vector<uint8_t> raw_der_certificate); + // DER-encoded RSA public key. + std::vector<uint8_t> der_rsa_public_key; - bssl::UniquePtr<X509> certificate_; - RSAPrivateKey key_pair_; - std::vector<uint8_t> private_key_base64_; - std::vector<uint8_t> public_key_base64_; - std::vector<uint8_t> public_key_hash_; - std::vector<uint8_t> raw_der_certificate_; + // DER-encoded X509 Certificate that is based on the above keys. + std::vector<uint8_t> der_x509_cert; }; } // namespace platform diff --git a/platform/base/tls_credentials_unittest.cc b/platform/base/tls_credentials_unittest.cc deleted file mode 100644 index b5d40360..00000000 --- a/platform/base/tls_credentials_unittest.cc +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "platform/base/tls_credentials.h" - -#include <openssl/bio.h> -#include <openssl/bn.h> -#include <openssl/rsa.h> -#include <openssl/x509.h> - -#include <algorithm> -#include <chrono> -#include <utility> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "platform/api/time.h" -#include "platform/base/error.h" -#include "platform/test/fake_clock.h" -#include "util/std_util.h" - -namespace openscreen { -namespace platform { - -using std::chrono::seconds; -using testing::EndsWith; -using testing::StartsWith; - -namespace { - -bssl::UniquePtr<EVP_PKEY> GenerateRsaKeypair() { - bssl::UniquePtr<BIGNUM> prime(BN_new()); - EXPECT_NE(0, BN_set_word(prime.get(), RSA_F4)); - - bssl::UniquePtr<RSA> rsa(RSA_new()); - EXPECT_NE(0, RSA_generate_key_ex(rsa.get(), 2048, prime.get(), nullptr)); - - bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new()); - EXPECT_NE(0, EVP_PKEY_set1_RSA(pkey.get(), rsa.get())); - - return pkey; -} - -} // namespace - -TEST(TlsCredentialsTest, CredentialsAreGeneratedAppropriately) { - bssl::UniquePtr<EVP_PKEY> pkey = GenerateRsaKeypair(); - - FakeClock clock(Clock::now()); - ErrorOr<TlsCredentials> creds_or_error = TlsCredentials::Create( - "test.com", seconds(31556952), platform::FakeClock::now, pkey.get()); - EXPECT_TRUE(creds_or_error.is_value()); - TlsCredentials credentials = std::move(creds_or_error.value()); - - // Validate the generated certificate. A const cast is necessary because - // openssl is not const correct for this method. - EXPECT_NE(0, - X509_verify(const_cast<X509*>(&credentials.certificate()), - const_cast<EVP_PKEY*>(credentials.key_pair().key()))); - - const auto raw_cert = credentials.raw_der_certificate(); - EXPECT_GT(raw_cert.size(), 0u); - - // Calling d2i_X509 does validation of the certificate, beyond the checking - // done in the i2d_X509 method that creates the raw_der_certificate. - const unsigned char* raw_cert_begin = raw_cert.data(); - const bssl::UniquePtr<X509> x509_certificate( - d2i_X509(nullptr, &raw_cert_begin, raw_cert.size())); - EXPECT_NE(nullptr, x509_certificate); - - // Validate the private key - // NOTE: both the private and public keys are base64 encoded, so we can't - // actually validate their contents properly. - const auto private_key = credentials.private_key_base64(); - EXPECT_GT(private_key.size(), 0u); - - // Validate the public key - const auto public_key = credentials.public_key_base64(); - EXPECT_GT(public_key.size(), 0u); - - // Validate the hash - // A SHA-256 hash should always be 256 bits, or 32 bytes. - const unsigned int kSha256HashSizeInBytes = 32; - EXPECT_EQ(credentials.public_key_hash().size(), kSha256HashSizeInBytes); -} - -} // namespace platform -} // namespace openscreen diff --git a/platform/impl/tls_connection_factory_posix.cc b/platform/impl/tls_connection_factory_posix.cc index eb99bf50..8b270dab 100644 --- a/platform/impl/tls_connection_factory_posix.cc +++ b/platform/impl/tls_connection_factory_posix.cc @@ -23,6 +23,7 @@ #include "platform/base/tls_listen_options.h" #include "platform/impl/stream_socket.h" #include "platform/impl/tls_connection_posix.h" +#include "util/crypto/certificate_utils.h" #include "util/crypto/openssl_util.h" #include "util/logging.h" #include "util/trace_logging.h" @@ -30,6 +31,17 @@ namespace openscreen { namespace platform { +namespace { + +ErrorOr<std::vector<uint8_t>> GetDEREncodedPeerCertificate(const SSL& ssl) { + X509* const peer_cert = SSL_get_peer_certificate(&ssl); + ErrorOr<std::vector<uint8_t>> der_peer_cert = ExportCertificate(*peer_cert); + X509_free(peer_cert); + return der_peer_cert; +} + +} // namespace + std::unique_ptr<TlsConnectionFactory> TlsConnectionFactory::CreateFactory( Client* client, TaskRunner* task_runner) { @@ -84,11 +96,20 @@ void TlsConnectionFactoryPosix::Connect(const IPEndpoint& remote_address, return; } + ErrorOr<std::vector<uint8_t>> der_peer_cert = + GetDEREncodedPeerCertificate(*connection->ssl_); + if (!der_peer_cert) { + DispatchConnectionFailed(connection->GetRemoteEndpoint()); + TRACE_SET_RESULT(der_peer_cert.error()); + return; + } + task_runner_->PostTask([weak_this = weak_factory_.GetWeakPtr(), + der = std::move(der_peer_cert.value()), moved_connection = std::move(connection)]() mutable { if (auto* self = weak_this.get()) { - X509* peer_cert = SSL_get_peer_certificate(moved_connection->ssl_.get()); - self->client_->OnConnected(self, peer_cert, std::move(moved_connection)); + self->client_->OnConnected(self, std::move(der), + std::move(moved_connection)); } }); } @@ -97,10 +118,10 @@ void TlsConnectionFactoryPosix::SetListenCredentials( const TlsCredentials& credentials) { EnsureInitialized(); - // We don't really change the certificate, but we do apply ref counting to - // it, so a const cast is unfortunately necessary. - X509* non_const_cert = const_cast<X509*>(&credentials.certificate()); - if (SSL_CTX_use_certificate(ssl_context_.get(), non_const_cert) != 1) { + ErrorOr<bssl::UniquePtr<X509>> cert = ImportCertificate( + credentials.der_x509_cert.data(), credentials.der_x509_cert.size()); + if (!cert || + SSL_CTX_use_certificate(ssl_context_.get(), cert.value().get()) != 1) { DispatchError(Error::Code::kSocketListenFailure); TRACE_SET_RESULT(Error::Code::kSocketListenFailure); return; @@ -170,11 +191,20 @@ void TlsConnectionFactoryPosix::OnSocketAccepted( return; } + ErrorOr<std::vector<uint8_t>> der_peer_cert = + GetDEREncodedPeerCertificate(*connection->ssl_); + if (!der_peer_cert) { + DispatchConnectionFailed(connection->GetRemoteEndpoint()); + TRACE_SET_RESULT(der_peer_cert.error()); + return; + } + task_runner_->PostTask([weak_this = weak_factory_.GetWeakPtr(), + der = std::move(der_peer_cert.value()), moved_connection = std::move(connection)]() mutable { if (auto* self = weak_this.get()) { - X509* peer_cert = SSL_get_peer_certificate(moved_connection->ssl_.get()); - self->client_->OnAccepted(self, peer_cert, std::move(moved_connection)); + self->client_->OnAccepted(self, std::move(der), + std::move(moved_connection)); } }); } diff --git a/platform/impl/tls_connection_factory_posix.h b/platform/impl/tls_connection_factory_posix.h index aa2aee1a..46ba6e25 100644 --- a/platform/impl/tls_connection_factory_posix.h +++ b/platform/impl/tls_connection_factory_posix.h @@ -5,6 +5,8 @@ #ifndef PLATFORM_IMPL_TLS_CONNECTION_FACTORY_POSIX_H_ #define PLATFORM_IMPL_TLS_CONNECTION_FACTORY_POSIX_H_ +#include <openssl/ssl.h> + #include <memory> #include "platform/api/tls_connection.h" diff --git a/util/BUILD.gn b/util/BUILD.gn index 475b2dd6..6d8b80ef 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -10,6 +10,8 @@ source_set("util") { "alarm.h", "big_endian.cc", "big_endian.h", + "crypto/certificate_utils.cc", + "crypto/certificate_utils.h", "crypto/openssl_util.cc", "crypto/openssl_util.h", "crypto/rsa_private_key.cc", @@ -52,6 +54,7 @@ source_set("unittests") { sources = [ "alarm_unittest.cc", "big_endian_unittest.cc", + "crypto/certificate_utils_unittest.cc", "crypto/rsa_private_key_unittest.cc", "crypto/secure_hash_unittest.cc", "crypto/sha2_unittest.cc", diff --git a/util/crypto/certificate_utils.cc b/util/crypto/certificate_utils.cc new file mode 100644 index 00000000..1d6873f4 --- /dev/null +++ b/util/crypto/certificate_utils.cc @@ -0,0 +1,124 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "util/crypto/certificate_utils.h" + +#include <openssl/asn1.h> +#include <openssl/bio.h> +#include <openssl/crypto.h> +#include <openssl/evp.h> +#include <openssl/rsa.h> +#include <openssl/ssl.h> +#include <time.h> + +#include <atomic> +#include <string> + +#include "util/crypto/openssl_util.h" +#include "util/crypto/sha2.h" + +namespace openscreen { + +namespace { + +// Returns whether or not the certificate field successfully was added. +bool AddCertificateField(X509_NAME* certificate_name, + absl::string_view field, + absl::string_view value) { + return X509_NAME_add_entry_by_txt( + certificate_name, std::string(field).c_str(), MBSTRING_ASC, + reinterpret_cast<const unsigned char*>(value.data()), + value.length(), -1, 0) == 1; +} + +bssl::UniquePtr<ASN1_TIME> ToAsn1Time(std::chrono::seconds time_since_epoch) { + return bssl::UniquePtr<ASN1_TIME>( + ASN1_TIME_set(nullptr, time_since_epoch.count())); +} + +bssl::UniquePtr<X509> CreateCertificateInternal( + absl::string_view name, + std::chrono::seconds certificate_duration, + EVP_PKEY key_pair, + std::chrono::seconds time_since_unix_epoch) { + bssl::UniquePtr<X509> certificate(X509_new()); + + // Serial numbers must be unique for this session. As a pretend CA, we should + // not issue certificates with the same serial number in the same session. + static std::atomic_int serial_number(1); + if (ASN1_INTEGER_set(X509_get_serialNumber(certificate.get()), + serial_number++) != 1) { + return nullptr; + } + + const bssl::UniquePtr<ASN1_TIME> now(ToAsn1Time(time_since_unix_epoch)); + const bssl::UniquePtr<ASN1_TIME> expiration_time( + ToAsn1Time(time_since_unix_epoch + certificate_duration)); + + if ((X509_set_notBefore(certificate.get(), now.get()) != 1) || + (X509_set_notAfter(certificate.get(), expiration_time.get()) != 1)) { + return nullptr; + } + + X509_NAME* certificate_name = X509_get_subject_name(certificate.get()); + if (!AddCertificateField(certificate_name, "CN", name)) { + return nullptr; + } + + if ((X509_set_issuer_name(certificate.get(), certificate_name) != 1) || + (X509_set_pubkey(certificate.get(), &key_pair) != 1) || + // Unlike all of the other BoringSSL methods here, X509_sign returns + // the size of the signature in bytes. + (X509_sign(certificate.get(), &key_pair, EVP_sha256()) <= 0) || + (X509_verify(certificate.get(), &key_pair) != 1)) { + return nullptr; + } + + return certificate; +} + +} // namespace + +ErrorOr<bssl::UniquePtr<X509>> CreateCertificate( + absl::string_view name, + std::chrono::seconds duration, + const EVP_PKEY& key_pair, + std::chrono::seconds time_since_unix_epoch) { + bssl::UniquePtr<X509> certificate = CreateCertificateInternal( + name, duration, key_pair, time_since_unix_epoch); + if (!certificate) { + return Error::Code::kCertificateCreationError; + } + return certificate; +} + +ErrorOr<std::vector<uint8_t>> ExportCertificate(const X509& certificate) { + unsigned char* buffer = nullptr; + // Casting-away the const because the legacy i2d_X509() function is not + // const-correct. + X509* const certificate_ptr = const_cast<X509*>(&certificate); + const int len = i2d_X509(certificate_ptr, &buffer); + if (len <= 0) { + return Error::Code::kCertificateValidationError; + } + std::vector<uint8_t> raw_der_certificate(buffer, buffer + len); + // BoringSSL doesn't free the temporary buffer. + OPENSSL_free(buffer); + return raw_der_certificate; +} + +ErrorOr<bssl::UniquePtr<X509>> ImportCertificate(const uint8_t* der_x509_cert, + int der_x509_cert_length) { + if (!der_x509_cert) { + return Error::Code::kErrCertsMissing; + } + bssl::UniquePtr<X509> certificate( + d2i_X509(nullptr, &der_x509_cert, der_x509_cert_length)); + if (!certificate) { + return Error::Code::kCertificateValidationError; + } + return certificate; +} + +} // namespace openscreen diff --git a/util/crypto/certificate_utils.h b/util/crypto/certificate_utils.h new file mode 100644 index 00000000..4bfe6e37 --- /dev/null +++ b/util/crypto/certificate_utils.h @@ -0,0 +1,40 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UTIL_CRYPTO_CERTIFICATE_UTILS_H_ +#define UTIL_CRYPTO_CERTIFICATE_UTILS_H_ + +#include <openssl/x509.h> +#include <stdint.h> + +#include <chrono> +#include <vector> + +#include "absl/strings/string_view.h" +#include "platform/api/time.h" +#include "platform/base/error.h" +#include "util/crypto/rsa_private_key.h" + +namespace openscreen { + +// Creates a new self-signed X509 certificate having the given |name| and +// |duration| until expiration, and based on the given |key_pair|. +// |time_since_unix_epoch| is the current time. +ErrorOr<bssl::UniquePtr<X509>> CreateCertificate( + absl::string_view name, + std::chrono::seconds duration, + const EVP_PKEY& key_pair, + std::chrono::seconds time_since_unix_epoch = + platform::GetWallTimeSinceUnixEpoch()); + +// Exports the given X509 certificate as its DER-encoded binary form. +ErrorOr<std::vector<uint8_t>> ExportCertificate(const X509& certificate); + +// Parses a DER-encoded X509 certificate from its binary form. +ErrorOr<bssl::UniquePtr<X509>> ImportCertificate(const uint8_t* der_x509_cert, + int der_x509_cert_length); + +} // namespace openscreen + +#endif // UTIL_CRYPTO_CERTIFICATE_UTILS_H_ diff --git a/util/crypto/certificate_utils_unittest.cc b/util/crypto/certificate_utils_unittest.cc new file mode 100644 index 00000000..7475756b --- /dev/null +++ b/util/crypto/certificate_utils_unittest.cc @@ -0,0 +1,70 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "util/crypto/certificate_utils.h" + +#include <openssl/bio.h> +#include <openssl/bn.h> +#include <openssl/rsa.h> +#include <openssl/x509.h> + +#include <chrono> + +#include "gtest/gtest.h" +#include "platform/api/time.h" +#include "platform/base/error.h" +#include "util/std_util.h" + +namespace openscreen { +namespace { + +constexpr char kName[] = "test.com"; +constexpr auto kDuration = std::chrono::seconds(31556952); + +bssl::UniquePtr<EVP_PKEY> GenerateRsaKeypair() { + bssl::UniquePtr<BIGNUM> prime(BN_new()); + EXPECT_NE(0, BN_set_word(prime.get(), RSA_F4)); + + bssl::UniquePtr<RSA> rsa(RSA_new()); + EXPECT_NE(0, RSA_generate_key_ex(rsa.get(), 2048, prime.get(), nullptr)); + + bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new()); + EXPECT_NE(0, EVP_PKEY_set1_RSA(pkey.get(), rsa.get())); + + return pkey; +} + +TEST(CertificateUtilTest, CreatesValidCertificate) { + bssl::UniquePtr<EVP_PKEY> pkey = GenerateRsaKeypair(); + + ErrorOr<bssl::UniquePtr<X509>> certificate = + CreateCertificate(kName, kDuration, *pkey); + ASSERT_TRUE(certificate.is_value()); + + // Validate the generated certificate. + EXPECT_NE(0, X509_verify(certificate.value().get(), pkey.get())); +} + +TEST(CertificateUtilTest, ExportsAndImportsCertificate) { + bssl::UniquePtr<EVP_PKEY> pkey = GenerateRsaKeypair(); + ErrorOr<bssl::UniquePtr<X509>> certificate = + CreateCertificate(kName, kDuration, *pkey); + ASSERT_TRUE(certificate.is_value()); + + ErrorOr<std::vector<uint8_t>> exported = + ExportCertificate(*certificate.value()); + ASSERT_TRUE(exported.is_value()) << exported.error(); + EXPECT_FALSE(exported.value().empty()); + + ErrorOr<bssl::UniquePtr<X509>> imported = + ImportCertificate(exported.value().data(), exported.value().size()); + ASSERT_TRUE(imported.is_value()) << imported.error(); + ASSERT_TRUE(imported.value().get()); + + // Validate the imported certificate. + EXPECT_NE(0, X509_verify(imported.value().get(), pkey.get())); +} + +} // namespace +} // namespace openscreen |