/src/boringssl/crypto/cipher/e_chacha20poly1305.cc
Line | Count | Source |
1 | | // Copyright 2014 The BoringSSL Authors |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // https://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | #include <openssl/aead.h> |
16 | | |
17 | | #include <assert.h> |
18 | | #include <string.h> |
19 | | |
20 | | #include <openssl/chacha.h> |
21 | | #include <openssl/cipher.h> |
22 | | #include <openssl/err.h> |
23 | | #include <openssl/mem.h> |
24 | | #include <openssl/poly1305.h> |
25 | | #include <openssl/span.h> |
26 | | |
27 | | #include "../chacha/internal.h" |
28 | | #include "../fipsmodule/cipher/internal.h" |
29 | | #include "../internal.h" |
30 | | #include "internal.h" |
31 | | |
32 | | using namespace bssl; |
33 | | |
34 | | struct aead_chacha20_poly1305_ctx { |
35 | | uint8_t key[32]; |
36 | | }; |
37 | | |
38 | | static_assert(sizeof(((EVP_AEAD_CTX *)nullptr)->state) >= |
39 | | sizeof(struct aead_chacha20_poly1305_ctx), |
40 | | "AEAD state is too small"); |
41 | | static_assert(alignof(union evp_aead_ctx_st_state) >= |
42 | | alignof(struct aead_chacha20_poly1305_ctx), |
43 | | "AEAD state has insufficient alignment"); |
44 | | |
45 | | static int aead_chacha20_poly1305_init(EVP_AEAD_CTX *ctx, const uint8_t *key, |
46 | 16.6k | size_t key_len, size_t tag_len) { |
47 | 16.6k | struct aead_chacha20_poly1305_ctx *c20_ctx = |
48 | 16.6k | (struct aead_chacha20_poly1305_ctx *)&ctx->state; |
49 | | |
50 | 16.6k | if (tag_len == 0) { |
51 | 16.6k | tag_len = POLY1305_TAG_LEN; |
52 | 16.6k | } |
53 | | |
54 | 16.6k | if (tag_len > POLY1305_TAG_LEN) { |
55 | 0 | OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE); |
56 | 0 | return 0; |
57 | 0 | } |
58 | | |
59 | 16.6k | if (key_len != sizeof(c20_ctx->key)) { |
60 | 0 | return 0; // internal error - EVP_AEAD_CTX_init should catch this. |
61 | 0 | } |
62 | | |
63 | 16.6k | OPENSSL_memcpy(c20_ctx->key, key, key_len); |
64 | 16.6k | ctx->tag_len = tag_len; |
65 | | |
66 | 16.6k | return 1; |
67 | 16.6k | } |
68 | | |
69 | 16.6k | static void aead_chacha20_poly1305_cleanup(EVP_AEAD_CTX *ctx) {} |
70 | | |
71 | 0 | static void poly1305_update_length(poly1305_state *poly1305, size_t data_len) { |
72 | 0 | uint8_t length_bytes[8]; |
73 | |
|
74 | 0 | for (unsigned i = 0; i < sizeof(length_bytes); i++) { |
75 | 0 | length_bytes[i] = data_len; |
76 | 0 | data_len >>= 8; |
77 | 0 | } |
78 | |
|
79 | 0 | CRYPTO_poly1305_update(poly1305, length_bytes, sizeof(length_bytes)); |
80 | 0 | } |
81 | | |
82 | | // calc_tag_pre prepares filling `tag` with the authentication tag for the given |
83 | | // inputs. |
84 | | static size_t calc_tag_pre(poly1305_state *ctx, const uint8_t key[32], |
85 | | const uint8_t nonce[12], |
86 | 0 | Span<const CRYPTO_IVEC> aadvecs) { |
87 | 0 | alignas(16) uint8_t poly1305_key[32]; |
88 | 0 | OPENSSL_memset(poly1305_key, 0, sizeof(poly1305_key)); |
89 | 0 | CRYPTO_chacha_20(poly1305_key, poly1305_key, sizeof(poly1305_key), key, nonce, |
90 | 0 | 0); |
91 | |
|
92 | 0 | static const uint8_t padding[16] = {0}; // Padding is all zeros. |
93 | 0 | CRYPTO_poly1305_init(ctx, poly1305_key); |
94 | 0 | size_t ad_len = 0; |
95 | 0 | for (const CRYPTO_IVEC &aadvec : aadvecs) { |
96 | 0 | CRYPTO_poly1305_update(ctx, aadvec.in, aadvec.len); |
97 | 0 | ad_len += aadvec.len; |
98 | 0 | } |
99 | 0 | if (ad_len % 16 != 0) { |
100 | 0 | CRYPTO_poly1305_update(ctx, padding, sizeof(padding) - (ad_len % 16)); |
101 | 0 | } |
102 | 0 | return ad_len; |
103 | 0 | } |
104 | | |
105 | | static void calc_tag_post(poly1305_state *ctx, uint8_t tag[POLY1305_TAG_LEN], |
106 | 0 | size_t ciphertext_total, size_t ad_len) { |
107 | 0 | static const uint8_t padding[16] = {0}; // Padding is all zeros. |
108 | 0 | if (ciphertext_total % 16 != 0) { |
109 | 0 | CRYPTO_poly1305_update(ctx, padding, |
110 | 0 | sizeof(padding) - (ciphertext_total % 16)); |
111 | 0 | } |
112 | 0 | poly1305_update_length(ctx, ad_len); |
113 | 0 | poly1305_update_length(ctx, ciphertext_total); |
114 | 0 | CRYPTO_poly1305_finish(ctx, tag); |
115 | 0 | } |
116 | | |
117 | | static int chacha20_poly1305_sealv(const uint8_t *key, |
118 | | Span<const CRYPTO_IOVEC> iovecs, |
119 | | Span<uint8_t> out_tag, size_t *out_tag_len, |
120 | | Span<const uint8_t> nonce, |
121 | | Span<const CRYPTO_IVEC> aadvecs, |
122 | 457 | size_t tag_len) { |
123 | 457 | if (out_tag.size() < tag_len) { |
124 | 0 | OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL); |
125 | 0 | return 0; |
126 | 0 | } |
127 | 457 | if (nonce.size() != 12) { |
128 | 0 | OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE); |
129 | 0 | return 0; |
130 | 0 | } |
131 | | |
132 | | // `CRYPTO_chacha_20` uses a 32-bit block counter. Therefore we disallow |
133 | | // individual operations that work on more than 256GB at a time. |
134 | | // `in_len_64` is needed because, on 32-bit platforms, size_t is only |
135 | | // 32-bits and this produces a warning because it's always false. |
136 | | // Casting to uint64_t inside the conditional is not sufficient to stop |
137 | | // the warning. |
138 | 457 | const uint64_t in_len_64 = bssl::iovec::TotalLength(iovecs); |
139 | 457 | if (in_len_64 >= (UINT64_C(1) << 32) * 64 - 64) { |
140 | 0 | OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE); |
141 | 0 | return 0; |
142 | 0 | } |
143 | | |
144 | 457 | union chacha20_poly1305_seal_data data; |
145 | 457 | if (chacha20_poly1305_asm_capable() && iovecs.size() <= 2 && |
146 | 457 | aadvecs.size() <= 1) { |
147 | 457 | OPENSSL_memcpy(data.in.key, key, 32); |
148 | 457 | data.in.counter = 0; |
149 | 457 | CopySpan(nonce, data.in.nonce); |
150 | 457 | if (iovecs.size() >= 2) { |
151 | | // `chacha20_poly1305_seal` only supports one extra input and expects it |
152 | | // to have been encrypted ahead of time. (Historically it was only used |
153 | | // for very short inputs.) |
154 | 340 | constexpr size_t kChaChaBlockSize = 64; |
155 | 340 | uint32_t block_counter = |
156 | 340 | (uint32_t)(1 + (iovecs[0].len / kChaChaBlockSize)); |
157 | 340 | size_t offset = iovecs[0].len % kChaChaBlockSize; |
158 | 340 | size_t done = 0; |
159 | 340 | if (offset != 0) { |
160 | 340 | uint8_t block[kChaChaBlockSize]; |
161 | 340 | memset(block, 0, sizeof(block)); |
162 | 340 | CRYPTO_chacha_20(block, block, sizeof(block), key, nonce.data(), |
163 | 340 | block_counter); |
164 | 680 | for (size_t i = offset; i < sizeof(block) && done < iovecs[1].len; |
165 | 340 | i++, done++) { |
166 | 340 | iovecs[1].out[done] = iovecs[1].in[done] ^ block[i]; |
167 | 340 | } |
168 | 340 | ++block_counter; |
169 | 340 | } |
170 | 340 | if (done < iovecs[1].len) { |
171 | 0 | CRYPTO_chacha_20(iovecs[1].out + done, iovecs[1].in + done, |
172 | 0 | iovecs[1].len - done, key, nonce.data(), |
173 | 0 | block_counter); |
174 | 0 | } |
175 | | // TODO(crbug.com/473454967): Support more than 1 extra ciphertext. |
176 | 340 | data.in.extra_ciphertext = iovecs[1].out; |
177 | 340 | data.in.extra_ciphertext_len = iovecs[1].len; |
178 | 340 | } else { |
179 | 117 | data.in.extra_ciphertext = nullptr; |
180 | 117 | data.in.extra_ciphertext_len = 0; |
181 | 117 | } |
182 | 457 | chacha20_poly1305_seal(iovecs.size() >= 1 ? iovecs[0].out : nullptr, |
183 | 457 | iovecs.size() >= 1 ? iovecs[0].in : nullptr, |
184 | 457 | iovecs.size() >= 1 ? iovecs[0].len : 0, |
185 | 457 | aadvecs.size() >= 1 ? aadvecs[0].in : nullptr, |
186 | 457 | aadvecs.size() >= 1 ? aadvecs[0].len : 0, &data); |
187 | 457 | } else { |
188 | 0 | poly1305_state ctx; |
189 | 0 | size_t ad_len = calc_tag_pre(&ctx, key, nonce.data(), aadvecs); |
190 | |
|
191 | 0 | size_t ciphertext_total = 0; |
192 | 0 | size_t block = 1; |
193 | 0 | bssl::iovec::ForEachBlockRange<64, /*WriteOut=*/true>( |
194 | 0 | iovecs, |
195 | 0 | [&](const uint8_t *in, uint8_t *out, size_t len) { |
196 | | // TODO(crbug.com/473454967): Maybe just provide asm version of this? |
197 | | // Here, len is always a multiple of 64. |
198 | 0 | CRYPTO_chacha_20(out, in, len, key, nonce.data(), block); |
199 | 0 | CRYPTO_poly1305_update(&ctx, out, len); |
200 | 0 | ciphertext_total += len; |
201 | 0 | block += len / 64; |
202 | 0 | return true; |
203 | 0 | }, |
204 | 0 | [&](const uint8_t *in, uint8_t *out, size_t len) { |
205 | | // Here, len may be anything. If an asm version can't handle that, |
206 | | // it will be worth splitting off multiples of 64 here. |
207 | 0 | CRYPTO_chacha_20(out, in, len, key, nonce.data(), block); |
208 | 0 | CRYPTO_poly1305_update(&ctx, out, len); |
209 | 0 | ciphertext_total += len; |
210 | 0 | return true; |
211 | 0 | }); |
212 | |
|
213 | 0 | calc_tag_post(&ctx, data.out.tag, ciphertext_total, ad_len); |
214 | 0 | } |
215 | | |
216 | 457 | CopyToPrefix(Span(data.out.tag).first(tag_len), out_tag); |
217 | 457 | *out_tag_len = tag_len; |
218 | 457 | return 1; |
219 | 457 | } |
220 | | |
221 | | static int aead_chacha20_poly1305_sealv(const EVP_AEAD_CTX *ctx, |
222 | | Span<const CRYPTO_IOVEC> iovecs, |
223 | | Span<uint8_t> out_tag, |
224 | | size_t *out_tag_len, |
225 | | Span<const uint8_t> nonce, |
226 | 457 | Span<const CRYPTO_IVEC> aadvecs) { |
227 | 457 | const struct aead_chacha20_poly1305_ctx *c20_ctx = |
228 | 457 | (struct aead_chacha20_poly1305_ctx *)&ctx->state; |
229 | | |
230 | 457 | return chacha20_poly1305_sealv(c20_ctx->key, iovecs, out_tag, out_tag_len, |
231 | 457 | nonce, aadvecs, ctx->tag_len); |
232 | 457 | } |
233 | | |
234 | | static int aead_xchacha20_poly1305_sealv(const EVP_AEAD_CTX *ctx, |
235 | | Span<const CRYPTO_IOVEC> iovecs, |
236 | | Span<uint8_t> out_tag, |
237 | | size_t *out_tag_len, |
238 | | Span<const uint8_t> nonce, |
239 | 0 | Span<const CRYPTO_IVEC> aadvecs) { |
240 | 0 | const struct aead_chacha20_poly1305_ctx *c20_ctx = |
241 | 0 | (struct aead_chacha20_poly1305_ctx *)&ctx->state; |
242 | |
|
243 | 0 | if (nonce.size() != 24) { |
244 | 0 | OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE); |
245 | 0 | return 0; |
246 | 0 | } |
247 | | |
248 | 0 | alignas(4) uint8_t derived_key[32]; |
249 | 0 | alignas(4) uint8_t derived_nonce[12]; |
250 | 0 | CRYPTO_hchacha20(derived_key, c20_ctx->key, nonce.data()); |
251 | 0 | OPENSSL_memset(derived_nonce, 0, 4); |
252 | 0 | OPENSSL_memcpy(&derived_nonce[4], &nonce[16], 8); |
253 | |
|
254 | 0 | return chacha20_poly1305_sealv(derived_key, iovecs, out_tag, out_tag_len, |
255 | 0 | derived_nonce, aadvecs, ctx->tag_len); |
256 | 0 | } |
257 | | |
258 | | static int chacha20_poly1305_openv_detached(const uint8_t *key, |
259 | | Span<const CRYPTO_IOVEC> iovecs, |
260 | | Span<const uint8_t> nonce, |
261 | | Span<const uint8_t> in_tag, |
262 | | Span<const CRYPTO_IVEC> aadvecs, |
263 | 782 | size_t tag_len) { |
264 | 782 | if (nonce.size() != 12) { |
265 | 0 | OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE); |
266 | 0 | return 0; |
267 | 0 | } |
268 | | |
269 | 782 | if (in_tag.size() != tag_len) { |
270 | 0 | OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT); |
271 | 0 | return 0; |
272 | 0 | } |
273 | | |
274 | | // `CRYPTO_chacha_20` uses a 32-bit block counter. Therefore we disallow |
275 | | // individual operations that work on more than 256GB at a time. |
276 | | // `in_len_64` is needed because, on 32-bit platforms, size_t is only |
277 | | // 32-bits and this produces a warning because it's always false. |
278 | | // Casting to uint64_t inside the conditional is not sufficient to stop |
279 | | // the warning. |
280 | 782 | const uint64_t in_len_64 = bssl::iovec::TotalLength(iovecs); |
281 | 782 | if (in_len_64 >= (UINT64_C(1) << 32) * 64 - 64) { |
282 | 0 | OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE); |
283 | 0 | return 0; |
284 | 0 | } |
285 | | |
286 | 782 | union chacha20_poly1305_open_data data; |
287 | 782 | if (chacha20_poly1305_asm_capable() && iovecs.size() <= 1 && |
288 | 782 | aadvecs.size() <= 1) { |
289 | | // TODO(crbug.com/473454967): Support more than 1 ciphertext segment. |
290 | 782 | OPENSSL_memcpy(data.in.key, key, 32); |
291 | 782 | data.in.counter = 0; |
292 | 782 | CopySpan(nonce, data.in.nonce); |
293 | 782 | chacha20_poly1305_open(iovecs.size() >= 1 ? iovecs[0].out : nullptr, |
294 | 782 | iovecs.size() >= 1 ? iovecs[0].in : nullptr, |
295 | 782 | iovecs.size() >= 1 ? iovecs[0].len : 0, |
296 | 782 | aadvecs.size() >= 1 ? aadvecs[0].in : nullptr, |
297 | 782 | aadvecs.size() >= 1 ? aadvecs[0].len : 0, &data); |
298 | 782 | } else { |
299 | 0 | poly1305_state ctx; |
300 | 0 | size_t ad_len = calc_tag_pre(&ctx, key, nonce.data(), aadvecs); |
301 | |
|
302 | 0 | size_t ciphertext_total = 0; |
303 | 0 | size_t block = 1; |
304 | 0 | bssl::iovec::ForEachBlockRange<64, /*WriteOut=*/true>( |
305 | 0 | iovecs, |
306 | 0 | [&](const uint8_t *in, uint8_t *out, size_t len) { |
307 | | // TODO(crbug.com/473454967): Maybe just provide asm version of this? |
308 | | // Here, len is always a multiple of 64. |
309 | 0 | CRYPTO_poly1305_update(&ctx, in, len); |
310 | 0 | CRYPTO_chacha_20(out, in, len, key, nonce.data(), block); |
311 | 0 | ciphertext_total += len; |
312 | 0 | block += len / 64; |
313 | 0 | return true; |
314 | 0 | }, |
315 | 0 | [&](const uint8_t *in, uint8_t *out, size_t len) { |
316 | | // Here, len may be anything. If an asm version can't handle that, |
317 | | // it will be worth splitting off multiples of 64 here. |
318 | 0 | CRYPTO_poly1305_update(&ctx, in, len); |
319 | 0 | CRYPTO_chacha_20(out, in, len, key, nonce.data(), block); |
320 | 0 | ciphertext_total += len; |
321 | 0 | return true; |
322 | 0 | }); |
323 | |
|
324 | 0 | calc_tag_post(&ctx, data.out.tag, ciphertext_total, ad_len); |
325 | 0 | } |
326 | | |
327 | 782 | if (CRYPTO_memcmp(data.out.tag, in_tag.data(), tag_len) != 0) { |
328 | 403 | OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT); |
329 | 403 | return 0; |
330 | 403 | } |
331 | | |
332 | 379 | return 1; |
333 | 782 | } |
334 | | |
335 | | static int aead_chacha20_poly1305_openv_detached( |
336 | | const EVP_AEAD_CTX *ctx, Span<const CRYPTO_IOVEC> iovecs, |
337 | | Span<const uint8_t> nonce, Span<const uint8_t> in_tag, |
338 | 782 | Span<const CRYPTO_IVEC> aadvecs) { |
339 | 782 | const struct aead_chacha20_poly1305_ctx *c20_ctx = |
340 | 782 | (struct aead_chacha20_poly1305_ctx *)&ctx->state; |
341 | | |
342 | 782 | return chacha20_poly1305_openv_detached(c20_ctx->key, iovecs, nonce, in_tag, |
343 | 782 | aadvecs, ctx->tag_len); |
344 | 782 | } |
345 | | |
346 | | static int aead_xchacha20_poly1305_openv_detached( |
347 | | const EVP_AEAD_CTX *ctx, Span<const CRYPTO_IOVEC> iovecs, |
348 | | Span<const uint8_t> nonce, Span<const uint8_t> in_tag, |
349 | 0 | Span<const CRYPTO_IVEC> aadvecs) { |
350 | 0 | const struct aead_chacha20_poly1305_ctx *c20_ctx = |
351 | 0 | (struct aead_chacha20_poly1305_ctx *)&ctx->state; |
352 | |
|
353 | 0 | if (nonce.size() != 24) { |
354 | 0 | OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE); |
355 | 0 | return 0; |
356 | 0 | } |
357 | | |
358 | 0 | alignas(4) uint8_t derived_key[32]; |
359 | 0 | alignas(4) uint8_t derived_nonce[12]; |
360 | 0 | CRYPTO_hchacha20(derived_key, c20_ctx->key, nonce.data()); |
361 | 0 | OPENSSL_memset(derived_nonce, 0, 4); |
362 | 0 | OPENSSL_memcpy(&derived_nonce[4], &nonce[16], 8); |
363 | |
|
364 | 0 | return chacha20_poly1305_openv_detached(derived_key, iovecs, derived_nonce, |
365 | 0 | in_tag, aadvecs, ctx->tag_len); |
366 | 0 | } |
367 | | |
368 | | static const EVP_AEAD aead_chacha20_poly1305 = { |
369 | | 32, // key len |
370 | | 12, // nonce len |
371 | | POLY1305_TAG_LEN, // overhead |
372 | | POLY1305_TAG_LEN, // max tag length |
373 | | |
374 | | aead_chacha20_poly1305_init, |
375 | | nullptr, // init_with_direction |
376 | | aead_chacha20_poly1305_cleanup, |
377 | | nullptr, // openv |
378 | | aead_chacha20_poly1305_sealv, |
379 | | aead_chacha20_poly1305_openv_detached, |
380 | | nullptr, // get_iv |
381 | | nullptr, // tag_len |
382 | | }; |
383 | | |
384 | | static const EVP_AEAD aead_xchacha20_poly1305 = { |
385 | | 32, // key len |
386 | | 24, // nonce len |
387 | | POLY1305_TAG_LEN, // overhead |
388 | | POLY1305_TAG_LEN, // max tag length |
389 | | |
390 | | aead_chacha20_poly1305_init, |
391 | | nullptr, // init_with_direction |
392 | | aead_chacha20_poly1305_cleanup, |
393 | | nullptr, // openv |
394 | | aead_xchacha20_poly1305_sealv, |
395 | | aead_xchacha20_poly1305_openv_detached, |
396 | | nullptr, // get_iv |
397 | | nullptr, // tag_len |
398 | | }; |
399 | | |
400 | 33.1k | const EVP_AEAD *EVP_aead_chacha20_poly1305() { return &aead_chacha20_poly1305; } |
401 | | |
402 | 0 | const EVP_AEAD *EVP_aead_xchacha20_poly1305() { |
403 | 0 | return &aead_xchacha20_poly1305; |
404 | 0 | } |