--- /dev/null
+/* 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 <iostream>
+#include <algorithm>
+#include <fmt/ostream.h>
+#include <krb5/krb5.h>
+#include <gssapi/gssapi.h>
+extern "C" {
+#define private foo
+#include <keyutils.h>
+#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<AUTHDATAMAX>;
+ * opaque display<AUTHPRINTABLEMAX>;
+ * };
+ *
+ * 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<rxrpc::Enc_buffer> 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<rxrpc::Enc_buffer> 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<rxrpc::Enc_buffer> 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<rxrpc::Enc_buffer> 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 <server name>"
+ * ARG: "[-cell <cell name>]"
+ * ARG: "[-enctype <enc>]"
+ * ARG: "[-level <level>]"
+ * ARG: "[-tenctype <enc>]"
+ * ARG: "[-kvno <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<kafs::FS_site> 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<unsigned int> 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");
+}