Commit 8b48bde1 authored by peter's avatar peter Committed by Commit bot

Implement support for draft-ietf-webpush-encryption-08

This CL builds upon the previous refactorings to implement support for
the latest drafts (which is WGLC) of the Web Push Encryption scheme.

Support is not yet enabled for incoming messages - since the message
format changed slightly as well, a more trivial update to
GCMEncryptionProvider is necessary as well.

BUG=679789

Review-Url: https://codereview.chromium.org/2716443002
Cr-Commit-Position: refs/heads/master@{#473876}
parent a4581b46
...@@ -51,12 +51,18 @@ class WebPushEncryptionDraft03 ...@@ -51,12 +51,18 @@ class WebPushEncryptionDraft03
// GCMMessageCryptographer::EncryptionScheme implementation. // GCMMessageCryptographer::EncryptionScheme implementation.
std::string DerivePseudoRandomKey( std::string DerivePseudoRandomKey(
const base::StringPiece& /* recipient_public_key */,
const base::StringPiece& /* sender_public_key */,
const base::StringPiece& ecdh_shared_secret, const base::StringPiece& ecdh_shared_secret,
const base::StringPiece& auth_secret) override { const base::StringPiece& auth_secret) override {
std::stringstream info_stream; const char kInfo[] = "Content-Encoding: auth";
info_stream << "Content-Encoding: auth" << '\x00';
std::string info;
info.reserve(sizeof(kInfo) + 1);
info.append(kInfo);
info.append(1, '\0');
crypto::HKDF hkdf(ecdh_shared_secret, auth_secret, info_stream.str(), crypto::HKDF hkdf(ecdh_shared_secret, auth_secret, info,
32, /* key_bytes_to_generate */ 32, /* key_bytes_to_generate */
0, /* iv_bytes_to_generate */ 0, /* iv_bytes_to_generate */
0 /* subkey_secret_bytes_to_generate */); 0 /* subkey_secret_bytes_to_generate */);
...@@ -123,6 +129,19 @@ class WebPushEncryptionDraft03 ...@@ -123,6 +129,19 @@ class WebPushEncryptionDraft03
return record; return record;
} }
// The |ciphertext| must be at least of size kAuthenticationTagBytes with two
// padding bytes, which is the case for an empty message with zero padding.
// 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
bool ValidateCiphertextSize(size_t ciphertext_size,
size_t record_size) override {
return ciphertext_size >=
sizeof(uint16_t) +
GCMMessageCryptographer::kAuthenticationTagBytes &&
ciphertext_size <=
record_size + GCMMessageCryptographer::kAuthenticationTagBytes;
}
// The record padding in draft-ietf-webpush-encryption-03 is included at the // 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 // 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. // padding. All padding bytes immediately follow, and must be set to zero.
...@@ -157,6 +176,118 @@ class WebPushEncryptionDraft03 ...@@ -157,6 +176,118 @@ class WebPushEncryptionDraft03
DISALLOW_COPY_AND_ASSIGN(WebPushEncryptionDraft03); DISALLOW_COPY_AND_ASSIGN(WebPushEncryptionDraft03);
}; };
// Implementation of draft 08 of the Web Push Encryption standard:
// https://tools.ietf.org/html/draft-ietf-webpush-encryption-08
// https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-07
class WebPushEncryptionDraft08
: public GCMMessageCryptographer::EncryptionScheme {
public:
WebPushEncryptionDraft08() = default;
~WebPushEncryptionDraft08() override = default;
// GCMMessageCryptographer::EncryptionScheme implementation.
std::string DerivePseudoRandomKey(
const base::StringPiece& recipient_public_key,
const base::StringPiece& sender_public_key,
const base::StringPiece& ecdh_shared_secret,
const base::StringPiece& auth_secret) override {
DCHECK_EQ(recipient_public_key.size(), 65u);
DCHECK_EQ(sender_public_key.size(), 65u);
const char kInfo[] = "WebPush: info";
std::string info;
info.reserve(sizeof(kInfo) + 1 + 65 + 65);
info.append(kInfo);
info.append(1, '\0');
recipient_public_key.AppendToString(&info);
sender_public_key.AppendToString(&info);
crypto::HKDF hkdf(ecdh_shared_secret, auth_secret, info,
32, /* key_bytes_to_generate */
0, /* iv_bytes_to_generate */
0 /* subkey_secret_bytes_to_generate */);
return hkdf.client_write_key().as_string();
}
// The info string used for generating the content encryption key and the
// nonce was simplified in draft-ietf-webpush-encryption-08, because the
// public keys of both the recipient and the sender are now in the PRK.
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 << "aes128gcm";
break;
case EncodingType::NONCE:
info_stream << "nonce";
break;
}
info_stream << '\x00';
return info_stream.str();
}
// draft-ietf-webpush-encryption-08 defines that the padding follows the
// plaintext of a message. A delimiter byte (0x02 for the final record) will
// be added, and then zero or more bytes of padding.
//
// 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(plaintext.size() + sizeof(uint8_t));
plaintext.AppendToString(&record);
record.append(sizeof(uint8_t), '\x02');
return record;
}
// The |ciphertext| must be at least of size kAuthenticationTagBytes with one
// padding delimiter, which is the case for an empty message with minimal
// padding. The |record_size| must be large enough to use only one record.
// https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-08#section-2
bool ValidateCiphertextSize(size_t ciphertext_size,
size_t record_size) override {
return ciphertext_size >=
sizeof(uint8_t) +
GCMMessageCryptographer::kAuthenticationTagBytes &&
ciphertext_size <=
record_size + GCMMessageCryptographer::kAuthenticationTagBytes;
}
// The record padding in draft-ietf-webpush-encryption-08 is included at the
// end of the record. The length is not defined, but all padding bytes must be
// zero until the delimiter (0x02) is found.
bool ValidateAndRemovePadding(base::StringPiece& record) override {
DCHECK_GE(record.size(), 1u);
size_t padding_length = 1;
for (; padding_length <= record.size(); ++padding_length) {
size_t offset = record.size() - padding_length;
if (record[offset] == 0x02 /* padding delimiter octet */)
break;
if (record[offset] != 0x00 /* valid padding byte */)
return false;
}
record.remove_suffix(padding_length);
return true;
}
private:
DISALLOW_COPY_AND_ASSIGN(WebPushEncryptionDraft08);
};
} // namespace } // namespace
const size_t GCMMessageCryptographer::kAuthenticationTagBytes = 16; const size_t GCMMessageCryptographer::kAuthenticationTagBytes = 16;
...@@ -167,6 +298,9 @@ GCMMessageCryptographer::GCMMessageCryptographer(Version version) { ...@@ -167,6 +298,9 @@ GCMMessageCryptographer::GCMMessageCryptographer(Version version) {
case Version::DRAFT_03: case Version::DRAFT_03:
encryption_scheme_ = base::MakeUnique<WebPushEncryptionDraft03>(); encryption_scheme_ = base::MakeUnique<WebPushEncryptionDraft03>();
return; return;
case Version::DRAFT_08:
encryption_scheme_ = base::MakeUnique<WebPushEncryptionDraft08>();
return;
} }
NOTREACHED(); NOTREACHED();
...@@ -192,7 +326,7 @@ bool GCMMessageCryptographer::Encrypt( ...@@ -192,7 +326,7 @@ bool GCMMessageCryptographer::Encrypt(
DCHECK(ciphertext); DCHECK(ciphertext);
std::string prk = encryption_scheme_->DerivePseudoRandomKey( std::string prk = encryption_scheme_->DerivePseudoRandomKey(
ecdh_shared_secret, auth_secret); recipient_public_key, sender_public_key, ecdh_shared_secret, auth_secret);
std::string content_encryption_key = DeriveContentEncryptionKey( std::string content_encryption_key = DeriveContentEncryptionKey(
recipient_public_key, sender_public_key, prk, salt); recipient_public_key, sender_public_key, prk, salt);
...@@ -235,7 +369,7 @@ bool GCMMessageCryptographer::Decrypt( ...@@ -235,7 +369,7 @@ bool GCMMessageCryptographer::Decrypt(
return false; return false;
std::string prk = encryption_scheme_->DerivePseudoRandomKey( std::string prk = encryption_scheme_->DerivePseudoRandomKey(
ecdh_shared_secret, auth_secret); recipient_public_key, sender_public_key, ecdh_shared_secret, auth_secret);
std::string content_encryption_key = DeriveContentEncryptionKey( std::string content_encryption_key = DeriveContentEncryptionKey(
recipient_public_key, sender_public_key, prk, salt); recipient_public_key, sender_public_key, prk, salt);
...@@ -243,12 +377,8 @@ bool GCMMessageCryptographer::Decrypt( ...@@ -243,12 +377,8 @@ bool GCMMessageCryptographer::Decrypt(
std::string nonce = std::string nonce =
DeriveNonce(recipient_public_key, sender_public_key, prk, salt); DeriveNonce(recipient_public_key, sender_public_key, prk, salt);
// The |ciphertext| must be at least of size kAuthenticationTagBytes, which if (!encryption_scheme_->ValidateCiphertextSize(ciphertext.size(),
// is the case when an empty message with a zero padding length has been record_size)) {
// 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; return false;
} }
......
...@@ -17,9 +17,11 @@ ...@@ -17,9 +17,11 @@
namespace gcm { namespace gcm {
// Messages delivered through GCM may be encrypted according to the IETF Web // Messages delivered through GCM may be encrypted according to the IETF Web
// Push protocol. We support the third draft of ietf-webpush-encryption: // Push protocol. We support two versions of ietf-webpush-encryption. The user
// of this class must pass in the version to use when constructing an instance.
// //
// https://tools.ietf.org/html/draft-ietf-webpush-encryption-03 // https://tools.ietf.org/html/draft-ietf-webpush-encryption-03
// https://tools.ietf.org/html/draft-ietf-webpush-encryption-08 (WGLC)
// //
// This class implements the ability to encrypt or decrypt such messages using // This class implements the ability to encrypt or decrypt such messages using
// AEAD_AES_128_GCM with a 16-octet authentication tag. The encrypted payload // AEAD_AES_128_GCM with a 16-octet authentication tag. The encrypted payload
...@@ -31,6 +33,9 @@ namespace gcm { ...@@ -31,6 +33,9 @@ namespace gcm {
// messages provided that a cryptographically-strong random salt is used. // messages provided that a cryptographically-strong random salt is used.
class GCMMessageCryptographer { class GCMMessageCryptographer {
public: public:
// Size, in bytes, of the authentication tag included in the messages.
static const size_t kAuthenticationTagBytes;
// Salt size, in bytes, that will be used together with the key to create a // Salt size, in bytes, that will be used together with the key to create a
// unique content encryption key for a given message. // unique content encryption key for a given message.
static const size_t kSaltSize; static const size_t kSaltSize;
...@@ -38,9 +43,10 @@ class GCMMessageCryptographer { ...@@ -38,9 +43,10 @@ class GCMMessageCryptographer {
// Version of the encryption scheme desired by the consumer. // Version of the encryption scheme desired by the consumer.
enum class Version { enum class Version {
// https://tools.ietf.org/html/draft-ietf-webpush-encryption-03 // https://tools.ietf.org/html/draft-ietf-webpush-encryption-03
DRAFT_03 DRAFT_03,
// TODO(peter): Add support for ietf-webpush-encryption-08. // https://tools.ietf.org/html/draft-ietf-webpush-encryption-08 (WGLC)
DRAFT_08
}; };
// Interface that different versions of the encryption scheme must implement. // Interface that different versions of the encryption scheme must implement.
...@@ -54,6 +60,8 @@ class GCMMessageCryptographer { ...@@ -54,6 +60,8 @@ class GCMMessageCryptographer {
// Derives the pseudo random key (PRK) to use for deriving the content // Derives the pseudo random key (PRK) to use for deriving the content
// encryption key and the nonce. // encryption key and the nonce.
virtual std::string DerivePseudoRandomKey( virtual std::string DerivePseudoRandomKey(
const base::StringPiece& recipient_public_key,
const base::StringPiece& sender_public_key,
const base::StringPiece& ecdh_shared_secret, const base::StringPiece& ecdh_shared_secret,
const base::StringPiece& auth_secret) = 0; const base::StringPiece& auth_secret) = 0;
...@@ -67,6 +75,10 @@ class GCMMessageCryptographer { ...@@ -67,6 +75,10 @@ class GCMMessageCryptographer {
// Creates an encryption record to contain the given |plaintext|. // Creates an encryption record to contain the given |plaintext|.
virtual std::string CreateRecord(const base::StringPiece& plaintext) = 0; virtual std::string CreateRecord(const base::StringPiece& plaintext) = 0;
// Validates that the |ciphertext_size| is valid following the scheme.
virtual bool ValidateCiphertextSize(size_t ciphertext_size,
size_t record_size) = 0;
// Verifies that the padding included in |record| is valid and removes it // Verifies that the padding included in |record| is valid and removes it
// from the StringPiece. Returns whether the padding was valid. // from the StringPiece. Returns whether the padding was valid.
virtual bool ValidateAndRemovePadding(base::StringPiece& record) = 0; virtual bool ValidateAndRemovePadding(base::StringPiece& record) = 0;
...@@ -123,9 +135,6 @@ class GCMMessageCryptographer { ...@@ -123,9 +135,6 @@ class GCMMessageCryptographer {
FRIEND_TEST_ALL_PREFIXES(GCMMessageCryptographerTest, AuthSecretAffectsPRK); FRIEND_TEST_ALL_PREFIXES(GCMMessageCryptographerTest, AuthSecretAffectsPRK);
FRIEND_TEST_ALL_PREFIXES(GCMMessageCryptographerTest, InvalidRecordPadding); FRIEND_TEST_ALL_PREFIXES(GCMMessageCryptographerTest, InvalidRecordPadding);
// Size, in bytes, of the authentication tag included in the messages.
static const size_t kAuthenticationTagBytes;
enum class Direction { ENCRYPT, DECRYPT }; enum class Direction { ENCRYPT, DECRYPT };
// Derives the content encryption key from |ecdh_shared_secret| and |salt|. // Derives the content encryption key from |ecdh_shared_secret| and |salt|.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment