blob: 33e8302a79e3315eb4ebb9ea856ba1901544683d [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0-or-later
/* RxRPC key management
*
* Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* RxRPC keys should have a description of describing their purpose:
* "afs@example.com"
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <crypto/skcipher.h>
#include <linux/module.h>
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/key-type.h>
#include <linux/ctype.h>
#include <linux/slab.h>
#include <net/sock.h>
#include <net/af_rxrpc.h>
#include <keys/rxrpc-type.h>
#include <keys/user-type.h>
#include "ar-internal.h"
static int rxrpc_preparse(struct key_preparsed_payload *);
static void rxrpc_free_preparse(struct key_preparsed_payload *);
static void rxrpc_destroy(struct key *);
static void rxrpc_describe(const struct key *, struct seq_file *);
static long rxrpc_read(const struct key *, char *, size_t);
/*
* rxrpc defined keys take an arbitrary string as the description and an
* arbitrary blob of data as the payload
*/
struct key_type key_type_rxrpc = {
.name = "rxrpc",
.flags = KEY_TYPE_NET_DOMAIN,
.preparse = rxrpc_preparse,
.free_preparse = rxrpc_free_preparse,
.instantiate = generic_key_instantiate,
.destroy = rxrpc_destroy,
.describe = rxrpc_describe,
.read = rxrpc_read,
};
EXPORT_SYMBOL(key_type_rxrpc);
/*
* parse an RxKAD type XDR format token
* - the caller guarantees we have at least 4 words
*/
static int rxrpc_preparse_xdr_rxkad(struct key_preparsed_payload *prep,
size_t datalen,
const __be32 *xdr, unsigned int toklen)
{
struct rxrpc_key_token *token, **pptoken;
time64_t expiry;
size_t plen;
u32 tktlen;
_enter(",{%x,%x,%x,%x},%u",
ntohl(xdr[0]), ntohl(xdr[1]), ntohl(xdr[2]), ntohl(xdr[3]),
toklen);
if (toklen <= 8 * 4)
return -EKEYREJECTED;
tktlen = ntohl(xdr[7]);
_debug("tktlen: %x", tktlen);
if (tktlen > AFSTOKEN_RK_TIX_MAX)
return -EKEYREJECTED;
if (toklen < 8 * 4 + tktlen)
return -EKEYREJECTED;
plen = sizeof(*token) + sizeof(*token->kad) + tktlen;
prep->quotalen = datalen + plen;
plen -= sizeof(*token);
token = kzalloc(sizeof(*token), GFP_KERNEL);
if (!token)
return -ENOMEM;
token->kad = kzalloc(plen, GFP_KERNEL);
if (!token->kad) {
kfree(token);
return -ENOMEM;
}
token->security_index = RXRPC_SECURITY_RXKAD;
token->kad->ticket_len = tktlen;
token->kad->vice_id = ntohl(xdr[0]);
token->kad->kvno = ntohl(xdr[1]);
token->kad->start = ntohl(xdr[4]);
token->kad->expiry = ntohl(xdr[5]);
token->kad->primary_flag = ntohl(xdr[6]);
memcpy(&token->kad->session_key, &xdr[2], 8);
memcpy(&token->kad->ticket, &xdr[8], tktlen);
_debug("SCIX: %u", token->security_index);
_debug("TLEN: %u", token->kad->ticket_len);
_debug("EXPY: %x", token->kad->expiry);
_debug("KVNO: %u", token->kad->kvno);
_debug("PRIM: %u", token->kad->primary_flag);
_debug("SKEY: %02x%02x%02x%02x%02x%02x%02x%02x",
token->kad->session_key[0], token->kad->session_key[1],
token->kad->session_key[2], token->kad->session_key[3],
token->kad->session_key[4], token->kad->session_key[5],
token->kad->session_key[6], token->kad->session_key[7]);
if (token->kad->ticket_len >= 8)
_debug("TCKT: %02x%02x%02x%02x%02x%02x%02x%02x",
token->kad->ticket[0], token->kad->ticket[1],
token->kad->ticket[2], token->kad->ticket[3],
token->kad->ticket[4], token->kad->ticket[5],
token->kad->ticket[6], token->kad->ticket[7]);
/* count the number of tokens attached */
prep->payload.data[1] = (void *)((unsigned long)prep->payload.data[1] + 1);
/* attach the data */
for (pptoken = (struct rxrpc_key_token **)&prep->payload.data[0];
*pptoken;
pptoken = &(*pptoken)->next)
continue;
*pptoken = token;
expiry = rxrpc_u32_to_time64(token->kad->expiry);
if (expiry < prep->expiry)
prep->expiry = expiry;
_leave(" = 0");
return 0;
}
/*
* attempt to parse the data as the XDR format
* - the caller guarantees we have more than 7 words
*/
static int rxrpc_preparse_xdr(struct key_preparsed_payload *prep)
{
const __be32 *xdr = prep->data, *token, *p;
const char *cp;
unsigned int len, paddedlen, loop, ntoken, toklen, sec_ix;
size_t datalen = prep->datalen;
int ret, ret2;
_enter(",{%x,%x,%x,%x},%zu",
ntohl(xdr[0]), ntohl(xdr[1]), ntohl(xdr[2]), ntohl(xdr[3]),
prep->datalen);
if (datalen > AFSTOKEN_LENGTH_MAX)
goto not_xdr;
/* XDR is an array of __be32's */
if (datalen & 3)
goto not_xdr;
/* the flags should be 0 (the setpag bit must be handled by
* userspace) */
if (ntohl(*xdr++) != 0)
goto not_xdr;
datalen -= 4;
/* check the cell name */
len = ntohl(*xdr++);
if (len < 1 || len > AFSTOKEN_CELL_MAX)
goto not_xdr;
datalen -= 4;
paddedlen = (len + 3) & ~3;
if (paddedlen > datalen)
goto not_xdr;
cp = (const char *) xdr;
for (loop = 0; loop < len; loop++)
if (!isprint(cp[loop]))
goto not_xdr;
for (; loop < paddedlen; loop++)
if (cp[loop])
goto not_xdr;
_debug("cellname: [%u/%u] '%*.*s'",
len, paddedlen, len, len, (const char *) xdr);
datalen -= paddedlen;
xdr += paddedlen >> 2;
/* get the token count */
if (datalen < 12)
goto not_xdr;
ntoken = ntohl(*xdr++);
datalen -= 4;
_debug("ntoken: %x", ntoken);
if (ntoken < 1 || ntoken > AFSTOKEN_MAX)
goto not_xdr;
/* check each token wrapper */
p = xdr;
loop = ntoken;
do {
if (datalen < 8)
goto not_xdr;
toklen = ntohl(*p++);
sec_ix = ntohl(*p);
datalen -= 4;
_debug("token: [%x/%zx] %x", toklen, datalen, sec_ix);
paddedlen = (toklen + 3) & ~3;
if (toklen < 20 || toklen > datalen || paddedlen > datalen)
goto not_xdr;
datalen -= paddedlen;
p += paddedlen >> 2;
} while (--loop > 0);
_debug("remainder: %zu", datalen);
if (datalen != 0)
goto not_xdr;
/* okay: we're going to assume it's valid XDR format
* - we ignore the cellname, relying on the key to be correctly named
*/
ret = -EPROTONOSUPPORT;
do {
toklen = ntohl(*xdr++);
token = xdr;
xdr += (toklen + 3) / 4;
sec_ix = ntohl(*token++);
toklen -= 4;
_debug("TOKEN type=%x len=%x", sec_ix, toklen);
switch (sec_ix) {
case RXRPC_SECURITY_RXKAD:
ret2 = rxrpc_preparse_xdr_rxkad(prep, datalen, token, toklen);
break;
default:
ret2 = -EPROTONOSUPPORT;
break;
}
switch (ret2) {
case 0:
ret = 0;
break;
case -EPROTONOSUPPORT:
break;
case -ENOPKG:
if (ret != 0)
ret = -ENOPKG;
break;
default:
ret = ret2;
goto error;
}
} while (--ntoken > 0);
error:
_leave(" = %d", ret);
return ret;
not_xdr:
_leave(" = -EPROTO");
return -EPROTO;
}
/*
* Preparse an rxrpc defined key.
*
* Data should be of the form:
* OFFSET LEN CONTENT
* 0 4 key interface version number
* 4 2 security index (type)
* 6 2 ticket length
* 8 4 key expiry time (time_t)
* 12 4 kvno
* 16 8 session key
* 24 [len] ticket
*
* if no data is provided, then a no-security key is made
*/
static int rxrpc_preparse(struct key_preparsed_payload *prep)
{
const struct rxrpc_key_data_v1 *v1;
struct rxrpc_key_token *token, **pp;
time64_t expiry;
size_t plen;
u32 kver;
int ret;
_enter("%zu", prep->datalen);
/* handle a no-security key */
if (!prep->data && prep->datalen == 0)
return 0;
/* determine if the XDR payload format is being used */
if (prep->datalen > 7 * 4) {
ret = rxrpc_preparse_xdr(prep);
if (ret != -EPROTO)
return ret;
}
/* get the key interface version number */
ret = -EINVAL;
if (prep->datalen <= 4 || !prep->data)
goto error;
memcpy(&kver, prep->data, sizeof(kver));
prep->data += sizeof(kver);
prep->datalen -= sizeof(kver);
_debug("KEY I/F VERSION: %u", kver);
ret = -EKEYREJECTED;
if (kver != 1)
goto error;
/* deal with a version 1 key */
ret = -EINVAL;
if (prep->datalen < sizeof(*v1))
goto error;
v1 = prep->data;
if (prep->datalen != sizeof(*v1) + v1->ticket_length)
goto error;
_debug("SCIX: %u", v1->security_index);
_debug("TLEN: %u", v1->ticket_length);
_debug("EXPY: %x", v1->expiry);
_debug("KVNO: %u", v1->kvno);
_debug("SKEY: %02x%02x%02x%02x%02x%02x%02x%02x",
v1->session_key[0], v1->session_key[1],
v1->session_key[2], v1->session_key[3],
v1->session_key[4], v1->session_key[5],
v1->session_key[6], v1->session_key[7]);
if (v1->ticket_length >= 8)
_debug("TCKT: %02x%02x%02x%02x%02x%02x%02x%02x",
v1->ticket[0], v1->ticket[1],
v1->ticket[2], v1->ticket[3],
v1->ticket[4], v1->ticket[5],
v1->ticket[6], v1->ticket[7]);
ret = -EPROTONOSUPPORT;
if (v1->security_index != RXRPC_SECURITY_RXKAD)
goto error;
plen = sizeof(*token->kad) + v1->ticket_length;
prep->quotalen = plen + sizeof(*token);
ret = -ENOMEM;
token = kzalloc(sizeof(*token), GFP_KERNEL);
if (!token)
goto error;
token->kad = kzalloc(plen, GFP_KERNEL);
if (!token->kad)
goto error_free;
token->security_index = RXRPC_SECURITY_RXKAD;
token->kad->ticket_len = v1->ticket_length;
token->kad->expiry = v1->expiry;
token->kad->kvno = v1->kvno;
memcpy(&token->kad->session_key, &v1->session_key, 8);
memcpy(&token->kad->ticket, v1->ticket, v1->ticket_length);
/* count the number of tokens attached */
prep->payload.data[1] = (void *)((unsigned long)prep->payload.data[1] + 1);
/* attach the data */
pp = (struct rxrpc_key_token **)&prep->payload.data[0];
while (*pp)
pp = &(*pp)->next;
*pp = token;
expiry = rxrpc_u32_to_time64(token->kad->expiry);
if (expiry < prep->expiry)
prep->expiry = expiry;
token = NULL;
ret = 0;
error_free:
kfree(token);
error:
return ret;
}
/*
* Free token list.
*/
static void rxrpc_free_token_list(struct rxrpc_key_token *token)
{
struct rxrpc_key_token *next;
for (; token; token = next) {
next = token->next;
switch (token->security_index) {
case RXRPC_SECURITY_RXKAD:
kfree(token->kad);
break;
default:
pr_err("Unknown token type %x on rxrpc key\n",
token->security_index);
BUG();
}
kfree(token);
}
}
/*
* Clean up preparse data.
*/
static void rxrpc_free_preparse(struct key_preparsed_payload *prep)
{
rxrpc_free_token_list(prep->payload.data[0]);
}
/*
* dispose of the data dangling from the corpse of a rxrpc key
*/
static void rxrpc_destroy(struct key *key)
{
rxrpc_free_token_list(key->payload.data[0]);
}
/*
* describe the rxrpc key
*/
static void rxrpc_describe(const struct key *key, struct seq_file *m)
{
const struct rxrpc_key_token *token;
const char *sep = ": ";
seq_puts(m, key->description);
for (token = key->payload.data[0]; token; token = token->next) {
seq_puts(m, sep);
switch (token->security_index) {
case RXRPC_SECURITY_RXKAD:
seq_puts(m, "ka");
break;
default: /* we have a ticket we can't encode */
seq_printf(m, "%u", token->security_index);
break;
}
sep = " ";
}
}
/*
* grab the security key for a socket
*/
int rxrpc_request_key(struct rxrpc_sock *rx, sockptr_t optval, int optlen)
{
struct key *key;
char *description;
_enter("");
if (optlen <= 0 || optlen > PAGE_SIZE - 1 || rx->securities)
return -EINVAL;
description = memdup_sockptr_nul(optval, optlen);
if (IS_ERR(description))
return PTR_ERR(description);
key = request_key_net(&key_type_rxrpc, description, sock_net(&rx->sk), NULL);
if (IS_ERR(key)) {
kfree(description);
_leave(" = %ld", PTR_ERR(key));
return PTR_ERR(key);
}
rx->key = key;
kfree(description);
_leave(" = 0 [key %x]", key->serial);
return 0;
}
/*
* generate a server data key
*/
int rxrpc_get_server_data_key(struct rxrpc_connection *conn,
const void *session_key,
time64_t expiry,
u32 kvno)
{
const struct cred *cred = current_cred();
struct key *key;
int ret;
struct {
u32 kver;
struct rxrpc_key_data_v1 v1;
} data;
_enter("");
key = key_alloc(&key_type_rxrpc, "x",
GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred, 0,
KEY_ALLOC_NOT_IN_QUOTA, NULL);
if (IS_ERR(key)) {
_leave(" = -ENOMEM [alloc %ld]", PTR_ERR(key));
return -ENOMEM;
}
_debug("key %d", key_serial(key));
data.kver = 1;
data.v1.security_index = RXRPC_SECURITY_RXKAD;
data.v1.ticket_length = 0;
data.v1.expiry = rxrpc_time64_to_u32(expiry);
data.v1.kvno = 0;
memcpy(&data.v1.session_key, session_key, sizeof(data.v1.session_key));
ret = key_instantiate_and_link(key, &data, sizeof(data), NULL, NULL);
if (ret < 0)
goto error;
conn->key = key;
_leave(" = 0 [%d]", key_serial(key));
return 0;
error:
key_revoke(key);
key_put(key);
_leave(" = -ENOMEM [ins %d]", ret);
return -ENOMEM;
}
EXPORT_SYMBOL(rxrpc_get_server_data_key);
/**
* rxrpc_get_null_key - Generate a null RxRPC key
* @keyname: The name to give the key.
*
* Generate a null RxRPC key that can be used to indicate anonymous security is
* required for a particular domain.
*/
struct key *rxrpc_get_null_key(const char *keyname)
{
const struct cred *cred = current_cred();
struct key *key;
int ret;
key = key_alloc(&key_type_rxrpc, keyname,
GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
KEY_POS_SEARCH, KEY_ALLOC_NOT_IN_QUOTA, NULL);
if (IS_ERR(key))
return key;
ret = key_instantiate_and_link(key, NULL, 0, NULL, NULL);
if (ret < 0) {
key_revoke(key);
key_put(key);
return ERR_PTR(ret);
}
return key;
}
EXPORT_SYMBOL(rxrpc_get_null_key);
/*
* read the contents of an rxrpc key
* - this returns the result in XDR form
*/
static long rxrpc_read(const struct key *key,
char *buffer, size_t buflen)
{
const struct rxrpc_key_token *token;
size_t size;
__be32 *xdr, *oldxdr;
u32 cnlen, toksize, ntoks, tok, zero;
u16 toksizes[AFSTOKEN_MAX];
_enter("");
/* we don't know what form we should return non-AFS keys in */
if (memcmp(key->description, "afs@", 4) != 0)
return -EOPNOTSUPP;
cnlen = strlen(key->description + 4);
#define RND(X) (((X) + 3) & ~3)
/* AFS keys we return in XDR form, so we need to work out the size of
* the XDR */
size = 2 * 4; /* flags, cellname len */
size += RND(cnlen); /* cellname */
size += 1 * 4; /* token count */
ntoks = 0;
for (token = key->payload.data[0]; token; token = token->next) {
toksize = 4; /* sec index */
switch (token->security_index) {
case RXRPC_SECURITY_RXKAD:
toksize += 8 * 4; /* viceid, kvno, key*2, begin,
* end, primary, tktlen */
if (!token->no_leak_key)
toksize += RND(token->kad->ticket_len);
break;
default: /* we have a ticket we can't encode */
pr_err("Unsupported key token type (%u)\n",
token->security_index);
return -ENOPKG;
}
_debug("token[%u]: toksize=%u", ntoks, toksize);
if (WARN_ON(toksize > AFSTOKEN_LENGTH_MAX))
return -EIO;
toksizes[ntoks++] = toksize;
size += toksize + 4; /* each token has a length word */
}
#undef RND
if (!buffer || buflen < size)
return size;
xdr = (__be32 *)buffer;
zero = 0;
#define ENCODE(x) \
do { \
*xdr++ = htonl(x); \
} while(0)
#define ENCODE_DATA(l, s) \
do { \
u32 _l = (l); \
ENCODE(l); \
memcpy(xdr, (s), _l); \
if (_l & 3) \
memcpy((u8 *)xdr + _l, &zero, 4 - (_l & 3)); \
xdr += (_l + 3) >> 2; \
} while(0)
#define ENCODE_BYTES(l, s) \
do { \
u32 _l = (l); \
memcpy(xdr, (s), _l); \
if (_l & 3) \
memcpy((u8 *)xdr + _l, &zero, 4 - (_l & 3)); \
xdr += (_l + 3) >> 2; \
} while(0)
#define ENCODE64(x) \
do { \
__be64 y = cpu_to_be64(x); \
memcpy(xdr, &y, 8); \
xdr += 8 >> 2; \
} while(0)
#define ENCODE_STR(s) \
do { \
const char *_s = (s); \
ENCODE_DATA(strlen(_s), _s); \
} while(0)
ENCODE(0); /* flags */
ENCODE_DATA(cnlen, key->description + 4); /* cellname */
ENCODE(ntoks);
tok = 0;
for (token = key->payload.data[0]; token; token = token->next) {
toksize = toksizes[tok++];
ENCODE(toksize);
oldxdr = xdr;
ENCODE(token->security_index);
switch (token->security_index) {
case RXRPC_SECURITY_RXKAD:
ENCODE(token->kad->vice_id);
ENCODE(token->kad->kvno);
ENCODE_BYTES(8, token->kad->session_key);
ENCODE(token->kad->start);
ENCODE(token->kad->expiry);
ENCODE(token->kad->primary_flag);
if (token->no_leak_key)
ENCODE(0);
else
ENCODE_DATA(token->kad->ticket_len, token->kad->ticket);
break;
default:
pr_err("Unsupported key token type (%u)\n",
token->security_index);
return -ENOPKG;
}
if (WARN_ON((unsigned long)xdr - (unsigned long)oldxdr !=
toksize))
return -EIO;
}
#undef ENCODE_STR
#undef ENCODE_DATA
#undef ENCODE64
#undef ENCODE
if (WARN_ON(tok != ntoks))
return -EIO;
if (WARN_ON((unsigned long)xdr - (unsigned long)buffer != size))
return -EIO;
_leave(" = %zu", size);
return size;
}