Newer
Older
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
#include "tink/subtle/aes_eax_boringssl.h"
#include <string>
#include <vector>
#include <memory>
#include "openssl/err.h"
#include "openssl/evp.h"
#include "tink/aead.h"
#include "tink/subtle/random.h"
#include "tink/subtle/subtle_util_boringssl.h"
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#include "tink/util/errors.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
namespace crypto {
namespace tink {
namespace subtle {
static const int BLOCK_SIZE = 16;
namespace {
// TODO(bleichen): There has to be a way to implement
// the following routines fast. E.g. Clang 6.0.0 optimizes
// Load64, Store64, BigendianLoad64, but does not optimize
// BigEndianStore64.
// Loads and stores 8 bytes. The endianness of the two routines
// does not matter, as long as the two routines use the same order.
uint64_t Load64(const uint8_t src[8]) {
uint64_t res;
memmove(&res, src, 8);
return res;
}
void Store64(uint8_t dst[8], uint64_t val) {
memmove(dst, &val, 8);
}
uint64_t BigEndianLoad64(const uint8_t src[8]) {
return static_cast<uint64_t>(src[7])
| (static_cast<uint64_t>(src[6]) << 8)
| (static_cast<uint64_t>(src[5]) << 16)
| (static_cast<uint64_t>(src[4]) << 24)
| (static_cast<uint64_t>(src[3]) << 32)
| (static_cast<uint64_t>(src[2]) << 40)
| (static_cast<uint64_t>(src[1]) << 48)
| (static_cast<uint64_t>(src[0]) << 56);
}
void BigEndianStore64(uint8_t dst[8], uint64_t val) {
dst[0] = (val >> 56) & 0xff;
dst[1] = (val >> 48) & 0xff;
dst[2] = (val >> 40) & 0xff;
dst[3] = (val >> 32) & 0xff;
dst[4] = (val >> 24) & 0xff;
dst[5] = (val >> 16) & 0xff;
dst[6] = (val >> 8) & 0xff;
dst[7] = val & 0xff;
}
void XorBlock(const uint8_t x[BLOCK_SIZE],
const uint8_t y[BLOCK_SIZE],
uint8_t res[BLOCK_SIZE]) {
uint64_t r0 = Load64(x) ^ Load64(y);
uint64_t r1 = Load64(x + 8) ^ Load64(y + 8);
Store64(res, r0);
Store64(res + 8, r1);
}
void MultiplyByX(const uint8_t in[BLOCK_SIZE],
uint8_t out[BLOCK_SIZE]) {
uint64_t in_high = BigEndianLoad64(in);
uint64_t in_low = BigEndianLoad64(in + 8);
uint64_t out_high = (in_high << 1) ^ (in_low >> 63);
// If the most significant bit is set then the result has to
// be reduced by x^128 + x^7 + x^4 + x^2 + x + 1.
// The representation of x^7 + x^4 + x^2 + x + 1 is 0x87.
uint64_t out_low = (in_low << 1) ^ (in_high >> 63 ? 0x87 : 0);
BigEndianStore64(out, out_high);
BigEndianStore64(out + 8, out_low);
}
bool EqualBlocks(const uint8_t x[BLOCK_SIZE],
const uint8_t y[BLOCK_SIZE]) {
uint64_t res = Load64(x) ^ Load64(y);
res |= Load64(x + 8) ^ Load64(y + 8);
return res == 0;
}
} // namespace
bool AesEaxBoringSsl::IsValidKeySize(size_t key_size_in_bytes) {
return key_size_in_bytes == 16 ||
key_size_in_bytes == 32;
}
bool AesEaxBoringSsl::IsValidNonceSize(size_t nonce_size_in_bytes) {
return nonce_size_in_bytes == 12 ||
nonce_size_in_bytes == 16;
}
AesEaxBoringSsl::AesEaxBoringSsl(
absl::string_view key_value, size_t nonce_size)
: nonce_size_(nonce_size) {
int status = AES_set_encrypt_key(
reinterpret_cast<const uint8_t*>(key_value.data()), key_value.size() * 8,
&aeskey_);
// status != 0 happens if key_value or aeskey_ is invalid. In both cases
// this indicates a programming error.
if (status != 0) {
is_initialized_ = false;
return;
}
uint8_t block[BLOCK_SIZE];
memset(block, 0, BLOCK_SIZE);
EncryptBlock(block, block);
MultiplyByX(block, B_);
MultiplyByX(B_, P_);
is_initialized_ = true;
}
crypto::tink::util::StatusOr<std::unique_ptr<Aead>> AesEaxBoringSsl::New(
absl::string_view key_value,
size_t nonce_size_in_bytes) {
if (!IsValidKeySize(key_value.size())) {
return util::Status(util::error::INTERNAL, "Invalid key");
}
if (!IsValidNonceSize(nonce_size_in_bytes)) {
return util::Status(util::error::INTERNAL, "Invalid nonce size");
}
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
std::unique_ptr<AesEaxBoringSsl> aead(
new AesEaxBoringSsl(key_value, nonce_size_in_bytes));
if (!aead->is_initialized_) {
return util::Status(util::error::INTERNAL,
"Could not initialize AesEaxBoringSsl");
}
return std::unique_ptr<Aead>(aead.release());
}
void AesEaxBoringSsl::Pad(const uint8_t* data, int len,
uint8_t padded_block[BLOCK_SIZE]) const {
// TODO(bleichen): What are we using in tink to encode assertions?
// The caller must ensure that data is no longer than a block.
// CHECK(0 <= len && len <= BLOCK_SIZE) << "Invalid data size";
memset(padded_block, 0, BLOCK_SIZE);
memmove(padded_block, data, len);
if (len == BLOCK_SIZE) {
XorBlock(padded_block, B_, padded_block);
} else {
padded_block[len] = 0x80;
XorBlock(padded_block, P_, padded_block);
}
}
void AesEaxBoringSsl::EncryptBlock(const uint8_t in[BLOCK_SIZE],
uint8_t out[BLOCK_SIZE]) const {
AES_encrypt(in, out, &aeskey_);
}
void AesEaxBoringSsl::Omac(
absl::string_view blob,
int tag,
uint8_t mac[BLOCK_SIZE]) const {
Omac(reinterpret_cast<const uint8_t *>(blob.data()), blob.size(), tag, mac);
}
void AesEaxBoringSsl::Omac(const uint8_t* data,
size_t len,
int tag,
uint8_t mac[BLOCK_SIZE]) const {
uint8_t block[BLOCK_SIZE];
memset(block, 0, BLOCK_SIZE);
block[15] = tag;
if (len == 0) {
XorBlock(block, B_, block);
EncryptBlock(block, mac);
return;
}
EncryptBlock(block, block);
int idx = 0;
while (len - idx > BLOCK_SIZE) {
XorBlock(block, &data[idx], block);
EncryptBlock(block, block);
idx += BLOCK_SIZE;
}
uint8_t padded_block[BLOCK_SIZE];
Pad(&data[idx], len - idx, padded_block);
XorBlock(block, padded_block, block);
EncryptBlock(block, mac);
}
void AesEaxBoringSsl::CtrCrypt(
const uint8_t N[BLOCK_SIZE],
const uint8_t *in,
uint8_t *result,
size_t size) const {
// This special case is necessary to avoid problems when in == null.
// in == null is possible since absl::string_view can contain null pointers.
if (size == 0) {
return;
}
// Make a copy of N, since BoringSsl changes ctr.
uint8_t ctr[BLOCK_SIZE];
memcpy(ctr, N, BLOCK_SIZE);
unsigned int num = 0;
uint8_t ecount_buf[BLOCK_SIZE];
memset(ecount_buf, 0, BLOCK_SIZE);
AES_ctr128_encrypt(in, result, size, &aeskey_, ctr, ecount_buf, &num);
}
crypto::tink::util::StatusOr<std::string> AesEaxBoringSsl::Encrypt(
absl::string_view plaintext,
absl::string_view additional_data) const {
// BoringSSL expects a non-null pointer for plaintext and additional_data,
// regardless of whether the size is 0.
plaintext = SubtleUtilBoringSSL::EnsureNonNull(plaintext);
additional_data = SubtleUtilBoringSSL::EnsureNonNull(additional_data);
size_t ciphertext_size = plaintext.size() + nonce_size_ + TAG_SIZE;
std::string ciphertext(ciphertext_size, '\0');
uint8_t N[BLOCK_SIZE];
const std::string nonce = Random::GetRandomBytes(nonce_size_);
Omac(nonce, 0, N);
uint8_t H[BLOCK_SIZE];
Omac(additional_data, 1, H);
uint8_t* ct_start = reinterpret_cast<uint8_t*>(&ciphertext[nonce_size_]);
CtrCrypt(N, reinterpret_cast<const uint8_t*>(plaintext.data()),
ct_start, plaintext.size());
uint8_t mac[BLOCK_SIZE];
Omac(ct_start, plaintext.size(), 2, mac);
XorBlock(mac, N, mac);
XorBlock(mac, H, mac);
memmove(&ciphertext[0], nonce.data(), nonce_size_);
memmove(&ciphertext[ciphertext_size - TAG_SIZE], mac, TAG_SIZE);
return std::move(ciphertext);
}
crypto::tink::util::StatusOr<std::string> AesEaxBoringSsl::Decrypt(
absl::string_view ciphertext,
absl::string_view additional_data) const {
// BoringSSL expects a non-null pointer for additional_data,
// regardless of whether the size is 0.
additional_data = SubtleUtilBoringSSL::EnsureNonNull(additional_data);
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
size_t ct_size = ciphertext.size();
if (ct_size < nonce_size_ + TAG_SIZE) {
return util::Status(util::error::INTERNAL, "Ciphertext too short");
}
size_t out_size = ct_size - TAG_SIZE - nonce_size_;
absl::string_view nonce = ciphertext.substr(0, nonce_size_);
absl::string_view encrypted = ciphertext.substr(nonce_size_, out_size);
absl::string_view tag = ciphertext.substr(ct_size - TAG_SIZE, TAG_SIZE);
uint8_t N[BLOCK_SIZE];
Omac(nonce, 0, N);
uint8_t H[BLOCK_SIZE];
Omac(additional_data, 1, H);
uint8_t mac[BLOCK_SIZE];
Omac(encrypted, 2, mac);
XorBlock(mac, N, mac);
XorBlock(mac, H, mac);
const uint8_t *sig = reinterpret_cast<const uint8_t*>(tag.data());
if (!EqualBlocks(mac, sig)) {
return util::Status(util::error::INTERNAL, "Tag mismatch");
}
std::string res(out_size, '\0');
CtrCrypt(N, reinterpret_cast<const uint8_t*>(encrypted.data()),
reinterpret_cast<uint8_t*>(&res[0]), out_size);
return std::move(res);
}
} // namespace subtle
} // namespace tink
} // namespace crypto