krb5 commit: Add cred marshalling functions

Greg Hudson ghudson at MIT.EDU
Sat May 17 19:54:29 EDT 2014


https://github.com/krb5/krb5/commit/99e1d7a4b91676d968ecf9329a48ec2b3c0a193a
commit 99e1d7a4b91676d968ecf9329a48ec2b3c0a193a
Author: Greg Hudson <ghudson at mit.edu>
Date:   Mon May 5 00:06:38 2014 -0400

    Add cred marshalling functions
    
    Add a new file ccmarshal.c containing functions to marshal and
    unmarshal credentials in file formats version 1-4.  These will replace
    the functions in cc_file.c and cc_keyring.c, and can be used for KCM
    in the future.

 src/lib/krb5/ccache/Makefile.in |    3 +
 src/lib/krb5/ccache/cc-int.h    |   16 ++
 src/lib/krb5/ccache/ccmarshal.c |  476 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 495 insertions(+), 0 deletions(-)

diff --git a/src/lib/krb5/ccache/Makefile.in b/src/lib/krb5/ccache/Makefile.in
index ad53e65..19a8dc9 100644
--- a/src/lib/krb5/ccache/Makefile.in
+++ b/src/lib/krb5/ccache/Makefile.in
@@ -21,6 +21,7 @@ STLIBOBJS= \
 	cccursor.o \
 	ccdefault.o \
 	ccdefops.o \
+	ccmarshal.o \
 	ccselect.o \
 	ccselect_k5identity.o \
 	ccselect_realm.o \
@@ -37,6 +38,7 @@ OBJS=	$(OUTPRE)ccbase.$(OBJEXT) \
 	$(OUTPRE)cccursor.$(OBJEXT) \
 	$(OUTPRE)ccdefault.$(OBJEXT) \
 	$(OUTPRE)ccdefops.$(OBJEXT) \
+	$(OUTPRE)ccmarshal.$(OBJEXT) \
 	$(OUTPRE)ccselect.$(OBJEXT) \
 	$(OUTPRE)ccselect_k5identity.$(OBJEXT) \
 	$(OUTPRE)ccselect_realm.$(OBJEXT) \
@@ -53,6 +55,7 @@ SRCS=	$(srcdir)/ccbase.c \
 	$(srcdir)/cccursor.c \
 	$(srcdir)/ccdefault.c \
 	$(srcdir)/ccdefops.c \
+	$(srcdir)/ccmarshal.c \
 	$(srcdir)/ccselect.c \
 	$(srcdir)/ccselect_k5identity.c \
 	$(srcdir)/ccselect_realm.c \
diff --git a/src/lib/krb5/ccache/cc-int.h b/src/lib/krb5/ccache/cc-int.h
index b125d87..1aa42bb 100644
--- a/src/lib/krb5/ccache/cc-int.h
+++ b/src/lib/krb5/ccache/cc-int.h
@@ -131,6 +131,22 @@ krb5_error_code
 ccselect_k5identity_initvt(krb5_context context, int maj_ver, int min_ver,
                            krb5_plugin_vtable vtable);
 
+krb5_error_code
+k5_unmarshal_cred(const unsigned char *data, size_t len, int version,
+                  krb5_creds *creds);
+
+krb5_error_code
+k5_unmarshal_princ(const unsigned char *data, size_t len, int version,
+                   krb5_principal *princ_out);
+
+krb5_error_code
+k5_marshal_cred(krb5_creds *creds, int version, unsigned char **bytes_out,
+                size_t *len_out);
+
+krb5_error_code
+k5_marshal_princ(krb5_principal princ, int version, unsigned char **bytes_out,
+                 size_t *len_out);
+
 /*
  * Per-type ccache cursor.
  */
diff --git a/src/lib/krb5/ccache/ccmarshal.c b/src/lib/krb5/ccache/ccmarshal.c
new file mode 100644
index 0000000..c27b43f
--- /dev/null
+++ b/src/lib/krb5/ccache/ccmarshal.c
@@ -0,0 +1,476 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ccmarshal.c - Functions for serializing creds */
+/*
+ * Copyright (C) 2014 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This file implements marshalling and unmarshalling of krb5 credentials and
+ * principals in versions 1 through 4 of the FILE ccache format.  Version 4 is
+ * also used for the KEYRING ccache type.
+ *
+ * The FILE credential cache format uses fixed 16-bit or 32-bit representations
+ * of integers.  In versions 1 and 2 these are in host byte order; in later
+ * versions they are in big-endian byte order.  Variable-length fields are
+ * represented with a 32-bit length followed by the field value.  There is no
+ * type tagging; field representations are simply concatenated together.
+ *
+ * A psuedo-BNF grammar for the credential and principal formats is:
+ *
+ * credential ::=
+ *   client (principal)
+ *   server (principal)
+ *   keyblock (keyblock)
+ *   authtime (32 bits)
+ *   starttime (32 bits)
+ *   endtime (32 bits)
+ *   renew_till (32 bits)
+ *   is_skey (1 byte, 0 or 1)
+ *   ticket_flags (32 bits)
+ *   addresses (addresses)
+ *   authdata (authdata)
+ *   ticket (data)
+ *   second_ticket (data)
+ *
+ * principal ::=
+ *   name type (32 bits) [omitted in version 1]
+ *   count of components (32 bits) [includes realm in version 1]
+ *   realm (data)
+ *   component1 (data)
+ *   component2 (data)
+ *   ...
+ *
+ * keyblock ::=
+ *   enctype (16 bits) [repeated twice in version 3; see below]
+ *   data
+ *
+ * addresses ::=
+ *   count (32 bits)
+ *   address1
+ *   address2
+ *   ...
+ *
+ * address ::=
+ *   addrtype (16 bits)
+ *   data
+ *
+ * authdata ::=
+ *   count (32 bits)
+ *   authdata1
+ *   authdata2
+ *   ...
+ *
+ * authdata ::=
+ *   ad_type (16 bits)
+ *   data
+ *
+ * data ::=
+ *   length (32 bits)
+ *   value (length bytes)
+ *
+ * When version 3 was current (before release 1.0), the keyblock had separate
+ * key type and enctype fields, and both were recorded.  At present we record
+ * the enctype field twice when writing the version 3 format and ignore the
+ * second value when reading it.
+ */
+
+#include "k5-input.h"
+#include "cc-int.h"
+
+/* Read a 16-bit integer in host byte order for versions 1 and 2, or in
+ * big-endian byte order for later versions.*/
+static uint16_t
+get16(struct k5input *in, int version)
+{
+    return (version < 3) ? k5_input_get_uint16_n(in) :
+        k5_input_get_uint16_be(in);
+}
+
+/* Read a 32-bit integer in host byte order for versions 1 and 2, or in
+ * big-endian byte order for later versions.*/
+static uint32_t
+get32(struct k5input *in, int version)
+{
+    return (version < 3) ? k5_input_get_uint32_n(in) :
+        k5_input_get_uint32_be(in);
+}
+
+/* Read a 32-bit length and make a copy of that many bytes.  Return NULL on
+ * error. */
+static void *
+get_len_bytes(struct k5input *in, int version, unsigned int *len_out)
+{
+    krb5_error_code ret;
+    unsigned int len = get32(in, version);
+    const void *bytes = k5_input_get_bytes(in, len);
+    void *copy;
+
+    *len_out = 0;
+    if (bytes == NULL)
+        return NULL;
+    copy = k5memdup0(bytes, len, &ret);
+    if (copy == NULL) {
+        k5_input_set_status(in, ret);
+        return NULL;
+    }
+    *len_out = len;
+    return copy;
+}
+
+/* Like get_len_bytes, but put the result in data. */
+static void
+get_data(struct k5input *in, int version, krb5_data *data)
+{
+    unsigned int len;
+    void *bytes = get_len_bytes(in, version, &len);
+
+    *data = (bytes == NULL) ? empty_data() : make_data(bytes, len);
+}
+
+static krb5_principal
+unmarshal_princ(struct k5input *in, int version)
+{
+    krb5_error_code ret;
+    krb5_principal princ;
+    uint32_t i, ncomps;
+
+    princ = k5alloc(sizeof(krb5_principal_data), &ret);
+    if (princ == NULL) {
+        k5_input_set_status(in, ret);
+        return NULL;
+    }
+    princ->magic = KV5M_PRINCIPAL;
+    /* Version 1 does not store the principal name type, and counts the realm
+     * in the number of components. */
+    princ->type = (version == 1) ? KRB5_NT_UNKNOWN : get32(in, version);
+    ncomps = get32(in, version);
+    if (version == 1)
+        ncomps--;
+    if (ncomps > in->len) {     /* Sanity check to avoid large allocations */
+        ret = EINVAL;
+        goto error;
+    }
+    if (ncomps != 0) {
+        princ->data = k5calloc(ncomps, sizeof(krb5_data), &ret);
+        if (princ->data == NULL)
+            goto error;
+        princ->length = ncomps;
+    }
+    get_data(in, version, &princ->realm);
+    for (i = 0; i < ncomps; i++)
+        get_data(in, version, &princ->data[i]);
+    return princ;
+
+error:
+    k5_input_set_status(in, ret);
+    krb5_free_principal(NULL, princ);
+    return NULL;
+}
+
+static void
+unmarshal_keyblock(struct k5input *in, int version, krb5_keyblock *kb)
+{
+    memset(kb, 0, sizeof(*kb));
+    kb->magic = KV5M_KEYBLOCK;
+    kb->enctype = get16(in, version);
+    /* Version 3 stores the enctype twice. */
+    if (version == 3)
+        (void)get16(in, version);
+    kb->contents = get_len_bytes(in, version, &kb->length);
+}
+
+static krb5_address *
+unmarshal_addr(struct k5input *in, int version)
+{
+    krb5_address *addr;
+
+    addr = calloc(1, sizeof(*addr));
+    if (addr == NULL) {
+        k5_input_set_status(in, ENOMEM);
+        return NULL;
+    }
+    addr->magic = KV5M_ADDRESS;
+    addr->addrtype = get16(in, version);
+    addr->contents = get_len_bytes(in, version, &addr->length);
+    return addr;
+}
+
+static krb5_address **
+unmarshal_addrs(struct k5input *in, int version)
+{
+    krb5_address **addrs;
+    size_t i, count;
+
+    count = get32(in, version);
+    if (count > in->len) {      /* Sanity check to avoid large allocations */
+        k5_input_set_status(in, EINVAL);
+        return NULL;
+    }
+    addrs = calloc(count + 1, sizeof(*addrs));
+    if (addrs == NULL) {
+        k5_input_set_status(in, ENOMEM);
+        return NULL;
+    }
+    for (i = 0; i < count; i++)
+        addrs[i] = unmarshal_addr(in, version);
+    return addrs;
+}
+
+static krb5_authdata *
+unmarshal_authdatum(struct k5input *in, int version)
+{
+    krb5_authdata *ad;
+
+    ad = calloc(1, sizeof(*ad));
+    if (ad == NULL) {
+        k5_input_set_status(in, ENOMEM);
+        return NULL;
+    }
+    ad->magic = KV5M_ADDRESS;
+    /* Authdata types can be negative, so sign-extend the get16 result. */
+    ad->ad_type = (int16_t)get16(in, version);
+    ad->contents = get_len_bytes(in, version, &ad->length);
+    return ad;
+}
+
+static krb5_authdata **
+unmarshal_authdata(struct k5input *in, int version)
+{
+    krb5_authdata **authdata;
+    size_t i, count;
+
+    count = get32(in, version);
+    if (count > in->len) {      /* Sanity check to avoid large allocations */
+        k5_input_set_status(in, EINVAL);
+        return NULL;
+    }
+    authdata = calloc(count + 1, sizeof(*authdata));
+    if (authdata == NULL) {
+        k5_input_set_status(in, ENOMEM);
+        return NULL;
+    }
+    for (i = 0; i < count; i++)
+        authdata[i] = unmarshal_authdatum(in, version);
+    return authdata;
+}
+
+/* Unmarshal a credential using the specified file ccache version (expressed as
+ * an integer from 1 to 4).  Does not check for trailing garbage. */
+krb5_error_code
+k5_unmarshal_cred(const unsigned char *data, size_t len, int version,
+                  krb5_creds *creds)
+{
+    struct k5input in;
+
+    k5_input_init(&in, data, len);
+    creds->client = unmarshal_princ(&in, version);
+    creds->server = unmarshal_princ(&in, version);
+    unmarshal_keyblock(&in, version, &creds->keyblock);
+    creds->times.authtime = get32(&in, version);
+    creds->times.starttime = get32(&in, version);
+    creds->times.endtime = get32(&in, version);
+    creds->times.renew_till = get32(&in, version);
+    creds->is_skey = k5_input_get_byte(&in);
+    creds->ticket_flags = get32(&in, version);
+    creds->addresses = unmarshal_addrs(&in, version);
+    creds->authdata = unmarshal_authdata(&in, version);
+    get_data(&in, version, &creds->ticket);
+    get_data(&in, version, &creds->second_ticket);
+    if (in.status) {
+        krb5_free_cred_contents(NULL, creds);
+        memset(creds, 0, sizeof(*creds));
+    }
+    return (in.status == EINVAL) ? KRB5_CC_FORMAT : in.status;
+}
+
+/* Unmarshal a principal using the specified file ccache version (expressed as
+ * an integer from 1 to 4).  Does not check for trailing garbage. */
+krb5_error_code
+k5_unmarshal_princ(const unsigned char *data, size_t len, int version,
+                   krb5_principal *princ_out)
+{
+    struct k5input in;
+    krb5_principal princ;
+
+    *princ_out = NULL;
+    k5_input_init(&in, data, len);
+    princ = unmarshal_princ(&in, version);
+    if (in.status)
+        krb5_free_principal(NULL, princ);
+    else
+        *princ_out = princ;
+    return (in.status == EINVAL) ? KRB5_CC_FORMAT : in.status;
+}
+
+/* Store a 16-bit integer in host byte order for versions 1 and 2, or in
+ * big-endian byte order for later versions.*/
+static void
+put16(struct k5buf *buf, int version, uint16_t num)
+{
+    char n[2];
+
+    if (version < 3)
+        store_16_n(num, n);
+    else
+        store_16_be(num, n);
+    k5_buf_add_len(buf, n, 2);
+}
+
+/* Store a 32-bit integer in host byte order for versions 1 and 2, or in
+ * big-endian byte order for later versions.*/
+static void
+put32(struct k5buf *buf, int version, uint32_t num)
+{
+    char n[4];
+
+    if (version < 3)
+        store_32_n(num, n);
+    else
+        store_32_be(num, n);
+    k5_buf_add_len(buf, n, 4);
+}
+
+static void
+put_len_bytes(struct k5buf *buf, int version, const void *bytes,
+              unsigned int len)
+{
+    put32(buf, version, len);
+    k5_buf_add_len(buf, bytes, len);
+}
+
+static void
+put_data(struct k5buf *buf, int version, krb5_data *data)
+{
+    put_len_bytes(buf, version, data->data, data->length);
+}
+
+static void
+marshal_princ(struct k5buf *buf, int version, krb5_principal princ)
+{
+    int32_t i, ncomps;
+
+    /* Version 1 does not store the principal name type, and counts the realm
+     * in the number of components. */
+    if (version != 1)
+        put32(buf, version, princ->type);
+    ncomps = princ->length + ((version == 1) ? 1 : 0);
+    put32(buf, version, ncomps);
+    put_data(buf, version, &princ->realm);
+    for (i = 0; i < princ->length; i++)
+        put_data(buf, version, &princ->data[i]);
+}
+
+static void
+marshal_keyblock(struct k5buf *buf, int version, krb5_keyblock *kb)
+{
+    put16(buf, version, kb->enctype);
+    /* Version 3 stores the enctype twice. */
+    if (version == 3)
+        put16(buf, version, kb->enctype);
+    put_len_bytes(buf, version, kb->contents, kb->length);
+}
+
+static void
+marshal_addrs(struct k5buf *buf, int version, krb5_address **addrs)
+{
+    size_t i, count;
+
+    for (count = 0; addrs != NULL && addrs[count] != NULL; count++);
+    put32(buf, version, count);
+    for (i = 0; i < count; i++) {
+        put16(buf, version, addrs[i]->addrtype);
+        put_len_bytes(buf, version, addrs[i]->contents, addrs[i]->length);
+    }
+}
+
+static void
+marshal_authdata(struct k5buf *buf, int version, krb5_authdata **authdata)
+{
+    size_t i, count;
+
+    for (count = 0; authdata != NULL && authdata[count] != NULL; count++);
+    put32(buf, version, count);
+    for (i = 0; i < count; i++) {
+        put16(buf, version, authdata[i]->ad_type);
+        put_len_bytes(buf, version, authdata[i]->contents,
+                      authdata[i]->length);
+    }
+}
+
+/* Marshal a credential using the specified file ccache version (expressed as
+ * an integer from 1 to 4). */
+krb5_error_code
+k5_marshal_cred(krb5_creds *creds, int version, unsigned char **bytes_out,
+                size_t *len_out)
+{
+    struct k5buf buf;
+    char is_skey;
+
+    *bytes_out = NULL;
+    *len_out = 0;
+    k5_buf_init_dynamic(&buf);
+    marshal_princ(&buf, version, creds->client);
+    marshal_princ(&buf, version, creds->server);
+    marshal_keyblock(&buf, version, &creds->keyblock);
+    put32(&buf, version, creds->times.authtime);
+    put32(&buf, version, creds->times.starttime);
+    put32(&buf, version, creds->times.endtime);
+    put32(&buf, version, creds->times.renew_till);
+    is_skey = creds->is_skey;
+    k5_buf_add_len(&buf, &is_skey, 1);
+    put32(&buf, version, creds->ticket_flags);
+    marshal_addrs(&buf, version, creds->addresses);
+    marshal_authdata(&buf, version, creds->authdata);
+    put_data(&buf, version, &creds->ticket);
+    put_data(&buf, version, &creds->second_ticket);
+    if (k5_buf_data(&buf) == NULL)
+        return ENOMEM;
+    *bytes_out = (unsigned char *)k5_buf_data(&buf);
+    *len_out = k5_buf_len(&buf);
+    return 0;
+}
+
+/* Marshal a principal using the specified file ccache version (expressed as an
+ * integer from 1 to 4). */
+krb5_error_code
+k5_marshal_princ(krb5_principal princ, int version, unsigned char **bytes_out,
+                 size_t *len_out)
+{
+    struct k5buf buf;
+
+    *bytes_out = NULL;
+    *len_out = 0;
+    k5_buf_init_dynamic(&buf);
+    marshal_princ(&buf, version, princ);
+    if (k5_buf_data(&buf) == NULL)
+        return ENOMEM;
+    *bytes_out = (unsigned char *)k5_buf_data(&buf);
+    *len_out = k5_buf_len(&buf);
+    return 0;
+}


More information about the cvs-krb5 mailing list