diff --git a/lib/gssapi/Makefile.am b/lib/gssapi/Makefile.am index 744232e2e..a4b203b0f 100644 --- a/lib/gssapi/Makefile.am +++ b/lib/gssapi/Makefile.am @@ -59,6 +59,7 @@ krb5src = \ krb5/inquire_mechs_for_name.c \ krb5/inquire_names_for_mech.c \ krb5/inquire_sec_context_by_oid.c \ + krb5/name_attrs.c \ krb5/pname_to_uid.c \ krb5/process_context_token.c \ krb5/prf.c \ @@ -381,6 +382,8 @@ LDADD = libgssapi.la \ $(top_builddir)/lib/krb5/libkrb5.la \ $(LIB_roken) +test_names_LDADD = $(LDADD) $(top_builddir)/lib/asn1/libasn1.la + # gss dist_gsstool_SOURCES = gsstool.c diff --git a/lib/gssapi/gssapi/gssapi.h b/lib/gssapi/gssapi/gssapi.h index 4214acc0e..671da64cd 100644 --- a/lib/gssapi/gssapi/gssapi.h +++ b/lib/gssapi/gssapi/gssapi.h @@ -233,6 +233,7 @@ typedef OM_uint32 gss_qop_t; #define GSS_C_NO_CREDENTIAL ((gss_cred_id_t) 0) #define GSS_C_NO_CHANNEL_BINDINGS ((gss_channel_bindings_t) 0) #define GSS_C_EMPTY_BUFFER {0, NULL} +#define GSS_C_EMPTY_BUFFER_SET {0, NULL} #define GSS_C_NO_IOV_BUFFER ((gss_iov_buffer_t)0) #define GSS_C_NO_CRED_STORE ((gss_key_value_set_t)0) @@ -393,6 +394,18 @@ extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_nt_anonymous_oid_desc; extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_nt_export_name_oid_desc; #define GSS_C_NT_EXPORT_NAME (&__gss_c_nt_export_name_oid_desc) +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {6, (void *)"\x2b\x06\x01\x05\x06\x06"}, corresponding to an + * object-identifier value of {iso(1) identified-organization(3) dod(6) + * internet(1) security(5) nametypes(6) gss-composite-export(6)}. + * The constant GSS_C_NT_COMPOSITE_EXPORT [RFC6680] should be initialized to + * point to that gss_OID_desc. + */ +extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_nt_composite_export_oid_desc; +#define GSS_C_NT_COMPOSITE_EXPORT (&__gss_c_nt_composite_export_oid_desc) + /* Major status codes */ #define GSS_S_COMPLETE 0 diff --git a/lib/gssapi/gssapi/gssapi_krb5.h b/lib/gssapi/gssapi/gssapi_krb5.h index 74d5109aa..818042fa7 100644 --- a/lib/gssapi/gssapi/gssapi_krb5.h +++ b/lib/gssapi/gssapi/gssapi_krb5.h @@ -218,6 +218,8 @@ gss_krb5_set_allowable_enctypes(OM_uint32 *minor_status, OM_uint32 num_enctypes, int32_t *enctypes); +#define GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "urn:ietf:kerberos:nameattr-" + GSSAPI_CPP_END #endif /* GSSAPI_SPNEGO_H_ */ diff --git a/lib/gssapi/krb5/external.c b/lib/gssapi/krb5/external.c index 91947025c..f84b078d5 100644 --- a/lib/gssapi/krb5/external.c +++ b/lib/gssapi/krb5/external.c @@ -152,6 +152,13 @@ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_nt_export_name_oid_desc = gss_OID_desc GSSAPI_LIB_VARIABLE __gss_krb5_nt_principal_name_oid_desc = {10, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x01") }; +/* + * GSS_C_NT_COMPOSITE_EXPORT [RFC6680], OID {iso(1) identified-organization(3) + * dod(6) internet(1) security(5) nametypes(6) gss-composite-export(6)}. + */ +gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_nt_composite_export_oid_desc = + {6, rk_UNCONST("\x2b\x06\x01\x05\x06\x06")}; + /* * draft-ietf-cat-iakerb-09, IAKERB: * The mechanism ID for IAKERB proxy GSS-API Kerberos, in accordance @@ -383,12 +390,12 @@ static gssapi_mech_interface_desc krb5_mech = { sizeof(krb5_mo) / sizeof(krb5_mo[0]), _gsskrb5_localname, _gsskrb5_authorize_localname, - NULL, /* gm_display_name_ext */ - NULL, /* gm_inquire_name */ - NULL, /* gm_get_name_attribute */ + _gsskrb5_display_name_ext, + _gsskrb5_inquire_name, + _gsskrb5_get_name_attribute, NULL, /* gm_set_name_attribute */ NULL, /* gm_delete_name_attribute */ - NULL, /* gm_export_name_composite */ + _gsskrb5_export_name_composite, _gsskrb5_duplicate_cred, _gsskrb5_add_cred_from, _gsskrb5_store_cred_into, diff --git a/lib/gssapi/krb5/import_name.c b/lib/gssapi/krb5/import_name.c index 6a362640b..110ee2b12 100644 --- a/lib/gssapi/krb5/import_name.c +++ b/lib/gssapi/krb5/import_name.c @@ -169,9 +169,11 @@ import_export_name (OM_uint32 *minor_status, const gss_buffer_t input_name_buffer, gss_name_t *output_name) { + CompositePrincipal *composite; unsigned char *p; - uint32_t length; + size_t length, sz; OM_uint32 ret; + int is_composite; char *name; if (input_name_buffer->length < 10 + GSS_KRB5_MECHANISM->length) @@ -181,7 +183,9 @@ import_export_name (OM_uint32 *minor_status, p = input_name_buffer->value; - if (memcmp(&p[0], "\x04\x01\x00", 3) != 0 || + if (p[0] != 0x04 || + (p[1] != 0x01 && p[1] != 0x02) || + p[2] != 0x00 || p[3] != GSS_KRB5_MECHANISM->length + 2 || p[4] != 0x06 || p[5] != GSS_KRB5_MECHANISM->length || @@ -189,6 +193,8 @@ import_export_name (OM_uint32 *minor_status, GSS_KRB5_MECHANISM->length) != 0) return GSS_S_BAD_NAME; + is_composite = p[1] == 0x02; + p += 6 + GSS_KRB5_MECHANISM->length; length = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; @@ -197,6 +203,28 @@ import_export_name (OM_uint32 *minor_status, if (length > input_name_buffer->length - 10 - GSS_KRB5_MECHANISM->length) return GSS_S_BAD_NAME; + if (is_composite) { + if ((composite = calloc(1, sizeof(*composite))) == NULL) { + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + + ret = decode_CompositePrincipal(p, length, composite, &sz); + if (ret) { + *minor_status = ret; + return GSS_S_FAILURE; + } + if (sz != length) { + free_CompositePrincipal(composite); + free(composite); + *minor_status = EINVAL; + return GSS_S_FAILURE; + } + + *output_name = (void *)composite; + return GSS_S_COMPLETE; + } + name = malloc(length + 1); if (name == NULL) { *minor_status = ENOMEM; @@ -207,7 +235,6 @@ import_export_name (OM_uint32 *minor_status, ret = parse_krb5_name(minor_status, context, name, output_name); free(name); - return ret; } @@ -239,7 +266,8 @@ OM_uint32 GSSAPI_CALLCONV _gsskrb5_import_name context, input_name_buffer, output_name); - else if (gss_oid_equal(input_name_type, GSS_C_NT_EXPORT_NAME)) { + else if (gss_oid_equal(input_name_type, GSS_C_NT_EXPORT_NAME) || + gss_oid_equal(input_name_type, GSS_C_NT_COMPOSITE_EXPORT)) { return import_export_name(minor_status, context, input_name_buffer, diff --git a/lib/gssapi/krb5/name_attrs.c b/lib/gssapi/krb5/name_attrs.c new file mode 100644 index 000000000..4c06c65ec --- /dev/null +++ b/lib/gssapi/krb5/name_attrs.c @@ -0,0 +1,692 @@ +/* + * Copyright (c) 2021 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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. + */ + +#include "gsskrb5_locl.h" + +/* + * (Not-yet-)Standard name attributes for Kerberos MNs, + * GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "...". + * + * I.e., "urn:ietf:kerberos:nameattr-...". (XXX Register this URN namespace + * with IANA.) + * + * Note that we do use URN fragments. + * + * Specific attributes below the base URN: + * + * - name access attributes: + * - "realm" -> realm of name + * - "name-ncomp" -> count of name components + * - "name-ncomp#" -> name component N (0 <= N <= 9) + * + * Ticket and Authenticator access attributes: + * + * - "transit-path" -> encoding of the transited path + * - "authenticator-authz-data" -> encoding of all of the authz-data from + * the AP-REQ's Authenticator + * - "ticket-authz-data" -> encoding of all of the authz-data from + * the AP-REQ's Ticket + * - "ticket-authz-data#pac" -> the PAC + * - "authz-data#" -> encoding of all of a specific auth-data + * element type N (e.g., 2, meaning + * AD-INTENDED-FOR-SERVER) + * + * Misc. attributes: + * + * - "peer-realm" -> name of peer's realm (if this is an MN + * resulting for establishing a security + * context) + * - "canonical-name" -> exported name token and RFC1964 display + * syntax of the name's canonical name + * + * Compatibility with MIT: + * + * - "urn:mspac:" -> the PAC + * + * TODO: + * + * - Add some sort of display syntax for transit path + * - Add support for URN q-components or attribute prefixes to specify + * alternative raw and/or display value encodings (JSON?) + * - Add support for attributes for accessing other parts of the Ticket / KDC + * reply enc-parts, like auth times + * - Add support for getting specific PAC buffers + * - Add support for getting PAC login fields, including SIDs (one at a time) + * - Add support for CAMMAC? + */ + +static int +attr_eq(gss_buffer_t attr, const char *aname, size_t aname_len) +{ + const char *s; + + if (attr->length < aname_len) + return 0; + /* Note: `s' is not NUL-terminated */ + s = ((const char *)attr->value) + attr->length - aname_len; + return strncmp(s, aname, aname_len) == 0 && + (attr->length == aname_len || s[aname_len] == '#'); +} + +#define ATTR_EQ(a, an) (attr_eq(a, an, sizeof(an) - 1)) + +/* Split attribute into prefix, suffix, and fragment. See RFC6680. */ +static void +split_attr(restrict gss_const_buffer_t orig, + restrict gss_buffer_t prefix, + restrict gss_buffer_t attr, + restrict gss_buffer_t frag, + int *is_urn) +{ + char *last = NULL; + char *p = orig->value; + + *attr = *orig; + prefix->value = orig->value; + prefix->length = 0; + frag->length = 0; + frag->value = NULL; + + /* FIXME We don't have a memrchr() in lib/roken */ + for (p = memchr(p, ' ', orig->length); + p; + p = p ? memchr(p + 1, ' ', orig->length) : NULL) { + last = p; + prefix->length = last - (const char *)orig->value; + attr->value = last + 1; + attr->length = orig->length - (prefix->length + 1); + } + if (prefix->length == 0) + prefix->value = NULL; + + if ((*is_urn = (strncmp(attr->value, "urn:", sizeof("urn:") - 1) == 0)) && + (p = memchr((char *)attr->value + 1, '#', attr->length - 1))) { + frag->value = ++p; + frag->length = attr->length - (p - (const char *)attr->value); + attr->length = --p - (const char *)attr->value; + } +} + +OM_uint32 GSSAPI_CALLCONV +_gsskrb5_get_name_attribute(OM_uint32 *minor_status, + gss_name_t gname, + gss_buffer_t original_attr, + int *authenticated, + int *complete, + gss_buffer_t value, + gss_buffer_t display_value, + int *more) +{ + krb5_const_principal name = (krb5_const_principal)gname; + krb5_error_code kret = 0; + gss_buffer_desc prefix, attr, frag; + PrincipalNameAttrs *nameattrs = name->nameattrs; + PrincipalNameAttrSrc *src = nameattrs ? nameattrs->source : NULL; + EncTicketPart *ticket = NULL; + EncKDCRepPart *kdcrep = NULL; + int is_urn; + + if (src) switch (src->element) { + case choice_PrincipalNameAttrSrc_enc_kdc_rep_part: + kdcrep = &src->u.enc_kdc_rep_part; + break; + case choice_PrincipalNameAttrSrc_enc_ticket_part: + ticket = &src->u.enc_ticket_part; + break; + default: + break; + } + + *minor_status = 0; + if (authenticated) + *authenticated = 0; + if (complete) + *complete = 0; + if (more) + *more = 0; + if (value) { + value->length = 0; + value->value = NULL; + } + if (display_value) { + display_value->length = 0; + display_value->value = NULL; + } + + split_attr(original_attr, &prefix, &attr, &frag, &is_urn); + if (prefix.length || !is_urn) + return GSS_S_UNAVAILABLE; + + if (ATTR_EQ(&attr, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "realm")) { + /* + * Output the principal's realm. The value and display value are the + * same in this case. + */ + if (authenticated && nameattrs && nameattrs->authenticated) + *authenticated = 1; + if (complete) + *complete = 1; + if (value) { + if ((value->value = strdup(name->realm)) == NULL) + goto enomem; + value->length = strlen(value->value); + } + if (display_value) { + if ((display_value->value = strdup(name->realm)) == NULL) + goto enomem; + display_value->length = strlen(display_value->value); + } + return GSS_S_COMPLETE; + } else if (ATTR_EQ(&attr, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "peer-realm") && + nameattrs && nameattrs->peer_realm) { + /* + * Output the peer's realm. The value and display value are the + * same in this case. + */ + if (authenticated) + *authenticated = 1; + if (complete) + *complete = 1; + if (value) { + if ((value->value = strdup(nameattrs->peer_realm[0])) == NULL) + goto enomem; + value->length = strlen(value->value); + } + if (display_value) { + if ((display_value->value = + strdup(nameattrs->peer_realm[0])) == NULL) + goto enomem; + display_value->length = strlen(display_value->value); + } + return GSS_S_COMPLETE; + } else if (ATTR_EQ(&attr, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "name-ncomp")) { + unsigned char n; + + if (authenticated && nameattrs && nameattrs->authenticated) + *authenticated = 1; + if (complete) + *complete = 1; + if (frag.length == 0) { + if (value) { + if ((value->value = malloc(sizeof(size_t))) == NULL) + goto enomem; + *((size_t *)value->value) = name->name.name_string.len; + value->length = sizeof(size_t); + } + if (display_value) { + char *s = NULL; + + if (asprintf(&s, "%u", + (unsigned int)name->name.name_string.len) == -1 || + s == NULL) + goto enomem; + display_value->value = s; + display_value->length = strlen(display_value->value); + } + return GSS_S_COMPLETE; + } /* else caller wants a component */ + if (frag.length != 1 || + ((const char *)frag.value)[0] < '0' || + ((const char *)frag.value)[0] > '9') { + *minor_status = EINVAL; + return GSS_S_UNAVAILABLE; + } + n = ((const char *)frag.value)[0] - '0'; + if (n >= name->name.name_string.len) { + *minor_status = EINVAL; + return GSS_S_UNAVAILABLE; + } + /* The value and the display value are the same in this case */ + if (value) { + if ((value->value = strdup(name->name.name_string.val[n])) == NULL) + goto enomem; + value->length = strlen(name->name.name_string.val[n]); + } + if (display_value) { + if ((display_value->value = + strdup(name->name.name_string.val[n])) == NULL) + goto enomem; + if (display_value) + display_value->length = strlen(name->name.name_string.val[n]); + } + return GSS_S_COMPLETE; + } else if (ATTR_EQ(&attr, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "canonical-name") && src) { + krb5_principal p = NULL; + krb5_context context; + + GSSAPI_KRB5_INIT(&context); + + if (authenticated) + *authenticated = 1; + if (complete) + *complete = 1; + + if (kdcrep) { + kret = _krb5_principalname2krb5_principal(context, &p, + kdcrep->sname, + kdcrep->srealm); + } else if (ticket) { + kret = _krb5_principalname2krb5_principal(context, &p, + ticket->cname, + ticket->crealm); + } else + return GSS_S_UNAVAILABLE; + if (kret == 0 && value) { + OM_uint32 major; + /* + * Value is exported name token (exported composite name token + * should also work). + */ + major = _gsskrb5_export_name(minor_status, (gss_name_t)p, value); + if (major != GSS_S_COMPLETE) { + krb5_free_principal(context, p); + return major; + } + } + if (kret == 0 && display_value) { + /* Display value is principal name display form */ + kret = krb5_unparse_name(context, p, + (char **)&display_value->value); + if (kret == 0) + display_value->length = strlen(display_value->value); + } + + krb5_free_principal(context, p); + if (kret) { + if (value) { + free(value->value); + value->length = 0; + value->value = NULL; + } + *minor_status = kret; + return GSS_S_UNAVAILABLE; + } + return GSS_S_COMPLETE; + } else if (ATTR_EQ(&attr, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "authz-data") && + frag.length && + ((nameattrs && nameattrs->authenticator_ad) || + (ticket && ticket->authorization_data))) { + krb5_context context; + krb5_data data; + char *s, *end; + int64_t n; + + /* Output a specific AD element from the ticket or authenticator */ + if ((s = strndup(frag.value, frag.length)) == NULL) { + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + errno = 0; + n = strtoll(s, &end, 10); + free(s); + if (end[0] == '\0' && (errno || n > INT_MAX || n < INT_MIN)) { + *minor_status = ERANGE; + return GSS_S_FAILURE; + } + if (end[0] != '\0') { + *minor_status = EINVAL; + return GSS_S_FAILURE; + } + + if (authenticated) + *authenticated = 0; + if (complete) + *complete = 1; + + GSSAPI_KRB5_INIT(&context); + + kret = ENOENT; + if (ticket->authorization_data) { + kret = _krb5_get_ad(context, ticket->authorization_data, + NULL, n, value ? &data : NULL); + + /* If it's from the ticket, it may be authenticated: */ + if (kret == 0 && authenticated) { + if (n == KRB5_AUTHDATA_KDC_ISSUED) + *authenticated = nameattrs->kdc_issued_verified; + else if (n == KRB5_AUTHDATA_WIN2K_PAC) + *authenticated = nameattrs->pac_verified; + } + } + if (kret == ENOENT && nameattrs->authenticator_ad && + n != KRB5_AUTHDATA_KDC_ISSUED && + n != KRB5_AUTHDATA_WIN2K_PAC) { + kret = _krb5_get_ad(context, ticket->authorization_data, + NULL, n, value ? &data : NULL); + } + + if (value) { + value->length = data.length; + value->value = data.data; + } + *minor_status = kret; + if (kret == ENOENT) + return GSS_S_UNAVAILABLE; + return kret == 0 ? GSS_S_COMPLETE : GSS_S_FAILURE; + } else if ((ATTR_EQ(&attr, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "ticket-authz-data#pac") || + ATTR_EQ(&attr, "urn:mspac:")) && + ticket && ticket->authorization_data) { + krb5_context context; + krb5_data data; + + /* + * In MIT the attribute for the whole PAC is "urn:mspac:". + * + * TBD: Add support for attributes for specific PAC buffers, like MIT. + */ + + GSSAPI_KRB5_INIT(&context); + + if (authenticated) + *authenticated = nameattrs->pac_verified; + if (complete) + *complete = 1; + + kret = _krb5_get_ad(context, ticket->authorization_data, + NULL, KRB5_AUTHDATA_WIN2K_PAC, + value ? &data : NULL); + + if (value) { + value->length = data.length; + value->value = data.data; + } + *minor_status = kret; + if (kret == ENOENT) + return GSS_S_UNAVAILABLE; + return kret == 0 ? GSS_S_COMPLETE : GSS_S_FAILURE; + } else if (ATTR_EQ(&attr, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "ticket-authz-data#kdc-issued") && + ticket && + ticket->authorization_data) { + krb5_context context; + krb5_data data; + + GSSAPI_KRB5_INIT(&context); + + if (authenticated) + *authenticated = nameattrs->kdc_issued_verified; + if (complete) + *complete = 1; + + kret = _krb5_get_ad(context, ticket->authorization_data, + NULL, KRB5_AUTHDATA_KDC_ISSUED, + value ? &data : NULL); + if (value) { + value->length = data.length; + value->value = data.data; + } + *minor_status = kret; + if (kret == ENOENT) + return GSS_S_UNAVAILABLE; + return kret == 0 ? GSS_S_COMPLETE : GSS_S_FAILURE; + } else if (ATTR_EQ(&attr, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "ticket-authz-data") && + frag.length == 0 && ticket && ticket->authorization_data) { + size_t sz; + + /* Just because it's in the Ticket doesn't make it authenticated */ + if (authenticated) + *authenticated = 0; + if (complete) + *complete = 1; + + if (value) { + ASN1_MALLOC_ENCODE(AuthorizationData, value->value, value->length, + ticket->authorization_data, &sz, kret); + *minor_status = kret; + } + return kret == 0 ? GSS_S_COMPLETE : GSS_S_FAILURE; + } else if (ATTR_EQ(&attr, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "authenticator-authz-data") && + nameattrs && nameattrs->authenticator_ad) { + size_t sz; + + if (authenticated) + *authenticated = 0; + if (complete) + *complete = 1; + + if (value) { + ASN1_MALLOC_ENCODE(AuthorizationData, value->value, value->length, + nameattrs->authenticator_ad, &sz, kret); + *minor_status = kret; + } + return kret == 0 ? GSS_S_COMPLETE : GSS_S_FAILURE; + } else if (ATTR_EQ(&attr, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "transit-path") && + (ticket || (nameattrs && nameattrs->transited))) { + size_t sz; + + if (authenticated) + *authenticated = 1; + if (complete) + *complete = 1; + + if (value && ticket) + ASN1_MALLOC_ENCODE(TransitedEncoding, value->value, value->length, + &ticket->transited, &sz, kret); + else if (value && nameattrs->transited) + ASN1_MALLOC_ENCODE(TransitedEncoding, value->value, value->length, + nameattrs->transited, &sz, kret); + *minor_status = kret; + return kret == 0 ? GSS_S_COMPLETE : GSS_S_FAILURE; + } + + return GSS_S_UNAVAILABLE; + +enomem: + if (value) + gss_release_buffer(minor_status, value); + *minor_status = ENOMEM; + return GSS_S_FAILURE; +} + +static OM_uint32 +add_urn(OM_uint32 *minor_status, + gss_name_t name, + gss_buffer_t urn, + gss_buffer_set_t *attrs) +{ + OM_uint32 major; + + major = _gsskrb5_get_name_attribute(minor_status, name, urn, + 0, 0, 0, 0, 0); + if (major == GSS_S_COMPLETE) { + major = gss_add_buffer_set_member(minor_status, urn, attrs); + if (major) + return major; + } + if (major == GSS_S_UNAVAILABLE) + return GSS_S_COMPLETE; + return major; +} + +#define ADD_URN(l) \ + do if (major == GSS_S_COMPLETE) { \ + if (strncmp(l, "urn:", sizeof("urn:") - 1) == 0) { \ + urn.value = l; \ + urn.length = sizeof(l) - 1; \ + } else { \ + urn.value = GSS_KRB5_NAME_ATTRIBUTE_BASE_URN l; \ + urn.length = sizeof(GSS_KRB5_NAME_ATTRIBUTE_BASE_URN l) - 1;\ + } \ + major = add_urn(minor_status, name, &urn, attrs); \ + } while (0) + +OM_uint32 GSSAPI_CALLCONV +_gsskrb5_inquire_name(OM_uint32 *minor_status, + gss_name_t name, + int *name_is_MN, + gss_OID *MN_mech, + gss_buffer_set_t *attrs) +{ + OM_uint32 major = GSS_S_COMPLETE; + gss_buffer_desc urn; + krb5_error_code ret; + krb5_context context; + char lname[32]; + + GSSAPI_KRB5_INIT(&context); + + *minor_status = 0; + if (name_is_MN) + *name_is_MN = 1; + if (MN_mech) + *MN_mech = GSS_KRB5_MECHANISM; + if (name == GSS_C_NO_NAME) + return GSS_S_CALL_INACCESSIBLE_READ; + if (attrs == NULL) + return GSS_S_CALL_INACCESSIBLE_WRITE; + ADD_URN("realm"); + ADD_URN("name-ncomp"); + ADD_URN("name-ncomp#0"); + ADD_URN("name-ncomp#1"); + ADD_URN("name-ncomp#2"); + ADD_URN("name-ncomp#3"); + ADD_URN("name-ncomp#4"); + ADD_URN("name-ncomp#5"); + ADD_URN("name-ncomp#6"); + ADD_URN("name-ncomp#7"); + ADD_URN("name-ncomp#8"); + ADD_URN("name-ncomp#9"); + ADD_URN("peer-realm"); + ADD_URN("ticket-authz-data#pac"); + ADD_URN("urn:mspac:"); + ADD_URN("authenticator-authz-data"); /* XXX Add fragments? */ + ADD_URN("ticket-authz-data"); /* XXX Add fragments? */ + ADD_URN("authz-data"); + ADD_URN("transit-path"); + ADD_URN("canonical-name"); + major = GSS_S_COMPLETE; + lname[0] = '\0'; + ret = krb5_aname_to_localname(context, (void *)name, + sizeof(lname) - 1, lname); + if (ret == 0 && lname[0] != '\0') + major = gss_add_buffer_set_member(minor_status, + GSS_C_ATTR_LOCAL_LOGIN_USER, attrs); + return major; +} + +OM_uint32 GSSAPI_CALLCONV +_gsskrb5_display_name_ext(OM_uint32 *minor_status, + gss_name_t name, + gss_OID display_as_name_type, + gss_buffer_t display_name) +{ + krb5_const_principal p = (void *)name; + char *s = NULL; + + *minor_status = 0; + if (display_name == NULL) + return GSS_S_CALL_INACCESSIBLE_WRITE; + display_name->length = 0; + display_name->value = NULL; + + if (gss_oid_equal(display_as_name_type, GSS_C_NT_USER_NAME)) { + if (p->name.name_string.len != 1) + return GSS_S_UNAVAILABLE; + return _gsskrb5_localname(minor_status, name, GSS_KRB5_MECHANISM, + display_name); + } + if (!gss_oid_equal(display_as_name_type, GSS_C_NT_HOSTBASED_SERVICE) || + p->name.name_string.len != 2 || + strchr(p->name.name_string.val[0], '@') || + strchr(p->name.name_string.val[1], '.') == NULL) + return GSS_S_UNAVAILABLE; + if (asprintf(&s, "%s@%s", p->name.name_string.val[0], + p->name.name_string.val[1]) == -1 || s == NULL) { + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + display_name->length = strlen(s); + display_name->value = s; + return GSS_S_COMPLETE; +} + +OM_uint32 GSSAPI_CALLCONV +_gsskrb5_export_name_composite(OM_uint32 *minor_status, + gss_name_t name, + gss_buffer_t exported_name) +{ + krb5_error_code kret; + gss_buffer_desc inner = GSS_C_EMPTY_BUFFER; + unsigned char *buf; + size_t sz; + + if (name == NULL) + return GSS_S_CALL_INACCESSIBLE_READ; + if (exported_name == NULL) + return GSS_S_CALL_INACCESSIBLE_WRITE; + + ASN1_MALLOC_ENCODE(CompositePrincipal, inner.value, inner.length, + (void *)name, &sz, kret); + if (kret != 0) { + *minor_status = kret; + return GSS_S_FAILURE; + } + + exported_name->length = 10 + inner.length + GSS_KRB5_MECHANISM->length; + exported_name->value = malloc(exported_name->length); + if (exported_name->value == NULL) { + free(inner.value); + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + + /* TOK, MECH_OID_LEN, DER(MECH_OID), NAME_LEN, NAME */ + + buf = exported_name->value; + buf[0] = 0x04; + buf[1] = 0x02; + buf[2] = ((GSS_KRB5_MECHANISM->length + 2) >> 8) & 0xff; + buf[3] = (GSS_KRB5_MECHANISM->length + 2) & 0xff; + buf[4] = 0x06; + buf[5] = (GSS_KRB5_MECHANISM->length) & 0xFF; + + memcpy(buf + 6, GSS_KRB5_MECHANISM->elements, GSS_KRB5_MECHANISM->length); + buf += 6 + GSS_KRB5_MECHANISM->length; + + buf[0] = (inner.length >> 24) & 0xff; + buf[1] = (inner.length >> 16) & 0xff; + buf[2] = (inner.length >> 8) & 0xff; + buf[3] = (inner.length) & 0xff; + buf += 4; + + memcpy(buf, inner.value, inner.length); + free(inner.value); + + *minor_status = 0; + return GSS_S_COMPLETE; +} diff --git a/lib/gssapi/libgssapi-exports.def b/lib/gssapi/libgssapi-exports.def index b8f9e75c4..f12dea10e 100644 --- a/lib/gssapi/libgssapi-exports.def +++ b/lib/gssapi/libgssapi-exports.def @@ -1,5 +1,6 @@ EXPORTS __gss_c_nt_anonymous_oid_desc DATA + __gss_c_nt_composite_export_oid_desc DATA __gss_c_nt_export_name_oid_desc DATA __gss_c_nt_hostbased_service_oid_desc DATA __gss_c_nt_hostbased_service_x_oid_desc DATA diff --git a/lib/gssapi/mech/gss_import_name.c b/lib/gssapi/mech/gss_import_name.c index f01899db6..92773f037 100644 --- a/lib/gssapi/mech/gss_import_name.c +++ b/lib/gssapi/mech/gss_import_name.c @@ -31,6 +31,7 @@ static OM_uint32 _gss_import_export_name(OM_uint32 *minor_status, const gss_buffer_t input_name_buffer, + const gss_OID name_type, gss_name_t *output_name) { OM_uint32 major_status; @@ -65,6 +66,24 @@ _gss_import_export_name(OM_uint32 *minor_status, p += 2; len -= 2; + /* + * If the name token is a composite token (TOK_ID 0x04 0x02) then per + * RFC6680 everything after that is implementation-specific. This + * mech-glue is pluggable however, so we need the format of the rest of + * the header to be stable, otherwise we couldn't reliably determine + * what mechanism the token is for and we'd have to try all of them. + * + * So... we keep the same format for the exported composite name token + * as for normal exported name tokens (see RFC2743, section 3.2), with + * the TOK_ID 0x04 0x02, but only up to the mechanism OID. We don't + * enforce that there be a NAME_LEN in the exported composite name + * token, or that it match the length of the remainder of the token. + * + * FYI, at least one out-of-tree mechanism implements exported + * composite name tokens as the same as exported name tokens with + * attributes appended and the NAME_LEN not modified to match. + */ + /* * Get the mech length and the name length and sanity * check the size of of the buffer. @@ -107,17 +126,19 @@ _gss_import_export_name(OM_uint32 *minor_status, mech_oid.elements = p; - if (len < t + 4) - return (GSS_S_BAD_NAME); - p += t; - len -= t; + if (!composite) { + if (len < t + 4) + return (GSS_S_BAD_NAME); + p += t; + len -= t; - t = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; - p += 4; - len -= 4; + t = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + p += 4; + len -= 4; - if (!composite && len != t) - return (GSS_S_BAD_NAME); + if (len != t) + return (GSS_S_BAD_NAME); + } m = __gss_get_mechanism(&mech_oid); if (!m || !m->gm_import_name) @@ -127,7 +148,7 @@ _gss_import_export_name(OM_uint32 *minor_status, * Ask the mechanism to import the name. */ major_status = m->gm_import_name(minor_status, - input_name_buffer, GSS_C_NT_EXPORT_NAME, &new_canonical_name); + input_name_buffer, name_type, &new_canonical_name); if (major_status != GSS_S_COMPLETE) { _gss_mg_error(m, *minor_status); return major_status; @@ -156,6 +177,7 @@ _gss_import_export_name(OM_uint32 *minor_status, * - GSS_C_NT_USER_NAME * - GSS_C_NT_HOSTBASED_SERVICE * - GSS_C_NT_EXPORT_NAME + * - GSS_C_NT_COMPOSITE_EXPORT * - GSS_C_NT_ANONYMOUS * - GSS_KRB5_NT_PRINCIPAL_NAME * @@ -202,9 +224,10 @@ gss_import_name(OM_uint32 *minor_status, * the mechanism and then import it as an MN. See RFC 2743 * section 3.2 for a description of the format. */ - if (gss_oid_equal(name_type, GSS_C_NT_EXPORT_NAME)) { - return _gss_import_export_name(minor_status, - input_name_buffer, output_name); + if (gss_oid_equal(name_type, GSS_C_NT_EXPORT_NAME) || + gss_oid_equal(name_type, GSS_C_NT_COMPOSITE_EXPORT)) { + return _gss_import_export_name(minor_status, input_name_buffer, + name_type, output_name); } diff --git a/lib/gssapi/test_names.c b/lib/gssapi/test_names.c index e19531350..cc4c86028 100644 --- a/lib/gssapi/test_names.c +++ b/lib/gssapi/test_names.c @@ -43,42 +43,286 @@ #include #include #include +#include #include #include +static void make_composite_name(CompositePrincipal *, gss_name_t *); +static void assert_attr(gss_name_t, const char *, OM_uint32, gss_buffer_t, + const char *, int, int, int); +static void assert_attr_unavail(gss_name_t, const char *); +static void assert_attr_set(gss_name_t, gss_buffer_set_t); + static void -gss_print_errors (int min_stat) +gss_print_errors(OM_uint32 stat, gss_OID mech) { - OM_uint32 new_stat; - OM_uint32 msg_ctx = 0; - gss_buffer_desc status_string; + OM_uint32 junk; + OM_uint32 more = 0; + gss_buffer_desc buf = GSS_C_EMPTY_BUFFER; OM_uint32 ret; + if (mech) { + junk = gss_oid_to_str(&junk, mech, &buf); + if (junk == GSS_S_COMPLETE) + fprintf(stderr, "mech = %.*s\n", (int)buf.length, buf.value); + gss_release_buffer(&junk, &buf); + } do { - ret = gss_display_status (&new_stat, - min_stat, - GSS_C_MECH_CODE, - GSS_C_NO_OID, - &msg_ctx, - &status_string); - if (!GSS_ERROR(ret)) { - fprintf (stderr, "%.*s\n", (int)status_string.length, - (char *)status_string.value); - gss_release_buffer (&new_stat, &status_string); - } - } while (!GSS_ERROR(ret) && msg_ctx != 0); + ret = gss_display_status(&junk, + stat, + mech ? GSS_C_MECH_CODE : GSS_C_GSS_CODE, + mech, + &more, + &buf); + if (ret != GSS_S_COMPLETE) + errx(1, "gss_display_status() failed"); + fprintf(stderr, "%.*s\n", (int)buf.length, (char *)buf.value); + gss_release_buffer(&junk, &buf); + } while (more); } static void -gss_err(int exitval, int status, const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 5, 6))) +gss_err(int exitval, + OM_uint32 maj, + OM_uint32 min, + gss_OID mech, + const char *fmt, ...) { va_list args; va_start(args, fmt); - vwarnx (fmt, args); - gss_print_errors (status); + vwarnx(fmt, args); va_end(args); - exit (exitval); + gss_print_errors(maj, GSS_C_NO_OID); + if (mech) + gss_print_errors(min, mech); + exit(exitval); +} + +#define MAKE_URN(tail) \ + { sizeof(GSS_KRB5_NAME_ATTRIBUTE_BASE_URN tail) - 1, \ + GSS_KRB5_NAME_ATTRIBUTE_BASE_URN tail } + +/* + * Test RFC6680 name attributes for Kerberos. + */ +static void +check_name_attrs(void) +{ + CompositePrincipal p; + EncTicketPart *t; + gss_buffer_desc v = GSS_C_EMPTY_BUFFER; + gss_name_t n; + OM_uint32 maj, min; + int32_t ret; + gss_buffer_desc attrs[] = { + MAKE_URN("realm"), + MAKE_URN("name-ncomp"), + MAKE_URN("name-ncomp#0"), + MAKE_URN("peer-realm"), + MAKE_URN("ticket-authz-data"), + MAKE_URN("transit-path"), + MAKE_URN("canonical-name"), + }; /* Set of attributes we expect to see indicated */ + gss_buffer_set_desc attr_set; + size_t i, sz; + + memset(&p, 0, sizeof(p)); + attr_set.elements = attrs; + /* + * attr_set.count is set in each of the following sections to ever more + * items. + */ + + /* + * Testing name attributes is pretty tricky. + * + * Our approach is to construct a composite name, construct an exported + * composite name token for it, import it, then test the gss_inquire_name() + * and gss_get_name_attribute() accessors, and then gss_display_name_ext(). + * + * Ideally we'd test the accessors on names imported from query forms with + * gss_import_name(), and on names from established contexts. However, + * that belongs in the test_context program. + * + * TODO: Implement and test gss_set_name_attribute() and + * gss_delete_name_attribute(). + */ + + /* First construct and test an unauthenticated name */ + p.realm = estrdup("TEST.H5L.SE"); + p.name.name_type = KRB5_NT_PRINCIPAL; + p.name.name_string.val = ecalloc(1, sizeof(p.name.name_string.val[0])); + p.name.name_string.len = 1; + p.name.name_string.val[0] = estrdup("someuser"); + p.nameattrs = NULL; + make_composite_name(&p, &n); + + /* Test the attributes we expect it to have */ + v.length = sizeof("TEST.H5L.SE") - 1; + v.value = "TEST.H5L.SE"; + assert_attr(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "realm", GSS_S_COMPLETE, + &v, "TEST.H5L.SE", 0, 1, 0); + + i = 1; + v.length = sizeof(size_t); + v.value = &i; + assert_attr(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "name-ncomp", + GSS_S_COMPLETE, &v, "1", 0, 1, 0); + + v.length = sizeof("someuser") - 1; + v.value = "someuser"; + assert_attr(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "name-ncomp#0", + GSS_S_COMPLETE, &v, "someuser", 0, 1, 0); + + attr_set.count = 3; + assert_attr_set(n, &attr_set); + + /* Check that it does not have prefixed attributes */ + assert_attr_unavail(n, "whatever " GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "realm"); + assert_attr_unavail(n, "whatever " GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "name-ncomp"); + assert_attr_unavail(n, "whatever " GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "name-ncomp#0"); + assert_attr_unavail(n, "what ever " GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "name-ncomp#0"); + + /* Check that it does not have various other supported attributes */ + assert_attr_unavail(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "peer-realm"); + assert_attr_unavail(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "name-ncomp#1"); + assert_attr_unavail(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "canonical-name"); + assert_attr_unavail(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "ticket-authz-data#pac"); + assert_attr_unavail(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN + "ticket-authz-data"); + assert_attr_unavail(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "transit-path"); + + /* Exercise URN parser */ + assert_attr_unavail(n, "urn:whatever"); + assert_attr_unavail(n, "urn:whatever#"); + assert_attr_unavail(n, "urn:what#ever"); + assert_attr_unavail(n, "#"); + assert_attr_unavail(n, "#whatever"); + assert_attr_unavail(n, "whatever"); + assert_attr_unavail(n, "what ever"); + assert_attr_unavail(n, "what ever#"); + + /* Now test an authenticated name */ + gss_release_name(&min, &n); + p.nameattrs = ecalloc(1, sizeof(p.nameattrs[0])); + p.nameattrs->authenticated = 1; + make_composite_name(&p, &n); + + v.length = sizeof("TEST.H5L.SE") - 1; + v.value = "TEST.H5L.SE"; + assert_attr(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "realm", GSS_S_COMPLETE, + &v, "TEST.H5L.SE", 1, 1, 0); + + i = 1; + v.length = sizeof(size_t); + v.value = &i; + assert_attr(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "name-ncomp", + GSS_S_COMPLETE, &v, "1", 1, 1, 0); + + v.length = sizeof("someuser") - 1; + v.value = "someuser"; + assert_attr(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "name-ncomp#0", + GSS_S_COMPLETE, &v, "someuser", 1, 1, 0); + + assert_attr_set(n, &attr_set); + + /* Now add a peer realm */ + gss_release_name(&min, &n); + p.nameattrs->peer_realm = ecalloc(1, sizeof(p.nameattrs->peer_realm[0])); + p.nameattrs->peer_realm[0] = estrdup("FOO.TEST.H5L.SE"); + make_composite_name(&p, &n); + + v.length = sizeof("FOO.TEST.H5L.SE") - 1; + v.value = "FOO.TEST.H5L.SE"; + assert_attr(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "peer-realm", + GSS_S_COMPLETE, &v, "FOO.TEST.H5L.SE", 1, 1, 0); + attr_set.count = 4; + assert_attr_set(n, &attr_set); + + /* Now add canonical name and an authz-data element */ + gss_release_name(&min, &n); + p.nameattrs->source = ecalloc(1, sizeof(p.nameattrs->source[0])); + p.nameattrs->source->element = choice_PrincipalNameAttrSrc_enc_ticket_part; + + t = &p.nameattrs->source->u.enc_ticket_part; + t->cname.name_type = KRB5_NT_PRINCIPAL; + t->cname.name_string.val = ecalloc(1, sizeof(t->cname.name_string.val[0])); + t->crealm = estrdup("TEST.H5L.SE"); + t->cname.name_string.len = 1; + t->cname.name_string.val[0] = estrdup("realusername"); + t->authorization_data = ecalloc(1, sizeof(t->authorization_data[0])); + t->authorization_data->val = + ecalloc(1, sizeof(t->authorization_data->val[0])); + t->authorization_data->len = 1; + t->authorization_data->val[0].ad_type = + KRB5_AUTHDATA_ON_BEHALF_OF; /* whatever */ + t->authorization_data->val[0].ad_data.data = + estrdup("foobar@TEST.H5L.SE"); + t->authorization_data->val[0].ad_data.length = + sizeof("foobar@TEST.H5L.SE") - 1; + make_composite_name(&p, &n); + + assert_attr(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "canonical-name", + GSS_S_COMPLETE, GSS_C_NO_BUFFER, "realusername@TEST.H5L.SE", 1, + 1, 0); + + ASN1_MALLOC_ENCODE(AuthorizationData, v.value, v.length, + t->authorization_data, &sz, ret); + if (ret) + errx(1, "Failed to encode AuthorizationData"); + + assert_attr(n, GSS_KRB5_NAME_ATTRIBUTE_BASE_URN "ticket-authz-data", + GSS_S_COMPLETE, &v, NULL, 0, 1, 0); + free(v.value); + + attr_set.count = 7; + assert_attr_set(n, &attr_set); + + gss_release_name(&min, &n); + free_CompositePrincipal(&p); + + /* + * Test gss_display_name_ext() with a host-based service principal + * "host/somehost.test.h5l.se@TEST.H5L.SE". + * + * Where gss_display_name() would display this as a Kerberos principal + * name, gss_display_name_ext() with GSS_C_NT_HOSTBASED_SERVICE should + * display it as "host@somehost.test.h5l.se". + */ + p.realm = estrdup("TEST.H5L.SE"); + p.name.name_type = KRB5_NT_SRV_HST; + p.name.name_string.val = ecalloc(2, sizeof(p.name.name_string.val[0])); + p.name.name_string.len = 2; + p.name.name_string.val[0] = estrdup("host"); + p.name.name_string.val[1] = estrdup("somehost.test.h5l.se"); + p.nameattrs = NULL; + make_composite_name(&p, &n); + + maj = gss_display_name_ext(&min, n, GSS_C_NT_HOSTBASED_SERVICE, &v); + if (maj) + gss_err(1, maj, min, GSS_KRB5_MECHANISM, "display name ext"); + if (v.length != sizeof("host@somehost.test.h5l.se") - 1 || + strncmp(v.value, "host@somehost.test.h5l.se", v.length) != 0) + errx(1, "display name ext"); + gss_release_buffer(&min, &v); + gss_release_name(&min, &n); + free_CompositePrincipal(&p); + + /* + * TODO: + * + * - test URN fragments for access to specific authorization data element + * types + * - test GSS_C_ATTR_LOCAL_LOGIN_USER support (requires configuration or + * that we register a plugin here) + */ } static int version_flag = 0; @@ -145,7 +389,7 @@ main(int argc, char **argv) GSS_C_NT_HOSTBASED_SERVICE, &name); if (maj_stat != GSS_S_COMPLETE) - gss_err(1, min_stat, "import name error"); + gss_err(1, maj_stat, min_stat, GSS_C_NO_OID, "import name error"); free(str); if (anon_flag) @@ -158,13 +402,13 @@ main(int argc, char **argv) mech_oid, &MNname); if (maj_stat != GSS_S_COMPLETE) - gss_err(1, min_stat, "canonicalize name error"); + gss_err(1, maj_stat, min_stat, mech_oid, "canonicalize name error"); maj_stat = gss_export_name(&min_stat, MNname, &name_buffer); if (maj_stat != GSS_S_COMPLETE) - gss_err(1, min_stat, "export name error (KRB5)"); + gss_err(1, maj_stat, min_stat, mech_oid, "export name error"); /* * Import the exported name and compare @@ -174,13 +418,13 @@ main(int argc, char **argv) GSS_C_NT_EXPORT_NAME, &MNname2); if (maj_stat != GSS_S_COMPLETE) - gss_err(1, min_stat, "import name error (exported KRB5 name)"); + gss_err(1, maj_stat, min_stat, mech_oid, "export name error"); maj_stat = gss_compare_name(&min_stat, MNname, MNname2, &equal); if (maj_stat != GSS_S_COMPLETE) - errx(1, "gss_compare_name"); - if (equal == anon_flag) + gss_err(1, maj_stat, min_stat, mech_oid, "compare name error"); + if (equal && anon_flag) errx(1, "names %s equal", anon_flag ? "incorrectly" : "not"); gss_release_name(&min_stat, &MNname2); @@ -205,13 +449,13 @@ main(int argc, char **argv) GSS_C_NO_OID, &name); if (maj_stat != GSS_S_COMPLETE) - gss_err(1, min_stat, "import (no oid) name error"); + gss_err(1, maj_stat, min_stat, NULL, "import (no oid) name error"); maj_stat = gss_import_name(&min_stat, &name_buffer, GSS_KRB5_NT_USER_NAME, &MNname); if (maj_stat != GSS_S_COMPLETE) - gss_err(1, min_stat, "import (krb5 mn) name error"); + gss_err(1, maj_stat, min_stat, NULL, "import (krb5 mn) name error"); free(str); @@ -230,14 +474,16 @@ main(int argc, char **argv) GSS_SPNEGO_MECHANISM, &MNname); if (maj_stat != GSS_S_COMPLETE) - gss_err(1, min_stat, "canonicalize name error"); + gss_err(1, maj_stat, min_stat, GSS_SPNEGO_MECHANISM, + "canonicalize name error"); maj_stat = gss_export_name(&maj_stat, MNname, &name_buffer); if (maj_stat != GSS_S_COMPLETE) - gss_err(1, min_stat, "export name error (SPNEGO)"); + gss_err(1, maj_stat, min_stat, GSS_SPNEGO_MECHANISM, + "export name error (SPNEGO)"); gss_release_name(&min_stat, &MNname); gss_release_buffer(&min_stat, &name_buffer); @@ -253,29 +499,177 @@ main(int argc, char **argv) maj_stat = gss_import_name(&min_stat, &name_buffer, GSS_C_NT_ANONYMOUS, &name); if (maj_stat != GSS_S_COMPLETE) - gss_err(1, min_stat, "import (anon) name error"); + gss_err(1, maj_stat, min_stat, GSS_C_NO_OID, + "import (anon) name error"); maj_stat = gss_canonicalize_name(&min_stat, name, GSS_SANON_X25519_MECHANISM, &MNname); if (maj_stat != GSS_S_COMPLETE) - gss_err(1, min_stat, "canonicalize (anon) name error"); + gss_err(1, maj_stat, min_stat, GSS_SANON_X25519_MECHANISM, + "canonicalize (anon) name error"); maj_stat = gss_display_name(&min_stat, MNname, &name_buffer, &name_type); if (maj_stat != GSS_S_COMPLETE) - gss_err(1, min_stat, "display_name (anon) name error"); + gss_err(1, maj_stat, min_stat, GSS_SANON_X25519_MECHANISM, + "display_name (anon) name error"); if (!gss_oid_equal(name_type, GSS_C_NT_ANONYMOUS)) - gss_err(1, 0, "display name type not anonymous"); + errx(1, "display name type not anonymous"); if (memcmp(name_buffer.value, "WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS", sizeof("WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS") - 1) != 0) - gss_err(1, 0, "display name string not well known anonymous name"); + errx(1, "display name string not well known anonymous name"); gss_release_name(&min_stat, &MNname); gss_release_name(&min_stat, &name); gss_release_buffer(&min_stat, &name_buffer); } + check_name_attrs(); return 0; } + +/* Copied from _gsskrb5_export_name_composite() */ +static void +export_name_composite(CompositePrincipal *name, gss_buffer_t exported_name) +{ + gss_buffer_desc inner = GSS_C_EMPTY_BUFFER; + unsigned char *buf; + int32_t ret; + size_t sz; + + ASN1_MALLOC_ENCODE(CompositePrincipal, inner.value, inner.length, + (void *)name, &sz, ret); + if (ret) + errx(1, "Failed to encode exported composite name token"); + + exported_name->length = 10 + inner.length + GSS_KRB5_MECHANISM->length; + exported_name->value = malloc(exported_name->length); + if (exported_name->value == NULL) + errx(1, "Failed to allocate exported composite name token"); + + /* TOK, MECH_OID_LEN, DER(MECH_OID), NAME_LEN, NAME */ + + buf = exported_name->value; + buf[0] = 0x04; + buf[1] = 0x02; + buf[2] = ((GSS_KRB5_MECHANISM->length + 2) >> 8) & 0xff; + buf[3] = (GSS_KRB5_MECHANISM->length + 2) & 0xff; + buf[4] = 0x06; + buf[5] = (GSS_KRB5_MECHANISM->length) & 0xFF; + + memcpy(buf + 6, GSS_KRB5_MECHANISM->elements, GSS_KRB5_MECHANISM->length); + buf += 6 + GSS_KRB5_MECHANISM->length; + + buf[0] = (inner.length >> 24) & 0xff; + buf[1] = (inner.length >> 16) & 0xff; + buf[2] = (inner.length >> 8) & 0xff; + buf[3] = (inner.length) & 0xff; + buf += 4; + + memcpy(buf, inner.value, inner.length); + free(inner.value); +} + +static void +make_composite_name(CompositePrincipal *princ, gss_name_t *n) +{ + gss_buffer_desc token, exported; + OM_uint32 maj, min; + + export_name_composite(princ, &token); + maj = gss_import_name(&min, &token, GSS_C_NT_COMPOSITE_EXPORT, n); + if (maj) + gss_err(1, maj, min, GSS_KRB5_MECHANISM, "import composite name"); + maj = gss_export_name_composite(&min, *n, &exported); + if (maj) + gss_err(1, maj, min, GSS_KRB5_MECHANISM, "export composite name"); + if (token.length != exported.length || + memcmp(token.value, exported.value, token.length) != 0) + errx(1, "import/export composite token disagreement"); + gss_release_buffer(&min, &exported); + free(token.value); /* Use free because we allocated this one */ +} + +static void +assert_attr(gss_name_t n, + const char *aname, + OM_uint32 exp_maj, + gss_buffer_t exp_v, + const char *exp_dv, + int exp_authenticated, + int exp_complete, + int exp_multivalued) +{ + gss_buffer_desc dv = GSS_C_EMPTY_BUFFER; + gss_buffer_desc v = GSS_C_EMPTY_BUFFER; + gss_buffer_desc a; + OM_uint32 maj, min; + int authenticated, complete, more; + + a.value = (void*)(uintptr_t)aname; + a.length = strlen(aname); + more = 0; + maj = gss_get_name_attribute(&min, n, &a, &authenticated, &complete, &v, + &dv, &more); + if (maj != GSS_S_COMPLETE && maj != exp_maj) + gss_err(1, maj, min, GSS_KRB5_MECHANISM, + "import composite name error"); + if (maj == GSS_S_COMPLETE && maj != exp_maj) + errx(1, "unexpected name attribute %s", aname); + if (maj == GSS_S_COMPLETE) { + if (exp_v && + (v.length != exp_v->length || + memcmp(v.value, exp_v->value, exp_v->length) != 0)) + errx(1, "import composite name: wrong %s value", aname); + if (exp_dv && + (dv.length != strlen(exp_dv) || + strncmp(dv.value, exp_dv, dv.length) != 0)) + errx(1, "import composite name: wrong %s display value " + "(wanted %s, got %.*s)", aname, exp_dv, + (int)dv.length, (char *)dv.value); + if (authenticated != exp_authenticated) + errx(1, "import composite name: %s incorrectly marked " + "%sauthenticated", aname, authenticated ? "" : "un"); + if (complete != exp_complete) + errx(1, "import composite name: %s incorrectly marked " + "%scomplete", aname, complete ? "" : "in"); + if (more != exp_multivalued) + errx(1, "import composite name: %s incorrectly marked " + "%s-valued", aname, more ? "multi" : "single"); + } + gss_release_buffer(&min, &dv); + gss_release_buffer(&min, &v); +} + +static void +assert_attr_unavail(gss_name_t n, const char *aname) +{ + assert_attr(n, aname, GSS_S_UNAVAILABLE, GSS_C_NO_BUFFER, NULL, 0, 0, 0); +} + +static void +assert_attr_set(gss_name_t n, gss_buffer_set_t exp_as) +{ + OM_uint32 maj, min; + gss_buffer_set_t as = NULL; + gss_OID MN_mech = GSS_C_NO_OID; + size_t i; + int name_is_MN = 0; + + maj = gss_inquire_name(&min, n, &name_is_MN, &MN_mech, &as); + if (maj) + gss_err(1, maj, min, MN_mech, "inquire name"); + for (i = 0; i < as->count && i < exp_as->count; i++) { + if (as->elements[i].length != exp_as->elements[i].length || + memcmp(as->elements[i].value, exp_as->elements[i].value, + as->elements[i].length) != 0) + errx(1, "attribute sets differ"); + } + if (i < as->count) + errx(1, "more attributes indicated than expected"); + if (i < exp_as->count) + errx(1, "fewer attributes indicated than expected"); + gss_release_buffer_set(&min, &as); +} diff --git a/lib/gssapi/version-script.map b/lib/gssapi/version-script.map index be266da77..66e1b8dd7 100644 --- a/lib/gssapi/version-script.map +++ b/lib/gssapi/version-script.map @@ -4,6 +4,7 @@ HEIMDAL_GSS_2.0 { global: # __gss_c_nt_anonymous; __gss_c_nt_anonymous_oid_desc; + __gss_c_nt_composite_export_oid_desc; __gss_c_nt_export_name_oid_desc; __gss_c_nt_hostbased_service_oid_desc; __gss_c_nt_hostbased_service_x_oid_desc;