krb5 commit: Implement NegoEx

Greg Hudson ghudson at mit.edu
Fri Dec 6 15:09:49 EST 2019


https://github.com/krb5/krb5/commit/c2ca2f26eaf817a6a7ed42257c380437ab802bd9
commit c2ca2f26eaf817a6a7ed42257c380437ab802bd9
Author: Luke Howard <lukeh at padl.com>
Date:   Wed Dec 26 22:52:18 2018 +1100

    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

 doc/plugindev/gssapi.rst                |   25 +
 src/include/k5-trace.h                  |    5 +
 src/lib/gssapi/generic/gssapi_ext.h     |   45 ++
 src/lib/gssapi/generic/gssapi_generic.c |  129 +++---
 src/lib/gssapi/libgssapi_krb5.exports   |    3 +
 src/lib/gssapi/mechglue/Makefile.in     |    4 +
 src/lib/gssapi/mechglue/deps            |   17 +-
 src/lib/gssapi/mechglue/g_initialize.c  |    4 +
 src/lib/gssapi/mechglue/g_negoex.c      |  237 +++++++++
 src/lib/gssapi/mechglue/mglueP.h        |   31 ++
 src/lib/gssapi/spnego/Makefile.in       |    7 +-
 src/lib/gssapi/spnego/deps              |   36 ++-
 src/lib/gssapi/spnego/gssapiP_negoex.h  |  210 ++++++++
 src/lib/gssapi/spnego/gssapiP_spnego.h  |   41 ++-
 src/lib/gssapi/spnego/negoex_ctx.c      |  785 ++++++++++++++++++++++++++++++
 src/lib/gssapi/spnego/negoex_trace.c    |  121 +++++
 src/lib/gssapi/spnego/negoex_util.c     |  812 +++++++++++++++++++++++++++++++
 src/lib/gssapi/spnego/spnego_mech.c     |  407 ++++++++++------
 18 files changed, 2691 insertions(+), 228 deletions(-)

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


More information about the cvs-krb5 mailing list