#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"
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();
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);
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;
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;
}
p = payload;
p[0] = htonl(0);
p[1] = htonl(1);
p[2] = htonl(0x20000000);
p[3] = htonl(1);
p[4] = htonl(15 * sizeof(__be32) + xdr_round_up(klen) +
xdr_round_up(ticket_len));
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];
q[2] = t[2];
q[3] = t[5];
q[4] = t[6];
q[5] = 0;
q[6] = t[0];
q[7] = 0;
q[8] = t[3];
q[9] = 0;
q[10] = t[4];
q[11] = 0;
q[12] = htonl(enctype);
q[13] = htonl(klen);
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;
}
q += xdr_round_up(ticket_len) / 4;
if (WARN_ON((unsigned long)q - (unsigned long)payload != payload_len)) {
ret = -EIO;
goto error;
}
key = key_alloc(&key_type_rxrpc, "x",
GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
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;
}
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;
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;
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:
return ret;
short_packet:
return rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO,
rxgk_abort_resp_tok_short);
}