diff --git a/net/sunrpc/auth_gss/gss_krb5_crypto.c b/net/sunrpc/auth_gss/gss_krb5_crypto.c index 16dcf115de1e..85425d4a28c2 100644 --- a/net/sunrpc/auth_gss/gss_krb5_crypto.c +++ b/net/sunrpc/auth_gss/gss_krb5_crypto.c @@ -953,3 +953,100 @@ krb5_etm_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, ret = GSS_S_FAILURE; return ret; } + +/** + * gss_krb5_aead_encrypt - Encrypt a wrap token using crypto/krb5 + * @kctx: Kerberos context + * @offset: byte offset of the GSS token header in @buf + * @buf: OUT: send buffer + * @pages: plaintext payload pages (page cache data) + * + * The xdr_buf setup mirrors the original per-enctype encrypt + * functions, but the CBC-CTS encryption and HMAC are replaced + * by a single AEAD operation through the crypto/krb5 library. + * + * Return values: + * %GSS_S_COMPLETE: Encryption successful + * %GSS_S_FAILURE: Encryption failed + */ +u32 +gss_krb5_aead_encrypt(struct krb5_ctx *kctx, u32 offset, + struct xdr_buf *buf, struct page **pages) +{ + const struct krb5_enctype *krb5 = kctx->krb5e; + struct crypto_aead *aead = kctx->initiate ? + kctx->initiator_enc_aead : kctx->acceptor_enc_aead; + unsigned int conflen = krb5->conf_len; + unsigned int cksum_len = krb5->cksum_len; + unsigned int sec_offset, sec_len, data_len; + struct scatterlist sg[XDR_BUF_TO_SG_NENTS]; + struct scatterlist *sg_overflow = NULL; + ssize_t ret; + int nsg; + + /* Insert space for the confounder */ + if (xdr_extend_head(buf, offset + GSS_KRB5_TOK_HDR_LEN, conflen)) + return GSS_S_FAILURE; + + /* Ensure a tail segment exists */ + if (buf->tail[0].iov_base == NULL) { + buf->tail[0].iov_base = buf->head[0].iov_base + + buf->head[0].iov_len; + buf->tail[0].iov_len = 0; + } + + /* Append a copy of the plaintext GSS token header (RFC 4121 Sec 4.2.4) */ + memcpy(buf->tail[0].iov_base + buf->tail[0].iov_len, + buf->head[0].iov_base + offset, GSS_KRB5_TOK_HDR_LEN); + buf->tail[0].iov_len += GSS_KRB5_TOK_HDR_LEN; + buf->len += GSS_KRB5_TOK_HDR_LEN; + + /* Reserve space for the integrity checksum */ + buf->tail[0].iov_len += cksum_len; + buf->len += cksum_len; + + /* + * The AEAD operates in-place, but on the client send path the + * plaintext payload lives in page cache pages that must not be + * modified. Copy the payload into the scratch output pages + * first. On the server send path @pages and buf->pages are + * the same array, and no copy is needed. + * + * Both arrays share buf->page_base, so the same index and + * intra-page offset address corresponding data in each. + */ + if (pages != buf->pages) { + unsigned int poff = buf->page_base; + unsigned int plen = buf->page_len; + unsigned int i = poff >> PAGE_SHIFT; + unsigned int off = offset_in_page(poff); + + while (plen) { + unsigned int n = min_t(unsigned int, plen, + PAGE_SIZE - off); + memcpy_page(buf->pages[i], off, pages[i], off, n); + plen -= n; + i++; + off = 0; + } + } + + /* Build scatterlist covering the secured region */ + sec_offset = offset + GSS_KRB5_TOK_HDR_LEN; + sec_len = buf->len - sec_offset; + data_len = sec_len - conflen - cksum_len; + + nsg = xdr_buf_to_sg_alloc(buf, sec_offset, sec_len, + sg, ARRAY_SIZE(sg), + &sg_overflow, GFP_NOFS); + if (nsg < 0) + return GSS_S_FAILURE; + + ret = crypto_krb5_encrypt(krb5, aead, sg, nsg, sec_len, + conflen, data_len, false); + kfree(sg_overflow); + if (ret < 0) + return GSS_S_FAILURE; + + return GSS_S_COMPLETE; +} diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index 33d41d972bd1..ce43e1be7577 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -186,6 +186,11 @@ u32 krb5_etm_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, u32 gss_krb5_errno_to_status(int err); +u32 gss_krb5_aead_encrypt(struct krb5_ctx *kctx, u32 offset, + struct xdr_buf *buf, struct page **pages); +u32 gss_krb5_aead_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, + struct xdr_buf *buf, u32 *headskip, u32 *tailskip); + #if IS_ENABLED(CONFIG_KUNIT) void krb5_nfold(u32 inbits, const u8 *in, u32 outbits, u8 *out); const struct gss_krb5_enctype *gss_krb5_lookup_enctype(u32 etype); diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 35189c57fd0c..6cd7eb203350 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -43,7 +43,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .aux_cipher = "cbc(aes)", .cksum_name = "hmac(sha1)", .derive_key = krb5_derive_key_v2, - .encrypt = gss_krb5_aes_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aes_decrypt, .get_mic = gss_krb5_get_mic_v2, @@ -72,7 +72,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .aux_cipher = "cbc(aes)", .cksum_name = "hmac(sha1)", .derive_key = krb5_derive_key_v2, - .encrypt = gss_krb5_aes_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aes_decrypt, .get_mic = gss_krb5_get_mic_v2, @@ -111,7 +111,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(128), .derive_key = krb5_kdf_feedback_cmac, - .encrypt = gss_krb5_aes_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aes_decrypt, .get_mic = gss_krb5_get_mic_v2, @@ -137,7 +137,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(256), .derive_key = krb5_kdf_feedback_cmac, - .encrypt = gss_krb5_aes_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aes_decrypt, .get_mic = gss_krb5_get_mic_v2, @@ -166,7 +166,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(128), .derive_key = krb5_kdf_hmac_sha2, - .encrypt = krb5_etm_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = krb5_etm_decrypt, .get_mic = gss_krb5_get_mic_v2, @@ -192,7 +192,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(192), .derive_key = krb5_kdf_hmac_sha2, - .encrypt = krb5_etm_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = krb5_etm_decrypt, .get_mic = gss_krb5_get_mic_v2,