Implement the rxgk server security object routines
[openafs.git] / src / rxgk / rxgk_server.c
index 241b8c8..06bd46f 100644 (file)
 #include <afs/param.h>
 #include <afs/stds.h>
 
+#include <roken.h>
+
 #include <afs/opr.h>
 #include <rx/rx.h>
+#include <rx/xdr.h>
 #include <rx/rx_packet.h>
 #include <rx/rxgk.h>
 
 #include "rxgk_private.h"
 
-/* Pre-declare the securityclass routines for the securityOps definition. */
-struct rx_securityClass *rxgk_NewServerSecurityObject(void *getkey_rock,
-                                                     rxgk_getkey_func getkey);
-static int rxgk_ServerClose(struct rx_securityClass *aobj);
-static int rxgk_NewServerConnection(struct rx_securityClass *aobj,
-                                   struct rx_connection *aconn);
-static int rxgk_ServerPreparePacket(struct rx_securityClass *aobj,
-                                   struct rx_call *acall,
-                                   struct rx_packet *apacket);
-static int rxgk_CheckAuthentication(struct rx_securityClass *aobj,
-                                   struct rx_connection *aconn);
-static int rxgk_CreateChallenge(struct rx_securityClass *aobj,
-                               struct rx_connection *aconn);
-static int rxgk_GetChallenge(struct rx_securityClass *aobj,
-                            struct rx_connection *aconn,
-                            struct rx_packet *apacket);
-static int rxgk_CheckResponse(struct rx_securityClass *aobj,
-                             struct rx_connection *aconn,
-                             struct rx_packet *apacket);
-static int rxgk_ServerCheckPacket(struct rx_securityClass *aobj,
-                                 struct rx_call *acall, struct rx_packet *apacket);
-static void rxgk_DestroyServerConnection(struct rx_securityClass *aobj,
-                                        struct rx_connection *aconn);
-static int rxgk_ServerGetStats(struct rx_securityClass *aobj,
-                              struct rx_connection *aconn,
-                              struct rx_securityObjectStats *astats);
-
+/*
+ * Increment the reference count on the security object secobj.
+ */
+static_inline void
+obj_ref(struct rx_securityClass *secobj)
+{
+    secobj->refCount++;
+}
 
-static struct rx_securityOps rxgk_server_ops = {
-    rxgk_ServerClose,
-    rxgk_NewServerConnection,
-    rxgk_ServerPreparePacket,          /* once per packet creation */
-    0,                                 /* send packet (once per retrans) */
-    rxgk_CheckAuthentication,
-    rxgk_CreateChallenge,
-    rxgk_GetChallenge,
-    0,
-    rxgk_CheckResponse,
-    rxgk_ServerCheckPacket,            /* check data packet */
-    rxgk_DestroyServerConnection,
-    rxgk_ServerGetStats,
-    0,
-    0,                         /* spare 1 */
-    0,                         /* spare 2 */
-};
+/*
+ * Decrement the reference count on the security object secobj.
+ * If the reference count falls to zero, release the underlying storage.
+ */
+static void
+obj_rele(struct rx_securityClass *secobj)
+{
+    struct rxgk_sprivate *sp;
 
-static struct rx_securityClass dummySC = {
-    &rxgk_server_ops,
-    NULL,
-    0
-};
+    secobj->refCount--;
+    if (secobj->refCount > 0) {
+       /* still in use */
+       return;
+    }
 
-struct rx_securityClass *
-rxgk_NewServerSecurityObject(void *getkey_rock, rxgk_getkey_func getkey)
-{
-    return &dummySC;
+    sp = secobj->privateData;
+    rxi_Free(secobj, sizeof(*secobj));
+    rxi_Free(sp, sizeof(*sp));
+    return;
 }
 
+/* Release a server security object. */
 static int
 rxgk_ServerClose(struct rx_securityClass *aobj)
 {
-    return RXGK_INCONSISTENCY;
+    obj_rele(aobj);
+    return 0;
+}
+
+/* Set fields in 'sc' to invalid/uninitialized values, so we don't accidentally
+ * use blank/zeroed values later. */
+static void
+sconn_set_noauth(struct rxgk_sconn *sc)
+{
+    rxgk_release_key(&sc->k0);
+    if (sc->client != NULL)
+        rx_identity_free(&sc->client);
+    sc->start_time = 0;
+    sc->auth = 0;
+
+    /*
+     * The values here should never be seen; set some bogus values. For
+     * 'expiration' and 'level', values of 0 are not bogus, so we explicitly
+     * set some nonzero values that are sure to be invalid, just in case they
+     * get used.
+     */
+    sc->expiration = 1;
+    sc->level = RXGK_LEVEL_BOGUS;
 }
 
+/*
+ * Create a new rx connection on this given server security object.
+ */
 static int
-rxgk_NewServerConnection(struct rx_securityClass *aobj, struct rx_connection *aconn)
+rxgk_NewServerConnection(struct rx_securityClass *aobj,
+                        struct rx_connection *aconn)
 {
+    struct rxgk_sconn *sc;
+
+    if (rx_GetSecurityData(aconn) != NULL)
+       goto error;
+
+    sc = rxi_Alloc(sizeof(*sc));
+    if (sc == NULL)
+       goto error;
+
+    sconn_set_noauth(sc);
+    rx_SetSecurityData(aconn, sc);
+    obj_ref(aobj);
+    return 0;
+
+ error:
     return RXGK_INCONSISTENCY;
 }
 
+/*
+ * Server-specific packet preparation routine. All the interesting bits are in
+ * rxgk_packet.c; all we have to do here is extract data from the security data
+ * on the connection and use the proper key usage.
+ */
 static int
 rxgk_ServerPreparePacket(struct rx_securityClass *aobj, struct rx_call *acall,
                         struct rx_packet *apacket)
 {
-    return RXGK_INCONSISTENCY;
+    struct rxgk_sconn *sc;
+    struct rx_connection *aconn;
+    rxgk_key tk;
+    afs_uint32 lkvno;
+    afs_uint16 wkvno, len;
+    int ret;
+
+    aconn = rx_ConnectionOf(acall);
+    sc = rx_GetSecurityData(aconn);
+
+    if (sc->expiration < RXGK_NOW() && sc->expiration != RXGK_NEVERDATE)
+       return RXGK_EXPIRED;
+
+    len = rx_GetDataSize(apacket);
+    lkvno = sc->key_number;
+    sc->stats.psent++;
+    sc->stats.bsent += len;
+    wkvno = (afs_uint16)lkvno;
+    rx_SetPacketCksum(apacket, wkvno);
+
+    if (sc->level == RXGK_LEVEL_CLEAR)
+       return 0;
+
+    ret = rxgk_derive_tk(&tk, sc->k0, rx_GetConnectionEpoch(aconn),
+                        rx_GetConnectionId(aconn), sc->start_time, lkvno);
+    if (ret != 0)
+       return ret;
+
+    switch(sc->level) {
+       case RXGK_LEVEL_AUTH:
+           ret = rxgk_mic_packet(tk, RXGK_SERVER_MIC_PACKET, aconn, apacket);
+           break;
+       case RXGK_LEVEL_CRYPT:
+           ret = rxgk_enc_packet(tk, RXGK_SERVER_ENC_PACKET, aconn, apacket);
+           break;
+       default:
+           ret = RXGK_INCONSISTENCY;
+           break;
+    }
+
+    rxgk_release_key(&tk);
+    return ret;
 }
 
+/* Did a connection properly authenticate? */
 static int
 rxgk_CheckAuthentication(struct rx_securityClass *aobj,
                         struct rx_connection *aconn)
 {
-    return RXGK_INCONSISTENCY;
+    struct rxgk_sconn *sc;
+
+    sc = rx_GetSecurityData(aconn);
+    if (sc == NULL)
+       return RXGK_INCONSISTENCY;
+
+    if (sc->auth == 0)
+       return RXGK_NOTAUTH;
+
+    return 0;
 }
 
+/* Generate a challenge to be used later. */
 static int
 rxgk_CreateChallenge(struct rx_securityClass *aobj,
                     struct rx_connection *aconn)
 {
-    return RXGK_INCONSISTENCY;
+    struct rxgk_sconn *sc;
+    struct rx_opaque buf = RX_EMPTY_OPAQUE;
+    opr_StaticAssert(sizeof(sc->challenge) == RXGK_CHALLENGE_NONCE_LEN);
+
+    sc = rx_GetSecurityData(aconn);
+    if (sc == NULL)
+       return RXGK_INCONSISTENCY;
+    sc->auth = 0;
+
+    /* The challenge is a 20-byte random nonce. */
+    if (rxgk_nonce(&buf, RXGK_CHALLENGE_NONCE_LEN) != 0)
+       return RXGK_INCONSISTENCY;
+
+    opr_Assert(buf.len == RXGK_CHALLENGE_NONCE_LEN);
+    memcpy(&sc->challenge, buf.val, RXGK_CHALLENGE_NONCE_LEN);
+    rx_opaque_freeContents(&buf);
+    sc->challenge_valid = 1;
+    return 0;
+}
+
+/*
+ * Read the challenge stored in 'sc', performing some sanity checks. Always go
+ * through this function to access the challenge, and never read from
+ * sc->challenge directly.
+ */
+static int
+read_challenge(struct rxgk_sconn *sc, void *buf, int len)
+{
+    opr_StaticAssert(sizeof(sc->challenge) == RXGK_CHALLENGE_NONCE_LEN);
+
+    if (len != RXGK_CHALLENGE_NONCE_LEN) {
+       return RXGK_INCONSISTENCY;
+    }
+    if (!sc->challenge_valid) {
+       return RXGK_INCONSISTENCY;
+    }
+    memcpy(buf, sc->challenge, RXGK_CHALLENGE_NONCE_LEN);
+    return 0;
 }
 
+/* Incorporate a challenge into a packet */
 static int
 rxgk_GetChallenge(struct rx_securityClass *aobj, struct rx_connection *aconn,
                  struct rx_packet *apacket)
 {
-    return RXGK_INCONSISTENCY;
+    XDR xdrs;
+    struct rxgk_sconn *sc;
+    void *data = NULL;
+    RXGK_Challenge challenge;
+    int ret;
+    u_int len = 0;
+    opr_StaticAssert(sizeof(challenge.nonce) == RXGK_CHALLENGE_NONCE_LEN);
+
+    memset(&xdrs, 0, sizeof(xdrs));
+    memset(&challenge, 0, sizeof(challenge));
+
+    sc = rx_GetSecurityData(aconn);
+    if (sc == NULL) {
+       ret = RXGK_INCONSISTENCY;
+       goto done;
+    }
+    ret = read_challenge(sc, challenge.nonce, sizeof(challenge.nonce));
+    if (ret)
+       goto done;
+
+    xdrlen_create(&xdrs);
+    if (!xdr_RXGK_Challenge(&xdrs, &challenge)) {
+       ret = RXGEN_SS_MARSHAL;
+       goto done;
+    }
+    len = xdr_getpos(&xdrs);
+
+    data = rxi_Alloc(len);
+    if (data == NULL) {
+       ret = RXGK_INCONSISTENCY;
+       goto done;
+    }
+    xdr_destroy(&xdrs);
+    xdrmem_create(&xdrs, data, len, XDR_ENCODE);
+    if (!xdr_RXGK_Challenge(&xdrs, &challenge)) {
+       ret = RXGEN_SS_MARSHAL;
+       goto done;
+    }
+    opr_Assert(len <= 0xffffu);
+    rx_packetwrite(apacket, 0, len, data);
+    rx_SetDataSize(apacket, len);
+
+    /* Nothing should really pay attention to the checksum of a challenge
+     * packet, but just set it to 0 so it's always set to _something_. */
+    rx_SetPacketCksum(apacket, 0);
+
+    ret = 0;
+
+ done:
+    rxi_Free(data, len);
+    if (xdrs.x_ops)
+       xdr_destroy(&xdrs);
+    return ret;
+}
+
+/*
+ * Helper functions for CheckResponse.
+ */
+
+/**
+ * The XDR token format uses the XDR PrAuthName type to store identities.
+ * However, there is an existing rx_identity type used in libauth, so
+ * we convert from the wire type to the internal type as soon as possible
+ * in order to be able to use the most library code. 'a_identity' will contain
+ * a single identity on success, not an array.
+ *
+ * @return rxgk error codes
+ */
+static int
+prnames_to_identity(struct rx_identity **a_identity, PrAuthName *namelist,
+                   size_t nnames)
+{
+    rx_identity_kind kind;
+    size_t len;
+    char *display;
+
+    *a_identity = NULL;
+
+    /* Could grab the acceptor identity from ServiceSpecific if wanted. */
+    if (nnames == 0) {
+       *a_identity = rx_identity_new(RX_ID_SUPERUSER, "<printed token>", "",
+                                     0);
+       return 0;
+
+    } else if (nnames > 1) {
+       /* Compound identities are not supported yet. */
+       return RXGK_INCONSISTENCY;
+    }
+
+    if (namelist[0].kind == PRAUTHTYPE_KRB4)
+       kind = RX_ID_KRB4;
+    else if (namelist[0].kind == PRAUTHTYPE_GSS)
+       kind = RX_ID_GSS;
+    else
+       return RXGK_INCONSISTENCY;
+    len = namelist[0].display.len;
+    display = rxi_Alloc(len + 1);
+    if (display == NULL)
+       return RXGK_INCONSISTENCY;
+    memcpy(display, namelist[0].display.val, len);
+    display[len] = '\0';
+    *a_identity = rx_identity_new(kind, display, namelist[0].data.val,
+                                 namelist[0].data.len);
+    rxi_Free(display, len + 1);
+    return 0;
+}
+
+/*
+ * Unpack, decrypt, and extract information from a token.
+ * Store the relevant bits in the connection security data.
+ */
+static int
+process_token(RXGK_Data *tc, struct rxgk_sprivate *sp, struct rxgk_sconn *sc)
+{
+    RXGK_Token token;
+    int ret;
+
+    memset(&token, 0, sizeof(token));
+
+    ret = rxgk_extract_token(tc, &token, sp->getkey, sp->rock);
+    if (ret != 0)
+       goto done;
+
+    /* Stash the token master key in the per-connection data. */
+    rxgk_release_key(&sc->k0);
+    ret = rxgk_make_key(&sc->k0, token.K0.val, token.K0.len, token.enctype);
+    if (ret != 0)
+       goto done;
+
+    sc->level = token.level;
+    sc->expiration = token.expirationtime;
+    /*
+     * TODO: note that we currently ignore the bytelife and lifetime in
+     * 'token'. In the future, we should of course actually remember these and
+     * potentially alter our rekeying frequency according to them.
+     */
+
+    if (sc->client != NULL)
+       rx_identity_free(&sc->client);
+    ret = prnames_to_identity(&sc->client, token.identities.val,
+                             token.identities.len);
+    if (ret != 0)
+       goto done;
+
+ done:
+    xdr_free((xdrproc_t)xdr_RXGK_Token, &token);
+    return ret;
 }
 
+static void
+update_kvno(struct rxgk_sconn *sc, afs_uint32 kvno)
+{
+    sc->key_number = kvno;
+
+    /* XXX Our statistics for tracking when to re-key the conn should be reset
+     * here. */
+}
+
+/* Caller is responsible for freeing 'out'. */
+static int
+decrypt_authenticator(RXGK_Authenticator *out, struct rx_opaque *in,
+                     struct rx_connection *aconn, struct rxgk_sconn *sc,
+                     afs_uint16 wkvno)
+{
+    XDR xdrs;
+    struct rx_opaque packauth = RX_EMPTY_OPAQUE;
+    rxgk_key tk = NULL;
+    afs_uint32 lkvno, kvno = 0;
+    int ret;
+
+    memset(&xdrs, 0, sizeof(xdrs));
+
+    lkvno = sc->key_number;
+    ret = rxgk_key_number(wkvno, lkvno, &kvno);
+    if (ret != 0)
+       goto done;
+    ret = rxgk_derive_tk(&tk, sc->k0, rx_GetConnectionEpoch(aconn),
+                        rx_GetConnectionId(aconn), sc->start_time, kvno);
+    if (ret != 0)
+       goto done;
+    ret = rxgk_decrypt_in_key(tk, RXGK_CLIENT_ENC_RESPONSE, in, &packauth);
+    if (ret != 0) {
+       goto done;
+    }
+    if (kvno > lkvno)
+       update_kvno(sc, kvno);
+
+    xdrmem_create(&xdrs, packauth.val, packauth.len, XDR_DECODE);
+    if (!xdr_RXGK_Authenticator(&xdrs, out)) {
+       ret = RXGEN_SS_UNMARSHAL;
+       goto done;
+    }
+    ret = 0;
+
+ done:
+    rx_opaque_freeContents(&packauth);
+    rxgk_release_key(&tk);
+    if (xdrs.x_ops)
+       xdr_destroy(&xdrs);
+    return ret;
+}
+
+/*
+ * Make the authenticator do its job with channel binding and nonce
+ * verification.
+ */
+static int
+check_authenticator(RXGK_Authenticator *authenticator,
+                   struct rx_connection *aconn, struct rxgk_sconn *sc)
+{
+    /*
+     * To check the data in the authenticator, we could simply check
+     * if (got_value == expected_value) for each field we care about. But since
+     * this is a security-sensitive check, we should try to do this check in
+     * constant time to avoid timing-based attacks. So to do that, we construct
+     * a small structure of the values we got and the expected values, and run
+     * ct_memcmp on the whole thing at the end.
+     */
+
+    int code;
+    struct {
+       unsigned char challenge[RXGK_CHALLENGE_NONCE_LEN];
+       RXGK_Level level;
+       afs_uint32 epoch;
+       afs_uint32 cid;
+       int calls_len;
+    } auth_got, auth_exp;
+
+    opr_StaticAssert(sizeof(auth_got.challenge) == RXGK_CHALLENGE_NONCE_LEN);
+    opr_StaticAssert(sizeof(auth_exp.challenge) == RXGK_CHALLENGE_NONCE_LEN);
+    opr_StaticAssert(sizeof(authenticator->nonce) == RXGK_CHALLENGE_NONCE_LEN);
+
+    memset(&auth_got, 0, sizeof(auth_got));
+    memset(&auth_exp, 0, sizeof(auth_exp));
+
+    memcpy(auth_got.challenge, authenticator->nonce, RXGK_CHALLENGE_NONCE_LEN);
+    code = read_challenge(sc, auth_exp.challenge, sizeof(auth_exp.challenge));
+    if (code)
+       return code;
+
+    auth_got.level = authenticator->level;
+    auth_exp.level = sc->level;
+
+    auth_got.epoch = authenticator->epoch;
+    auth_exp.epoch = rx_GetConnectionEpoch(aconn);
+
+    auth_got.cid = authenticator->cid;
+    auth_exp.cid = rx_GetConnectionId(aconn);
+
+    auth_got.calls_len = authenticator->call_numbers.len;
+    auth_exp.calls_len = RX_MAXCALLS;
+
+    /* XXX We do nothing with the appdata for now. */
+
+    if (ct_memcmp(&auth_got, &auth_exp, sizeof(auth_got)) != 0) {
+       return RXGK_BADCHALLENGE;
+    }
+    return 0;
+}
+
+/* Process the response packet to a challenge */
 static int
 rxgk_CheckResponse(struct rx_securityClass *aobj,
                   struct rx_connection *aconn, struct rx_packet *apacket)
 {
-    return RXGK_INCONSISTENCY;
+    struct rxgk_sprivate *sp;
+    struct rxgk_sconn *sc;
+    XDR xdrs;
+    RXGK_Response response;
+    RXGK_Authenticator authenticator;
+    int ret;
+
+    memset(&xdrs, 0, sizeof(xdrs));
+    memset(&response, 0, sizeof(response));
+    memset(&authenticator, 0, sizeof(authenticator));
+
+    sp = aobj->privateData;
+    sc = rx_GetSecurityData(aconn);
+
+    /*
+     * This assumes that the entire response is in a contiguous data block in
+     * the packet. rx in general can store packet data in multiple different
+     * buffers (pointed to by apacket->wirevec[N]), but the payload when
+     * receiving a Response packet should all be in one buffer (so we can just
+     * reference it directly via rx_DataOf()). If this assumption turns out to
+     * be wrong, then we'll just see a truncated response blob and this
+     * function will likely return an error; there should be no danger of
+     * buffer overrun or anything scary like that.
+     */
+    xdrmem_create(&xdrs, rx_DataOf(apacket), rx_Contiguous(apacket),
+                 XDR_DECODE);
+    if (!xdr_RXGK_Response(&xdrs, &response)) {
+       ret = RXGEN_SS_UNMARSHAL;
+       goto done;
+    }
+
+    /* Stash useful bits from the token in sc. */
+    ret = process_token(&response.token, sp, sc);
+    if (ret != 0)
+       goto done;
+    if (sc->expiration < RXGK_NOW() && sc->expiration != RXGK_NEVERDATE) {
+       ret = RXGK_EXPIRED;
+       goto done;
+    }
+
+    /*
+     * Cache the client-provided start_time. If this is wrong, we cannot derive
+     * the correct transport key and the authenticator decryption will fail.
+     */
+    sc->start_time = response.start_time;
+
+    /* Try to decrypt the authenticator. */
+    ret = decrypt_authenticator(&authenticator, &response.authenticator, aconn,
+                               sc, rx_GetPacketCksum(apacket));
+    if (ret != 0)
+       goto done;
+    ret = check_authenticator(&authenticator, aconn, sc);
+    if (ret != 0)
+       goto done;
+    ret = rxgk_security_overhead(aconn, sc->level, sc->k0);
+    if (ret != 0)
+       goto done;
+    if (rxi_SetCallNumberVector(aconn, (afs_int32 *)authenticator.call_numbers.val) != 0) {
+       ret = RXGK_INCONSISTENCY;
+       goto done;
+    }
+    /* Success! */
+    sc->auth = 1;
+    sc->challenge_valid = 0;
+
+ done:
+    if (ret != 0)
+       sconn_set_noauth(sc);
+    if (xdrs.x_ops)
+       xdr_destroy(&xdrs);
+    xdr_free((xdrproc_t)xdr_RXGK_Response, &response);
+    xdr_free((xdrproc_t)xdr_RXGK_Authenticator, &authenticator);
+    return ret;
 }
 
+/*
+ * Server-specific packet receipt routine.
+ * The interesting bits are in rxgk_packet.c, we just extract data from the
+ * connection security data.
+ */
 static int
 rxgk_ServerCheckPacket(struct rx_securityClass *aobj, struct rx_call *acall,
                       struct rx_packet *apacket)
 {
-    return RXGK_INCONSISTENCY;
+    struct rxgk_sconn *sc;
+    struct rx_connection *aconn;
+    afs_uint32 lkvno, kvno;
+    afs_uint16 len;
+    int ret;
+
+    aconn = rx_ConnectionOf(acall);
+    sc = rx_GetSecurityData(aconn);
+    if (sc == NULL)
+       return RXGK_INCONSISTENCY;
+
+    len = rx_GetDataSize(apacket);
+    sc->stats.precv++;
+    sc->stats.brecv += len;
+    if (sc->expiration < RXGK_NOW() && sc->expiration != RXGK_NEVERDATE)
+       return RXGK_EXPIRED;
+
+    lkvno = kvno = sc->key_number;
+    ret = rxgk_check_packet(1, aconn, apacket, sc->level, sc->start_time,
+                           &kvno, sc->k0);
+    if (ret != 0)
+       return ret;
+
+    if (kvno > lkvno)
+       update_kvno(sc, kvno);
+
+    return ret;
 }
 
+/*
+ * Perform server-side connection-specific teardown.
+ */
 static void
 rxgk_DestroyServerConnection(struct rx_securityClass *aobj,
                             struct rx_connection *aconn)
 {
+    struct rxgk_sconn *sc;
+
+    sc = rx_GetSecurityData(aconn);
+    if (sc == NULL) {
+       return;
+    }
+    rx_SetSecurityData(aconn, NULL);
+
+    rxgk_release_key(&sc->k0);
+    if (sc->client != NULL)
+       rx_identity_free(&sc->client);
+    rxi_Free(sc, sizeof(*sc));
+    obj_rele(aobj);
 }
 
+/*
+ * Get statistics about this connection.
+ */
 static int
 rxgk_ServerGetStats(struct rx_securityClass *aobj, struct rx_connection *aconn,
                    struct rx_securityObjectStats *astats)
 {
-    return RXGK_INCONSISTENCY;
+    struct rxgkStats *stats;
+    struct rxgk_sconn *sc;
+
+    astats->type = RX_SECTYPE_GK;
+    sc = rx_GetSecurityData(aconn);
+    if (sc == NULL) {
+       astats->flags |= RXGK_STATS_UNALLOC;
+       return 0;
+    }
+
+    stats = &sc->stats;
+    astats->level = sc->level;
+    if (sc->auth)
+       astats->flags |= RXGK_STATS_AUTH;
+    astats->expires = (afs_uint32)rxgkTimeToSeconds(sc->expiration);
+
+    astats->packetsReceived = stats->precv;
+    astats->packetsSent = stats->psent;
+    astats->bytesReceived = stats->brecv;
+    astats->bytesSent = stats->bsent;
+
+    return 0;
+}
+
+/*
+ * Get some information about this connection, in particular the security
+ * level, expiry time, and the remote user's identity.
+ */
+afs_int32
+rxgk_GetServerInfo(struct rx_connection *conn, RXGK_Level *level,
+                  rxgkTime *expiry, struct rx_identity **identity)
+{
+    struct rxgk_sconn *sconn;
+
+    if (rx_SecurityClassOf(conn) != RX_SECIDX_GK) {
+       return EINVAL;
+    }
+
+    sconn = rx_GetSecurityData(conn);
+    if (sconn == NULL)
+       return RXGK_INCONSISTENCY;
+    if (identity != NULL) {
+       *identity = rx_identity_copy(sconn->client);
+       if (*identity == NULL)
+           return RXGK_INCONSISTENCY;
+    }
+    if (level != NULL)
+       *level = sconn->level;
+    if (expiry != NULL)
+       *expiry = sconn->expiration;
+    return 0;
+}
+
+static struct rx_securityOps rxgk_server_ops = {
+    rxgk_ServerClose,
+    rxgk_NewServerConnection,
+    rxgk_ServerPreparePacket,          /* once per packet creation */
+    0,                                 /* send packet (once per retrans) */
+    rxgk_CheckAuthentication,
+    rxgk_CreateChallenge,
+    rxgk_GetChallenge,
+    0,
+    rxgk_CheckResponse,
+    rxgk_ServerCheckPacket,            /* check data packet */
+    rxgk_DestroyServerConnection,
+    rxgk_ServerGetStats,
+    0,
+    0,                         /* spare 1 */
+    0,                         /* spare 2 */
+};
+
+/**
+ * The low-level routine to generate a new server security object.
+ *
+ * Takes a getkey function and its rock.
+ *
+ * It is not expected that most callers will use this function, as
+ * we provide helpers that do other setup, setting service-specific
+ * data and such.
+ */
+struct rx_securityClass *
+rxgk_NewServerSecurityObject(void *getkey_rock, rxgk_getkey_func getkey)
+{
+    struct rx_securityClass *sc;
+    struct rxgk_sprivate *sp;
+
+    sc = rxi_Alloc(sizeof(*sc));
+    if (sc == NULL)
+       return NULL;
+    sp = rxi_Alloc(sizeof(*sp));
+    if (sp == NULL) {
+       rxi_Free(sc, sizeof(*sc));
+       return NULL;
+    }
+    sc->ops = &rxgk_server_ops;
+    sc->refCount = 1;
+    sc->privateData = sp;
+
+    /* Now set the server-private data. */
+    sp->rock = getkey_rock;
+    sp->getkey = getkey;
+
+    return sc;
 }