Implement rxgk client security object routines 73/10573/24
authorBen Kaduk <kaduk@mit.edu>
Tue, 10 Dec 2013 05:09:35 +0000 (00:09 -0500)
committerBenjamin Kaduk <kaduk@mit.edu>
Sun, 24 Mar 2019 06:13:36 +0000 (02:13 -0400)
Change-Id: Ic7e11b02cb1573cfdb6d11d4de9a77ab1c563262
Reviewed-on: https://gerrit.openafs.org/10573
Reviewed-by: Andrew Deason <adeason@sinenomine.net>
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: BuildBot <buildbot@rampaginggeek.com>

src/rxgk/rxgk.h
src/rxgk/rxgk_client.c
src/rxgk/rxgk_private.h

index 03b8749..f92d6f0 100644 (file)
@@ -86,8 +86,7 @@ afs_int32 rxgk_GetServerInfo(struct rx_connection *conn, RXGK_Level *level,
 struct rx_securityClass *rxgk_NewClientSecurityObject(RXGK_Level level,
                                                      afs_int32 enctype,
                                                      rxgk_key k0,
-                                                     RXGK_Data *token,
-                                                     afsUUID *uuid);
+                                                     RXGK_Data *token);
 
 /* rxgk_crypto_IMPL.c (currently rfc3961 is the only IMPL) */
 afs_int32 rxgk_make_key(rxgk_key *key_out, void *raw_key, afs_uint32 length,
index c0305e7..a737839 100644 (file)
 #include <afs/param.h>
 #include <afs/stds.h>
 
+#include <afs/opr.h>
+
 /* OS-specific system headers go here */
 
 #include <rx/rx.h>
 #include <rx/rx_packet.h>
+#include <rx/rx_identity.h>
 #include <rx/rxgk.h>
+#include <rx/xdr.h>
 
 #include "rxgk_private.h"
 
-/* Pre-declare the securityclass routines for the securityOps definition. */
-static int rxgk_ClientClose(struct rx_securityClass *aobj);
-static int rxgk_NewClientConnection(struct rx_securityClass *aobj,
-                                   struct rx_connection *aconn);
-static int rxgk_ClientPreparePacket(struct rx_securityClass *aobj,
-                                   struct rx_call *acall,
-                                   struct rx_packet *apacket);
-static int rxgk_GetResponse(struct rx_securityClass *aobj,
-                           struct rx_connection *aconn,
-                           struct rx_packet *apacket);
-static int rxgk_ClientCheckPacket(struct rx_securityClass *aobj,
-                                 struct rx_call *acall,
-                                 struct rx_packet *apacket);
-static void rxgk_DestroyClientConnection(struct rx_securityClass *aobj,
-                                        struct rx_connection *aconn);
-static int rxgk_ClientGetStats(struct rx_securityClass *aobj,
-                              struct rx_connection *aconn,
-                              struct rx_securityObjectStats *astats);
-
-static struct rx_securityOps rxgk_client_ops = {
-    rxgk_ClientClose,
-    rxgk_NewClientConnection,          /* every new connection */
-    rxgk_ClientPreparePacket,          /* once per packet creation */
-    0,                                 /* send packet (once per retrans) */
-    0,
-    0,
-    0,
-    rxgk_GetResponse,                  /* respond to challenge packet */
-    0,
-    rxgk_ClientCheckPacket,            /* check data packet */
-    rxgk_DestroyClientConnection,
-    rxgk_ClientGetStats,
-    0,
-    0,
-    0,
-};
+static void
+cprivate_destroy(struct rxgk_cprivate *cp)
+{
+    if (cp == NULL)
+        return;
+    rxgk_release_key(&cp->k0);
+    rx_opaque_freeContents(&cp->token);
+    rxi_Free(cp, sizeof(*cp));
+}
 
-static struct rx_securityClass dummySC = {
-    &rxgk_client_ops,
-    NULL,
-    0
-};
+/*
+ * Increment the reference count on the security object secobj.
+ */
+static_inline void
+obj_ref(struct rx_securityClass *secobj)
+{
+    secobj->refCount++;
+}
 
-struct rx_securityClass *
-rxgk_NewClientSecurityObject(RXGK_Level level, afs_int32 enctype, rxgk_key k0,
-                            RXGK_Data *token, afsUUID *uuid)
+/*
+ * 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)
 {
-    return &dummySC;
+    struct rxgk_cprivate *cp;
+
+    secobj->refCount--;
+    if (secobj->refCount > 0) {
+       /* still in use */
+       return;
+    }
+
+    cp = secobj->privateData;
+    cprivate_destroy(cp);
+    rxi_Free(secobj, sizeof(*secobj));
+    return;
 }
 
 static int
 rxgk_ClientClose(struct rx_securityClass *aobj)
 {
-    return RXGK_INCONSISTENCY;
+    obj_rele(aobj);
+    return 0;
 }
 
 static int
 rxgk_NewClientConnection(struct rx_securityClass *aobj,
                         struct rx_connection *aconn)
 {
+    struct rxgk_cconn *cc = NULL;
+    struct rxgk_cprivate *cp;
+
+    if (rx_GetSecurityData(aconn) != NULL)
+       goto error;
+    cp = aobj->privateData;
+
+    cc = rxi_Alloc(sizeof(*cc));
+    if (cc == NULL)
+       goto error;
+    cc->start_time = RXGK_NOW();
+    /* Set the header and trailer size to be reserved for the security
+     * class in each packet. */
+    if (rxgk_security_overhead(aconn, cp->level, cp->k0) != 0)
+       goto error;
+    rx_SetSecurityData(aconn, cc);
+    obj_ref(aobj);
+    return 0;
+
+ error:
+    rxi_Free(cc, sizeof(*cc));
     return RXGK_INCONSISTENCY;
 }
 
@@ -112,32 +128,395 @@ static int
 rxgk_ClientPreparePacket(struct rx_securityClass *aobj, struct rx_call *acall,
                         struct rx_packet *apacket)
 {
-    return RXGK_INCONSISTENCY;
+    struct rxgk_cconn *cc;
+    struct rxgk_cprivate *cp;
+    struct rx_connection *aconn;
+    rxgk_key tk;
+    afs_uint32 lkvno;
+    afs_uint16 wkvno, len;
+    int ret;
+
+    aconn = rx_ConnectionOf(acall);
+    cc = rx_GetSecurityData(aconn);
+    cp = aobj->privateData;
+
+    len = rx_GetDataSize(apacket);
+    lkvno = cc->key_number;
+    cc->stats.psent++;
+    cc->stats.bsent += len;
+    wkvno = (afs_uint16)lkvno;
+    rx_SetPacketCksum(apacket, wkvno);
+
+    if (cp->level == RXGK_LEVEL_CLEAR)
+        return 0;
+
+    ret = rxgk_derive_tk(&tk, cp->k0, rx_GetConnectionEpoch(aconn),
+                        rx_GetConnectionId(aconn), cc->start_time, lkvno);
+    if (ret != 0)
+       return ret;
+
+    switch(cp->level) {
+       case RXGK_LEVEL_AUTH:
+           ret = rxgk_mic_packet(tk, RXGK_CLIENT_MIC_PACKET, aconn, apacket);
+           break;
+       case RXGK_LEVEL_CRYPT:
+           ret = rxgk_enc_packet(tk, RXGK_CLIENT_ENC_PACKET, aconn, apacket);
+           break;
+       default:
+           ret = RXGK_INCONSISTENCY;
+           break;
+    }
+
+    rxgk_release_key(&tk);
+    return ret;
+}
+
+/*
+ * Helpers for GetResponse.
+ */
+
+/*
+ * Populate the RXGK_Authenticator structure.
+ * The caller is responsible for pre-zeroing the structure and freeing
+ * the resulting allocations, including partial allocations in the case
+ * of failure.
+ */
+static int
+fill_authenticator(RXGK_Authenticator *authenticator, RXGK_Challenge *challenge,
+                  struct rxgk_cprivate *cp, struct rx_connection *aconn)
+{
+    afs_int32 call_numbers[RX_MAXCALLS];
+    int ret, call_i;
+    opr_StaticAssert(sizeof(authenticator->nonce) == RXGK_CHALLENGE_NONCE_LEN);
+    opr_StaticAssert(sizeof(challenge->nonce) == RXGK_CHALLENGE_NONCE_LEN);
+
+    memset(&call_numbers, 0, sizeof(call_numbers));
+
+    memcpy(authenticator->nonce, challenge->nonce, sizeof(authenticator->nonce));
+
+    authenticator->level = cp->level;
+    authenticator->epoch = rx_GetConnectionEpoch(aconn);
+    authenticator->cid = rx_GetConnectionId(aconn);
+    /* Export the call numbers. */
+    ret = rxi_GetCallNumberVector(aconn, call_numbers);
+    if (ret != 0)
+       return ret;
+    authenticator->call_numbers.val = xdr_alloc(RX_MAXCALLS *
+                                               sizeof(afs_int32));
+    if (authenticator->call_numbers.val == NULL)
+       return RXGEN_CC_MARSHAL;
+    authenticator->call_numbers.len = RX_MAXCALLS;
+    for (call_i = 0; call_i < RX_MAXCALLS; call_i++)
+       authenticator->call_numbers.val[call_i] = (afs_uint32)call_numbers[call_i];
+    return 0;
+}
+
+/* XDR-encode an authenticator and encrypt it. */
+static int
+pack_wrap_authenticator(RXGK_Data *encdata, RXGK_Authenticator *authenticator,
+                       struct rxgk_cprivate *cp, struct rxgk_cconn *cc)
+{
+    XDR xdrs;
+    struct rx_opaque data = RX_EMPTY_OPAQUE;
+    rxgk_key tk = NULL;
+    int ret;
+    u_int len;
+
+    memset(&xdrs, 0, sizeof(xdrs));
+
+    xdrlen_create(&xdrs);
+    if (!xdr_RXGK_Authenticator(&xdrs, authenticator)) {
+       ret = RXGEN_CC_MARSHAL;
+       goto done;
+    }
+    len = xdr_getpos(&xdrs);
+    ret = rx_opaque_alloc(&data, len);
+    if (ret != 0)
+       goto done;
+    xdr_destroy(&xdrs);
+    xdrmem_create(&xdrs, data.val, len, XDR_ENCODE);
+    if (!xdr_RXGK_Authenticator(&xdrs, authenticator)) {
+       ret = RXGEN_CC_MARSHAL;
+       goto done;
+    }
+    ret = rxgk_derive_tk(&tk, cp->k0, authenticator->epoch, authenticator->cid,
+                        cc->start_time, cc->key_number);
+    if (ret != 0)
+       goto done;
+    ret = rxgk_encrypt_in_key(tk, RXGK_CLIENT_ENC_RESPONSE, &data, encdata);
+    if (ret != 0)
+       goto done;
+
+ done:
+    if (xdrs.x_ops) {
+        xdr_destroy(&xdrs);
+    }
+    rx_opaque_freeContents(&data);
+    rxgk_release_key(&tk);
+    return ret;
 }
 
+/* XDR-encode an RXGK_Response structure to put it on the wire.
+ * The caller must free the contents of the out parameter. */
+static int
+pack_response(RXGK_Data *out, RXGK_Response *response)
+{
+    XDR xdrs;
+    int ret;
+    u_int len;
+
+    memset(&xdrs, 0, sizeof(xdrs));
+
+    xdrlen_create(&xdrs);
+    if (!xdr_RXGK_Response(&xdrs, response)) {
+       ret = RXGEN_CC_MARSHAL;
+       goto done;
+    }
+    len = xdr_getpos(&xdrs);
+    ret = rx_opaque_alloc(out, len);
+    if (ret != 0)
+       goto done;
+    xdr_destroy(&xdrs);
+    xdrmem_create(&xdrs, out->val, len, XDR_ENCODE);
+    if (!xdr_RXGK_Response(&xdrs, response)) {
+       rx_opaque_freeContents(out);
+       ret = RXGEN_CC_MARSHAL;
+       goto done;
+    }
+
+ done:
+    if (xdrs.x_ops) {
+        xdr_destroy(&xdrs);
+    }
+    return ret;
+}
+
+/*
+ * Respond to a challenge packet.
+ * The data of the packet on entry is the XDR-encoded RXGK_Challenge.
+ * We decode it and reuse the packet structure to prepare a response.
+ */
 static int
 rxgk_GetResponse(struct rx_securityClass *aobj, struct rx_connection *aconn,
                 struct rx_packet *apacket)
 {
-    return RXGK_INCONSISTENCY;
+    struct rxgk_cprivate *cp;
+    struct rxgk_cconn *cc;
+    XDR xdrs;
+    RXGK_Challenge challenge;
+    RXGK_Response response;
+    RXGK_Authenticator authenticator;
+    struct rx_opaque packed = RX_EMPTY_OPAQUE;
+    int ret;
+
+    memset(&xdrs, 0, sizeof(xdrs));
+    memset(&challenge, 0, sizeof(challenge));
+    memset(&response, 0, sizeof(response));
+    memset(&authenticator, 0, sizeof(authenticator));
+
+    cp = aobj->privateData;
+    cc = rx_GetSecurityData(aconn);
+
+    /*
+     * Decode the challenge to get the nonce.
+     * This assumes that the entire challenge 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 Challenge 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 challenge blob and this
+     * function will likely return an error; there should be no danger of
+     * buffer overrun or anything scary like that.
+     */
+    if (rx_Contiguous(apacket) < RXGK_CHALLENGE_NONCE_LEN) {
+        ret = RXGK_PACKETSHORT;
+       goto done;
+    }
+    xdrmem_create(&xdrs, rx_DataOf(apacket), rx_Contiguous(apacket),
+                 XDR_DECODE);
+    if (!xdr_RXGK_Challenge(&xdrs, &challenge)) {
+       ret = RXGEN_CC_UNMARSHAL;
+       goto done;
+    }
+
+    /* Start filling the response. */
+    response.start_time = cc->start_time;
+    if (rx_opaque_copy(&response.token, &cp->token) != 0) {
+       ret = RXGEN_CC_MARSHAL;
+       goto done;
+    }
+
+    /* Fill up the authenticator */
+    ret = fill_authenticator(&authenticator, &challenge, cp, aconn);
+    if (ret != 0)
+       goto done;
+    /* Authenticator is full, now to pack and encrypt it. */
+    ret = pack_wrap_authenticator(&response.authenticator, &authenticator, cp, cc);
+    if (ret != 0)
+       goto done;
+    /* Put the kvno we used on the wire for the remote end. */
+    rx_SetPacketCksum(apacket, (afs_uint16)cc->key_number);
+
+    /* Response is ready, now to shove it in a packet. */
+    ret = pack_response(&packed, &response);
+    if (ret != 0)
+       goto done;
+    osi_Assert(packed.len <= 0xffffu);
+    rx_packetwrite(apacket, 0, packed.len, packed.val);
+    rx_SetDataSize(apacket, (afs_uint16)packed.len);
+
+ done:
+    if (xdrs.x_ops) {
+        xdr_destroy(&xdrs);
+    }
+    xdr_free((xdrproc_t)xdr_RXGK_Challenge, &challenge);
+    xdr_free((xdrproc_t)xdr_RXGK_Response, &response);
+    xdr_free((xdrproc_t)xdr_RXGK_Authenticator, &authenticator);
+    rx_opaque_freeContents(&packed);
+    return ret;
+}
+
+static void
+update_kvno(struct rxgk_cconn *cc, afs_uint32 kvno)
+{
+    cc->key_number = kvno;
+
+    /* XXX Our statistics for tracking when to re-key the conn should be reset
+     * here. */
 }
 
 static int
 rxgk_ClientCheckPacket(struct rx_securityClass *aobj, struct rx_call *acall,
                       struct rx_packet *apacket)
 {
-    return RXGK_INCONSISTENCY;
+    struct rxgk_cconn *cc;
+    struct rxgk_cprivate *cp;
+    struct rx_connection *aconn;
+    afs_uint32 lkvno, kvno;
+    afs_uint16 len;
+    int ret;
+
+    aconn = rx_ConnectionOf(acall);
+    cc = rx_GetSecurityData(aconn);
+    cp = aobj->privateData;
+
+    len = rx_GetDataSize(apacket);
+    cc->stats.precv++;
+    cc->stats.brecv += len;
+
+    lkvno = kvno = cc->key_number;
+    ret = rxgk_check_packet(0, aconn, apacket, cp->level, cc->start_time,
+                            &kvno, cp->k0);
+    if (ret != 0)
+       return ret;
+
+    if (kvno > lkvno)
+       update_kvno(cc, kvno);
+
+    return ret;
 }
 
 static void
 rxgk_DestroyClientConnection(struct rx_securityClass *aobj,
                             struct rx_connection *aconn)
 {
+    struct rxgk_cconn *cc;
+
+    cc = rx_GetSecurityData(aconn);
+    if (cc == NULL) {
+        return;
+    }
+    rx_SetSecurityData(aconn, NULL);
+
+    rxi_Free(cc, sizeof(*cc));
+    obj_rele(aobj);
 }
 
 static int
 rxgk_ClientGetStats(struct rx_securityClass *aobj, struct rx_connection *aconn,
                    struct rx_securityObjectStats *astats)
 {
-    return RXGK_INCONSISTENCY;
+    struct rxgkStats *stats;
+    struct rxgk_cprivate *cp;
+    struct rxgk_cconn *cc;
+
+    astats->type = RX_SECTYPE_GK;
+    cc = rx_GetSecurityData(aconn);
+    if (cc == NULL) {
+       astats->flags |= RXGK_STATS_UNALLOC;
+       return 0;
+    }
+
+    stats = &cc->stats;
+    cp = aobj->privateData;
+    astats->level = cp->level;
+
+    astats->packetsReceived = stats->precv;
+    astats->packetsSent = stats->psent;
+    astats->bytesReceived = stats->brecv;
+    astats->bytesSent = stats->bsent;
+
+    return 0;
+}
+
+static struct rx_securityOps rxgk_client_ops = {
+    rxgk_ClientClose,
+    rxgk_NewClientConnection,          /* every new connection */
+    rxgk_ClientPreparePacket,          /* once per packet creation */
+    0,                                 /* send packet (once per retrans) */
+    0,
+    0,
+    0,
+    rxgk_GetResponse,                  /* respond to challenge packet */
+    0,
+    rxgk_ClientCheckPacket,            /* check data packet */
+    rxgk_DestroyClientConnection,
+    rxgk_ClientGetStats,
+    0,
+    0,                                 /* spare 1 */
+    0,                                 /* spare 2 */
+};
+
+/*
+ * Create an rxgk client security object
+ *
+ * @param[in] level    The security level of the token; this must be the
+ *                     level used for all connections using this security
+ *                     object.
+ * @param[in] enctype  The RFC 3961 enctype of k0.
+ * @param[in] k0       The master key of the token.
+ * @param[in] token    The rxgk token used to authenticate the connection.
+ * @return NULL on failure, and a pointer to the security object on success.
+ */
+struct rx_securityClass *
+rxgk_NewClientSecurityObject(RXGK_Level level, afs_int32 enctype, rxgk_key k0,
+                            RXGK_Data *token)
+{
+    struct rx_securityClass *sc = NULL;
+    struct rxgk_cprivate *cp = NULL;
+
+    sc = rxi_Alloc(sizeof(*sc));
+    if (sc == NULL)
+       goto error;
+    cp = rxi_Alloc(sizeof(*cp));
+    if (cp == NULL)
+       goto error;
+    sc->ops = &rxgk_client_ops;
+    sc->refCount = 1;
+    sc->privateData = cp;
+
+    /* Now get the client-private data. */
+    if (rxgk_copy_key(k0, &cp->k0) != 0)
+       goto error;
+    cp->enctype = enctype;
+    cp->level = level;
+    if (rx_opaque_copy(&cp->token, token) != 0)
+       goto error;
+
+    return sc;
+
+ error:
+    rxi_Free(sc, sizeof(*sc));
+    cprivate_destroy(cp);
+    return NULL;
 }
index 0b7a8f7..a5eef12 100644 (file)
@@ -103,13 +103,10 @@ struct rxgk_sconn {
  * or tied to a particular file server (which does have a UUID).
  */
 struct rxgk_cprivate {
-    afs_int32 flags;
     rxgk_key k0;
     afs_int32 enctype;
     RXGK_Level level;
     RXGK_Data token;
-    afsUUID *client_uuid;
-    afsUUID *target_uuid;
 };
 /**
  * Per-connection security data for client.
@@ -122,9 +119,6 @@ struct rxgk_cprivate {
 struct rxgk_cconn {
     rxgkTime start_time;
     afs_uint32 key_number;
-    RXGK_Data cb_tok;
-    RXGK_Data cb_k0;
-    afs_int32 cb_enctype;
     struct rxgkStats stats;
 };