// 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 #include #include #include #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; * opaque display; * }; * * 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(""); /* 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; struct { __be32 kvno; __be32 enctype; __be32 token_len; } container; /* 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) return rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO, rxgk_abort_resp_tok_short); 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 - 3 * 4) return rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO, rxgk_abort_resp_tok_short); _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) 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; }