| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- // 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);
- }
|