git: ae18720d2792 - main - crypto: Support multiple nonce lengths for AES-CCM.

From: John Baldwin <jhb_at_FreeBSD.org>
Date: Wed, 06 Oct 2021 21:10:30 UTC
The branch main has been updated by jhb:

URL: https://cgit.FreeBSD.org/src/commit/?id=ae18720d2792287c9ec658404f1a3173014d4979

commit ae18720d2792287c9ec658404f1a3173014d4979
Author:     John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2021-10-06 21:08:47 +0000
Commit:     John Baldwin <jhb@FreeBSD.org>
CommitDate: 2021-10-06 21:08:47 +0000

    crypto: Support multiple nonce lengths for AES-CCM.
    
    Permit nonces of lengths 7 through 13 in the OCF framework and the
    cryptosoft driver.  A helper function (ccm_max_payload_length) can be
    used in OCF drivers to reject CCM requests which are too large for the
    specified nonce length.
    
    Reviewed by:    sef
    Sponsored by:   Chelsio Communications, The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D32111
---
 share/man/man7/crypto.7        | 18 +++++++++-----
 sys/opencrypto/crypto.c        | 56 ++++++++++++++++++++++++++++++++++++------
 sys/opencrypto/cryptodev.h     | 29 ++++++++++++++++++++++
 sys/opencrypto/cryptosoft.c    | 17 +++++++------
 sys/opencrypto/xform_aes_icm.c |  7 +++---
 5 files changed, 102 insertions(+), 25 deletions(-)

diff --git a/share/man/man7/crypto.7 b/share/man/man7/crypto.7
index 6e5bd83621aa..d75daa62adcb 100644
--- a/share/man/man7/crypto.7
+++ b/share/man/man7/crypto.7
@@ -1,9 +1,13 @@
-.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" Copyright (c) 2014-2021 The FreeBSD Foundation
 .\" All rights reserved.
 .\"
-.\" This documentation was written by John-Mark Gurney under
-.\" the sponsorship of the FreeBSD Foundation and
+.\" Portions of this documentation were written by John-Mark Gurney
+.\" under the sponsorship of the FreeBSD Foundation and
 .\" Rubicon Communications, LLC (Netgate).
+.\"
+.\" Portions of this documentation were written by Ararat River
+.\" Consulting, LLC under sponsorship of the FreeBSD Foundation.
+.\"
 .\" Redistribution and use in source and binary forms, with or without
 .\" modification, are permitted provided that the following conditions
 .\" are met:
@@ -27,7 +31,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd March 3, 2021
+.Dd October 6, 2021
 .Dt CRYPTO 7
 .Os
 .Sh NAME
@@ -153,13 +157,15 @@ This nonce must be provided in
 via the
 .Dv CRYPTO_F_IV_SEPARATE
 flag.
+Some AEAD algorithms support multiple nonce sizes.
+The first size listed is the default nonce size.
 .Pp
 The following AEAD algorithms are supported:
-.Bl -column "CRYPTO_AES_NIST_GCM_16" "Nonce" "16, 24, 32" "Tag"
+.Bl -column "CRYPTO_AES_NIST_GCM_16" "12, 7-13" "16, 24, 32" "Tag"
 .It Sy Name Ta Sy Nonce Ta Sy Key Sizes Ta Sy Tag Ta Sy Description
 .It Dv CRYPTO_AES_NIST_GCM_16 Ta 12 Ta 16, 24, 32 Ta 16 Ta
 AES Galois/Counter Mode
-.It Dv CRYPTO_AES_CCM_16 Ta 12 Ta 16, 24, 32 Ta 16 Ta
+.It Dv CRYPTO_AES_CCM_16 Ta 12, 7-13 Ta 16, 24, 32 Ta 16 Ta
 AES Counter with CBC-MAC
 .It Dv CRYPTO_CHACHA20_POLY1305 Ta 12 Ta 32 Ta 16 Ta
 ChaCha20-Poly1305
diff --git a/sys/opencrypto/crypto.c b/sys/opencrypto/crypto.c
index ef8422d5f32c..3a71e53129be 100644
--- a/sys/opencrypto/crypto.c
+++ b/sys/opencrypto/crypto.c
@@ -1,5 +1,9 @@
 /*-
  * Copyright (c) 2002-2006 Sam Leffler.  All rights reserved.
+ * Copyright (c) 2021 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Ararat River
+ * Consulting, LLC under sponsorship of the FreeBSD Foundation.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -713,6 +717,24 @@ alg_is_aead(int alg)
 	return (alg_type(alg) == ALG_AEAD);
 }
 
+static bool
+ccm_tag_length_valid(int len)
+{
+	/* RFC 3610 */
+	switch (len) {
+	case 4:
+	case 6:
+	case 8:
+	case 10:
+	case 12:
+	case 14:
+	case 16:
+		return (true);
+	default:
+		return (false);
+	}
+}
+
 #define SUPPORTED_SES (CSP_F_SEPARATE_OUTPUT | CSP_F_SEPARATE_AAD | CSP_F_ESN)
 
 /* Various sanity checks on crypto session parameters. */
@@ -770,8 +792,21 @@ check_csp(const struct crypto_session_params *csp)
 			return (false);
 
 		/* IV is optional for digests (e.g. GMAC). */
-		if (csp->csp_ivlen >= EALG_MAX_BLOCK_LEN)
-			return (false);
+		switch (csp->csp_auth_alg) {
+		case CRYPTO_AES_CCM_CBC_MAC:
+			if (csp->csp_ivlen < 7 || csp->csp_ivlen > 13)
+				return (false);
+			break;
+		case CRYPTO_AES_NIST_GMAC:
+			if (csp->csp_ivlen != AES_GCM_IV_LEN)
+				return (false);
+			break;
+		default:
+			if (csp->csp_ivlen != 0)
+				return (false);
+			break;
+		}
+
 		if (!alg_is_digest(csp->csp_auth_alg))
 			return (false);
 
@@ -790,6 +825,10 @@ check_csp(const struct crypto_session_params *csp)
 			axf = crypto_auth_hash(csp);
 			if (axf == NULL || csp->csp_auth_mlen > axf->hashsize)
 				return (false);
+
+			if (csp->csp_auth_alg == CRYPTO_AES_CCM_CBC_MAC &&
+			    !ccm_tag_length_valid(csp->csp_auth_mlen))
+				return (false);
 		}
 		break;
 	case CSP_MODE_AEAD:
@@ -803,13 +842,16 @@ check_csp(const struct crypto_session_params *csp)
 		if (csp->csp_auth_alg != 0 || csp->csp_auth_klen != 0)
 			return (false);
 
-		/*
-		 * XXX: Would be nice to have a better way to get this
-		 * value.
-		 */
 		switch (csp->csp_cipher_alg) {
-		case CRYPTO_AES_NIST_GCM_16:
 		case CRYPTO_AES_CCM_16:
+			if (csp->csp_auth_mlen != 0 &&
+			    !ccm_tag_length_valid(csp->csp_auth_mlen))
+				return (false);
+
+			if (csp->csp_ivlen < 7 || csp->csp_ivlen > 13)
+				return (false);
+			break;
+		case CRYPTO_AES_NIST_GCM_16:
 		case CRYPTO_CHACHA20_POLY1305:
 			if (csp->csp_auth_mlen > 16)
 				return (false);
diff --git a/sys/opencrypto/cryptodev.h b/sys/opencrypto/cryptodev.h
index b3c79a48f632..6611a8925020 100644
--- a/sys/opencrypto/cryptodev.h
+++ b/sys/opencrypto/cryptodev.h
@@ -686,5 +686,34 @@ crypto_read_iv(struct cryptop *crp, void *iv)
 		crypto_copydata(crp, crp->crp_iv_start, csp->csp_ivlen, iv);
 }
 
+static __inline size_t
+ccm_max_payload_length(const struct crypto_session_params *csp)
+{
+	/* RFC 3160 */
+	const u_int L = 15 - csp->csp_ivlen;
+
+	switch (L) {
+	case 2:
+		return (0xffff);
+	case 3:
+		return (0xffffff);
+#ifdef __LP64__
+	case 4:
+		return (0xffffffff);
+	case 5:
+		return (0xffffffffff);
+	case 6:
+		return (0xffffffffffff);
+	case 7:
+		return (0xffffffffffffff);
+	default:
+		return (0xffffffffffffffff);
+#else
+	default:
+		return (0xffffffff);
+#endif
+	}
+}
+
 #endif /* _KERNEL */
 #endif /* _CRYPTO_CRYPTO_H_ */
diff --git a/sys/opencrypto/cryptosoft.c b/sys/opencrypto/cryptosoft.c
index 19092e56b004..ae71f0d6c096 100644
--- a/sys/opencrypto/cryptosoft.c
+++ b/sys/opencrypto/cryptosoft.c
@@ -642,17 +642,19 @@ swcr_ccm_cbc_mac(struct swcr_session *ses, struct cryptop *crp)
 	u_char tag[AES_CBC_MAC_HASH_LEN];
 	u_char iv[AES_BLOCK_LEN];
 	union authctx ctx;
+	const struct crypto_session_params *csp;
 	struct swcr_auth *swa;
 	const struct auth_hash *axf;
 	int error, ivlen;
 
+	csp = crypto_get_params(crp->crp_session);
 	swa = &ses->swcr_auth;
 	axf = swa->sw_axf;
 
 	bcopy(swa->sw_ictx, &ctx, axf->ctxsize);
 
 	/* Initialize the IV */
-	ivlen = AES_CCM_IV_LEN;
+	ivlen = csp->csp_ivlen;
 	crypto_read_iv(crp, iv);
 
 	/*
@@ -694,6 +696,7 @@ swcr_ccm_cbc_mac(struct swcr_session *ses, struct cryptop *crp)
 static int
 swcr_ccm(struct swcr_session *ses, struct cryptop *crp)
 {
+	const struct crypto_session_params *csp;
 	uint32_t blkbuf[howmany(AES_BLOCK_LEN, sizeof(uint32_t))];
 	u_char *blk = (u_char *)blkbuf;
 	u_char tag[AES_CBC_MAC_HASH_LEN];
@@ -708,6 +711,7 @@ swcr_ccm(struct swcr_session *ses, struct cryptop *crp)
 	size_t len;
 	int blksz, error, ivlen, r, resid;
 
+	csp = crypto_get_params(crp->crp_session);
 	swa = &ses->swcr_auth;
 	axf = swa->sw_axf;
 
@@ -721,10 +725,13 @@ swcr_ccm(struct swcr_session *ses, struct cryptop *crp)
 	KASSERT(axf->blocksize == exf->native_blocksize,
 	    ("%s: blocksize mismatch", __func__));
 
+	if (crp->crp_payload_length > ccm_max_payload_length(csp))
+		return (EMSGSIZE);
+
 	if ((crp->crp_flags & CRYPTO_F_IV_SEPARATE) == 0)
 		return (EINVAL);
 
-	ivlen = AES_CCM_IV_LEN;
+	ivlen = csp->csp_ivlen;
 
 	/*
 	 * AES CCM-CBC-MAC needs to know the length of both the auth
@@ -1130,7 +1137,6 @@ swcr_setup_cipher(struct swcr_session *ses,
 
 	swe = &ses->swcr_encdec;
 	txf = crypto_cipher(csp);
-	MPASS(txf->ivsize == csp->csp_ivlen);
 	if (txf->ctxsize != 0) {
 		swe->sw_kschedule = malloc(txf->ctxsize, M_CRYPTO_DATA,
 		    M_NOWAIT);
@@ -1282,9 +1288,6 @@ swcr_setup_ccm(struct swcr_session *ses,
 	struct swcr_auth *swa;
 	const struct auth_hash *axf;
 
-	if (csp->csp_ivlen != AES_CCM_IV_LEN)
-		return (EINVAL);
-
 	/* First, setup the auth side. */
 	swa = &ses->swcr_auth;
 	switch (csp->csp_cipher_klen * 8) {
@@ -1392,8 +1395,6 @@ swcr_auth_supported(const struct crypto_session_params *csp)
 		}
 		if (csp->csp_auth_key == NULL)
 			return (false);
-		if (csp->csp_ivlen != AES_CCM_IV_LEN)
-			return (false);
 		break;
 	}
 	return (true);
diff --git a/sys/opencrypto/xform_aes_icm.c b/sys/opencrypto/xform_aes_icm.c
index 860dceda5232..09880ee426b8 100644
--- a/sys/opencrypto/xform_aes_icm.c
+++ b/sys/opencrypto/xform_aes_icm.c
@@ -144,15 +144,14 @@ aes_ccm_reinit(void *key, const uint8_t *iv, size_t ivlen)
 {
 	struct aes_icm_ctx *ctx;
 
-	KASSERT(ivlen == AES_CCM_IV_LEN,
+	KASSERT(ivlen >= 7 && ivlen <= 13,
 	    ("%s: invalid IV length", __func__));
 	ctx = key;
 
 	/* CCM has flags, then the IV, then the counter, which starts at 1 */
 	bzero(ctx->ac_block, sizeof(ctx->ac_block));
-	/* 3 bytes for length field; this gives a nonce of 12 bytes */
-	ctx->ac_block[0] = (15 - AES_CCM_IV_LEN) - 1;
-	bcopy(iv, ctx->ac_block+1, AES_CCM_IV_LEN);
+	ctx->ac_block[0] = (15 - ivlen) - 1;
+	bcopy(iv, ctx->ac_block + 1, ivlen);
 	ctx->ac_block[AESICM_BLOCKSIZE - 1] = 1;
 }