From 0a3351c2a7c81284f82e6531380a21d079f55056 Mon Sep 17 00:00:00 2001 From: estark Date: Fri, 24 Apr 2015 14:07:51 -0700 Subject: Encrypt certificate reports before uploading to HTTP URLs This CL introduces a new protobuf to store encrypted CertLoggerRequests. Serialized certificate reports are encrypted with an AES-CTR-128-HMAC-SHA256 AEAD (from BoringSSL, thus encrypted reports are only supported on BoringSSL platforms) before being uploaded to HTTP endpoints. |CertificateErrorReporter::IsHttpUploadUrlSupported| allows users of the class to set an HTTP URL if supported. BUG=461590 Review URL: https://codereview.chromium.org/1083493003 Cr-Commit-Position: refs/heads/master@{#326876} --- chrome/browser/net/cert_logger.proto | 19 ++- chrome/browser/net/certificate_error_reporter.cc | 150 ++++++++++++++++++++- chrome/browser/net/certificate_error_reporter.h | 28 ++++ .../net/certificate_error_reporter_unittest.cc | 68 ++++++++-- chrome/browser/safe_browsing/ping_manager.cc | 30 +++-- crypto/aead_openssl.cc | 125 +++++++++++++++++ crypto/aead_openssl.h | 48 +++++++ crypto/aead_openssl_unittest.cc | 54 ++++++++ crypto/crypto.gyp | 3 + crypto/crypto.gypi | 2 + 10 files changed, 501 insertions(+), 26 deletions(-) create mode 100644 crypto/aead_openssl.cc create mode 100644 crypto/aead_openssl.h create mode 100644 crypto/aead_openssl_unittest.cc diff --git a/chrome/browser/net/cert_logger.proto b/chrome/browser/net/cert_logger.proto index b42c5df..7ef828a 100644 --- a/chrome/browser/net/cert_logger.proto +++ b/chrome/browser/net/cert_logger.proto @@ -41,6 +41,24 @@ message CertLoggerRequest { repeated string pin = 5; }; +// A wrapper proto containing an encrypted CertLoggerRequest +message EncryptedCertLoggerRequest { + // An encrypted, serialized CertLoggerRequest + required bytes encrypted_report = 1; + // The server public key version that was used to derive the shared secret. + required uint32 server_public_key_version = 2; + // The client public key that corresponds to the private key that was used + // to derive the shared secret. + required bytes client_public_key = 3; + // The encryption algorithm used to encrypt the report. + enum Algorithm { + UNKNOWN_ALGORITHM = 0; + AEAD_ECDH_AES_128_CTR_HMAC_SHA256 = 1; + } + optional Algorithm algorithm = 4 + [default = AEAD_ECDH_AES_128_CTR_HMAC_SHA256]; +}; + // The response sent back to the user. message CertLoggerResponse { enum ResponseCode { @@ -53,4 +71,3 @@ message CertLoggerResponse { }; required ResponseCode response = 1; }; - diff --git a/chrome/browser/net/certificate_error_reporter.cc b/chrome/browser/net/certificate_error_reporter.cc index 279209c..edff489 100644 --- a/chrome/browser/net/certificate_error_reporter.cc +++ b/chrome/browser/net/certificate_error_reporter.cc @@ -10,6 +10,14 @@ #include "base/stl_util.h" #include "base/time/time.h" #include "chrome/browser/net/cert_logger.pb.h" + +#if defined(USE_OPENSSL) +#include "crypto/aead_openssl.h" +#endif + +#include "crypto/curve25519.h" +#include "crypto/hkdf.h" +#include "crypto/random.h" #include "net/base/elements_upload_data_stream.h" #include "net/base/load_flags.h" #include "net/base/request_priority.h" @@ -18,15 +26,87 @@ #include "net/ssl/ssl_info.h" #include "net/url_request/url_request_context.h" +namespace { + +// Constants used for crypto +static const uint8 kServerPublicKey[] = { + 0x51, 0xcc, 0x52, 0x67, 0x42, 0x47, 0x3b, 0x10, 0xe8, 0x63, 0x18, + 0x3c, 0x61, 0xa7, 0x96, 0x76, 0x86, 0x91, 0x40, 0x71, 0x39, 0x5f, + 0x31, 0x1a, 0x39, 0x5b, 0x76, 0xb1, 0x6b, 0x3d, 0x6a, 0x2b}; +static const uint32 kServerPublicKeyVersion = 1; + +#if defined(USE_OPENSSL) + +static const char kHkdfLabel[] = "certificate report"; + +bool EncryptSerializedReport( + const uint8* server_public_key, + uint32 server_public_key_version, + const std::string& report, + chrome_browser_net::EncryptedCertLoggerRequest* encrypted_report) { + // Generate an ephemeral key pair to generate a shared secret. + uint8 public_key[crypto::curve25519::kBytes]; + uint8 private_key[crypto::curve25519::kScalarBytes]; + uint8 shared_secret[crypto::curve25519::kBytes]; + + crypto::RandBytes(private_key, sizeof(private_key)); + crypto::curve25519::ScalarBaseMult(private_key, public_key); + crypto::curve25519::ScalarMult(private_key, server_public_key, shared_secret); + + crypto::Aead aead(crypto::Aead::AES_128_CTR_HMAC_SHA256); + crypto::HKDF hkdf(std::string((char*)shared_secret, sizeof(shared_secret)), + kHkdfLabel, std::string(), 0, 0, aead.KeyLength()); + + const std::string key(hkdf.subkey_secret().data(), + hkdf.subkey_secret().size()); + aead.Init(&key); + + // Use an all-zero nonce because the key is random per-message. + std::string nonce(aead.NonceLength(), 0); + + std::string ciphertext; + if (!aead.Seal(report, nonce, "", &ciphertext)) { + LOG(ERROR) << "Error sealing certificate report."; + return false; + } + + encrypted_report->set_encrypted_report(ciphertext); + encrypted_report->set_server_public_key_version(server_public_key_version); + encrypted_report->set_client_public_key( + std::string((char*)public_key, sizeof(public_key))); + encrypted_report->set_algorithm( + chrome_browser_net::EncryptedCertLoggerRequest:: + AEAD_ECDH_AES_128_CTR_HMAC_SHA256); + return true; +} +#endif + +} // namespace + namespace chrome_browser_net { CertificateErrorReporter::CertificateErrorReporter( net::URLRequestContext* request_context, const GURL& upload_url, CookiesPreference cookies_preference) + : CertificateErrorReporter(request_context, + upload_url, + cookies_preference, + kServerPublicKey, + kServerPublicKeyVersion) { +} + +CertificateErrorReporter::CertificateErrorReporter( + net::URLRequestContext* request_context, + const GURL& upload_url, + CookiesPreference cookies_preference, + const uint8 server_public_key[32], + const uint32 server_public_key_version) : request_context_(request_context), upload_url_(upload_url), - cookies_preference_(cookies_preference) { + cookies_preference_(cookies_preference), + server_public_key_(server_public_key), + server_public_key_version_(server_public_key_version) { DCHECK(!upload_url.is_empty()); } @@ -38,8 +118,6 @@ void CertificateErrorReporter::SendReport(ReportType type, const std::string& hostname, const net::SSLInfo& ssl_info) { CertLoggerRequest request; - std::string out; - BuildReport(hostname, ssl_info, &request); switch (type) { @@ -47,9 +125,25 @@ void CertificateErrorReporter::SendReport(ReportType type, SendCertLoggerRequest(request); break; case REPORT_TYPE_EXTENDED_REPORTING: - // TODO(estark): Encrypt the report if not sending over HTTPS. - DCHECK(upload_url_.SchemeIsCryptographic()); - SendCertLoggerRequest(request); + if (upload_url_.SchemeIsCryptographic()) { + SendCertLoggerRequest(request); + } else { + DCHECK(IsHttpUploadUrlSupported()); +#if defined(USE_OPENSSL) + EncryptedCertLoggerRequest encrypted_report; + std::string serialized_report; + request.SerializeToString(&serialized_report); + if (!EncryptSerializedReport(server_public_key_, + server_public_key_version_, + serialized_report, &encrypted_report)) { + LOG(ERROR) << "Failed to encrypt serialized report."; + return; + } + std::string serialized_encrypted_report; + encrypted_report.SerializeToString(&serialized_encrypted_report); + SendSerializedRequest(serialized_encrypted_report); +#endif + } break; default: NOTREACHED(); @@ -84,11 +178,55 @@ scoped_ptr CertificateErrorReporter::CreateURLRequest( return request.Pass(); } +bool CertificateErrorReporter::IsHttpUploadUrlSupported() { +#if defined(USE_OPENSSL) + return true; +#else + return false; +#endif +} + +// Used only by tests. +#if defined(USE_OPENSSL) +bool CertificateErrorReporter::DecryptCertificateErrorReport( + const uint8 server_private_key[32], + const EncryptedCertLoggerRequest& encrypted_report, + CertLoggerRequest* decrypted_report) { + uint8 shared_secret[crypto::curve25519::kBytes]; + crypto::curve25519::ScalarMult( + server_private_key, (uint8*)encrypted_report.client_public_key().data(), + shared_secret); + + crypto::Aead aead(crypto::Aead::AES_128_CTR_HMAC_SHA256); + crypto::HKDF hkdf(std::string((char*)shared_secret, sizeof(shared_secret)), + kHkdfLabel, std::string(), 0, 0, aead.KeyLength()); + + const std::string key(hkdf.subkey_secret().data(), + hkdf.subkey_secret().size()); + aead.Init(&key); + + // Use an all-zero nonce because the key is random per-message. + std::string nonce(aead.NonceLength(), 0); + + std::string plaintext; + if (!aead.Open(encrypted_report.encrypted_report(), nonce, "", &plaintext)) { + LOG(ERROR) << "Error opening certificate report"; + return false; + } + + return decrypted_report->ParseFromString(plaintext); +} +#endif + void CertificateErrorReporter::SendCertLoggerRequest( const CertLoggerRequest& request) { std::string serialized_request; request.SerializeToString(&serialized_request); + SendSerializedRequest(serialized_request); +} +void CertificateErrorReporter::SendSerializedRequest( + const std::string& serialized_request) { scoped_ptr url_request = CreateURLRequest(request_context_); url_request->set_method("POST"); diff --git a/chrome/browser/net/certificate_error_reporter.h b/chrome/browser/net/certificate_error_reporter.h index 3c20ec1..cba431f 100644 --- a/chrome/browser/net/certificate_error_reporter.h +++ b/chrome/browser/net/certificate_error_reporter.h @@ -21,6 +21,7 @@ class SSLInfo; namespace chrome_browser_net { class CertLoggerRequest; +class EncryptedCertLoggerRequest; // Provides functionality for sending reports about invalid SSL // certificate chains to a report collection server. @@ -48,6 +49,13 @@ class CertificateErrorReporter : public net::URLRequest::Delegate { const GURL& upload_url, CookiesPreference cookies_preference); + // Allows tests to use a server public key with known private key. + CertificateErrorReporter(net::URLRequestContext* request_context, + const GURL& upload_url, + CookiesPreference cookies_preference, + const uint8 server_public_key[32], + const uint32 server_public_key_version); + ~CertificateErrorReporter() override; // Construct, serialize, and send a certificate report to the report @@ -58,6 +66,11 @@ class CertificateErrorReporter : public net::URLRequest::Delegate { // responsible for enforcing any preconditions (such as obtaining user // opt-in, only sending reports for certain hostnames, checking for // incognito mode, etc.). + // + // On some platforms (but not all), CertificateErrorReporter can use + // an HTTP endpoint to send encrypted extended reporting reports. On + // unsupported platforms, callers must send extended reporting reports + // over SSL. virtual void SendReport(ReportType type, const std::string& hostname, const net::SSLInfo& ssl_info); @@ -66,6 +79,16 @@ class CertificateErrorReporter : public net::URLRequest::Delegate { void OnResponseStarted(net::URLRequest* request) override; void OnReadCompleted(net::URLRequest* request, int bytes_read) override; + // Callers can use this method to determine if sending reports over + // HTTP is supported. + static bool IsHttpUploadUrlSupported(); + + // Used by tests. + static bool DecryptCertificateErrorReport( + const uint8 server_private_key[32], + const EncryptedCertLoggerRequest& encrypted_report, + CertLoggerRequest* decrypted_report); + private: // Create a URLRequest with which to send a certificate report to the // server. @@ -76,6 +99,8 @@ class CertificateErrorReporter : public net::URLRequest::Delegate { // collection server. void SendCertLoggerRequest(const CertLoggerRequest& request); + void SendSerializedRequest(const std::string& serialized_request); + // Populate the CertLoggerRequest for a report. static void BuildReport(const std::string& hostname, const net::SSLInfo& ssl_info, @@ -92,6 +117,9 @@ class CertificateErrorReporter : public net::URLRequest::Delegate { CookiesPreference cookies_preference_; + const uint8* server_public_key_; + const uint32 server_public_key_version_; + DISALLOW_COPY_AND_ASSIGN(CertificateErrorReporter); }; diff --git a/chrome/browser/net/certificate_error_reporter_unittest.cc b/chrome/browser/net/certificate_error_reporter_unittest.cc index cc6fee7..f9054cd 100644 --- a/chrome/browser/net/certificate_error_reporter_unittest.cc +++ b/chrome/browser/net/certificate_error_reporter_unittest.cc @@ -19,6 +19,7 @@ #include "chrome/browser/net/cert_logger.pb.h" #include "chrome/common/chrome_paths.h" #include "content/public/browser/browser_thread.h" +#include "crypto/curve25519.h" #include "net/base/load_flags.h" #include "net/base/network_delegate_impl.h" #include "net/base/test_data_directory.h" @@ -47,6 +48,7 @@ const char kHostname[] = "test.mail.google.com"; const char kSecondRequestHostname[] = "test2.mail.google.com"; const char kDummyFailureLog[] = "dummy failure log"; const char kTestCertFilename[] = "test_mail_google_com.pem"; +const uint32 kServerPublicKeyVersion = 1; SSLInfo GetTestSSLInfo() { SSLInfo info; @@ -83,7 +85,9 @@ void EnableUrlRequestMocks(bool enable) { // |GetTestSSLInfo()|). The hostname sent in the report will be erased // from |expect_hostnames|. void CheckUploadData(URLRequest* request, - std::set* expect_hostnames) { + std::set* expect_hostnames, + bool encrypted, + const uint8* server_private_key) { const net::UploadDataStream* upload = request->get_upload(); ASSERT_TRUE(upload); ASSERT_TRUE(upload->GetElementReaders()); @@ -93,9 +97,27 @@ void CheckUploadData(URLRequest* request, (*upload->GetElementReaders())[0]->AsBytesReader(); ASSERT_TRUE(reader); std::string upload_data(reader->bytes(), reader->length()); - chrome_browser_net::CertLoggerRequest uploaded_request; - uploaded_request.ParseFromString(upload_data); + chrome_browser_net::CertLoggerRequest uploaded_request; +#if defined(USE_OPENSSL) + if (encrypted) { + chrome_browser_net::EncryptedCertLoggerRequest encrypted_request; + encrypted_request.ParseFromString(upload_data); + EXPECT_EQ(kServerPublicKeyVersion, + encrypted_request.server_public_key_version()); + EXPECT_EQ(chrome_browser_net::EncryptedCertLoggerRequest:: + AEAD_ECDH_AES_128_CTR_HMAC_SHA256, + encrypted_request.algorithm()); + ASSERT_TRUE( + chrome_browser_net::CertificateErrorReporter:: + DecryptCertificateErrorReport(server_private_key, encrypted_request, + &uploaded_request)); + } else { + ASSERT_TRUE(uploaded_request.ParseFromString(upload_data)); + } +#else + ASSERT_TRUE(uploaded_request.ParseFromString(upload_data)); +#endif EXPECT_EQ(1u, expect_hostnames->count(uploaded_request.hostname())); expect_hostnames->erase(uploaded_request.hostname()); @@ -116,7 +138,11 @@ class TestCertificateErrorReporterNetworkDelegate : public NetworkDelegateImpl { : url_request_destroyed_callback_(base::Bind(&base::DoNothing)), all_url_requests_destroyed_callback_(base::Bind(&base::DoNothing)), num_requests_(0), - expect_cookies_(false) {} + expect_cookies_(false), + expect_request_encrypted_(false) { + memset(server_private_key_, 1, sizeof(server_private_key_)); + crypto::curve25519::ScalarBaseMult(server_private_key_, server_public_key_); + } ~TestCertificateErrorReporterNetworkDelegate() override {} @@ -145,6 +171,10 @@ class TestCertificateErrorReporterNetworkDelegate : public NetworkDelegateImpl { expect_cookies_ = expect_cookies; } + void set_expect_request_encrypted(bool expect_request_encrypted) { + expect_request_encrypted_ = expect_request_encrypted; + } + // NetworkDelegateImpl implementation int OnBeforeURLRequest(URLRequest* request, const CompletionCallback& callback, @@ -162,7 +192,8 @@ class TestCertificateErrorReporterNetworkDelegate : public NetworkDelegateImpl { } std::string uploaded_request_hostname; - CheckUploadData(request, &expect_hostnames_); + CheckUploadData(request, &expect_hostnames_, expect_request_encrypted_, + server_private_key_); expect_hostnames_.erase(uploaded_request_hostname); return net::OK; } @@ -173,6 +204,8 @@ class TestCertificateErrorReporterNetworkDelegate : public NetworkDelegateImpl { all_url_requests_destroyed_callback_.Run(); } + const uint8* server_public_key() { return server_public_key_; } + private: base::Closure url_request_destroyed_callback_; base::Closure all_url_requests_destroyed_callback_; @@ -180,6 +213,10 @@ class TestCertificateErrorReporterNetworkDelegate : public NetworkDelegateImpl { GURL expect_url_; std::set expect_hostnames_; bool expect_cookies_; + bool expect_request_encrypted_; + + uint8 server_public_key_[32]; + uint8 server_private_key_[32]; DISALLOW_COPY_AND_ASSIGN(TestCertificateErrorReporterNetworkDelegate); }; @@ -237,11 +274,24 @@ TEST_F(CertificateErrorReporterTest, PinningViolationSendReportSendsRequest) { } TEST_F(CertificateErrorReporterTest, ExtendedReportingSendReportSendsRequest) { - GURL url = net::URLRequestMockDataJob::GetMockHttpsUrl("dummy data", 1); - CertificateErrorReporter reporter( - context(), url, CertificateErrorReporter::DO_NOT_SEND_COOKIES); - SendReport(&reporter, network_delegate(), kHostname, url, 0, + // Data should not be encrypted when sent to an HTTPS URL. + GURL https_url = net::URLRequestMockDataJob::GetMockHttpsUrl("dummy data", 1); + CertificateErrorReporter https_reporter( + context(), https_url, CertificateErrorReporter::DO_NOT_SEND_COOKIES); + network_delegate()->set_expect_request_encrypted(false); + SendReport(&https_reporter, network_delegate(), kHostname, https_url, 0, CertificateErrorReporter::REPORT_TYPE_EXTENDED_REPORTING); + + // Data should be encrypted when sent to an HTTP URL. + if (CertificateErrorReporter::IsHttpUploadUrlSupported()) { + GURL http_url = net::URLRequestMockDataJob::GetMockHttpUrl("dummy data", 1); + CertificateErrorReporter http_reporter( + context(), http_url, CertificateErrorReporter::DO_NOT_SEND_COOKIES, + network_delegate()->server_public_key(), kServerPublicKeyVersion); + network_delegate()->set_expect_request_encrypted(true); + SendReport(&http_reporter, network_delegate(), kHostname, http_url, 1, + CertificateErrorReporter::REPORT_TYPE_EXTENDED_REPORTING); + } } TEST_F(CertificateErrorReporterTest, SendMultipleReportsSequentially) { diff --git a/chrome/browser/safe_browsing/ping_manager.cc b/chrome/browser/safe_browsing/ping_manager.cc index d23c67a..86b29d3 100644 --- a/chrome/browser/safe_browsing/ping_manager.cc +++ b/chrome/browser/safe_browsing/ping_manager.cc @@ -24,8 +24,12 @@ using chrome_browser_net::CertificateErrorReporter; using content::BrowserThread; namespace { -// URL to upload invalid certificate chain reports -const char kExtendedReportingUploadUrl[] = +// URLs to upload invalid certificate chain reports. The HTTP URL is +// preferred since a client seeing an invalid cert might not be able to +// make an HTTPS connection to report it. +// TODO(estark): insert the production HTTP URL when it's ready +const char kExtendedReportingUploadUrlInsecure[] = ""; +const char kExtendedReportingUploadUrlSecure[] = "https://sb-ssl.google.com/safebrowsing/clientreport/chrome-certs"; } // namespace @@ -44,16 +48,22 @@ SafeBrowsingPingManager::SafeBrowsingPingManager( const SafeBrowsingProtocolConfig& config) : client_name_(config.client_name), request_context_getter_(request_context_getter), - url_prefix_(config.url_prefix), - certificate_error_reporter_( - request_context_getter - ? new CertificateErrorReporter( - request_context_getter->GetURLRequestContext(), - GURL(kExtendedReportingUploadUrl), - CertificateErrorReporter::SEND_COOKIES) - : nullptr) { + url_prefix_(config.url_prefix) { DCHECK(!url_prefix_.empty()); + if (request_context_getter) { + bool use_insecure_certificate_upload_url = + CertificateErrorReporter::IsHttpUploadUrlSupported() && + strlen(kExtendedReportingUploadUrlInsecure) > 0; + GURL certificate_upload_url(use_insecure_certificate_upload_url + ? kExtendedReportingUploadUrlInsecure + : kExtendedReportingUploadUrlSecure); + + certificate_error_reporter_.reset(new CertificateErrorReporter( + request_context_getter->GetURLRequestContext(), certificate_upload_url, + CertificateErrorReporter::SEND_COOKIES)); + } + version_ = SafeBrowsingProtocolManagerHelper::Version(); } diff --git a/crypto/aead_openssl.cc b/crypto/aead_openssl.cc new file mode 100644 index 0000000..e32168a --- /dev/null +++ b/crypto/aead_openssl.cc @@ -0,0 +1,125 @@ +// Copyright 2015 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 "crypto/aead_openssl.h" + +#if defined(USE_OPENSSL) + +#include +#include +#include + +#include "base/basictypes.h" +#include "base/strings/string_util.h" +#include "crypto/openssl_util.h" + +namespace crypto { + +Aead::Aead(AeadAlgorithm algorithm) : key_(nullptr) { + EnsureOpenSSLInit(); + switch (algorithm) { + case AES_128_CTR_HMAC_SHA256: + aead_ = EVP_aead_aes_128_ctr_hmac_sha256(); + break; + } +} + +Aead::~Aead() { +} + +void Aead::Init(const std::string* key) { + DCHECK(!key_); + DCHECK_EQ(KeyLength(), key->size()); + key_ = key; +} + +bool Aead::Seal(const base::StringPiece& plaintext, + const base::StringPiece& nonce, + const base::StringPiece& additional_data, + std::string* ciphertext) const { + DCHECK(key_); + DCHECK_EQ(NonceLength(), nonce.size()); + EVP_AEAD_CTX ctx; + + if (!EVP_AEAD_CTX_init(&ctx, aead_, + reinterpret_cast(key_->data()), + key_->size(), EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr)) { + return false; + } + + std::string result; + const size_t max_output_length = + EVP_AEAD_max_overhead(aead_) + plaintext.size(); + size_t output_length; + uint8* out_ptr = + reinterpret_cast(WriteInto(&result, max_output_length + 1)); + + if (!EVP_AEAD_CTX_seal( + &ctx, out_ptr, &output_length, max_output_length, + reinterpret_cast(nonce.data()), nonce.size(), + reinterpret_cast(plaintext.data()), plaintext.size(), + reinterpret_cast(additional_data.data()), + additional_data.size())) { + EVP_AEAD_CTX_cleanup(&ctx); + return false; + } + + DCHECK_LE(output_length, max_output_length); + result.resize(output_length); + + ciphertext->swap(result); + EVP_AEAD_CTX_cleanup(&ctx); + + return true; +} + +bool Aead::Open(const base::StringPiece& ciphertext, + const base::StringPiece& nonce, + const base::StringPiece& additional_data, + std::string* plaintext) const { + DCHECK(key_); + EVP_AEAD_CTX ctx; + + if (!EVP_AEAD_CTX_init(&ctx, aead_, + reinterpret_cast(key_->data()), + key_->size(), EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr)) { + return false; + } + + std::string result; + const size_t max_output_length = ciphertext.size(); + size_t output_length; + uint8* out_ptr = + reinterpret_cast(WriteInto(&result, max_output_length + 1)); + + if (!EVP_AEAD_CTX_open( + &ctx, out_ptr, &output_length, max_output_length, + reinterpret_cast(nonce.data()), nonce.size(), + reinterpret_cast(ciphertext.data()), ciphertext.size(), + reinterpret_cast(additional_data.data()), + additional_data.size())) { + EVP_AEAD_CTX_cleanup(&ctx); + return false; + } + + DCHECK_LE(output_length, max_output_length); + result.resize(output_length); + + plaintext->swap(result); + EVP_AEAD_CTX_cleanup(&ctx); + + return true; +} + +size_t Aead::KeyLength() const { + return EVP_AEAD_key_length(aead_); +} + +size_t Aead::NonceLength() const { + return EVP_AEAD_nonce_length(aead_); +} + +} // namespace + +#endif diff --git a/crypto/aead_openssl.h b/crypto/aead_openssl.h new file mode 100644 index 0000000..773cce1 --- /dev/null +++ b/crypto/aead_openssl.h @@ -0,0 +1,48 @@ +// Copyright 2015 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 CRYPTO_AEAD_H_ +#define CRYPTO_AEAD_H_ + +#include "base/strings/string_piece.h" +#include "crypto/crypto_export.h" + +struct evp_aead_st; + +namespace crypto { + +// This class exposes the AES-128-CTR-HMAC-SHA256 AEAD, currently only +// for OpenSSL builds. +class CRYPTO_EXPORT Aead { + public: + enum AeadAlgorithm { AES_128_CTR_HMAC_SHA256 }; + + explicit Aead(AeadAlgorithm algorithm); + + ~Aead(); + + void Init(const std::string* key); + + bool Seal(const base::StringPiece& plaintext, + const base::StringPiece& nonce, + const base::StringPiece& additional_data, + std::string* ciphertext) const; + + bool Open(const base::StringPiece& ciphertext, + const base::StringPiece& nonce, + const base::StringPiece& additional_data, + std::string* plaintext) const; + + size_t KeyLength() const; + + size_t NonceLength() const; + + private: + const std::string* key_; + const evp_aead_st* aead_; +}; + +} // namespace crypto + +#endif // CRYPTO_ENCRYPTOR_H_ diff --git a/crypto/aead_openssl_unittest.cc b/crypto/aead_openssl_unittest.cc new file mode 100644 index 0000000..446bca2 --- /dev/null +++ b/crypto/aead_openssl_unittest.cc @@ -0,0 +1,54 @@ +// Copyright 2015 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 "crypto/aead_openssl.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +#if defined(USE_OPENSSL) + +TEST(AeadTest, SealOpen) { + crypto::Aead aead(crypto::Aead::AES_128_CTR_HMAC_SHA256); + std::string key(aead.KeyLength(), 0); + aead.Init(&key); + std::string nonce(aead.NonceLength(), 0); + std::string plaintext("this is the plaintext"); + std::string ad("this is the additional data"); + std::string ciphertext; + EXPECT_TRUE(aead.Seal(plaintext, nonce, ad, &ciphertext)); + EXPECT_LT(0U, ciphertext.size()); + + std::string decrypted; + EXPECT_TRUE(aead.Open(ciphertext, nonce, ad, &decrypted)); + + EXPECT_EQ(plaintext, decrypted); +} + +TEST(AeadTest, SealOpenWrongKey) { + crypto::Aead aead(crypto::Aead::AES_128_CTR_HMAC_SHA256); + std::string key(aead.KeyLength(), 0); + std::string wrong_key(aead.KeyLength(), 1); + aead.Init(&key); + crypto::Aead aead_wrong_key(crypto::Aead::AES_128_CTR_HMAC_SHA256); + aead_wrong_key.Init(&wrong_key); + + std::string nonce(aead.NonceLength(), 0); + std::string plaintext("this is the plaintext"); + std::string ad("this is the additional data"); + std::string ciphertext; + EXPECT_TRUE(aead.Seal(plaintext, nonce, ad, &ciphertext)); + EXPECT_LT(0U, ciphertext.size()); + + std::string decrypted; + EXPECT_FALSE(aead_wrong_key.Open(ciphertext, nonce, ad, &decrypted)); + EXPECT_EQ(0U, decrypted.size()); +} + +#endif + +} // namespace diff --git a/crypto/crypto.gyp b/crypto/crypto.gyp index a9cff55..00b59b5 100644 --- a/crypto/crypto.gyp +++ b/crypto/crypto.gyp @@ -125,6 +125,8 @@ ], }, { 'sources!': [ + 'aead_openssl.cc', + 'aead_openssl.h', 'ec_private_key_openssl.cc', 'ec_signature_creator_openssl.cc', 'encryptor_openssl.cc', @@ -158,6 +160,7 @@ 'target_name': 'crypto_unittests', 'type': 'executable', 'sources': [ + 'aead_openssl_unittest.cc', 'curve25519_unittest.cc', 'ec_private_key_unittest.cc', 'ec_signature_creator_unittest.cc', diff --git a/crypto/crypto.gypi b/crypto/crypto.gypi index 6fdbdf4..4456b10 100644 --- a/crypto/crypto.gypi +++ b/crypto/crypto.gypi @@ -27,6 +27,8 @@ # NOTE: all transitive dependencies of HMAC on windows need # to be placed in the source list above. '<@(hmac_win64_related_sources)', + 'aead_openssl.cc', + 'aead_openssl.h', 'apple_keychain.h', 'apple_keychain_ios.mm', 'apple_keychain_mac.mm', -- cgit v1.1