--- net/rxrpc/ar-internal.h | 1 net/rxrpc/key.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++ net/rxrpc/rxgk.c | 25 +++++++++ net/rxrpc/rxgk_app.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++ net/rxrpc/rxgk_common.h | 2 + net/rxrpc/security.c | 3 + 6 files changed, 302 insertions(+) diff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h index 3f2469714422..ed44ceeeab68 100644 --- a/net/rxrpc/ar-internal.h +++ b/net/rxrpc/ar-internal.h @@ -1070,6 +1070,7 @@ void rxrpc_peer_init_rtt(struct rxrpc_peer *); /* * rxgk.c */ +extern const struct rxrpc_security rxgk_openafs; extern const struct rxrpc_security rxgk_yfs; /* diff --git a/net/rxrpc/key.c b/net/rxrpc/key.c index b7f154701d97..3479ef285980 100644 --- a/net/rxrpc/key.c +++ b/net/rxrpc/key.c @@ -147,6 +147,135 @@ static time64_t rxrpc_s64_to_time64(s64 time_in_100ns) return neg ? -tmp : tmp; } +/* + * Parse an OpenAFS RxGK type XDR format token + * - the caller guarantees we have at least 4 words + * + * struct token_rxgk { + * afs_int64 0 gk_viceid; + * afs_int32 2 gk_enctype; + * afs_int32 3 gk_level; + * afs_uint32 4 gk_lifetime; + * afs_uint32 5 gk_bytelife; + * afs_int64 6 gk_expiration; + * opaque 8 gk_token<AFSTOKEN_GK_TOK_MAX>; + * opaque 9 gk_k0<AFSTOKEN_GK_TOK_MAX>; + * }; + */ +static int rxrpc_preparse_xdr_rxgk(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; + const __be32 *ticket, *key; + u32 tktlen, keylen; + + _enter(",{%x,%x,%x,%x},%x", + ntohl(xdr[0]), ntohl(xdr[1]), ntohl(xdr[2]), ntohl(xdr[3]), + toklen); + + if (toklen / 4 < 10) + goto reject; + + ticket = xdr + 9; + tktlen = ntohl(ticket[-1]); + _debug("tktlen: %x", tktlen); + tktlen = round_up(tktlen, 4); + if (toklen < 10 * 4 + tktlen) + goto reject; + + key = ticket + (tktlen / 4) + 1; + keylen = ntohl(key[-1]); + _debug("keylen: %x", keylen); + keylen = round_up(keylen, 4); + if (10 * 4 + tktlen + keylen != toklen) { + kleave(" = -EKEYREJECTED [%x!=%x, %x,%x]", + 10 * 4 + tktlen + keylen, toklen, tktlen, keylen); + goto reject; + } + + plen = sizeof(*token) + sizeof(*token->rxgk) + tktlen + keylen; + prep->quotalen = datalen + plen; + + plen -= sizeof(*token); + token = kzalloc(sizeof(*token), GFP_KERNEL); + if (!token) + goto nomem; + + token->rxgk = kzalloc(sizeof(struct rxgk_key) + keylen, GFP_KERNEL); + if (!token->rxgk) + goto nomem_token; + + token->security_index = RXRPC_SECURITY_RXGK; + token->rxgk->begintime = 0; + token->rxgk->endtime = xdr_dec64(xdr + 6); + token->rxgk->level = ntohl(xdr[3]); + if (token->rxgk->level > RXRPC_SECURITY_ENCRYPT) + goto reject_token; + token->rxgk->lifetime = ntohl(xdr[4]); + token->rxgk->bytelife = ntohl(xdr[5]); + token->rxgk->enctype = ntohl(xdr[2]); + token->rxgk->key.len = ntohl(key[-1]); + token->rxgk->key.data = token->rxgk->_key; + token->rxgk->ticket.len = ntohl(ticket[-1]); + + expiry = rxrpc_s64_to_time64(token->rxgk->endtime); + if (expiry < 0) + goto expired; + if (expiry < prep->expiry) + prep->expiry = expiry; + + memcpy(token->rxgk->key.data, key, token->rxgk->key.len); + + /* Pad the ticket so that we can use it directly in XDR */ + token->rxgk->ticket.data = kzalloc(round_up(token->rxgk->ticket.len, 4), + GFP_KERNEL); + if (!token->rxgk->ticket.data) + goto nomem_yrxgk; + memcpy(token->rxgk->ticket.data, ticket, token->rxgk->ticket.len); + + _debug("SCIX: %u", token->security_index); + _debug("LIFE: %llx", token->rxgk->lifetime); + _debug("BYTE: %llx", token->rxgk->bytelife); + _debug("ENC : %u", token->rxgk->enctype); + _debug("LEVL: %u", token->rxgk->level); + _debug("KLEN: %u", token->rxgk->key.len); + _debug("TLEN: %u", token->rxgk->ticket.len); + _debug("KEY0: %*phN", token->rxgk->key.len, token->rxgk->key.data); + _debug("TICK: %*phN", + min_t(u32, token->rxgk->ticket.len, 32), token->rxgk->ticket.data); + + /* 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; + + _leave(" = 0"); + return 0; + +nomem_yrxgk: + kfree(token->rxgk); +nomem_token: + kfree(token); +nomem: + return -ENOMEM; +reject_token: + kfree(token); +reject: + return -EKEYREJECTED; +expired: + kfree(token->rxgk); + kfree(token); + return -EKEYEXPIRED; +} + /* * Parse a YFS-RxGK type XDR format token * - the caller guarantees we have at least 4 words @@ -380,6 +509,9 @@ static int rxrpc_preparse_xdr(struct key_preparsed_payload *prep) case RXRPC_SECURITY_RXKAD: ret2 = rxrpc_preparse_xdr_rxkad(prep, datalen, token, toklen); break; + case RXRPC_SECURITY_RXGK: + ret2 = rxrpc_preparse_xdr_rxgk(prep, datalen, token, toklen); + break; case RXRPC_SECURITY_YFS_RXGK: ret2 = rxrpc_preparse_xdr_yfs_rxgk(prep, datalen, token, toklen); break; @@ -545,6 +677,7 @@ static void rxrpc_free_token_list(struct rxrpc_key_token *token) case RXRPC_SECURITY_RXKAD: kfree(token->kad); break; + case RXRPC_SECURITY_RXGK: case RXRPC_SECURITY_YFS_RXGK: kfree(token->rxgk->ticket.data); kfree(token->rxgk); @@ -592,6 +725,9 @@ static void rxrpc_describe(const struct key *key, struct seq_file *m) case RXRPC_SECURITY_RXKAD: seq_puts(m, "ka"); break; + case RXRPC_SECURITY_RXGK: + seq_puts(m, "ogk"); + break; case RXRPC_SECURITY_YFS_RXGK: seq_puts(m, "ygk"); break; diff --git a/net/rxrpc/rxgk.c b/net/rxrpc/rxgk.c index 0aa6da93b8d4..bad68d293ced 100644 --- a/net/rxrpc/rxgk.c +++ b/net/rxrpc/rxgk.c @@ -1181,6 +1181,31 @@ static void rxgk_exit(void) { } +/* + * RxRPC OpenAFS GSSAPI-based security + */ +const struct rxrpc_security rxgk_openafs = { + .name = "rxgk", + .security_index = RXRPC_SECURITY_RXGK, + .no_key_abort = RXGK_NOTAUTH, + .init = rxgk_init, + .exit = rxgk_exit, + .preparse_server_key = rxgk_preparse_server_key, + .free_preparse_server_key = rxgk_free_preparse_server_key, + .destroy_server_key = rxgk_destroy_server_key, + .describe_server_key = rxgk_describe_server_key, + .init_connection_security = rxgk_init_connection_security, + .secure_packet = rxgk_secure_packet, + .verify_packet = rxgk_verify_packet, + .free_call_crypto = rxgk_free_call_crypto, + .locate_data = rxgk_locate_data, + .issue_challenge = rxgk_issue_challenge, + .respond_to_challenge = rxgk_respond_to_challenge, + .verify_response = rxgk_verify_response, + .clear = rxgk_clear, + .default_decode_ticket = rxgk_openafs_decode_ticket, +}; + /* * RxRPC YFS GSSAPI-based security */ diff --git a/net/rxrpc/rxgk_app.c b/net/rxrpc/rxgk_app.c index 895879f3acfb..8c35e3a88119 100644 --- a/net/rxrpc/rxgk_app.c +++ b/net/rxrpc/rxgk_app.c @@ -14,6 +14,141 @@ #include "ar-internal.h" #include "rxgk_common.h" +/* + * Decode a default-style OpenAFS ticket in a response and turn it into an + * rxrpc-type key. + * + * struct RXGK_Token { + * afs_int32 enctype; + * opaque K0<>; + * RXGK_Level level; + * afs_int32 lifetime; + * afs_int32 bytelife; + * rxgkTime expirationtime; + * struct RXGK_PrAuthName identities<>; + * }; + */ +int rxgk_openafs_decode_ticket(struct sk_buff *skb, + unsigned int ticket_offset, unsigned int ticket_len, + u32 *_abort_code, + 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) + goto error_out; + enctype = ntohl(tmp[0]); + klen = ntohl(tmp[1]); + + if (klen > ticket_len - 8 * sizeof(__be32)) { + *_abort_code = RXGK_INCONSISTENCY; + return -EPROTO; + } + + pre_ticket_len = ((5 + 10) * sizeof(__be32)); + payload_len = pre_ticket_len + xdr_round_up(ticket_len) + + sizeof(__be32) + xdr_round_up(klen); + + 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) + 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(11 * 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_RXGK); + q[ 1] = 0; /* gk_viceid - msw */ + q[ 2] = 0; /* - lsw */ + q[ 3] = htonl(enctype); /* gkenctype - msw */ + q[ 4] = t[0]; /* gk_level */ + q[ 5] = t[1]; /* gk_lifetime */ + q[ 6] = t[2]; /* gk_bytelife */ + q[ 7] = t[3]; /* gk_expiration - msw */ + q[ 8] = t[4]; /* - lsw */ + q[ 9] = htonl(ticket_len); /* gk_token.length */ + + q += 10; + if (WARN_ON((unsigned long)q != (unsigned long)ticket)) { + kdebug("%lx %lx", (long)q, (long)ticket); + ret = -EIO; + goto error; + } + + /* Ticket read in with skb_copy_bits above */ + q += xdr_round_up(ticket_len) / 4; + q[0] = ntohl(klen); + q++; + + memcpy(q, ticket + sizeof(__be32) * 2, klen); + + q += xdr_round_up(klen) / 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, 0, // TODO: Use socket owner + KEY_ALLOC_NOT_IN_QUOTA, NULL); + if (IS_ERR(key)) { + _leave(" = -ENOMEM [alloc %ld]", 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); +error_out: + _leave(" = %d", ret); + return ret; +} + /* * Decode a default-style YFS ticket in a response and turn it into an * rxrpc-type key. diff --git a/net/rxrpc/rxgk_common.h b/net/rxrpc/rxgk_common.h index 38473b13e67d..88278da64c6a 100644 --- a/net/rxrpc/rxgk_common.h +++ b/net/rxrpc/rxgk_common.h @@ -38,6 +38,8 @@ struct rxgk_context { /* * rxgk_app.c */ +int rxgk_openafs_decode_ticket(struct sk_buff *, unsigned int, unsigned int, + u32 *, struct key **); int rxgk_yfs_decode_ticket(struct sk_buff *, unsigned int, unsigned int, u32 *, struct key **); int rxgk_extract_token(struct rxrpc_connection *, diff --git a/net/rxrpc/security.c b/net/rxrpc/security.c index 278a510b2956..dd11aa1aa137 100644 --- a/net/rxrpc/security.c +++ b/net/rxrpc/security.c @@ -20,6 +20,9 @@ static const struct rxrpc_security *rxrpc_security_types[] = { #ifdef CONFIG_RXKAD [RXRPC_SECURITY_RXKAD] = &rxkad, #endif +#ifdef CONFIG_RXGK + [RXRPC_SECURITY_RXGK] = &rxgk_openafs, +#endif #ifdef CONFIG_RXGK [RXRPC_SECURITY_YFS_RXGK] = &rxgk_yfs, #endif