Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / gcm_driver / crypto / gcm_message_cryptographer.cc
blob62b7247d41da5cffd26aa270cfa61a8bd9d0511c
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"
7 #include <algorithm>
9 #include "base/big_endian.h"
10 #include "base/logging.h"
11 #include "crypto/hkdf.h"
13 namespace gcm {
14 namespace {
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;
30 } // namespace
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,
41 size_t* record_size,
42 std::string* ciphertext) const {
43 DCHECK(ciphertext);
44 DCHECK(record_size);
46 if (salt.size() != kSaltSize)
47 return false;
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.
56 std::string record;
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)) {
64 return false;
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);
72 return true;
75 bool GCMMessageCryptographer::Decrypt(
76 const base::StringPiece& ciphertext,
77 const base::StringPiece& key,
78 const base::StringPiece& salt,
79 size_t record_size,
80 std::string* plaintext) const {
81 DCHECK(plaintext);
83 if (salt.size() != kSaltSize || record_size <= 1)
84 return false;
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) {
92 return false;
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)) {
101 return false;
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())
113 return false;
115 for (size_t i = 1; i <= padding_length; ++i) {
116 if (decrypted_record[i] != 0)
117 return false;
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);
124 return true;
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",
144 kNonceSize,
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();
155 } // namespace gcm