xref: /dragonfly/sys/crypto/chachapoly.c (revision 03517d4e)
1 /*
2  * Copyright (c) 2015 Mike Belopuhov
3  * Copyright (c) 2023-2024 Aaron LI <aly@aaronly.me>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/endian.h>
21 #include <sys/mbuf.h>
22 
23 #include <crypto/chachapoly.h>
24 #include <crypto/chacha20/chacha.h>
25 #include <crypto/poly1305/poly1305.h>
26 
27 
28 struct chacha20poly1305_ctx {
29 	struct chacha_ctx	chacha;
30 	poly1305_state		poly;
31 	uint8_t			tag[CHACHA20POLY1305_AUTHTAG_SIZE];
32 	size_t			ad_len;
33 	size_t			data_len;
34 	int			flags;
35 #define F_MODE_ENCRYPTION	0x001	/* encryption operation */
36 #define F_INITIALIZED		0x002	/* context initialized */
37 #define F_AD_DONE		0x004	/* no more additional data */
38 };
39 
40 #define PADDING_SIZE		16
41 #define PADDING_LEN(len) \
42 	((PADDING_SIZE - ((len) & (PADDING_SIZE - 1))) & (PADDING_SIZE - 1))
43 
44 static const uint8_t pad0[PADDING_SIZE] = { 0 };
45 
46 
47 static void
48 _chacha20poly1305_init(struct chacha20poly1305_ctx *ctx, bool enc_mode,
49 		       const uint8_t nonce[CHACHA20POLY1305_NONCE_SIZE],
50 		       const uint8_t key[CHACHA20POLY1305_KEY_SIZE])
51 {
52 	uint8_t poly_key[CHACHA20POLY1305_KEY_SIZE];
53 	uint8_t nonce64[CHACHA_NONCELEN], counter64[CHACHA_CTRLEN];
54 
55 	bzero(ctx, sizeof(*ctx));
56 	bzero(poly_key, sizeof(poly_key));
57 	bzero(nonce64, sizeof(nonce64));
58 	bzero(counter64, sizeof(counter64));
59 
60 	/*
61 	 * The original ChaCha uses a 64-bit nonce and a 64-bit counter,
62 	 * but the IETF version is modified to use a 96-bit nonce and
63 	 * a 32-bit counter. (RFC 8439)
64 	 */
65 	memcpy(counter64 + 4, nonce, 4);
66 	memcpy(nonce64, nonce + 4, 8);
67 
68 	/* Generate the Poly1305 one-time key. */
69 	chacha_keysetup(&ctx->chacha, key, CHACHA20POLY1305_KEY_SIZE * 8);
70 	chacha_ivsetup(&ctx->chacha, nonce64, counter64);
71 	chacha_encrypt_bytes(&ctx->chacha, poly_key, poly_key,
72 			     sizeof(poly_key));
73 
74 	/* Initialize the authenticator. */
75 	poly1305_init(&ctx->poly, poly_key);
76 
77 	ctx->flags |= (enc_mode ? F_MODE_ENCRYPTION : 0);
78 	ctx->flags |= F_INITIALIZED;
79 }
80 
81 /*
82  * Authenticate the additional data (AD).
83  *
84  * This function may be called zero, one or more times to pass successive
85  * fragments of the AD, but must be done before processing any cipher data,
86  * i.e., calling _chacha20poly1305_update().
87  */
88 static void
89 _chacha20poly1305_update_ad(struct chacha20poly1305_ctx *ctx,
90 			    const uint8_t *ad, size_t ad_len)
91 {
92 	KKASSERT((ctx->flags & F_INITIALIZED) != 0);
93 	KKASSERT((ctx->flags & F_AD_DONE) == 0);
94 
95 	if (ad_len == 0 || ad == NULL)
96 		return;
97 
98 	poly1305_update(&ctx->poly, ad, ad_len);
99 	ctx->ad_len += ad_len;
100 }
101 
102 /*
103  * Encrypt/decrypt the cipher data (i.e., plaintext or ciphertext).
104  *
105  * NOTE: The cipher data must be complete blocks.  This requirement is
106  *       placed to help easily implement the in-place encryption/
107  *       decryption for data in non-contiguous buffers (e.g., mbuf chain).
108  */
109 static void
110 _chacha20poly1305_update(struct chacha20poly1305_ctx *ctx,
111 			 uint8_t *out, const uint8_t *in, size_t in_len)
112 {
113 	KKASSERT((ctx->flags & F_INITIALIZED) != 0);
114 	KKASSERT(in_len == 0 || (in_len > 0 && out != NULL));
115 	KKASSERT(in_len % CHACHA_BLOCKLEN == 0); /* must be complete blocks */
116 
117 	if ((ctx->flags & F_AD_DONE) == 0) {
118 		poly1305_update(&ctx->poly, pad0, PADDING_LEN(ctx->ad_len));
119 		ctx->flags |= F_AD_DONE;
120 	}
121 
122 	if (in_len == 0 || in == NULL)
123 		return;
124 
125 	/* Swap the Poly1305/ChaCha order to support in-place operation. */
126 	if (ctx->flags & F_MODE_ENCRYPTION) {
127 		chacha_encrypt_bytes(&ctx->chacha, in, out, in_len);
128 		poly1305_update(&ctx->poly, out, in_len);
129 	} else {
130 		poly1305_update(&ctx->poly, in, in_len);
131 		chacha_encrypt_bytes(&ctx->chacha, in, out, in_len);
132 	}
133 
134 	ctx->data_len += in_len;
135 }
136 
137 /*
138  * Process the last cipher block, which can be empty, complete or incomplete.
139  *
140  * In the encryption mode, the tag is calculated and is available via
141  * 'ctx->tag'.  Let the caller get the tag and append to the output, because
142  * the allocated tag space may not directly follow the output buffer; e.g.,
143  * the tag is allocated on a separate mbuf.
144  *
145  * In the decryption mode, the caller must set the expected tag in
146  * 'ctx->tag' before calling this function.  The tag will be verified and
147  * a boolean is returned to indicate whether the tag matches.
148  */
149 static bool
150 _chacha20poly1305_final(struct chacha20poly1305_ctx *ctx,
151 			uint8_t *out, const uint8_t *in, size_t in_len)
152 {
153 	uint64_t lens[2];
154 	uint8_t tag[CHACHA20POLY1305_AUTHTAG_SIZE];
155 	bool ret;
156 
157 	KKASSERT((ctx->flags & F_INITIALIZED) != 0);
158 	KKASSERT(in_len == 0 || (in_len > 0 && out != NULL));
159 	KKASSERT(in_len <= CHACHA_BLOCKLEN);
160 
161 	if (ctx->ad_len > 0 && (ctx->flags & F_AD_DONE) == 0) {
162 		KKASSERT(ctx->data_len == 0);
163 		poly1305_update(&ctx->poly, pad0, PADDING_LEN(ctx->ad_len));
164 		ctx->flags |= F_AD_DONE;
165 	}
166 
167 	if (in_len > 0) {
168 		if (ctx->flags & F_MODE_ENCRYPTION) {
169 			chacha_encrypt_bytes(&ctx->chacha, in, out, in_len);
170 			poly1305_update(&ctx->poly, out, in_len);
171 		} else {
172 			poly1305_update(&ctx->poly, in, in_len);
173 			chacha_encrypt_bytes(&ctx->chacha, in, out, in_len);
174 		}
175 		poly1305_update(&ctx->poly, pad0, PADDING_LEN(in_len));
176 		ctx->data_len += in_len;
177 	}
178 
179 	/* Calculate the tag. */
180 	lens[0] = htole64(ctx->ad_len);
181 	lens[1] = htole64(ctx->data_len);
182 	poly1305_update(&ctx->poly, (uint8_t *)lens, sizeof(lens));
183 	poly1305_finish(&ctx->poly, tag);
184 
185 	if (ctx->flags & F_MODE_ENCRYPTION) {
186 		ret = true;
187 		explicit_bzero(ctx, sizeof(*ctx));
188 		memcpy(ctx->tag, tag, sizeof(tag));
189 	} else {
190 		ret = (timingsafe_bcmp(ctx->tag, tag, sizeof(tag)) == 0);
191 		explicit_bzero(ctx, sizeof(*ctx));
192 	}
193 
194 	return (ret);
195 }
196 
197 /*-------------------------------------------------------------------*/
198 
199 void
200 chacha20poly1305_encrypt(uint8_t *dst, const uint8_t *src, size_t src_len,
201 			 const uint8_t *ad, size_t ad_len,
202 			 const uint8_t nonce[CHACHA20POLY1305_NONCE_SIZE],
203 			 const uint8_t key[CHACHA20POLY1305_KEY_SIZE])
204 {
205 	struct chacha20poly1305_ctx ctx;
206 	size_t len;
207 
208 	_chacha20poly1305_init(&ctx, true, nonce, key);
209 
210 	_chacha20poly1305_update_ad(&ctx, ad, ad_len);
211 
212 	len = rounddown2(src_len, CHACHA_BLOCKLEN);
213 	_chacha20poly1305_update(&ctx, dst, src, len);
214 
215 	_chacha20poly1305_final(&ctx, dst + len, src + len, src_len - len);
216 	memcpy(dst + src_len, ctx.tag, sizeof(ctx.tag));
217 }
218 
219 bool
220 chacha20poly1305_decrypt(uint8_t *dst, const uint8_t *src, size_t src_len,
221 			 const uint8_t *ad, size_t ad_len,
222 			 const uint8_t nonce[CHACHA20POLY1305_NONCE_SIZE],
223 			 const uint8_t key[CHACHA20POLY1305_KEY_SIZE])
224 {
225 	struct chacha20poly1305_ctx ctx;
226 	size_t data_len, len;
227 
228 	if (src_len < sizeof(ctx.tag))
229 		return (false);
230 
231 	_chacha20poly1305_init(&ctx, false, nonce, key);
232 
233 	_chacha20poly1305_update_ad(&ctx, ad, ad_len);
234 
235 	data_len = src_len - sizeof(ctx.tag);
236 	len = rounddown2(data_len, CHACHA_BLOCKLEN);
237 	_chacha20poly1305_update(&ctx, dst, src, len);
238 
239 	memcpy(ctx.tag, src + data_len, sizeof(ctx.tag));
240 	return _chacha20poly1305_final(&ctx, dst + len, src + len,
241 				       data_len - len);
242 }
243 
244 /*-------------------------------------------------------------------*/
245 
246 /*
247  * XChaCha: eXtended-nonce ChaCha and AEAD_XChaCha20_Poly1305
248  * RFC draft: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
249  */
250 void
251 xchacha20poly1305_encrypt(uint8_t *dst, const uint8_t *src, size_t src_len,
252 			  const uint8_t *ad, size_t ad_len,
253 			  const uint8_t nonce[XCHACHA20POLY1305_NONCE_SIZE],
254 			  const uint8_t key[CHACHA20POLY1305_KEY_SIZE])
255 {
256 	uint8_t derived_key[CHACHA20POLY1305_KEY_SIZE];
257 	uint8_t derived_nonce[CHACHA20POLY1305_NONCE_SIZE];
258 
259 	/* Derive a subkey using the first 16 bytes of the nonce. */
260 	hchacha20(derived_key, nonce, key);
261 
262 	/* Prefix the remaining 8 bytes of the nonce with 4 NUL bytes. */
263 	bzero(derived_nonce, sizeof(derived_nonce));
264 	memcpy(derived_nonce + 4, nonce + 16, 8);
265 
266 	chacha20poly1305_encrypt(dst, src, src_len, ad, ad_len,
267 				 derived_nonce, derived_key);
268 
269 	explicit_bzero(derived_key, sizeof(derived_key));
270 }
271 
272 bool
273 xchacha20poly1305_decrypt(uint8_t *dst, const uint8_t *src, size_t src_len,
274 			  const uint8_t *ad, size_t ad_len,
275 			  const uint8_t nonce[XCHACHA20POLY1305_NONCE_SIZE],
276 			  const uint8_t key[CHACHA20POLY1305_KEY_SIZE])
277 {
278 	uint8_t derived_key[CHACHA20POLY1305_KEY_SIZE];
279 	uint8_t derived_nonce[CHACHA20POLY1305_NONCE_SIZE];
280 	bool ret;
281 
282 	hchacha20(derived_key, nonce, key);
283 
284 	bzero(derived_nonce, sizeof(derived_nonce));
285 	memcpy(derived_nonce + 4, nonce + 16, 8);
286 
287 	ret = chacha20poly1305_decrypt(dst, src, src_len, ad, ad_len,
288 				       derived_nonce, derived_key);
289 
290 	explicit_bzero(derived_key, sizeof(derived_key));
291 	return (ret);
292 }
293 
294 /*-------------------------------------------------------------------*/
295 
296 static int
297 _chacha20poly1305_mbuf(struct chacha20poly1305_ctx *ctx,
298 		       struct mbuf *m, int len)
299 {
300 	uint8_t block[CHACHA_BLOCKLEN], *p;
301 	int off, blen, ret;
302 
303 	off = 0;
304 	while (len >= (int)sizeof(block)) {
305 		if (m->m_len == off) {
306 			off = 0;
307 			m = m->m_next;
308 			/* Skip possibly empty mbufs. */
309 			while (m != NULL && m->m_len == 0)
310 				m = m->m_next;
311 			if (m == NULL)
312 				return (EINVAL); /* because len > 0 */
313 		}
314 
315 		if (m->m_len >= off + sizeof(block)) {
316 			p = mtod(m, uint8_t *) + off;
317 			blen = rounddown2(MIN(len, m->m_len - off),
318 					  sizeof(block));
319 			_chacha20poly1305_update(ctx, p, p, blen);
320 
321 			off += blen;
322 			len -= blen;
323 		} else {
324 			/* Insufficient data at the end; do some copying. */
325 			m_copydata(m, off, sizeof(block), block);
326 			_chacha20poly1305_update(ctx, block, block,
327 						 sizeof(block));
328 			m_copyback(m, off, sizeof(block), block);
329 
330 			/* Advance pointer. */
331 			m = m_getptr(m, off + sizeof(block), &off);
332 			if (m == NULL)
333 				return (EINVAL);
334 
335 			len -= (int)sizeof(block);
336 		}
337 	}
338 
339 	m_copydata(m, off, len, block); /* len may be 0 */
340 	if (ctx->flags & F_MODE_ENCRYPTION) {
341 		ret = _chacha20poly1305_final(ctx, block, block, len) ?
342 		      0 : EBADMSG;
343 		m_copyback(m, off + len, sizeof(ctx->tag), ctx->tag);
344 	} else {
345 		m_copydata(m, off + len, sizeof(ctx->tag), ctx->tag);
346 		ret = _chacha20poly1305_final(ctx, block, block, len) ?
347 		      0 : EBADMSG;
348 	}
349 	m_copyback(m, off, len, block);
350 
351 	return (ret);
352 }
353 
354 int
355 chacha20poly1305_encrypt_mbuf(struct mbuf *m, const uint8_t *ad, size_t ad_len,
356 			      const uint8_t nonce[CHACHA20POLY1305_NONCE_SIZE],
357 			      const uint8_t key[CHACHA20POLY1305_KEY_SIZE])
358 {
359 	static const uint8_t blank_tag[CHACHA20POLY1305_AUTHTAG_SIZE] = { 0 };
360 	struct chacha20poly1305_ctx ctx;
361 	int len;
362 
363 	M_ASSERTPKTHDR(m);
364 	len = m->m_pkthdr.len;
365 
366 	if (!m_append(m, sizeof(blank_tag), blank_tag))
367 		return (ENOMEM);
368 
369 	_chacha20poly1305_init(&ctx, true, nonce, key);
370 
371 	_chacha20poly1305_update_ad(&ctx, ad, ad_len);
372 
373 	return _chacha20poly1305_mbuf(&ctx, m, len);
374 }
375 
376 int
377 chacha20poly1305_decrypt_mbuf(struct mbuf *m, const uint8_t *ad, size_t ad_len,
378 			      const uint8_t nonce[CHACHA20POLY1305_NONCE_SIZE],
379 			      const uint8_t key[CHACHA20POLY1305_KEY_SIZE])
380 {
381 	struct chacha20poly1305_ctx ctx;
382 	int len, ret;
383 
384 	M_ASSERTPKTHDR(m);
385 	len = m->m_pkthdr.len - CHACHA20POLY1305_AUTHTAG_SIZE;
386 	if (len < 0)
387 		return (EBADMSG);
388 
389 	_chacha20poly1305_init(&ctx, false, nonce, key);
390 
391 	_chacha20poly1305_update_ad(&ctx, ad, ad_len);
392 
393 	ret = _chacha20poly1305_mbuf(&ctx, m, len);
394 	if (ret == 0)
395 		m_adj(m, -CHACHA20POLY1305_AUTHTAG_SIZE);
396 
397 	return (ret);
398 }
399