Implement the rxgk server security object routines 72/10572/28
authorBen Kaduk <kaduk@mit.edu>
Tue, 10 Dec 2013 03:13:16 +0000 (22:13 -0500)
committerBenjamin Kaduk <kaduk@mit.edu>
Sun, 24 Mar 2019 05:03:21 +0000 (01:03 -0400)
Provide non-trivial implementations of the security class routines
used by the server, along with helpers as necessary.

The identity supplied in a client's token is given as a list of
PrAuthNames; we assume that at most one name is supplied at present,
as the meaning of compound identities (and the use of compound
identities for keyed cache managers) is not fully specified yet.
Convert the PrAuthName to an rx_identity for caching in the server
connection state, as the rx_identity type is more compatible with
superuser checks on the connection.

Also provide an rxgk_GetServerInfo routine which extracts the cached
identity, for use in libauth when making superuser checks.
This moves our dependency on rx_identity from the private data structures
into the public header, so move the nested include accordingly.

Change-Id: I0f48b69d4ab758d8a4d76ebfb1daf3009c4fe060
Reviewed-on: https://gerrit.openafs.org/10572
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: BuildBot <buildbot@rampaginggeek.com>

src/rx/rx.h
src/rxgk/liboafs_rxgk.la.sym
src/rxgk/rxgk.h
src/rxgk/rxgk_int.xg
src/rxgk/rxgk_packet.c
src/rxgk/rxgk_private.h
src/rxgk/rxgk_server.c

index 8d5f648..f91f457 100644 (file)
@@ -523,6 +523,7 @@ enum {
     RX_SECTYPE_NULL = 1,
     RX_SECTYPE_VAB = 2,
     RX_SECTYPE_KAD = 3,
+    RX_SECTYPE_GK = 4,
 };
 struct rx_securityObjectStats {
     char type;                 /* An RX_SECTYPE_* value */
index af6007f..c784879 100644 (file)
@@ -1,5 +1,6 @@
 RXGK_CombineTokens
 RXGK_GSSNegotiate
+rxgk_GetServerInfo
 rxgk_NewClientSecurityObject
 rxgk_NewServerSecurityObject
 rxgk_check_mic_in_key
index 1e98332..03b8749 100644 (file)
@@ -44,6 +44,7 @@
 
 /* RX-internal headers we depend on. */
 #include <rx/rx_opaque.h>
+#include <rx/rx_identity.h>
 
 /* rxgkTime is defined in rxgk_int.xg. rxgkTime values are unix timestamps, but
  * in 100-nanosecond units. */
@@ -70,9 +71,17 @@ typedef struct rxgk_key_s * rxgk_key;
 
 typedef afs_int32 (*rxgk_getkey_func)(void *rock, afs_int32 *kvno,
                                      afs_int32 *enctype, rxgk_key *key);
+
+/* Flags for our rx security stats */
+#define RXGK_STATS_UNALLOC 0x1
+#define RXGK_STATS_AUTH    0x2
+
 /* rxgk_server.c */
 struct rx_securityClass * rxgk_NewServerSecurityObject(void *getkey_rock,
                                                       rxgk_getkey_func getkey);
+afs_int32 rxgk_GetServerInfo(struct rx_connection *conn, RXGK_Level *level,
+                            rxgkTime *expiry, struct rx_identity **identity);
+
 /* rxgk_client.c */
 struct rx_securityClass *rxgk_NewClientSecurityObject(RXGK_Level level,
                                                      afs_int32 enctype,
index 14fd072..76effd6 100644 (file)
@@ -28,7 +28,13 @@ const RXGK_SERVER_ENC_TOKEN             = 1036;
 enum RXGK_Level {
     RXGK_LEVEL_CLEAR = 0,
     RXGK_LEVEL_AUTH = 1,
-    RXGK_LEVEL_CRYPT = 2
+    RXGK_LEVEL_CRYPT = 2,
+
+    /* RXGK_LEVEL_BOGUS is an invalid level we only use internally for objects
+     * that are not yet initialized. This can be used to make sure we don't
+     * accidentally treat an uninitialized context as an RXGK_LEVEL_CLEAR
+     * connection. This level should never be used on the wire. */
+    RXGK_LEVEL_BOGUS = 71
 };
 
 /* limits for variable-length arrays */
@@ -127,6 +133,7 @@ struct PrAuthName {
 };
 
 /* PrAuthName 'kind' values */
+const PRAUTHTYPE_KRB4 = 1;
 const PRAUTHTYPE_GSS = 2;
 
 /* RPC-L from draft-wilkinson-afs3-rxgk-afs. */
index 7c058db..498d381 100644 (file)
@@ -90,7 +90,7 @@ populate_header(struct rxgk_header *header, struct rx_packet *apacket,
  * @param[in,out] apacket      The packet to be processed.
  * @return rxgk error codes.  An error is returned if the MIC is invalid.
  */
-int
+static int
 rxgk_check_mic_packet(rxgk_key tk, afs_int32 keyusage,
                      struct rx_connection *aconn, struct rx_packet *apacket)
 {
@@ -151,7 +151,7 @@ rxgk_check_mic_packet(rxgk_key tk, afs_int32 keyusage,
  * @param[in,out] apacket      The packet being decrypted.
  * @return rxgk and system error codes.
  */
-int
+static int
 rxgk_decrypt_packet(rxgk_key tk, afs_int32 keyusage,
                    struct rx_connection *aconn, struct rx_packet *apacket)
 {
@@ -349,3 +349,71 @@ rxgk_enc_packet(rxgk_key tk, afs_int32 keyusage, struct rx_connection *aconn,
     rx_opaque_freeContents(&crypt);
     return ret;
 }
+
+/*
+ * Server/client common bits for the packet receipt routine.
+ * Wrap the appropriate check_mic/decrypt routines for the given level
+ * and client/server role, using the given start_time/kvno/k0 to generate
+ * the transport key needed.  Since the kvno may have been updated by the
+ * peer, 'a_kvno' is set to the new kvno on return, if it has changed.
+ */
+int
+rxgk_check_packet(int server, struct rx_connection *aconn,
+                 struct rx_packet *apacket, RXGK_Level level,
+                 rxgkTime start_time, afs_uint32 *a_kvno, rxgk_key k0)
+{
+    afs_uint16 wkvno;
+    afs_uint32 lkvno;
+    int ret;
+
+    wkvno = rx_GetPacketCksum(apacket);
+    lkvno = *a_kvno;
+    ret = rxgk_key_number(wkvno, lkvno, a_kvno);
+    if (ret != 0 && level == RXGK_LEVEL_CLEAR) {
+       /*
+        * We ignore kvno errors for RXGK_LEVEL_CLEAR connections, since the
+        * peer is not required to process kvno changes accurately for CLEAR
+        * connections. We still call rxgk_key_number to try to track when then
+        * kvno is updated properly, but if we got an error, just pretend
+        * nothing happened and nobody tried to change the kvno.
+        */
+       ret = 0;
+       *a_kvno = lkvno;
+    }
+    if (ret != 0)
+       return ret;
+
+    if (level != RXGK_LEVEL_CLEAR) {
+       /* We only need to deal with per-packet encryption stuff for non-CLEAR
+        * connections. */
+       rxgk_key tk;
+       afs_uint32 keyusage;
+
+       ret = rxgk_derive_tk(&tk, k0, rx_GetConnectionEpoch(aconn),
+                            rx_GetConnectionId(aconn), start_time, *a_kvno);
+       if (ret != 0)
+           return ret;
+
+       switch (level) {
+           case RXGK_LEVEL_AUTH:
+               keyusage = server ? RXGK_CLIENT_MIC_PACKET :
+                                   RXGK_SERVER_MIC_PACKET;
+
+               ret = rxgk_check_mic_packet(tk, keyusage, aconn, apacket);
+               break;
+
+           case RXGK_LEVEL_CRYPT:
+               keyusage = server ? RXGK_CLIENT_ENC_PACKET :
+                                   RXGK_SERVER_ENC_PACKET;
+
+               ret = rxgk_decrypt_packet(tk, keyusage, aconn, apacket);
+               break;
+
+           default:
+               ret = RXGK_INCONSISTENCY;
+       }
+       rxgk_release_key(&tk);
+    }
+
+    return ret;
+}
index 7e682ef..0b7a8f7 100644 (file)
@@ -36,9 +36,6 @@
 #ifndef RXGK_PRIVATE_H
 #define RXGK_PRIVATE_H
 
-/* RX-internal headers we depend on. */
-#include <rx/rx_identity.h>
-
 /** Statistics about a connection.  Bytes and packets sent/received. */
 struct rxgkStats {
     afs_uint32 brecv;
@@ -68,7 +65,6 @@ struct rxgk_header {
  * sends us.
  */
 struct rxgk_sprivate {
-    afs_int32 flags;
     void *rock;
     rxgk_getkey_func getkey;
 };
@@ -82,8 +78,8 @@ struct rxgk_sprivate {
  */
 struct rxgk_sconn {
     RXGK_Level level;
-    unsigned char tried_auth;
     unsigned char auth;
+    unsigned char challenge_valid;
     rxgkTime expiration;
     unsigned char challenge[RXGK_CHALLENGE_NONCE_LEN];
     struct rxgkStats stats;
@@ -91,8 +87,6 @@ struct rxgk_sconn {
     struct rx_identity *client;
     afs_uint32 key_number;
     rxgk_key k0;
-    RXGK_Data cb_tok;
-    rxgk_key cb_key;
 };
 
 /*
@@ -148,14 +142,12 @@ afs_int32 rxgk_security_overhead(struct rx_connection *aconn, RXGK_Level level,
 afs_int32 rxgk_key_number(afs_uint16 wire, afs_uint32 local, afs_uint32 *real);
 
 /* rxgk_packet.c */
-int rxgk_check_mic_packet(rxgk_key tk, afs_int32 keyusage,
-                         struct rx_connection *aconn,
-                         struct rx_packet *apacket);
-int rxgk_decrypt_packet(rxgk_key tk, afs_int32 keyusage,
-                       struct rx_connection *aconn, struct rx_packet *apacket);
 int rxgk_mic_packet(rxgk_key tk, afs_int32 keyusage,
                    struct rx_connection *aconn, struct rx_packet *apacket);
 int rxgk_enc_packet(rxgk_key tk, afs_int32 keyusage,
                    struct rx_connection *aconn, struct rx_packet *apacket);
+int rxgk_check_packet(int server, struct rx_connection *aconn,
+                      struct rx_packet *apacket, RXGK_Level level,
+                      rxgkTime start_time, afs_uint32 *a_kvno, rxgk_key k0);
 
 #endif /* RXGK_PRIVATE_H */
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;
 }