/* * linux/net/sunrpc/gss_krb5_crypto.c * * Copyright (c) 2000-2008 The Regents of the University of Michigan. * All rights reserved. * * Andy Adamson <andros@umich.edu> * Bruce Fields <bfields@umich.edu> */ /* * Copyright (C) 1998 by the FundsXpress, INC. * * All rights reserved. * * Export of this software from the United States of America may require * a specific license from the United States Government. It is the * responsibility of any person or organization contemplating export to * obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of FundsXpress. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. FundsXpress makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include <linux/err.h> #include <linux/types.h> #include <linux/mm.h> #include <linux/scatterlist.h> #include <linux/highmem.h> #include <linux/pagemap.h> #include <linux/sunrpc/gss_krb5.h> #include <linux/sunrpc/xdr.h> #include "gss_krb5_internal.h" #if IS_ENABLED(CONFIG_SUNRPC_DEBUG) # define RPCDBG_FACILITY RPCDBG_AUTH #endif /* * This function makes the assumption that it was ultimately called * from gss_wrap(). * * The client auth_gss code moves any existing tail data into a * separate page before calling gss_wrap. * The server svcauth_gss code ensures that both the head and the * tail have slack space of RPC_MAX_AUTH_SIZE before calling gss_wrap. * * Even with that guarantee, this function may be called more than * once in the processing of gss_wrap(). The best we can do is * verify at compile-time (see GSS_KRB5_MAX_SLACK_NEEDED) that the * largest expected shift will fit within RPC_MAX_AUTH_SIZE. * At run-time we can verify that a single invocation of this * function doesn't attempt to use more the RPC_MAX_AUTH_SIZE. */ int xdr_extend_head(struct xdr_buf *buf, unsigned int base, unsigned int shiftlen) { u8 *p; if (shiftlen == 0) return 0; BUG_ON(shiftlen > RPC_MAX_AUTH_SIZE); p = buf->head[0].iov_base + base; memmove(p + shiftlen, p, buf->head[0].iov_len - base); buf->head[0].iov_len += shiftlen; buf->len += shiftlen; return 0; } /** * 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; } /** * gss_krb5_aead_decrypt - Decrypt a wrap token using crypto/krb5 * @kctx: Kerberos context * @offset: byte offset of the GSS token header in @buf * @len: total length of the GSS token * @buf: ciphertext buffer, decrypted in-place * @headskip: OUT: confounder length, in octets * @tailskip: OUT: checksum length, in octets * * Return values: * %GSS_S_COMPLETE: Decryption and integrity verification succeeded * %GSS_S_BAD_SIG: Integrity checksum did not match * %GSS_S_DEFECTIVE_TOKEN: Token is malformed or truncated * %GSS_S_FAILURE: Decryption failed */ u32 gss_krb5_aead_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, struct xdr_buf *buf, u32 *headskip, u32 *tailskip) { const struct krb5_enctype *krb5 = kctx->krb5e; struct crypto_aead *aead = kctx->initiate ? kctx->acceptor_enc_aead : kctx->initiator_enc_aead; unsigned int sec_offset, sec_len; size_t data_offset, data_len; struct scatterlist sg[XDR_BUF_TO_SG_NENTS]; struct scatterlist *sg_overflow = NULL; int nsg, ret; /* Secured region starts after the GSS token header */ sec_offset = offset + GSS_KRB5_TOK_HDR_LEN; if (len < sec_offset) return GSS_S_DEFECTIVE_TOKEN; sec_len = len - sec_offset; 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; data_offset = 0; data_len = sec_len; ret = crypto_krb5_decrypt(krb5, aead, sg, nsg, &data_offset, &data_len); kfree(sg_overflow); if (ret < 0) return gss_krb5_errno_to_status(ret); *headskip = data_offset; *tailskip = sec_len - data_offset - data_len; return GSS_S_COMPLETE; } /** * gss_krb5_mic_build_sg - Build scatterlist for MIC token operations * @body: xdr_buf containing the message body * @cksum: pointer to checksum area in the token buffer * @cksum_len: length of checksum area * @hdr: pointer to GSS token header * @sg_head: caller-provided scatterlist array; if more than * XDR_BUF_TO_SG_NENTS entries are needed, an overflow * scatterlist is allocated and chained automatically * @sg_overflow: OUT: overflow scatterlist, caller must kfree * * Per RFC 4121 Section 4.2.4, MIC token checksums cover the * message body followed by the token header. The checksum * output or received checksum occupies the first scatterlist * entry. This layout cannot be constructed by * xdr_buf_to_sg_alloc() because the checksum area and the GSS * header lie outside the xdr_buf. * * Returns the number of scatterlist entries on success, or a * negative errno on failure. */ int gss_krb5_mic_build_sg(const struct xdr_buf *body, void *cksum, unsigned int cksum_len, void *hdr, struct scatterlist *sg_head, struct scatterlist **sg_overflow) { struct scatterlist *entry; int body_max, body_nsg, nsg; *sg_overflow = NULL; body_max = 2; if (body->page_len) body_max += DIV_ROUND_UP(body->page_len + offset_in_page(body->page_base), PAGE_SIZE); nsg = 1 + body_max + 1; if (nsg <= XDR_BUF_TO_SG_NENTS) { sg_init_table(sg_head, nsg); } else { unsigned int overflow_nents = nsg - XDR_BUF_TO_SG_NENTS + 1; *sg_overflow = kmalloc_array(overflow_nents, sizeof(**sg_overflow), GFP_NOFS); if (!*sg_overflow) return -ENOMEM; sg_init_table(sg_head, XDR_BUF_TO_SG_NENTS); sg_init_table(*sg_overflow, overflow_nents); sg_chain(sg_head, XDR_BUF_TO_SG_NENTS, *sg_overflow); } sg_set_buf(&sg_head[0], cksum, cksum_len); body_nsg = xdr_buf_to_sg(body, 0, body->len, sg_next(&sg_head[0]), body_max); if (body_nsg < 0) goto out_err; /* * xdr_buf_to_sg marks the last body entry as end-of-list; * clear it so the trailing header entry is reachable. */ if (body_nsg > 0) { entry = sg_last(sg_next(&sg_head[0]), body_nsg); sg_unmark_end(entry); entry = sg_next(entry); } else { entry = sg_next(&sg_head[0]); } sg_set_buf(entry, hdr, GSS_KRB5_TOK_HDR_LEN); sg_mark_end(entry); return 1 + body_nsg + 1; out_err: kfree(*sg_overflow); *sg_overflow = NULL; return body_nsg; }