Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(139)

Unified Diff: components/gcm_driver/crypto/gcm_message_cryptographer.cc

Issue 2713673002: Separate out the scheme from the GCMMessageCryptographer (Closed)
Patch Set: comments Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: components/gcm_driver/crypto/gcm_message_cryptographer.cc
diff --git a/components/gcm_driver/crypto/gcm_message_cryptographer.cc b/components/gcm_driver/crypto/gcm_message_cryptographer.cc
index 73a708a059cb86c3679bf1a72f77b9498b587d9f..d11989174215f04dea77c29f528ac6be758a666f 100644
--- a/components/gcm_driver/crypto/gcm_message_cryptographer.cc
+++ b/components/gcm_driver/crypto/gcm_message_cryptographer.cc
@@ -11,6 +11,8 @@
#include <sstream>
#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
#include "base/numerics/safe_math.h"
#include "base/strings/string_util.h"
#include "base/sys_byteorder.h"
@@ -18,6 +20,7 @@
#include "third_party/boringssl/src/include/openssl/aead.h"
namespace gcm {
+
namespace {
// Size, in bytes, of the nonce for a record. This must be at least the size
@@ -37,91 +40,173 @@ using EVP_AEAD_CTX_TransformFunction =
size_t max_out_len, const uint8_t *nonce, size_t nonce_len,
const uint8_t *in, size_t in_len, const uint8_t *ad, size_t ad_len);
-// Creates the info parameter for an HKDF value for the given |content_encoding|
-// in accordance with draft-thomson-http-encryption.
-//
-// cek_info = "Content-Encoding: aesgcm" || 0x00 || context
-// nonce_info = "Content-Encoding: nonce" || 0x00 || context
-//
-// context = "P-256" || 0x00 ||
-// length(recipient_public) || recipient_public ||
-// length(sender_public) || sender_public
-//
-// The length of the public keys must be written as a two octet unsigned integer
-// in network byte order (big endian).
-std::string InfoForContentEncoding(
- const char* content_encoding,
- const base::StringPiece& recipient_public_key,
- const base::StringPiece& sender_public_key) {
- DCHECK_EQ(recipient_public_key.size(), 65u);
- DCHECK_EQ(sender_public_key.size(), 65u);
+// Implementation of draft 03 of the Web Push Encryption standard:
+// https://tools.ietf.org/html/draft-ietf-webpush-encryption-03
+// https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02
+class WebPushEncryptionDraft03
+ : public GCMMessageCryptographer::EncryptionScheme {
+ public:
+ WebPushEncryptionDraft03() = default;
+ ~WebPushEncryptionDraft03() override = default;
+
+ // GCMMessageCryptographer::EncryptionScheme implementation.
+ std::string DerivePseudoRandomKey(
+ const base::StringPiece& ecdh_shared_secret,
+ const base::StringPiece& auth_secret) override {
+ std::stringstream info_stream;
+ info_stream << "Content-Encoding: auth" << '\x00';
+
+ crypto::HKDF hkdf(ecdh_shared_secret, auth_secret, info_stream.str(),
+ 32, /* key_bytes_to_generate */
+ 0, /* iv_bytes_to_generate */
+ 0 /* subkey_secret_bytes_to_generate */);
+
+ return hkdf.client_write_key().as_string();
+ }
- std::stringstream info_stream;
- info_stream << "Content-Encoding: " << content_encoding << '\x00';
- info_stream << "P-256" << '\x00';
+ // Creates the info parameter for an HKDF value for the given
+ // |content_encoding| in accordance with draft-ietf-webpush-encryption-03.
+ //
+ // cek_info = "Content-Encoding: aesgcm" || 0x00 || context
+ // nonce_info = "Content-Encoding: nonce" || 0x00 || context
+ //
+ // context = "P-256" || 0x00 ||
+ // length(recipient_public) || recipient_public ||
+ // length(sender_public) || sender_public
+ //
+ // The length of the public keys must be written as a two octet unsigned
+ // integer in network byte order (big endian).
+ std::string GenerateInfoForContentEncoding(
+ EncodingType type,
+ const base::StringPiece& recipient_public_key,
+ const base::StringPiece& sender_public_key) override {
+ std::stringstream info_stream;
+ info_stream << "Content-Encoding: ";
+
+ switch (type) {
+ case EncodingType::CONTENT_ENCRYPTION_KEY:
+ info_stream << "aesgcm";
+ break;
+ case EncodingType::NONCE:
+ info_stream << "nonce";
+ break;
+ }
+
+ info_stream << '\x00' << "P-256" << '\x00';
+
+ uint16_t local_len =
+ base::HostToNet16(static_cast<uint16_t>(recipient_public_key.size()));
+ info_stream.write(reinterpret_cast<char*>(&local_len), sizeof(local_len));
+ info_stream << recipient_public_key;
+
+ uint16_t peer_len =
+ base::HostToNet16(static_cast<uint16_t>(sender_public_key.size()));
+ info_stream.write(reinterpret_cast<char*>(&peer_len), sizeof(peer_len));
+ info_stream << sender_public_key;
+
+ return info_stream.str();
+ }
- uint16_t local_len =
- base::HostToNet16(static_cast<uint16_t>(recipient_public_key.size()));
- info_stream.write(reinterpret_cast<char*>(&local_len), sizeof(local_len));
- info_stream << recipient_public_key;
+ // draft-ietf-webpush-encryption-03 defines that the padding is included at
+ // the beginning of the message. The first two bytes, in network byte order,
+ // contain the length of the included padding. Then that exact number of bytes
+ // must follow as padding, all of which must have a zero value.
+ //
+ // TODO(peter): Add support for message padding if the GCMMessageCryptographer
+ // starts encrypting payloads for reasons other than testing.
+ std::string CreateRecord(const base::StringPiece& plaintext) override {
+ std::string record;
+ record.reserve(sizeof(uint16_t) + plaintext.size());
+ record.append(sizeof(uint16_t), '\x00');
+
+ plaintext.AppendToString(&record);
+ return record;
+ }
- uint16_t peer_len =
- base::HostToNet16(static_cast<uint16_t>(sender_public_key.size()));
- info_stream.write(reinterpret_cast<char*>(&peer_len), sizeof(peer_len));
- info_stream << sender_public_key;
+ // The record padding in draft-ietf-webpush-encryption-03 is included at the
+ // beginning of the record. The first two bytes indicate the length of the
+ // padding. All padding bytes immediately follow, and must be set to zero.
+ bool ValidateAndRemovePadding(base::StringPiece& record) override {
+ // Records must be at least two octets in size (to hold the padding).
+ // Records that are smaller, i.e. a single octet, are invalid.
+ if (record.size() < sizeof(uint16_t))
+ return false;
- return info_stream.str();
-}
+ // Records contain a two-byte, big-endian padding length followed by zero to
+ // 65535 bytes of padding. Padding bytes must be zero but, since AES-GCM
+ // authenticates the plaintext, checking and removing padding need not be
+ // done in constant-time.
+ uint16_t padding_length = (static_cast<uint8_t>(record[0]) << 8) |
+ static_cast<uint8_t>(record[1]);
+ record.remove_prefix(sizeof(uint16_t));
+
+ if (padding_length > record.size()) {
+ return false;
+ }
+
+ for (size_t i = 0; i < padding_length; ++i) {
+ if (record[i] != 0)
+ return false;
+ }
+
+ record.remove_prefix(padding_length);
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebPushEncryptionDraft03);
+};
} // namespace
const size_t GCMMessageCryptographer::kAuthenticationTagBytes = 16;
const size_t GCMMessageCryptographer::kSaltSize = 16;
-GCMMessageCryptographer::GCMMessageCryptographer(
- const base::StringPiece& recipient_public_key,
- const base::StringPiece& sender_public_key,
- const std::string& auth_secret)
- : content_encryption_key_info_(
- InfoForContentEncoding("aesgcm", recipient_public_key,
- sender_public_key)),
- nonce_info_(
- InfoForContentEncoding("nonce", recipient_public_key,
- sender_public_key)),
- auth_secret_(auth_secret) {
+GCMMessageCryptographer::GCMMessageCryptographer(Version version) {
+ switch (version) {
+ case Version::DRAFT_03:
+ encryption_scheme_ = base::MakeUnique<WebPushEncryptionDraft03>();
+ return;
+ }
+
+ NOTREACHED();
}
-GCMMessageCryptographer::~GCMMessageCryptographer() {}
+GCMMessageCryptographer::~GCMMessageCryptographer() = default;
-bool GCMMessageCryptographer::Encrypt(const base::StringPiece& plaintext,
- const base::StringPiece& ikm,
- const base::StringPiece& salt,
- size_t* record_size,
- std::string* ciphertext) const {
- DCHECK(ciphertext);
+bool GCMMessageCryptographer::Encrypt(
+ const base::StringPiece& recipient_public_key,
+ const base::StringPiece& sender_public_key,
+ const base::StringPiece& ecdh_shared_secret,
+ const base::StringPiece& auth_secret,
+ const base::StringPiece& salt,
+ const base::StringPiece& plaintext,
+ size_t* record_size,
+ std::string* ciphertext) const {
+ DCHECK_EQ(recipient_public_key.size(), 65u);
+ DCHECK_EQ(sender_public_key.size(), 65u);
DCHECK(record_size);
+ DCHECK(ciphertext);
+
+ // TODO(peter): DCHECK the lengths of |ecdh_shared_secret|, |auth_secret| and
+ // |salt|.
if (salt.size() != kSaltSize)
return false;
- std::string prk = DerivePseudoRandomKey(ikm);
-
- std::string content_encryption_key = DeriveContentEncryptionKey(prk, salt);
- std::string nonce = DeriveNonce(prk, salt);
+ std::string prk = encryption_scheme_->DerivePseudoRandomKey(
+ ecdh_shared_secret, auth_secret);
- // Prior to the plaintext, draft-thomson-http-encryption has a two-byte
- // padding length followed by zero to 65535 bytes of padding. There is no need
- // for payloads created by Chrome to be padded so the padding length is set to
- // zero.
- std::string record;
- record.reserve(sizeof(uint16_t) + plaintext.size());
- record.append(sizeof(uint16_t), '\0');
-
- plaintext.AppendToString(&record);
+ std::string content_encryption_key = DeriveContentEncryptionKey(
+ recipient_public_key, sender_public_key, prk, salt);
+ std::string nonce =
+ DeriveNonce(recipient_public_key, sender_public_key, prk, salt);
+ std::string record = encryption_scheme_->CreateRecord(plaintext);
std::string encrypted_record;
- if (!EncryptDecryptRecordInternal(ENCRYPT, record, content_encryption_key,
- nonce, &encrypted_record)) {
+
+ if (!TransformRecord(Direction::ENCRYPT, record, content_encryption_key,
+ nonce, &encrypted_record)) {
return false;
}
@@ -133,74 +218,64 @@ bool GCMMessageCryptographer::Encrypt(const base::StringPiece& plaintext,
return true;
}
-bool GCMMessageCryptographer::Decrypt(const base::StringPiece& ciphertext,
- const base::StringPiece& ikm,
- const base::StringPiece& salt,
- size_t record_size,
- std::string* plaintext) const {
+bool GCMMessageCryptographer::Decrypt(
+ const base::StringPiece& recipient_public_key,
+ const base::StringPiece& sender_public_key,
+ const base::StringPiece& ecdh_shared_secret,
+ const base::StringPiece& auth_secret,
+ const base::StringPiece& salt,
+ const base::StringPiece& ciphertext,
+ size_t record_size,
+ std::string* plaintext) const {
+ DCHECK_EQ(recipient_public_key.size(), 65u);
+ DCHECK_EQ(sender_public_key.size(), 65u);
DCHECK(plaintext);
- if (salt.size() != kSaltSize || record_size <= 1)
+ // TODO(peter): DCHECK the lengths of |ecdh_shared_secret|, |auth_secret| and
+ // |salt|.
+
+ if (record_size <= 1)
return false;
- // The |ciphertext| must be at least of size kAuthenticationTagBytes plus
- // len(uint16) to hold the message's padding length, which is the case when an
- // empty message with a zero padding length has been received. Per
- // https://tools.ietf.org/html/draft-thomson-http-encryption-02#section-3, the
- // |record_size| parameter must be large enough to use only one record.
+ std::string prk = encryption_scheme_->DerivePseudoRandomKey(
+ ecdh_shared_secret, auth_secret);
+
+ std::string content_encryption_key = DeriveContentEncryptionKey(
+ recipient_public_key, sender_public_key, prk, salt);
+
+ std::string nonce =
+ DeriveNonce(recipient_public_key, sender_public_key, prk, salt);
+
+ // The |ciphertext| must be at least of size kAuthenticationTagBytes, which
+ // is the case when an empty message with a zero padding length has been
+ // received. The |record_size| must be large enough to use only one record.
+ // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-03#section-2
if (ciphertext.size() < sizeof(uint16_t) + kAuthenticationTagBytes ||
ciphertext.size() > record_size + kAuthenticationTagBytes) {
return false;
}
- std::string prk = DerivePseudoRandomKey(ikm);
-
- std::string content_encryption_key = DeriveContentEncryptionKey(prk, salt);
- std::string nonce = DeriveNonce(prk, salt);
-
std::string decrypted_record_string;
- if (!EncryptDecryptRecordInternal(DECRYPT, ciphertext, content_encryption_key,
- nonce, &decrypted_record_string)) {
+ if (!TransformRecord(Direction::DECRYPT, ciphertext, content_encryption_key,
+ nonce, &decrypted_record_string)) {
return false;
}
DCHECK(!decrypted_record_string.empty());
base::StringPiece decrypted_record(decrypted_record_string);
-
- // Records must be at least two octets in size (to hold the padding). Records
- // that are smaller, i.e. a single octet, are invalid.
- if (decrypted_record.size() < sizeof(uint16_t))
+ if (!encryption_scheme_->ValidateAndRemovePadding(decrypted_record))
return false;
- // Records contain a two-byte, big-endian padding length followed by zero to
- // 65535 bytes of padding. Padding bytes must be zero but, since AES-GCM
- // authenticates the plaintext, checking and removing padding need not be done
- // in constant-time.
- uint16_t padding_length = (static_cast<uint8_t>(decrypted_record[0]) << 8) |
- static_cast<uint8_t>(decrypted_record[1]);
- decrypted_record.remove_prefix(sizeof(uint16_t));
-
- if (padding_length > decrypted_record.size()) {
- return false;
- }
-
- for (size_t i = 0; i < padding_length; ++i) {
- if (decrypted_record[i] != 0)
- return false;
- }
-
- decrypted_record.remove_prefix(padding_length);
decrypted_record.CopyToString(plaintext);
return true;
}
-bool GCMMessageCryptographer::EncryptDecryptRecordInternal(
- Mode mode,
- const base::StringPiece& input,
- const base::StringPiece& key,
- const base::StringPiece& nonce,
- std::string* output) const {
+bool GCMMessageCryptographer::TransformRecord(Direction direction,
+ const base::StringPiece& input,
+ const base::StringPiece& key,
+ const base::StringPiece& nonce,
+ std::string* output) const {
DCHECK(output);
const EVP_AEAD* aead = EVP_aead_aes_128_gcm();
@@ -213,7 +288,7 @@ bool GCMMessageCryptographer::EncryptDecryptRecordInternal(
}
base::CheckedNumeric<size_t> maximum_output_length(input.size());
- if (mode == ENCRYPT)
+ if (direction == Direction::ENCRYPT)
maximum_output_length += kAuthenticationTagBytes;
// WriteInto requires the buffer to finish with a NULL-byte.
@@ -224,7 +299,7 @@ bool GCMMessageCryptographer::EncryptDecryptRecordInternal(
base::WriteInto(output, maximum_output_length.ValueOrDie()));
EVP_AEAD_CTX_TransformFunction* transform_function =
- mode == ENCRYPT ? EVP_AEAD_CTX_seal : EVP_AEAD_CTX_open;
+ direction == Direction::ENCRYPT ? EVP_AEAD_CTX_seal : EVP_AEAD_CTX_open;
if (!transform_function(
&context, raw_output, &output_length, output->size(),
@@ -238,7 +313,7 @@ bool GCMMessageCryptographer::EncryptDecryptRecordInternal(
EVP_AEAD_CTX_cleanup(&context);
base::CheckedNumeric<size_t> expected_output_length(input.size());
- if (mode == ENCRYPT)
+ if (direction == Direction::ENCRYPT)
expected_output_length += kAuthenticationTagBytes;
else
expected_output_length -= kAuthenticationTagBytes;
@@ -249,49 +324,40 @@ bool GCMMessageCryptographer::EncryptDecryptRecordInternal(
return true;
}
-std::string GCMMessageCryptographer::DerivePseudoRandomKey(
- const base::StringPiece& ikm) const {
- if (allow_empty_auth_secret_for_tests_ && auth_secret_.empty())
- return ikm.as_string();
-
- CHECK(!auth_secret_.empty());
-
- std::stringstream info_stream;
- info_stream << "Content-Encoding: auth" << '\x00';
-
- crypto::HKDF hkdf(ikm, auth_secret_,
- info_stream.str(),
- 32, /* key_bytes_to_generate */
- 0, /* iv_bytes_to_generate */
- 0 /* subkey_secret_bytes_to_generate */);
-
- return hkdf.client_write_key().as_string();
-}
-
std::string GCMMessageCryptographer::DeriveContentEncryptionKey(
- const base::StringPiece& prk,
+ const base::StringPiece& recipient_public_key,
+ const base::StringPiece& sender_public_key,
+ const base::StringPiece& ecdh_shared_secret,
const base::StringPiece& salt) const {
- crypto::HKDF hkdf(prk, salt,
- content_encryption_key_info_,
- kContentEncryptionKeySize,
- 0, /* iv_bytes_to_generate */
- 0 /* subkey_secret_bytes_to_generate */);
+ std::string content_encryption_key_info =
+ encryption_scheme_->GenerateInfoForContentEncoding(
+ EncryptionScheme::EncodingType::CONTENT_ENCRYPTION_KEY,
+ recipient_public_key, sender_public_key);
+
+ crypto::HKDF hkdf(ecdh_shared_secret, salt, content_encryption_key_info,
+ kContentEncryptionKeySize, 0, /* iv_bytes_to_generate */
+ 0 /* subkey_secret_bytes_to_generate */);
return hkdf.client_write_key().as_string();
}
std::string GCMMessageCryptographer::DeriveNonce(
- const base::StringPiece& prk,
+ const base::StringPiece& recipient_public_key,
+ const base::StringPiece& sender_public_key,
+ const base::StringPiece& ecdh_shared_secret,
const base::StringPiece& salt) const {
- crypto::HKDF hkdf(prk, salt,
- nonce_info_,
- kNonceSize,
- 0, /* iv_bytes_to_generate */
- 0 /* subkey_secret_bytes_to_generate */);
-
- // draft-thomson-http-encryption defines that the result should be XOR'ed with
- // the record's sequence number, however, Web Push encryption is limited to a
- // single record per draft-ietf-webpush-encryption.
+ std::string nonce_info = encryption_scheme_->GenerateInfoForContentEncoding(
+ EncryptionScheme::EncodingType::NONCE, recipient_public_key,
+ sender_public_key);
+
+ crypto::HKDF hkdf(ecdh_shared_secret, salt, nonce_info, kNonceSize,
+ 0, /* iv_bytes_to_generate */
+ 0 /* subkey_secret_bytes_to_generate */);
+
+ // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02
+ // defines that the result should be XOR'ed with the record's sequence number,
+ // however, Web Push encryption is limited to a single record per
+ // https://tools.ietf.org/html/draft-ietf-webpush-encryption-03.
return hkdf.client_write_key().as_string();
}

Powered by Google App Engine
This is Rietveld 408576698