--- /dev/null
+/* GSSAPI-based aklog
+ *
+ * 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"
+
+using rxrpc::ref;
+using kafs::afs::RXGK_StartParams;
+using kafs::afs::RXGK_ClientInfo;
+
+static bool debug_gss = true;
+
+#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)
+
+struct rxgk_nonce {
+ unsigned int nonce[kafs::afs::RXGK_NONCE_SIZE / sizeof(unsigned int)];
+
+ void set() {
+ for (size_t i = 0;
+ i < sizeof(nonce) / sizeof(unsigned int);
+ i += sizeof(unsigned int))
+ nonce[i] = random();
+ }
+
+ size_t size() const { return sizeof(nonce); }
+};
+
+static size_t rxgk_get_enc_len(RXGK_ClientInfo &client_info)
+{
+ switch (client_info.enctype) {
+ case ENCTYPE_AES128_CTS_HMAC_SHA1_96: return 128 / 8;
+ case ENCTYPE_AES256_CTS_HMAC_SHA1_96: return 256 / 8;
+ case ENCTYPE_AES128_CTS_HMAC_SHA256_128: return 128 / 8;
+ case ENCTYPE_AES256_CTS_HMAC_SHA384_192: return 256 / 8;
+ case ENCTYPE_CAMELLIA128_CTS_CMAC: return 128 / 8;
+ case ENCTYPE_CAMELLIA256_CTS_CMAC: return 128 / 8;
+ default:
+ throw std::runtime_error(
+ fmt::format("GSSAPI Unknown enctype {}", client_info.enctype));
+ }
+}
+
+static void throw_error(const std::string &what, OM_uint32 major, OM_uint32 minor)
+{
+ gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
+ OM_uint32 ret_minor, mc;
+
+ debug_gss("{} -> maj={:x} min={:x})\n", what, major, minor);
+
+ if (GSS_ERROR(major)) {
+ gss_display_status(&ret_minor, minor, GSS_C_MECH_CODE, GSS_C_NO_OID,
+ &mc, &buf);
+ std::string msg((const char *)buf.value, buf.length);
+
+ throw std::runtime_error(
+ fmt::format("GSSAPI {}: {} [{:d},{:d}]",
+ what, msg, major, minor));
+ }
+}
+
+/*
+ * Invoke RxGK negotiation on the RxGK server.
+ */
+static void rxgk_negotiate(rxrpc::Call_params &gss_params,
+ RXGK_StartParams &client_start,
+ gss_buffer_desc &input_token,
+ gss_buffer_desc &output_token,
+ rxrpc::Opaque &opaque_cache,
+ rxrpc::Opaque &_rxgk_info,
+ bool a_verbose)
+{
+ rxrpc::Opaque input_token_buffer;
+ rxrpc::Opaque output_token_buffer;
+ rxrpc::Opaque opaque_out;
+
+ uint32_t gss_major_status;
+ uint32_t gss_minor_status;
+ rxrpc::Opaque rxgk_info;
+
+ debug_gss("-> GSSNegotiate()\n");
+
+ input_token_buffer.buffer_size = input_token.length;
+ input_token_buffer.buffer = input_token.value;
+
+ debug_gss("input token {}\n", input_token_buffer.size());
+ debug_gss("input opaque {}\n", opaque_cache.size());
+
+ verbose_gss("Invoke RXGK::GSSNegotiate()\n");
+
+ kafs::afs::YFSRXGK::GSSNegotiate(&gss_params,
+ client_start, input_token_buffer, opaque_cache,
+ output_token_buffer, opaque_out,
+ gss_major_status, gss_minor_status, rxgk_info);
+
+ debug_gss("GSSNegotiate() -> maj={:x} min={:x}\n", gss_major_status, gss_minor_status);
+ debug_gss("output token {}\n", output_token_buffer.size());
+ debug_gss("output opaque {}\n", opaque_out.size());
+ debug_gss("output info {}\n", rxgk_info.size());
+
+ output_token.value = output_token_buffer.buffer;
+ output_token.length = output_token_buffer.size();
+ output_token_buffer.del_buffer = false;
+
+ if (opaque_cache.del_buffer)
+ free(opaque_cache.buffer);
+ opaque_cache = opaque_cache;
+ opaque_out.del_buffer = false;
+
+ if (_rxgk_info.del_buffer)
+ free(_rxgk_info.buffer);
+ _rxgk_info = rxgk_info;
+ rxgk_info.del_buffer = false;
+}
+
+/*
+ * Unwrap the RxGK client information.
+ */
+static void rxgk_unwrap(gss_ctx_id_t &gssctx, rxrpc::Opaque &rxgk_info,
+ RXGK_ClientInfo &client_info)
+{
+ gss_buffer_desc wrapping = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc info = GSS_C_EMPTY_BUFFER;
+ gss_qop_t qop_state;
+ OM_uint32 major, minor;
+ int conf_state;
+
+ wrapping.length = rxgk_info.size();
+ wrapping.value = rxgk_info.buffer;
+
+ debug_gss("rxgk_info: {}\n", rxgk_info.size());
+ major = gss_unwrap(&minor, gssctx, &wrapping, &info, &conf_state, &qop_state);
+ if (GSS_ERROR(major))
+ throw_error("gss_unwrap()", major, minor);
+
+ debug_gss("unwrapped: {}\n", info.length);
+
+ rxrpc::Rx_queue rxq("RXGK::GSSNegotiate");
+ rxq.client_side = true;
+ rxq.dec_amount = info.length;
+
+ rxrpc::Rx_buffer buf;
+ buf.buf = (unsigned char *)info.value;
+ buf.len = info.length;
+ buf.no_delete = true;
+ rxq.dec_buffers.push_back(buf);
+
+ kafs::afs::xdr::decode_RXGK_ClientInfo(rxq, client_info);
+
+ debug_gss("error : {}\n", client_info.errorcode);
+ debug_gss("enctype : {}\n", client_info.enctype);
+ debug_gss("level : {}\n", client_info.level);
+ debug_gss("lifetime: {}\n", client_info.lifetime);
+ debug_gss("bytelife: {}\n", client_info.bytelife);
+ debug_gss("expire : {}\n", client_info.expiration);
+ debug_gss("mic : size={}\n", client_info.mic.size());
+ debug_gss("token : size={}\n", client_info.token.size());
+ debug_gss("s-nonce : size={}\n", client_info.server_nonce.size());
+}
+
+/*
+ * Check the MIC on the last GSSNegotiate call.
+ */
+static void rxgk_check_mic(gss_ctx_id_t &gssctx,
+ RXGK_StartParams &rxgk_params,
+ RXGK_ClientInfo &client_info,
+ bool a_verbose)
+{
+ gss_qop_t qop_state;
+ OM_uint32 major, minor;
+
+ rxrpc::Resv resv = { .max_ioc = 1, .flat = true };
+ kafs::afs::xdr::reserve_RXGK_StartParams(resv, rxgk_params);
+
+ ref<rxrpc::Enc_buffer> buf = alloc_enc_buffer(resv);
+ kafs::afs::xdr::encode_RXGK_StartParams(buf, rxgk_params);
+ rxrpc::seal_buffer(buf);
+
+ gss_buffer_desc msg = {
+ .length = buf->size,
+ .value = buf->enc_buf,
+ };
+ gss_buffer_desc mic = {
+ .length = client_info.mic.size(),
+ .value = client_info.mic.buffer,
+ };
+
+ verbose_gss("Verify MIC\n");
+ major = gss_verify_mic(&minor, gssctx, &msg, &mic, &qop_state);
+ if (GSS_ERROR(major))
+ throw_error("gss_verify_mic()", major, minor);
+}
+
+/*
+ * Derive the transport key, K0.
+ */
+static void rxgk_derive_K0(gss_ctx_id_t &gssctx,
+ RXGK_StartParams &rxgk_params,
+ RXGK_ClientInfo &client_info,
+ rxrpc::Opaque &K0_buffer,
+ bool a_verbose)
+{
+ gss_buffer_desc K0 = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc prf_in;
+ OM_uint32 major, minor;
+ size_t enclen = rxgk_get_enc_len(client_info);
+ size_t csize = rxgk_params.client_nonce.size();
+ size_t ssize = client_info.server_nonce.size();
+
+ prf_in.length = csize + ssize;
+ prf_in.value = alloca(prf_in.length);
+ memcpy(prf_in.value, rxgk_params.client_nonce.buffer, csize);
+ memcpy((char *)prf_in.value + csize, client_info.server_nonce.buffer, ssize);
+
+ major = gss_pseudo_random(&minor, gssctx, GSS_C_PRF_KEY_FULL, &prf_in,
+ enclen, &K0);
+ if (GSS_ERROR(major))
+ throw_error("gss_pseudo_random()", major, minor);
+
+ debug_gss("K0 size {}\n", K0.length);
+
+ K0_buffer.buffer_size = K0.length;
+ K0_buffer.buffer = K0.value;
+ K0_buffer.del_buffer = true;
+}
+
+static void clear_gss_buffer(gss_buffer_t buf)
+{
+ free(buf->value);
+ buf->value = NULL;
+ buf->length = 0;
+}
+
+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("afs@{}", 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 aklog - Authenticate via GSSAPI for RxGK
+ * ARG: "[-cell <cell name>]"
+ * ARG: "[-principal <principal name>]"
+ * ARG: "[-enctypes <enc>+]"
+ * ARG: "[-levels <levels>+]"
+ * ARG: "[-noauth]" - Auth
+ * ARG: "[-localauth]" - Auth
+ * ARG: "[-verbose]"
+ * ARG: "[-encrypt]" - Auth
+ *
+ * Authenticate via GSSAPI to get tokens for the RxGK security class.
+ */
+void COMMAND_gssapi_aklog(
+ kafs::Context *ctx,
+ std::string &a_principal,
+ std::vector<std::string> &a_enctypes,
+ std::vector<std::string> &a_levels,
+ bool a_verbose)
+{
+ gss_buffer_desc token_for_gssapi = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc token_for_rxgk = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER;
+ gss_ctx_id_t gssctx = GSS_C_NO_CONTEXT;
+ gss_name_t target_name = GSS_C_NO_NAME;
+ gss_OID actual_mech = GSS_C_NO_OID;
+ OM_uint32 major, minor, req_flags, ret_flags;
+ bool anon = false;
+
+ ref<kafs::VL_service> vlservice = new kafs::VL_service(ctx);
+ rxrpc::Opaque opaque_cache, rxgk_info, K0;
+ RXGK_ClientInfo client_info;
+
+ rxrpc::Call_params gss_params = vlservice->vl_params;
+ gss_params.peer.srx_service = kafs::afs::YFSRXGK_SERVICE_ID;
+ gss_params.security = NULL;
+
+ rxgk_nonce nonce;
+ nonce.set();
+
+ RXGK_StartParams rxgk_params;
+ if (a_enctypes.size() == 0) {
+ rxgk_params.enctypes.push_back(ENCTYPE_AES128_CTS_HMAC_SHA1_96);
+ rxgk_params.enctypes.push_back(ENCTYPE_AES256_CTS_HMAC_SHA1_96);
+ rxgk_params.enctypes.push_back(ENCTYPE_AES128_CTS_HMAC_SHA256_128);
+ rxgk_params.enctypes.push_back(ENCTYPE_AES256_CTS_HMAC_SHA384_192);
+ rxgk_params.enctypes.push_back(ENCTYPE_CAMELLIA128_CTS_CMAC);
+ rxgk_params.enctypes.push_back(ENCTYPE_CAMELLIA256_CTS_CMAC);
+ } else {
+ for (size_t i = 0; i < a_enctypes.size(); i++) {
+ std::string &enc = a_enctypes[i];
+ rxgk_params.enctypes.push_back(stoi(enc));
+ }
+ }
+
+ if (a_levels.size() == 0) {
+ rxgk_params.levels.push_back(kafs::afs::RXGK_LEVEL_CLEAR);
+ rxgk_params.levels.push_back(kafs::afs::RXGK_LEVEL_AUTH);
+ rxgk_params.levels.push_back(kafs::afs::RXGK_LEVEL_CRYPT);
+ } else {
+ for (size_t i = 0; i < a_levels.size(); i++) {
+ std::string &level = a_levels[i];
+ if (level == "a")
+ rxgk_params.levels.push_back(kafs::afs::RXGK_LEVEL_CLEAR);
+ else if (level == "i")
+ rxgk_params.levels.push_back(kafs::afs::RXGK_LEVEL_AUTH);
+ else if (level == "c")
+ rxgk_params.levels.push_back(kafs::afs::RXGK_LEVEL_CRYPT);
+ else
+ throw std::invalid_argument("Unknown level");
+ }
+ }
+
+ rxgk_params.lifetime = 0;
+ rxgk_params.bytelife = 0;
+ rxgk_params.client_nonce.buffer_size = nonce.size();
+ rxgk_params.client_nonce.buffer = nonce.nonce;
+
+ try {
+ std::string name = fmt::format("yfs-rxgk@_afs.{}", ctx->cell_name);
+
+ verbose_gss("Name: {}\n", name);
+
+ name_buf.value = (void *)name.c_str();
+ name_buf.length = name.size();
+ major = gss_import_name(&minor, &name_buf,
+ GSS_C_NT_HOSTBASED_SERVICE, &target_name);
+ if (GSS_ERROR(major))
+ throw std::runtime_error("GSSAPI: Could not import name");
+
+ /* Mutual authentication will require a token from acceptor to
+ * initiator and thus a second call to gss_init_sec_context().
+ */
+ req_flags = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG; // | GSS_C_CONF_FLAG
+ if (anon)
+ req_flags |= GSS_C_ANON_FLAG;
+
+ for (;;) {
+ verbose_gss("Invoke gss_init_sec_context()\n");
+
+ /* The initiator_cred_handle, mech_type, time_req,
+ * input_chan_bindings, actual_mech_type, and time_rec
+ * parameters are not needed in many cases. We pass
+ * GSS_C_NO_CREDENTIAL, GSS_C_NO_OID, 0, NULL, NULL,
+ * and NULL for them, respectively.
+ */
+ major = gss_init_sec_context(
+ &minor,
+ GSS_C_NO_CREDENTIAL, /* Claimant cred */
+ &gssctx,
+ target_name, /* Target name */
+ GSS_C_NO_OID, /* Mech type */
+ req_flags, /* Request flags */
+ GSS_C_INDEFINITE, /* Lifetime req */
+ GSS_C_NO_CHANNEL_BINDINGS, /* Input chan bindings */
+ &token_for_gssapi, /* Input token */
+ &actual_mech, /* Actual mech type */
+ &token_for_rxgk, /* Output token */
+ &ret_flags, /* Return flags */
+ NULL); /* Lifetime_rec */
+
+ if (GSS_ERROR(major)) {
+ /* Should really call rxgk_negotiate() again
+ * here to transfer the error to the server.
+ */
+ throw_error("gss_init_sec_context()", major, minor);
+ }
+
+ clear_gss_buffer(&token_for_gssapi);
+
+ /* Check that an anonymous initiator retained their
+ * anonymity.
+ */
+ if (anon && !(ret_flags & GSS_C_ANON_FLAG))
+ throw std::runtime_error(
+ "GSSAPI: Anonymous requested but not available");
+
+ /* Always send a token if we are expecting another input token
+ * (GSS_S_CONTINUE_NEEDED is set) or if it is nonempty.
+ */
+ if ((major & GSS_S_CONTINUE_NEEDED) ||
+ token_for_rxgk.length > 0)
+ rxgk_negotiate(gss_params, rxgk_params,
+ token_for_rxgk, token_for_gssapi,
+ opaque_cache, rxgk_info, a_verbose);
+
+ gss_release_buffer(&minor, &token_for_rxgk);
+
+ if (major & GSS_S_CONTINUE_NEEDED)
+ continue;
+
+ if (major == GSS_S_COMPLETE)
+ break;
+
+ /* This situation is forbidden by RFC 2743. Bail out. */
+ throw std::runtime_error(
+ "GSSAPI: major not complete or continue but not error");
+ }
+
+ if ((ret_flags & req_flags) != req_flags)
+ throw std::runtime_error(
+ "GSSAPI: Negotiated context does not support requested flags");
+
+ rxgk_unwrap(gssctx, rxgk_info, client_info);
+ rxgk_check_mic(gssctx, rxgk_params, client_info, a_verbose);
+ rxgk_derive_K0(gssctx, rxgk_params, client_info, K0, a_verbose);
+
+ rxgk_add_key(ctx, client_info, K0, a_verbose,
+ kafs::afs::AFSTOKEN_UNION_YFSGK);
+
+ verbose_gss("Negotiation successful\n");
+ } catch (...) {
+ gss_release_buffer(&minor, &token_for_rxgk);
+ gss_delete_sec_context(&minor, &gssctx, NULL);
+ gss_release_name(&minor, &target_name);
+ throw;
+ }
+
+ gss_release_buffer(&minor, &token_for_rxgk);
+ gss_delete_sec_context(&minor, &gssctx, NULL);
+ gss_release_name(&minor, &target_name);
+}
--- /dev/null
+/* -*- c -*-
+ *
+ * Auristor/YFS fileserver and cache manager opcodes. Please do not add or
+ * modify opcodes here without consulting Auristor, Inc..
+ *
+ * Copyright (c) 2015-2020 AuriStor, Inc. <https://www.auristor.com/>
+ * 255 W 94th St, New York NY 10025-6985 United States, Earth, Sol, Milky Way
+ *
+ * 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.
+ */
+
+package YFSRXGK_
+//prefix S
+//statindex 14
+
+//%#include <opr/time.h>
+//%#include <opr/uuid.h>
+//%#include "rxgk.h"
+//%#include "util.h"
+
+//%#ifndef DEFINED_OPR_UUID_VECTOR
+//%#define DEFINED_OPR_UUID_VECTOR 1
+//%typedef struct opr_uuid opr_uuid_vector;
+//%#endif
+
+/* The spec requires us to use PrAuthName, as defined by the Extended
+ * Authentication Names specifcation. However, in our implementation this
+ * code is in the ptserver, not in the RX layer. Directly using those
+ * symbols here would be a layering violation, so just use an idential
+ * structure with a different name - they'll be identical on the wire.
+ */
+
+const PRAUTHTYPE_GSS = 2;
+
+struct RXGK_AuthName {
+ afs_int32 kind;
+ opaque data<AUTHDATAMAX>;
+ opaque display<AUTHPRINTABLEMAX>;
+};
+
+/* Key derivation values */
+
+const YFSRXGK_SERVICE_ID = 2508;
+const RXGK_SERVICE_ID = 34567;
+
+const RXGK_NONCE_SIZE = 20;
+
+/* Limits */
+//const RXGK_MAXENCTYPES = 255;
+//const RXGK_MAXLEVELS = 255;
+//const RXGK_MAXMIC = 1024;
+//const RXGK_MAXNONCE = 1024;
+//const RXGK_MAXDATA = 1048576;
+const RXGK_MAXAUTHENTICATOR = 1416;
+
+//typedef afs_int32 RXGK_Enctypes<RXGK_MAXENCTYPES>;
+typedef RXGK_Level RXGK_Levels<RXGK_MAXLEVELS>;
+
+//typedef opaque RXGK_Data<RXGK_MAXDATA>;
+
+#if 0
+struct RXGK_StartParams {
+ RXGK_Enctypes enctypes;
+ RXGK_Levels levels;
+ afs_int32 lifetime;
+ afs_int32 bytelife;
+ opaque client_nonce<RXGK_MAXNONCE>;
+};
+
+struct RXGK_ClientInfo {
+ afs_int32 errorcode;
+ afs_int32 enctype;
+ RXGK_Level level;
+ afs_int32 lifetime;
+ afs_int32 bytelife;
+ rxgkTime expiration;
+ opaque mic<RXGK_MAXMIC>;
+ opaque token<RXGK_MAXDATA>;
+ opaque server_nonce<RXGK_MAXNONCE>;
+};
+
+struct RXGK_CombineOptions {
+ RXGK_Enctypes enctypes;
+ RXGK_Levels levels;
+};
+
+struct RXGK_TokenInfo {
+ afs_int32 enctype;
+ RXGK_Level level;
+ afs_int32 lifetime;
+ afs_int32 bytelife;
+ rxgkTime expirationtime;
+};
+#endif
+
+GSSNegotiate(IN RXGK_StartParams *client_start,
+ IN RXGK_Data *input_token_buffer,
+ IN RXGK_Data *opaque_in,
+ OUT RXGK_Data *output_token_buffer,
+ OUT RXGK_Data *opaque_out,
+ OUT afs_uint32 *gss_major,
+ OUT afs_uint32 *gss_minor,
+ OUT RXGK_Data *rxgk_info) = 1;
+
+CombineTokens(IN RXGK_Data *token0,
+ IN RXGK_Data *token1,
+ IN RXGK_CombineOptions *options,
+ OUT RXGK_Data *new_token,
+ OUT RXGK_TokenInfo *info) = 2;
+
+const RXGKCAPABILITIESMAX = 196;
+typedef afs_uint32 RXGKCapabilities<RXGKCAPABILITIESMAX>;
+
+GetCapabilities(
+ OUT RXGKCapabilities *caps
+) multi = 5;
+
+/**********************************************************************
+ * RX challenge and response
+ **********************************************************************/
+
+struct RXGK_Challenge {
+ opaque nonce<RXGK_NONCE_SIZE>;
+};
+
+struct RXGK_Response {
+ rxgkTime start_time;
+ opaque token<RXGK_MAXDATA>;
+ opaque authenticator<RXGK_MAXAUTHENTICATOR>;
+};
+
+struct RXGK_Authenticator {
+ opaque nonce<RXGK_NONCE_SIZE>;
+ opaque appdata<>;
+ RXGK_Level level;
+ afs_uint32 epoch;
+ afs_uint32 cid;
+ afs_int32 call_numbers<>;
+};
+
+/**********************************************************************
+ * AFS specifics
+ **********************************************************************/
+
+#if 0
+AFSCombineTokensOld(IN RXGK_Data *token0,
+ IN RXGK_Data *token1,
+ IN RXGK_CombineOptions *options,
+ IN opr_uuid *destination,
+ OUT RXGK_Data *new_token,
+ OUT RXGK_TokenInfo *token_info) = 3;
+#endif
+
+AFSCombineTokens(IN RXGK_Data *token0,
+ IN RXGK_Data *token1,
+ IN RXGK_CombineOptions *options,
+ IN opr_uuid *destination,
+ OUT RXGK_Data *new_token,
+ OUT RXGK_TokenInfo *token_info) = 4;
+
+/* Token format */
+
+struct rxgk_key {
+ afs_uint32 enctype;
+ opaque key<>;
+};
+
+struct RXGK_Token {
+ rxgk_key K0;
+ RXGK_Level level;
+ rxgkTime starttime;
+ afs_int32 lifetime;
+ afs_int32 bytelife;
+ rxgkTime expirationtime;
+ struct RXGK_AuthName identities<>;
+};
+
+struct RXGK_TokenContainer {
+ afs_int32 kvno;
+ afs_int32 enctype;
+ opaque encrypted_token<>;
+};
+
+#if 0
+/* Server registration (actual RPC is a generic VL one, these are
+ * just marshalling types
+ */
+
+struct RXGK_ServerKeyDataRequest {
+ afs_int32 enctypes<>;
+ opaque nonce1<>;
+};
+
+struct RXGK_ServerKeyDataResponse {
+ afs_int32 enctype;
+ opaque nonce2<>;
+};
+#endif