aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Howard <lukeh@padl.com>2018-12-26 22:52:18 +1100
committerGreg Hudson <ghudson@mit.edu>2019-12-06 13:03:18 -0500
commitc2ca2f26eaf817a6a7ed42257c380437ab802bd9 (patch)
tree5b3ce2dfb4d98bb64370ff1c11f08c71b8772de3
parent996353767fe8afa7f67a3b5b465e4d70e18bad7c (diff)
downloadkrb5-c2ca2f26eaf817a6a7ed42257c380437ab802bd9.zip
krb5-c2ca2f26eaf817a6a7ed42257c380437ab802bd9.tar.gz
krb5-c2ca2f26eaf817a6a7ed42257c380437ab802bd9.tar.bz2
Implement NegoEx
Implement draft-zhu-negoex. Mechanisms supporting the NegoEx GSS extensions will be negotiated only through NegoEx, unless they assert the GSS_C_MA_NEGOEX_AND_SPNEGO mech attribute, in which case they may also be negotiated directly via SPNEGO. ticket: 8851
-rw-r--r--doc/plugindev/gssapi.rst25
-rw-r--r--src/include/k5-trace.h5
-rw-r--r--src/lib/gssapi/generic/gssapi_ext.h45
-rw-r--r--src/lib/gssapi/generic/gssapi_generic.c133
-rw-r--r--src/lib/gssapi/libgssapi_krb5.exports3
-rw-r--r--src/lib/gssapi/mechglue/Makefile.in4
-rw-r--r--src/lib/gssapi/mechglue/deps17
-rw-r--r--src/lib/gssapi/mechglue/g_initialize.c4
-rw-r--r--src/lib/gssapi/mechglue/g_negoex.c237
-rw-r--r--src/lib/gssapi/mechglue/mglueP.h31
-rw-r--r--src/lib/gssapi/spnego/Makefile.in7
-rw-r--r--src/lib/gssapi/spnego/deps36
-rw-r--r--src/lib/gssapi/spnego/gssapiP_negoex.h210
-rw-r--r--src/lib/gssapi/spnego/gssapiP_spnego.h41
-rw-r--r--src/lib/gssapi/spnego/negoex_ctx.c785
-rw-r--r--src/lib/gssapi/spnego/negoex_trace.c121
-rw-r--r--src/lib/gssapi/spnego/negoex_util.c812
-rw-r--r--src/lib/gssapi/spnego/spnego_mech.c407
18 files changed, 2693 insertions, 230 deletions
diff --git a/doc/plugindev/gssapi.rst b/doc/plugindev/gssapi.rst
index 28e62ae..cb1f462 100644
--- a/doc/plugindev/gssapi.rst
+++ b/doc/plugindev/gssapi.rst
@@ -31,6 +31,31 @@ the mechanism's status codes onto unique values, and then map them
back again when **gss_display_status** is called.
+NegoEx modules
+--------------
+
+Some Windows GSSAPI mechanisms can only be negotiated via a Microsoft
+extension to SPNEGO called NegoEx. Beginning with release 1.18,
+mechanism modules can support NegoEx as follows:
+
+* Implement the gssspi_query_meta_data(), gssspi_exchange_meta_data(),
+ and gssspi_query_mechanism_info() SPIs declared in
+ ``<gssapi/gssapi_ext.h>``.
+
+* Implement gss_inquire_sec_context_by_oid() and answer the
+ **GSS_C_INQ_NEGOEX_KEY** and **GSS_C_INQ_NEGOEX_VERIFY_KEY** OIDs
+ to provide the checksum keys for outgoing and incoming checksums,
+ respectively. The answer must be in two buffers: the first buffer
+ contains the key contents, and the second buffer contains the key
+ encryption type as a four-byte little-endian integer.
+
+By default, NegoEx mechanisms will not be directly negotiated via
+SPNEGO. If direct SPNEGO negotiation is required for
+interoperability, implement gss_inquire_attrs_for_mech() and assert
+the GSS_C_MA_NEGOEX_AND_SPNEGO attribute (along with any applicable
+RFC 5587 attributes).
+
+
Interposer modules
------------------
diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h
index d08e5ec..1da53db 100644
--- a/src/include/k5-trace.h
+++ b/src/include/k5-trace.h
@@ -294,6 +294,11 @@ void krb5int_trace(krb5_context context, const char *fmt, ...);
#define TRACE_MSPAC_DISCARD_UNVERF(c) \
TRACE(c, "Filtering out unverified MS PAC")
+#define TRACE_NEGOEX_INCOMING(c, seqnum, typestr, info) \
+ TRACE(c, "NegoEx received [{int}]{str}: {str}", (int)seqnum, typestr, info)
+#define TRACE_NEGOEX_OUTGOING(c, seqnum, typestr, info) \
+ TRACE(c, "NegoEx sending [{int}]{str}: {str}", (int)seqnum, typestr, info)
+
#define TRACE_PREAUTH_CONFLICT(c, name1, name2, patype) \
TRACE(c, "Preauth module {str} conflicts with module {str} for pa " \
"type {patype}", name1, name2, patype)
diff --git a/src/lib/gssapi/generic/gssapi_ext.h b/src/lib/gssapi/generic/gssapi_ext.h
index d3fd2a5..218456e 100644
--- a/src/lib/gssapi/generic/gssapi_ext.h
+++ b/src/lib/gssapi/generic/gssapi_ext.h
@@ -237,6 +237,9 @@ OM_uint32 KRB5_CALLCONV gss_unwrap_aead
*/
GSS_DLLIMP extern gss_OID GSS_C_INQ_SSPI_SESSION_KEY;
+GSS_DLLIMP extern gss_OID GSS_C_INQ_NEGOEX_KEY;
+GSS_DLLIMP extern gss_OID GSS_C_INQ_NEGOEX_VERIFY_KEY;
+
OM_uint32 KRB5_CALLCONV gss_complete_auth_token
(OM_uint32 *minor_status,
const gss_ctx_id_t context_handle,
@@ -578,6 +581,48 @@ gss_store_cred_into(
gss_OID_set *, /* elements_stored */
gss_cred_usage_t *); /* cred_usage_stored */
+/*
+ * A mech can make itself negotiable via NegoEx (draft-zhu-negoex) by
+ * implementing the following three SPIs, and also implementing
+ * gss_inquire_sec_context_by_oid() and answering the GSS_C_INQ_NEGOEX_KEY and
+ * GSS_C_INQ_NEGOEX_VERIFY_KEY OIDs. The answer must be in two buffers: the
+ * first contains the key contents, and the second contains the key enctype as
+ * a four-byte little-endian integer.
+ *
+ * By default, NegoEx mechanisms will not be directly negotiated via SPNEGO.
+ * If direct SPNEGO negotiation is required for interoperability, implement
+ * gss_inquire_attrs_for_mech() and assert the GSS_C_MA_NEGOEX_AND_SPNEGO
+ * attribute (along with any applicable RFC 5587 attributes).
+ */
+
+OM_uint32 KRB5_CALLCONV
+gssspi_query_meta_data(
+ OM_uint32 *minor_status,
+ gss_const_OID mech_oid,
+ gss_cred_id_t cred_handle,
+ gss_ctx_id_t *context_handle,
+ const gss_name_t targ_name,
+ OM_uint32 req_flags,
+ gss_buffer_t meta_data);
+
+OM_uint32 KRB5_CALLCONV
+gssspi_exchange_meta_data(
+ OM_uint32 *minor_status,
+ gss_const_OID mech_oid,
+ gss_cred_id_t cred_handle,
+ gss_ctx_id_t *context_handle,
+ const gss_name_t targ_name,
+ OM_uint32 req_flags,
+ gss_const_buffer_t meta_data);
+
+OM_uint32 KRB5_CALLCONV
+gssspi_query_mechanism_info(
+ OM_uint32 *minor_status,
+ gss_const_OID mech_oid,
+ unsigned char auth_scheme[16]);
+
+GSS_DLLIMP extern gss_const_OID GSS_C_MA_NEGOEX_AND_SPNEGO;
+
#ifdef __cplusplus
}
#endif
diff --git a/src/lib/gssapi/generic/gssapi_generic.c b/src/lib/gssapi/generic/gssapi_generic.c
index 1b362c3..3601585 100644
--- a/src/lib/gssapi/generic/gssapi_generic.c
+++ b/src/lib/gssapi/generic/gssapi_generic.c
@@ -128,6 +128,10 @@ static const gss_OID_desc const_oids[] = {
*/
/* GSS_C_INQ_SSPI_SESSION_KEY 1.2.840.113554.1.2.2.5.5 */
{11, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x05"},
+ /* GSS_C_INQ_NEGOEX_KEY 1.2.840.113554.1.2.2.5.16 */
+ {11, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x10"},
+ /* GSS_C_INQ_NEGOEX_VERIFY_KEY 1.2.840.113554.1.2.2.5.17 */
+ {11, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x11"},
/* RFC 5587 attributes, see below */
{7, (void *)"\x2b\x06\x01\x05\x05\x0d\x01"},
@@ -157,6 +161,8 @@ static const gss_OID_desc const_oids[] = {
{7, (void *)"\x2b\x06\x01\x05\x05\x0d\x19"},
{7, (void *)"\x2b\x06\x01\x05\x05\x0d\x1a"},
{7, (void *)"\x2b\x06\x01\x05\x05\x0d\x1b"},
+ /* GSS_C_MA_NEGOEX_AND_SPNEGO 1.2.840.113554.1.2.2.5.18 */
+ {11, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x12"},
/*
* GSS_SEC_CONTEXT_SASL_SSF_OID 1.2.840.113554.1.2.2.5.15
@@ -194,40 +200,43 @@ GSS_DLLIMP gss_OID GSS_C_NT_EXPORT_NAME = oids+6;
gss_OID gss_nt_exported_name = oids+6;
GSS_DLLIMP gss_OID GSS_C_NT_COMPOSITE_EXPORT = oids+7;
-
GSS_DLLIMP gss_OID GSS_C_INQ_SSPI_SESSION_KEY = oids+8;
+GSS_DLLIMP gss_OID GSS_C_INQ_NEGOEX_KEY = oids+9;
+GSS_DLLIMP gss_OID GSS_C_INQ_NEGOEX_VERIFY_KEY = oids+10;
+
+GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_CONCRETE = oids+11;
+GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_PSEUDO = oids+12;
+GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_COMPOSITE = oids+13;
+GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_NEGO = oids+14;
+GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_GLUE = oids+15;
+GSS_DLLIMP gss_const_OID GSS_C_MA_NOT_MECH = oids+16;
+GSS_DLLIMP gss_const_OID GSS_C_MA_DEPRECATED = oids+17;
+GSS_DLLIMP gss_const_OID GSS_C_MA_NOT_DFLT_MECH = oids+18;
+GSS_DLLIMP gss_const_OID GSS_C_MA_ITOK_FRAMED = oids+19;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT = oids+20;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG = oids+21;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT_INIT = oids+22;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG_INIT = oids+23;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT_ANON = oids+24;
+GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG_ANON = oids+25;
+GSS_DLLIMP gss_const_OID GSS_C_MA_DELEG_CRED = oids+26;
+GSS_DLLIMP gss_const_OID GSS_C_MA_INTEG_PROT = oids+27;
+GSS_DLLIMP gss_const_OID GSS_C_MA_CONF_PROT = oids+28;
+GSS_DLLIMP gss_const_OID GSS_C_MA_MIC = oids+29;
+GSS_DLLIMP gss_const_OID GSS_C_MA_WRAP = oids+30;
+GSS_DLLIMP gss_const_OID GSS_C_MA_PROT_READY = oids+31;
+GSS_DLLIMP gss_const_OID GSS_C_MA_REPLAY_DET = oids+32;
+GSS_DLLIMP gss_const_OID GSS_C_MA_OOS_DET = oids+33;
+GSS_DLLIMP gss_const_OID GSS_C_MA_CBINDINGS = oids+34;
+GSS_DLLIMP gss_const_OID GSS_C_MA_PFS = oids+35;
+GSS_DLLIMP gss_const_OID GSS_C_MA_COMPRESS = oids+36;
+GSS_DLLIMP gss_const_OID GSS_C_MA_CTX_TRANS = oids+37;
+GSS_DLLIMP gss_const_OID GSS_C_MA_NEGOEX_AND_SPNEGO = oids+38;
+
+GSS_DLLIMP gss_OID GSS_C_SEC_CONTEXT_SASL_SSF = oids+39;
+
+static gss_OID_set_desc gss_ma_known_attrs_desc = { 28, oids+11 };
-GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_CONCRETE = oids+9;
-GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_PSEUDO = oids+10;
-GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_COMPOSITE = oids+11;
-GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_NEGO = oids+12;
-GSS_DLLIMP gss_const_OID GSS_C_MA_MECH_GLUE = oids+13;
-GSS_DLLIMP gss_const_OID GSS_C_MA_NOT_MECH = oids+14;
-GSS_DLLIMP gss_const_OID GSS_C_MA_DEPRECATED = oids+15;
-GSS_DLLIMP gss_const_OID GSS_C_MA_NOT_DFLT_MECH = oids+16;
-GSS_DLLIMP gss_const_OID GSS_C_MA_ITOK_FRAMED = oids+17;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT = oids+18;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG = oids+19;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT_INIT = oids+20;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG_INIT = oids+21;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_INIT_ANON = oids+22;
-GSS_DLLIMP gss_const_OID GSS_C_MA_AUTH_TARG_ANON = oids+23;
-GSS_DLLIMP gss_const_OID GSS_C_MA_DELEG_CRED = oids+24;
-GSS_DLLIMP gss_const_OID GSS_C_MA_INTEG_PROT = oids+25;
-GSS_DLLIMP gss_const_OID GSS_C_MA_CONF_PROT = oids+26;
-GSS_DLLIMP gss_const_OID GSS_C_MA_MIC = oids+27;
-GSS_DLLIMP gss_const_OID GSS_C_MA_WRAP = oids+28;
-GSS_DLLIMP gss_const_OID GSS_C_MA_PROT_READY = oids+29;
-GSS_DLLIMP gss_const_OID GSS_C_MA_REPLAY_DET = oids+30;
-GSS_DLLIMP gss_const_OID GSS_C_MA_OOS_DET = oids+31;
-GSS_DLLIMP gss_const_OID GSS_C_MA_CBINDINGS = oids+32;
-GSS_DLLIMP gss_const_OID GSS_C_MA_PFS = oids+33;
-GSS_DLLIMP gss_const_OID GSS_C_MA_COMPRESS = oids+34;
-GSS_DLLIMP gss_const_OID GSS_C_MA_CTX_TRANS = oids+35;
-
-GSS_DLLIMP gss_OID GSS_C_SEC_CONTEXT_SASL_SSF = oids+36;
-
-static gss_OID_set_desc gss_ma_known_attrs_desc = { 27, oids+9 };
gss_OID_set gss_ma_known_attrs = &gss_ma_known_attrs_desc;
static struct mech_attr_info_desc {
@@ -237,170 +246,176 @@ static struct mech_attr_info_desc {
const char *long_desc;
} mech_attr_info[] = {
{
- oids+9,
+ oids+11,
"GSS_C_MA_MECH_CONCRETE",
"concrete-mech",
"Mechanism is neither a pseudo-mechanism nor a composite mechanism.",
},
{
- oids+10,
+ oids+12,
"GSS_C_MA_MECH_PSEUDO",
"pseudo-mech",
"Mechanism is a pseudo-mechanism.",
},
{
- oids+11,
+ oids+13,
"GSS_C_MA_MECH_COMPOSITE",
"composite-mech",
"Mechanism is a composite of other mechanisms.",
},
{
- oids+12,
+ oids+14,
"GSS_C_MA_MECH_NEGO",
"mech-negotiation-mech",
"Mechanism negotiates other mechanisms.",
},
{
- oids+13,
+ oids+15,
"GSS_C_MA_MECH_GLUE",
"mech-glue",
"OID is not a mechanism but the GSS-API itself.",
},
{
- oids+14,
+ oids+16,
"GSS_C_MA_NOT_MECH",
"not-mech",
"Known OID but not a mechanism OID.",
},
{
- oids+15,
+ oids+17,
"GSS_C_MA_DEPRECATED",
"mech-deprecated",
"Mechanism is deprecated.",
},
{
- oids+16,
+ oids+18,
"GSS_C_MA_NOT_DFLT_MECH",
"mech-not-default",
"Mechanism must not be used as a default mechanism.",
},
{
- oids+17,
+ oids+19,
"GSS_C_MA_ITOK_FRAMED",
"initial-is-framed",
"Mechanism's initial contexts are properly framed.",
},
{
- oids+18,
+ oids+20,
"GSS_C_MA_AUTH_INIT",
"auth-init-princ",
"Mechanism supports authentication of initiator to acceptor.",
},
{
- oids+19,
+ oids+21,
"GSS_C_MA_AUTH_TARG",
"auth-targ-princ",
"Mechanism supports authentication of acceptor to initiator.",
},
{
- oids+20,
+ oids+22,
"GSS_C_MA_AUTH_INIT_INIT",
"auth-init-princ-initial",
"Mechanism supports authentication of initiator using "
"initial credentials.",
},
{
- oids+21,
+ oids+23,
"GSS_C_MA_AUTH_TARG_INIT",
"auth-target-princ-initial",
"Mechanism supports authentication of acceptor using "
"initial credentials.",
},
{
- oids+22,
+ oids+24,
"GSS_C_MA_AUTH_INIT_ANON",
"auth-init-princ-anon",
"Mechanism supports GSS_C_NT_ANONYMOUS as an initiator name.",
},
{
- oids+23,
+ oids+25,
"GSS_C_MA_AUTH_TARG_ANON",
"auth-targ-princ-anon",
"Mechanism supports GSS_C_NT_ANONYMOUS as an acceptor name.",
},
{
- oids+24,
+ oids+26,
"GSS_C_MA_DELEG_CRED",
"deleg-cred",
"Mechanism supports credential delegation.",
},
{
- oids+25,
+ oids+27,
"GSS_C_MA_INTEG_PROT",
"integ-prot",
"Mechanism supports per-message integrity protection.",
},
{
- oids+26,
+ oids+28,
"GSS_C_MA_CONF_PROT",
"conf-prot",
"Mechanism supports per-message confidentiality protection.",
},
{
- oids+27,
+ oids+29,
"GSS_C_MA_MIC",
"mic",
"Mechanism supports Message Integrity Code (MIC) tokens.",
},
{
- oids+28,
+ oids+30,
"GSS_C_MA_WRAP",
"wrap",
"Mechanism supports wrap tokens.",
},
{
- oids+29,
+ oids+31,
"GSS_C_MA_PROT_READY",
"prot-ready",
"Mechanism supports per-message proteciton prior to "
"full context establishment.",
},
{
- oids+30,
+ oids+32,
"GSS_C_MA_REPLAY_DET",
"replay-detection",
"Mechanism supports replay detection.",
},
{
- oids+31,
+ oids+33,
"GSS_C_MA_OOS_DET",
"oos-detection",
"Mechanism supports out-of-sequence detection.",
},
{
- oids+32,
+ oids+34,
"GSS_C_MA_CBINDINGS",
"channel-bindings",
"Mechanism supports channel bindings.",
},
{
- oids+33,
+ oids+35,
"GSS_C_MA_PFS",
"pfs",
"Mechanism supports Perfect Forward Security.",
},
{
- oids+34,
+ oids+36,
"GSS_C_MA_COMPRESS",
"compress",
"Mechanism supports compression of data inputs to gss_wrap().",
},
{
- oids+35,
+ oids+37,
"GSS_C_MA_CTX_TRANS",
"context-transfer",
"Mechanism supports security context export/import.",
},
+ {
+ oids+38,
+ "GSS_C_MA_NEGOEX_AND_SPNEGO",
+ "negoex-only",
+ "NegoEx mechanism should also be negotiable through SPNEGO.",
+ },
};
OM_uint32
diff --git a/src/lib/gssapi/libgssapi_krb5.exports b/src/lib/gssapi/libgssapi_krb5.exports
index c292cb1..166acfa 100644
--- a/src/lib/gssapi/libgssapi_krb5.exports
+++ b/src/lib/gssapi/libgssapi_krb5.exports
@@ -1,5 +1,7 @@
GSS_C_ATTR_LOCAL_LOGIN_USER
GSS_C_INQ_SSPI_SESSION_KEY
+GSS_C_INQ_NEGOEX_KEY
+GSS_C_INQ_NEGOEX_VERIFY_KEY
GSS_C_NT_ANONYMOUS
GSS_C_NT_COMPOSITE_EXPORT
GSS_C_NT_EXPORT_NAME
@@ -39,6 +41,7 @@ GSS_C_MA_CBINDINGS
GSS_C_MA_PFS
GSS_C_MA_COMPRESS
GSS_C_MA_CTX_TRANS
+GSS_C_MA_NEGOEX_AND_SPNEGO
GSS_C_SEC_CONTEXT_SASL_SSF
gss_accept_sec_context
gss_acquire_cred
diff --git a/src/lib/gssapi/mechglue/Makefile.in b/src/lib/gssapi/mechglue/Makefile.in
index 5e1ed6d..33d9319 100644
--- a/src/lib/gssapi/mechglue/Makefile.in
+++ b/src/lib/gssapi/mechglue/Makefile.in
@@ -1,6 +1,7 @@
mydir=lib$(S)gssapi$(S)mechglue
BUILDTOP=$(REL)..$(S)..$(S)..
LOCALINCLUDES = -I. -I$(srcdir) -I$(srcdir)/.. -I../generic -I$(srcdir)/../generic -I../krb5 -I$(srcdir)/../krb5 -I../spnego -I$(srcdir)/../spnego
+
DEFINES=-D_GSS_STATIC_LINK=1
##DOSBUILDTOP = ..\..\..
@@ -49,6 +50,7 @@ SRCS = \
$(srcdir)/g_mech_invoke.c \
$(srcdir)/g_mechattr.c \
$(srcdir)/g_mechname.c \
+ $(srcdir)/g_negoex.c \
$(srcdir)/g_oid_ops.c \
$(srcdir)/g_prf.c \
$(srcdir)/g_process_context.c \
@@ -113,6 +115,7 @@ OBJS = \
$(OUTPRE)g_mech_invoke.$(OBJEXT) \
$(OUTPRE)g_mechattr.$(OBJEXT) \
$(OUTPRE)g_mechname.$(OBJEXT) \
+ $(OUTPRE)g_negoex.$(OBJEXT) \
$(OUTPRE)g_oid_ops.$(OBJEXT) \
$(OUTPRE)g_prf.$(OBJEXT) \
$(OUTPRE)g_process_context.$(OBJEXT) \
@@ -177,6 +180,7 @@ STLIBOBJS = \
g_mech_invoke.o \
g_mechattr.o \
g_mechname.o \
+ g_negoex.o \
g_oid_ops.o \
g_prf.o \
g_process_context.o \
diff --git a/src/lib/gssapi/mechglue/deps b/src/lib/gssapi/mechglue/deps
index 26f62aa..95fd7f4 100644
--- a/src/lib/gssapi/mechglue/deps
+++ b/src/lib/gssapi/mechglue/deps
@@ -234,10 +234,11 @@ g_initialize.so g_initialize.po $(OUTPRE)g_initialize.$(OBJEXT): \
$(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(srcdir)/../generic/gssapiP_generic.h \
$(srcdir)/../generic/gssapi_ext.h $(srcdir)/../generic/gssapi_generic.h \
$(srcdir)/../krb5/gssapiP_krb5.h $(srcdir)/../krb5/gssapi_krb5.h \
- $(srcdir)/../spnego/gssapiP_spnego.h $(top_srcdir)/include/k5-buf.h \
- $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
- $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
- $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(srcdir)/../spnego/gssapiP_negoex.h $(srcdir)/../spnego/gssapiP_spnego.h \
+ $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-queue.h \
$(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
$(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
$(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
@@ -324,6 +325,14 @@ g_mechname.so g_mechname.po $(OUTPRE)g_mechname.$(OBJEXT): \
$(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-platform.h \
$(top_srcdir)/include/k5-thread.h ../generic/gssapi_err_generic.h \
g_mechname.c mechglue.h mglueP.h
+g_negoex.so g_negoex.po $(OUTPRE)g_negoex.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/gssapi/gssapi.h \
+ $(BUILDTOP)/include/gssapi/gssapi_alloc.h $(BUILDTOP)/include/gssapi/gssapi_ext.h \
+ $(COM_ERR_DEPS) $(srcdir)/../generic/gssapiP_generic.h \
+ $(srcdir)/../generic/gssapi_ext.h $(srcdir)/../generic/gssapi_generic.h \
+ $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-thread.h ../generic/gssapi_err_generic.h \
+ g_negoex.c mechglue.h mglueP.h
g_oid_ops.so g_oid_ops.po $(OUTPRE)g_oid_ops.$(OBJEXT): \
$(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/gssapi/gssapi.h \
$(BUILDTOP)/include/gssapi/gssapi_alloc.h $(BUILDTOP)/include/gssapi/gssapi_ext.h \
diff --git a/src/lib/gssapi/mechglue/g_initialize.c b/src/lib/gssapi/mechglue/g_initialize.c
index 120d73e..2f9ce7a 100644
--- a/src/lib/gssapi/mechglue/g_initialize.c
+++ b/src/lib/gssapi/mechglue/g_initialize.c
@@ -770,6 +770,10 @@ build_dynamicMech(void *dl, const gss_OID mech_type)
GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_sec_context_by_mech);
GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_name_by_mech);
GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_cred_by_mech);
+ /* draft-zhu-negoex */
+ GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gssspi_query_meta_data);
+ GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gssspi_exchange_meta_data);
+ GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gssspi_query_mechanism_info);
assert(mech_type != GSS_C_NO_OID);
diff --git a/src/lib/gssapi/mechglue/g_negoex.c b/src/lib/gssapi/mechglue/g_negoex.c
new file mode 100644
index 0000000..9360fa4
--- /dev/null
+++ b/src/lib/gssapi/mechglue/g_negoex.c
@@ -0,0 +1,237 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2011 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 contains dispatch functions for the three GSSAPI extensions
+ * described in draft-zhu-negoex-04, renamed to use the gssspi_ prefix. Since
+ * the only caller of these functions is SPNEGO, argument validation is
+ * omitted.
+ */
+
+#include "mglueP.h"
+
+OM_uint32 KRB5_CALLCONV
+gssspi_query_meta_data(OM_uint32 *minor_status, gss_const_OID mech_oid,
+ gss_cred_id_t cred_handle, gss_ctx_id_t *context_handle,
+ const gss_name_t targ_name, OM_uint32 req_flags,
+ gss_buffer_t meta_data)
+{
+ OM_uint32 status, minor;
+ gss_union_ctx_id_t ctx = (gss_union_ctx_id_t)*context_handle;
+ gss_union_cred_t cred = (gss_union_cred_t)cred_handle;
+ gss_union_name_t union_name = (gss_union_name_t)targ_name;
+ gss_mechanism mech;
+ gss_OID selected_mech, public_mech;
+ gss_cred_id_t internal_cred = GSS_C_NO_CREDENTIAL;
+ gss_name_t internal_name = GSS_C_NO_NAME, imported_name = GSS_C_NO_NAME;
+ gss_ctx_id_t new_ctx = GSS_C_NO_CONTEXT, *internal_ctx;
+
+ *minor_status = 0;
+ meta_data->length = 0;
+ meta_data->value = NULL;
+
+ status = gssint_select_mech_type(minor_status, mech_oid, &selected_mech);
+ if (status != GSS_S_COMPLETE)
+ return status;
+ public_mech = gssint_get_public_oid(selected_mech);
+
+ mech = gssint_get_mechanism(selected_mech);
+ if (mech == NULL)
+ return GSS_S_BAD_MECH;
+ if (mech->gssspi_query_meta_data == NULL)
+ return GSS_S_UNAVAILABLE;
+
+ if (cred != NULL) {
+ internal_cred = gssint_get_mechanism_cred(cred, selected_mech);
+ if (internal_cred == GSS_C_NO_CREDENTIAL)
+ return GSS_S_NO_CRED;
+ }
+
+ if (union_name != NULL) {
+ if (union_name->mech_type != GSS_C_NO_OID &&
+ g_OID_equal(union_name->mech_type, selected_mech)) {
+ internal_name = union_name->mech_name;
+ } else {
+ status = gssint_import_internal_name(minor_status, selected_mech,
+ union_name, &imported_name);
+ if (status != GSS_S_COMPLETE)
+ goto cleanup;
+ internal_name = imported_name;
+ }
+ }
+
+ internal_ctx = (ctx != NULL) ? &ctx->internal_ctx_id : &new_ctx;
+ status = mech->gssspi_query_meta_data(minor_status, public_mech,
+ internal_cred, internal_ctx,
+ internal_name, req_flags, meta_data);
+ if (status != GSS_S_COMPLETE) {
+ map_error(minor_status, mech);
+ goto cleanup;
+ }
+
+ /* If the mech created a context, wrap it in a union context. */
+ if (new_ctx != GSS_C_NO_CONTEXT) {
+ assert(ctx == NULL);
+ status = gssint_create_union_context(minor_status, selected_mech,
+ &ctx);
+ if (status != GSS_S_COMPLETE)
+ goto cleanup;
+
+ ctx->internal_ctx_id = new_ctx;
+ new_ctx = GSS_C_NO_CONTEXT;
+ *context_handle = (gss_ctx_id_t)ctx;
+ }
+
+cleanup:
+ if (imported_name != GSS_C_NO_NAME) {
+ (void)gssint_release_internal_name(&minor, selected_mech,
+ &imported_name);
+ }
+ if (new_ctx != GSS_C_NO_CONTEXT) {
+ (void)gssint_delete_internal_sec_context(&minor, &mech->mech_type,
+ &new_ctx, GSS_C_NO_BUFFER);
+ }
+ return status;
+}
+
+OM_uint32 KRB5_CALLCONV
+gssspi_exchange_meta_data(OM_uint32 *minor_status, gss_const_OID mech_oid,
+ gss_cred_id_t cred_handle,
+ gss_ctx_id_t *context_handle,
+ const gss_name_t targ_name, OM_uint32 req_flags,
+ gss_const_buffer_t meta_data)
+{
+ OM_uint32 status, minor;
+ gss_union_ctx_id_t ctx = (gss_union_ctx_id_t)*context_handle;
+ gss_union_cred_t cred = (gss_union_cred_t)cred_handle;
+ gss_union_name_t union_name = (gss_union_name_t)targ_name;
+ gss_mechanism mech;
+ gss_OID selected_mech, public_mech;
+ gss_cred_id_t internal_cred = GSS_C_NO_CREDENTIAL;
+ gss_name_t internal_name = GSS_C_NO_NAME, imported_name = GSS_C_NO_NAME;
+ gss_ctx_id_t new_ctx = GSS_C_NO_CONTEXT, *internal_ctx;
+
+ *minor_status = 0;
+
+ status = gssint_select_mech_type(minor_status, mech_oid, &selected_mech);
+ if (status != GSS_S_COMPLETE)
+ return status;
+ public_mech = gssint_get_public_oid(selected_mech);
+
+ mech = gssint_get_mechanism(selected_mech);
+ if (mech == NULL)
+ return GSS_S_BAD_MECH;
+ if (mech->gssspi_exchange_meta_data == NULL)
+ return GSS_S_UNAVAILABLE;
+
+ if (cred != NULL) {
+ internal_cred = gssint_get_mechanism_cred(cred, selected_mech);
+ if (internal_cred == GSS_C_NO_CREDENTIAL)
+ return GSS_S_NO_CRED;
+ }
+
+ if (union_name != NULL) {
+ if (union_name->mech_type != GSS_C_NO_OID &&
+ g_OID_equal(union_name->mech_type, selected_mech)) {
+ internal_name = union_name->mech_name;
+ } else {
+ status = gssint_import_internal_name(minor_status, selected_mech,
+ union_name, &imported_name);
+ if (GSS_ERROR(status))
+ return status;
+ internal_name = imported_name;
+ }
+ }
+
+ internal_ctx = (ctx != NULL) ? &ctx->internal_ctx_id : &new_ctx;
+ status = mech->gssspi_exchange_meta_data(minor_status, public_mech,
+ internal_cred, internal_ctx,
+ internal_name, req_flags,
+ meta_data);
+ if (status != GSS_S_COMPLETE) {
+ map_error(minor_status, mech);
+ goto cleanup;
+ }
+
+ /* If the mech created a context, wrap it in a union context. */
+ if (new_ctx != GSS_C_NO_CONTEXT) {
+ assert(ctx == NULL);
+ status = gssint_create_union_context(minor_status, selected_mech,
+ &ctx);
+ if (status != GSS_S_COMPLETE)
+ goto cleanup;
+
+ ctx->internal_ctx_id = new_ctx;
+ new_ctx = GSS_C_NO_CONTEXT;
+ *context_handle = (gss_ctx_id_t)ctx;
+ }
+
+cleanup:
+ if (imported_name != GSS_C_NO_NAME) {
+ (void)gssint_release_internal_name(&minor, selected_mech,
+ &imported_name);
+ }
+ if (new_ctx != GSS_C_NO_CONTEXT) {
+ (void)gssint_delete_internal_sec_context(&minor, &mech->mech_type,
+ &new_ctx, GSS_C_NO_BUFFER);
+ }
+ return status;
+}
+
+OM_uint32 KRB5_CALLCONV
+gssspi_query_mechanism_info(OM_uint32 *minor_status, gss_const_OID mech_oid,
+ unsigned char auth_scheme[16])
+{
+ OM_uint32 status;
+ gss_OID selected_mech, public_mech;
+ gss_mechanism mech;
+
+ *minor_status = 0;
+ memset(auth_scheme, 0, 16);
+
+ status = gssint_select_mech_type(minor_status, mech_oid, &selected_mech);
+ if (status != GSS_S_COMPLETE)
+ return status;
+ public_mech = gssint_get_public_oid(selected_mech);
+
+ mech = gssint_get_mechanism(selected_mech);
+ if (mech == NULL)
+ return GSS_S_BAD_MECH;
+ if (mech->gssspi_query_mechanism_info == NULL)
+ return GSS_S_UNAVAILABLE;
+
+ status = mech->gssspi_query_mechanism_info(minor_status, public_mech,
+ auth_scheme);
+ if (GSS_ERROR(status))
+ map_error(minor_status, mech);
+
+ return status;
+}
diff --git a/src/lib/gssapi/mechglue/mglueP.h b/src/lib/gssapi/mechglue/mglueP.h
index c296354..f16333c 100644
--- a/src/lib/gssapi/mechglue/mglueP.h
+++ b/src/lib/gssapi/mechglue/mglueP.h
@@ -702,6 +702,37 @@ typedef struct gss_config {
int /* iov_count */
);
+ /* NegoEx extensions added in 1.18 */
+
+ OM_uint32 (KRB5_CALLCONV *gssspi_query_meta_data)
+ (
+ OM_uint32 *, /* minor_status */
+ gss_const_OID, /* mech_oid */
+ gss_cred_id_t, /* cred_handle */
+ gss_ctx_id_t *, /* context_handle */
+ const gss_name_t, /* targ_name */
+ OM_uint32, /* req_flags */
+ gss_buffer_t /* meta_data */
+ /* */);
+
+ OM_uint32 (KRB5_CALLCONV *gssspi_exchange_meta_data)
+ (
+ OM_uint32 *, /* minor_status */
+ gss_const_OID, /* mech_oid */
+ gss_cred_id_t, /* cred_handle */
+ gss_ctx_id_t *, /* context_handle */
+ const gss_name_t, /* targ_name */
+ OM_uint32, /* req_flags */
+ gss_const_buffer_t /* meta_data */
+ /* */);
+
+ OM_uint32 (KRB5_CALLCONV *gssspi_query_mechanism_info)
+ (
+ OM_uint32 *, /* minor_status */
+ gss_const_OID, /* mech_oid */
+ unsigned char[16] /* auth_scheme */
+ /* */);
+
} *gss_mechanism;
/*
diff --git a/src/lib/gssapi/spnego/Makefile.in b/src/lib/gssapi/spnego/Makefile.in
index c21ea23..b44ad0e 100644
--- a/src/lib/gssapi/spnego/Makefile.in
+++ b/src/lib/gssapi/spnego/Makefile.in
@@ -9,11 +9,12 @@ DEFINES=-D_GSS_STATIC_LINK=1
##DOS##DLL_EXP_TYPE=GSS
-SRCS = $(srcdir)/spnego_mech.c
+SRCS = $(srcdir)/spnego_mech.c $(srcdir)/negoex_ctx.c $(srcdir)/negoex_util.c
-OBJS = $(OUTPRE)spnego_mech.$(OBJEXT)
+OBJS = $(OUTPRE)spnego_mech.$(OBJEXT) $(OUTPRE)negoex_ctx.$(OBJEXT) \
+ $(OUTPRE)negoex_util.$(OBJEXT)
-STLIBOBJS = spnego_mech.o
+STLIBOBJS = spnego_mech.o negoex_ctx.o negoex_util.o
all-unix: all-libobjs
diff --git a/src/lib/gssapi/spnego/deps b/src/lib/gssapi/spnego/deps
index feb409e..1b5daff 100644
--- a/src/lib/gssapi/spnego/deps
+++ b/src/lib/gssapi/spnego/deps
@@ -11,8 +11,40 @@ spnego_mech.so spnego_mech.po $(OUTPRE)spnego_mech.$(OBJEXT): \
$(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
$(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
$(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
- $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-queue.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h ../generic/gssapi_err_generic.h \
+ gssapiP_negoex.h gssapiP_spnego.h spnego_mech.c
+negoex_ctx.so negoex_ctx.po $(OUTPRE)negoex_ctx.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/gssapi/gssapi.h \
+ $(BUILDTOP)/include/gssapi/gssapi_alloc.h $(BUILDTOP)/include/gssapi/gssapi_ext.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(srcdir)/../generic/gssapiP_generic.h \
+ $(srcdir)/../generic/gssapi_ext.h $(srcdir)/../generic/gssapi_generic.h \
+ $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-queue.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h ../generic/gssapi_err_generic.h \
+ gssapiP_negoex.h gssapiP_spnego.h negoex_ctx.c
+negoex_util.so negoex_util.po $(OUTPRE)negoex_util.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/gssapi/gssapi.h \
+ $(BUILDTOP)/include/gssapi/gssapi_alloc.h $(BUILDTOP)/include/gssapi/gssapi_ext.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(srcdir)/../generic/gssapiP_generic.h \
+ $(srcdir)/../generic/gssapi_ext.h $(srcdir)/../generic/gssapi_generic.h \
+ $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-input.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-queue.h $(top_srcdir)/include/k5-thread.h \
$(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
$(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
$(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
- ../generic/gssapi_err_generic.h gssapiP_spnego.h spnego_mech.c
+ ../generic/gssapi_err_generic.h gssapiP_negoex.h gssapiP_spnego.h \
+ negoex_util.c
diff --git a/src/lib/gssapi/spnego/gssapiP_negoex.h b/src/lib/gssapi/spnego/gssapiP_negoex.h
new file mode 100644
index 0000000..44b08f5
--- /dev/null
+++ b/src/lib/gssapi/spnego/gssapiP_negoex.h
@@ -0,0 +1,210 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2011-2018 PADL Software Pty Ltd.
+ * 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.
+ */
+
+#include "k5-int.h"
+
+/*
+ * { iso(1) identified-organization(3) dod(6) internet(1) private(4)
+ * enterprise(1) microsoft (311) security(2) mechanisms(2) negoex(30) }
+ */
+#define NEGOEX_OID_LENGTH 10
+#define NEGOEX_OID "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e"
+
+#define MESSAGE_SIGNATURE 0x535458454F47454EULL
+
+#define EXTENSION_LENGTH 12
+
+#define EXTENSION_FLAG_CRITICAL 0x80000000
+
+#define CHECKSUM_SCHEME_RFC3961 1
+
+#define NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM 23
+#define NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM 25
+
+#define CHECKSUM_HEADER_LENGTH 20
+
+#define GUID_LENGTH 16
+
+typedef uint8_t auth_scheme[GUID_LENGTH];
+typedef uint8_t conversation_id[GUID_LENGTH];
+#define GUID_EQ(a, b) (memcmp(a, b, GUID_LENGTH) == 0)
+
+#define NEGO_MESSAGE_HEADER_LENGTH 96
+#define EXCHANGE_MESSAGE_HEADER_LENGTH 64
+#define VERIFY_MESSAGE_HEADER_LENGTH 80
+#define ALERT_MESSAGE_HEADER_LENGTH 72
+#define ALERT_LENGTH 12
+#define ALERT_PULSE_LENGTH 8
+
+#define ALERT_TYPE_PULSE 1
+#define ALERT_VERIFY_NO_KEY 1
+
+enum message_type {
+ INITIATOR_NEGO = 0, /* NEGO_MESSAGE */
+ ACCEPTOR_NEGO, /* NEGO_MESSAGE */
+ INITIATOR_META_DATA, /* EXCHANGE_MESSAGE */
+ ACCEPTOR_META_DATA, /* EXCHANGE_MESSAGE */
+ CHALLENGE, /* EXCHANGE_MESSAGE */
+ AP_REQUEST, /* EXCHANGE_MESSAGE */
+ VERIFY, /* VERIFY_MESSAGE */
+ ALERT, /* ALERT */
+};
+
+struct nego_message {
+ uint8_t random[32];
+ const uint8_t *schemes;
+ uint16_t nschemes;
+};
+
+struct exchange_message {
+ auth_scheme scheme;
+ gss_buffer_desc token;
+};
+
+struct verify_message {
+ auth_scheme scheme;
+ uint32_t cksum_type;
+ const uint8_t *cksum;
+ size_t cksum_len;
+ size_t offset_in_token;
+};
+
+struct alert_message {
+ auth_scheme scheme;
+ int verify_no_key;
+};
+
+struct negoex_message {
+ uint32_t type;
+ union {
+ struct nego_message n;
+ struct exchange_message e;
+ struct verify_message v;
+ struct alert_message a;
+ } u;
+};
+
+struct negoex_auth_mech {
+ K5_TAILQ_ENTRY(negoex_auth_mech) links;
+ gss_OID oid;
+ auth_scheme scheme;
+ gss_ctx_id_t mech_context;
+ gss_buffer_desc metadata;
+ krb5_keyblock key;
+ krb5_keyblock verify_key;
+ int complete;
+ int sent_checksum;
+ int verified_checksum;
+};
+
+/* negoex_util.c */
+
+OM_uint32
+negoex_parse_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+ gss_const_buffer_t token,
+ struct negoex_message **messages_out, size_t *count_out);
+
+
+struct nego_message *
+negoex_locate_nego_message(struct negoex_message *messages, size_t nmessages,
+ enum message_type type);
+struct exchange_message *
+negoex_locate_exchange_message(struct negoex_message *messages,
+ size_t nmessages, enum message_type type);
+struct verify_message *
+negoex_locate_verify_message(struct negoex_message *messages,
+ size_t nmessages);
+struct alert_message *
+negoex_locate_alert_message(struct negoex_message *messages, size_t nmessages);
+
+void
+negoex_add_nego_message(spnego_gss_ctx_id_t ctx, enum message_type type,
+ uint8_t random[32]);
+void
+negoex_add_exchange_message(spnego_gss_ctx_id_t ctx, enum message_type type,
+ const auth_scheme scheme, gss_buffer_t token);
+void
+negoex_add_verify_message(spnego_gss_ctx_id_t ctx, const auth_scheme scheme,
+ uint32_t cksum_type, const uint8_t *cksum,
+ uint32_t cksum_len);
+
+void
+negoex_add_verify_no_key_alert(spnego_gss_ctx_id_t ctx,
+ const auth_scheme scheme);
+
+OM_uint32
+negoex_random(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+ unsigned char *data, size_t length);
+
+void
+negoex_prep_context_for_spnego(spnego_gss_ctx_id_t ctx);
+
+OM_uint32
+negoex_prep_context_for_negoex(OM_uint32 *minor, spnego_gss_ctx_id_t ctx);
+
+void
+negoex_release_context(spnego_gss_ctx_id_t ctx);
+
+OM_uint32
+negoex_add_auth_mech(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+ gss_const_OID oid, auth_scheme scheme);
+
+void
+negoex_delete_auth_mech(spnego_gss_ctx_id_t ctx,
+ struct negoex_auth_mech *mech);
+
+void
+negoex_select_auth_mech(spnego_gss_ctx_id_t ctx,
+ struct negoex_auth_mech *mech);
+
+struct negoex_auth_mech *
+negoex_locate_auth_scheme(spnego_gss_ctx_id_t ctx, const auth_scheme scheme);
+
+void
+negoex_common_auth_schemes(spnego_gss_ctx_id_t ctx,
+ const uint8_t *schemes, uint16_t nschemes);
+
+void
+negoex_restrict_auth_schemes(spnego_gss_ctx_id_t ctx,
+ const uint8_t *schemes, uint16_t nschemes);
+
+/* negoex_ctx.c */
+
+OM_uint32
+negoex_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+ gss_name_t target_name, OM_uint32 req_flags, OM_uint32 time_req,
+ gss_buffer_t input_token, gss_buffer_t output_token,
+ OM_uint32 *time_rec);
+
+OM_uint32
+negoex_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+ gss_buffer_t input_token, gss_buffer_t output_token,
+ OM_uint32 *time_rec);
diff --git a/src/lib/gssapi/spnego/gssapiP_spnego.h b/src/lib/gssapi/spnego/gssapiP_spnego.h
index cad47ee..a937633 100644
--- a/src/lib/gssapi/spnego/gssapiP_spnego.h
+++ b/src/lib/gssapi/spnego/gssapiP_spnego.h
@@ -12,7 +12,12 @@
extern "C" {
#endif
+typedef struct spnego_ctx_st *spnego_gss_ctx_id_t;
+
#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_ext.h>
+#include <k5-queue.h>
+#include "gssapiP_negoex.h"
#define SEC_CONTEXT_TOKEN 1
#define SPNEGO_SIZE_OF_INT 4
@@ -41,13 +46,27 @@ extern "C" {
#define GENERAL_STRING 0x1b
/*
- * SPNEGO specific error codes (minor status codes)
+ * SPNEGO and NegoEx minor status codes
*/
-#define ERR_SPNEGO_NO_MECHS_AVAILABLE 0x20000001
-#define ERR_SPNEGO_NO_CREDS_ACQUIRED 0x20000002
-#define ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR 0x20000003
-#define ERR_SPNEGO_NEGOTIATION_FAILED 0x20000004
-#define ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR 0x20000005
+#define ERR_SPNEGO_NO_MECHS_AVAILABLE 0x20000001
+#define ERR_SPNEGO_NO_CREDS_ACQUIRED 0x20000002
+#define ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR 0x20000003
+#define ERR_SPNEGO_NEGOTIATION_FAILED 0x20000004
+#define ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR 0x20000005
+#define ERR_NEGOEX_INVALID_MESSAGE_SIGNATURE 0x20000006
+#define ERR_NEGOEX_INVALID_MESSAGE_TYPE 0x20000007
+#define ERR_NEGOEX_INVALID_MESSAGE_SIZE 0x20000008
+#define ERR_NEGOEX_INVALID_CONVERSATION_ID 0x20000009
+#define ERR_NEGOEX_AUTH_SCHEME_NOT_FOUND 0x20000010
+#define ERR_NEGOEX_MISSING_NEGO_MESSAGE 0x20000011
+#define ERR_NEGOEX_MISSING_AP_REQUEST_MESSAGE 0x20000012
+#define ERR_NEGOEX_NO_AVAILABLE_MECHS 0x20000013
+#define ERR_NEGOEX_NO_VERIFY_KEY 0x20000014
+#define ERR_NEGOEX_UNKNOWN_CHECKSUM_SCHEME 0x20000015
+#define ERR_NEGOEX_INVALID_CHECKSUM 0x20000016
+#define ERR_NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION 0x20000017
+#define ERR_NEGOEX_UNSUPPORTED_VERSION 0x20000018
+#define ERR_NEGOEX_MESSAGE_OUT_OF_SEQUENCE 0x20000019
/*
* send_token_flag is used to indicate in later steps what type
@@ -89,7 +108,7 @@ typedef struct {
} spnego_gss_cred_id_rec, *spnego_gss_cred_id_t;
/* Structure for context handle */
-typedef struct {
+struct spnego_ctx_st {
OM_uint32 magic_num;
gss_buffer_desc DER_mechTypes;
gss_OID_set mech_set;
@@ -107,7 +126,13 @@ typedef struct {
gss_name_t internal_name;
gss_OID actual_mech;
gss_cred_id_t deleg_cred;
-} spnego_gss_ctx_id_rec, *spnego_gss_ctx_id_t;
+ int negoex_step;
+ struct k5buf negoex_transcript;
+ uint32_t negoex_seqnum;
+ conversation_id negoex_conv_id;
+ K5_TAILQ_HEAD(negoex_mech_list, negoex_auth_mech) negoex_mechs;
+ krb5_context kctx;
+};
/*
* The magic number must be less than a standard pagesize
diff --git a/src/lib/gssapi/spnego/negoex_ctx.c b/src/lib/gssapi/spnego/negoex_ctx.c
new file mode 100644
index 0000000..e69b720
--- /dev/null
+++ b/src/lib/gssapi/spnego/negoex_ctx.c
@@ -0,0 +1,785 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2011-2018 PADL Software Pty Ltd.
+ * 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.
+ */
+
+#include "k5-platform.h"
+#include "gssapiP_spnego.h"
+#include <generic/gssapiP_generic.h>
+
+/*
+ * The initial context token emitted by the initiator is a INITIATOR_NEGO
+ * message followed by zero or more INITIATOR_META_DATA tokens, and zero
+ * or one AP_REQUEST tokens.
+ *
+ * Upon receiving this, the acceptor computes the list of mutually supported
+ * authentication mechanisms and performs the metadata exchange. The output
+ * token is ACCEPTOR_NEGO followed by zero or more ACCEPTOR_META_DATA tokens,
+ * and zero or one CHALLENGE tokens.
+ *
+ * Once the metadata exchange is complete and a mechanism is selected, the
+ * selected mechanism's context token exchange continues with AP_REQUEST and
+ * CHALLENGE messages.
+ *
+ * Once the context token exchange is complete, VERIFY messages are sent to
+ * authenticate the entire exchange.
+ */
+
+static void
+zero_and_release_buffer_set(gss_buffer_set_t *pbuffers)
+{
+ OM_uint32 tmpmin;
+ gss_buffer_set_t buffers = *pbuffers;
+ uint32_t i;
+
+ if (buffers != GSS_C_NO_BUFFER_SET) {
+ for (i = 0; i < buffers->count; i++)
+ zap(buffers->elements[i].value, buffers->elements[i].length);
+
+ gss_release_buffer_set(&tmpmin, &buffers);
+ }
+
+ *pbuffers = GSS_C_NO_BUFFER_SET;
+}
+
+static OM_uint32
+buffer_set_to_key(OM_uint32 *minor, gss_buffer_set_t buffers,
+ krb5_keyblock *key)
+{
+ krb5_error_code ret;
+
+ /* Returned keys must be in two buffers, with the key contents in the first
+ * and the enctype as a 32-bit little-endian integer in the second. */
+ if (buffers->count != 2 || buffers->elements[1].length != 4) {
+ *minor = ERR_NEGOEX_NO_VERIFY_KEY;
+ return GSS_S_FAILURE;
+ }
+
+ krb5_free_keyblock_contents(NULL, key);
+
+ key->contents = k5memdup(buffers->elements[0].value,
+ buffers->elements[0].length, &ret);
+ if (key->contents == NULL) {
+ *minor = ret;
+ return GSS_S_FAILURE;
+ }
+ key->length = buffers->elements[0].length;
+ key->enctype = load_32_le(buffers->elements[1].value);
+
+ return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+get_session_keys(OM_uint32 *minor, struct negoex_auth_mech *mech)
+{
+ OM_uint32 major, tmpmin;
+ gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET;
+
+ major = gss_inquire_sec_context_by_oid(&tmpmin, mech->mech_context,
+ GSS_C_INQ_NEGOEX_KEY, &buffers);
+ if (major == GSS_S_COMPLETE) {
+ major = buffer_set_to_key(minor, buffers, &mech->key);
+ zero_and_release_buffer_set(&buffers);
+ if (major != GSS_S_COMPLETE)
+ return major;
+ }
+
+ major = gss_inquire_sec_context_by_oid(&tmpmin, mech->mech_context,
+ GSS_C_INQ_NEGOEX_VERIFY_KEY,
+ &buffers);
+ if (major == GSS_S_COMPLETE) {
+ major = buffer_set_to_key(minor, buffers, &mech->verify_key);
+ zero_and_release_buffer_set(&buffers);
+ if (major != GSS_S_COMPLETE)
+ return major;
+ }
+
+ return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+emit_initiator_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
+{
+ OM_uint32 major;
+ uint8_t random[32];
+
+ major = negoex_random(minor, ctx, random, 32);
+ if (major != GSS_S_COMPLETE)
+ return major;
+
+ negoex_add_nego_message(ctx, INITIATOR_NEGO, random);
+ return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+process_initiator_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+ struct negoex_message *messages, size_t nmessages)
+{
+ struct nego_message *msg;
+
+ assert(!ctx->initiate && ctx->negoex_step == 1);
+
+ msg = negoex_locate_nego_message(messages, nmessages, INITIATOR_NEGO);
+ if (msg == NULL) {
+ *minor = ERR_NEGOEX_MISSING_NEGO_MESSAGE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+
+ negoex_restrict_auth_schemes(ctx, msg->schemes, msg->nschemes);
+ return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+emit_acceptor_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
+{
+ OM_uint32 major;
+ uint8_t random[32];
+
+ major = negoex_random(minor, ctx, random, 32);
+ if (major != GSS_S_COMPLETE)
+ return major;
+
+ negoex_add_nego_message(ctx, ACCEPTOR_NEGO, random);
+ return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+process_acceptor_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+ struct negoex_message *messages, size_t nmessages)
+{
+ struct nego_message *msg;
+
+ msg = negoex_locate_nego_message(messages, nmessages, ACCEPTOR_NEGO);
+ if (msg == NULL) {
+ *minor = ERR_NEGOEX_MISSING_NEGO_MESSAGE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+
+ /* Reorder and prune our mech list to match the acceptor's list (or a
+ * subset of it). */
+ negoex_common_auth_schemes(ctx, msg->schemes, msg->nschemes);
+
+ return GSS_S_COMPLETE;
+}
+
+static void
+query_meta_data(spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+ gss_name_t target, OM_uint32 req_flags)
+{
+ OM_uint32 major, minor;
+ struct negoex_auth_mech *p, *next;
+
+ K5_TAILQ_FOREACH_SAFE(p, &ctx->negoex_mechs, links, next) {
+ major = gssspi_query_meta_data(&minor, p->oid, cred, &p->mech_context,
+ target, req_flags, &p->metadata);
+ /* GSS_Query_meta_data failure removes mechanism from list. */
+ if (major != GSS_S_COMPLETE)
+ negoex_delete_auth_mech(ctx, p);
+ }
+}
+
+static void
+exchange_meta_data(spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+ gss_name_t target, OM_uint32 req_flags,
+ struct negoex_message *messages, size_t nmessages)
+{
+ OM_uint32 major, minor;
+ struct negoex_auth_mech *mech;
+ enum message_type type;
+ struct exchange_message *msg;
+ uint32_t i;
+
+ type = ctx->initiate ? ACCEPTOR_META_DATA : INITIATOR_META_DATA;
+
+ for (i = 0; i < nmessages; i++) {
+ if (messages[i].type != type)
+ continue;
+ msg = &messages[i].u.e;
+
+ mech = negoex_locate_auth_scheme(ctx, msg->scheme);
+ if (mech == NULL)
+ continue;
+
+ major = gssspi_exchange_meta_data(&minor, mech->oid, cred,
+ &mech->mech_context, target,
+ req_flags, &msg->token);
+ /* GSS_Exchange_meta_data failure removes mechanism from list. */
+ if (major != GSS_S_COMPLETE)
+ negoex_delete_auth_mech(ctx, mech);
+ }
+}
+
+/*
+ * In the initiator, if we are processing the acceptor's first reply, discard
+ * the optimistic context if the acceptor ignored the optimistic token. If the
+ * acceptor continued the optimistic mech, discard all other mechs.
+ */
+static void
+check_optimistic_result(spnego_gss_ctx_id_t ctx,
+ struct negoex_message *messages, size_t nmessages)
+{
+ struct negoex_auth_mech *mech;
+ OM_uint32 tmpmin;
+
+ assert(ctx->initiate && ctx->negoex_step == 2);
+
+ /* Do nothing if we didn't make an optimistic context. */
+ mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+ if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT)
+ return;
+
+ /* If the acceptor used the optimistic token, it will send an acceptor
+ * token or a checksum (or both) in its first reply. */
+ if (negoex_locate_exchange_message(messages, nmessages,
+ CHALLENGE) != NULL ||
+ negoex_locate_verify_message(messages, nmessages) != NULL) {
+ /* The acceptor continued the optimistic mech, and metadata exchange
+ * didn't remove it. Commit to this mechanism. */
+ negoex_select_auth_mech(ctx, mech);
+ } else {
+ /* The acceptor ignored the optimistic token. Restart the mech. */
+ (void)gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL);
+ krb5_free_keyblock_contents(NULL, &mech->key);
+ krb5_free_keyblock_contents(NULL, &mech->verify_key);
+ mech->complete = mech->sent_checksum = FALSE;
+ }
+}
+
+/* Perform an initiator step of the underlying mechanism exchange. */
+static OM_uint32
+mech_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+ gss_name_t target, OM_uint32 req_flags, OM_uint32 time_req,
+ struct negoex_message *messages, size_t nmessages,
+ gss_buffer_t output_token, OM_uint32 *time_rec)
+{
+ OM_uint32 major, first_major = 0, first_minor = 0;
+ struct negoex_auth_mech *mech = NULL;
+ gss_buffer_t input_token = GSS_C_NO_BUFFER;
+ struct exchange_message *msg;
+ int first_mech;
+
+ output_token->value = NULL;
+ output_token->length = 0;
+
+ /* Allow disabling of optimistic token for testing. */
+ if (ctx->negoex_step == 1 &&
+ secure_getenv("NEGOEX_NO_OPTIMISTIC_TOKEN") != NULL)
+ return GSS_S_COMPLETE;
+
+ if (K5_TAILQ_EMPTY(&ctx->negoex_mechs)) {
+ *minor = ERR_NEGOEX_NO_AVAILABLE_MECHS;
+ return GSS_S_FAILURE;
+ }
+
+ /*
+ * Get the input token. The challenge could be for the optimistic mech,
+ * which we might have discarded in metadata exchange, so ignore the
+ * challenge if it doesn't match the first auth mech.
+ */
+ mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+ msg = negoex_locate_exchange_message(messages, nmessages, CHALLENGE);
+ if (msg != NULL && GUID_EQ(msg->scheme, mech->scheme))
+ input_token = &msg->token;
+
+ if (mech->complete)
+ return GSS_S_COMPLETE;
+
+ first_mech = TRUE;
+
+ while (!K5_TAILQ_EMPTY(&ctx->negoex_mechs)) {
+ mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+
+ major = gss_init_sec_context(minor, cred, &mech->mech_context, target,
+ mech->oid, req_flags, time_req,
+ GSS_C_NO_CHANNEL_BINDINGS, input_token,
+ &ctx->actual_mech, output_token,
+ &ctx->ctx_flags, time_rec);
+
+ if (major == GSS_S_COMPLETE)
+ mech->complete = 1;
+
+ if (!GSS_ERROR(major))
+ return get_session_keys(minor, mech);
+
+ /* Remember the error we got from the first mech. */
+ if (first_mech) {
+ first_major = major;
+ first_minor = *minor;
+ }
+
+ /* If we still have multiple mechs to try, move on to the next one. */
+ negoex_delete_auth_mech(ctx, mech);
+ first_mech = FALSE;
+ input_token = GSS_C_NO_BUFFER;
+ }
+
+ if (K5_TAILQ_EMPTY(&ctx->negoex_mechs)) {
+ major = first_major;
+ *minor = first_minor;
+ }
+
+ return major;
+}
+
+/* Perform an acceptor step of the underlying mechanism exchange. */
+static OM_uint32
+mech_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+ gss_cred_id_t cred, struct negoex_message *messages,
+ size_t nmessages, gss_buffer_t output_token, OM_uint32 *time_rec)
+{
+ OM_uint32 major, tmpmin;
+ struct negoex_auth_mech *mech;
+ struct exchange_message *msg;
+
+ assert(!ctx->initiate && !K5_TAILQ_EMPTY(&ctx->negoex_mechs));
+
+ msg = negoex_locate_exchange_message(messages, nmessages, AP_REQUEST);
+ if (msg == NULL) {
+ /* No input token is okay on the first request or if the mech is
+ * complete. */
+ if (ctx->negoex_step == 1 ||
+ K5_TAILQ_FIRST(&ctx->negoex_mechs)->complete)
+ return GSS_S_COMPLETE;
+ *minor = ERR_NEGOEX_MISSING_AP_REQUEST_MESSAGE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+
+ if (ctx->negoex_step == 1) {
+ /* Ignore the optimistic token if it isn't for our most preferred
+ * mech. */
+ mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+ if (!GUID_EQ(msg->scheme, mech->scheme))
+ return GSS_S_COMPLETE;
+ } else {
+ /* The initiator has selected a mech; discard other entries. */
+ mech = negoex_locate_auth_scheme(ctx, msg->scheme);
+ if (mech == NULL) {
+ *minor = ERR_NEGOEX_NO_AVAILABLE_MECHS;
+ return GSS_S_FAILURE;
+ }
+ negoex_select_auth_mech(ctx, mech);
+ }
+
+ if (mech->complete)
+ return GSS_S_COMPLETE;
+
+ if (ctx->internal_name != GSS_C_NO_NAME)
+ gss_release_name(&tmpmin, &ctx->internal_name);
+ if (ctx->deleg_cred != GSS_C_NO_CREDENTIAL)
+ gss_release_cred(&tmpmin, &ctx->deleg_cred);
+
+ major = gss_accept_sec_context(minor, &mech->mech_context, cred,
+ &msg->token, GSS_C_NO_CHANNEL_BINDINGS,
+ &ctx->internal_name, &ctx->actual_mech,
+ output_token, &ctx->ctx_flags,
+ time_rec, &ctx->deleg_cred);
+
+ if (major == GSS_S_COMPLETE)
+ mech->complete = 1;
+
+ if (!GSS_ERROR(major)) {
+ major = get_session_keys(minor, mech);
+ } else if (ctx->negoex_step == 1) {
+ /* This was an optimistic token; pretend this never happened. */
+ major = GSS_S_COMPLETE;
+ *minor = 0;
+ gss_release_buffer(&tmpmin, output_token);
+ gss_delete_sec_context(&tmpmin, &mech->mech_context, GSS_C_NO_BUFFER);
+ }
+
+ return major;
+}
+
+static krb5_keyusage
+verify_keyusage(spnego_gss_ctx_id_t ctx, int make_checksum)
+{
+ /* Of course, these are the wrong way around in the spec. */
+ return (ctx->initiate ^ !make_checksum) ?
+ NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM : NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM;
+}
+
+static OM_uint32
+verify_checksum(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+ struct negoex_message *messages, size_t nmessages,
+ gss_buffer_t input_token, int *send_alert_out)
+{
+ krb5_error_code ret;
+ struct negoex_auth_mech *mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+ struct verify_message *msg;
+ krb5_crypto_iov iov[3];
+ krb5_keyusage usage = verify_keyusage(ctx, FALSE);
+ krb5_boolean valid;
+
+ *send_alert_out = FALSE;
+ assert(mech != NULL);
+
+ /* The other party may not be ready to send a verify token yet, or (in the
+ * first initiator step) may send one for a mechanism we don't support. */
+ msg = negoex_locate_verify_message(messages, nmessages);
+ if (msg == NULL || !GUID_EQ(msg->scheme, mech->scheme))
+ return GSS_S_COMPLETE;
+
+ /* A recoverable error may cause us to be unable to verify a token from the
+ * other party. In this case we should send an alert. */
+ if (mech->verify_key.enctype == ENCTYPE_NULL) {
+ *send_alert_out = TRUE;
+ return GSS_S_COMPLETE;
+ }
+
+ /* Verify the checksum over the existing transcript and the portion of the
+ * input token leading up to the verify message. */
+ iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
+ iov[0].data = make_data(ctx->negoex_transcript.data,
+ ctx->negoex_transcript.len);
+ iov[1].flags = KRB5_CRYPTO_TYPE_DATA;
+ iov[1].data = make_data(input_token->value, msg->offset_in_token);
+ iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
+ iov[2].data = make_data((uint8_t *)msg->cksum, msg->cksum_len);
+
+ ret = krb5_c_verify_checksum_iov(ctx->kctx, msg->cksum_type,
+ &mech->verify_key, usage, iov, 3, &valid);
+ if (ret) {
+ *minor = ret;
+ return GSS_S_FAILURE;
+ }
+ if (!valid || !krb5_c_is_keyed_cksum(msg->cksum_type)) {
+ *minor = ERR_NEGOEX_INVALID_CHECKSUM;
+ return GSS_S_BAD_SIG;
+ }
+
+ mech->verified_checksum = TRUE;
+ return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+make_checksum(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
+{
+ krb5_error_code ret;
+ krb5_data d;
+ krb5_keyusage usage = verify_keyusage(ctx, TRUE);
+ krb5_checksum cksum;
+ struct negoex_auth_mech *mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+
+ assert(mech != NULL);
+
+ if (mech->key.enctype == ENCTYPE_NULL) {
+ if (mech->complete) {
+ *minor = ERR_NEGOEX_NO_VERIFY_KEY;
+ return GSS_S_UNAVAILABLE;
+ } else {
+ return GSS_S_COMPLETE;
+ }
+ }
+
+ d = make_data(ctx->negoex_transcript.data, ctx->negoex_transcript.len);
+ ret = krb5_c_make_checksum(ctx->kctx, 0, &mech->key, usage, &d, &cksum);
+ if (ret) {
+ *minor = ret;
+ return GSS_S_FAILURE;
+ }
+
+ negoex_add_verify_message(ctx, mech->scheme, cksum.checksum_type,
+ cksum.contents, cksum.length);
+
+ mech->sent_checksum = TRUE;
+ krb5_free_checksum_contents(ctx->kctx, &cksum);
+ return GSS_S_COMPLETE;
+}
+
+/* If the other side sent a VERIFY_NO_KEY pulse alert, clear the checksum state
+ * on the mechanism so that we send another VERIFY message. */
+static void
+process_alerts(spnego_gss_ctx_id_t ctx,
+ struct negoex_message *messages, uint32_t nmessages)
+{
+ struct alert_message *msg;
+ struct negoex_auth_mech *mech;
+
+ msg = negoex_locate_alert_message(messages, nmessages);
+ if (msg != NULL && msg->verify_no_key) {
+ mech = negoex_locate_auth_scheme(ctx, msg->scheme);
+ if (mech != NULL) {
+ mech->sent_checksum = FALSE;
+ krb5_free_keyblock_contents(NULL, &mech->key);
+ krb5_free_keyblock_contents(NULL, &mech->verify_key);
+ }
+ }
+}
+
+static OM_uint32
+make_output_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+ gss_buffer_t mech_output_token, int send_alert,
+ gss_buffer_t output_token)
+{
+ OM_uint32 major;
+ struct negoex_auth_mech *mech;
+ enum message_type type;
+ size_t old_transcript_len = ctx->negoex_transcript.len;
+
+ output_token->length = 0;
+ output_token->value = NULL;
+
+ /* If the mech is complete and we previously sent a checksum, we just
+ * processed the last leg and don't need to send another token. */
+ if (mech_output_token->length == 0 &&
+ K5_TAILQ_FIRST(&ctx->negoex_mechs)->sent_checksum)
+ return GSS_S_COMPLETE;
+
+ if (ctx->negoex_step == 1) {
+ if (ctx->initiate)
+ major = emit_initiator_nego(minor, ctx);
+ else
+ major = emit_acceptor_nego(minor, ctx);
+ if (major != GSS_S_COMPLETE)
+ return major;
+
+ type = ctx->initiate ? INITIATOR_META_DATA : ACCEPTOR_META_DATA;
+ K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
+ if (mech->metadata.length > 0) {
+ negoex_add_exchange_message(ctx, type, mech->scheme,
+ &mech->metadata);
+ }
+ }
+ }
+
+ mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+
+ if (mech_output_token->length > 0) {
+ type = ctx->initiate ? AP_REQUEST : CHALLENGE;
+ negoex_add_exchange_message(ctx, type, mech->scheme,
+ mech_output_token);
+ }
+
+ if (send_alert)
+ negoex_add_verify_no_key_alert(ctx, mech->scheme);
+
+ /* Try to add a VERIFY message if we haven't already done so. */
+ if (!mech->sent_checksum) {
+ major = make_checksum(minor, ctx);
+ if (major != GSS_S_COMPLETE)
+ return major;
+ }
+
+ if (ctx->negoex_transcript.data == NULL) {
+ *minor = ENOMEM;
+ return GSS_S_FAILURE;
+ }
+
+ /* Copy what we added to the transcript into the output token. */
+ output_token->length = ctx->negoex_transcript.len - old_transcript_len;
+ output_token->value = gssalloc_malloc(output_token->length);
+ if (output_token->value == NULL) {
+ *minor = ENOMEM;
+ return GSS_S_FAILURE;
+ }
+ memcpy(output_token->value,
+ (uint8_t *)ctx->negoex_transcript.data + old_transcript_len,
+ output_token->length);
+
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32
+negoex_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+ gss_name_t target_name, OM_uint32 req_flags, OM_uint32 time_req,
+ gss_buffer_t input_token, gss_buffer_t output_token,
+ OM_uint32 *time_rec)
+{
+ OM_uint32 major, tmpmin;
+ gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER;
+ struct negoex_message *messages = NULL;
+ struct negoex_auth_mech *mech;
+ size_t nmessages = 0;
+ int send_alert = FALSE;
+
+ if (ctx->negoex_step == 0 && input_token != GSS_C_NO_BUFFER &&
+ input_token->length != 0)
+ return GSS_S_DEFECTIVE_TOKEN;
+
+ major = negoex_prep_context_for_negoex(minor, ctx);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+
+ ctx->negoex_step++;
+
+ if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) {
+ major = negoex_parse_token(minor, ctx, input_token, &messages,
+ &nmessages);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+ }
+
+ process_alerts(ctx, messages, nmessages);
+
+ if (ctx->negoex_step == 1) {
+ /* Choose a random conversation ID. */
+ major = negoex_random(minor, ctx, ctx->negoex_conv_id, GUID_LENGTH);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+
+ /* Query each mech for its metadata (this may prune the mech list). */
+ query_meta_data(ctx, cred, target_name, req_flags);
+ } else if (ctx->negoex_step == 2) {
+ /* See if the mech processed the optimistic token. */
+ check_optimistic_result(ctx, messages, nmessages);
+
+ /* Pass the acceptor metadata to each mech to prune the list. */
+ exchange_meta_data(ctx, cred, target_name, req_flags,
+ messages, nmessages);
+
+ /* Process the ACCEPTOR_NEGO message. */
+ major = process_acceptor_nego(minor, ctx, messages, nmessages);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+ }
+
+ /* Process the input token and/or produce an output token. This may prune
+ * the mech list, but on success there will be at least one mech entry. */
+ major = mech_init(minor, ctx, cred, target_name, req_flags, time_req,
+ messages, nmessages, &mech_output_token, time_rec);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+ assert(!K5_TAILQ_EMPTY(&ctx->negoex_mechs));
+
+ /* At this point in step 2 we have performed the metadata exchange and
+ * chosen a mech we can use, so discard any fallback mech entries. */
+ if (ctx->negoex_step == 2)
+ negoex_select_auth_mech(ctx, K5_TAILQ_FIRST(&ctx->negoex_mechs));
+
+ major = verify_checksum(minor, ctx, messages, nmessages, input_token,
+ &send_alert);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+
+ if (input_token != GSS_C_NO_BUFFER) {
+ k5_buf_add_len(&ctx->negoex_transcript, input_token->value,
+ input_token->length);
+ }
+
+ major = make_output_token(minor, ctx, &mech_output_token, send_alert,
+ output_token);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+
+ mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+ major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
+ GSS_S_CONTINUE_NEEDED;
+
+cleanup:
+ free(messages);
+ gss_release_buffer(&tmpmin, &mech_output_token);
+ negoex_prep_context_for_spnego(ctx);
+ return major;
+}
+
+OM_uint32
+negoex_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
+ gss_buffer_t input_token, gss_buffer_t output_token,
+ OM_uint32 *time_rec)
+{
+ OM_uint32 major, tmpmin;
+ gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER;
+ struct negoex_message *messages = NULL;
+ struct negoex_auth_mech *mech;
+ size_t nmessages;
+ int send_alert = FALSE;
+
+ if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) {
+ major = GSS_S_DEFECTIVE_TOKEN;
+ goto cleanup;
+ }
+
+ major = negoex_prep_context_for_negoex(minor, ctx);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+
+ ctx->negoex_step++;
+
+ major = negoex_parse_token(minor, ctx, input_token, &messages, &nmessages);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+
+ process_alerts(ctx, messages, nmessages);
+
+ if (ctx->negoex_step == 1) {
+ /* Read the INITIATOR_NEGO message to prune the candidate mech list. */
+ major = process_initiator_nego(minor, ctx, messages, nmessages);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+
+ /*
+ * Pass the initiator metadata to each mech to prune the list, and
+ * query each mech for its acceptor metadata (which may also prune the
+ * list).
+ */
+ exchange_meta_data(ctx, cred, GSS_C_NO_NAME, 0, messages, nmessages);
+ query_meta_data(ctx, cred, GSS_C_NO_NAME, 0);
+
+ if (K5_TAILQ_EMPTY(&ctx->negoex_mechs)) {
+ *minor = ERR_NEGOEX_NO_AVAILABLE_MECHS;
+ major = GSS_S_FAILURE;
+ goto cleanup;
+ }
+ }
+
+ /*
+ * Process the input token and possibly produce an output token. This may
+ * prune the list to a single mech. Continue on error if an output token
+ * is generated, so that we send the token to the initiator.
+ */
+ major = mech_accept(minor, ctx, cred, messages, nmessages,
+ &mech_output_token, time_rec);
+ if (major != GSS_S_COMPLETE && mech_output_token.length == 0)
+ goto cleanup;
+
+ if (major == GSS_S_COMPLETE) {
+ major = verify_checksum(minor, ctx, messages, nmessages, input_token,
+ &send_alert);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+ }
+
+ k5_buf_add_len(&ctx->negoex_transcript,
+ input_token->value, input_token->length);
+
+ major = make_output_token(minor, ctx, &mech_output_token, send_alert,
+ output_token);
+ if (major != GSS_S_COMPLETE)
+ goto cleanup;
+
+ mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+ major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
+ GSS_S_CONTINUE_NEEDED;
+
+cleanup:
+ free(messages);
+ gss_release_buffer(&tmpmin, &mech_output_token);
+ negoex_prep_context_for_spnego(ctx);
+ return major;
+}
diff --git a/src/lib/gssapi/spnego/negoex_trace.c b/src/lib/gssapi/spnego/negoex_trace.c
new file mode 100644
index 0000000..04ed992
--- /dev/null
+++ b/src/lib/gssapi/spnego/negoex_trace.c
@@ -0,0 +1,121 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2011-2018 PADL Software Pty Ltd.
+ * 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.
+ */
+
+#include "gssapiP_spnego.h"
+
+static int
+guid_to_string(const uint8_t guid[16], char *buffer, size_t bufsiz)
+{
+ uint32_t data1;
+ uint16_t data2, data3;
+
+ data1 = load_32_le(guid);
+ data2 = load_16_le(guid + 4);
+ data3 = load_16_le(guid + 6);
+
+ return snprintf(buffer, bufsiz,
+ "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ data1, data2, data3, guid[8], guid[9], guid[10], guid[11],
+ guid[12], guid[13], guid[14], guid[15]);
+}
+
+static void
+trace_auth_scheme(spnego_gss_ctx_id_t ctx, const char *prefix, int ind,
+ const auth_scheme scheme)
+{
+ char trace_msg[128];
+ char szAuthScheme[37];
+
+ guid_to_string(scheme, szAuthScheme, sizeof(szAuthScheme));
+
+ snprintf(trace_msg, sizeof(trace_msg),
+ "NEGOEXTS: %20s[%02u] -- AuthScheme %s",
+ prefix, ind, szAuthScheme);
+ TRACE_NEGOEX_AUTH_SCHEMES(ctx->kctx, trace_msg);
+}
+
+void
+negoex_trace_auth_schemes(spnego_gss_ctx_id_t ctx, const char *prefix,
+ const uint8_t *schemes, uint16_t nschemes)
+{
+ uint16_t i;
+
+ for (i = 0; i < nschemes; i++)
+ trace_auth_scheme(ctx, prefix, i, schemes + i * GUID_LENGTH);
+}
+
+void
+negoex_trace_ctx_auth_schemes(spnego_gss_ctx_id_t ctx, const char *prefix)
+{
+ negoex_auth_mech_t mech;
+ int ind = 0;
+
+ K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
+ trace_auth_scheme(ctx, prefix, ind++, mech->scheme);
+}
+
+void
+negoex_trace_message(spnego_gss_ctx_id_t ctx, int direction,
+ enum message_type type, const conversation_id conv_id,
+ unsigned int seqnum, unsigned int header_len,
+ unsigned int msg_len)
+{
+ char trace_msg[128];
+ char conv_str[37];
+ char *typestr;
+
+ if (type == INITIATOR_NEGO)
+ typestr = "INITIATOR_NEGO";
+ else if (type == ACCEPTOR_NEGO)
+ typestr = "ACCEPTOR_NEGO";
+ else if (type == INITIATOR_META_DATA)
+ typestr = "INITIATOR_META_DATA";
+ else if (type == ACCEPTOR_META_DATA)
+ typestr = "ACCEPTOR_META_DATA";
+ else if (type == CHALLENGE)
+ typestr = "CHALLENGE";
+ else if (type == AP_REQUEST)
+ typestr = "AP_REQUEST";
+ else if (type == VERIFY)
+ typestr = "VERIFY";
+ else if (type == ALERT)
+ typestr = "ALERT";
+ else
+ typestr = "UNKNOWN";
+
+ guid_to_string(conv_id, conv_str, sizeof(conv_str));
+ snprintf(trace_msg, sizeof(trace_msg),
+ "NEGOEXTS%c %20s[%02u] -- ConvId %s HdrLength %u MsgLength %u",
+ direction ? '<' : '>', typestr, seqnum, conv_str, header_len,
+ msg_len);
+
+ TRACE_NEGOEX_MESSAGE(ctx->kctx, trace_msg);
+}
diff --git a/src/lib/gssapi/spnego/negoex_util.c b/src/lib/gssapi/spnego/negoex_util.c
new file mode 100644
index 0000000..7003684
--- /dev/null
+++ b/src/lib/gssapi/spnego/negoex_util.c
@@ -0,0 +1,812 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2011-2018 PADL Software Pty Ltd.
+ * 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.
+ */
+
+#include "gssapiP_spnego.h"
+#include <generic/gssapiP_generic.h>
+#include "k5-input.h"
+
+static void
+release_auth_mech(struct negoex_auth_mech *mech);
+
+OM_uint32
+negoex_random(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+ uint8_t *data, size_t length)
+{
+ krb5_data d = make_data(data, length);
+
+ *minor = krb5_c_random_make_octets(ctx->kctx, &d);
+ return *minor ? GSS_S_FAILURE : GSS_S_COMPLETE;
+}
+
+/*
+ * SPNEGO functions expect to find the active mech context in ctx->ctx_handle,
+ * but the metadata exchange APIs force us to have one mech context per mech
+ * entry. To address this mismatch, move the active mech context (if we have
+ * one) to ctx->ctx_handle at the end of NegoEx processing.
+ */
+void
+negoex_prep_context_for_spnego(spnego_gss_ctx_id_t ctx)
+{
+ struct negoex_auth_mech *mech;
+
+ mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+ if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT)
+ return;
+
+ assert(ctx->ctx_handle == GSS_C_NO_CONTEXT);
+ ctx->ctx_handle = mech->mech_context;
+ mech->mech_context = GSS_C_NO_CONTEXT;
+}
+
+OM_uint32
+negoex_prep_context_for_negoex(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
+{
+ krb5_error_code ret;
+ struct negoex_auth_mech *mech;
+
+ if (ctx->kctx != NULL) {
+ /* The context is already initialized for NegoEx. Undo what
+ * negoex_prep_for_spnego() did, if applicable. */
+ if (ctx->ctx_handle != GSS_C_NO_CONTEXT) {
+ mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
+ assert(mech != NULL && mech->mech_context == GSS_C_NO_CONTEXT);
+ mech->mech_context = ctx->ctx_handle;
+ ctx->ctx_handle = GSS_C_NO_CONTEXT;
+ }
+ return GSS_S_COMPLETE;
+ }
+
+ /* Initialize the NegoEX context fields. (negoex_mechs is already set up
+ * by SPNEGO.) */
+ ret = krb5_init_context(&ctx->kctx);
+ if (ret) {
+ *minor = ret;
+ return GSS_S_FAILURE;
+ }
+
+ k5_buf_init_dynamic(&ctx->negoex_transcript);
+
+ return GSS_S_COMPLETE;
+}
+
+static void
+release_all_mechs(spnego_gss_ctx_id_t ctx)
+{
+ struct negoex_auth_mech *mech, *next;
+
+ K5_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next)
+ release_auth_mech(mech);
+ K5_TAILQ_INIT(&ctx->negoex_mechs);
+}
+
+void
+negoex_release_context(spnego_gss_ctx_id_t ctx)
+{
+ k5_buf_free(&ctx->negoex_transcript);
+ release_all_mechs(ctx);
+ krb5_free_context(ctx->kctx);
+ ctx->kctx = NULL;
+}
+
+static const char *
+typestr(enum message_type type)
+{
+ if (type == INITIATOR_NEGO)
+ return "INITIATOR_NEGO";
+ else if (type == ACCEPTOR_NEGO)
+ return "ACCEPTOR_NEGO";
+ else if (type == INITIATOR_META_DATA)
+ return "INITIATOR_META_DATA";
+ else if (type == ACCEPTOR_META_DATA)
+ return "ACCEPTOR_META_DATA";
+ else if (type == CHALLENGE)
+ return "CHALLENGE";
+ else if (type == AP_REQUEST)
+ return "AP_REQUEST";
+ else if (type == VERIFY)
+ return "VERIFY";
+ else if (type == ALERT)
+ return "ALERT";
+ else
+ return "UNKNOWN";
+}
+
+static void
+add_guid(struct k5buf *buf, const uint8_t guid[GUID_LENGTH])
+{
+ uint32_t data1 = load_32_le(guid);
+ uint16_t data2 = load_16_le(guid + 4), data3 = load_16_le(guid + 6);
+
+ k5_buf_add_fmt(buf, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ data1, data2, data3, guid[8], guid[9], guid[10], guid[11],
+ guid[12], guid[13], guid[14], guid[15]);
+}
+
+static char *
+guid_to_string(const uint8_t guid[GUID_LENGTH])
+{
+ struct k5buf buf;
+
+ k5_buf_init_dynamic(&buf);
+ add_guid(&buf, guid);
+ return buf.data;
+}
+
+/* Check that the described vector lies within the message, and return a
+ * pointer to its first element. */
+static inline const uint8_t *
+vector_base(size_t offset, size_t count, size_t width,
+ const uint8_t *msg_base, size_t msg_len)
+{
+ if (offset > msg_len || count > (msg_len - offset) / width)
+ return NULL;
+ return msg_base + offset;
+}
+
+/* Trace a received message. Call after the context sequence number is
+ * incremented. */
+static void
+trace_received_message(spnego_gss_ctx_id_t ctx,
+ const struct negoex_message *msg)
+{
+ struct k5buf buf;
+ uint16_t i;
+ char *info = NULL;
+
+ if (msg->type == INITIATOR_NEGO || msg->type == ACCEPTOR_NEGO) {
+ k5_buf_init_dynamic(&buf);
+ for (i = 0; i < msg->u.n.nschemes; i++) {
+ add_guid(&buf, msg->u.n.schemes + i * GUID_LENGTH);
+ if (i + 1 < msg->u.n.nschemes)
+ k5_buf_add(&buf, " ");
+ }
+ info = buf.data;
+ } else if (msg->type == INITIATOR_META_DATA ||
+ msg->type == ACCEPTOR_META_DATA ||
+ msg->type == CHALLENGE || msg->type == AP_REQUEST) {
+ info = guid_to_string(msg->u.e.scheme);
+ } else if (msg->type == VERIFY) {
+ info = guid_to_string(msg->u.v.scheme);
+ } else if (msg->type == ALERT) {
+ info = guid_to_string(msg->u.a.scheme);
+ }
+
+ if (info == NULL)
+ return;
+
+ TRACE_NEGOEX_INCOMING(ctx->kctx, ctx->negoex_seqnum - 1,
+ typestr(msg->type), info);
+ free(info);
+}
+
+/* Trace an outgoing message with a GUID info string. Call after the context
+ * sequence number is incremented. */
+static void
+trace_outgoing_message(spnego_gss_ctx_id_t ctx, enum message_type type,
+ const uint8_t guid[GUID_LENGTH])
+{
+ char *info = guid_to_string(guid);
+
+ if (info == NULL)
+ return;
+ TRACE_NEGOEX_OUTGOING(ctx->kctx, ctx->negoex_seqnum - 1, typestr(type),
+ info);
+ free(info);
+}
+
+static OM_uint32
+parse_nego_message(OM_uint32 *minor, struct k5input *in,
+ const uint8_t *msg_base, size_t msg_len,
+ struct nego_message *msg)
+{
+ const uint8_t *p;
+ uint64_t protocol_version;
+ uint32_t extension_type;
+ size_t offset, count, i;
+
+ p = k5_input_get_bytes(in, sizeof(msg->random));
+ if (p != NULL)
+ memcpy(msg->random, p, sizeof(msg->random));
+ protocol_version = k5_input_get_uint64_le(in);
+ if (protocol_version != 0) {
+ *minor = ERR_NEGOEX_UNSUPPORTED_VERSION;
+ return GSS_S_UNAVAILABLE;
+ }
+
+ offset = k5_input_get_uint32_le(in);
+ count = k5_input_get_uint16_le(in);
+ msg->schemes = vector_base(offset, count, GUID_LENGTH, msg_base, msg_len);
+ msg->nschemes = count;
+ if (msg->schemes == NULL) {
+ *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+
+ offset = k5_input_get_uint32_le(in);
+ count = k5_input_get_uint16_le(in);
+ p = vector_base(offset, count, EXTENSION_LENGTH, msg_base, msg_len);
+ for (i = 0; i < count; i++) {
+ extension_type = load_32_le(p + i * EXTENSION_LENGTH);
+ if (extension_type & EXTENSION_FLAG_CRITICAL) {
+ *minor = ERR_NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION;
+ return GSS_S_UNAVAILABLE;
+ }
+ }
+
+ return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+parse_exchange_message(OM_uint32 *minor, struct k5input *in,
+ const uint8_t *msg_base, size_t msg_len,
+ struct exchange_message *msg)
+{
+ const uint8_t *p;
+ size_t offset, len;
+
+ p = k5_input_get_bytes(in, GUID_LENGTH);
+ if (p != NULL)
+ memcpy(msg->scheme, p, GUID_LENGTH);
+
+ offset = k5_input_get_uint32_le(in);
+ len = k5_input_get_uint32_le(in);
+ p = vector_base(offset, len, 1, msg_base, msg_len);
+ if (p == NULL) {
+ *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+ msg->token.value = (void *)p;
+ msg->token.length = len;
+
+ return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+parse_verify_message(OM_uint32 *minor, struct k5input *in,
+ const uint8_t *msg_base, size_t msg_len,
+ size_t token_offset, struct verify_message *msg)
+{
+ const uint8_t *p;
+ size_t offset, len;
+ uint32_t hdrlen, cksum_scheme;
+
+ p = k5_input_get_bytes(in, GUID_LENGTH);
+ if (p != NULL)
+ memcpy(msg->scheme, p, GUID_LENGTH);
+
+ hdrlen = k5_input_get_uint32_le(in);
+ if (hdrlen != CHECKSUM_HEADER_LENGTH) {
+ *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+ cksum_scheme = k5_input_get_uint32_le(in);
+ if (cksum_scheme != CHECKSUM_SCHEME_RFC3961) {
+ *minor = ERR_NEGOEX_UNKNOWN_CHECKSUM_SCHEME;
+ return GSS_S_UNAVAILABLE;
+ }
+ msg->cksum_type = k5_input_get_uint32_le(in);
+
+ offset = k5_input_get_uint32_le(in);
+ len = k5_input_get_uint32_le(in);
+ msg->cksum = vector_base(offset, len, 1, msg_base, msg_len);
+ msg->cksum_len = len;
+ if (msg->cksum == NULL) {
+ *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+
+ msg->offset_in_token = token_offset;
+ return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+parse_alert_message(OM_uint32 *minor, struct k5input *in,
+ const uint8_t *msg_base, size_t msg_len,
+ struct alert_message *msg)
+{
+ const uint8_t *p;
+ uint32_t atype, reason;
+ size_t alerts_offset, nalerts, value_offset, value_len, i;
+ struct k5input alerts_in, pulse_in;
+
+ p = k5_input_get_bytes(in, GUID_LENGTH);
+ if (p != NULL)
+ memcpy(msg->scheme, p, GUID_LENGTH);
+ (void)k5_input_get_uint32_le(in); /* skip over ErrorCode */
+ alerts_offset = k5_input_get_uint32_le(in);
+ nalerts = k5_input_get_uint32_le(in);
+ p = vector_base(alerts_offset, nalerts, ALERT_LENGTH, msg_base, msg_len);
+ if (p == NULL) {
+ *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+
+ /* Look for a VERIFY_NO_KEY pulse alert in the alerts vector. */
+ msg->verify_no_key = FALSE;
+ k5_input_init(&alerts_in, p, nalerts * ALERT_LENGTH);
+ for (i = 0; i < nalerts; i++) {
+ atype = k5_input_get_uint32_le(&alerts_in);
+ value_offset = k5_input_get_uint32_le(&alerts_in);
+ value_len = k5_input_get_uint32_le(&alerts_in);
+ p = vector_base(value_offset, value_len, 1, msg_base, msg_len);
+ if (p == NULL) {
+ *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+
+ if (atype == ALERT_TYPE_PULSE && value_len >= ALERT_PULSE_LENGTH) {
+ k5_input_init(&pulse_in, p, value_len);
+ (void)k5_input_get_uint32_le(&pulse_in); /* skip header length */
+ reason = k5_input_get_uint32_le(&pulse_in);
+ if (reason == ALERT_VERIFY_NO_KEY)
+ msg->verify_no_key = TRUE;
+ }
+ }
+
+ return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+parse_message(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, struct k5input *in,
+ const uint8_t *token_base, struct negoex_message *msg)
+{
+ OM_uint32 major;
+ const uint8_t *msg_base = in->ptr, *conv_id;
+ size_t token_remaining = in->len, header_len, msg_len;
+ uint64_t signature;
+ uint32_t type, seqnum;
+
+ signature = k5_input_get_uint64_le(in);
+ type = k5_input_get_uint32_le(in);
+ seqnum = k5_input_get_uint32_le(in);
+ header_len = k5_input_get_uint32_le(in);
+ msg_len = k5_input_get_uint32_le(in);
+ conv_id = k5_input_get_bytes(in, GUID_LENGTH);
+
+ if (in->status || msg_len > token_remaining || header_len > msg_len) {
+ *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+ if (signature != MESSAGE_SIGNATURE) {
+ *minor = ERR_NEGOEX_INVALID_MESSAGE_SIGNATURE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+ if (seqnum != ctx->negoex_seqnum) {
+ *minor = ERR_NEGOEX_MESSAGE_OUT_OF_SEQUENCE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+ if (seqnum == 0) {
+ memcpy(ctx->negoex_conv_id, conv_id, GUID_LENGTH);
+ } else if (!GUID_EQ(conv_id, ctx->negoex_conv_id)) {
+ *minor = ERR_NEGOEX_INVALID_CONVERSATION_ID;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+
+ /* Restrict the input region to the header. */
+ in->len = header_len - (in->ptr - msg_base);
+
+ msg->type = type;
+ if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO) {
+ major = parse_nego_message(minor, in, msg_base, msg_len, &msg->u.n);
+ } else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA ||
+ type == CHALLENGE || type == AP_REQUEST) {
+ major = parse_exchange_message(minor, in, msg_base, msg_len,
+ &msg->u.e);
+ } else if (type == VERIFY) {
+ major = parse_verify_message(minor, in, msg_base, msg_len,
+ msg_base - token_base, &msg->u.v);
+ } else if (type == ALERT) {
+ major = parse_alert_message(minor, in, msg_base, msg_len, &msg->u.a);
+ } else {
+ *minor = ERR_NEGOEX_INVALID_MESSAGE_TYPE;
+ return GSS_S_DEFECTIVE_TOKEN;
+ }
+ if (major != GSS_S_COMPLETE)
+ return major;
+
+ /* Reset the input buffer to the remainder of the token. */
+ if (!in->status)
+ k5_input_init(in, msg_base + msg_len, token_remaining - msg_len);
+
+ ctx->negoex_seqnum++;
+ trace_received_message(ctx, msg);
+ return GSS_S_COMPLETE;
+}
+
+/*
+ * Parse token into an array of negoex_message structures. All pointer fields
+ * within the parsed messages are aliases into token, so the result can be
+ * freed with free(). An unknown protocol version, a critical extension, or an
+ * unknown checksum scheme will cause a parsing failure. Increment the
+ * sequence number in ctx for each message, and record and check the
+ * conversation ID in ctx as appropriate.
+ */
+OM_uint32
+negoex_parse_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+ gss_const_buffer_t token,
+ struct negoex_message **messages_out, size_t *count_out)
+{
+ OM_uint32 major;
+ size_t count = 0;
+ struct k5input in;
+ struct negoex_message *messages = NULL, *newptr;
+
+ *messages_out = NULL;
+ *count_out = 0;
+ assert(token != GSS_C_NO_BUFFER);
+ k5_input_init(&in, token->value, token->length);
+
+ while (in.status == 0 && in.len > 0) {
+ newptr = realloc(messages, (count + 1) * sizeof(*newptr));
+ if (newptr == NULL) {
+ free(messages);
+ *minor = ENOMEM;
+ return GSS_S_FAILURE;
+ }
+ messages = newptr;
+
+ major = parse_message(minor, ctx, &in, token->value, &messages[count]);
+ if (major != GSS_S_COMPLETE)
+ break;
+
+ count++;
+ }
+
+ if (in.status) {
+ *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
+ major = GSS_S_DEFECTIVE_TOKEN;
+ }
+ if (major != GSS_S_COMPLETE) {
+ free(messages);
+ return major;
+ }
+
+ *messages_out = messages;
+ *count_out = count;
+ return GSS_S_COMPLETE;
+}
+
+static struct negoex_message *
+locate_message(struct negoex_message *messages, size_t nmessages,
+ enum message_type type)
+{
+ uint32_t i;
+
+ for (i = 0; i < nmessages; i++) {
+ if (messages[i].type == type)
+ return &messages[i];
+ }
+
+ return NULL;
+}
+
+struct nego_message *
+negoex_locate_nego_message(struct negoex_message *messages, size_t nmessages,
+ enum message_type type)
+{
+ struct negoex_message *msg = locate_message(messages, nmessages, type);
+
+ return (msg == NULL) ? NULL : &msg->u.n;
+}
+
+struct exchange_message *
+negoex_locate_exchange_message(struct negoex_message *messages,
+ size_t nmessages, enum message_type type)
+{
+ struct negoex_message *msg = locate_message(messages, nmessages, type);
+
+ return (msg == NULL) ? NULL : &msg->u.e;
+}
+
+struct verify_message *
+negoex_locate_verify_message(struct negoex_message *messages,
+ size_t nmessages)
+{
+ struct negoex_message *msg = locate_message(messages, nmessages, VERIFY);
+
+ return (msg == NULL) ? NULL : &msg->u.v;
+}
+
+struct alert_message *
+negoex_locate_alert_message(struct negoex_message *messages, size_t nmessages)
+{
+ struct negoex_message *msg = locate_message(messages, nmessages, ALERT);
+
+ return (msg == NULL) ? NULL : &msg->u.a;
+}
+
+/*
+ * Add the encoding of a MESSAGE_HEADER structure to buf, given the number of
+ * bytes of the payload following the full header. Increment the sequence
+ * number in ctx. Set *payload_start_out to the position of the payload within
+ * the message.
+ */
+static void
+put_message_header(spnego_gss_ctx_id_t ctx, enum message_type type,
+ uint32_t payload_len, uint32_t *payload_start_out)
+{
+ size_t header_len;
+
+ if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO)
+ header_len = NEGO_MESSAGE_HEADER_LENGTH;
+ else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA ||
+ type == CHALLENGE || type == AP_REQUEST)
+ header_len = EXCHANGE_MESSAGE_HEADER_LENGTH;
+ else if (type == VERIFY)
+ header_len = VERIFY_MESSAGE_HEADER_LENGTH;
+ else if (type == ALERT)
+ header_len = ALERT_MESSAGE_HEADER_LENGTH;
+ else
+ abort();
+
+ k5_buf_add_uint64_le(&ctx->negoex_transcript, MESSAGE_SIGNATURE);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, type);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, ctx->negoex_seqnum++);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, header_len);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, header_len + payload_len);
+ k5_buf_add_len(&ctx->negoex_transcript, ctx->negoex_conv_id, GUID_LENGTH);
+
+ *payload_start_out = header_len;
+}
+
+void
+negoex_add_nego_message(spnego_gss_ctx_id_t ctx, enum message_type type,
+ uint8_t random[32])
+{
+ struct negoex_auth_mech *mech;
+ uint32_t payload_start, seqnum = ctx->negoex_seqnum;
+ uint16_t nschemes;
+ struct k5buf buf;
+
+ nschemes = 0;
+ K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
+ nschemes++;
+
+ put_message_header(ctx, type, nschemes * GUID_LENGTH, &payload_start);
+ k5_buf_add_len(&ctx->negoex_transcript, random, 32);
+ /* ProtocolVersion */
+ k5_buf_add_uint64_le(&ctx->negoex_transcript, 0);
+ /* AuthSchemes vector */
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
+ k5_buf_add_uint16_le(&ctx->negoex_transcript, nschemes);
+ /* Extensions vector */
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
+ k5_buf_add_uint16_le(&ctx->negoex_transcript, 0);
+ /* Four bytes of padding to reach a multiple of 8 bytes. */
+ k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0", 4);
+
+ /* Payload (auth schemes); also build guid string for tracing. */
+ k5_buf_init_dynamic(&buf);
+ K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
+ k5_buf_add_len(&ctx->negoex_transcript, mech->scheme, GUID_LENGTH);
+ add_guid(&buf, mech->scheme);
+ k5_buf_add(&buf, " ");
+ }
+
+ if (buf.len > 0) {
+ k5_buf_truncate(&buf, buf.len - 1);
+ TRACE_NEGOEX_OUTGOING(ctx->kctx, seqnum, typestr(type), buf.data);
+ k5_buf_free(&buf);
+ }
+}
+
+void
+negoex_add_exchange_message(spnego_gss_ctx_id_t ctx, enum message_type type,
+ const auth_scheme scheme, gss_buffer_t token)
+{
+ uint32_t payload_start;
+
+ put_message_header(ctx, type, token->length, &payload_start);
+ k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
+ /* Exchange byte vector */
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, token->length);
+ /* Payload (token) */
+ k5_buf_add_len(&ctx->negoex_transcript, token->value, token->length);
+
+ trace_outgoing_message(ctx, type, scheme);
+}
+
+void
+negoex_add_verify_message(spnego_gss_ctx_id_t ctx, const auth_scheme scheme,
+ uint32_t cksum_type, const uint8_t *cksum,
+ uint32_t cksum_len)
+{
+ uint32_t payload_start;
+
+ put_message_header(ctx, VERIFY, cksum_len, &payload_start);
+ k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, CHECKSUM_HEADER_LENGTH);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, CHECKSUM_SCHEME_RFC3961);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, cksum_type);
+ /* ChecksumValue vector */
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, cksum_len);
+ /* Four bytes of padding to reach a multiple of 8 bytes. */
+ k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0", 4);
+ /* Payload (checksum contents) */
+ k5_buf_add_len(&ctx->negoex_transcript, cksum, cksum_len);
+
+ trace_outgoing_message(ctx, VERIFY, scheme);
+}
+
+/* Add an ALERT_MESSAGE containing a single ALERT_TYPE_PULSE alert with the
+ * reason ALERT_VERIFY_NO_KEY. */
+void
+negoex_add_verify_no_key_alert(spnego_gss_ctx_id_t ctx,
+ const auth_scheme scheme)
+{
+ uint32_t payload_start;
+
+ put_message_header(ctx, ALERT, ALERT_LENGTH + ALERT_PULSE_LENGTH,
+ &payload_start);
+ k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
+ /* ErrorCode */
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, 0);
+ /* Alerts vector */
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
+ k5_buf_add_uint16_le(&ctx->negoex_transcript, 1);
+ /* Six bytes of padding to reach a multiple of 8 bytes. */
+ k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0\0\0", 6);
+ /* Payload part 1: a single ALERT element */
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_TYPE_PULSE);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript,
+ payload_start + ALERT_LENGTH);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_PULSE_LENGTH);
+ /* Payload part 2: ALERT_PULSE */
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_PULSE_LENGTH);
+ k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_VERIFY_NO_KEY);
+
+ trace_outgoing_message(ctx, ALERT, scheme);
+}
+
+static void
+release_auth_mech(struct negoex_auth_mech *mech)
+{
+ OM_uint32 tmpmin;
+
+ if (mech == NULL)
+ return;
+
+ gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL);
+ generic_gss_release_oid(&tmpmin, &mech->oid);
+ gss_release_buffer(&tmpmin, &mech->metadata);
+ krb5_free_keyblock_contents(NULL, &mech->key);
+ krb5_free_keyblock_contents(NULL, &mech->verify_key);
+
+ free(mech);
+}
+
+void
+negoex_delete_auth_mech(spnego_gss_ctx_id_t ctx,
+ struct negoex_auth_mech *mech)
+{
+ K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
+ release_auth_mech(mech);
+}
+
+/* Remove all auth mech entries except for mech from ctx->mechs. */
+void
+negoex_select_auth_mech(spnego_gss_ctx_id_t ctx,
+ struct negoex_auth_mech *mech)
+{
+ assert(mech != NULL);
+ K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
+ release_all_mechs(ctx);
+ K5_TAILQ_INSERT_HEAD(&ctx->negoex_mechs, mech, links);
+}
+
+OM_uint32
+negoex_add_auth_mech(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
+ gss_const_OID oid, auth_scheme scheme)
+{
+ OM_uint32 major;
+ struct negoex_auth_mech *mech;
+
+ mech = calloc(1, sizeof(*mech));
+ if (mech == NULL) {
+ *minor = ENOMEM;
+ return GSS_S_FAILURE;
+ }
+
+ major = generic_gss_copy_oid(minor, (gss_OID)oid, &mech->oid);
+ if (major != GSS_S_COMPLETE) {
+ free(mech);
+ return major;
+ }
+
+ memcpy(mech->scheme, scheme, GUID_LENGTH);
+
+ K5_TAILQ_INSERT_TAIL(&ctx->negoex_mechs, mech, links);
+
+ *minor = 0;
+ return GSS_S_COMPLETE;
+}
+
+struct negoex_auth_mech *
+negoex_locate_auth_scheme(spnego_gss_ctx_id_t ctx, const auth_scheme scheme)
+{
+ struct negoex_auth_mech *mech;
+
+ K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
+ if (GUID_EQ(mech->scheme, scheme))
+ return mech;
+ }
+
+ return NULL;
+}
+
+/* Prune ctx->mechs to the schemes present in schemes, and reorder them to
+ * match its order. */
+void
+negoex_common_auth_schemes(spnego_gss_ctx_id_t ctx,
+ const uint8_t *schemes, uint16_t nschemes)
+{
+ struct negoex_mech_list list;
+ struct negoex_auth_mech *mech;
+ uint16_t i;
+
+ /* Construct a new list in the order of schemes. */
+ K5_TAILQ_INIT(&list);
+ for (i = 0; i < nschemes; i++) {
+ mech = negoex_locate_auth_scheme(ctx, schemes + i * GUID_LENGTH);
+ if (mech == NULL)
+ continue;
+ K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
+ K5_TAILQ_INSERT_TAIL(&list, mech, links);
+ }
+
+ /* Release any leftover entries and replace the context list. */
+ release_all_mechs(ctx);
+ K5_TAILQ_CONCAT(&ctx->negoex_mechs, &list, links);
+}
+
+/* Prune ctx->mechs to the schemes present in schemes, but do not change
+ * their order. */
+void
+negoex_restrict_auth_schemes(spnego_gss_ctx_id_t ctx,
+ const uint8_t *schemes, uint16_t nschemes)
+{
+ struct negoex_auth_mech *mech, *next;
+ uint16_t i;
+ int found;
+
+ K5_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next) {
+ found = FALSE;
+ for (i = 0; i < nschemes && !found; i++) {
+ if (GUID_EQ(mech->scheme, schemes + i * GUID_LENGTH))
+ found = TRUE;
+ }
+
+ if (!found)
+ negoex_delete_auth_mech(ctx, mech);
+ }
+}
diff --git a/src/lib/gssapi/spnego/spnego_mech.c b/src/lib/gssapi/spnego/spnego_mech.c
index 7aa03e7..8e0c3a3 100644
--- a/src/lib/gssapi/spnego/spnego_mech.c
+++ b/src/lib/gssapi/spnego/spnego_mech.c
@@ -98,8 +98,8 @@ static OM_uint32 get_available_mechs(OM_uint32 *, gss_name_t, gss_cred_usage_t,
gss_const_key_value_set_t,
gss_cred_id_t *, gss_OID_set *,
OM_uint32 *);
-static OM_uint32 get_negotiable_mechs(OM_uint32 *, spnego_gss_cred_id_t,
- gss_cred_usage_t, gss_OID_set *);
+static OM_uint32 get_negotiable_mechs(OM_uint32 *, spnego_gss_ctx_id_t,
+ spnego_gss_cred_id_t, gss_cred_usage_t);
static void release_spnego_ctx(spnego_gss_ctx_id_t *);
static spnego_gss_ctx_id_t create_spnego_ctx(int);
static int put_mech_set(gss_OID_set mechSet, gss_buffer_t buf);
@@ -148,7 +148,7 @@ acc_ctx_call_acc(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t,
send_token_flag *);
static gss_OID
-negotiate_mech(gss_OID_set, gss_OID_set, OM_uint32 *);
+negotiate_mech(spnego_gss_ctx_id_t, gss_OID_set, OM_uint32 *);
static int
g_get_tag_and_length(unsigned char **, int, unsigned int, unsigned int *);
@@ -185,6 +185,8 @@ static const gss_OID_set_desc spnego_oidsets[] = {
};
const gss_OID_set_desc * const gss_mech_set_spnego = spnego_oidsets+0;
+static gss_OID_desc negoex_mech = { NEGOEX_OID_LENGTH, NEGOEX_OID };
+
static int make_NegHints(OM_uint32 *, gss_buffer_t *);
static int put_neg_hints(unsigned char **, gss_buffer_t, unsigned int);
static OM_uint32
@@ -331,7 +333,7 @@ create_spnego_cred(OM_uint32 *minor_status, gss_cred_id_t mcred,
spnego_gss_cred_id_t spcred;
*cred_out = NULL;
- spcred = calloc(1, sizeof(spnego_gss_cred_id_rec));
+ spcred = calloc(1, sizeof(*spcred));
if (spcred == NULL) {
*minor_status = ENOMEM;
return GSS_S_FAILURE;
@@ -444,9 +446,8 @@ static spnego_gss_ctx_id_t
create_spnego_ctx(int initiate)
{
spnego_gss_ctx_id_t spnego_ctx = NULL;
- spnego_ctx = (spnego_gss_ctx_id_t)
- malloc(sizeof (spnego_gss_ctx_id_rec));
+ spnego_ctx = malloc(sizeof(*spnego_ctx));
if (spnego_ctx == NULL) {
return (NULL);
}
@@ -467,6 +468,12 @@ create_spnego_ctx(int initiate)
spnego_ctx->internal_name = GSS_C_NO_NAME;
spnego_ctx->actual_mech = GSS_C_NO_OID;
spnego_ctx->deleg_cred = GSS_C_NO_CREDENTIAL;
+ spnego_ctx->negoex_step = 0;
+ memset(&spnego_ctx->negoex_transcript, 0, sizeof(struct k5buf));
+ spnego_ctx->negoex_seqnum = 0;
+ K5_TAILQ_INIT(&spnego_ctx->negoex_mechs);
+ spnego_ctx->kctx = NULL;
+ memset(spnego_ctx->negoex_conv_id, 0, GUID_LENGTH);
return (spnego_ctx);
}
@@ -677,8 +684,7 @@ init_ctx_new(OM_uint32 *minor_status,
return GSS_S_FAILURE;
/* determine negotiation mech set */
- ret = get_negotiable_mechs(minor_status, spcred, GSS_C_INITIATE,
- &sc->mech_set);
+ ret = get_negotiable_mechs(minor_status, sc, spcred, GSS_C_INITIATE);
if (ret != GSS_S_COMPLETE)
goto cleanup;
@@ -912,19 +918,19 @@ init_ctx_call_init(OM_uint32 *minor_status,
if (spcred == NULL || !spcred->no_ask_integ)
mech_req_flags |= GSS_C_INTEG_FLAG;
- ret = gss_init_sec_context(minor_status,
- mcred,
- &sc->ctx_handle,
- target_name,
- sc->internal_mech,
- mech_req_flags,
- time_req,
- GSS_C_NO_CHANNEL_BINDINGS,
- mechtok_in,
- &sc->actual_mech,
- mechtok_out,
- &sc->ctx_flags,
- time_rec);
+ if (gss_oid_equal(sc->internal_mech, &negoex_mech)) {
+ ret = negoex_init(minor_status, sc, mcred, target_name,
+ mech_req_flags, time_req, mechtok_in,
+ mechtok_out, time_rec);
+ } else {
+ ret = gss_init_sec_context(minor_status, mcred,
+ &sc->ctx_handle, target_name,
+ sc->internal_mech, mech_req_flags,
+ time_req, GSS_C_NO_CHANNEL_BINDINGS,
+ mechtok_in, &sc->actual_mech,
+ mechtok_out, &sc->ctx_flags,
+ time_rec);
+ }
/* Bail out if the acceptor gave us an error token but the mech didn't
* see it as an error. */
@@ -1286,22 +1292,15 @@ acc_ctx_hints(OM_uint32 *minor_status,
send_token_flag *return_token,
spnego_gss_ctx_id_t *sc_out)
{
- OM_uint32 tmpmin, ret;
- gss_OID_set supported_mechSet;
+ OM_uint32 ret;
spnego_gss_ctx_id_t sc = NULL;
*mechListMIC = GSS_C_NO_BUFFER;
- supported_mechSet = GSS_C_NO_OID_SET;
*return_token = NO_TOKEN_SEND;
*negState = REJECT;
*minor_status = 0;
*sc_out = NULL;
- ret = get_negotiable_mechs(minor_status, spcred, GSS_C_ACCEPT,
- &supported_mechSet);
- if (ret != GSS_S_COMPLETE)
- goto cleanup;
-
ret = make_NegHints(minor_status, mechListMIC);
if (ret != GSS_S_COMPLETE)
goto cleanup;
@@ -1311,7 +1310,12 @@ acc_ctx_hints(OM_uint32 *minor_status,
ret = GSS_S_FAILURE;
goto cleanup;
}
- if (put_mech_set(supported_mechSet, &sc->DER_mechTypes) < 0) {
+
+ ret = get_negotiable_mechs(minor_status, sc, spcred, GSS_C_ACCEPT);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+
+ if (put_mech_set(sc->mech_set, &sc->DER_mechTypes) < 0) {
ret = GSS_S_FAILURE;
goto cleanup;
}
@@ -1326,7 +1330,6 @@ acc_ctx_hints(OM_uint32 *minor_status,
cleanup:
release_spnego_ctx(&sc);
- gss_release_oid_set(&tmpmin, &supported_mechSet);
return ret;
}
@@ -1348,7 +1351,7 @@ acc_ctx_new(OM_uint32 *minor_status,
spnego_gss_ctx_id_t *sc_out)
{
OM_uint32 tmpmin, ret, req_flags;
- gss_OID_set supported_mechSet, mechTypes;
+ gss_OID_set mechTypes;
gss_buffer_desc der_mechTypes;
gss_OID mech_wanted;
spnego_gss_ctx_id_t sc = NULL;
@@ -1357,7 +1360,7 @@ acc_ctx_new(OM_uint32 *minor_status,
der_mechTypes.length = 0;
der_mechTypes.value = NULL;
*mechToken = *mechListMIC = GSS_C_NO_BUFFER;
- supported_mechSet = mechTypes = GSS_C_NO_OID_SET;
+ mechTypes = GSS_C_NO_OID_SET;
*return_token = ERROR_TOKEN_SEND;
*negState = REJECT;
*minor_status = 0;
@@ -1368,8 +1371,15 @@ acc_ctx_new(OM_uint32 *minor_status,
if (ret != GSS_S_COMPLETE) {
goto cleanup;
}
- ret = get_negotiable_mechs(minor_status, spcred, GSS_C_ACCEPT,
- &supported_mechSet);
+
+ sc = create_spnego_ctx(0);
+ if (sc == NULL) {
+ ret = GSS_S_FAILURE;
+ *return_token = NO_TOKEN_SEND;
+ goto cleanup;
+ }
+
+ ret = get_negotiable_mechs(minor_status, sc, spcred, GSS_C_INITIATE);
if (ret != GSS_S_COMPLETE) {
*return_token = NO_TOKEN_SEND;
goto cleanup;
@@ -1379,19 +1389,12 @@ acc_ctx_new(OM_uint32 *minor_status,
* that the initiator requested and the list that
* the acceptor will support.
*/
- mech_wanted = negotiate_mech(supported_mechSet, mechTypes, negState);
+ mech_wanted = negotiate_mech(sc, mechTypes, negState);
if (*negState == REJECT) {
ret = GSS_S_BAD_MECH;
goto cleanup;
}
- sc = create_spnego_ctx(0);
- if (sc == NULL) {
- ret = GSS_S_FAILURE;
- *return_token = NO_TOKEN_SEND;
- goto cleanup;
- }
- sc->mech_set = mechTypes;
- mechTypes = GSS_C_NO_OID_SET;
+
sc->internal_mech = mech_wanted;
sc->DER_mechTypes = der_mechTypes;
der_mechTypes.length = 0;
@@ -1403,10 +1406,12 @@ acc_ctx_new(OM_uint32 *minor_status,
*return_token = INIT_TOKEN_SEND;
sc->firstpass = 1;
*sc_out = sc;
+ sc = NULL;
ret = GSS_S_COMPLETE;
+
cleanup:
+ release_spnego_ctx(&sc);
gss_release_oid_set(&tmpmin, &mechTypes);
- gss_release_oid_set(&tmpmin, &supported_mechSet);
if (der_mechTypes.length != 0)
gss_release_buffer(&tmpmin, &der_mechTypes);
@@ -1543,8 +1548,9 @@ acc_ctx_call_acc(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
OM_uint32 ret, tmpmin;
gss_OID_desc mechoid;
gss_cred_id_t mcred;
+ int negoex = gss_oid_equal(sc->internal_mech, &negoex_mech);
- if (sc->ctx_handle == GSS_C_NO_CONTEXT) {
+ if (sc->ctx_handle == GSS_C_NO_CONTEXT && !negoex) {
/*
* mechoid is an alias; don't free it.
*/
@@ -1562,11 +1568,18 @@ acc_ctx_call_acc(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred;
(void) gss_release_name(&tmpmin, &sc->internal_name);
(void) gss_release_cred(&tmpmin, &sc->deleg_cred);
- ret = gss_accept_sec_context(minor_status, &sc->ctx_handle, mcred,
- mechtok_in, GSS_C_NO_CHANNEL_BINDINGS,
- &sc->internal_name, &sc->actual_mech,
- mechtok_out, &sc->ctx_flags, time_rec,
- &sc->deleg_cred);
+ if (negoex) {
+ ret = negoex_accept(minor_status, sc, mcred, mechtok_in,
+ mechtok_out, time_rec);
+ } else {
+ ret = gss_accept_sec_context(minor_status, &sc->ctx_handle,
+ mcred, mechtok_in,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &sc->internal_name,
+ &sc->actual_mech, mechtok_out,
+ &sc->ctx_flags, time_rec,
+ &sc->deleg_cred);
+ }
if (ret == GSS_S_COMPLETE) {
#ifdef MS_BUG_TEST
/*
@@ -1801,6 +1814,50 @@ cleanup:
}
#endif /* LEAN_CLIENT */
+static struct {
+ OM_uint32 status;
+ const char *msg;
+} msg_table[] = {
+ { ERR_SPNEGO_NO_MECHS_AVAILABLE,
+ N_("SPNEGO cannot find mechanisms to negotiate") },
+ { ERR_SPNEGO_NO_CREDS_ACQUIRED,
+ N_("SPNEGO failed to acquire creds") },
+ { ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR,
+ N_("SPNEGO acceptor did not select a mechanism") },
+ { ERR_SPNEGO_NEGOTIATION_FAILED,
+ N_("SPNEGO failed to negotiate a mechanism") },
+ { ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR,
+ N_("SPNEGO acceptor did not return a valid token") },
+ { ERR_NEGOEX_INVALID_MESSAGE_SIGNATURE,
+ N_("Invalid NegoEx signature") },
+ { ERR_NEGOEX_INVALID_MESSAGE_TYPE,
+ N_("Invalid NegoEx message type") },
+ { ERR_NEGOEX_INVALID_MESSAGE_SIZE,
+ N_("Invalid NegoEx message size") },
+ { ERR_NEGOEX_INVALID_CONVERSATION_ID,
+ N_("Invalid NegoEx conversation ID") },
+ { ERR_NEGOEX_AUTH_SCHEME_NOT_FOUND,
+ N_("NegoEx authentication scheme not found") },
+ { ERR_NEGOEX_MISSING_NEGO_MESSAGE,
+ N_("Missing NegoEx negotiate message") },
+ { ERR_NEGOEX_MISSING_AP_REQUEST_MESSAGE,
+ N_("Missing NegoEx authentication protocol request message") },
+ { ERR_NEGOEX_NO_AVAILABLE_MECHS,
+ N_("No mutually supported NegoEx authentication schemes") },
+ { ERR_NEGOEX_NO_VERIFY_KEY,
+ N_("No NegoEx verify key") },
+ { ERR_NEGOEX_UNKNOWN_CHECKSUM_SCHEME,
+ N_("Unknown NegoEx checksum scheme") },
+ { ERR_NEGOEX_INVALID_CHECKSUM,
+ N_("Invalid NegoEx checksum") },
+ { ERR_NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION,
+ N_("Unsupported critical NegoEx extension") },
+ { ERR_NEGOEX_UNSUPPORTED_VERSION,
+ N_("Unsupported NegoEx version") },
+ { ERR_NEGOEX_MESSAGE_OUT_OF_SEQUENCE,
+ N_("NegoEx message out of sequence") },
+};
+
/*ARGSUSED*/
OM_uint32 KRB5_CALLCONV
spnego_gss_display_status(
@@ -1812,62 +1869,40 @@ spnego_gss_display_status(
gss_buffer_t status_string)
{
OM_uint32 maj = GSS_S_COMPLETE;
+ const char *msg;
+ size_t i;
int ret;
- dsyslog("Entering display_status\n");
-
*message_context = 0;
- switch (status_value) {
- case ERR_SPNEGO_NO_MECHS_AVAILABLE:
- /* CSTYLED */
- *status_string = make_err_msg(_("SPNEGO cannot find "
- "mechanisms to negotiate"));
- break;
- case ERR_SPNEGO_NO_CREDS_ACQUIRED:
- /* CSTYLED */
- *status_string = make_err_msg(_("SPNEGO failed to acquire "
- "creds"));
- break;
- case ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR:
- /* CSTYLED */
- *status_string = make_err_msg(_("SPNEGO acceptor did not "
- "select a mechanism"));
- break;
- case ERR_SPNEGO_NEGOTIATION_FAILED:
- /* CSTYLED */
- *status_string = make_err_msg(_("SPNEGO failed to negotiate a "
- "mechanism"));
- break;
- case ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR:
- /* CSTYLED */
- *status_string = make_err_msg(_("SPNEGO acceptor did not "
- "return a valid token"));
- break;
- default:
- /* Not one of our minor codes; might be from a mech. Call back
- * to gss_display_status, but first check for recursion. */
- if (k5_getspecific(K5_KEY_GSS_SPNEGO_STATUS) != NULL) {
- /* Perhaps we returned a com_err code like ENOMEM. */
- const char *err = error_message(status_value);
- *status_string = make_err_msg(err);
- break;
- }
- /* Set a non-null pointer value; doesn't matter which one. */
- ret = k5_setspecific(K5_KEY_GSS_SPNEGO_STATUS, &ret);
- if (ret != 0) {
- *minor_status = ret;
- maj = GSS_S_FAILURE;
- break;
+ for (i = 0; i < sizeof(msg_table) / sizeof(*msg_table); i++) {
+ if (status_value == msg_table[i].status) {
+ msg = dgettext(KRB5_TEXTDOMAIN, msg_table[i].msg);
+ *status_string = make_err_msg(msg);
+ return GSS_S_COMPLETE;
}
- maj = gss_display_status(minor_status, status_value,
- status_type, mech_type,
- message_context, status_string);
- /* This is unlikely to fail; not much we can do if it does. */
- (void)k5_setspecific(K5_KEY_GSS_SPNEGO_STATUS, NULL);
- break;
}
- dsyslog("Leaving display_status\n");
+ /* Not one of our minor codes; might be from a mech. Call back
+ * to gss_display_status, but first check for recursion. */
+ if (k5_getspecific(K5_KEY_GSS_SPNEGO_STATUS) != NULL) {
+ /* Perhaps we returned a com_err code like ENOMEM. */
+ const char *err = error_message(status_value);
+ *status_string = make_err_msg(err);
+ return GSS_S_COMPLETE;
+ }
+ /* Set a non-null pointer value; doesn't matter which one. */
+ ret = k5_setspecific(K5_KEY_GSS_SPNEGO_STATUS, &ret);
+ if (ret != 0) {
+ *minor_status = ret;
+ return GSS_S_FAILURE;
+ }
+
+ maj = gss_display_status(minor_status, status_value,
+ status_type, mech_type,
+ message_context, status_string);
+ /* This is unlikely to fail; not much we can do if it does. */
+ (void)k5_setspecific(K5_KEY_GSS_SPNEGO_STATUS, NULL);
+
return maj;
}
@@ -2899,6 +2934,12 @@ spnego_gss_set_neg_mechs(OM_uint32 *minor_status,
gss_release_oid_set(minor_status, &spcred->neg_mechs);
ret = generic_gss_copy_oid_set(minor_status, mech_list,
&spcred->neg_mechs);
+ if (ret == GSS_S_COMPLETE) {
+ (void) gss_set_neg_mechs(minor_status,
+ spcred->mcred,
+ spcred->neg_mechs);
+ }
+
return (ret);
}
@@ -3076,6 +3117,8 @@ release_spnego_ctx(spnego_gss_ctx_id_t *ctx)
(void) gss_release_name(&minor_stat, &context->internal_name);
(void) gss_release_cred(&minor_stat, &context->deleg_cred);
+ negoex_release_context(context);
+
free(context);
*ctx = NULL;
}
@@ -3086,7 +3129,12 @@ release_spnego_ctx(spnego_gss_ctx_id_t *ctx)
* SPNEGO because it will also return the SPNEGO mech and we do not
* want to consider SPNEGO as an available security mech for
* negotiation. For this reason, get_available_mechs will return
- * all available, non-deprecated mechs except SPNEGO.
+ * all available, non-deprecated mechs except SPNEGO and NegoEx-
+ * only mechanisms.
+ *
+ * Note that gss_acquire_cred_from(GSS_C_NO_OID_SET) will filter
+ * out hidden (GSS_C_MA_NOT_INDICATED) mechanisms such as NegoEx, so
+ * calling gss_indicate_mechs_by_attrs() also works around that.
*
* If a ptr to a creds list is given, this function will attempt
* to acquire creds for the creds given and trim the list of
@@ -3152,77 +3200,121 @@ get_available_mechs(OM_uint32 *minor_status,
return (major_status);
}
+/* Return true if mech asserts the GSS_C_MA_NEGOEX_AND_SPNEGO attribute. */
+static int
+negoex_and_spnego(gss_OID mech)
+{
+ OM_uint32 ret, minor;
+ gss_OID_set attrs;
+ int present;
+
+ ret = gss_inquire_attrs_for_mech(&minor, mech, &attrs, NULL);
+ if (ret != GSS_S_COMPLETE || attrs == GSS_C_NO_OID_SET)
+ return 0;
+
+ (void) generic_gss_test_oid_set_member(&minor,
+ GSS_C_MA_NEGOEX_AND_SPNEGO,
+ attrs, &present);
+ (void) gss_release_oid_set(&minor, &attrs);
+ return present;
+}
+
/*
- * Return a list of mechanisms we are willing to negotiate for a credential,
- * taking into account the mech set provided with gss_set_neg_mechs if it
- * exists.
+ * Fill sc->mech_set with the SPNEGO-negotiable mechanism OIDs, and
+ * sc->negoex_mechs with an entry for each NegoEx-negotiable mechanism. Take
+ * into account the mech set provided with gss_set_neg_mechs() if it exists.
*/
static OM_uint32
-get_negotiable_mechs(OM_uint32 *minor_status, spnego_gss_cred_id_t spcred,
- gss_cred_usage_t usage, gss_OID_set *rmechs)
+get_negotiable_mechs(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
+ spnego_gss_cred_id_t spcred, gss_cred_usage_t usage)
{
OM_uint32 ret, tmpmin;
gss_cred_id_t creds = GSS_C_NO_CREDENTIAL;
- gss_OID_set cred_mechs = GSS_C_NULL_OID_SET;
- gss_OID_set intersect_mechs = GSS_C_NULL_OID_SET;
+ gss_OID_set cred_mechs = GSS_C_NULL_OID_SET, mechs;
unsigned int i;
- int present;
+ int present, added_negoex = 0;
+ auth_scheme scheme;
- if (spcred == NULL) {
- /* The default credentials were supplied. Return a list of all
- * permissible mechs we can acquire a cred for. */
+ if (spcred != NULL) {
+ /* Get the list of mechs in the mechglue cred. */
+ ret = gss_inquire_cred(minor_status, spcred->mcred, NULL,
+ NULL, NULL, &cred_mechs);
+ if (ret != GSS_S_COMPLETE)
+ return (ret);
+ } else {
+ /* Start with the list of available mechs. */
ret = get_available_mechs(minor_status, GSS_C_NO_NAME, usage,
GSS_C_NO_CRED_STORE, &creds,
- rmechs, NULL);
+ &cred_mechs, NULL);
+ if (ret != GSS_S_COMPLETE)
+ return (ret);
gss_release_cred(&tmpmin, &creds);
- return (ret);
}
- /* Get the list of mechs in the mechglue cred. */
- ret = gss_inquire_cred(minor_status, spcred->mcred, NULL, NULL, NULL,
- &cred_mechs);
+ /* If gss_set_neg_mechs() was called, use that to determine the
+ * iteration order. Otherwise iterate over the credential mechs. */
+ mechs = (spcred != NULL && spcred->neg_mechs != GSS_C_NULL_OID_SET) ?
+ spcred->neg_mechs : cred_mechs;
+
+ ret = gss_create_empty_oid_set(minor_status, &sc->mech_set);
if (ret != GSS_S_COMPLETE)
- return (ret);
+ goto cleanup;
- if (spcred->neg_mechs == GSS_C_NULL_OID_SET) {
- /* gss_set_neg_mechs was never called; return cred_mechs. */
- *rmechs = cred_mechs;
- *minor_status = 0;
- return (GSS_S_COMPLETE);
- }
+ for (i = 0; i < mechs->count; i++) {
+ if (mechs != cred_mechs) {
+ /* Intersect neg_mechs with cred_mechs. */
+ gss_test_oid_set_member(&tmpmin, &mechs->elements[i],
+ cred_mechs, &present);
+ if (!present)
+ continue;
+ }
- /* Compute the intersection of cred_mechs and spcred->neg_mechs,
- * preserving the order in spcred->neg_mechs. */
- ret = gss_create_empty_oid_set(minor_status, &intersect_mechs);
- if (ret != GSS_S_COMPLETE) {
- gss_release_oid_set(&tmpmin, &cred_mechs);
- return (ret);
- }
+ /* Query the auth scheme to see if this is a NegoEx mech. */
+ ret = gssspi_query_mechanism_info(&tmpmin, &mechs->elements[i],
+ scheme);
+ if (ret == GSS_S_COMPLETE) {
+ /* Add an entry for this mech to the NegoEx list. */
+ ret = negoex_add_auth_mech(minor_status, sc,
+ &mechs->elements[i],
+ scheme);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+
+ /* Add the NegoEx OID to the SPNEGO list at the
+ * position of the first NegoEx mechanism. */
+ if (!added_negoex) {
+ ret = gss_add_oid_set_member(minor_status,
+ &negoex_mech,
+ &sc->mech_set);
+ if (ret != GSS_S_COMPLETE)
+ goto cleanup;
+ added_negoex = 1;
+ }
- for (i = 0; i < spcred->neg_mechs->count; i++) {
- gss_test_oid_set_member(&tmpmin,
- &spcred->neg_mechs->elements[i],
- cred_mechs, &present);
- if (!present)
- continue;
- ret = gss_add_oid_set_member(minor_status,
- &spcred->neg_mechs->elements[i],
- &intersect_mechs);
+ /* Skip this mech in the SPNEGO list unless it asks for
+ * direct SPNEGO negotiation. */
+ if (!negoex_and_spnego(&mechs->elements[i]))
+ continue;
+ }
+
+ /* Add this mech to the SPNEGO list. */
+ ret = gss_add_oid_set_member(minor_status, &mechs->elements[i],
+ &sc->mech_set);
if (ret != GSS_S_COMPLETE)
- break;
+ goto cleanup;
}
- gss_release_oid_set(&tmpmin, &cred_mechs);
- if (intersect_mechs->count == 0 || ret != GSS_S_COMPLETE) {
- gss_release_oid_set(&tmpmin, &intersect_mechs);
+ *minor_status = 0;
+
+cleanup:
+ if (ret != GSS_S_COMPLETE || sc->mech_set->count == 0) {
*minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE;
map_errcode(minor_status);
- return (GSS_S_FAILURE);
+ ret = GSS_S_FAILURE;
}
- *rmechs = intersect_mechs;
- *minor_status = 0;
- return (GSS_S_COMPLETE);
+ gss_release_oid_set(&tmpmin, &cred_mechs);
+ return (ret);
}
/* following are token creation and reading routines */
@@ -3680,23 +3772,30 @@ put_negResult(unsigned char **buf_out, OM_uint32 negResult,
* mechanisms supported by the acceptor.
*/
static gss_OID
-negotiate_mech(gss_OID_set supported, gss_OID_set received,
+negotiate_mech(spnego_gss_ctx_id_t ctx, gss_OID_set received,
OM_uint32 *negResult)
{
size_t i, j;
+ int wrong_krb5_oid;
for (i = 0; i < received->count; i++) {
gss_OID mech_oid = &received->elements[i];
/* Accept wrong mechanism OID from MS clients */
- if (g_OID_equal(mech_oid, &gss_mech_krb5_wrong_oid))
+ wrong_krb5_oid = 0;
+ if (g_OID_equal(mech_oid, &gss_mech_krb5_wrong_oid)) {
mech_oid = (gss_OID)&gss_mech_krb5_oid;
+ wrong_krb5_oid = 1;
+ }
- for (j = 0; j < supported->count; j++) {
- if (g_OID_equal(mech_oid, &supported->elements[j])) {
+ for (j = 0; j < ctx->mech_set->count; j++) {
+ if (g_OID_equal(mech_oid,
+ &ctx->mech_set->elements[j])) {
*negResult = (i == 0) ? ACCEPT_INCOMPLETE :
REQUEST_MIC;
- return &received->elements[i];
+ return wrong_krb5_oid ?
+ (gss_OID)&gss_mech_krb5_wrong_oid :
+ &ctx->mech_set->elements[j];
}
}
}