root/net/sunrpc/auth_gss/gss_krb5_crypto.c
/*
 *  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;
}