Coverage Report

Created: 2026-11-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wolfssl-normal-math/wolfcrypt/src/chacha20_poly1305.c
Line
Count
Source
1
/* chacha.c
2
 *
3
 * Copyright (C) 2006-2026 wolfSSL Inc.
4
 *
5
 * This file is part of wolfSSL.
6
 *
7
 * wolfSSL is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * wolfSSL is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
20
 */
21
/*
22
23
DESCRIPTION
24
This library contains implementation for the ChaCha20 stream cipher and
25
the Poly1305 authenticator, both as as combined-mode,
26
or Authenticated Encryption with Additional Data (AEAD) algorithm.
27
28
*/
29
30
#include <wolfssl/wolfcrypt/libwolfssl_sources.h>
31
32
#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
33
34
#include <wolfssl/wolfcrypt/chacha20_poly1305.h>
35
#include <wolfssl/wolfcrypt/cpuid.h>
36
37
#ifdef WOLF_CRYPTO_CB
38
    #include <wolfssl/wolfcrypt/cryptocb.h>
39
#endif
40
41
#ifdef NO_INLINE
42
#include <wolfssl/wolfcrypt/misc.h>
43
#else
44
#define WOLFSSL_MISC_INCLUDED
45
#include <wolfcrypt/src/misc.c>
46
#endif
47
48
240
#define CHACHA20_POLY1305_AEAD_INITIAL_COUNTER  0
49
50
#ifdef WOLFSSL_CHACHA20_POLY1305_FUSED
51
/* Fused single-pass encrypt kernel (in chacha_asm.S) and the 4-way power
52
 * precompute it depends on. */
53
#ifdef __cplusplus
54
extern "C" {
55
#endif
56
WOLFSSL_LOCAL void chacha20_poly1305_avx512(ChaCha* chacha, Poly1305* poly,
57
    const byte* m, byte* c, word32 bytes);
58
WOLFSSL_LOCAL void poly1305_calc_powers_avx2(Poly1305* ctx);
59
#ifdef __cplusplus
60
}
61
#endif
62
63
/* The fused kernel uses 4-block ChaCha (256-bit) + 4-way Poly1305, which beats
64
 * the wide two-pass only where 512-bit code is throttled - Intel Ice Lake and
65
 * later, under the AVX-512 frequency license.  On AMD (no throttle, very fast
66
 * wide primitives) the two-pass wins, so gate on an Intel vendor.  Override:
67
 * WOLFSSL_CHACHA20_POLY1305_FUSED_ALWAYS / _NEVER. */
68
static WC_INLINE int chacha20_poly1305_use_fused(void)
69
{
70
#if defined(WOLFSSL_CHACHA20_POLY1305_FUSED_NEVER)
71
    return 0;
72
#elif defined(WOLFSSL_CHACHA20_POLY1305_FUSED_ALWAYS)
73
    return 1;
74
#else
75
    cpuid_flags_t f = cpuid_get_flags();
76
    return (IS_CPU_INTEL(f) != 0) && (IS_INTEL_AVX512(f) != 0);
77
#endif
78
}
79
80
/* Encrypt with the fused kernel: no AAD, so Poly1305 starts clean and 256-byte
81
 * aligned.  Drive Poly1305 4-way (forceAvx2) so the kernel and the tail/final
82
 * share the layout; the kernel does the aligned bulk, the tail and length
83
 * framing go through the normal 4-way path. */
84
static int chacha20_poly1305_encrypt_fused(ChaChaPoly_Aead* aead,
85
    const byte* pt, word32 ptLen, byte* ct, byte* tag)
86
{
87
    word32 bulk = ptLen & ~(word32)0xff;
88
    int ret;
89
90
    aead->poly.forceAvx2 = 1;
91
    /* The cpuid setkey may have zeroed a different accumulator; ready the 4-way
92
     * hash and let the kernel initialise the lanes. */
93
    XMEMSET(aead->poly.hh, 0, sizeof(aead->poly.hh));
94
    aead->poly.started = 0;
95
    aead->poly.leftover = 0;
96
    aead->state = CHACHA20_POLY1305_STATE_DATA;
97
98
    SAVE_VECTOR_REGISTERS(return _svr_ret;);
99
    poly1305_calc_powers_avx2(&aead->poly);
100
    aead->poly.started = 1;
101
    /* bulk is a non-zero multiple of 256 here (caller gates on >= 256), but the
102
     * kernel has no short-length entry guard - it would run a full 256-byte unit
103
     * off the end on a zero length, so never call it with nothing to do. */
104
    if (bulk > 0)
105
        chacha20_poly1305_avx512(&aead->chacha, &aead->poly, pt, ct, bulk);
106
    RESTORE_VECTOR_REGISTERS();
107
108
    aead->dataLen = bulk;
109
    ret = 0;
110
    if (ptLen > bulk)
111
        ret = wc_ChaCha20Poly1305_UpdateData(aead, pt + bulk, ct + bulk,
112
                                             ptLen - bulk);
113
    if (ret == 0)
114
        ret = wc_ChaCha20Poly1305_Final(aead, tag);
115
116
    return ret;
117
}
118
#endif /* WOLFSSL_CHACHA20_POLY1305_FUSED */
119
120
#ifdef WOLFSSL_CHACHA20_POLY1305_FUSED_IFMA
121
/* IFMA stitched single-pass encrypt kernel (in chacha_asm.S): full 512-bit
122
 * 16-block ChaCha interleaved with an 8-way IFMA (vpmadd52) Poly1305 that
123
 * collapses to the scalar hash.  Processes 1024-byte units.  Depends on the
124
 * radix-2^44 powers. */
125
#ifdef __cplusplus
126
extern "C" {
127
#endif
128
WOLFSSL_LOCAL void chacha20_poly1305_ifma(ChaCha* chacha, Poly1305* poly,
129
    const byte* m, byte* c, word32 bytes);
130
/* Decrypt counterpart: hashes the ciphertext INPUT (m) as it decrypts to c
131
 * (in-place safe - m is hashed before it is overwritten).  Same 1024-byte
132
 * units and radix-2^44 powers. */
133
WOLFSSL_LOCAL void chacha20_poly1305_ifma_decrypt(ChaCha* chacha,
134
    Poly1305* poly, const byte* m, byte* c, word32 bytes);
135
WOLFSSL_LOCAL void poly1305_calc_powers_avx512ifma(Poly1305* ctx);
136
/* ctx->h = ctx->hh * r^nBlocks + ctx->h - advances the running hash (saved by
137
 * the kernel to ctx->hh) past a chunk the kernel hashed from zero into ctx->h.
138
 * Radix-2^64 scalar, so no 26<->64 conversions. */
139
WOLFSSL_LOCAL void poly1305_fold_avx512ifma(Poly1305* ctx, word32 nBlocks);
140
#ifdef __cplusplus
141
}
142
#endif
143
144
/* Minimum length to stitch.  The kernel processes 1024-byte units and a
145
 * once-per-op power precompute, and any sub-1024 remainder is authenticated by
146
 * the slower scalar Poly1305; below this the two-pass wins (measured crossover
147
 * on Zen5).  Above it the stitch wins 1.1-1.4x, growing with size.
148
 *
149
 * HARD LOWER BOUND 1024: callers gate on this then pass bulk = sz & ~0x3ff to
150
 * the kernel, and the kernel has NO short-length entry guard - its first length
151
 * test runs only AFTER a full 1024-byte chunk.  A value below 1024 lets a
152
 * sub-1024 message compute bulk == 0, and the kernel then reads/writes a whole
153
 * 1024-byte unit off the end of the buffer and underflows its counter into a
154
 * multi-million-iteration loop.  Enforced at compile time below; stitch_chunk()
155
 * also guards bulk == 0 at runtime as defence in depth. */
156
#ifndef CHACHA20_POLY1305_STITCH_MIN
157
#define CHACHA20_POLY1305_STITCH_MIN 4096
158
#endif
159
#if CHACHA20_POLY1305_STITCH_MIN < 1024
160
#error "CHACHA20_POLY1305_STITCH_MIN must be >= 1024 (the kernel unit size)"
161
#endif
162
163
/* Short-message fused path: for messages this small the Poly1305 key block
164
 * (ChaCha counter 0) and the whole ciphertext (counter 1+) fit in a single
165
 * ChaCha keystream generation - one pass instead of two - and a scalar
166
 * Poly1305 avoids the vector power-precompute cost.  Needs the forceScalar
167
 * flag (same builds as the fused kernels).  Measured 1.1-1.5x on Zen5 for
168
 * 64-192 byte records; above SHORT_MAX the poly key spills to a second ChaCha
169
 * chunk and the saving is gone. */
170
#if defined(WOLFSSL_CHACHA20_POLY1305_FUSED_IFMA) && \
171
    !defined(WOLFSSL_NO_CHACHA20_POLY1305_SHORT)
172
    #define WOLFSSL_CHACHA20_POLY1305_SHORT
173
    #ifndef CHACHA20_POLY1305_SHORT_MAX
174
        /* 64 (poly-key block) + 192 = 256 = one AVX-512VL 4-block chunk */
175
        #define CHACHA20_POLY1305_SHORT_MAX 192
176
    #endif
177
#endif
178
179
/* The IFMA stitch runs a full-width 512-bit 16-block ChaCha interleaved with an
180
 * 8-way IFMA Poly1305: ChaCha is the bottleneck and Poly hides under it, so it
181
 * beats the two-pass (which runs the two passes back to back) by ~1.3-1.4x at
182
 * >=16KB - measured on AMD Zen5, and expected wherever AVX-512 + IFMA exist
183
 * (both use 512-bit ChaCha, so any frequency throttle hits both equally).  Gate
184
 * on AVX-512 + IFMA, any vendor.  Override: ..._FUSED_IFMA_ALWAYS / _NEVER. */
185
static WC_INLINE int chacha20_poly1305_use_fused_ifma(void)
186
{
187
#if defined(WOLFSSL_CHACHA20_POLY1305_FUSED_IFMA_NEVER)
188
    return 0;
189
#elif defined(WOLFSSL_CHACHA20_POLY1305_FUSED_IFMA_ALWAYS)
190
    return 1;
191
#else
192
    cpuid_flags_t f = cpuid_get_flags();
193
    return (IS_INTEL_AVX512(f) != 0) && (IS_INTEL_AVX512_IFMA(f) != 0);
194
#endif
195
}
196
197
/* Stitch one 1024-byte-aligned bulk.  (IFMA path) The kernel hashes this
198
 * chunk's ciphertext from zero (leaving ctx->h = H_chunk) and advances the
199
 * ChaCha counter, saving the running hash (the AAD, or previous chunks) to
200
 * ctx->hh; poly1305_fold_avx512ifma then advances that hash past this chunk
201
 * (ctx->h = ctx->hh * r^nBlocks + H_chunk).  Powers are computed once (started
202
 * flag).  Caller must have ctx->h = running hash, leftover == 0, forceScalar
203
 * and finished set, and the ChaCha counter placed for this chunk.  decrypt: in
204
 * is ciphertext, out is plaintext (in-place safe - the kernel hashes in before
205
 * overwriting it); the hash math is identical. */
206
static int chacha20_poly1305_stitch_chunk(ChaCha* chacha, Poly1305* poly,
207
    const byte* in, byte* out, word32 bulk, int decrypt)
208
{
209
    int fold;
210
211
    /* The kernel has no short-length entry guard and would run a full 1024-byte
212
     * unit off the end of the buffer on a zero length.  bulk is always a
213
     * non-zero multiple of 1024 here (STITCH_MIN >= 1024, enforced at compile
214
     * time), but never invoke the kernel with nothing to do. */
215
    if (bulk == 0)
216
        return 0;
217
218
    /* A running hash (AAD or previous chunks) must be folded past this chunk;
219
     * detect it before the kernel overwrites poly->h with this chunk's hash. */
220
    fold = (poly->h[0] | poly->h[1] | poly->h[2]) != 0;
221
222
    SAVE_VECTOR_REGISTERS(return _svr_ret;);
223
    if (!poly->started) {
224
        poly1305_calc_powers_avx512ifma(poly);
225
        poly->started = 1;
226
    }
227
    if (decrypt)
228
        chacha20_poly1305_ifma_decrypt(chacha, poly, in, out, bulk);
229
    else
230
        chacha20_poly1305_ifma(chacha, poly, in, out, bulk);
231
    RESTORE_VECTOR_REGISTERS();
232
233
    /* poly->h = H_chunk, poly->hh = running hash (scalar fold, no vectors). */
234
    if (fold)
235
        poly1305_fold_avx512ifma(poly, bulk / 16);
236
    return 0;
237
}
238
239
/* Encrypt the whole message with the IFMA stitch (one-shot path).  AAD is
240
 * hashed scalar into ctx->h, the 1024-aligned bulk is stitched (folding AAD
241
 * through it), the tail + length framing go through the scalar path.
242
 * forceScalar/finished: see the AVX2 fused note (setkey_avx2 leaves finished
243
 * clear).  Both this and the streaming UpdateData path share stitch_chunk(). */
244
static int chacha20_poly1305_encrypt_fused_ifma(ChaChaPoly_Aead* aead,
245
    const byte* aad, word32 aadLen, const byte* pt, word32 ptLen, byte* ct,
246
    byte* tag)
247
{
248
    word32 bulk = ptLen & ~(word32)0x3ff;
249
    int ret = 0;
250
251
    aead->poly.forceScalar = 1;
252
    XMEMSET(aead->poly.h, 0, sizeof(aead->poly.h));
253
    aead->poly.finished = 1;
254
    aead->poly.leftover = 0;
255
    aead->poly.started = 0;
256
257
    /* Hash AAD + pad1 (scalar) -> H_aad in ctx->h. */
258
    if (aadLen > 0) {
259
        ret = wc_Poly1305Update(&aead->poly, aad, aadLen);
260
        if (ret == 0)
261
            ret = wc_Poly1305_Pad(&aead->poly, aadLen);
262
    }
263
    aead->aadLen = aadLen;
264
    aead->state = CHACHA20_POLY1305_STATE_DATA;
265
266
    if (ret == 0)
267
        ret = chacha20_poly1305_stitch_chunk(&aead->chacha, &aead->poly, pt,
268
                                             ct, bulk, 0);
269
    if (ret == 0) {
270
        aead->dataLen = bulk;
271
        if (ptLen > bulk)
272
            ret = wc_ChaCha20Poly1305_UpdateData(aead, pt + bulk, ct + bulk,
273
                                                 ptLen - bulk);
274
        if (ret == 0)
275
            ret = wc_ChaCha20Poly1305_Final(aead, tag);
276
    }
277
    return ret;
278
}
279
#endif /* WOLFSSL_CHACHA20_POLY1305_FUSED_IFMA */
280
281
#ifdef WOLFSSL_CHACHA20_POLY1305_SHORT
282
283
/* small_enc/small_dec are HAVE_INTEL_AVX2 kernels in chacha_asm.S; gate on
284
 * NO_AVX2_SUPPORT so a -DNO_AVX2_SUPPORT build does not reference them. */
285
#if defined(USE_INTEL_SPEEDUP) && defined(WOLFSSL_X86_64_BUILD) && \
286
    !defined(WOLFSSL_NO_CHACHA20_POLY1305_SMALL_ASM) && \
287
    !defined(NO_AVX2_SUPPORT)
288
#define WOLFSSL_CP_SMALL_ASM
289
290
/* Fused single-call ChaCha20-Poly1305 encrypt for a one-block (<=64 byte)
291
 * record (in chacha_asm.S): SSSE3 crypt2 produces the Poly1305 key block and
292
 * the single data block together; the scalar poly1305_*_avx do the MAC. */
293
#ifdef __cplusplus
294
extern "C" {
295
#endif
296
WOLFSSL_LOCAL void chacha20_poly1305_small_enc(ChaCha* chacha, Poly1305* poly,
297
    const byte* m, byte* c, word32 mLen, const byte* aad, word32 aadLen,
298
    byte* tag);
299
/* Decrypt twin: decrypts in->out AND verifies the tag in one pass (decrypt-
300
 * then-verify).  Constant-time-compares the computed tag against the received
301
 * tag internally and returns 0 on match, 1 on mismatch; the caller ForceZeros
302
 * the output on mismatch, so no plaintext is released on a bad tag. */
303
WOLFSSL_LOCAL int chacha20_poly1305_small_dec(ChaCha* chacha, Poly1305* poly,
304
    const byte* in, byte* out, word32 ctLen, const byte* aad, word32 aadLen,
305
    const byte* tag);
306
#ifdef __cplusplus
307
}
308
#endif
309
310
static WC_INLINE int chacha20_poly1305_use_small(void)
311
{
312
    return IS_INTEL_AVX2(cpuid_get_flags()) != 0;
313
}
314
#endif
315
316
/* Fused short-message (sz <= CHACHA20_POLY1305_SHORT_MAX) encrypt for the
317
 * pre-keyed contexts: derive the Poly1305 key (counter 0) and the encryption
318
 * keystream (counter 1+) in a SINGLE ChaCha pass, then scalar-hash.  Saves the
319
 * second ChaCha invocation the two-pass path would make. */
320
static int chacha20_poly1305_encrypt_short(ChaCha* chacha, Poly1305* poly,
321
    byte* out, const byte* in, word32 sz, const byte* nonce, byte* tag,
322
    const byte* aad, word32 aadSz)
323
{
324
    byte ks[64 + CHACHA20_POLY1305_SHORT_MAX];
325
    int  ret;
326
327
#ifdef WOLFSSL_CP_SMALL_ASM
328
    /* One block or less of data: the fused single-call kernel. */
329
    if (sz <= 64 && chacha20_poly1305_use_small()) {
330
        ret = wc_Chacha_SetIV(chacha, nonce,
331
                              CHACHA20_POLY1305_AEAD_INITIAL_COUNTER);
332
        if (ret == 0) {
333
            SAVE_VECTOR_REGISTERS(return _svr_ret;);
334
            chacha20_poly1305_small_enc(chacha, poly, in, out, sz, aad, aadSz,
335
                                        tag);
336
            RESTORE_VECTOR_REGISTERS();
337
        }
338
        return ret;
339
    }
340
#endif
341
342
    XMEMSET(ks, 0, 64 + sz);
343
    ret = wc_Chacha_SetIV(chacha, nonce,
344
                          CHACHA20_POLY1305_AEAD_INITIAL_COUNTER);
345
    if (ret == 0)                       /* ctr0 (poly key) .. ctrN, one pass */
346
        ret = wc_Chacha_Process(chacha, ks, ks, 64 + sz);
347
    if (ret == 0)
348
        ret = wc_Poly1305SetKey(poly, ks, CHACHA20_POLY1305_AEAD_KEYSIZE);
349
    if (ret == 0) {
350
        xorbufout(out, in, ks + 64, sz);   /* ct = pt ^ keystream (ctr1+) */
351
        poly->forceScalar = 1;
352
        poly->finished = 1;
353
        ret = wc_Poly1305_MAC(poly, aad, aadSz, out, sz, tag,
354
                              CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE);
355
    }
356
    ForceZero(ks, 64 + sz);                /* ks[0:32] was the poly key */
357
    return ret;
358
}
359
360
/* Fused short-message decrypt twin: derive key + keystream in one ChaCha pass,
361
 * MAC the ciphertext INPUT and verify the tag BEFORE decrypting, so no
362
 * plaintext is produced on a bad tag (stronger than the stitch, cheap here
363
 * because the message is small).  In-place safe. */
364
static int chacha20_poly1305_decrypt_short(ChaCha* chacha, Poly1305* poly,
365
    byte* out, const byte* in, word32 sz, const byte* nonce, const byte* tag,
366
    const byte* aad, word32 aadSz)
367
{
368
    byte ks[64 + CHACHA20_POLY1305_SHORT_MAX];
369
    byte calcTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
370
    int  ret;
371
372
#ifdef WOLFSSL_CP_SMALL_ASM
373
    /* One block or less of data: the fused single-call kernel decrypts in->out
374
     * and computes calcTag in one pass (decrypt-then-verify).  Zero the output
375
     * if the tag is bad, so no plaintext is released. */
376
    if (sz <= 64 && chacha20_poly1305_use_small()) {
377
        ret = wc_Chacha_SetIV(chacha, nonce,
378
                              CHACHA20_POLY1305_AEAD_INITIAL_COUNTER);
379
        if (ret == 0) {
380
            int bad;
381
            SAVE_VECTOR_REGISTERS(return _svr_ret;);
382
            bad = chacha20_poly1305_small_dec(chacha, poly, in, out, sz, aad,
383
                                              aadSz, tag);
384
            RESTORE_VECTOR_REGISTERS();
385
            if (bad) {                         /* bad tag: no plaintext */
386
                if (sz > 0)
387
                    ForceZero(out, sz);
388
                ret = MAC_CMP_FAILED_E;
389
            }
390
        }
391
        (void)calcTag;
392
        return ret;
393
    }
394
#endif
395
396
    XMEMSET(ks, 0, 64 + sz);
397
    ret = wc_Chacha_SetIV(chacha, nonce,
398
                          CHACHA20_POLY1305_AEAD_INITIAL_COUNTER);
399
    if (ret == 0)
400
        ret = wc_Chacha_Process(chacha, ks, ks, 64 + sz);
401
    if (ret == 0)
402
        ret = wc_Poly1305SetKey(poly, ks, CHACHA20_POLY1305_AEAD_KEYSIZE);
403
    if (ret == 0) {
404
        poly->forceScalar = 1;
405
        poly->finished = 1;
406
        ret = wc_Poly1305_MAC(poly, aad, aadSz, in, sz, calcTag,
407
                              CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE);
408
    }
409
    if (ret == 0)
410
        ret = wc_ChaCha20Poly1305_CheckTag(tag, calcTag);
411
    if (ret == 0)                          /* tag good: decrypt pt = ct ^ ks */
412
        xorbufout(out, in, ks + 64, sz);
413
    else if (sz > 0)                       /* bad tag/error: no stale output */
414
        ForceZero(out, sz);
415
    ForceZero(ks, 64 + sz);
416
    return ret;
417
}
418
#endif /* WOLFSSL_CHACHA20_POLY1305_SHORT */
419
420
/* Encrypt + authenticate one message with PRE-KEYED ChaCha20 and Poly1305
421
 * contexts - the ChaCha20-Poly1305 analogue of wc_AesGcmEncrypt on a keyed Aes.
422
 * Intended for the TLS record layer, which keeps the ChaCha context keyed once
423
 * (per traffic key) and only varies the nonce per record.  The per-record
424
 * Poly1305 key is derived here from the ChaCha keystream.  Uses the single-pass
425
 * IFMA stitch when beneficial, else the two-pass; identical output either way.
426
 *
427
 *   chacha  ChaCha20 context with the key already set (wc_Chacha_SetKey)
428
 *   poly    Poly1305 scratch context (re-keyed here every call)
429
 *   out     ciphertext out (may alias in)
430
 *   in/sz   plaintext / length
431
 *   nonce   CHACHA20_POLY1305_AEAD_IV_SIZE (12) byte record nonce
432
 *   tag     CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE (16) byte tag out
433
 *   aad/aadSz  additional authenticated data
434
 * returns 0 on success, negative on error.
435
 */
436
WOLFSSL_API int wc_ChaCha20Poly1305_Encrypt_ex(ChaCha* chacha, Poly1305* poly,
437
    byte* out, const byte* in, word32 sz, const byte* nonce, byte* tag,
438
    const byte* aad, word32 aadSz)
439
95
{
440
95
    byte polyKey[CHACHA20_POLY1305_AEAD_KEYSIZE];
441
95
    int  ret;
442
443
95
    if (chacha == NULL || poly == NULL || nonce == NULL || tag == NULL ||
444
95
            (sz > 0 && (in == NULL || out == NULL)) ||
445
95
            (aadSz > 0 && aad == NULL)) {
446
0
        return BAD_FUNC_ARG;
447
0
    }
448
449
95
#ifdef WOLF_CRYPTO_CB
450
    /* devId comes from wc_Chacha_SetKey_ex(). This is the path the TLS
451
     * record layer uses. Software runs if the device declines.
452
     *
453
     * wc_Chacha_SetKey_ex() also accepts a 16 byte key, which this AEAD is
454
     * not defined for and which the callback carries no length for, so a
455
     * device would key itself with 32 bytes and diverge from software. Keep
456
     * those contexts on the software path. */
457
95
    if (chacha->devId != INVALID_DEVID &&
458
0
            chacha->devKeySz == CHACHA20_POLY1305_AEAD_KEYSIZE) {
459
0
        ret = wc_CryptoCb_Chacha20Poly1305Encrypt(chacha->devId, chacha->devKey,
460
0
            nonce, aad, aadSz, in, sz, out, tag);
461
0
        if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE))
462
0
            return ret;
463
0
    }
464
95
#endif
465
466
#ifdef WOLFSSL_CHACHA20_POLY1305_SHORT
467
    if (sz <= CHACHA20_POLY1305_SHORT_MAX)
468
        return chacha20_poly1305_encrypt_short(chacha, poly, out, in, sz,
469
                                               nonce, tag, aad, aadSz);
470
#endif
471
472
    /* Per-record Poly1305 key = first 32 bytes of ChaCha20(nonce, ctr 0). */
473
95
    XMEMSET(polyKey, 0, sizeof(polyKey));
474
95
    ret = wc_Chacha_SetIV(chacha, nonce,
475
95
                          CHACHA20_POLY1305_AEAD_INITIAL_COUNTER);
476
95
    if (ret == 0)
477
95
        ret = wc_Chacha_Process(chacha, polyKey, polyKey, sizeof(polyKey));
478
95
    if (ret == 0)   /* message data starts at counter 1 */
479
95
        ret = wc_Chacha_SetIV(chacha, nonce,
480
95
            CHACHA20_POLY1305_AEAD_INITIAL_COUNTER + 1);
481
95
    if (ret == 0)
482
95
        ret = wc_Poly1305SetKey(poly, polyKey, sizeof(polyKey));
483
95
    ForceZero(polyKey, sizeof(polyKey));
484
95
    if (ret != 0)
485
0
        return ret;
486
487
#ifdef WOLFSSL_CHACHA20_POLY1305_FUSED_IFMA
488
    if (sz >= CHACHA20_POLY1305_STITCH_MIN &&
489
            chacha20_poly1305_use_fused_ifma()) {
490
        word32 bulk = sz & ~(word32)0x3ff;
491
        /* Scalar running hash (in poly->h) so stitch and tail chain; poly->h,
492
         * leftover and started are all zeroed by wc_Poly1305SetKey. */
493
        poly->forceScalar = 1;
494
        poly->finished = 1;
495
        if (aadSz > 0) {                             /* H_aad + pad1 (scalar) */
496
            ret = wc_Poly1305Update(poly, aad, aadSz);
497
            if (ret == 0)
498
                ret = wc_Poly1305_Pad(poly, aadSz);
499
        }
500
        if (ret == 0)                            /* stitch bulk + fold AAD */
501
            ret = chacha20_poly1305_stitch_chunk(chacha, poly, in, out, bulk,
502
                                                 0);
503
        if (ret == 0 && sz > bulk) {                 /* scalar tail */
504
            ret = wc_Chacha_Process(chacha, out + bulk, in + bulk, sz - bulk);
505
            if (ret == 0)
506
                ret = wc_Poly1305Update(poly, out + bulk, sz - bulk);
507
        }
508
        if (ret == 0)                                /* pad2 + lengths + tag */
509
            ret = wc_Poly1305_Pad(poly, sz);
510
        if (ret == 0)
511
            ret = wc_Poly1305_EncodeSizes(poly, aadSz, sz);
512
        if (ret == 0)
513
            ret = wc_Poly1305Final(poly, tag);
514
        return ret;
515
    }
516
#endif
517
518
    /* Two-pass: fast vector Poly1305 (small msgs, or stitch not beneficial). */
519
95
    ret = wc_Chacha_Process(chacha, out, in, sz);
520
95
    if (ret == 0)
521
95
        ret = wc_Poly1305_MAC(poly, aad, aadSz, out, sz, tag,
522
95
                              CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE);
523
95
    return ret;
524
95
}
525
526
/* Verify+decrypt one message with pre-keyed ChaCha20 and Poly1305 contexts -
527
 * the decrypt counterpart of wc_ChaCha20Poly1305_Encrypt_ex, for the TLS record
528
 * layer.  Verifies the Poly1305 tag over AAD+ciphertext and decrypts to out
529
 * (in-place safe).  Uses the single-pass IFMA decrypt stitch when beneficial.
530
 * The plaintext is produced while the tag is computed, so on tag mismatch out
531
 * is zeroed and MAC_CMP_FAILED_E returned - callers must check the result.
532
 *
533
 *   chacha  ChaCha20 context with the key already set (wc_Chacha_SetKey)
534
 *   poly    Poly1305 scratch context (re-keyed here every call)
535
 *   out     plaintext out (may alias in)
536
 *   in/sz   ciphertext / length
537
 *   nonce   CHACHA20_POLY1305_AEAD_IV_SIZE (12) byte record nonce
538
 *   tag     CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE (16) byte tag to verify
539
 *   aad/aadSz  additional authenticated data
540
 * returns 0 on success, MAC_CMP_FAILED_E on tag mismatch, else negative.
541
 */
542
WOLFSSL_API int wc_ChaCha20Poly1305_Decrypt_ex(ChaCha* chacha, Poly1305* poly,
543
    byte* out, const byte* in, word32 sz, const byte* nonce, const byte* tag,
544
    const byte* aad, word32 aadSz)
545
25
{
546
25
    byte polyKey[CHACHA20_POLY1305_AEAD_KEYSIZE];
547
25
    byte calcTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
548
25
    int  ret;
549
550
25
    if (chacha == NULL || poly == NULL || nonce == NULL || tag == NULL ||
551
25
            (sz > 0 && (in == NULL || out == NULL)) ||
552
25
            (aadSz > 0 && aad == NULL)) {
553
0
        return BAD_FUNC_ARG;
554
0
    }
555
556
25
#ifdef WOLF_CRYPTO_CB
557
    /* See the encrypt counterpart, including the 16 byte key exclusion. */
558
25
    if (chacha->devId != INVALID_DEVID &&
559
0
            chacha->devKeySz == CHACHA20_POLY1305_AEAD_KEYSIZE) {
560
0
        ret = wc_CryptoCb_Chacha20Poly1305Decrypt(chacha->devId, chacha->devKey,
561
0
            nonce, aad, aadSz, in, sz, tag, out);
562
0
        if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE))
563
0
            return ret;
564
0
    }
565
25
#endif
566
567
#ifdef WOLFSSL_CHACHA20_POLY1305_SHORT
568
    if (sz <= CHACHA20_POLY1305_SHORT_MAX)
569
        return chacha20_poly1305_decrypt_short(chacha, poly, out, in, sz,
570
                                               nonce, tag, aad, aadSz);
571
#endif
572
573
    /* Per-record Poly1305 key = first 32 bytes of ChaCha20(nonce, ctr 0). */
574
25
    XMEMSET(polyKey, 0, sizeof(polyKey));
575
25
    ret = wc_Chacha_SetIV(chacha, nonce,
576
25
                          CHACHA20_POLY1305_AEAD_INITIAL_COUNTER);
577
25
    if (ret == 0)
578
25
        ret = wc_Chacha_Process(chacha, polyKey, polyKey, sizeof(polyKey));
579
25
    if (ret == 0)   /* message data starts at counter 1 */
580
25
        ret = wc_Chacha_SetIV(chacha, nonce,
581
25
            CHACHA20_POLY1305_AEAD_INITIAL_COUNTER + 1);
582
25
    if (ret == 0)
583
25
        ret = wc_Poly1305SetKey(poly, polyKey, sizeof(polyKey));
584
25
    ForceZero(polyKey, sizeof(polyKey));
585
25
    if (ret != 0)
586
0
        return ret;
587
588
#ifdef WOLFSSL_CHACHA20_POLY1305_FUSED_IFMA
589
    if (sz >= CHACHA20_POLY1305_STITCH_MIN &&
590
            chacha20_poly1305_use_fused_ifma()) {
591
        word32 bulk = sz & ~(word32)0x3ff;
592
        /* Scalar running hash (in poly->h) so stitch and tail chain; poly->h,
593
         * leftover and started are all zeroed by wc_Poly1305SetKey. */
594
        poly->forceScalar = 1;
595
        poly->finished = 1;
596
        if (aadSz > 0) {                             /* H_aad + pad1 (scalar) */
597
            ret = wc_Poly1305Update(poly, aad, aadSz);
598
            if (ret == 0)
599
                ret = wc_Poly1305_Pad(poly, aadSz);
600
        }
601
        if (ret == 0)                        /* stitch: hash CT + decrypt */
602
            ret = chacha20_poly1305_stitch_chunk(chacha, poly, in, out, bulk,
603
                                                 1);
604
        if (ret == 0 && sz > bulk) {                 /* scalar tail */
605
            /* hash the ciphertext tail before decrypt overwrites it */
606
            ret = wc_Poly1305Update(poly, in + bulk, sz - bulk);
607
            if (ret == 0)
608
                ret = wc_Chacha_Process(chacha, out + bulk, in + bulk,
609
                                        sz - bulk);
610
        }
611
        if (ret == 0)                                /* pad2 + lengths + tag */
612
            ret = wc_Poly1305_Pad(poly, sz);
613
        if (ret == 0)
614
            ret = wc_Poly1305_EncodeSizes(poly, aadSz, sz);
615
        if (ret == 0)
616
            ret = wc_Poly1305Final(poly, calcTag);
617
    }
618
    else
619
#endif
620
25
    {
621
        /* Two-pass: MAC the ciphertext (in), then decrypt in -> out. */
622
25
        ret = wc_Poly1305_MAC(poly, aad, aadSz, in, sz, calcTag,
623
25
                              CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE);
624
25
        if (ret == 0)
625
25
            ret = wc_Chacha_Process(chacha, out, in, sz);
626
25
    }
627
628
25
    if (ret == 0)
629
25
        ret = wc_ChaCha20Poly1305_CheckTag(tag, calcTag);
630
25
    if (ret != 0 && sz > 0)
631
24
        ForceZero(out, sz);
632
25
    return ret;
633
25
}
634
635
/* Clear a temporary ChaChaPoly_Aead.  On an AVX-512/IFMA build the Poly1305
636
 * state carries ~320 extra bytes (r5..r8 for the 16-way poly, ifma_h for the
637
 * IFMA stitch) that are only ever written on AVX-512-capable CPUs; on a CPU
638
 * without AVX-512 they are never touched, so zeroing them on every call is pure
639
 * overhead - a large fraction of a small AEAD op.  Skip them there (they hold
640
 * no key material from this call), and clear the full struct otherwise. */
641
static WC_INLINE void chacha20_poly1305_aead_zero(ChaChaPoly_Aead* aead)
642
0
{
643
#ifdef WOLFSSL_POLY1305_AVX512
644
    if (IS_INTEL_AVX512(cpuid_get_flags()) != 0)
645
        ForceZero(aead, sizeof(ChaChaPoly_Aead));
646
    else
647
        ForceZero(aead, (word32)((const byte*)&aead->poly.r5
648
                                 - (const byte*)aead));
649
#else
650
0
    ForceZero(aead, sizeof(ChaChaPoly_Aead));
651
0
#endif
652
0
}
653
654
WOLFSSL_ABI
655
int wc_ChaCha20Poly1305_Encrypt(
656
                const byte inKey[CHACHA20_POLY1305_AEAD_KEYSIZE],
657
                const byte inIV[CHACHA20_POLY1305_AEAD_IV_SIZE],
658
                const byte* inAAD, const word32 inAADLen,
659
                const byte* inPlaintext, const word32 inPlaintextLen,
660
                byte* outCiphertext,
661
                byte outAuthTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE])
662
0
{
663
0
    int ret;
664
0
    WC_DECLARE_VAR(aead, ChaChaPoly_Aead, 1, 0);
665
666
    /* Validate function arguments.  A NULL data pointer is rejected even at
667
     * zero length: the direct/short paths below bypass UpdateData, whose own
668
     * check rejects a NULL data pointer unconditionally - this preserves that
669
     * contract.  A valid pointer with zero length still succeeds (empty AEAD
670
     * message). */
671
0
    if (!inKey || !inIV ||
672
0
        inPlaintext == NULL ||
673
0
        (inAADLen > 0 && inAAD == NULL) ||
674
0
        !outCiphertext ||
675
0
        !outAuthTag)
676
0
    {
677
0
        return BAD_FUNC_ARG;
678
0
    }
679
680
0
    WC_ALLOC_VAR_EX(aead, ChaChaPoly_Aead, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER,
681
0
        return MEMORY_E);
682
683
#ifdef WOLFSSL_CHACHA20_POLY1305_SHORT
684
    /* Small record: derive the Poly1305 key and the keystream in a SINGLE
685
     * ChaCha pass (SSSE3), then scalar-hash - the same short path Encrypt_ex
686
     * uses.  Avoids wc_ChaCha20Poly1305_Init's separate scalar poly-key block
687
     * and the second scalar data block the two-pass fallback would run. */
688
    if (inPlaintextLen <= CHACHA20_POLY1305_SHORT_MAX) {
689
        ret = wc_Chacha_SetKey(&aead->chacha, inKey,
690
            CHACHA20_POLY1305_AEAD_KEYSIZE);
691
        if (ret == 0)
692
            ret = chacha20_poly1305_encrypt_short(&aead->chacha, &aead->poly,
693
                outCiphertext, inPlaintext, inPlaintextLen, inIV, outAuthTag,
694
                inAAD, inAADLen);
695
    }
696
    else
697
#endif
698
0
    {
699
0
    ret = wc_ChaCha20Poly1305_Init(aead, inKey, inIV,
700
0
        CHACHA20_POLY1305_AEAD_ENCRYPT);
701
    /* Prefer the IFMA stitch - full 512-bit ChaCha, beats the others where
702
     * AVX-512 + IFMA exist. */
703
#ifdef WOLFSSL_CHACHA20_POLY1305_FUSED_IFMA
704
    if (ret == 0 && inPlaintextLen >= CHACHA20_POLY1305_STITCH_MIN &&
705
            chacha20_poly1305_use_fused_ifma()) {
706
        ret = chacha20_poly1305_encrypt_fused_ifma(aead, inAAD, inAADLen,
707
                  inPlaintext, inPlaintextLen, outCiphertext, outAuthTag);
708
    }
709
    else
710
#endif
711
#ifdef WOLFSSL_CHACHA20_POLY1305_FUSED
712
    if (ret == 0 && inAADLen == 0 && inPlaintextLen >= 256 &&
713
            chacha20_poly1305_use_fused()) {
714
        ret = chacha20_poly1305_encrypt_fused(aead, inPlaintext,
715
                  inPlaintextLen, outCiphertext, outAuthTag);
716
    }
717
    else
718
#endif
719
0
    {
720
        /* Direct two-pass on the contexts Init already keyed (ChaCha counter is
721
         * at 1, Poly1305 keyed).  Faster than the UpdateAad/UpdateData/Final
722
         * state machine for the common non-stitched case - in particular
723
         * wc_Poly1305_MAC hashes the AAD inline instead of buffering it through
724
         * UpdateAad, which is where the small-message-with-AAD cost was. */
725
0
        if (ret == 0)
726
0
            ret = wc_Chacha_Process(&aead->chacha, outCiphertext, inPlaintext,
727
0
                                    inPlaintextLen);
728
0
        if (ret == 0)
729
0
            ret = wc_Poly1305_MAC(&aead->poly, inAAD, inAADLen, outCiphertext,
730
0
                      inPlaintextLen, outAuthTag,
731
0
                      CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE);
732
0
    }
733
0
    }
734
0
    #ifdef WOLFSSL_SMALL_STACK
735
0
    if (aead != NULL)
736
0
    #endif
737
0
        chacha20_poly1305_aead_zero(aead);
738
0
    WC_FREE_VAR_EX(aead, NULL, DYNAMIC_TYPE_TMP_BUFFER);
739
740
0
    return ret;
741
0
}
742
743
WOLFSSL_ABI
744
int wc_ChaCha20Poly1305_Decrypt(
745
                const byte inKey[CHACHA20_POLY1305_AEAD_KEYSIZE],
746
                const byte inIV[CHACHA20_POLY1305_AEAD_IV_SIZE],
747
                const byte* inAAD, const word32 inAADLen,
748
                const byte* inCiphertext, const word32 inCiphertextLen,
749
                const byte inAuthTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE],
750
                byte* outPlaintext)
751
0
{
752
0
    int ret;
753
0
    WC_DECLARE_VAR(aead, ChaChaPoly_Aead, 1, 0);
754
0
    byte calculatedAuthTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
755
756
    /* Validate function arguments.  A NULL data pointer is rejected even at
757
     * zero length: the direct/short paths below bypass UpdateData, whose own
758
     * check rejects a NULL data pointer unconditionally - this preserves that
759
     * contract.  A valid pointer with zero length still succeeds (empty AEAD
760
     * message). */
761
0
    if (!inKey || !inIV ||
762
0
        inCiphertext == NULL ||
763
0
        (inAADLen > 0 && inAAD == NULL) ||
764
0
        !inAuthTag ||
765
0
        !outPlaintext)
766
0
    {
767
0
        return BAD_FUNC_ARG;
768
0
    }
769
770
0
    WC_ALLOC_VAR_EX(aead, ChaChaPoly_Aead, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER,
771
0
        return MEMORY_E);
772
773
0
    XMEMSET(calculatedAuthTag, 0, sizeof(calculatedAuthTag));
774
775
#ifdef WOLFSSL_CHACHA20_POLY1305_SHORT
776
    /* Small record: single ChaCha pass for poly key + keystream, MAC the
777
     * ciphertext and verify BEFORE decrypting (no plaintext on bad tag).  Same
778
     * short path Decrypt_ex uses; avoids Init's extra scalar poly-key block. */
779
    if (inCiphertextLen <= CHACHA20_POLY1305_SHORT_MAX) {
780
        ret = wc_Chacha_SetKey(&aead->chacha, inKey,
781
            CHACHA20_POLY1305_AEAD_KEYSIZE);
782
        if (ret == 0)
783
            ret = chacha20_poly1305_decrypt_short(&aead->chacha, &aead->poly,
784
                outPlaintext, inCiphertext, inCiphertextLen, inIV, inAuthTag,
785
                inAAD, inAADLen);
786
    }
787
    else
788
#endif
789
0
    {
790
0
    ret = wc_ChaCha20Poly1305_Init(aead, inKey, inIV,
791
0
        CHACHA20_POLY1305_AEAD_DECRYPT);
792
    /* Direct two-pass on the contexts Init already keyed: MAC the ciphertext,
793
     * verify the tag, then decrypt - verify-then-decrypt, so no plaintext is
794
     * produced on a bad tag.  Faster than the UpdateAad/UpdateData/Final state
795
     * machine (wc_Poly1305_MAC hashes the AAD inline).  In-place safe: the MAC
796
     * reads inCiphertext before the decrypt overwrites it. */
797
0
    if (ret == 0)
798
0
        ret = wc_Poly1305_MAC(&aead->poly, inAAD, inAADLen, inCiphertext,
799
0
                  inCiphertextLen, calculatedAuthTag,
800
0
                  CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE);
801
0
    if (ret == 0)
802
0
        ret = wc_ChaCha20Poly1305_CheckTag(inAuthTag, calculatedAuthTag);
803
0
    if (ret == 0)
804
0
        ret = wc_Chacha_Process(&aead->chacha, outPlaintext, inCiphertext,
805
0
                                inCiphertextLen);
806
0
    }
807
808
0
    if (ret != 0) {
809
        /* zero plaintext on error */
810
0
        ForceZero(outPlaintext, inCiphertextLen);
811
0
    }
812
0
    #ifdef WOLFSSL_SMALL_STACK
813
0
    if (aead != NULL)
814
0
    #endif
815
0
        chacha20_poly1305_aead_zero(aead);
816
0
    WC_FREE_VAR_EX(aead, NULL, DYNAMIC_TYPE_TMP_BUFFER);
817
818
0
    return ret;
819
0
}
820
821
int wc_ChaCha20Poly1305_CheckTag(
822
    const byte authTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE],
823
    const byte authTagChk[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE])
824
25
{
825
25
    int ret = 0;
826
25
    if (authTag == NULL || authTagChk == NULL) {
827
0
        return BAD_FUNC_ARG;
828
0
    }
829
25
    if (ConstantCompare(authTag, authTagChk,
830
25
            CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE) != 0) {
831
25
        ret = MAC_CMP_FAILED_E;
832
25
    }
833
25
    return ret;
834
25
}
835
836
int wc_ChaCha20Poly1305_Init_ex(ChaChaPoly_Aead* aead,
837
    const byte inKey[CHACHA20_POLY1305_AEAD_KEYSIZE],
838
    const byte inIV[CHACHA20_POLY1305_AEAD_IV_SIZE],
839
    int isEncrypt, void* heap, int devId)
840
0
{
841
0
    int ret;
842
0
    byte authKey[CHACHA20_POLY1305_AEAD_KEYSIZE];
843
844
    /* authKey will hold the derived Poly1305 key. Register from the top with a
845
     * zero baseline so every exit (including the arg-check return, where it is
846
     * still zero) is covered. */
847
#ifdef WOLFSSL_CHECK_MEM_ZERO
848
    XMEMSET(authKey, 0, sizeof(authKey));
849
    wc_MemZero_Add("wc_ChaCha20Poly1305_Init authKey", authKey,
850
        sizeof(authKey));
851
#endif
852
853
    /* check arguments */
854
0
    if (aead == NULL || inKey == NULL || inIV == NULL) {
855
    #ifdef WOLFSSL_CHECK_MEM_ZERO
856
        wc_MemZero_Check(authKey, sizeof(authKey));
857
    #endif
858
0
        return BAD_FUNC_ARG;
859
0
    }
860
861
    /* setup aead context (full clear: the aadLen/dataLen/state wrapper fields
862
     * live after the Poly1305 member and must be initialized) */
863
0
    XMEMSET(aead, 0, sizeof(ChaChaPoly_Aead));
864
0
    XMEMSET(authKey, 0, sizeof(authKey));
865
0
    aead->isEncrypt = isEncrypt ? 1 : 0;
866
867
    /* Initialize the ChaCha20 context (key and iv) */
868
0
    ret = wc_Chacha_SetKey_ex(&aead->chacha, inKey,
869
0
        CHACHA20_POLY1305_AEAD_KEYSIZE, heap, devId);
870
0
    (void)heap;
871
0
    (void)devId;
872
0
    if (ret == 0) {
873
0
        ret = wc_Chacha_SetIV(&aead->chacha, inIV,
874
0
            CHACHA20_POLY1305_AEAD_INITIAL_COUNTER);
875
0
    }
876
877
    /* Create the Poly1305 key */
878
0
    if (ret == 0) {
879
0
        ret = wc_Chacha_Process(&aead->chacha, authKey, authKey,
880
0
            CHACHA20_POLY1305_AEAD_KEYSIZE);
881
0
    }
882
883
    /* Initialize Poly1305 context */
884
0
    if (ret == 0) {
885
0
        ret = wc_Poly1305SetKey(&aead->poly, authKey,
886
0
            CHACHA20_POLY1305_AEAD_KEYSIZE);
887
0
    }
888
889
    /* advance counter by 1 after creating Poly1305 key */
890
0
    if (ret == 0) {
891
0
        ret = wc_Chacha_SetIV(&aead->chacha, inIV,
892
0
            CHACHA20_POLY1305_AEAD_INITIAL_COUNTER + 1);
893
0
    }
894
895
0
    if (ret == 0) {
896
0
        aead->state = CHACHA20_POLY1305_STATE_READY;
897
0
    }
898
899
0
    ForceZero(authKey, sizeof(authKey));
900
#ifdef WOLFSSL_CHECK_MEM_ZERO
901
    wc_MemZero_Check(authKey, sizeof(authKey));
902
#endif
903
904
0
    return ret;
905
0
}
906
907
int wc_ChaCha20Poly1305_Init(ChaChaPoly_Aead* aead,
908
    const byte inKey[CHACHA20_POLY1305_AEAD_KEYSIZE],
909
    const byte inIV[CHACHA20_POLY1305_AEAD_IV_SIZE],
910
    int isEncrypt)
911
0
{
912
0
    return wc_ChaCha20Poly1305_Init_ex(aead, inKey, inIV, isEncrypt, NULL,
913
0
        INVALID_DEVID);
914
0
}
915
916
/* optional additional authentication data */
917
int wc_ChaCha20Poly1305_UpdateAad(ChaChaPoly_Aead* aead,
918
    const byte* inAAD, word32 inAADLen)
919
0
{
920
0
    int ret = 0;
921
922
0
    if (aead == NULL || (inAAD == NULL && inAADLen > 0)) {
923
0
        return BAD_FUNC_ARG;
924
0
    }
925
0
    if (aead->state != CHACHA20_POLY1305_STATE_READY &&
926
0
        aead->state != CHACHA20_POLY1305_STATE_AAD) {
927
0
        return BAD_STATE_E;
928
0
    }
929
0
    if (inAADLen > CHACHA20_POLY1305_MAX - aead->aadLen)
930
0
        return CHACHA_POLY_OVERFLOW;
931
932
0
    if (inAAD && inAADLen > 0) {
933
0
        ret = wc_Poly1305Update(&aead->poly, inAAD, inAADLen);
934
0
        if (ret == 0) {
935
0
            aead->aadLen += inAADLen;
936
0
            aead->state = CHACHA20_POLY1305_STATE_AAD;
937
0
        }
938
0
    }
939
940
0
    return ret;
941
0
}
942
943
/* inData and outData can be same pointer (inline) */
944
int wc_ChaCha20Poly1305_UpdateData(ChaChaPoly_Aead* aead,
945
    const byte* inData, byte* outData, word32 dataLen)
946
0
{
947
0
    int ret = 0;
948
949
0
    if (aead == NULL || inData == NULL || outData == NULL) {
950
0
        return BAD_FUNC_ARG;
951
0
    }
952
0
    if (aead->state != CHACHA20_POLY1305_STATE_READY &&
953
0
        aead->state != CHACHA20_POLY1305_STATE_AAD &&
954
0
        aead->state != CHACHA20_POLY1305_STATE_DATA) {
955
0
        return BAD_STATE_E;
956
0
    }
957
0
    if (dataLen > CHACHA20_POLY1305_MAX - aead->dataLen)
958
0
        return CHACHA_POLY_OVERFLOW;
959
960
#ifdef WOLFSSL_CHACHA20_POLY1305_FUSED_IFMA
961
    /* Enter scalar-stitch mode at the first data chunk when it is large enough
962
     * to benefit and no vector Poly1305 state exists yet (started==0): any AAD
963
     * so far is then fully buffered by the vector path.  The vector buffer can
964
     * hold >16 bytes, which the scalar path cannot resume, so re-hash the
965
     * buffered AAD cleanly through the scalar path; the pad below finishes it.
966
     * If the AAD was large enough to be processed by the vector path
967
     * (started==1) we cannot switch, so it stays two-pass - no regression.
968
     * The IFMA stitch handles both directions (decrypt hashes the ciphertext
969
     * input). */
970
    if (!aead->poly.forceScalar && aead->poly.started == 0 &&
971
            aead->dataLen == 0 && dataLen >= CHACHA20_POLY1305_STITCH_MIN &&
972
            chacha20_poly1305_use_fused_ifma()) {
973
        word32 aadN = (word32)aead->poly.leftover;
974
        byte   aadBuf[8 * POLY1305_BLOCK_SIZE];
975
        if (aadN > 0)
976
            XMEMCPY(aadBuf, aead->poly.buffer, aadN);
977
        aead->poly.forceScalar = 1;
978
        aead->poly.finished = 1;
979
        aead->poly.leftover = 0;
980
        if (aadN > 0)
981
            ret = wc_Poly1305Update(&aead->poly, aadBuf, aadN);
982
    }
983
#endif
984
985
    /* Pad the AAD */
986
0
    if (ret == 0 && aead->state == CHACHA20_POLY1305_STATE_AAD) {
987
0
        ret = wc_Poly1305_Pad(&aead->poly, aead->aadLen);
988
0
    }
989
990
    /* advance state */
991
0
    aead->state = CHACHA20_POLY1305_STATE_DATA;
992
993
    /* Perform ChaCha20 encrypt/decrypt and Poly1305 auth calc */
994
0
    if (ret == 0) {
995
0
        if (aead->isEncrypt) {
996
0
            const byte* in = inData;
997
0
            byte* out = outData;
998
0
            word32 len = dataLen;
999
#ifdef WOLFSSL_CHACHA20_POLY1305_FUSED_IFMA
1000
            /* Stitch the 1024-aligned bulk (encrypt + auth in one pass) when
1001
             * the data so far is 64-byte aligned - so BOTH ChaCha (no buffered
1002
             * partial keystream) and Poly1305 (leftover==0, running hash in
1003
             * ctx->h) are at a block boundary - and we are in scalar-hash mode
1004
             * (set at Init for AVX-512+IFMA encrypt).  The kernel processes
1005
             * whole blocks from the ChaCha counter, so a mid-block position
1006
             * would make it skip the buffered keystream; the 64-alignment check
1007
             * prevents that.  The remainder falls through to the path below. */
1008
            if (aead->poly.forceScalar && (aead->dataLen & 63) == 0 &&
1009
                    len >= CHACHA20_POLY1305_STITCH_MIN) {
1010
                word32 bulk = len & ~(word32)0x3ff;
1011
                ret = chacha20_poly1305_stitch_chunk(&aead->chacha, &aead->poly,
1012
                                                     in, out, bulk, 0);
1013
                in += bulk;
1014
                out += bulk;
1015
                len -= bulk;
1016
            }
1017
#endif
1018
0
            if (ret == 0 && len > 0) {
1019
0
                ret = wc_Chacha_Process(&aead->chacha, out, in, len);
1020
0
                if (ret == 0)
1021
0
                    ret = wc_Poly1305Update(&aead->poly, out, len);
1022
0
            }
1023
0
        }
1024
0
        else {
1025
0
            const byte* in = inData;
1026
0
            byte* out = outData;
1027
0
            word32 len = dataLen;
1028
#ifdef WOLFSSL_CHACHA20_POLY1305_FUSED_IFMA
1029
            /* Stitch the 1024-aligned bulk (auth + decrypt in one pass) under
1030
             * the same conditions as encrypt.  The kernel hashes the ciphertext
1031
             * (in) before overwriting it, so in-place decrypt is safe. */
1032
            if (aead->poly.forceScalar && (aead->dataLen & 63) == 0 &&
1033
                    len >= CHACHA20_POLY1305_STITCH_MIN) {
1034
                word32 bulk = len & ~(word32)0x3ff;
1035
                ret = chacha20_poly1305_stitch_chunk(&aead->chacha, &aead->poly,
1036
                                                     in, out, bulk, 1);
1037
                in += bulk;
1038
                out += bulk;
1039
                len -= bulk;
1040
            }
1041
#endif
1042
0
            if (ret == 0 && len > 0) {
1043
                /* hash the ciphertext before decrypt overwrites it */
1044
0
                ret = wc_Poly1305Update(&aead->poly, in, len);
1045
0
                if (ret == 0)
1046
0
                    ret = wc_Chacha_Process(&aead->chacha, out, in, len);
1047
0
            }
1048
0
        }
1049
0
    }
1050
0
    if (ret == 0) {
1051
0
        aead->dataLen += dataLen;
1052
0
    }
1053
0
    return ret;
1054
0
}
1055
1056
int wc_ChaCha20Poly1305_Final(ChaChaPoly_Aead* aead,
1057
    byte outAuthTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE])
1058
0
{
1059
0
    int ret = 0;
1060
1061
0
    if (aead == NULL || outAuthTag == NULL) {
1062
0
        return BAD_FUNC_ARG;
1063
0
    }
1064
0
    if (aead->state != CHACHA20_POLY1305_STATE_AAD &&
1065
0
        aead->state != CHACHA20_POLY1305_STATE_DATA) {
1066
0
        return BAD_STATE_E;
1067
0
    }
1068
1069
    /* Pad the AAD - Make sure it is done */
1070
0
    if (aead->state == CHACHA20_POLY1305_STATE_AAD) {
1071
0
        ret = wc_Poly1305_Pad(&aead->poly, aead->aadLen);
1072
0
    }
1073
1074
    /* Pad the plaintext/ciphertext to 16 bytes */
1075
0
    if (ret == 0) {
1076
0
        ret = wc_Poly1305_Pad(&aead->poly, aead->dataLen);
1077
0
    }
1078
1079
    /* Add the aad length and plaintext/ciphertext length */
1080
0
    if (ret == 0) {
1081
0
        ret = wc_Poly1305_EncodeSizes(&aead->poly, aead->aadLen,
1082
0
            aead->dataLen);
1083
0
    }
1084
1085
    /* Finalize the auth tag */
1086
0
    if (ret == 0) {
1087
0
        ret = wc_Poly1305Final(&aead->poly, outAuthTag);
1088
0
    }
1089
1090
    /* reset and cleanup sensitive context */
1091
0
    ForceZero(aead, sizeof(ChaChaPoly_Aead));
1092
1093
0
    return ret;
1094
0
}
1095
1096
#ifdef HAVE_XCHACHA
1097
1098
int wc_XChaCha20Poly1305_Init(
1099
    ChaChaPoly_Aead *aead,
1100
    const byte *ad, word32 ad_len,
1101
    const byte *nonce, word32 nonce_len,
1102
    const byte *key, word32 key_len,
1103
    int isEncrypt)
1104
0
{
1105
0
    byte authKey[CHACHA20_POLY1305_AEAD_KEYSIZE];
1106
0
    int ret;
1107
1108
    /* authKey will hold the derived Poly1305 key. Register from the top with a
1109
     * zero baseline so every exit (including the arg/setup error returns below,
1110
     * where it is still zero) is covered. */
1111
#ifdef WOLFSSL_CHECK_MEM_ZERO
1112
    XMEMSET(authKey, 0, sizeof authKey);
1113
    wc_MemZero_Add("xchacha20poly1305 authKey", authKey, sizeof authKey);
1114
#endif
1115
1116
0
    if ((aead == NULL) || (ad == NULL && ad_len > 0) || (nonce == NULL) ||
1117
0
        (key == NULL)) {
1118
    #ifdef WOLFSSL_CHECK_MEM_ZERO
1119
        wc_MemZero_Check(authKey, sizeof authKey);
1120
    #endif
1121
0
        return BAD_FUNC_ARG;
1122
0
    }
1123
1124
0
    if ((key_len != CHACHA20_POLY1305_AEAD_KEYSIZE) ||
1125
0
        (nonce_len != XCHACHA20_POLY1305_AEAD_NONCE_SIZE)) {
1126
    #ifdef WOLFSSL_CHECK_MEM_ZERO
1127
        wc_MemZero_Check(authKey, sizeof authKey);
1128
    #endif
1129
0
        return BAD_FUNC_ARG;
1130
0
    }
1131
1132
    /* setup aead context (full clear: the aadLen/dataLen/state wrapper fields
1133
     * live after the Poly1305 member and must be initialized) */
1134
0
    XMEMSET(aead, 0, sizeof(ChaChaPoly_Aead));
1135
1136
0
    if ((ret = wc_XChacha_SetKey(&aead->chacha,
1137
0
                                 key, key_len,
1138
0
                                 nonce, nonce_len,
1139
0
                                 0 /* counter */)) < 0) {
1140
    #ifdef WOLFSSL_CHECK_MEM_ZERO
1141
        wc_MemZero_Check(authKey, sizeof authKey);
1142
    #endif
1143
0
        return ret;
1144
0
    }
1145
1146
0
    XMEMSET(authKey, 0, sizeof authKey);
1147
1148
    /* Create the Poly1305 key */
1149
0
    if ((ret = wc_Chacha_Process(&aead->chacha, authKey, authKey,
1150
0
                                 (word32)sizeof authKey)) < 0)
1151
0
        goto out;
1152
    /* advance to start of the next ChaCha block. */
1153
0
    wc_Chacha_purge_current_block(&aead->chacha);
1154
1155
    /* Initialize Poly1305 context */
1156
0
    if ((ret = wc_Poly1305SetKey(&aead->poly, authKey,
1157
0
                                 (word32)sizeof authKey)) < 0)
1158
0
        goto out;
1159
1160
    /* Absorb the AAD exactly as wc_ChaCha20Poly1305_UpdateAad does: hash it
1161
     * and record its length. The 16-byte pad is applied by the first
1162
     * wc_ChaCha20Poly1305_UpdateData or wc_ChaCha20Poly1305_Final call (or by
1163
     * the one-shot helper below), so the AAD length that
1164
     * wc_ChaCha20Poly1305_Final encodes into the tag is the length hashed. */
1165
0
    if (ad_len > 0) {
1166
0
        if ((ret = wc_Poly1305Update(&aead->poly, ad, ad_len)) < 0)
1167
0
            goto out;
1168
0
        aead->aadLen = ad_len;
1169
0
    }
1170
1171
0
    aead->isEncrypt = isEncrypt ? 1 : 0;
1172
0
    aead->state = CHACHA20_POLY1305_STATE_AAD;
1173
1174
0
    ret = 0;
1175
1176
0
out:
1177
0
    ForceZero(authKey, sizeof(authKey));
1178
#ifdef WOLFSSL_CHECK_MEM_ZERO
1179
    wc_MemZero_Check(authKey, sizeof(authKey));
1180
#endif
1181
1182
0
    return ret;
1183
0
}
1184
1185
static WC_INLINE int wc_XChaCha20Poly1305_crypt_oneshot(
1186
    byte *dst, const size_t dst_space,
1187
    const byte *src, const size_t src_len,
1188
    const byte *ad, const size_t ad_len,
1189
    const byte *nonce, const size_t nonce_len,
1190
    const byte *key, const size_t key_len,
1191
    int isEncrypt)
1192
0
{
1193
0
    int ret;
1194
0
    size_t dst_len;
1195
0
    const byte *src_i;
1196
0
    byte *dst_i;
1197
0
    size_t src_len_rem;
1198
0
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
1199
0
    ChaChaPoly_Aead *aead = (ChaChaPoly_Aead *)XMALLOC(sizeof *aead, NULL, DYNAMIC_TYPE_TMP_BUFFER);
1200
1201
0
    if (aead == NULL)
1202
0
        return MEMORY_E;
1203
#else
1204
    ChaChaPoly_Aead aead_buf, *aead = &aead_buf;
1205
#endif
1206
1207
0
    if (isEncrypt) {
1208
0
        if (src_len > (size_t)(CHACHA20_POLY1305_MAX - POLY1305_DIGEST_SIZE)) {
1209
0
            ret = BAD_FUNC_ARG;
1210
0
            goto out;
1211
0
        }
1212
0
        dst_len = src_len + (size_t)POLY1305_DIGEST_SIZE;
1213
0
    }
1214
0
    else {
1215
0
        if (src_len < POLY1305_DIGEST_SIZE) {
1216
0
            ret = BAD_FUNC_ARG;
1217
0
            goto out;
1218
0
        }
1219
0
        dst_len = src_len - (size_t)POLY1305_DIGEST_SIZE;
1220
0
    }
1221
1222
0
    if ((dst == NULL) || (src == NULL)) {
1223
0
        ret = BAD_FUNC_ARG;
1224
0
        goto out;
1225
0
    }
1226
1227
0
    if (dst_space < dst_len) {
1228
0
        ret = BUFFER_E;
1229
0
        goto out;
1230
0
    }
1231
1232
    /* Sanity check lengths to prevent truncation when cast to word32. */
1233
0
    if ((ad_len > WOLFSSL_MAX_32BIT) ||
1234
0
        (nonce_len > WOLFSSL_MAX_32BIT) ||
1235
0
        (key_len > WOLFSSL_MAX_32BIT)) {
1236
0
        ret = BAD_FUNC_ARG;
1237
0
        goto out;
1238
0
    }
1239
1240
0
    if ((ret = wc_XChaCha20Poly1305_Init(aead, ad, (word32)ad_len,
1241
0
                                         nonce, (word32)nonce_len,
1242
0
                                         key, (word32)key_len, 1)) < 0)
1243
0
        goto out;
1244
1245
    /* wc_XChaCha20Poly1305_Init hashes the AAD and leaves the pad to the
1246
     * streaming data call; this helper bypasses that call, so pad here. */
1247
0
    if ((ret = wc_Poly1305_Pad(&aead->poly, (word32)ad_len)) < 0)
1248
0
        goto out;
1249
1250
#ifdef WOLFSSL_CHECK_MEM_ZERO
1251
    wc_MemZero_Add("wc_XChaCha20Poly1305_crypt_oneshot aead", aead,
1252
        sizeof(ChaChaPoly_Aead));
1253
#endif
1254
1255
    /* process the input in 16k pieces to accommodate src_lens that don't fit in a word32,
1256
     * and to exploit hot cache for the input data.
1257
     */
1258
0
    src_i = src;
1259
0
    src_len_rem = isEncrypt ? src_len : dst_len;
1260
0
    dst_i = dst;
1261
0
    while (src_len_rem > 0) {
1262
0
        word32 this_src_len =
1263
0
            (src_len_rem > 16384) ?
1264
0
            16384 :
1265
0
            (word32)src_len_rem;
1266
1267
0
        if ((ret = wc_Chacha_Process(&aead->chacha, dst_i, src_i, this_src_len)) < 0)
1268
0
            goto out;
1269
1270
0
        if ((ret = wc_Poly1305Update(&aead->poly, isEncrypt ? dst_i : src_i, this_src_len)) < 0)
1271
0
            goto out;
1272
1273
0
        src_len_rem -= (size_t)this_src_len;
1274
0
        src_i += this_src_len;
1275
0
        dst_i += this_src_len;
1276
0
    }
1277
1278
0
    if (aead->poly.leftover) {
1279
0
        if ((ret = wc_Poly1305_Pad(&aead->poly, (word32)aead->poly.leftover)) < 0)
1280
0
            goto out;
1281
0
    }
1282
1283
0
#ifdef WORD64_AVAILABLE
1284
0
    ret = wc_Poly1305_EncodeSizes64(&aead->poly, ad_len, isEncrypt ? src_len : dst_len);
1285
#else
1286
    ret = wc_Poly1305_EncodeSizes(&aead->poly, ad_len, isEncrypt ? src_len : dst_len);
1287
#endif
1288
0
    if (ret < 0)
1289
0
        goto out;
1290
1291
0
    if (isEncrypt)
1292
0
        ret = wc_Poly1305Final(&aead->poly, dst + src_len);
1293
0
    else {
1294
0
        byte outAuthTag[POLY1305_DIGEST_SIZE];
1295
1296
0
        if ((ret = wc_Poly1305Final(&aead->poly, outAuthTag)) < 0)
1297
0
            goto out;
1298
1299
0
        if (ConstantCompare(outAuthTag, src + dst_len, POLY1305_DIGEST_SIZE)
1300
0
            != 0) {
1301
0
            ForceZero(dst, dst_space);
1302
0
            ret = MAC_CMP_FAILED_E;
1303
0
            goto out;
1304
0
        }
1305
0
    }
1306
1307
0
  out:
1308
1309
0
    ForceZero(aead, sizeof *aead);
1310
1311
0
#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC)
1312
0
    XFREE(aead, NULL, DYNAMIC_TYPE_TMP_BUFFER);
1313
#elif defined(WOLFSSL_CHECK_MEM_ZERO)
1314
    wc_MemZero_Check(aead, sizeof(ChaChaPoly_Aead));
1315
#endif
1316
1317
0
    return ret;
1318
0
}
1319
1320
int wc_XChaCha20Poly1305_Encrypt(
1321
    byte *dst, const size_t dst_space,
1322
    const byte *src, const size_t src_len,
1323
    const byte *ad, const size_t ad_len,
1324
    const byte *nonce, const size_t nonce_len,
1325
    const byte *key, const size_t key_len)
1326
0
{
1327
0
    return wc_XChaCha20Poly1305_crypt_oneshot(dst, dst_space, src, src_len, ad, ad_len, nonce, nonce_len, key, key_len, 1);
1328
0
}
1329
1330
int wc_XChaCha20Poly1305_Decrypt(
1331
    byte *dst, const size_t dst_space,
1332
    const byte *src, const size_t src_len,
1333
    const byte *ad, const size_t ad_len,
1334
    const byte *nonce, const size_t nonce_len,
1335
    const byte *key, const size_t key_len)
1336
0
{
1337
0
    return wc_XChaCha20Poly1305_crypt_oneshot(dst, dst_space, src, src_len, ad, ad_len, nonce, nonce_len, key, key_len, 0);
1338
0
}
1339
1340
#endif /* HAVE_XCHACHA */
1341
1342
#endif /* HAVE_CHACHA && HAVE_POLY1305 */