Coverage Report

Created: 2026-08-12 07:05

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