From 4e52018f3ca219f301e6a7c6d77ae2c18869802f Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 30 Sep 2020 09:22:46 +0100 Subject: [PATCH] Add gssapi test program --- kafs/Makefile | 5 +- kafs/gssapi_test.C | 364 +++++++++++++++++++++++++++++++++++++++++++++ rpc-api/add_key.xg | 15 ++ 3 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 kafs/gssapi_test.C diff --git a/kafs/Makefile b/kafs/Makefile index 13fa607..b543b43 100644 --- a/kafs/Makefile +++ b/kafs/Makefile @@ -29,7 +29,8 @@ FS_SRCS := \ GSSAPI_SRCS := \ gssapi.C \ gssapi_aklog.C \ - gssapi_help.C + gssapi_help.C \ + gssapi_test.C PTS_SRCS := \ pts.C \ @@ -94,7 +95,7 @@ LIBDIR := ../lib kafs: $(KAFS_OBJS) $(LIBDIR)/libkafs_utils.a $(CXX) -o $@ $(KAFS_OBJS) -L$(LIBDIR) -lkafs_utils -lkafs_client -luuid -lfmt \ - -lgssapi_krb5 -lkeyutils + -lgssapi_krb5 -lk5crypto -lkrb5 -lkeyutils clean: $(RM) *~ *.o $(DEPS) diff --git a/kafs/gssapi_test.C b/kafs/gssapi_test.C new file mode 100644 index 0000000..76f1ed2 --- /dev/null +++ b/kafs/gssapi_test.C @@ -0,0 +1,364 @@ +/* gssapi_test.C: gssapi test client + * + * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +extern "C" { +#define private foo +#include +#undef private +} +#include "kafs.H" +#include "afs_xg.H" +#include "vlservice.H" +#include "arg_parse.H" + +#define TEST_PORT 7009 +#define RX_PERF_SERVICE 147 + +using rxrpc::ref; +using kafs::afs::RXGK_StartParams; +using kafs::afs::RXGK_ClientInfo; +using kafs::afs::RXGK_AuthName; +using kafs::afs::RXGK_Token; +using kafs::afs::RXGK_TokenContainer; + +static bool debug_gss = false; + +#define verbose_gss(FMT, ...) \ + do { if (a_verbose) fmt::print("GSSAPI: " FMT, ## __VA_ARGS__); } while (0) +#define debug_gss(FMT, ...) \ + do { if (debug_gss) fmt::print("GSSAPI: " FMT, ## __VA_ARGS__); } while (0) + +class KRB5_context { + krb5_context ctx; +public: + KRB5_context() { + ctx = NULL; + int ec = krb5_init_context(&ctx); + if (ec) + throw std::runtime_error(fmt::format("Kerberos error {:d}", ec)); + } + ~KRB5_context() { krb5_free_context(ctx); } + operator krb5_context() { return ctx; } +}; + +static std::string KRB5_error(KRB5_context &krb, krb5_error_code ec) +{ + const char *p = krb5_get_error_message(krb, ec); + std::string msg(fmt::format("Kerberos error: {}", p)); + krb5_free_error_message(krb, p); + return msg; +} + + +/* + * Forge and encrypt a ticket. + * + * 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<>; + * }; + */ +static void forge_rxgk_ticket(kafs::Context *ctx, + RXGK_ClientInfo &client_info, + rxrpc::Opaque &ticket_key, + rxrpc::Opaque &K0, + int tenctype, + int kvno, + rxrpc::Opaque &ticket) +{ + krb5_error_code ec; + RXGK_TokenContainer container; + RXGK_Token token; + size_t enc_size; + + debug_gss("Ticket enctype {}\n", tenctype); + debug_gss("Session enctype {}\n", client_info.enctype); + + container.kvno = kvno; + container.enctype = tenctype; + + token.K0.enctype = client_info.enctype; + token.K0.key = K0; + token.level = client_info.level; + token.starttime = 0; + token.lifetime = client_info.lifetime; + token.bytelife = client_info.bytelife; + token.expirationtime = client_info.expiration; + + token.identities.resize(1); + token.identities[0].kind = kafs::afs::PRAUTHTYPE_GSS; + token.identities[0].data.buffer_size = 4; + token.identities[0].data.buffer = (void *)"test"; + token.identities[0].display.buffer_size = 5; + token.identities[0].display.buffer = (void *)"TEST!"; + + rxrpc::Resv resv = { .max_ioc = 1, .flat = true }; + kafs::afs::xdr::reserve_RXGK_Token(resv, token); + ref buf = alloc_enc_buffer(resv); + kafs::afs::xdr::encode_RXGK_Token(buf, token); + rxrpc::seal_buffer(buf); + + KRB5_context krb; + + ec = krb5_c_encrypt_length(krb, tenctype, buf->size, &enc_size); + if (ec) + throw std::runtime_error(KRB5_error(krb, ec)); + + container.encrypted_token.reserve(enc_size); + + krb5_keyblock keyblock; + keyblock.magic = KV5M_KEYBLOCK; + keyblock.enctype = tenctype; + keyblock.length = ticket_key.size(); + keyblock.contents = (krb5_octet *)ticket_key.buffer; + + debug_gss("Keyblock e={} l={}\n", keyblock.enctype, keyblock.length); + + krb5_data unenc; + unenc.magic = KV5M_DATA; + unenc.length = buf->size; + unenc.data = (char *)buf->enc_buf; + + debug_gss("Unenc l={}\n", unenc.length); + + krb5_enc_data enc; + enc.magic = KV5M_ENC_DATA; + enc.enctype = tenctype; + enc.kvno = kvno; + enc.ciphertext.magic = KV5M_DATA; + enc.ciphertext.length = enc_size; + enc.ciphertext.data = (char *)container.encrypted_token.buffer; + + debug_gss("Enc e={} k={} l={}\n", enc.enctype, enc.kvno, enc.ciphertext.length); + + ec = krb5_c_encrypt(krb, &keyblock, kafs::afs::RXGK_SERVER_ENC_TOKEN, + NULL, &unenc, &enc); + if (ec) + throw std::runtime_error(KRB5_error(krb, ec)); + container.encrypted_token.buffer_size = enc.ciphertext.length; + + rxrpc::Resv resv2 = { .max_ioc = 1, .flat = true }; + kafs::afs::xdr::reserve_RXGK_TokenContainer(resv2, container); + ref buf2 = alloc_enc_buffer(resv2); + kafs::afs::xdr::encode_RXGK_TokenContainer(buf2, container); + rxrpc::seal_buffer(buf2); + + ticket.reserve(buf2->size); + ticket.buffer_size = buf2->size; + memcpy(ticket.buffer, buf2->enc_buf, ticket.buffer_size); + ticket.del_buffer = true; +} + +static void rxgk_add_key(kafs::Context *ctx, + RXGK_ClientInfo &client_info, + rxrpc::Opaque &K0, + bool a_verbose, + unsigned sec_type) +{ + kafs::afs::ktc_tokenUnion token; + + token.type = sec_type; + switch (sec_type) { + case kafs::afs::AFSTOKEN_UNION_YFSGK: + token.ygk.begintime = 0; + token.ygk.endtime = client_info.expiration; + token.ygk.level = client_info.level; + token.ygk.lifetime = client_info.lifetime; + token.ygk.bytelife = client_info.bytelife; + token.ygk.enctype = client_info.enctype; + token.ygk.key = K0; + token.ygk.key.del_buffer = false; + token.ygk.ticket = client_info.token; + token.ygk.ticket.del_buffer = false; + break; + default: + throw std::runtime_error("Unknown security type"); + } + + rxrpc::Resv resv = { .max_ioc = 1, .flat = true }; + kafs::afs::xdr::reserve_ktc_tokenUnion(resv, token); + ref buf = alloc_enc_buffer(resv); + kafs::afs::xdr::encode_ktc_tokenUnion(buf, token); + rxrpc::seal_buffer(buf); + + verbose_gss("XDR encoded token {}\n", buf->size); + + kafs::afs::ktc_setTokenData payload; + payload.flags = 0; + payload.cell = ctx->cell_name; + payload.tokens.resize(1); + + rxrpc::Opaque &tok = payload.tokens[0]; + tok.buffer = buf->enc_buf; + tok.buffer_size = buf->size; + tok.del_buffer = false; + + rxrpc::Resv resv2 = { .max_ioc = 1, .flat = true }; + kafs::afs::xdr::reserve_ktc_setTokenData(resv2, payload); + ref buf2 = alloc_enc_buffer(resv2); + kafs::afs::xdr::encode_ktc_setTokenData(buf2, payload); + rxrpc::seal_buffer(buf2); + + std::string desc = fmt::format("test@{}", ctx->cell_name); + + verbose_gss("XDR encoded payload {}\n", buf2->size); + verbose_gss("Key name {}\n", desc); + + key_serial_t key = add_key("rxrpc", desc.c_str(), buf2->enc_buf, buf2->size, + KEY_SPEC_SESSION_KEYRING); + if (key == -1) + throw std::system_error( + std::error_code(errno, std::generic_category()), + fmt::format("GSSAPI: Failed to add key '{}'", desc)); +} + +/*** + * COMMAND: gssapi test - GSSAPI tester + * ARG: "-server " + * ARG: "[-cell ]" + * ARG: "[-enctype ]" + * ARG: "[-level ]" + * ARG: "[-tenctype ]" + * ARG: "[-kvno ]" + * ARG: "[-verbose]" + * ARG: "[-encrypt]" - Auth + * + * Forge a ticket and poke the server by RxGK. + */ +void COMMAND_gssapi_test( + kafs::Context *ctx, + kafs::Volserver_spec &a_server, + std::string &a_enctype, + std::string &a_level, + std::string &a_tenctype, + std::string &a_kvno, + bool a_verbose) +{ + rxrpc::security_auth_level rxlevel = rxrpc::security_encrypt; + RXGK_ClientInfo client_info; + rxrpc::Opaque ticket_key, K0; + unsigned int kvno = 1; + int tenctype = ENCTYPE_AES128_CTS_HMAC_SHA1_96; + int tklen, k0len, i; + + client_info.expiration = 0; + client_info.level = kafs::afs::RXGK_LEVEL_CRYPT; + client_info.lifetime = 0; + client_info.bytelife = 0; + client_info.enctype = ENCTYPE_AES128_CTS_HMAC_SHA1_96; + + if (a_enctype.size()) + client_info.enctype = stoi(a_enctype); + + switch (client_info.enctype) { + case ENCTYPE_AES128_CTS_HMAC_SHA1_96: k0len = 16; break; + case ENCTYPE_AES256_CTS_HMAC_SHA1_96: k0len = 32; break; + case ENCTYPE_AES128_CTS_HMAC_SHA256_128: k0len = 16; break; + case ENCTYPE_AES256_CTS_HMAC_SHA384_192: k0len = 32; break; + case ENCTYPE_CAMELLIA128_CTS_CMAC: k0len = 16; break; + case ENCTYPE_CAMELLIA256_CTS_CMAC: k0len = 32; break; + default: + throw std::runtime_error( + fmt::format("GSSAPI Unknown enctype {}", client_info.enctype)); + } + + K0.reserve(k0len); + for (i = 0; i < k0len; i++) + ((unsigned char *)K0.buffer)[i] = i; + K0.buffer_size = k0len; + + if (a_level.size()) { + if (a_level == "a") { + client_info.level = kafs::afs::RXGK_LEVEL_CLEAR; + rxlevel = rxrpc::security_clear; + } else if (a_level == "i") { + client_info.level = kafs::afs::RXGK_LEVEL_AUTH; + rxlevel = rxrpc::security_integrity_only; + } else if (a_level == "c") { + client_info.level = kafs::afs::RXGK_LEVEL_CRYPT; + rxlevel = rxrpc::security_encrypt; + } else { + throw std::invalid_argument("Unknown level"); + } + } + + if (a_tenctype.size()) + tenctype = stoi(a_tenctype); + + switch (tenctype) { + case ENCTYPE_AES128_CTS_HMAC_SHA1_96: tklen = 16; break; + case ENCTYPE_AES256_CTS_HMAC_SHA1_96: tklen = 32; break; + case ENCTYPE_AES128_CTS_HMAC_SHA256_128: tklen = 16; break; + case ENCTYPE_AES256_CTS_HMAC_SHA384_192: tklen = 32; break; + case ENCTYPE_CAMELLIA128_CTS_CMAC: tklen = 16; break; + case ENCTYPE_CAMELLIA256_CTS_CMAC: tklen = 32; break; + default: + throw std::runtime_error( + fmt::format("GSSAPI Unknown enctype {}", tenctype)); + } + + ticket_key.reserve(tklen); + for (i = 0; i < tklen; i++) + ((unsigned char *)ticket_key.buffer)[i] = i; + ticket_key.buffer_size = tklen; + + if (a_kvno.size()) + kvno = stoi(a_kvno); + + forge_rxgk_ticket(ctx, client_info, ticket_key, K0, tenctype, kvno, client_info.token); + rxgk_add_key(ctx, client_info, K0, a_verbose, kafs::afs::AFSTOKEN_UNION_YFSGK); + + rxrpc::find_transport(); + + ctx->security = rxrpc::new_security(fmt::format("test@{}", ctx->cell_name), rxlevel); + + kafs::open_endpoint(ctx); + + rxrpc::Call_params params; + params.endpoint = ctx->endpoint; + + ref site = kafs::resolve_server_spec(ctx, a_server); + params.peer = site->vs_addrs[0]; + params.peer.srx_service = RX_PERF_SERVICE; + if (params.peer.transport.family == AF_INET) + params.peer.transport.sin.sin_port = htons(TEST_PORT); + else + params.peer.transport.sin6.sin6_port = htons(TEST_PORT); + params.peer_len = sizeof(params.peer); + + std::vector caps; + kafs::afs::RXAFS::GetCapabilities(¶ms, caps); + fmt::print("Success!"); + for (size_t i = 0; i < caps.size(); i++) + fmt::print(" {:08x}", caps[i]); + fmt::print("\n"); +} diff --git a/rpc-api/add_key.xg b/rpc-api/add_key.xg index 5290839..508bbe0 100644 --- a/rpc-api/add_key.xg +++ b/rpc-api/add_key.xg @@ -8,6 +8,7 @@ typedef int64_t opr_time; const AFSTOKEN_RK_TIX_MAX = 12000; /* Matches entry in rxkad.h */ +const AFSTOKEN_GK_TOK_MAX = 1048576; /* Matches RXGK_MAXDATA in rxgk_int.xg */ struct token_rxkad { afs_int32 viceid; @@ -19,6 +20,17 @@ struct token_rxkad { opaque ticket; }; +struct token_rxgk { + afs_int64 gk_viceid; + afs_int32 gk_enctype; + afs_int32 gk_level; + afs_uint32 gk_lifetime; + afs_uint32 gk_bytelife; + afs_int64 gk_expiration; + opaque gk_token; + opaque gk_k0; +}; + struct token_yfs_rxgk { opr_time begintime; opr_time endtime; @@ -32,11 +44,14 @@ struct token_yfs_rxgk { const AFSTOKEN_UNION_NOAUTH = 0; const AFSTOKEN_UNION_KAD = 2; +const AFSTOKEN_UNION_GK = 4; const AFSTOKEN_UNION_YFSGK = 6; union ktc_tokenUnion switch (afs_int32 type) { case AFSTOKEN_UNION_KAD: token_rxkad kad; + case AFSTOKEN_UNION_GK: + token_rxgk gk; case AFSTOKEN_UNION_YFSGK: token_yfs_rxgk ygk; }; -- 2.49.0