Chromium Code Reviews| Index: chrome/browser/net/certificate_error_reporter.cc |
| diff --git a/chrome/browser/net/certificate_error_reporter.cc b/chrome/browser/net/certificate_error_reporter.cc |
| index 15dad135b373587ed318c3ac384c801275367e4b..18d3ea46e4ca7c01d62320fdf74c17e4ca3274fd 100644 |
| --- a/chrome/browser/net/certificate_error_reporter.cc |
| +++ b/chrome/browser/net/certificate_error_reporter.cc |
| @@ -10,6 +10,12 @@ |
| #include "base/stl_util.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/net/cert_logger.pb.h" |
| +#include "crypto/curve25519.h" |
| +#include "crypto/encryptor.h" |
| +#include "crypto/hmac.h" |
| +#include "crypto/random.h" |
| +#include "crypto/sha2.h" |
| +#include "crypto/symmetric_key.h" |
| #include "net/base/elements_upload_data_stream.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/request_priority.h" |
| @@ -18,15 +24,223 @@ |
| #include "net/ssl/ssl_info.h" |
| #include "net/url_request/url_request_context.h" |
| +namespace { |
| +// Constants used for crypto |
| +// TODO(estark): insert the production public key |
|
felt
2015/04/14 04:30:16
Why's it missing here?
estark
2015/04/14 04:39:16
Um, I don't know. I thought I had a reason for not
estark
2015/04/16 15:04:24
Done.
|
| +static const uint8 kServerPublicKey[] = { |
| + 0xc5, 0x3e, 0x58, 0x9e, 0x43, 0xec, 0xbc, 0x84, 0xff, 0xec, 0x8d, |
| + 0x57, 0x20, 0xa3, 0x61, 0x60, 0xe1, 0x0b, 0x7d, 0x30, 0x5d, 0x3b, |
| + 0x2a, 0x90, 0xcf, 0x73, 0xe7, 0x61, 0xa8, 0x92, 0xa1, 0x79}; |
| +static const uint32 kServerPublicKeyVersion = 1; |
| +static const size_t kAesKeySize = 16; |
| +static const size_t kAeadNonceSize = 12; |
| +static const size_t kAeadTagSize = 32; |
| +static const size_t kAesBlockSize = 16; |
| +static const size_t kAeadKeySize = 48; |
| +static const size_t kSha256BlockSize = 64; |
| + |
| +void CalculateAeadKey(const uint8 client_private_key[32], |
| + const uint8 server_public_key[32], |
| + std::string* aead_key) { |
| + // Compute the shared secret, and then hash it once with a 0 prepended |
| + // and once with a 1 prepended. |
| + uint8 shared_secret[crypto::curve25519::kBytes + 1]; |
| + |
| + crypto::curve25519::ScalarMult(client_private_key, server_public_key, |
| + shared_secret + 1); |
| + |
| + std::string shared_secret_str((char*)shared_secret, sizeof(shared_secret)); |
| + |
| + shared_secret_str[0] = 0; |
| + *aead_key = crypto::SHA256HashString(shared_secret_str); |
| + shared_secret[0] = 1; |
| + // Take only as many bytes of the second hash as we need to get to |
| + // |kAeadKeySize|. |
| + *aead_key += crypto::SHA256HashString(shared_secret_str) |
| + .substr(0, kAeadKeySize - aead_key->size()); |
| +} |
| + |
| +void CopyUint64(uint64 value, std::string* input) { |
| + unsigned i; |
| + uint8 bytes[8]; |
| + for (i = 0; i < sizeof(bytes); i++) { |
| + bytes[i] = value & 0xff; |
| + value >>= 8; |
| + } |
| + *input += std::string((char*)bytes, 8); |
| +} |
| + |
| +void ComputeHmacInput(const std::string& ciphertext, |
| + const std::string& nonce, |
| + std::string* hmac_input) { |
| + CopyUint64(0, hmac_input); |
| + CopyUint64(ciphertext.size(), hmac_input); |
| + |
| + *hmac_input += nonce; |
| + |
| + // Pad with zeroes to the end of the SHA256 block |
| + const size_t num_padding = |
| + (kSha256BlockSize - |
| + ((sizeof(uint64) * 2 + kAeadNonceSize) % kSha256BlockSize)) % |
| + kSha256BlockSize; |
| + uint8 padding[kSha256BlockSize]; |
| + memset(padding, 0, num_padding); |
| + *hmac_input += std::string((char*)padding, num_padding); |
| + |
| + *hmac_input += ciphertext; |
| +} |
| + |
| +// Used only by tests (by way of |DecryptCertificateErrorReport|). |
| +bool Open(const std::string& aead_key, |
| + const std::string& nonce, |
| + const std::string& ciphertext, |
| + std::string* const plaintext) { |
| + crypto::Encryptor encryptor; |
| + uint8 counter[kAesBlockSize]; |
| + scoped_ptr<crypto::SymmetricKey> aes_key(crypto::SymmetricKey::Import( |
| + crypto::SymmetricKey::AES, aead_key.substr(0, kAesKeySize))); |
| + |
| + memset(counter, 0, kAesBlockSize); |
| + memcpy(counter, nonce.data(), kAeadNonceSize); |
| + |
| + std::string aes_ciphertext = |
| + ciphertext.substr(0, ciphertext.size() - kAeadTagSize); |
| + std::string tag = ciphertext.substr(ciphertext.size() - kAeadTagSize); |
| + |
| + // Compute HMAC(ad_len || ct_len || nonce || ciphertext) |
| + crypto::HMAC hmac(crypto::HMAC::SHA256); |
| + if (!hmac.Init(aead_key.substr(kAesKeySize))) { |
| + LOG(ERROR) << "Failed to initialize HMAC"; |
| + return false; |
| + } |
| + |
| + std::string hmac_input; |
| + ComputeHmacInput(aes_ciphertext, nonce, &hmac_input); |
| + if (!hmac.Verify(hmac_input, tag)) |
| + return false; |
| + |
| + encryptor.Init(aes_key.get(), crypto::Encryptor::CTR, ""); |
| + encryptor.SetCounter(std::string((char*)counter, kAesBlockSize)); |
| + encryptor.Decrypt(aes_ciphertext, plaintext); |
| + return true; |
| +} |
| + |
| +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]; |
| + |
| + crypto::RandBytes(private_key, sizeof(private_key)); |
| + crypto::curve25519::ScalarBaseMult(private_key, public_key); |
| + |
| + std::string aead_key; |
| + std::string ciphertext; |
| + CalculateAeadKey(private_key, server_public_key, &aead_key); |
| + |
| + // Use an all-zero nonce because the key is random per-message. |
| + std::string nonce(kAeadNonceSize, 0); |
| + if (!chrome_browser_net::Seal(aead_key, nonce, report, &ciphertext)) |
| + 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, crypto::curve25519::kBytes)); |
| + encrypted_report->set_algorithm( |
| + chrome_browser_net::EncryptedCertLoggerRequest:: |
| + AEAD_ECDH_AES_128_CTR_HMAC_SHA256); |
| + return true; |
| +} |
| + |
| +} // namespace |
| + |
| namespace chrome_browser_net { |
| +// Seal |plaintext| with AES-CTR-128-HMAC-SHA256. Support for AD |
| +// (additional authenticated data) is not implemented because it's not |
| +// needed for cert reporting. |
| +bool Seal(const std::string& aead_key, |
|
estark
2015/04/13 23:43:57
I wasn't sure whether to put the AEAD code here or
|
| + const std::string& nonce, |
| + const std::string& plaintext, |
| + std::string* const ciphertext) { |
| + crypto::Encryptor encryptor; |
| + uint8 counter[kAesBlockSize]; |
| + scoped_ptr<crypto::SymmetricKey> aes_key(crypto::SymmetricKey::Import( |
| + crypto::SymmetricKey::AES, aead_key.substr(0, kAesKeySize))); |
| + |
| + memset(counter, 0, kAesBlockSize); |
| + memcpy(counter, nonce.data(), kAeadNonceSize); |
| + |
| + encryptor.Init(aes_key.get(), crypto::Encryptor::CTR, ""); |
| + encryptor.SetCounter(std::string((char*)counter, sizeof(counter))); |
| + encryptor.Encrypt(plaintext, ciphertext); |
| + |
| + // Compute HMAC(ad_len || ct_len || nonce || ad || padding || ciphertext) |
| + crypto::HMAC hmac(crypto::HMAC::SHA256); |
| + unsigned char digest[32]; |
| + CHECK_EQ(32u, hmac.DigestLength()); |
| + if (!hmac.Init(aead_key.substr(kAesKeySize))) { |
| + LOG(ERROR) << "Failed to initialize HMAC"; |
| + return false; |
| + } |
| + |
| + std::string hmac_input; |
| + ComputeHmacInput(*ciphertext, nonce, &hmac_input); |
| + if (!hmac.Sign(hmac_input, digest, sizeof(digest))) { |
| + LOG(ERROR) << "Failed to compute HMAC."; |
| + return false; |
| + } |
| + |
| + *ciphertext += std::string((char*)digest, kAeadTagSize); |
| + return true; |
| +} |
| + |
| +// Used only by tests. |
| +bool DecryptCertificateErrorReport( |
| + const uint8 server_private_key[32], |
| + const EncryptedCertLoggerRequest& encrypted_report, |
| + CertLoggerRequest* decrypted_report) { |
| + std::string aead_key; |
| + std::string plaintext; |
| + CalculateAeadKey(server_private_key, |
| + (uint8*)encrypted_report.client_public_key().data(), |
| + &aead_key); |
| + |
| + // Use an all-zero nonce because the key is random per-message. |
| + std::string nonce(kAeadNonceSize, 0); |
| + if (!Open(aead_key, nonce, encrypted_report.encrypted_report(), &plaintext)) |
| + return false; |
| + |
| + decrypted_report->ParseFromString(plaintext); |
| + return true; |
| +} |
| + |
| 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 +252,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 +259,22 @@ 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_.SchemeIsSecure()); |
| - SendCertLoggerRequest(request); |
| + if (upload_url_.SchemeIsSecure()) { |
| + SendCertLoggerRequest(request); |
| + } else { |
| + 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); |
| + } |
| break; |
| default: |
| NOTREACHED(); |
| @@ -88,7 +313,11 @@ 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<net::URLRequest> url_request = CreateURLRequest(request_context_); |
| url_request->set_method("POST"); |