From: Luke Howard Date: Wed, 26 Dec 2018 11:52:18 +0000 (+1100) Subject: Implement NegoEx X-Git-Tag: krb5-1.18-beta1~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c2ca2f26eaf817a6a7ed42257c380437ab802bd9;p=thirdparty%2Fkrb5.git 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 --- diff --git a/doc/plugindev/gssapi.rst b/doc/plugindev/gssapi.rst index 28e62ae8c9..cb1f462f86 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 + ````. + +* 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 d08e5ece47..1da53dbb10 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 d3fd2a59b7..218456e44a 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 1b362c3d8b..3601585fbe 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 c292cb1af9..166acfa34e 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 5e1ed6df6a..33d931938e 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 26f62aa8a2..95fd7f4ead 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 120d73e2f2..2f9ce7a47a 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 0000000000..9360fa4b1b --- /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 c296354d5c..f16333cd67 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 c21ea230ca..b44ad0e0ac 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 feb409e732..1b5daff800 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 0000000000..44b08f523d --- /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 cad47eefa6..a937633145 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 +#include +#include +#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 0000000000..e69b7200e3 --- /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 + +/* + * 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 0000000000..04ed992b5e --- /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 0000000000..7003684561 --- /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 +#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 7aa03e7e69..8e0c3a3488 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]; } } }