Coverage Report

Created: 2026-06-28 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}