root/net/rxrpc/rxgk_app.c
// SPDX-License-Identifier: GPL-2.0-or-later
/* Application-specific bits for GSSAPI-based RxRPC security
 *
 * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/key-type.h>
#include "ar-internal.h"
#include "rxgk_common.h"

/*
 * Decode a default-style YFS ticket in a response and turn it into an
 * rxrpc-type key.
 *
 * struct rxgk_key {
 *      afs_uint32      enctype;
 *      opaque          key<>;
 * };
 *
 * struct RXGK_AuthName {
 *      afs_int32       kind;
 *      opaque          data<AUTHDATAMAX>;
 *      opaque          display<AUTHPRINTABLEMAX>;
 * };
 *
 * struct RXGK_Token {
 *      rxgk_key                K0;
 *      RXGK_Level              level;
 *      rxgkTime                starttime;
 *      afs_int32               lifetime;
 *      afs_int32               bytelife;
 *      rxgkTime                expirationtime;
 *      struct RXGK_AuthName    identities<>;
 * };
 */
int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb,
                           unsigned int ticket_offset, unsigned int ticket_len,
                           struct key **_key)
{
        struct rxrpc_key_token *token;
        const struct cred *cred = current_cred(); // TODO - use socket creds
        struct key *key;
        size_t pre_ticket_len, payload_len;
        unsigned int klen, enctype;
        void *payload, *ticket;
        __be32 *t, *p, *q, tmp[2];
        int ret;

        _enter("");

        if (ticket_len < 10 * sizeof(__be32))
                return rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO,
                                        rxgk_abort_resp_short_yfs_tkt);

        /* Get the session key length */
        ret = skb_copy_bits(skb, ticket_offset, tmp, sizeof(tmp));
        if (ret < 0)
                return rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO,
                                        rxgk_abort_resp_short_yfs_klen);
        enctype = ntohl(tmp[0]);
        klen = ntohl(tmp[1]);

        if (klen > ticket_len - 10 * sizeof(__be32))
                return rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO,
                                        rxgk_abort_resp_short_yfs_key);

        pre_ticket_len = ((5 + 14) * sizeof(__be32) +
                          xdr_round_up(klen) +
                          sizeof(__be32));
        payload_len = pre_ticket_len + xdr_round_up(ticket_len);

        payload = kzalloc(payload_len, GFP_NOFS);
        if (!payload)
                return -ENOMEM;

        /* We need to fill out the XDR form for a key payload that we can pass
         * to add_key().  Start by copying in the ticket so that we can parse
         * it.
         */
        ticket = payload + pre_ticket_len;
        ret = skb_copy_bits(skb, ticket_offset, ticket, ticket_len);
        if (ret < 0) {
                ret = rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO,
                                       rxgk_abort_resp_short_yfs_tkt);
                goto error;
        }

        /* Fill out the form header. */
        p = payload;
        p[0] = htonl(0); /* Flags */
        p[1] = htonl(1); /* len(cellname) */
        p[2] = htonl(0x20000000); /* Cellname " " */
        p[3] = htonl(1); /* #tokens */
        p[4] = htonl(15 * sizeof(__be32) + xdr_round_up(klen) +
                     xdr_round_up(ticket_len)); /* Token len */

        /* Now fill in the body.  Most of this we can just scrape directly from
         * the ticket.
         */
        t = ticket + sizeof(__be32) * 2 + xdr_round_up(klen);
        q = payload + 5 * sizeof(__be32);
        q[0]  = htonl(RXRPC_SECURITY_YFS_RXGK);
        q[1]  = t[1];           /* begintime - msw */
        q[2]  = t[2];           /* - lsw */
        q[3]  = t[5];           /* endtime - msw */
        q[4]  = t[6];           /* - lsw */
        q[5]  = 0;              /* level - msw */
        q[6]  = t[0];           /* - lsw */
        q[7]  = 0;              /* lifetime - msw */
        q[8]  = t[3];           /* - lsw */
        q[9]  = 0;              /* bytelife - msw */
        q[10] = t[4];           /* - lsw */
        q[11] = 0;              /* enctype - msw */
        q[12] = htonl(enctype); /* - lsw */
        q[13] = htonl(klen);    /* Key length */

        q += 14;

        memcpy(q, ticket + sizeof(__be32) * 2, klen);
        q += xdr_round_up(klen) / 4;
        q[0] = htonl(ticket_len);
        q++;
        if (WARN_ON((unsigned long)q != (unsigned long)ticket)) {
                ret = -EIO;
                goto error;
        }

        /* Ticket read in with skb_copy_bits above */
        q += xdr_round_up(ticket_len) / 4;
        if (WARN_ON((unsigned long)q - (unsigned long)payload != payload_len)) {
                ret = -EIO;
                goto error;
        }

        /* Now turn that into a key. */
        key = key_alloc(&key_type_rxrpc, "x",
                        GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred, // TODO: Use socket owner
                        KEY_USR_VIEW,
                        KEY_ALLOC_NOT_IN_QUOTA, NULL);
        if (IS_ERR(key)) {
                _leave(" = -ENOMEM [alloc %ld]", PTR_ERR(key));
                ret = PTR_ERR(key);
                goto error;
        }

        _debug("key %d", key_serial(key));

        ret = key_instantiate_and_link(key, payload, payload_len, NULL, NULL);
        if (ret < 0)
                goto error_key;

        token = key->payload.data[0];
        token->no_leak_key = true;
        *_key = key;
        key = NULL;
        ret = 0;
        goto error;

error_key:
        key_put(key);
error:
        kfree_sensitive(payload);
        _leave(" = %d", ret);
        return ret;
}

/*
 * Extract the token and set up a session key from the details.
 *
 * struct RXGK_TokenContainer {
 *      afs_int32       kvno;
 *      afs_int32       enctype;
 *      opaque          encrypted_token<>;
 * };
 *
 * [tools.ietf.org/html/draft-wilkinson-afs3-rxgk-afs-08 sec 6.1]
 */
int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb,
                       unsigned int token_offset, unsigned int token_len,
                       struct key **_key)
{
        const struct krb5_enctype *krb5;
        const struct krb5_buffer *server_secret;
        struct crypto_aead *token_enc = NULL;
        struct key *server_key;
        unsigned int ticket_offset, ticket_len;
        u32 kvno, enctype;
        int ret, ec = 0;

        struct {
                __be32 kvno;
                __be32 enctype;
                __be32 token_len;
        } container;

        if (token_len < sizeof(container))
                goto short_packet;

        /* Decode the RXGK_TokenContainer object.  This tells us which server
         * key we should be using.  We can then fetch the key, get the secret
         * and set up the crypto to extract the token.
         */
        if (skb_copy_bits(skb, token_offset, &container, sizeof(container)) < 0)
                goto short_packet;

        kvno            = ntohl(container.kvno);
        enctype         = ntohl(container.enctype);
        ticket_len      = ntohl(container.token_len);
        ticket_offset   = token_offset + sizeof(container);

        if (xdr_round_up(ticket_len) > token_len - sizeof(container))
                goto short_packet;

        _debug("KVNO %u", kvno);
        _debug("ENC  %u", enctype);
        _debug("TLEN %u", ticket_len);

        server_key = rxrpc_look_up_server_security(conn, skb, kvno, enctype);
        if (IS_ERR(server_key))
                goto cant_get_server_key;

        down_read(&server_key->sem);
        server_secret = (const void *)&server_key->payload.data[2];
        ret = rxgk_set_up_token_cipher(server_secret, &token_enc, enctype, &krb5, GFP_NOFS);
        up_read(&server_key->sem);
        key_put(server_key);
        if (ret < 0)
                goto cant_get_token;

        /* We can now decrypt and parse the token/ticket.  This allows us to
         * gain access to K0, from which we can derive the transport key and
         * thence decode the authenticator.
         */
        ret = rxgk_decrypt_skb(krb5, token_enc, skb,
                               &ticket_offset, &ticket_len, &ec);
        crypto_free_aead(token_enc);
        token_enc = NULL;
        if (ret < 0) {
                if (ret != -ENOMEM)
                        return rxrpc_abort_conn(conn, skb, ec, ret,
                                                rxgk_abort_resp_tok_dec);
        }

        ret = conn->security->default_decode_ticket(conn, skb, ticket_offset,
                                                    ticket_len, _key);
        if (ret < 0)
                goto cant_get_token;

        _leave(" = 0");
        return ret;

cant_get_server_key:
        ret = PTR_ERR(server_key);
        switch (ret) {
        case -ENOMEM:
                goto temporary_error;
        case -ENOKEY:
        case -EKEYREJECTED:
        case -EKEYEXPIRED:
        case -EKEYREVOKED:
        case -EPERM:
                return rxrpc_abort_conn(conn, skb, RXGK_BADKEYNO, -EKEYREJECTED,
                                        rxgk_abort_resp_tok_nokey);
        default:
                return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EKEYREJECTED,
                                        rxgk_abort_resp_tok_keyerr);
        }

cant_get_token:
        switch (ret) {
        case -ENOMEM:
                goto temporary_error;
        case -EINVAL:
                return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EKEYREJECTED,
                                        rxgk_abort_resp_tok_internal_error);
        case -ENOPKG:
                return rxrpc_abort_conn(conn, skb, KRB5_PROG_KEYTYPE_NOSUPP,
                                        -EKEYREJECTED, rxgk_abort_resp_tok_nopkg);
        }

temporary_error:
        /* Ignore the response packet if we got a temporary error such as
         * ENOMEM.  We just want to send the challenge again.  Note that we
         * also come out this way if the ticket decryption fails.
         */
        return ret;

short_packet:
        return rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO,
                                rxgk_abort_resp_tok_short);
}