forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1407 lines
35 KiB
1407 lines
35 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* Kerberos-based RxRPC security |
|
* |
|
* Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. |
|
* Written by David Howells ([email protected]) |
|
*/ |
|
|
|
#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/udp.h> |
|
#include <linux/scatterlist.h> |
|
#include <linux/ctype.h> |
|
#include <linux/slab.h> |
|
#include <linux/key-type.h> |
|
#include <net/sock.h> |
|
#include <net/af_rxrpc.h> |
|
#include <keys/rxrpc-type.h> |
|
#include "ar-internal.h" |
|
|
|
#define RXKAD_VERSION 2 |
|
#define MAXKRB5TICKETLEN 1024 |
|
#define RXKAD_TKT_TYPE_KERBEROS_V5 256 |
|
#define ANAME_SZ 40 /* size of authentication name */ |
|
#define INST_SZ 40 /* size of principal's instance */ |
|
#define REALM_SZ 40 /* size of principal's auth domain */ |
|
#define SNAME_SZ 40 /* size of service name */ |
|
#define RXKAD_ALIGN 8 |
|
|
|
struct rxkad_level1_hdr { |
|
__be32 data_size; /* true data size (excluding padding) */ |
|
}; |
|
|
|
struct rxkad_level2_hdr { |
|
__be32 data_size; /* true data size (excluding padding) */ |
|
__be32 checksum; /* decrypted data checksum */ |
|
}; |
|
|
|
static int rxkad_prime_packet_security(struct rxrpc_connection *conn, |
|
struct crypto_sync_skcipher *ci); |
|
|
|
/* |
|
* this holds a pinned cipher so that keventd doesn't get called by the cipher |
|
* alloc routine, but since we have it to hand, we use it to decrypt RESPONSE |
|
* packets |
|
*/ |
|
static struct crypto_sync_skcipher *rxkad_ci; |
|
static struct skcipher_request *rxkad_ci_req; |
|
static DEFINE_MUTEX(rxkad_ci_mutex); |
|
|
|
/* |
|
* Parse the information from a server key |
|
* |
|
* The data should be the 8-byte secret key. |
|
*/ |
|
static int rxkad_preparse_server_key(struct key_preparsed_payload *prep) |
|
{ |
|
struct crypto_skcipher *ci; |
|
|
|
if (prep->datalen != 8) |
|
return -EINVAL; |
|
|
|
memcpy(&prep->payload.data[2], prep->data, 8); |
|
|
|
ci = crypto_alloc_skcipher("pcbc(des)", 0, CRYPTO_ALG_ASYNC); |
|
if (IS_ERR(ci)) { |
|
_leave(" = %ld", PTR_ERR(ci)); |
|
return PTR_ERR(ci); |
|
} |
|
|
|
if (crypto_skcipher_setkey(ci, prep->data, 8) < 0) |
|
BUG(); |
|
|
|
prep->payload.data[0] = ci; |
|
_leave(" = 0"); |
|
return 0; |
|
} |
|
|
|
static void rxkad_free_preparse_server_key(struct key_preparsed_payload *prep) |
|
{ |
|
|
|
if (prep->payload.data[0]) |
|
crypto_free_skcipher(prep->payload.data[0]); |
|
} |
|
|
|
static void rxkad_destroy_server_key(struct key *key) |
|
{ |
|
if (key->payload.data[0]) { |
|
crypto_free_skcipher(key->payload.data[0]); |
|
key->payload.data[0] = NULL; |
|
} |
|
} |
|
|
|
/* |
|
* initialise connection security |
|
*/ |
|
static int rxkad_init_connection_security(struct rxrpc_connection *conn, |
|
struct rxrpc_key_token *token) |
|
{ |
|
struct crypto_sync_skcipher *ci; |
|
int ret; |
|
|
|
_enter("{%d},{%x}", conn->debug_id, key_serial(conn->params.key)); |
|
|
|
conn->security_ix = token->security_index; |
|
|
|
ci = crypto_alloc_sync_skcipher("pcbc(fcrypt)", 0, 0); |
|
if (IS_ERR(ci)) { |
|
_debug("no cipher"); |
|
ret = PTR_ERR(ci); |
|
goto error; |
|
} |
|
|
|
if (crypto_sync_skcipher_setkey(ci, token->kad->session_key, |
|
sizeof(token->kad->session_key)) < 0) |
|
BUG(); |
|
|
|
switch (conn->params.security_level) { |
|
case RXRPC_SECURITY_PLAIN: |
|
case RXRPC_SECURITY_AUTH: |
|
case RXRPC_SECURITY_ENCRYPT: |
|
break; |
|
default: |
|
ret = -EKEYREJECTED; |
|
goto error; |
|
} |
|
|
|
ret = rxkad_prime_packet_security(conn, ci); |
|
if (ret < 0) |
|
goto error_ci; |
|
|
|
conn->rxkad.cipher = ci; |
|
return 0; |
|
|
|
error_ci: |
|
crypto_free_sync_skcipher(ci); |
|
error: |
|
_leave(" = %d", ret); |
|
return ret; |
|
} |
|
|
|
/* |
|
* Work out how much data we can put in a packet. |
|
*/ |
|
static int rxkad_how_much_data(struct rxrpc_call *call, size_t remain, |
|
size_t *_buf_size, size_t *_data_size, size_t *_offset) |
|
{ |
|
size_t shdr, buf_size, chunk; |
|
|
|
switch (call->conn->params.security_level) { |
|
default: |
|
buf_size = chunk = min_t(size_t, remain, RXRPC_JUMBO_DATALEN); |
|
shdr = 0; |
|
goto out; |
|
case RXRPC_SECURITY_AUTH: |
|
shdr = sizeof(struct rxkad_level1_hdr); |
|
break; |
|
case RXRPC_SECURITY_ENCRYPT: |
|
shdr = sizeof(struct rxkad_level2_hdr); |
|
break; |
|
} |
|
|
|
buf_size = round_down(RXRPC_JUMBO_DATALEN, RXKAD_ALIGN); |
|
|
|
chunk = buf_size - shdr; |
|
if (remain < chunk) |
|
buf_size = round_up(shdr + remain, RXKAD_ALIGN); |
|
|
|
out: |
|
*_buf_size = buf_size; |
|
*_data_size = chunk; |
|
*_offset = shdr; |
|
return 0; |
|
} |
|
|
|
/* |
|
* prime the encryption state with the invariant parts of a connection's |
|
* description |
|
*/ |
|
static int rxkad_prime_packet_security(struct rxrpc_connection *conn, |
|
struct crypto_sync_skcipher *ci) |
|
{ |
|
struct skcipher_request *req; |
|
struct rxrpc_key_token *token; |
|
struct scatterlist sg; |
|
struct rxrpc_crypt iv; |
|
__be32 *tmpbuf; |
|
size_t tmpsize = 4 * sizeof(__be32); |
|
|
|
_enter(""); |
|
|
|
if (!conn->params.key) |
|
return 0; |
|
|
|
tmpbuf = kmalloc(tmpsize, GFP_KERNEL); |
|
if (!tmpbuf) |
|
return -ENOMEM; |
|
|
|
req = skcipher_request_alloc(&ci->base, GFP_NOFS); |
|
if (!req) { |
|
kfree(tmpbuf); |
|
return -ENOMEM; |
|
} |
|
|
|
token = conn->params.key->payload.data[0]; |
|
memcpy(&iv, token->kad->session_key, sizeof(iv)); |
|
|
|
tmpbuf[0] = htonl(conn->proto.epoch); |
|
tmpbuf[1] = htonl(conn->proto.cid); |
|
tmpbuf[2] = 0; |
|
tmpbuf[3] = htonl(conn->security_ix); |
|
|
|
sg_init_one(&sg, tmpbuf, tmpsize); |
|
skcipher_request_set_sync_tfm(req, ci); |
|
skcipher_request_set_callback(req, 0, NULL, NULL); |
|
skcipher_request_set_crypt(req, &sg, &sg, tmpsize, iv.x); |
|
crypto_skcipher_encrypt(req); |
|
skcipher_request_free(req); |
|
|
|
memcpy(&conn->rxkad.csum_iv, tmpbuf + 2, sizeof(conn->rxkad.csum_iv)); |
|
kfree(tmpbuf); |
|
_leave(" = 0"); |
|
return 0; |
|
} |
|
|
|
/* |
|
* Allocate and prepare the crypto request on a call. For any particular call, |
|
* this is called serially for the packets, so no lock should be necessary. |
|
*/ |
|
static struct skcipher_request *rxkad_get_call_crypto(struct rxrpc_call *call) |
|
{ |
|
struct crypto_skcipher *tfm = &call->conn->rxkad.cipher->base; |
|
struct skcipher_request *cipher_req = call->cipher_req; |
|
|
|
if (!cipher_req) { |
|
cipher_req = skcipher_request_alloc(tfm, GFP_NOFS); |
|
if (!cipher_req) |
|
return NULL; |
|
call->cipher_req = cipher_req; |
|
} |
|
|
|
return cipher_req; |
|
} |
|
|
|
/* |
|
* Clean up the crypto on a call. |
|
*/ |
|
static void rxkad_free_call_crypto(struct rxrpc_call *call) |
|
{ |
|
if (call->cipher_req) |
|
skcipher_request_free(call->cipher_req); |
|
call->cipher_req = NULL; |
|
} |
|
|
|
/* |
|
* partially encrypt a packet (level 1 security) |
|
*/ |
|
static int rxkad_secure_packet_auth(const struct rxrpc_call *call, |
|
struct sk_buff *skb, u32 data_size, |
|
struct skcipher_request *req) |
|
{ |
|
struct rxrpc_skb_priv *sp = rxrpc_skb(skb); |
|
struct rxkad_level1_hdr hdr; |
|
struct rxrpc_crypt iv; |
|
struct scatterlist sg; |
|
size_t pad; |
|
u16 check; |
|
|
|
_enter(""); |
|
|
|
check = sp->hdr.seq ^ call->call_id; |
|
data_size |= (u32)check << 16; |
|
|
|
hdr.data_size = htonl(data_size); |
|
memcpy(skb->head, &hdr, sizeof(hdr)); |
|
|
|
pad = sizeof(struct rxkad_level1_hdr) + data_size; |
|
pad = RXKAD_ALIGN - pad; |
|
pad &= RXKAD_ALIGN - 1; |
|
if (pad) |
|
skb_put_zero(skb, pad); |
|
|
|
/* start the encryption afresh */ |
|
memset(&iv, 0, sizeof(iv)); |
|
|
|
sg_init_one(&sg, skb->head, 8); |
|
skcipher_request_set_sync_tfm(req, call->conn->rxkad.cipher); |
|
skcipher_request_set_callback(req, 0, NULL, NULL); |
|
skcipher_request_set_crypt(req, &sg, &sg, 8, iv.x); |
|
crypto_skcipher_encrypt(req); |
|
skcipher_request_zero(req); |
|
|
|
_leave(" = 0"); |
|
return 0; |
|
} |
|
|
|
/* |
|
* wholly encrypt a packet (level 2 security) |
|
*/ |
|
static int rxkad_secure_packet_encrypt(const struct rxrpc_call *call, |
|
struct sk_buff *skb, |
|
u32 data_size, |
|
struct skcipher_request *req) |
|
{ |
|
const struct rxrpc_key_token *token; |
|
struct rxkad_level2_hdr rxkhdr; |
|
struct rxrpc_skb_priv *sp; |
|
struct rxrpc_crypt iv; |
|
struct scatterlist sg[16]; |
|
unsigned int len; |
|
size_t pad; |
|
u16 check; |
|
int err; |
|
|
|
sp = rxrpc_skb(skb); |
|
|
|
_enter(""); |
|
|
|
check = sp->hdr.seq ^ call->call_id; |
|
|
|
rxkhdr.data_size = htonl(data_size | (u32)check << 16); |
|
rxkhdr.checksum = 0; |
|
memcpy(skb->head, &rxkhdr, sizeof(rxkhdr)); |
|
|
|
pad = sizeof(struct rxkad_level2_hdr) + data_size; |
|
pad = RXKAD_ALIGN - pad; |
|
pad &= RXKAD_ALIGN - 1; |
|
if (pad) |
|
skb_put_zero(skb, pad); |
|
|
|
/* encrypt from the session key */ |
|
token = call->conn->params.key->payload.data[0]; |
|
memcpy(&iv, token->kad->session_key, sizeof(iv)); |
|
|
|
sg_init_one(&sg[0], skb->head, sizeof(rxkhdr)); |
|
skcipher_request_set_sync_tfm(req, call->conn->rxkad.cipher); |
|
skcipher_request_set_callback(req, 0, NULL, NULL); |
|
skcipher_request_set_crypt(req, &sg[0], &sg[0], sizeof(rxkhdr), iv.x); |
|
crypto_skcipher_encrypt(req); |
|
|
|
/* we want to encrypt the skbuff in-place */ |
|
err = -EMSGSIZE; |
|
if (skb_shinfo(skb)->nr_frags > 16) |
|
goto out; |
|
|
|
len = round_up(data_size, RXKAD_ALIGN); |
|
|
|
sg_init_table(sg, ARRAY_SIZE(sg)); |
|
err = skb_to_sgvec(skb, sg, 8, len); |
|
if (unlikely(err < 0)) |
|
goto out; |
|
skcipher_request_set_crypt(req, sg, sg, len, iv.x); |
|
crypto_skcipher_encrypt(req); |
|
|
|
_leave(" = 0"); |
|
err = 0; |
|
|
|
out: |
|
skcipher_request_zero(req); |
|
return err; |
|
} |
|
|
|
/* |
|
* checksum an RxRPC packet header |
|
*/ |
|
static int rxkad_secure_packet(struct rxrpc_call *call, |
|
struct sk_buff *skb, |
|
size_t data_size) |
|
{ |
|
struct rxrpc_skb_priv *sp; |
|
struct skcipher_request *req; |
|
struct rxrpc_crypt iv; |
|
struct scatterlist sg; |
|
u32 x, y; |
|
int ret; |
|
|
|
sp = rxrpc_skb(skb); |
|
|
|
_enter("{%d{%x}},{#%u},%zu,", |
|
call->debug_id, key_serial(call->conn->params.key), |
|
sp->hdr.seq, data_size); |
|
|
|
if (!call->conn->rxkad.cipher) |
|
return 0; |
|
|
|
ret = key_validate(call->conn->params.key); |
|
if (ret < 0) |
|
return ret; |
|
|
|
req = rxkad_get_call_crypto(call); |
|
if (!req) |
|
return -ENOMEM; |
|
|
|
/* continue encrypting from where we left off */ |
|
memcpy(&iv, call->conn->rxkad.csum_iv.x, sizeof(iv)); |
|
|
|
/* calculate the security checksum */ |
|
x = (call->cid & RXRPC_CHANNELMASK) << (32 - RXRPC_CIDSHIFT); |
|
x |= sp->hdr.seq & 0x3fffffff; |
|
call->crypto_buf[0] = htonl(call->call_id); |
|
call->crypto_buf[1] = htonl(x); |
|
|
|
sg_init_one(&sg, call->crypto_buf, 8); |
|
skcipher_request_set_sync_tfm(req, call->conn->rxkad.cipher); |
|
skcipher_request_set_callback(req, 0, NULL, NULL); |
|
skcipher_request_set_crypt(req, &sg, &sg, 8, iv.x); |
|
crypto_skcipher_encrypt(req); |
|
skcipher_request_zero(req); |
|
|
|
y = ntohl(call->crypto_buf[1]); |
|
y = (y >> 16) & 0xffff; |
|
if (y == 0) |
|
y = 1; /* zero checksums are not permitted */ |
|
sp->hdr.cksum = y; |
|
|
|
switch (call->conn->params.security_level) { |
|
case RXRPC_SECURITY_PLAIN: |
|
ret = 0; |
|
break; |
|
case RXRPC_SECURITY_AUTH: |
|
ret = rxkad_secure_packet_auth(call, skb, data_size, req); |
|
break; |
|
case RXRPC_SECURITY_ENCRYPT: |
|
ret = rxkad_secure_packet_encrypt(call, skb, data_size, req); |
|
break; |
|
default: |
|
ret = -EPERM; |
|
break; |
|
} |
|
|
|
_leave(" = %d [set %hx]", ret, y); |
|
return ret; |
|
} |
|
|
|
/* |
|
* decrypt partial encryption on a packet (level 1 security) |
|
*/ |
|
static int rxkad_verify_packet_1(struct rxrpc_call *call, struct sk_buff *skb, |
|
unsigned int offset, unsigned int len, |
|
rxrpc_seq_t seq, |
|
struct skcipher_request *req) |
|
{ |
|
struct rxkad_level1_hdr sechdr; |
|
struct rxrpc_crypt iv; |
|
struct scatterlist sg[16]; |
|
bool aborted; |
|
u32 data_size, buf; |
|
u16 check; |
|
int ret; |
|
|
|
_enter(""); |
|
|
|
if (len < 8) { |
|
aborted = rxrpc_abort_eproto(call, skb, "rxkad_1_hdr", "V1H", |
|
RXKADSEALEDINCON); |
|
goto protocol_error; |
|
} |
|
|
|
/* Decrypt the skbuff in-place. TODO: We really want to decrypt |
|
* directly into the target buffer. |
|
*/ |
|
sg_init_table(sg, ARRAY_SIZE(sg)); |
|
ret = skb_to_sgvec(skb, sg, offset, 8); |
|
if (unlikely(ret < 0)) |
|
return ret; |
|
|
|
/* start the decryption afresh */ |
|
memset(&iv, 0, sizeof(iv)); |
|
|
|
skcipher_request_set_sync_tfm(req, call->conn->rxkad.cipher); |
|
skcipher_request_set_callback(req, 0, NULL, NULL); |
|
skcipher_request_set_crypt(req, sg, sg, 8, iv.x); |
|
crypto_skcipher_decrypt(req); |
|
skcipher_request_zero(req); |
|
|
|
/* Extract the decrypted packet length */ |
|
if (skb_copy_bits(skb, offset, &sechdr, sizeof(sechdr)) < 0) { |
|
aborted = rxrpc_abort_eproto(call, skb, "rxkad_1_len", "XV1", |
|
RXKADDATALEN); |
|
goto protocol_error; |
|
} |
|
offset += sizeof(sechdr); |
|
len -= sizeof(sechdr); |
|
|
|
buf = ntohl(sechdr.data_size); |
|
data_size = buf & 0xffff; |
|
|
|
check = buf >> 16; |
|
check ^= seq ^ call->call_id; |
|
check &= 0xffff; |
|
if (check != 0) { |
|
aborted = rxrpc_abort_eproto(call, skb, "rxkad_1_check", "V1C", |
|
RXKADSEALEDINCON); |
|
goto protocol_error; |
|
} |
|
|
|
if (data_size > len) { |
|
aborted = rxrpc_abort_eproto(call, skb, "rxkad_1_datalen", "V1L", |
|
RXKADDATALEN); |
|
goto protocol_error; |
|
} |
|
|
|
_leave(" = 0 [dlen=%x]", data_size); |
|
return 0; |
|
|
|
protocol_error: |
|
if (aborted) |
|
rxrpc_send_abort_packet(call); |
|
return -EPROTO; |
|
} |
|
|
|
/* |
|
* wholly decrypt a packet (level 2 security) |
|
*/ |
|
static int rxkad_verify_packet_2(struct rxrpc_call *call, struct sk_buff *skb, |
|
unsigned int offset, unsigned int len, |
|
rxrpc_seq_t seq, |
|
struct skcipher_request *req) |
|
{ |
|
const struct rxrpc_key_token *token; |
|
struct rxkad_level2_hdr sechdr; |
|
struct rxrpc_crypt iv; |
|
struct scatterlist _sg[4], *sg; |
|
bool aborted; |
|
u32 data_size, buf; |
|
u16 check; |
|
int nsg, ret; |
|
|
|
_enter(",{%d}", skb->len); |
|
|
|
if (len < 8) { |
|
aborted = rxrpc_abort_eproto(call, skb, "rxkad_2_hdr", "V2H", |
|
RXKADSEALEDINCON); |
|
goto protocol_error; |
|
} |
|
|
|
/* Decrypt the skbuff in-place. TODO: We really want to decrypt |
|
* directly into the target buffer. |
|
*/ |
|
sg = _sg; |
|
nsg = skb_shinfo(skb)->nr_frags; |
|
if (nsg <= 4) { |
|
nsg = 4; |
|
} else { |
|
sg = kmalloc_array(nsg, sizeof(*sg), GFP_NOIO); |
|
if (!sg) |
|
goto nomem; |
|
} |
|
|
|
sg_init_table(sg, nsg); |
|
ret = skb_to_sgvec(skb, sg, offset, len); |
|
if (unlikely(ret < 0)) { |
|
if (sg != _sg) |
|
kfree(sg); |
|
return ret; |
|
} |
|
|
|
/* decrypt from the session key */ |
|
token = call->conn->params.key->payload.data[0]; |
|
memcpy(&iv, token->kad->session_key, sizeof(iv)); |
|
|
|
skcipher_request_set_sync_tfm(req, call->conn->rxkad.cipher); |
|
skcipher_request_set_callback(req, 0, NULL, NULL); |
|
skcipher_request_set_crypt(req, sg, sg, len, iv.x); |
|
crypto_skcipher_decrypt(req); |
|
skcipher_request_zero(req); |
|
if (sg != _sg) |
|
kfree(sg); |
|
|
|
/* Extract the decrypted packet length */ |
|
if (skb_copy_bits(skb, offset, &sechdr, sizeof(sechdr)) < 0) { |
|
aborted = rxrpc_abort_eproto(call, skb, "rxkad_2_len", "XV2", |
|
RXKADDATALEN); |
|
goto protocol_error; |
|
} |
|
offset += sizeof(sechdr); |
|
len -= sizeof(sechdr); |
|
|
|
buf = ntohl(sechdr.data_size); |
|
data_size = buf & 0xffff; |
|
|
|
check = buf >> 16; |
|
check ^= seq ^ call->call_id; |
|
check &= 0xffff; |
|
if (check != 0) { |
|
aborted = rxrpc_abort_eproto(call, skb, "rxkad_2_check", "V2C", |
|
RXKADSEALEDINCON); |
|
goto protocol_error; |
|
} |
|
|
|
if (data_size > len) { |
|
aborted = rxrpc_abort_eproto(call, skb, "rxkad_2_datalen", "V2L", |
|
RXKADDATALEN); |
|
goto protocol_error; |
|
} |
|
|
|
_leave(" = 0 [dlen=%x]", data_size); |
|
return 0; |
|
|
|
protocol_error: |
|
if (aborted) |
|
rxrpc_send_abort_packet(call); |
|
return -EPROTO; |
|
|
|
nomem: |
|
_leave(" = -ENOMEM"); |
|
return -ENOMEM; |
|
} |
|
|
|
/* |
|
* Verify the security on a received packet or subpacket (if part of a |
|
* jumbo packet). |
|
*/ |
|
static int rxkad_verify_packet(struct rxrpc_call *call, struct sk_buff *skb, |
|
unsigned int offset, unsigned int len, |
|
rxrpc_seq_t seq, u16 expected_cksum) |
|
{ |
|
struct skcipher_request *req; |
|
struct rxrpc_crypt iv; |
|
struct scatterlist sg; |
|
bool aborted; |
|
u16 cksum; |
|
u32 x, y; |
|
|
|
_enter("{%d{%x}},{#%u}", |
|
call->debug_id, key_serial(call->conn->params.key), seq); |
|
|
|
if (!call->conn->rxkad.cipher) |
|
return 0; |
|
|
|
req = rxkad_get_call_crypto(call); |
|
if (!req) |
|
return -ENOMEM; |
|
|
|
/* continue encrypting from where we left off */ |
|
memcpy(&iv, call->conn->rxkad.csum_iv.x, sizeof(iv)); |
|
|
|
/* validate the security checksum */ |
|
x = (call->cid & RXRPC_CHANNELMASK) << (32 - RXRPC_CIDSHIFT); |
|
x |= seq & 0x3fffffff; |
|
call->crypto_buf[0] = htonl(call->call_id); |
|
call->crypto_buf[1] = htonl(x); |
|
|
|
sg_init_one(&sg, call->crypto_buf, 8); |
|
skcipher_request_set_sync_tfm(req, call->conn->rxkad.cipher); |
|
skcipher_request_set_callback(req, 0, NULL, NULL); |
|
skcipher_request_set_crypt(req, &sg, &sg, 8, iv.x); |
|
crypto_skcipher_encrypt(req); |
|
skcipher_request_zero(req); |
|
|
|
y = ntohl(call->crypto_buf[1]); |
|
cksum = (y >> 16) & 0xffff; |
|
if (cksum == 0) |
|
cksum = 1; /* zero checksums are not permitted */ |
|
|
|
if (cksum != expected_cksum) { |
|
aborted = rxrpc_abort_eproto(call, skb, "rxkad_csum", "VCK", |
|
RXKADSEALEDINCON); |
|
goto protocol_error; |
|
} |
|
|
|
switch (call->conn->params.security_level) { |
|
case RXRPC_SECURITY_PLAIN: |
|
return 0; |
|
case RXRPC_SECURITY_AUTH: |
|
return rxkad_verify_packet_1(call, skb, offset, len, seq, req); |
|
case RXRPC_SECURITY_ENCRYPT: |
|
return rxkad_verify_packet_2(call, skb, offset, len, seq, req); |
|
default: |
|
return -ENOANO; |
|
} |
|
|
|
protocol_error: |
|
if (aborted) |
|
rxrpc_send_abort_packet(call); |
|
return -EPROTO; |
|
} |
|
|
|
/* |
|
* Locate the data contained in a packet that was partially encrypted. |
|
*/ |
|
static void rxkad_locate_data_1(struct rxrpc_call *call, struct sk_buff *skb, |
|
unsigned int *_offset, unsigned int *_len) |
|
{ |
|
struct rxkad_level1_hdr sechdr; |
|
|
|
if (skb_copy_bits(skb, *_offset, &sechdr, sizeof(sechdr)) < 0) |
|
BUG(); |
|
*_offset += sizeof(sechdr); |
|
*_len = ntohl(sechdr.data_size) & 0xffff; |
|
} |
|
|
|
/* |
|
* Locate the data contained in a packet that was completely encrypted. |
|
*/ |
|
static void rxkad_locate_data_2(struct rxrpc_call *call, struct sk_buff *skb, |
|
unsigned int *_offset, unsigned int *_len) |
|
{ |
|
struct rxkad_level2_hdr sechdr; |
|
|
|
if (skb_copy_bits(skb, *_offset, &sechdr, sizeof(sechdr)) < 0) |
|
BUG(); |
|
*_offset += sizeof(sechdr); |
|
*_len = ntohl(sechdr.data_size) & 0xffff; |
|
} |
|
|
|
/* |
|
* Locate the data contained in an already decrypted packet. |
|
*/ |
|
static void rxkad_locate_data(struct rxrpc_call *call, struct sk_buff *skb, |
|
unsigned int *_offset, unsigned int *_len) |
|
{ |
|
switch (call->conn->params.security_level) { |
|
case RXRPC_SECURITY_AUTH: |
|
rxkad_locate_data_1(call, skb, _offset, _len); |
|
return; |
|
case RXRPC_SECURITY_ENCRYPT: |
|
rxkad_locate_data_2(call, skb, _offset, _len); |
|
return; |
|
default: |
|
return; |
|
} |
|
} |
|
|
|
/* |
|
* issue a challenge |
|
*/ |
|
static int rxkad_issue_challenge(struct rxrpc_connection *conn) |
|
{ |
|
struct rxkad_challenge challenge; |
|
struct rxrpc_wire_header whdr; |
|
struct msghdr msg; |
|
struct kvec iov[2]; |
|
size_t len; |
|
u32 serial; |
|
int ret; |
|
|
|
_enter("{%d}", conn->debug_id); |
|
|
|
get_random_bytes(&conn->rxkad.nonce, sizeof(conn->rxkad.nonce)); |
|
|
|
challenge.version = htonl(2); |
|
challenge.nonce = htonl(conn->rxkad.nonce); |
|
challenge.min_level = htonl(0); |
|
challenge.__padding = 0; |
|
|
|
msg.msg_name = &conn->params.peer->srx.transport; |
|
msg.msg_namelen = conn->params.peer->srx.transport_len; |
|
msg.msg_control = NULL; |
|
msg.msg_controllen = 0; |
|
msg.msg_flags = 0; |
|
|
|
whdr.epoch = htonl(conn->proto.epoch); |
|
whdr.cid = htonl(conn->proto.cid); |
|
whdr.callNumber = 0; |
|
whdr.seq = 0; |
|
whdr.type = RXRPC_PACKET_TYPE_CHALLENGE; |
|
whdr.flags = conn->out_clientflag; |
|
whdr.userStatus = 0; |
|
whdr.securityIndex = conn->security_ix; |
|
whdr._rsvd = 0; |
|
whdr.serviceId = htons(conn->service_id); |
|
|
|
iov[0].iov_base = &whdr; |
|
iov[0].iov_len = sizeof(whdr); |
|
iov[1].iov_base = &challenge; |
|
iov[1].iov_len = sizeof(challenge); |
|
|
|
len = iov[0].iov_len + iov[1].iov_len; |
|
|
|
serial = atomic_inc_return(&conn->serial); |
|
whdr.serial = htonl(serial); |
|
_proto("Tx CHALLENGE %%%u", serial); |
|
|
|
ret = kernel_sendmsg(conn->params.local->socket, &msg, iov, 2, len); |
|
if (ret < 0) { |
|
trace_rxrpc_tx_fail(conn->debug_id, serial, ret, |
|
rxrpc_tx_point_rxkad_challenge); |
|
return -EAGAIN; |
|
} |
|
|
|
conn->params.peer->last_tx_at = ktime_get_seconds(); |
|
trace_rxrpc_tx_packet(conn->debug_id, &whdr, |
|
rxrpc_tx_point_rxkad_challenge); |
|
_leave(" = 0"); |
|
return 0; |
|
} |
|
|
|
/* |
|
* send a Kerberos security response |
|
*/ |
|
static int rxkad_send_response(struct rxrpc_connection *conn, |
|
struct rxrpc_host_header *hdr, |
|
struct rxkad_response *resp, |
|
const struct rxkad_key *s2) |
|
{ |
|
struct rxrpc_wire_header whdr; |
|
struct msghdr msg; |
|
struct kvec iov[3]; |
|
size_t len; |
|
u32 serial; |
|
int ret; |
|
|
|
_enter(""); |
|
|
|
msg.msg_name = &conn->params.peer->srx.transport; |
|
msg.msg_namelen = conn->params.peer->srx.transport_len; |
|
msg.msg_control = NULL; |
|
msg.msg_controllen = 0; |
|
msg.msg_flags = 0; |
|
|
|
memset(&whdr, 0, sizeof(whdr)); |
|
whdr.epoch = htonl(hdr->epoch); |
|
whdr.cid = htonl(hdr->cid); |
|
whdr.type = RXRPC_PACKET_TYPE_RESPONSE; |
|
whdr.flags = conn->out_clientflag; |
|
whdr.securityIndex = hdr->securityIndex; |
|
whdr.serviceId = htons(hdr->serviceId); |
|
|
|
iov[0].iov_base = &whdr; |
|
iov[0].iov_len = sizeof(whdr); |
|
iov[1].iov_base = resp; |
|
iov[1].iov_len = sizeof(*resp); |
|
iov[2].iov_base = (void *)s2->ticket; |
|
iov[2].iov_len = s2->ticket_len; |
|
|
|
len = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len; |
|
|
|
serial = atomic_inc_return(&conn->serial); |
|
whdr.serial = htonl(serial); |
|
_proto("Tx RESPONSE %%%u", serial); |
|
|
|
ret = kernel_sendmsg(conn->params.local->socket, &msg, iov, 3, len); |
|
if (ret < 0) { |
|
trace_rxrpc_tx_fail(conn->debug_id, serial, ret, |
|
rxrpc_tx_point_rxkad_response); |
|
return -EAGAIN; |
|
} |
|
|
|
conn->params.peer->last_tx_at = ktime_get_seconds(); |
|
_leave(" = 0"); |
|
return 0; |
|
} |
|
|
|
/* |
|
* calculate the response checksum |
|
*/ |
|
static void rxkad_calc_response_checksum(struct rxkad_response *response) |
|
{ |
|
u32 csum = 1000003; |
|
int loop; |
|
u8 *p = (u8 *) response; |
|
|
|
for (loop = sizeof(*response); loop > 0; loop--) |
|
csum = csum * 0x10204081 + *p++; |
|
|
|
response->encrypted.checksum = htonl(csum); |
|
} |
|
|
|
/* |
|
* encrypt the response packet |
|
*/ |
|
static int rxkad_encrypt_response(struct rxrpc_connection *conn, |
|
struct rxkad_response *resp, |
|
const struct rxkad_key *s2) |
|
{ |
|
struct skcipher_request *req; |
|
struct rxrpc_crypt iv; |
|
struct scatterlist sg[1]; |
|
|
|
req = skcipher_request_alloc(&conn->rxkad.cipher->base, GFP_NOFS); |
|
if (!req) |
|
return -ENOMEM; |
|
|
|
/* continue encrypting from where we left off */ |
|
memcpy(&iv, s2->session_key, sizeof(iv)); |
|
|
|
sg_init_table(sg, 1); |
|
sg_set_buf(sg, &resp->encrypted, sizeof(resp->encrypted)); |
|
skcipher_request_set_sync_tfm(req, conn->rxkad.cipher); |
|
skcipher_request_set_callback(req, 0, NULL, NULL); |
|
skcipher_request_set_crypt(req, sg, sg, sizeof(resp->encrypted), iv.x); |
|
crypto_skcipher_encrypt(req); |
|
skcipher_request_free(req); |
|
return 0; |
|
} |
|
|
|
/* |
|
* respond to a challenge packet |
|
*/ |
|
static int rxkad_respond_to_challenge(struct rxrpc_connection *conn, |
|
struct sk_buff *skb, |
|
u32 *_abort_code) |
|
{ |
|
const struct rxrpc_key_token *token; |
|
struct rxkad_challenge challenge; |
|
struct rxkad_response *resp; |
|
struct rxrpc_skb_priv *sp = rxrpc_skb(skb); |
|
const char *eproto; |
|
u32 version, nonce, min_level, abort_code; |
|
int ret; |
|
|
|
_enter("{%d,%x}", conn->debug_id, key_serial(conn->params.key)); |
|
|
|
eproto = tracepoint_string("chall_no_key"); |
|
abort_code = RX_PROTOCOL_ERROR; |
|
if (!conn->params.key) |
|
goto protocol_error; |
|
|
|
abort_code = RXKADEXPIRED; |
|
ret = key_validate(conn->params.key); |
|
if (ret < 0) |
|
goto other_error; |
|
|
|
eproto = tracepoint_string("chall_short"); |
|
abort_code = RXKADPACKETSHORT; |
|
if (skb_copy_bits(skb, sizeof(struct rxrpc_wire_header), |
|
&challenge, sizeof(challenge)) < 0) |
|
goto protocol_error; |
|
|
|
version = ntohl(challenge.version); |
|
nonce = ntohl(challenge.nonce); |
|
min_level = ntohl(challenge.min_level); |
|
|
|
_proto("Rx CHALLENGE %%%u { v=%u n=%u ml=%u }", |
|
sp->hdr.serial, version, nonce, min_level); |
|
|
|
eproto = tracepoint_string("chall_ver"); |
|
abort_code = RXKADINCONSISTENCY; |
|
if (version != RXKAD_VERSION) |
|
goto protocol_error; |
|
|
|
abort_code = RXKADLEVELFAIL; |
|
ret = -EACCES; |
|
if (conn->params.security_level < min_level) |
|
goto other_error; |
|
|
|
token = conn->params.key->payload.data[0]; |
|
|
|
/* build the response packet */ |
|
resp = kzalloc(sizeof(struct rxkad_response), GFP_NOFS); |
|
if (!resp) |
|
return -ENOMEM; |
|
|
|
resp->version = htonl(RXKAD_VERSION); |
|
resp->encrypted.epoch = htonl(conn->proto.epoch); |
|
resp->encrypted.cid = htonl(conn->proto.cid); |
|
resp->encrypted.securityIndex = htonl(conn->security_ix); |
|
resp->encrypted.inc_nonce = htonl(nonce + 1); |
|
resp->encrypted.level = htonl(conn->params.security_level); |
|
resp->kvno = htonl(token->kad->kvno); |
|
resp->ticket_len = htonl(token->kad->ticket_len); |
|
resp->encrypted.call_id[0] = htonl(conn->channels[0].call_counter); |
|
resp->encrypted.call_id[1] = htonl(conn->channels[1].call_counter); |
|
resp->encrypted.call_id[2] = htonl(conn->channels[2].call_counter); |
|
resp->encrypted.call_id[3] = htonl(conn->channels[3].call_counter); |
|
|
|
/* calculate the response checksum and then do the encryption */ |
|
rxkad_calc_response_checksum(resp); |
|
ret = rxkad_encrypt_response(conn, resp, token->kad); |
|
if (ret == 0) |
|
ret = rxkad_send_response(conn, &sp->hdr, resp, token->kad); |
|
kfree(resp); |
|
return ret; |
|
|
|
protocol_error: |
|
trace_rxrpc_rx_eproto(NULL, sp->hdr.serial, eproto); |
|
ret = -EPROTO; |
|
other_error: |
|
*_abort_code = abort_code; |
|
return ret; |
|
} |
|
|
|
/* |
|
* decrypt the kerberos IV ticket in the response |
|
*/ |
|
static int rxkad_decrypt_ticket(struct rxrpc_connection *conn, |
|
struct key *server_key, |
|
struct sk_buff *skb, |
|
void *ticket, size_t ticket_len, |
|
struct rxrpc_crypt *_session_key, |
|
time64_t *_expiry, |
|
u32 *_abort_code) |
|
{ |
|
struct skcipher_request *req; |
|
struct rxrpc_skb_priv *sp = rxrpc_skb(skb); |
|
struct rxrpc_crypt iv, key; |
|
struct scatterlist sg[1]; |
|
struct in_addr addr; |
|
unsigned int life; |
|
const char *eproto; |
|
time64_t issue, now; |
|
bool little_endian; |
|
int ret; |
|
u32 abort_code; |
|
u8 *p, *q, *name, *end; |
|
|
|
_enter("{%d},{%x}", conn->debug_id, key_serial(server_key)); |
|
|
|
*_expiry = 0; |
|
|
|
ASSERT(server_key->payload.data[0] != NULL); |
|
ASSERTCMP((unsigned long) ticket & 7UL, ==, 0); |
|
|
|
memcpy(&iv, &server_key->payload.data[2], sizeof(iv)); |
|
|
|
ret = -ENOMEM; |
|
req = skcipher_request_alloc(server_key->payload.data[0], GFP_NOFS); |
|
if (!req) |
|
goto temporary_error; |
|
|
|
sg_init_one(&sg[0], ticket, ticket_len); |
|
skcipher_request_set_callback(req, 0, NULL, NULL); |
|
skcipher_request_set_crypt(req, sg, sg, ticket_len, iv.x); |
|
crypto_skcipher_decrypt(req); |
|
skcipher_request_free(req); |
|
|
|
p = ticket; |
|
end = p + ticket_len; |
|
|
|
#define Z(field) \ |
|
({ \ |
|
u8 *__str = p; \ |
|
eproto = tracepoint_string("rxkad_bad_"#field); \ |
|
q = memchr(p, 0, end - p); \ |
|
if (!q || q - p > (field##_SZ)) \ |
|
goto bad_ticket; \ |
|
for (; p < q; p++) \ |
|
if (!isprint(*p)) \ |
|
goto bad_ticket; \ |
|
p++; \ |
|
__str; \ |
|
}) |
|
|
|
/* extract the ticket flags */ |
|
_debug("KIV FLAGS: %x", *p); |
|
little_endian = *p & 1; |
|
p++; |
|
|
|
/* extract the authentication name */ |
|
name = Z(ANAME); |
|
_debug("KIV ANAME: %s", name); |
|
|
|
/* extract the principal's instance */ |
|
name = Z(INST); |
|
_debug("KIV INST : %s", name); |
|
|
|
/* extract the principal's authentication domain */ |
|
name = Z(REALM); |
|
_debug("KIV REALM: %s", name); |
|
|
|
eproto = tracepoint_string("rxkad_bad_len"); |
|
if (end - p < 4 + 8 + 4 + 2) |
|
goto bad_ticket; |
|
|
|
/* get the IPv4 address of the entity that requested the ticket */ |
|
memcpy(&addr, p, sizeof(addr)); |
|
p += 4; |
|
_debug("KIV ADDR : %pI4", &addr); |
|
|
|
/* get the session key from the ticket */ |
|
memcpy(&key, p, sizeof(key)); |
|
p += 8; |
|
_debug("KIV KEY : %08x %08x", ntohl(key.n[0]), ntohl(key.n[1])); |
|
memcpy(_session_key, &key, sizeof(key)); |
|
|
|
/* get the ticket's lifetime */ |
|
life = *p++ * 5 * 60; |
|
_debug("KIV LIFE : %u", life); |
|
|
|
/* get the issue time of the ticket */ |
|
if (little_endian) { |
|
__le32 stamp; |
|
memcpy(&stamp, p, 4); |
|
issue = rxrpc_u32_to_time64(le32_to_cpu(stamp)); |
|
} else { |
|
__be32 stamp; |
|
memcpy(&stamp, p, 4); |
|
issue = rxrpc_u32_to_time64(be32_to_cpu(stamp)); |
|
} |
|
p += 4; |
|
now = ktime_get_real_seconds(); |
|
_debug("KIV ISSUE: %llx [%llx]", issue, now); |
|
|
|
/* check the ticket is in date */ |
|
if (issue > now) { |
|
abort_code = RXKADNOAUTH; |
|
ret = -EKEYREJECTED; |
|
goto other_error; |
|
} |
|
|
|
if (issue < now - life) { |
|
abort_code = RXKADEXPIRED; |
|
ret = -EKEYEXPIRED; |
|
goto other_error; |
|
} |
|
|
|
*_expiry = issue + life; |
|
|
|
/* get the service name */ |
|
name = Z(SNAME); |
|
_debug("KIV SNAME: %s", name); |
|
|
|
/* get the service instance name */ |
|
name = Z(INST); |
|
_debug("KIV SINST: %s", name); |
|
return 0; |
|
|
|
bad_ticket: |
|
trace_rxrpc_rx_eproto(NULL, sp->hdr.serial, eproto); |
|
abort_code = RXKADBADTICKET; |
|
ret = -EPROTO; |
|
other_error: |
|
*_abort_code = abort_code; |
|
return ret; |
|
temporary_error: |
|
return ret; |
|
} |
|
|
|
/* |
|
* decrypt the response packet |
|
*/ |
|
static void rxkad_decrypt_response(struct rxrpc_connection *conn, |
|
struct rxkad_response *resp, |
|
const struct rxrpc_crypt *session_key) |
|
{ |
|
struct skcipher_request *req = rxkad_ci_req; |
|
struct scatterlist sg[1]; |
|
struct rxrpc_crypt iv; |
|
|
|
_enter(",,%08x%08x", |
|
ntohl(session_key->n[0]), ntohl(session_key->n[1])); |
|
|
|
mutex_lock(&rxkad_ci_mutex); |
|
if (crypto_sync_skcipher_setkey(rxkad_ci, session_key->x, |
|
sizeof(*session_key)) < 0) |
|
BUG(); |
|
|
|
memcpy(&iv, session_key, sizeof(iv)); |
|
|
|
sg_init_table(sg, 1); |
|
sg_set_buf(sg, &resp->encrypted, sizeof(resp->encrypted)); |
|
skcipher_request_set_sync_tfm(req, rxkad_ci); |
|
skcipher_request_set_callback(req, 0, NULL, NULL); |
|
skcipher_request_set_crypt(req, sg, sg, sizeof(resp->encrypted), iv.x); |
|
crypto_skcipher_decrypt(req); |
|
skcipher_request_zero(req); |
|
|
|
mutex_unlock(&rxkad_ci_mutex); |
|
|
|
_leave(""); |
|
} |
|
|
|
/* |
|
* verify a response |
|
*/ |
|
static int rxkad_verify_response(struct rxrpc_connection *conn, |
|
struct sk_buff *skb, |
|
u32 *_abort_code) |
|
{ |
|
struct rxkad_response *response; |
|
struct rxrpc_skb_priv *sp = rxrpc_skb(skb); |
|
struct rxrpc_crypt session_key; |
|
struct key *server_key; |
|
const char *eproto; |
|
time64_t expiry; |
|
void *ticket; |
|
u32 abort_code, version, kvno, ticket_len, level; |
|
__be32 csum; |
|
int ret, i; |
|
|
|
_enter("{%d}", conn->debug_id); |
|
|
|
server_key = rxrpc_look_up_server_security(conn, skb, 0, 0); |
|
if (IS_ERR(server_key)) { |
|
switch (PTR_ERR(server_key)) { |
|
case -ENOKEY: |
|
abort_code = RXKADUNKNOWNKEY; |
|
break; |
|
case -EKEYEXPIRED: |
|
abort_code = RXKADEXPIRED; |
|
break; |
|
default: |
|
abort_code = RXKADNOAUTH; |
|
break; |
|
} |
|
trace_rxrpc_abort(0, "SVK", |
|
sp->hdr.cid, sp->hdr.callNumber, sp->hdr.seq, |
|
abort_code, PTR_ERR(server_key)); |
|
*_abort_code = abort_code; |
|
return -EPROTO; |
|
} |
|
|
|
ret = -ENOMEM; |
|
response = kzalloc(sizeof(struct rxkad_response), GFP_NOFS); |
|
if (!response) |
|
goto temporary_error; |
|
|
|
eproto = tracepoint_string("rxkad_rsp_short"); |
|
abort_code = RXKADPACKETSHORT; |
|
if (skb_copy_bits(skb, sizeof(struct rxrpc_wire_header), |
|
response, sizeof(*response)) < 0) |
|
goto protocol_error; |
|
|
|
version = ntohl(response->version); |
|
ticket_len = ntohl(response->ticket_len); |
|
kvno = ntohl(response->kvno); |
|
_proto("Rx RESPONSE %%%u { v=%u kv=%u tl=%u }", |
|
sp->hdr.serial, version, kvno, ticket_len); |
|
|
|
eproto = tracepoint_string("rxkad_rsp_ver"); |
|
abort_code = RXKADINCONSISTENCY; |
|
if (version != RXKAD_VERSION) |
|
goto protocol_error; |
|
|
|
eproto = tracepoint_string("rxkad_rsp_tktlen"); |
|
abort_code = RXKADTICKETLEN; |
|
if (ticket_len < 4 || ticket_len > MAXKRB5TICKETLEN) |
|
goto protocol_error; |
|
|
|
eproto = tracepoint_string("rxkad_rsp_unkkey"); |
|
abort_code = RXKADUNKNOWNKEY; |
|
if (kvno >= RXKAD_TKT_TYPE_KERBEROS_V5) |
|
goto protocol_error; |
|
|
|
/* extract the kerberos ticket and decrypt and decode it */ |
|
ret = -ENOMEM; |
|
ticket = kmalloc(ticket_len, GFP_NOFS); |
|
if (!ticket) |
|
goto temporary_error_free_resp; |
|
|
|
eproto = tracepoint_string("rxkad_tkt_short"); |
|
abort_code = RXKADPACKETSHORT; |
|
if (skb_copy_bits(skb, sizeof(struct rxrpc_wire_header) + sizeof(*response), |
|
ticket, ticket_len) < 0) |
|
goto protocol_error_free; |
|
|
|
ret = rxkad_decrypt_ticket(conn, server_key, skb, ticket, ticket_len, |
|
&session_key, &expiry, _abort_code); |
|
if (ret < 0) |
|
goto temporary_error_free_ticket; |
|
|
|
/* use the session key from inside the ticket to decrypt the |
|
* response */ |
|
rxkad_decrypt_response(conn, response, &session_key); |
|
|
|
eproto = tracepoint_string("rxkad_rsp_param"); |
|
abort_code = RXKADSEALEDINCON; |
|
if (ntohl(response->encrypted.epoch) != conn->proto.epoch) |
|
goto protocol_error_free; |
|
if (ntohl(response->encrypted.cid) != conn->proto.cid) |
|
goto protocol_error_free; |
|
if (ntohl(response->encrypted.securityIndex) != conn->security_ix) |
|
goto protocol_error_free; |
|
csum = response->encrypted.checksum; |
|
response->encrypted.checksum = 0; |
|
rxkad_calc_response_checksum(response); |
|
eproto = tracepoint_string("rxkad_rsp_csum"); |
|
if (response->encrypted.checksum != csum) |
|
goto protocol_error_free; |
|
|
|
spin_lock(&conn->bundle->channel_lock); |
|
for (i = 0; i < RXRPC_MAXCALLS; i++) { |
|
struct rxrpc_call *call; |
|
u32 call_id = ntohl(response->encrypted.call_id[i]); |
|
|
|
eproto = tracepoint_string("rxkad_rsp_callid"); |
|
if (call_id > INT_MAX) |
|
goto protocol_error_unlock; |
|
|
|
eproto = tracepoint_string("rxkad_rsp_callctr"); |
|
if (call_id < conn->channels[i].call_counter) |
|
goto protocol_error_unlock; |
|
|
|
eproto = tracepoint_string("rxkad_rsp_callst"); |
|
if (call_id > conn->channels[i].call_counter) { |
|
call = rcu_dereference_protected( |
|
conn->channels[i].call, |
|
lockdep_is_held(&conn->bundle->channel_lock)); |
|
if (call && call->state < RXRPC_CALL_COMPLETE) |
|
goto protocol_error_unlock; |
|
conn->channels[i].call_counter = call_id; |
|
} |
|
} |
|
spin_unlock(&conn->bundle->channel_lock); |
|
|
|
eproto = tracepoint_string("rxkad_rsp_seq"); |
|
abort_code = RXKADOUTOFSEQUENCE; |
|
if (ntohl(response->encrypted.inc_nonce) != conn->rxkad.nonce + 1) |
|
goto protocol_error_free; |
|
|
|
eproto = tracepoint_string("rxkad_rsp_level"); |
|
abort_code = RXKADLEVELFAIL; |
|
level = ntohl(response->encrypted.level); |
|
if (level > RXRPC_SECURITY_ENCRYPT) |
|
goto protocol_error_free; |
|
conn->params.security_level = level; |
|
|
|
/* create a key to hold the security data and expiration time - after |
|
* this the connection security can be handled in exactly the same way |
|
* as for a client connection */ |
|
ret = rxrpc_get_server_data_key(conn, &session_key, expiry, kvno); |
|
if (ret < 0) |
|
goto temporary_error_free_ticket; |
|
|
|
kfree(ticket); |
|
kfree(response); |
|
_leave(" = 0"); |
|
return 0; |
|
|
|
protocol_error_unlock: |
|
spin_unlock(&conn->bundle->channel_lock); |
|
protocol_error_free: |
|
kfree(ticket); |
|
protocol_error: |
|
kfree(response); |
|
trace_rxrpc_rx_eproto(NULL, sp->hdr.serial, eproto); |
|
key_put(server_key); |
|
*_abort_code = abort_code; |
|
return -EPROTO; |
|
|
|
temporary_error_free_ticket: |
|
kfree(ticket); |
|
temporary_error_free_resp: |
|
kfree(response); |
|
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. |
|
*/ |
|
key_put(server_key); |
|
return ret; |
|
} |
|
|
|
/* |
|
* clear the connection security |
|
*/ |
|
static void rxkad_clear(struct rxrpc_connection *conn) |
|
{ |
|
_enter(""); |
|
|
|
if (conn->rxkad.cipher) |
|
crypto_free_sync_skcipher(conn->rxkad.cipher); |
|
} |
|
|
|
/* |
|
* Initialise the rxkad security service. |
|
*/ |
|
static int rxkad_init(void) |
|
{ |
|
struct crypto_sync_skcipher *tfm; |
|
struct skcipher_request *req; |
|
|
|
/* pin the cipher we need so that the crypto layer doesn't invoke |
|
* keventd to go get it */ |
|
tfm = crypto_alloc_sync_skcipher("pcbc(fcrypt)", 0, 0); |
|
if (IS_ERR(tfm)) |
|
return PTR_ERR(tfm); |
|
|
|
req = skcipher_request_alloc(&tfm->base, GFP_KERNEL); |
|
if (!req) |
|
goto nomem_tfm; |
|
|
|
rxkad_ci_req = req; |
|
rxkad_ci = tfm; |
|
return 0; |
|
|
|
nomem_tfm: |
|
crypto_free_sync_skcipher(tfm); |
|
return -ENOMEM; |
|
} |
|
|
|
/* |
|
* Clean up the rxkad security service. |
|
*/ |
|
static void rxkad_exit(void) |
|
{ |
|
crypto_free_sync_skcipher(rxkad_ci); |
|
skcipher_request_free(rxkad_ci_req); |
|
} |
|
|
|
/* |
|
* RxRPC Kerberos-based security |
|
*/ |
|
const struct rxrpc_security rxkad = { |
|
.name = "rxkad", |
|
.security_index = RXRPC_SECURITY_RXKAD, |
|
.no_key_abort = RXKADUNKNOWNKEY, |
|
.init = rxkad_init, |
|
.exit = rxkad_exit, |
|
.preparse_server_key = rxkad_preparse_server_key, |
|
.free_preparse_server_key = rxkad_free_preparse_server_key, |
|
.destroy_server_key = rxkad_destroy_server_key, |
|
.init_connection_security = rxkad_init_connection_security, |
|
.how_much_data = rxkad_how_much_data, |
|
.secure_packet = rxkad_secure_packet, |
|
.verify_packet = rxkad_verify_packet, |
|
.free_call_crypto = rxkad_free_call_crypto, |
|
.locate_data = rxkad_locate_data, |
|
.issue_challenge = rxkad_issue_challenge, |
|
.respond_to_challenge = rxkad_respond_to_challenge, |
|
.verify_response = rxkad_verify_response, |
|
.clear = rxkad_clear, |
|
};
|
|
|