1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/gcm_driver/crypto/gcm_message_cryptographer.h"
9 #include "base/big_endian.h"
10 #include "base/logging.h"
11 #include "crypto/hkdf.h"
16 // Size, in bytes, of the nonce for a record. This must be at least the size
17 // of a uint64_t, which is used to indicate the record sequence number.
18 const uint64_t kNonceSize
= 12;
20 // The default record size as defined by draft-thomson-http-encryption-01.
21 const size_t kDefaultRecordSize
= 4096;
23 // Key size, in bytes, of a valid AEAD_AES_128_GCM key.
24 const size_t kContentEncryptionKeySize
= 16;
26 // Salt size, in bytes, that will be used together with the key to create a
27 // unique content encryption key for a given message.
28 const size_t kSaltSize
= 16;
32 const size_t GCMMessageCryptographer::kAuthenticationTagBytes
= 16;
34 GCMMessageCryptographer::GCMMessageCryptographer() {}
36 GCMMessageCryptographer::~GCMMessageCryptographer() {}
38 bool GCMMessageCryptographer::Encrypt(const base::StringPiece
& plaintext
,
39 const base::StringPiece
& key
,
40 const base::StringPiece
& salt
,
42 std::string
* ciphertext
) const {
46 if (salt
.size() != kSaltSize
)
49 std::string content_encryption_key
= DeriveContentEncryptionKey(key
, salt
);
50 std::string nonce
= DeriveNonce(key
, salt
);
52 // draft-nottingham-http-encryption-encoding-00 allows between 0 and 255
53 // octets of padding to be inserted before the enciphered content, with the
54 // length of the padding stored in the first octet of the payload. Since
55 // there is no necessity for payloads to contain padding, don't add any.
57 record
.reserve(plaintext
.size() + 1);
58 record
.append(1, '\0');
59 plaintext
.AppendToString(&record
);
61 std::string encrypted_record
;
62 if (!EncryptDecryptRecordInternal(ENCRYPT
, record
, content_encryption_key
,
63 nonce
, &encrypted_record
)) {
67 // The advertised record size must be at least one more than the padded
68 // plaintext to ensure only one record.
69 *record_size
= std::max(kDefaultRecordSize
, record
.size() + 1);
71 ciphertext
->swap(encrypted_record
);
75 bool GCMMessageCryptographer::Decrypt(
76 const base::StringPiece
& ciphertext
,
77 const base::StringPiece
& key
,
78 const base::StringPiece
& salt
,
80 std::string
* plaintext
) const {
83 if (salt
.size() != kSaltSize
|| record_size
<= 1)
86 // The |ciphertext| must be at least kAuthenticationTagBytes + 1 bytes, which
87 // would be used for an empty message. Per
88 // https://tools.ietf.org/html/draft-thomson-webpush-encryption-01#section-3,
89 // the |record_size| parameter must be large enough to use only one record.
90 if (ciphertext
.size() < kAuthenticationTagBytes
+ 1 ||
91 ciphertext
.size() >= record_size
+ kAuthenticationTagBytes
+ 1) {
95 std::string content_encryption_key
= DeriveContentEncryptionKey(key
, salt
);
96 std::string nonce
= DeriveNonce(key
, salt
);
98 std::string decrypted_record
;
99 if (!EncryptDecryptRecordInternal(DECRYPT
, ciphertext
, content_encryption_key
,
100 nonce
, &decrypted_record
)) {
104 DCHECK(!decrypted_record
.empty());
106 // Records can contain between 0 and 255 octets of padding, indicated by the
107 // first octet of the decrypted message. Padding bytes that are not set to
108 // zero are considered a fatal decryption failure as well. Since AES-GCM
109 // includes an authentication check, neither verification nor removing the
110 // padding have to be done in constant time.
111 size_t padding_length
= static_cast<size_t>(decrypted_record
[0]);
112 if (padding_length
>= decrypted_record
.size())
115 for (size_t i
= 1; i
<= padding_length
; ++i
) {
116 if (decrypted_record
[i
] != 0)
120 base::StringPiece
decoded_record_string_piece(decrypted_record
);
121 decoded_record_string_piece
.remove_prefix(1 + padding_length
);
122 decoded_record_string_piece
.CopyToString(plaintext
);
127 std::string
GCMMessageCryptographer::DeriveContentEncryptionKey(
128 const base::StringPiece
& key
,
129 const base::StringPiece
& salt
) const {
130 crypto::HKDF
hkdf(key
, salt
,
131 "Content-Encoding: aesgcm128",
132 kContentEncryptionKeySize
,
133 0, /* iv_bytes_to_generate */
134 0 /* subkey_secret_bytes_to_generate */);
136 return hkdf
.client_write_key().as_string();
139 std::string
GCMMessageCryptographer::DeriveNonce(
140 const base::StringPiece
& key
,
141 const base::StringPiece
& salt
) const {
142 crypto::HKDF
hkdf(key
, salt
,
143 "Content-Encoding: nonce",
145 0, /* iv_bytes_to_generate */
146 0 /* subkey_secret_bytes_to_generate */);
148 // draft-thomson-http-encryption-01 defines that the result should be XOR'ed
149 // with the record's sequence number, but because Web Push encryption is
150 // limited to a single record we do not have to do that.
152 return hkdf
.client_write_key().as_string();