gss: port NegoEx implementation from MIT
authorLuke Howard <lukeh@padl.com>
Mon, 30 Dec 2019 02:34:10 +0000 (13:34 +1100)
committerLuke Howard <lukeh@padl.com>
Tue, 4 Feb 2020 06:28:35 +0000 (17:28 +1100)
An implementation of draft-zhu-negoex-04 for MIT Kerberos was developed in
2011. This has been recently integrated, with many fixes from Greg Hudson. This
commit ports it to Heimdal. The implementation has been interoperability tested
with MIT Kerberos and Windows, using the GSS EAP mechanism developed as part of
the Moonshot project.

The SPNEGO code was also updated to import the state machine from Apple which
improves mechListMIC processing and avoids discarding initial context tokens
generated during mechanism probing, that can be used for optimistic tokens.

Finally, to aid in testing, the GSS-API mechanism glue configuration file can
be changed using the environment variable GSS_MECH_CONFIG. This environment
variable name, along with the format of the configuration file, is compatible
with MIT (although it would be difficult for a single mechanism binary to
support both implementations).

45 files changed:
doc/setup.texi
doc/standardisation/draft-zhu-negoex-04.txt [new file with mode: 0644]
include/Makefile.am
lib/gssapi/Makefile.am
lib/gssapi/NTMakefile
lib/gssapi/gssapi/gssapi_oid.h
lib/gssapi/gssapi/gssapi_spnego.h
lib/gssapi/gssapi_mech.h
lib/gssapi/krb5/external.c
lib/gssapi/libgssapi-exports.def
lib/gssapi/mech/context.c
lib/gssapi/mech/gss_init_sec_context.c
lib/gssapi/mech/gss_inquire_cred.c
lib/gssapi/mech/gss_mech_switch.c
lib/gssapi/mech/gss_oid.c
lib/gssapi/mech/gss_utils.c
lib/gssapi/mech/gssspi_exchange_meta_data.c [new file with mode: 0644]
lib/gssapi/mech/gssspi_query_mechanism_info.c [new file with mode: 0644]
lib/gssapi/mech/gssspi_query_meta_data.c [new file with mode: 0644]
lib/gssapi/mech/mech_locl.h
lib/gssapi/mech/utils.h
lib/gssapi/ntlm/external.c
lib/gssapi/oid.txt
lib/gssapi/spnego/accept_sec_context.c
lib/gssapi/spnego/compat.c
lib/gssapi/spnego/context_stubs.c
lib/gssapi/spnego/cred_stubs.c
lib/gssapi/spnego/external.c
lib/gssapi/spnego/init_sec_context.c
lib/gssapi/spnego/negoex_ctx.c [new file with mode: 0644]
lib/gssapi/spnego/negoex_err.et [new file with mode: 0644]
lib/gssapi/spnego/negoex_locl.h [new file with mode: 0644]
lib/gssapi/spnego/negoex_util.c [new file with mode: 0644]
lib/gssapi/spnego/spnego.asn1
lib/gssapi/spnego/spnego_locl.h
lib/gssapi/test_context.c
lib/gssapi/test_negoex_mech.c [new file with mode: 0644]
lib/gssapi/version-script.map
tests/bin/setup-env.in
tests/gss/Makefile.am
tests/gss/check-gssmask.in
tests/gss/check-negoex.in [new file with mode: 0644]
tests/gss/check-ntlm.in
tests/gss/check-spnego.in
tests/gss/mech.in [new file with mode: 0644]

index f2fb07ec3861f02705cecd37da2dda47b6170fa0..0b3a860edb18cf1d19e6ce3aba658bd0120834b5 100644 (file)
@@ -121,6 +121,12 @@ can point a file with the environment variable @samp{KRB5_CONFIG}.
 env KRB5_CONFIG=$HOME/etc/krb5.conf kinit user@@REALM
 @end example
 
+@cindex GSS_MECH_CONFIG
+The GSS-API mechanism configuration file can also be changed from the
+default with the enviornment variable @samp{GSS_MECH_CONFIG}. Note that
+this file only configures additional plugin mechanisms: Kerberos, NTLM
+and SPNEGO are built in to the Heimdal GSS-API library.
+
 @node Creating the database, Modifying the database, Configuration file, Setting up a realm
 @section Creating the database
 
diff --git a/doc/standardisation/draft-zhu-negoex-04.txt b/doc/standardisation/draft-zhu-negoex-04.txt
new file mode 100644 (file)
index 0000000..17f189f
--- /dev/null
@@ -0,0 +1,1345 @@
+
+
+
+NETWORK WORKING GROUP                                           M. Short
+Internet-Draft                                                    L. Zhu
+Updates: 4178 (if approved)                                    K. Damour
+Intended status: Standards Track                            D. McPherson
+Expires: July 7, 2011                              Microsoft Corporation
+                                                         January 3, 2011
+
+
+        SPNEGO Extended Negotiation (NEGOEX) Security Mechanism
+                          draft-zhu-negoex-04
+
+Abstract
+
+   This document defines the SPNEGO Extended Negotiation (NEGOEX)
+   Security Mechanism.  NEGOEX enhances the capabilities of SPNEGO by
+   providing a security mechanism which can be negotiated by the SPNEGO
+   protocol as defined in RFC4178.
+
+   The NEGOEX protocol itself is a security mechanism negotiated by
+   SPNEGO.  When the NEGOEX security mechanism is selected by SPNEGO,
+   NEGOEX provides a method allowing selection of a common
+   authentication protocol based on factors beyond just the fact that
+   both client and server support a given security mechanism.  NEGOEX
+   OPTIONALLY adds a pair of meta-data messages for each negotiated
+   security mechanism.  The meta-data exchange allows security
+   mechanisms to exchange auxiliary information such as trust
+   configurations, thus NEGOEX provides more flexibility than just
+   exchanging security mechanism OIDs in SPNEGO.
+
+   NEGOEX preserves the optimistic token semantics of SPNEGO and applies
+   that recursively.  Consequently a context establishment mechanism
+   token can be included in the initial NEGOEX message, and NEGOEX does
+   not require an extra round-trip when the initiator's optimistic token
+   is accepted by the target.
+
+   Similar to SPNEGO, NEGOEX defines a few new GSS-API extensions that a
+   security mechanism MUST support in order to be negotiated by NEGOEX.
+   This document defines these GSS-API extensions.
+
+   Unlike SPNEGO however, NEGOEX defines its own way for signing the
+   protocol messages in order to protect the protocol negotiation.  The
+   NEGOEX message signing or verification can occur before the security
+   context for the negotiated real security mechanism is fully
+   established.
+
+Status of this Memo
+
+   This Internet-Draft is submitted in full conformance with the
+
+
+
+Short, et al.             Expires July 7, 2011                  [Page 1]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+   provisions of BCP 78 and BCP 79.
+
+   Internet-Drafts are working documents of the Internet Engineering
+   Task Force (IETF).  Note that other groups may also distribute
+   working documents as Internet-Drafts.  The list of current Internet-
+   Drafts is at http://datatracker.ietf.org/drafts/current/.
+
+   Internet-Drafts are draft documents valid for a maximum of six months
+   and may be updated, replaced, or obsoleted by other documents at any
+   time.  It is inappropriate to use Internet-Drafts as reference
+   material or to cite them other than as "work in progress."
+
+   This Internet-Draft will expire on July 7, 2011.
+
+Copyright Notice
+
+   Copyright (c) 2011 IETF Trust and the persons identified as the
+   document authors.  All rights reserved.
+
+   This document is subject to BCP 78 and the IETF Trust's Legal
+   Provisions Relating to IETF Documents
+   (http://trustee.ietf.org/license-info) in effect on the date of
+   publication of this document.  Please review these documents
+   carefully, as they describe your rights and restrictions with respect
+   to this document.  Code Components extracted from this document must
+   include Simplified BSD License text as described in Section 4.e of
+   the Trust Legal Provisions and are provided without warranty as
+   described in the Simplified BSD License.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Short, et al.             Expires July 7, 2011                  [Page 2]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+Table of Contents
+
+   1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  4
+   2.  Requirements Terminology . . . . . . . . . . . . . . . . . . .  6
+   3.  Presentation Language and Primitive Data Types . . . . . . . .  7
+     3.1.  Basic Block Size . . . . . . . . . . . . . . . . . . . . .  7
+     3.2.  Miscellaneous  . . . . . . . . . . . . . . . . . . . . . .  7
+     3.3.  Constants  . . . . . . . . . . . . . . . . . . . . . . . .  7
+     3.4.  Numbers  . . . . . . . . . . . . . . . . . . . . . . . . .  7
+     3.5.  Enum Types . . . . . . . . . . . . . . . . . . . . . . . .  7
+     3.6.  Typedef Declarations . . . . . . . . . . . . . . . . . . .  8
+     3.7.  Array Types  . . . . . . . . . . . . . . . . . . . . . . .  8
+     3.8.  Constructed Types  . . . . . . . . . . . . . . . . . . . .  8
+   4.  Vector Types . . . . . . . . . . . . . . . . . . . . . . . . . 10
+   5.  NEGOEX Messages  . . . . . . . . . . . . . . . . . . . . . . . 11
+   6.  Cryptographic Computations . . . . . . . . . . . . . . . . . . 12
+   7.  The NEGOEX Protocol  . . . . . . . . . . . . . . . . . . . . . 12
+     7.1.  High-level NEGOEX Message Flow . . . . . . . . . . . . . . 12
+     7.2.  NEGOEX Supported Security Mechanisms . . . . . . . . . . . 13
+     7.3.  ConversationID . . . . . . . . . . . . . . . . . . . . . . 13
+     7.4.  Generation of the Initiator Initial Token  . . . . . . . . 13
+     7.5.  Receipt of the Initial Initiator Token and Generation
+           of the Initial Acceptor Response . . . . . . . . . . . . . 15
+     7.6.  Receipt of the Acceptor Initial Response and
+           Completion of Authentication after the Negotiation
+           Phrase . . . . . . . . . . . . . . . . . . . . . . . . . . 16
+     7.7.  Finalizing Negotiation . . . . . . . . . . . . . . . . . . 16
+   8.  Supporting GSS-API Extensions  . . . . . . . . . . . . . . . . 17
+     8.1.  GSS_Query_meta_data  . . . . . . . . . . . . . . . . . . . 17
+     8.2.  GSS_Exchange_meta_data . . . . . . . . . . . . . . . . . . 18
+     8.3.  GSS_Query_mechanism_info . . . . . . . . . . . . . . . . . 19
+     8.4.  GSS_Inquire_context  . . . . . . . . . . . . . . . . . . . 19
+   9.  Security Considerations  . . . . . . . . . . . . . . . . . . . 19
+   10. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 20
+   11. IANA Considerations  . . . . . . . . . . . . . . . . . . . . . 20
+   12. Normative References . . . . . . . . . . . . . . . . . . . . . 20
+   Appendix A.  Protocol Data Structures and Constant Values  . . . . 20
+   Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 24
+
+
+
+
+
+
+
+
+
+
+
+
+
+Short, et al.             Expires July 7, 2011                  [Page 3]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+1.  Introduction
+
+   If more than one GSS-API mechanism is shared between the initator and
+   the acceptor, the Simple and Protected (GSS-API) Negotiation
+   Mechanism (SPNEGO) as defined in [RFC4178] can be deployed to choose
+   a mutually preferred one.  This pseudo mechanism does well in the
+   most basic scenarios but suffers from a couple of drawbacks, notably:
+
+   o  Since the SPNEGO negotiation is based on purely on exchanging
+      security mechanism OIDs, security mechanisms can be selected which
+      cannot successfully authenticate the initator.  Just because an
+      initator and acceptor support the same security mechanism does not
+      mean that they have a mutually trusted authentication authority.
+      In such cases, the authentication will fail with the preferred
+      security mechanism, but might succeed with another common
+      mechanism.
+
+   o  Secondly, the SPNEGO negotiation model is inadequate when the
+      choice cannot be made by the acceptor in the initial response.  In
+      SPNEGO, the negotiation information is sent one-way from the
+      initiator for the acceptor to make a choice, and the acceptor must
+      choose one when it makes the initial response.  This negotiation
+      model is counter intuitive.  The selection of a security mechanism
+      is typically the result of selecting one type of credentials from
+      the available set, and the initiator typically does not wish to
+      reveal credentials information often associated with user
+      identities.  In practice, in order to operate in this model, the
+      Kerberos GSS-API mechanism [RFC4121] must acquire the context
+      establishment token in the initial call to GSS_Init_sec_context().
+      If the initiator fails to acquire the initial Kerberos GSS-API
+      context token, it must not offer Kerberos; otherwise the SPNEGO
+      context negotiation will fail without being able to select the
+      next available mechanism that could work.  Obtaining the initial
+      Kerberos GSS-API context token may require multiple round-trips of
+      network calls and the cost of the operation can be substantial.
+      It is suboptimal when multiple GSS-API mechanisms have to add the
+      extra cost that would not exist if the negotiated security
+      mechanism were selected based on configuration.
+
+   The SPNEGO Extended Negotiation (NEGOEX) Security Mechanism is
+   designed to address these concerns.  NEGOEX is a security mechanism
+   that is negotiated by SPNEGO, and when negotiated, it can recursively
+   negotiate other security mechanisms.
+
+   Any security mechanism negotiated by NEGOEX MUST support integrity
+   protection and addition GSS-API interfaces specified in Section 8.
+
+   The basic form of NEGOEX works as follows:
+
+
+
+Short, et al.             Expires July 7, 2011                  [Page 4]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+   1.  The initiator proposes a list of mechanisms in decreasing
+       preference order.  For each of these mechanism, NEGOEX OPTIONALLY
+       includes a mechanism specific meta-data token.  GSS-API
+       extensions are defined later in this document for NEGOEX to query
+       the meta-data token for inclusion in the NEGOEX message.
+
+   2.  The acceptor then passes the meta-data token from the initiator
+       to the intended security mechanism.  A meta-data token for a
+       security mechanism not supported on the acceptor side is ignored.
+       New GSS-API extensions are defined later in this document for a
+       security mechanism to consume the meta-data token.  When
+       processing the received meta-data tokens, a security mechanism
+       that reports a failure is removed from the set of mutually
+       supported mechanisms.  The acceptor then responds with the list
+       of mutually supported mechanisms in decreasing preference order.
+       For each of these mechanism, NEGOEX again OPTIONALLY supplies a
+       mechanism specific meta-data token in the response which it
+       obtains from each remaining supported mechanism via the new GSS-
+       API extensions described in the initial step.
+
+   3.  The initiator then passes the meta-data tokens to the intended
+       security mechanisms by invoking the new GSS-API extensions.  When
+       processing the received meta-data token, a security mechanism
+       that reports a failure is removed from the set of mutually
+       supported mechanisms for this negotiation context.  The initiator
+       then selects one from the set of mutually-supported mechanisms.
+       If more than one security mechanism is available, unless
+       otherwise specified, the highest one in the acceptor's preference
+       order SHOULD be selected.  Later when the common security
+       mechanism is identified, the security mechanism may also
+       negotiate mechanism-specific options during its context
+       establishments.  This will be inside the mechanism tokens, and
+       invisible to the NEGOEX protocol during step 5.
+
+   4.  The selected security mechanism provides keying materials to
+       NEGOEX via new GSS-API extensions which defined later in this
+       document.  NEGOEX signs and verifies the negotiation NEGOEX
+       messages to protect the negotiation.
+
+   5.  The initiator and the acceptor proceed to exchange tokens until
+       the GSS-API context for selected security mechanism is
+       established.  Once the security context is established, the per-
+       message tokens are generated and verified in accordance with the
+       selected security mechanism.
+
+   NEGOEX does not work outside of SPNEGO.  When negotiated by SPNEGO,
+   NEGOEX uses the concepts developed in the GSS-API specification
+   [RFC2743].  The negotiation data is encapsulated in context-level
+
+
+
+Short, et al.             Expires July 7, 2011                  [Page 5]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+   tokens.  Therefore, callers of the GSS-API do not need to be aware of
+   the existence of the negotiation tokens but only of the SPNEGO
+   pseudo-security mechanism.
+
+   In its basic form NEGOEX requires at least one extra round-trip.
+   Network connection setup is a critical performance characteristic of
+   any network infrastructure and extra round trips over WAN links,
+   packet radio networks, etc. really make a difference.  In order to
+   avoid such an extra round trip the initial security token of the
+   preferred mechanism for the initiator may be embedded in the initial
+   NEGOEX token.  The optimistic mechanism token may be accompanied by
+   the meta-data tokens and the optimistic mechanism token MUST be that
+   of the first mechanism in the list of the mechanisms proposed by the
+   initiator.  The NEGOEX MESSAGE_TYPE_INITIATOR_NEGO message that
+   contains signatures for protecting the NEGOEX negotiation may also
+   accompany the optimistic mechanism token.  If the target preferred
+   mechanism matches the initiator's preferred mechanism, and when the
+   NEGOEX negotiation protection messages are included along with the
+   mechanism token, no additional round trips are incurred by using the
+   NEGOEX protocol with SPNEGO.
+
+   NEGOEX does not update the ASN.1 structures of SPNEGO [RFC4178]
+   because a widely deployed SPNEGO implementation does not have the
+   ASN.1 extensibility marker in the message definition.  There is no
+   change to the SPNEGO messages.
+
+   NEGOEX uses a C-like definition language to describe message formats.
+
+   The rest of the document is organized as follows:
+
+   o  Section 3 defines the encoding of NEGOEX data structures and all
+      the primitive data types.
+   o  Section 6 describes the cryptographic framework required by the
+      NEGOEX for protecting the NEGOEX negotiation.
+   o  Section 7 defines the NEGOEX messages and the NEGOEX protocol.
+   o  Section 8 defines the new GSS-API extensions that a security
+      mechanism MUST support in order to be negotiated by NEGOEX.
+   o  Section 9 contains the security considerations for NEGOEX.
+   o  Appendix A contains all the protocol constructs and constants.
+
+
+2.  Requirements Terminology
+
+   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+   "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+   document are to be interpreted as described in [RFC2119].
+
+
+
+
+
+Short, et al.             Expires July 7, 2011                  [Page 6]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+3.  Presentation Language and Primitive Data Types
+
+   The following very basic and somewhat casually defined presentation
+   syntax will be used in all NEGOEX messages.  Although it resembles
+   the programming language "C" in its syntax, it would be risky to draw
+   too many parallels.  The purpose of this presentation language is to
+   document NEGOEX only; it has no general application beyond that
+   particular goal.
+
+   This section also defines all the primitive data types.  The
+   semantics of the data types is explained in the next section.
+
+3.1.  Basic Block Size
+
+   The representation of all data items is explicitly specified.  The
+   basic data block size is one octet.  Multiple octet data items are
+   concatenations of octets, from left to right, from top to bottom
+   Unless otherwise specific a multi-octet numeric is in little endian
+   order with the least significant octet first.
+
+3.2.  Miscellaneous
+
+   Comments start with "//"' and continue until the end of the line.
+
+3.3.  Constants
+
+   Constants are denoted using "#define" followed by the symbolic name
+   and then the constant value.
+
+3.4.  Numbers
+
+   UCHAR is the data type for a one-octet number.
+
+   ULONG is the data type for a 4-octet number encoded in little endian.
+
+   USHORT is the data type for a 2-octet number encoded in little
+   endian.
+
+   ULONG64 is the data type for a 8-octet number encoded in little
+   endian.
+
+   GUID is the data type for a 16-octet number encoded in little endian.
+
+3.5.  Enum Types
+
+   An enum type is the data type for a number with a small number of
+   permissible values.  An instance of an enum type is a 4-octet number
+   encoded in little endian.
+
+
+
+Short, et al.             Expires July 7, 2011                  [Page 7]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+   The definition of an enum type follows the simple "C" convention.
+
+   MESSAGE_TYPE is an enum type defined as follows:
+
+       enum
+       {
+           MESSAGE_TYPE_INITIATOR_NEGO = 0,
+           MESSAGE_TYPE_ACCEPTOR_NEGO,
+           MESSAGE_TYPE_INITIATOR_META_DATA,
+           MESSAGE_TYPE_ACCEPTOR_META_DATA,
+           MESSAGE_TYPE_CHALLENGE,
+               // an exchange message from the acceptor
+           MESSAGE_TYPE_AP_REQUEST,
+               // an exchange message from the initiator
+           MESSAGE_TYPE_VERIFY,
+           MESSAGE_TYPE_ALERT,
+       } MESSAGE_TYPE;
+
+   MESSAGE_TYPE_INITIATOR_NEGO has the value 0, and MESSAGE_TYPE_ALERT
+   has the value 7.
+
+3.6.  Typedef Declarations
+
+   A typedef creates a synonym for the type.  This is used to create
+   more meaningful names for existing types.
+
+   The following two type synonyms are defined.
+
+   typedef GUID AUTH_SCHEME;
+   typedef GUID CONVERSATION_ID;
+
+3.7.  Array Types
+
+   Arrays are a data structure which holds multiple variables of the
+   same data type consecutively and the number of elements is fixed.  An
+   array is declared using "C" convention.  The following defines an
+   array of 32 octets.
+
+   UCHAR Random[32];
+
+3.8.  Constructed Types
+
+   Structure types may be constructed from primitive types for
+   convenience.  Each specification declares a new, unique type.  The
+   syntax for definition is much like that of C.
+
+
+
+
+
+
+Short, et al.             Expires July 7, 2011                  [Page 8]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+           struct {
+           T1 f1;
+           T2 f2;
+           ...
+           Tn fn;
+           } T;
+
+
+   Structure definitions may be embedded.
+
+   The following types are defined as constructed types:
+
+           struct
+           {
+           ULONG ExtensionType; // negative extensions are critical
+           BYTE_VECTOR ExtensionValue;
+           } EXTENSION;
+
+   An extension has two fields.  The ExtensionType field indicates how
+   the extension data should be interpreted.  The ExtensionValue field
+   contains the extension data.
+
+           //
+           // schemes defined for the checksum in the VERIFY message
+           //
+
+           struct
+           {
+           ULONG cbHeaderLength;
+           ULONG ChecksumScheme;
+           ULONG ChecksumType; // in the case of RFC3961 scheme, this is
+           // the RFC3961 checksum type
+           BYTE_VECTOR ChecksumValue;
+           } CHECKSUM;
+
+   The CHECKSUM structure contains 4 fields.  The cbHeaderLength length
+   contains the length of the structure defintion in octets, and this
+   field has a value of 20.
+
+   The ChecksumScheme field describes how checksum is computed and
+   verified.  Currently only one value is defined.
+
+           #define CHECKSUM_SCHEME_RFC3961 1
+
+   When the value of the ChecksumScheme field is 1
+   (CHECKSUM_SCHEME_RFC3961), the ChecksumValue field contains a
+   sequence of octets computed according to [RFC3961] and the
+   ChecksumType field contains the checksum type value defined according
+
+
+
+Short, et al.             Expires July 7, 2011                  [Page 9]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+   to [RFC3961].
+
+
+4.  Vector Types
+
+   Vectors are a data structure which holds multiple variables of the
+   same data type consecutively and the number of elements is not fixed.
+   A vector contains a fixed length header followed by a variable length
+   payload.  The header of a vector structure contains the count of
+   elements and the offset to the payload.  In this document all the
+   offset fields are relative to the beginning of the containing NEGOEX
+   message.  The size of each element is specified by the vector type
+   definition.
+
+   The following vector types are defined.
+
+       struct
+       {
+           ULONG ByteArrayOffset; // each element contains an octet/byte
+           ULONG ByteArrayLength;
+       } BYTE_VECTOR;
+
+   BYTE_VECTOR encapsulates a variable length array of octets (or bytes)
+   that are stored consecutively.  Each element in is a byte (8 bits).
+
+       struct
+       {
+           ULONG AuthSchemeArrayOffset;
+                // each element contains an AUTH_SCHEME
+           USHORT AuthSchemeCount;
+       } AUTH_SCHEME_VECTOR;
+
+   AUTH_SCHEME_VECTOR encapsulates a variable length array of
+   AUTH_SCHEMEs that are stored consecutively.  Each element is a
+   structure of the type AUTH_SCHEME.
+
+       struct
+       {
+           ULONG ExtensionArrayOffset;
+               // each element contains an EXTENSION
+           USHORT ExtensionCount;
+       } EXTENSION_VECTOR;
+
+   EXTENSION_VECTOR encapsulates a variable length array of EXTENSIONs
+   that are stored consecutively.  Each element is a structure of the
+   type EXTENSION.
+
+
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 10]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+5.  NEGOEX Messages
+
+   The following structure is the MESSAGE_HEADER:
+
+           struct
+           {
+           ULONG64 Signature; // contains MESSAGE_SIGNATURE
+           MESSAGE_TYPE MessageType;
+           ULONG SequenceNum; // the message sequence number of this,
+           // conversation, starting with 0 and sequentially
+           // incremented
+           ULONG cbHeaderLength; // the header length of this message,
+           // including the message specific header, excluding the
+           // payload
+           ULONG cbMessageLength; // the length of this message
+           CONVERSATION_ID ConversationId;
+           } MESSAGE_HEADER;
+
+   The following structure is the NEGO_MESSAGE:
+
+           struct
+           {
+           MESSAGE_HEADER Header;
+           // MESSAGE_TYPE_INITIATOR_NEGO for the initiator,
+           // MESSAGE_TYPE_ACCEPTOR_NEGO for the acceptor
+           UCHAR Random[32];
+           ULONG64 ProtocolVersion;
+           // version of the protocol, this contains 0
+           AUTH_SCHEME_VECTOR AuthSchemes;
+           EXTENSION_VECTOR Extensions;
+           } NEGO_MESSAGE;
+
+   The following structure is the EXCHANGE_MESSAGE:
+
+           struct
+           {
+           MESSAGE_HEADER Header;
+           // MESSAGE_TYPE_CHALLENGE for the acceptor,
+           // or MESSAGE_TYPE_AP_REQUEST for the initiator
+           // MESSAGE_TYPE_INITIATOR_META_DATA for
+           // the initiator metadata
+           // MESSAGE_TYPE_ACCEPTOR_META_DATA for
+           // the acceptor metadata
+           AUTH_SCHEME AuthScheme;
+           BYTE_VECTOR Exchange;
+           // contains the opaque handshake message for the
+           // authentication scheme
+           } EXCHANGE_MESSAGE;
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 11]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+6.  Cryptographic Computations
+
+   The message signing and verification in NEGOEX is based on [RFC3961].
+   [RFC3961] is used here as a generic framework and this application is
+   not Kerberos specific.
+
+   A security mechanism MUST support [RFC3961] in order to be negotiated
+   by NEGOEX.
+
+
+7.  The NEGOEX Protocol
+
+   This section describes the NEGOEX protocol and it defines NEGOEX
+   messages in the order that the messages can appear on the wire.  The
+   enum type MESSAGE_TYPE defined in Section 3.5 lists all NEGOEX
+   message types.  A GSS-API context token for NEGOEX consists of one or
+   more NEGOEX messages.  If there is more than one NEGOEX message,
+   these messages are concatenated together.  The smallest data unit for
+   NEGOEX to compute the checksum for negotiation protection is s NEGOEX
+   message.  Note that NEGOEX is not a GSS-API mechanism itself and the
+   initial NEGOEX context establishment token does not follow the
+   mechanism-independent token format defined in Section 3.1 of
+   [RFC2743].
+
+   The object identifier of the NEGOEX within SPNEGO is iso(1)
+   identified-organization(3) dod(6) internet(1) private(4)
+   enterprise(1) microsoft (311) security(2) mechanisms(2) negoex(30).
+
+7.1.  High-level NEGOEX Message Flow
+
+   The following text art summarizes the protocol message flow:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 12]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+        Initiator                                               Acceptor
+
+        INITIATOR_NEGO
+        +*INITIATOR_META_DATA
+        *AP_REQUEST
+                                    --------->
+                                                           ACCEPTOR_NEGO
+                                                    ACCEPTOR_META_DATA*+
+                                    <---------                CHALLENGE*
+
+                                        .
+                                        .
+
+        *AP_REQUEST                 --------->
+                                    <---------                CHALLENGE*
+
+                                        .
+                                        .
+        *AP_REQUEST
+        VERIFY                      --------->
+                                                              CHALLENGE*
+                                    <---------                    VERIFY
+        * Indicates optional or situation-dependent messages that are
+        not always sent.
+        + Indicates there can be more than one instance.
+
+
+7.2.  NEGOEX Supported Security Mechanisms
+
+   NEGOEX maintains an ordered list of supported security mechanisms
+   names to determine priority of security mechanisms.  A security
+   mechanism negotiable by NEGOEX is identified by a unique identifier
+   of data type AUTH_SCHEME defined in Section 3.5.  Supported security
+   mechanisms are referenced by their corresponding authentication
+   scheme IDs.  The authentication scheme ID of a security mechanism is
+   returned to NEGOEX by calling GSS_Query_mechanism_info() with the
+   name of the security mechnism as defined in Section 8.3.
+
+7.3.  ConversationID
+
+   Both initiator and acceptor must keep protocol state in the form of a
+   GUID, which will be referred to hereafter as the ConversationID.
+
+7.4.  Generation of the Initiator Initial Token
+
+   The GSS-API initiator makes the first call to GSS_Init_sec_context()
+   with no input token, and the output token will be a NEGO_MESSAGE
+   message with the MESSAGE_TYPE_INITIATOR_NEGO message followed by zero
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 13]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+   or more EXCHANGE_MESSAGE messages containing meta-data tokens,
+   followed by zero or one AP_REQUEST messages containing an optimistic
+   initial context token.
+
+   The initiator generates a cryptographic strength random 16 byte
+   value, stores it as the ConversationID, then sets the MESSAGE_HEADER
+   header field with the same name to that value.  The ConversationID in
+   subsequent NEGOEX messages MUST remain the same.  The initiator also
+   fills the Random field using a secure random number generator.  The
+   initiator fills the AuthSchemes with available security mechanisms
+   supported by the initiator in decreasing preference order.
+
+   The extensions field contains NEGOEX extensions for future
+   extensibility.  There are no extensions defined in this document.
+   All negative extension types (the highest bit is set to 1) are
+   critical.  If the receiver does not understand a critical extension,
+   the authentication attempt must be rejected.
+
+   The initiator can OPTIONALLY include a meta-data token, one for each
+   available security mechanism.
+
+   A meta-data token is returned to NEGOEX for a security mechanism
+   using GSS_Query_meta_data() extension as defined in Section 8.1.  If
+   a non-empty meta-data token is returned, then the meta-data token is
+   encapsulated in an EXCHANGE message with the message type
+   MESSAGE_TYPE_INITIATOR_META_DATA.  On GSS_Query_meta_data call
+   failure, NEGOEX SHOULD remove the security mechanism from the set of
+   authentication schemes to be negotiated.
+
+   The AuthScheme field signifies the security mechanism for which the
+   EXCHANGE message is targeted.  If a security mechanism fails to
+   produce the metadata token, it should be removed from the list of
+   supported security mechanism for this negotiation context.
+
+   If there is more than one exchange message, the order in which the
+   exchange message is included bears no significance.  In other words,
+   the exchange messages are in an unordered set.  The NEGO_MESSAGE MAY
+   be followed by a set of MESSAGE_TYPE_INITIATOR_META_DATA messages as
+   described above, in which case all the NEGOEX messages concatenated
+   are returned as a single output token.
+
+   The first mechanism in the initiator proposed list can OPTIONALLY
+   include its initial context token in an AP_REQUEST message.
+
+   Both an AP_REQUEST(short for MESSAGE_TYPE_AP_REQUEST) message and a
+   INITIATOR_META_DATA(short for MESSAGE_TYPE_INITIATOR_META_DATA)
+   message are instances of the EXCHANGE_MESSAGE structure with
+   different message type values.  An AP_REQUEST message contains the
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 14]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+   type MESSAGE_TYPE_AP_REQUEST while an INITIATOR_META_DATA message
+   contains the type MESSAGE_TYPE_INITIATOR_META_DATA.
+
+7.5.  Receipt of the Initial Initiator Token and Generation of the
+      Initial Acceptor Response
+
+   Upon receipt of the NEGO_MESSAGE from the initiator, the acceptor
+   verifies the NEGO_MESSAGE to make sure it is well-formed.  The
+   acceptor extracts the ConversationID from the NEGO_MESSAGE and stores
+   it as the ConversationID for the context handle.  The acceptor then
+   computes the list of authentication schemes that are mutually
+   supported by examining the set of security mechanisms proposed by the
+   initiator and the meta-data tokens from the initiator.  The meta-data
+   tokens are passed to the security mechanism via
+   GSS_Exchange_meta_data() as defined in Section 8.2.  On
+   GSS_Exchange_meta_data call failure, NEGOEX SHOULD remove the
+   security mechanism from the set of authentication schemes to be
+   negotiated.
+
+   The acceptor MUST examine the NEGOEX extensions in the NEGO_MESSAGE.
+   If there is an unknown critical extension, the authentication must be
+   rejected.
+
+   The acceptor's output token is a NEGO_MESSAGE but with the the
+   Header.MessageType set to MESSAGE_TYPE_ACCEPTOR_NEGO followed by zero
+   or more EXCHANGE_MESSAGE containing meta-data tokens.  The
+   AuthSchemes field contains the list of mutually supported security
+   mechanism in decreasing preference order of the acceptor.  The
+   acceptor does not need to honor the preference order proposed by the
+   initiator when computing its preference list.
+
+   As with the initiator, the acceptor can OPTIONALLY include a meta-
+   data token, one for each available security mechanism.
+
+   A meta-data token is obtained by NEGOEX for a security mechanism
+   using GSS_Query_meta_data() extension as defined in Section 8.1.  If
+   a non-empty meta-data token is returned, then the meta-data token is
+   encapsulated in an EXCHANGE message with the message type
+   MESSAGE_TYPE_ACCEPTOR_META_DATA.  For a given security mechanism if a
+   meta-token is received from the initiator, GSS_Query_meta_data() MUST
+   be invoked on the acceptor side for that security mechanism, and the
+   output meta-data token, if present, MUST be included in the NEGOEX
+   reply.  On GSS_Query_meta_data call failure, NEGOEX SHOULD remove the
+   security mechanism from the set of authentication schemes to be
+   negotiated.
+
+
+
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 15]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+7.6.  Receipt of the Acceptor Initial Response and Completion of
+      Authentication after the Negotiation Phrase
+
+   Upon receipt of the initial response token from the acceptor, the
+   application calls GSS_Init_sec_context with the response token.  The
+   initiator verifies the NEGOEX message received to make sure it is
+   well-formed.  The initiator ensures the correct context handle by
+   verifying that the ConversationID of the context handle matches the
+   conversation ID in the NEGOEX message received.  The initiator then
+   computes the list of authentication schemes that are mutually
+   supported by examining the set of security mechanisms returned by the
+   acceptor and the meta-data tokens from the acceptor The meta-data
+   tokens are passed to the security mechanism via
+   GSS_Exchange_meta_data() as defined in Section 8.2.  On
+   GSS_Exchange_meta_data call failure, NEGOEX SHOULD remove the
+   security mechanism from the set of authentication schemes to be
+   negotiated.
+
+   The initiator MUST examine the NEGOEX extensions in the NEGO_MESSAGE.
+   If there is an unknown critical extension, the authentication must be
+   rejected.
+
+   After the initial exchange of NEGO_MESSAGE messages, the initiator
+   MUST choose the negotiated security mechanism.  The negotiated
+   security mechanism cannot be changed once it is selected.
+
+   The initiator and the acceptor can then proceed to exchange handshake
+   messages by returning GSS_S_CONTINUE_NEEDED to the calling
+   application as determined by the negotiated security mechanism until
+   its authentication context is established.  The context tokens of the
+   negotiated security mechanism are encapsulated in an
+   EXCHANGE_MESSAGE.  If the context token is from the initiator, the
+   EXCHANGE_MESSAGE message has the message type
+   MESSAGE_TYPE_AP_REQUEST; otherwise, the message type is
+   MESSAGE_TYPE_CHALLENGE.
+
+7.7.  Finalizing Negotiation
+
+   After the security mechanism has been selected, the initiator and
+   acceptor can use GSS_Inquire_context to obtain the Negoex_Verify_key
+   as defined in Section 8.4 to determine if there is a shared key for
+   the VERIFY message.  When there is a shared key established returned
+   by GSS_Inquire_context as defined in Section 8.4, a VERIFY message is
+   produced using the required checksum mechanism per RFC 3961 and
+   included in the output token.  The returned protocol key is used as
+   the base key in the parlance of RFC3961 to sign all the NEGOEX
+   messages in the negotiation context.
+
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 16]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+   A VERIFY message is a VERIFY_MESSAGE structure.  The AuthScheme field
+   signifies from which security mechanism the protocol key was
+   obtained.  The checksum is computed based on RFC3961 and the key
+   usage number is 23 for the message signed by the initiator, 25
+   otherwise.  The checksum is performed over all the previous NEGOEX
+   messages in the context negotiation.
+
+       struct
+       {
+           MESSAGE_HEADER Header; // MESSAGE_TYPE_VERIFY
+           AUTH_SCHEME AuthScheme;
+           CHECKSUM Checksum;
+                // contains the checksum of all the previously
+                // exchanged messages in the order they were sent.
+       } VERIFY_MESSAGE;
+
+   Note that the VERIFY_MESSAGE message can be included before the
+   security context for the negotiated security mechanism is fully
+   established.
+
+
+8.  Supporting GSS-API Extensions
+
+   This section defined all the required GSS-API extensions required by
+   NEGOEX which must be supported by security mechanisms usable with
+   NEGOEX.
+
+8.1.  GSS_Query_meta_data
+
+   Inputs:
+
+   o  input_context_handle CONTEXT HANDLE
+   o  targ_name INTERNAL NAME, optional
+   o  deleg_req_flag BOOLEAN,
+   o  mutual_req_flag BOOLEAN,
+   o  replay_det_req_flag BOOLEAN,
+   o  sequence_req_flag BOOLEAN,
+   o  conf_req_flag BOOLEAN,
+   o  integ_req_flag BOOLEAN,
+
+   Outputs:
+
+   o  metadata OCTET STRING,
+   o  output_context_handle CONTEXT HANDLE
+
+   Return major_status codes:
+
+
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 17]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+   o  GSS_S_COMPLETE indicates that the context referenced by the
+      input_context_handle argument is valid, and that the output
+      metadata value represents the security mechanism's provided
+      metadata.  A security mechanism may return empty metadata.
+   o  GSS_S_NO_CONTEXT indicates that no valid context was recognized
+      for the input context_handle provided.  Return values other than
+      major_status and minor_status are undefined.
+   o  GSS_S_NO_CRED indicates that no metadata could be returned about
+      the referenced credentials either because the input cred_handle
+      was invalid or the caller lacks authorization to access the
+      referenced credentials.
+   o  GSS_S_UNAVAILABLE indicates that the authentication security
+      service does not support this operation.
+   o  GSS_S_FAILURE indicates that the requested operation failed for
+      reasons unspecified at the GSS-API level.  Return values other
+      than major_status and minor_status are undefined.
+
+   GSS_Query_meta_data is used to retrieve a security mechanism's
+   metadata.
+
+8.2.  GSS_Exchange_meta_data
+
+   Inputs:
+
+   o  input_context_handle CONTEXT HANDLE
+   o  cred_handle CREDENTIAL HANDLE, optional
+   o  targ_name INTERNAL NAME, optional
+   o  deleg_req_flag BOOLEAN,
+   o  mutual_req_flag BOOLEAN,
+   o  replay_det_req_flag BOOLEAN,
+   o  sequence_req_flag BOOLEAN,
+   o  conf_req_flag BOOLEAN,
+   o  integ_req_flag BOOLEAN,
+   o  metadata OCTET STRING,
+
+   Outputs:
+
+   o  output_context_handle CONTEXT HANDLE
+
+   Return major_status codes:
+
+   o  GSS_S_COMPLETE indicates that the metadata was provided to the
+      security mechanism.
+   o  GSS_S_NO_CONTEXT indicates that no valid context was recognized
+      for the input context_handle provided.  Return values other than
+      major_status and minor_status are undefined.
+
+
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 18]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+   o  GSS_S_NO_CRED indicates that the metadata passed requested
+      credentials not available via this credential handle.
+   o  GSS_S_UNAVAILABLE indicates that the security mechanism does not
+      support this operation.
+   o  GSS_S_FAILURE indicates that the requested operation failed for
+      reasons unspecified at the GSS-API level.  Return values other
+      than major_status and minor_status are undefined.
+
+   GSS_Exchange_meta_data is used to provide the metadata to each
+   security mechanism.
+
+8.3.  GSS_Query_mechanism_info
+
+   Inputs:
+
+   o  SecMechName STRING,
+
+   Outputs:
+
+   o  AuthScheme AUTH_SCHEME
+
+   Return major_status codes:
+
+   o  GSS_S_COMPLETE indicates that the authentication scheme value
+      represents the security mechanism's AUTH_SCHEME.
+   o  GSS_S_FAILURE indicates that the security mechanism does not
+      support NEGOEX.  Return values other than major_status and
+      minor_status are undefined.
+
+   GSS_Query_mechanism_info returns a security mechanism's
+   authentication scheme value.
+
+8.4.  GSS_Inquire_context
+
+   The following output is added to GSS_Inquire_context as defined in
+   [RFC2743].
+
+   Outputs:
+
+   o  Negoex_Verify_key OCTET STRING
+
+   This new output is the key to be used by NEGOEX for the VERIFY
+   message.
+
+
+9.  Security Considerations
+
+   Security mechanism SHOULD support providing VERIFY key material.
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 19]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+   This ensures that VERIFY messages are generated to make NEGOEX safe
+   from downgrade attacks.
+
+
+10.  Acknowledgements
+
+   TBD.
+
+
+11.  IANA Considerations
+
+   There is no action required for IANA.
+
+
+12.  Normative References
+
+   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
+              Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+   [RFC2743]  Linn, J., "Generic Security Service Application Program
+              Interface Version 2, Update 1", RFC 2743, January 2000.
+
+   [RFC3961]  Raeburn, K., "Encryption and Checksum Specifications for
+              Kerberos 5", RFC 3961, February 2005.
+
+   [RFC4120]  Neuman, C., Yu, T., Hartman, S., and K. Raeburn, "The
+              Kerberos Network Authentication Service (V5)", RFC 4120,
+              July 2005.
+
+   [RFC4121]  Zhu, L., Jaganathan, K., and S. Hartman, "The Kerberos
+              Version 5 Generic Security Service Application Program
+              Interface (GSS-API) Mechanism: Version 2", RFC 4121,
+              July 2005.
+
+   [RFC4178]  Zhu, L., Leach, P., Jaganathan, K., and W. Ingersoll, "The
+              Simple and Protected Generic Security Service Application
+              Program Interface (GSS-API) Negotiation Mechanism",
+              RFC 4178, October 2005.
+
+
+Appendix A.  Protocol Data Structures and Constant Values
+
+   This section compiles all the protocol data structures and constant
+   values.
+
+       #define MESSAGE_SIGNATURE    0x535458454f47454ei64
+           // "NEGOEXTS"
+
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 20]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+       struct
+       {
+           ULONG ByteArrayOffset; // each element contains a byte
+           ULONG ByteArrayLength;
+       } BYTE_VECTOR;
+
+       struct
+       {
+           ULONG AuthSchemeArrayOffset;
+               // each element contains an AUTH_SCHEME
+           USHORT AuthSchemeCount;
+       } AUTH_SCHEME_VECTOR;
+
+       struct
+       {
+           ULONG ExtensionArrayOffset;
+               // each element contains an EXTENSION
+           USHORT ExtensionCount;
+       } EXTENSION_VECTOR;
+
+       struct
+       {
+           ULONG ExtensionType; // negative extensions are critical
+           BYTE_VECTOR ExtensionValue;
+       } EXTENSION;
+
+       //
+       // schemes defined for the checksum in the VERIFY message
+       //
+
+       #define CHECKSUM_SCHEME_RFC3961  1
+
+       struct
+       {
+           ULONG cbHeaderLength;
+           ULONG ChecksumScheme;
+           ULONG ChecksumType; // in the case of RFC3961 scheme, this is
+              // the RFC3961 checksum type
+           BYTE_VECTOR ChecksumValue;
+       } CHECKSUM;
+
+       typedef GUID AUTH_SCHEME;
+       typedef GUID CONVERSATION_ID;
+
+       enum
+       {
+           MESSAGE_TYPE_INITIATOR_NEGO = 0,
+           MESSAGE_TYPE_ACCEPTOR_NEGO,
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 21]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+           MESSAGE_TYPE_INITIATOR_META_DATA,
+           MESSAGE_TYPE_ACCEPTOR_META_DATA,
+           MESSAGE_TYPE_CHALLENGE,
+               // an exchange message from the acceptor
+           MESSAGE_TYPE_AP_REQUEST,
+               // an exchange message from the initiator
+           MESSAGE_TYPE_VERIFY,
+           MESSAGE_TYPE_ALERT,
+       } MESSAGE_TYPE;
+
+       struct
+       {
+           ULONG64 Signature; // contains MESSAGE_SIGNATURE
+           MESSAGE_TYPE MessageType;
+           ULONG SequenceNum; // the message sequence number of this,
+                  // conversation, starting with 0 and sequentially
+                  // incremented
+           ULONG cbHeaderLength; // the header length of this message,
+              // including the message specific header, excluding the
+              // payload
+           ULONG cbMessageLength; // the length of this message
+           CONVERSATION_ID ConversationId;
+       } MESSAGE_HEADER;
+
+       struct
+       {
+           MESSAGE_HEADER Header;
+                    // MESSAGE_TYPE_INITIATOR_NEGO for the initiator,
+                    // MESSAGE_TYPE_ACCEPTOR_NEGO for the acceptor
+           UCHAR Random[32];
+           ULONG64 ProtocolVersion;
+                   // version of the protocol, this contains 0
+           AUTH_SCHEME_VECTOR AuthSchemes;
+           EXTENSION_VECTOR Extensions;
+       } NEGO_MESSAGE;
+
+       struct
+       {
+           MESSAGE_HEADER Header;
+                // MESSAGE_TYPE_CHALLENGE for the acceptor,
+                // or MESSAGE_TYPE_AP_REQUEST for the initiator
+               // MESSAGE_TYPE_INITiATOR_META_DATA for
+               // the initiator metadata
+               // MESSAGE_TYPE_ACCEPTOR_META_DATA for
+               // the acceptor metadata
+           AUTH_SCHEME AuthScheme;
+           BYTE_VECTOR Exchange;
+               // contains the opaque handshake message for the
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 22]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+               // authentication scheme
+       } EXCHANGE_MESSAGE;
+
+       struct
+       {
+           MESSAGE_HEADER Header; // MESSAGE_TYPE_VERIFY
+           AUTH_SCHEME AuthScheme;
+           CHECKSUM Checksum;
+                // contains the checksum of all the previously
+                // exchanged messages in the order they were sent.
+       } VERIFY_MESSAGE;
+
+       struct
+       {
+           ULONG AlertType;
+           BYTE_VECTOR AlertValue;
+       } ALERT;
+
+       //
+       // alert types
+       //
+
+       #define ALERT_TYPE_PULSE             1
+
+       //
+       // reason codes for the heartbeat message
+       //
+
+       #define ALERT_VERIFY_NO_KEY          1
+
+       struct
+       {
+           ULONG cbHeaderLength;
+           ULONG Reason;
+       } ALERT_PULSE;
+
+       struct
+       {
+           ULONG AlertArrayOffset; // the element is an ALERT
+           USHORT AlertCount; // contains the number of alerts
+       } ALERT_VECTOR;
+
+       struct
+       {
+           MESSAGE_HEADER Header;
+           AUTH_SCHEME AuthScheme;
+           ULONG ErrorCode; // an NTSTATUS code
+           ALERT_VECTOR Alerts;
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 23]
+\f
+Internet-Draft                   NEGOEX                     January 2011
+
+
+       } ALERT_MESSAGE;
+
+
+Authors' Addresses
+
+   Michiko Short
+   Microsoft Corporation
+   One Microsoft Way
+   Redmond, WA  98052
+   US
+
+   Email: michikos@microsoft.com
+
+
+   Larry Zhu
+   Microsoft Corporation
+   One Microsoft Way
+   Redmond, WA  98052
+   US
+
+   Email: lzhu@microsoft.com
+
+
+   Kevin Damour
+   Microsoft Corporation
+   One Microsoft Way
+   Redmond, WA  98052
+   US
+
+   Email: kdamour@microsoft.com
+
+
+   Dave McPherson
+   Microsoft Corporation
+   One Microsoft Way
+   Redmond, WA  98052
+   US
+
+   Email: davemm@microsoft.com
+
+
+
+
+
+
+
+
+
+
+
+
+Short, et al.             Expires July 7, 2011                 [Page 24]
+\f
+
index 7c2e609d123c922827419b81c43862e58fc472c4..16dd2250c20cfa21c06a372936399c67667233bc 100644 (file)
@@ -49,6 +49,7 @@ CLEANFILES =                  \
        getarg.h                \
        glob.h                  \
        gssapi.h                \
+       gssapi_asn1.h           \
        gssapi_mech.h           \
        hdb-private.h           \
        hdb-protos.h            \
index 8c61450ca89fe32217b8806e26bf1a68c3e5f55b..aacb080aea4b2bdab9445ea9aefa25a0768767d9 100644 (file)
@@ -14,7 +14,7 @@ AM_CPPFLAGS += \
        -I$(srcdir)/spnego \
        $(INCLUDE_libintl)
 
-lib_LTLIBRARIES = libgssapi.la
+lib_LTLIBRARIES = libgssapi.la test_negoex_mech.la
 
 krb5src = \
        krb5/8003.c \
@@ -157,6 +157,9 @@ mechsrc = \
        mech/gss_wrap.c \
        mech/gss_wrap_size_limit.c \
        mech/gss_inquire_sec_context_by_oid.c \
+       mech/gssspi_exchange_meta_data.c \
+       mech/gssspi_query_mechanism_info.c \
+       mech/gssspi_query_meta_data.c \
        mech/mech_switch.h \
        mech/mech_locl.h \
        mech/name.h \
@@ -169,7 +172,10 @@ spnegosrc = \
        spnego/cred_stubs.c \
        spnego/external.c \
        spnego/init_sec_context.c \
+       spnego/negoex_ctx.c \
+       spnego/negoex_util.c \
        spnego/spnego_locl.h \
+       spnego/negoex_locl.h \
        $(srcdir)/spnego/spnego-private.h
 
 ntlmsrc = \
@@ -217,6 +223,8 @@ dist_libgssapi_la_SOURCES  = \
 nodist_libgssapi_la_SOURCES  = \
        gkrb5_err.c \
        gkrb5_err.h \
+       negoex_err.c \
+       negoex_err.h \
        $(BUILT_SOURCES)
 
 libgssapi_la_DEPENDENCIES = version-script.map
@@ -239,6 +247,7 @@ man_MANS = gssapi.3 gss_acquire_cred.3 mech/mech.5 gss-token.1
 
 include_HEADERS = gssapi.h
 noinst_HEADERS = \
+       gssapi_asn1.h \
        gssapi_mech.h \
        $(srcdir)/ntlm/ntlm-private.h \
        $(srcdir)/spnego/spnego-private.h \
@@ -252,7 +261,7 @@ nobase_include_HEADERS = \
        gssapi/gssapi_spnego.h
 
 gssapidir = $(includedir)/gssapi
-nodist_gssapi_HEADERS = gkrb5_err.h
+nodist_gssapi_HEADERS = gkrb5_err.h negoex_err.h
 
 gssapi_files = asn1_GSSAPIContextToken.x
 
@@ -265,7 +274,8 @@ spnego_files =                                      \
        asn1_NegHints.x                         \
        asn1_NegTokenInit.x                     \
        asn1_NegTokenInitWin.x                  \
-       asn1_NegTokenResp.x
+       asn1_NegTokenResp.x                     \
+       asn1_NegResultEnum.x
 
 BUILTHEADERS = \
        $(srcdir)/krb5/gsskrb5-private.h \
@@ -279,11 +289,12 @@ $(libgssapi_la_OBJECTS): $(srcdir)/version-script.map
 
 BUILT_SOURCES = $(spnego_files:.x=.c) $(gssapi_files:.x=.c)
 
-$(libgssapi_la_OBJECTS): gkrb5_err.h
+$(libgssapi_la_OBJECTS): gkrb5_err.h negoex_err.h
 gkrb5_err.h: $(srcdir)/krb5/gkrb5_err.et
+negoex_err.h: $(srcdir)/spnego/negoex_err.et
 
 CLEANFILES = $(BUILT_SOURCES) \
-       gkrb5_err.h gkrb5_err.c \
+       gkrb5_err.[ch] negoex_err.[ch] \
        $(spnego_files) spnego_asn1*.h* spnego_asn1_files spnego_asn1-template.[cx] \
        $(gssapi_files) gssapi_asn1*.h* gssapi_asn1_files gssapi_asn1-template.[cx] \
        gss-commands.h gss-commands.c
@@ -365,6 +376,8 @@ EXTRA_DIST = \
        mech/gssapi.asn1 \
        spnego/spnego.asn1 \
        spnego/spnego.opt \
+       spnego/negoex_err.et \
+       test_negoex_mech.c \
        version-script.map \
        gss-commands.in
 
@@ -375,6 +388,20 @@ $(libgssapi_la_OBJECTS): $(srcdir)/gssapi/gssapi_oid.h
 gkrb5_err.h gkrb5_err.c: $(srcdir)/krb5/gkrb5_err.et
        $(COMPILE_ET) $(srcdir)/krb5/gkrb5_err.et
 
+negoex_err.h negoex_err.c: $(srcdir)/spnego/negoex_err.et
+       $(COMPILE_ET) $(srcdir)/spnego/negoex_err.et
+
 $(srcdir)/gssapi/gssapi_oid.h $(srcdir)/mech/gss_oid.c:
        perl $(srcdir)/gen-oid.pl -b base -h $(srcdir)/oid.txt > $(srcdir)/gssapi/gssapi_oid.h
        perl $(srcdir)/gen-oid.pl -b base $(srcdir)/oid.txt > $(srcdir)/mech/gss_oid.c
+
+#
+# NegoEx test mechanism, uses decode_GSSAPIContextToken
+#
+
+test_negoex_mech_la_SOURCES = test_negoex_mech.c $(gssapi_files:.x=.c)
+test_negoex_mech_la_LDFLAGS = -module
+test_negoex_mech_la_LIBADD = \
+       $(top_builddir)/lib/asn1/libasn1.la \
+       libgssapi.la
+
index 0a2bab4af465425849abf7ab6b491d8c6cc03a22..7d20d8ebc0a17ea9ec3de22aead6e26ad8c78dfa 100644 (file)
@@ -173,6 +173,9 @@ mechsrc = \
        mech/gss_wrap.c \
        mech/gss_wrap_size_limit.c \
        mech/gss_inquire_sec_context_by_oid.c \
+       mech/gssspi_exchange_meta_data.c \
+       mech/gssspi_query_mechanism_info.c \
+       mech/gssspi_query_meta_data.c \
        mech/mech_switch.h \
        mech/mech_locl.h \
        mech/name.h \
@@ -185,7 +188,10 @@ spnegosrc = \
        spnego/cred_stubs.c \
        spnego/external.c \
        spnego/init_sec_context.c \
-       spnego/spnego_locl.h
+       spnego/negoex_ctx.c \
+       spnego/negoex_util.c \
+       spnego/spnego_locl.h \
+       spnego/negoex_locl.h
 
 ntlmsrc = \
        ntlm/accept_sec_context.c \
@@ -258,6 +264,11 @@ $(OBJ)\gkrb5_err.c $(OBJ)\gkrb5_err.h: krb5\gkrb5_err.et
        $(BINDIR)\compile_et.exe $(SRCDIR)\krb5\gkrb5_err.et
        cd $(SRCDIR)
 
+$(OBJ)\negoex_err.c $(OBJ)\negoex_err.h: spnego\negoex_err.et
+       cd $(OBJ)
+       $(BINDIR)\compile_et.exe $(SRCDIR)\spnego\negoex_err.et
+       cd $(SRCDIR)
+
 INCFILES=                              \
     $(INCDIR)\gssapi.h                 \
     $(INCDIR)\gssapi\gssapi.h          \
@@ -270,6 +281,7 @@ INCFILES=                           \
     $(OBJ)\spnego\spnego-private.h     \
     $(OBJ)\krb5\gsskrb5-private.h      \
     $(OBJ)\gkrb5_err.h                 \
+    $(OBJ)\negoex_err.h                        \
     $(OBJ)\gssapi\gssapi_asn1.h                \
     $(OBJ)\gssapi\gssapi_asn1-priv.h   \
     $(OBJ)\spnego\spnego_asn1.h                \
@@ -409,12 +421,17 @@ libgssapi_OBJs = \
        $(OBJ)\mech/gss_wrap.obj \
        $(OBJ)\mech/gss_wrap_size_limit.obj \
        $(OBJ)\mech/gss_inquire_sec_context_by_oid.obj \
+       $(OBJ)\mech/gssspi_exchange_meta_data.obj \
+       $(OBJ)\mech/gssspi_query_mechanism_info.obj \
+       $(OBJ)\mech/gssspi_query_meta_data.obj \
        $(OBJ)\spnego/accept_sec_context.obj \
        $(OBJ)\spnego/compat.obj \
        $(OBJ)\spnego/context_stubs.obj \
        $(OBJ)\spnego/cred_stubs.obj \
        $(OBJ)\spnego/external.obj \
        $(OBJ)\spnego/init_sec_context.obj \
+       $(OBJ)\spnego/negoex_ctx.obj \
+       $(OBJ)\spnego/negoex_util.obj \
        $(OBJ)\ntlm/accept_sec_context.obj \
        $(OBJ)\ntlm/acquire_cred.obj \
        $(OBJ)\ntlm/add_cred.obj \
@@ -447,6 +464,7 @@ libgssapi_OBJs = \
        $(OBJ)\ntlm/set_sec_context_option.obj \
        $(OBJ)\ntlm/kdc.obj \
        $(OBJ)\gkrb5_err.obj \
+       $(OBJ)\negoex_err.obj \
        $(spnego_files:.x=.obj) \
        $(gssapi_files:.x=.obj)
 
@@ -588,6 +606,7 @@ $(OBJ)\gss-commands.c $(OBJ)\gss-commands.h: gss-commands.in
 
 (generate-obj-macro "libgssapi_OBJs"
                    (concat "\t$(OBJ)\\gkrb5_err.obj \\\n"
+                            "\t$(OBJ)\\negoex_err.obj \\\n"
                             "\t$(spnego_files:.x=.obj) \\\n"
                             "\t$(gssapi_files:.x=.obj)")
                     "krb5src" "mechsrc" "spnegosrc" "ntlmsrc")
index 30ffbe4adb30e350d07629bbd5815b593274a40d..2682718619ad74ab34c17862c30d2d139a116d0d 100644 (file)
@@ -132,6 +132,12 @@ extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_inq_win2k_pac_x_oid_desc;
 extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_inq_sspi_session_key_oid_desc;
 #define GSS_C_INQ_SSPI_SESSION_KEY (&__gss_c_inq_sspi_session_key_oid_desc)
 
+extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_inq_negoex_key_oid_desc;
+#define GSS_C_INQ_NEGOEX_KEY (&__gss_c_inq_negoex_key_oid_desc)
+
+extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_inq_negoex_verify_key_oid_desc;
+#define GSS_C_INQ_NEGOEX_VERIFY_KEY (&__gss_c_inq_negoex_verify_key_oid_desc)
+
 /*
  * "Standard" mechs
  */
@@ -151,6 +157,9 @@ extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_peer_has_updated_spnego_oid_desc
 extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_ntlm_reset_crypto_oid_desc;
 #define GSS_C_NTLM_RESET_CRYPTO (&__gss_c_ntlm_reset_crypto_oid_desc)
 
+extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_negoex_mechanism_oid_desc;
+#define GSS_NEGOEX_MECHANISM (&__gss_negoex_mechanism_oid_desc)
+
 /*
  * OID mappings with name and short description and and slightly longer description
  */
@@ -238,4 +247,7 @@ extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_ma_compress_oid_desc;
 extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_ma_ctx_trans_oid_desc;
 #define GSS_C_MA_CTX_TRANS (&__gss_c_ma_ctx_trans_oid_desc)
 
+extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_ma_negoex_and_spnego_oid_desc;
+#define GSS_C_MA_NEGOEX_AND_SPNEGO (&__gss_c_ma_negoex_and_spnego_oid_desc)
+
 #endif /* GSSAPI_GSSAPI_OID */
index dd3b2a5c3f8be2335f7329b7204d7ff02bc98288..8b4519e75728c68b368c709bda6cb99a919da153 100644 (file)
@@ -50,6 +50,38 @@ extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_spnego_mechanism_oid_desc;
 #define GSS_SPNEGO_MECHANISM (&__gss_spnego_mechanism_oid_desc)
 #define gss_mech_spnego GSS_SPNEGO_MECHANISM
 
+/*
+ * NegoEx extensions, to be implemented by mechanisms
+ */
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gssspi_query_mechanism_info(
+    OM_uint32 * /* minor_status */,
+    gss_const_OID /* mech_oid */,
+    unsigned char[16] /* auth_scheme */
+);
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+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 */,
+    gss_const_name_t /* targ_name */,
+    OM_uint32 /* req_flags */,
+    gss_buffer_t /* meta_data */
+);
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+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 */,
+    gss_const_name_t /* targ_name */,
+    OM_uint32 /* req_flags */,
+    gss_const_buffer_t /* meta_data */
+);
+
 GSSAPI_CPP_END
 
 #endif /* GSSAPI_SPNEGO_H_ */
index 6afc7be96d38decd7cb6203a9066573aaaf66aba..68732f7f77747cc9d72e6fae5e856804c923750e 100644 (file)
@@ -484,6 +484,29 @@ _gss_get_neg_mechs_t(OM_uint32 *minor_status,
                     gss_const_cred_id_t cred_handle,
                     gss_OID_set *mechs);
 
+typedef OM_uint32 GSSAPI_CALLCONV
+_gss_query_mechanism_info_t(OM_uint32 *minor_status,
+                           gss_const_OID mech_oid,
+                           unsigned char auth_scheme[16]);
+
+typedef OM_uint32 GSSAPI_CALLCONV
+_gss_query_meta_data_t(OM_uint32 *minor_status,
+                      gss_const_OID mech_oid,
+                      gss_cred_id_t cred_handle,
+                      gss_ctx_id_t *ctx_handle,
+                      gss_const_name_t targ_name,
+                      OM_uint32 req_flags,
+                      gss_buffer_t meta_data);
+
+typedef OM_uint32 GSSAPI_CALLCONV
+_gss_exchange_meta_data_t(OM_uint32 *minor_status,
+                         gss_const_OID mech_oid,
+                         gss_cred_id_t cred_handle,
+                         gss_ctx_id_t *ctx_handle,
+                         gss_const_name_t targ_name,
+                         OM_uint32 req_flags,
+                         gss_const_buffer_t meta_data);
+
 /*
  *
  */
@@ -597,6 +620,9 @@ typedef struct gssapi_mech_interface_desc {
        _gss_store_cred_into_t          *gm_store_cred_into;
        _gss_set_neg_mechs_t            *gm_set_neg_mechs;
        _gss_get_neg_mechs_t            *gm_get_neg_mechs;
+       _gss_query_mechanism_info_t     *gm_query_mechanism_info;
+       _gss_query_meta_data_t          *gm_query_meta_data;
+       _gss_exchange_meta_data_t       *gm_exchange_meta_data;
         struct gss_mech_compat_desc_struct  *gm_compat;
 } gssapi_mech_interface_desc, *gssapi_mech_interface;
 
@@ -669,4 +695,17 @@ gss_mg_set_error_string(gss_OID mech,
                        OM_uint32 maj, OM_uint32 min,
                        const char *fmt, ...);
 
+gss_cred_id_t
+_gss_mg_find_mech_cred(gss_const_cred_id_t cred_handle,
+                      gss_const_OID mech_type);
+
+#include <krb5.h>
+
+/*
+ * Mechglue krb5 context for use by NegoEx. This is not shared with the
+ * krb5 GSS mechanism so we don't clobber its error state.
+ */
+krb5_context
+_gss_mg_krb5_context(void);
+
 #endif /* GSSAPI_MECH_H */
index a3928fed5374d155b0813b14cb3667be398fb826..71fc3974db7d66ec222965176f3dbb452157e80e 100644 (file)
@@ -401,6 +401,9 @@ static gssapi_mech_interface_desc krb5_mech = {
     _gsskrb5_store_cred_into,
     NULL, /* gm_set_neg_mechs */
     NULL, /* gm_get_neg_mechs */
+    NULL, /* gm_query_mechanism_info */
+    NULL, /* gm_query_meta_data */
+    NULL, /* gm_exchange_meta_data */
     NULL  /* gm_compat */
 };
 
index 6d0f8bb4a3ceb8056575f3474fd11ff4dc9ef1b1..992e8eb3fa170f2e824ffb67feffe87c2eb28e23 100644 (file)
@@ -195,3 +195,6 @@ EXPORTS
        __gss_c_ma_pfs_oid_desc DATA
        __gss_c_ma_compress_oid_desc    DATA
        __gss_c_ma_ctx_trans_oid_desc   DATA
+       __gss_c_ma_negoex_and_spnego_oid_desc   DATA
+       __gss_c_inq_negoex_key_oid_desc DATA
+       __gss_c_inq_negoex_verify_key_oid_desc  DATA
index 889ed6160c084694fe6c8dfa957b19eb56453194..5a029b79a2c9847f890d945a247ae26f15c488c6 100644 (file)
@@ -37,6 +37,7 @@
 #include "heim_threads.h"
 #include <krb5.h>
 #include "krb5_locl.h"
+#include "negoex_err.h"
 
 struct mg_thread_ctx {
     gss_OID mech;
@@ -99,6 +100,8 @@ _gss_mechglue_thread(void)
            return NULL;
        }
 
+       krb5_add_et_list(ctx->context, initialize_ngex_error_table_r);
+
        HEIMDAL_setspecific(context_key, ctx, ret);
        if (ret) {
            krb5_free_context(ctx->context);
@@ -109,6 +112,16 @@ _gss_mechglue_thread(void)
     return ctx;
 }
 
+krb5_context
+_gss_mg_krb5_context(void)
+{
+    struct mg_thread_ctx *mg;
+
+    mg = _gss_mechglue_thread();
+
+    return mg ? mg->context : NULL;
+}
+
 OM_uint32
 _gss_mg_get_error(const gss_OID mech,
                  OM_uint32 value,
index 0970513f92a5068a51450da64eb596f2e15b8b43..250eafef251530b19ac5ca58b63d2db8db8fd65d 100644 (file)
 
 #include "mech_locl.h"
 
-static gss_cred_id_t
-_gss_mech_cred_find(gss_const_cred_id_t cred_handle, gss_OID mech_type)
+gss_cred_id_t
+_gss_mg_find_mech_cred(
+    gss_const_cred_id_t cred_handle,
+    gss_const_OID mech_type)
 {
        struct _gss_cred *cred = (struct _gss_cred *)cred_handle;
        struct _gss_mechanism_cred *mc;
@@ -227,7 +229,7 @@ gss_init_sec_context(OM_uint32 * minor_status,
        if (m->gm_flags & GM_USE_MG_CRED)
                cred_handle = initiator_cred_handle;
        else
-               cred_handle = _gss_mech_cred_find(initiator_cred_handle, mech_type);
+               cred_handle = _gss_mg_find_mech_cred(initiator_cred_handle, mech_type);
 
         if (initiator_cred_handle != GSS_C_NO_CREDENTIAL &&
             cred_handle == NULL) {
index 5e2348faa7baf8dbd4ab850a572753271be6b308..f7408c84cb382e55f7dcc1d0958b95cfa2fa9de6 100644 (file)
@@ -96,8 +96,8 @@ gss_inquire_cred(OM_uint32 *minor_status,
                struct _gss_mechanism_cred *mc;
 
                HEIM_TAILQ_FOREACH(mc, &cred->gc_mc, gmc_link) {
-                       gss_name_t mc_name;
-                       OM_uint32 mc_lifetime;
+                       gss_name_t mc_name = GSS_C_NO_NAME;
+                       OM_uint32 mc_lifetime = GSS_C_INDEFINITE;
 
                        if (mc->gmc_mech->gm_inquire_cred == NULL)
                                continue;
index 6bad68611becd745a7ca67d1cdb97809ff495a1c..e4a56f3ce95069da4d7c2cebd58355c813bdfd86 100644 (file)
@@ -179,11 +179,20 @@ do {                                                                      \
                m->gm_mech.gm_ ## name = NULL;                          \
 } while (0)
 
+/* mech exports gssspi_XXX, internally referred to as gss_XXX */
 #define OPTSPISYM(name)                                                        \
 do {                                                                   \
        m->gm_mech.gm_ ## name =  (_gss_##name##_t *)dlsym(so, "gssspi_" #name); \
 } while (0)
 
+/* mech exports gssspi_XXX, internally referred to as gssspi_XXX */
+#define OPTSPISPISYM(name)                                                     \
+do {                                                                   \
+       m->gm_mech.gm_ ## name =  (_gss_##name##_t *)dlsym(so, "gssspi_" #name); \
+       if (m->gm_mech.gm_ ## name == gssspi_ ## name)                  \
+               m->gm_mech.gm_ ## name = NULL;                          \
+} while (0)
+
 #define COMPATSYM(name)                                                        \
 do {                                                                   \
        m->gm_mech.gm_compat->gmc_ ## name =  (_gss_##name##_t *)dlsym(so, "gss_" #name); \
@@ -262,6 +271,7 @@ _gss_load_mech(void)
        void            *so;
        gss_OID         mech_oid;
        int             found;
+       const char      *conf = secure_getenv("GSS_MECH_CONFIG");
 #endif
 
        heim_base_once_f(&once, &_gss_mechs, init_mech_switch_list);
@@ -285,7 +295,7 @@ _gss_load_mech(void)
        add_builtin(__gss_ntlm_initialize());
 
 #ifdef HAVE_DLOPEN
-       fp = fopen(_PATH_GSS_MECH, "r");
+       fp = fopen(conf ? conf : _PATH_GSS_MECH, "r");
        if (!fp) {
                HEIMDAL_MUTEX_unlock(&_gss_mech_mutex);
                return;
@@ -410,6 +420,9 @@ _gss_load_mech(void)
                OPTSYM(set_neg_mechs);
                OPTSYM(get_neg_mechs);
                OPTSPISYM(authorize_localname);
+               OPTSPISPISYM(query_mechanism_info);
+               OPTSPISPISYM(query_meta_data);
+               OPTSPISPISYM(exchange_meta_data);
 
                mi = (_gss_mo_init *)dlsym(so, "gss_mo_init");
                if (mi != NULL) {
index 5b9bd362360ee0cd2e351fb936aa47253347412c..14d53c97794bc5605167f99101617fbc26764dba 100644 (file)
@@ -124,6 +124,12 @@ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_inq_win2k_pac_x_oid_desc = { 8, rk_UNCO
 /* GSS_C_INQ_SSPI_SESSION_KEY - 1.2.840.113554.1.2.2.5.5 */
 gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_inq_sspi_session_key_oid_desc = { 11, rk_UNCONST("\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 */
+gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_inq_negoex_key_oid_desc = { 11, rk_UNCONST("\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 */
+gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_inq_negoex_verify_key_oid_desc = { 11, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x11") };
+
 /* GSS_KRB5_MECHANISM - 1.2.840.113554.1.2.2 */
 gss_OID_desc GSSAPI_LIB_VARIABLE __gss_krb5_mechanism_oid_desc = { 9, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02") };
 
@@ -139,6 +145,9 @@ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_peer_has_updated_spnego_oid_desc = { 9,
 /* GSS_C_NTLM_RESET_CRYPTO - 1.3.6.1.4.1.7165.655.1.3 */
 gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_ntlm_reset_crypto_oid_desc = { 11, rk_UNCONST("\x2b\x06\x01\x04\x01\xb7\x7d\x85\x0f\x01\x03") };
 
+/* GSS_NEGOEX_MECHANISM - 1.3.6.1.4.1.311.2.2.30 */
+gss_OID_desc GSSAPI_LIB_VARIABLE __gss_negoex_mechanism_oid_desc = { 10, rk_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e") };
+
 /* GSS_C_MA_MECH_CONCRETE - 1.3.6.1.5.5.13.1 */
 gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_ma_mech_concrete_oid_desc = { 7, rk_UNCONST("\x2b\x06\x01\x05\x05\x0d\x01") };
 
@@ -220,6 +229,9 @@ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_ma_compress_oid_desc = { 7, rk_UNCONST(
 /* GSS_C_MA_CTX_TRANS - 1.3.6.1.5.5.13.27 */
 gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_ma_ctx_trans_oid_desc = { 7, rk_UNCONST("\x2b\x06\x01\x05\x05\x0d\x1b") };
 
+/* GSS_C_MA_NEGOEX_AND_SPNEGO - 1.2.840.113554.1.2.2.5.18 */
+gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_ma_negoex_and_spnego_oid_desc = { 11, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x12") };
+
 struct _gss_oid_name_table _gss_ont_ma[] = {
   { GSS_C_MA_AUTH_INIT, "GSS_C_MA_AUTH_INIT", "auth-init-princ", "" },
   { GSS_C_MA_AUTH_INIT_ANON, "GSS_C_MA_AUTH_INIT_ANON", "auth-init-princ-anon", "" },
@@ -243,6 +255,7 @@ struct _gss_oid_name_table _gss_ont_ma[] = {
   { GSS_C_MA_MECH_NEGO, "GSS_C_MA_MECH_NEGO", "mech-negotiation-mech", "" },
   { GSS_C_MA_MECH_PSEUDO, "GSS_C_MA_MECH_PSEUDO", "pseudo-mech", "" },
   { GSS_C_MA_MIC, "GSS_C_MA_MIC", "mic", "" },
+  { GSS_C_MA_NEGOEX_AND_SPNEGO, "GSS_C_MA_NEGOEX_AND_SPNEGO", "negoex-and-spnego", "Indicates that a mechanism supports both NegoEx and SPNEGO" },
   { GSS_C_MA_NOT_DFLT_MECH, "GSS_C_MA_NOT_DFLT_MECH", "mech-not-default", "" },
   { GSS_C_MA_NOT_MECH, "GSS_C_MA_NOT_MECH", "not-mech", "" },
   { GSS_C_MA_OOS_DET, "GSS_C_MA_OOS_DET", "oos-detection", "" },
@@ -303,11 +316,14 @@ gss_OID _gss_ot_internal[] = {
   &__gss_netlogon_nt_netbios_dns_name_oid_desc,
   &__gss_c_inq_win2k_pac_x_oid_desc,
   &__gss_c_inq_sspi_session_key_oid_desc,
+  &__gss_c_inq_negoex_key_oid_desc,
+  &__gss_c_inq_negoex_verify_key_oid_desc,
   &__gss_krb5_mechanism_oid_desc,
   &__gss_ntlm_mechanism_oid_desc,
   &__gss_spnego_mechanism_oid_desc,
   &__gss_c_peer_has_updated_spnego_oid_desc,
   &__gss_c_ntlm_reset_crypto_oid_desc,
+  &__gss_negoex_mechanism_oid_desc,
   &__gss_c_ma_mech_concrete_oid_desc,
   &__gss_c_ma_mech_pseudo_oid_desc,
   &__gss_c_ma_mech_composite_oid_desc,
@@ -335,6 +351,7 @@ gss_OID _gss_ot_internal[] = {
   &__gss_c_ma_pfs_oid_desc,
   &__gss_c_ma_compress_oid_desc,
   &__gss_c_ma_ctx_trans_oid_desc,
+  &__gss_c_ma_negoex_and_spnego_oid_desc,
 };
 
 size_t _gss_ot_internal_count = sizeof(_gss_ot_internal) / sizeof(_gss_ot_internal[0]);
index 571382b78ec60c420b32d7d3d3997e59396e09c9..f9e79c1440fdaf0ce24a754dcc991982ceed8d6f 100644 (file)
@@ -147,3 +147,62 @@ _gss_copy_buffer(OM_uint32 *minor_status,
        return (GSS_S_COMPLETE);
 }
 
+void
+_gss_mg_encode_le_uint32(uint32_t n, uint8_t *p)
+{
+    p[0] = (n >> 0 ) & 0xFF;
+    p[1] = (n >> 8 ) & 0xFF;
+    p[2] = (n >> 16) & 0xFF;
+    p[3] = (n >> 24) & 0xFF;
+}
+
+void
+_gss_mg_decode_le_uint32(const void *ptr, uint32_t *n)
+{
+    const uint8_t *p = ptr;
+    *n = (p[0] << 0) | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+}
+
+void
+_gss_mg_encode_be_uint32(uint32_t n, uint8_t *p)
+{
+    p[0] = (n >> 24) & 0xFF;
+    p[1] = (n >> 16) & 0xFF;
+    p[2] = (n >> 8 ) & 0xFF;
+    p[3] = (n >> 0 ) & 0xFF;
+}
+
+void
+_gss_mg_decode_be_uint32(const void *ptr, uint32_t *n)
+{
+    const uint8_t *p = ptr;
+    *n = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3] << 0);
+}
+
+void
+_gss_mg_encode_le_uint16(uint16_t n, uint8_t *p)
+{
+    p[0] = (n >> 0 ) & 0xFF;
+    p[1] = (n >> 8 ) & 0xFF;
+}
+
+void
+_gss_mg_decode_le_uint16(const void *ptr, uint16_t *n)
+{
+    const uint8_t *p = ptr;
+    *n = (p[0] << 0) | (p[1] << 8);
+}
+
+void
+_gss_mg_encode_be_uint16(uint16_t n, uint8_t *p)
+{
+    p[0] = (n >> 24) & 0xFF;
+    p[1] = (n >> 16) & 0xFF;
+}
+
+void
+_gss_mg_decode_be_uint16(const void *ptr, uint16_t *n)
+{
+    const uint8_t *p = ptr;
+    *n = (p[0] << 24) | (p[1] << 16);
+}
diff --git a/lib/gssapi/mech/gssspi_exchange_meta_data.c b/lib/gssapi/mech/gssspi_exchange_meta_data.c
new file mode 100644 (file)
index 0000000..1a83bf6
--- /dev/null
@@ -0,0 +1,115 @@
+/*-
+ * Copyright (c) 2005 Doug Rabson
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ * Portions Copyright (c) 2019 AuriStor, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "mech_locl.h"
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gssspi_exchange_meta_data(
+    OM_uint32 *minor_status,
+    gss_const_OID input_mech_type,
+    gss_cred_id_t input_cred_handle,
+    gss_ctx_id_t *context_handle,
+    gss_const_name_t target_name,
+    OM_uint32 req_flags,
+    gss_const_buffer_t meta_data)
+{
+    OM_uint32 major_status, junk;
+    gssapi_mech_interface m;
+    struct _gss_name *name = (struct _gss_name *) target_name;
+    struct _gss_mechanism_name *mn;
+    struct _gss_context *ctx = (struct _gss_context *) *context_handle;
+    gss_cred_id_t cred_handle;
+    int allocated_ctx;
+    gss_const_OID mech_type = input_mech_type;
+
+    *minor_status = 0;
+
+    if (mech_type == GSS_C_NO_OID)
+       return GSS_S_BAD_MECH;
+
+    if (ctx == NULL) {
+       ctx = calloc(1, sizeof(struct _gss_context));
+       if (ctx == NULL) {
+           *minor_status = ENOMEM;
+           return GSS_S_FAILURE;
+       }
+
+       m = ctx->gc_mech = __gss_get_mechanism(mech_type);
+       if (m == NULL) {
+           free(ctx);
+           return GSS_S_BAD_MECH;
+       }
+       allocated_ctx = 1;
+    } else {
+       m = ctx->gc_mech;
+       mech_type = &m->gm_mech_oid;
+       allocated_ctx = 0;
+    }
+
+    if (m->gm_exchange_meta_data == NULL) {
+       major_status = GSS_S_BAD_MECH;
+       goto cleanup;
+    }
+
+    major_status = _gss_find_mn(minor_status, name, mech_type, &mn);
+    if (major_status != GSS_S_COMPLETE)
+       goto cleanup;
+
+    if (m->gm_flags & GM_USE_MG_CRED)
+       cred_handle = input_cred_handle;
+    else
+       cred_handle = _gss_mg_find_mech_cred(input_cred_handle, mech_type);
+
+    if (input_cred_handle != GSS_C_NO_CREDENTIAL &&
+       cred_handle == NULL) {
+       major_status = GSS_S_NO_CRED;
+       goto cleanup;
+    }
+
+    /* note: mechanism is not obligated to allocate a context on success */
+    major_status = m->gm_exchange_meta_data(minor_status,
+           mech_type,
+           cred_handle,
+           &ctx->gc_ctx,
+           mn ? mn->gmn_name : GSS_C_NO_NAME,
+           req_flags,
+           meta_data);
+    if (major_status != GSS_S_COMPLETE)
+       _gss_mg_error(m, *minor_status);
+
+cleanup:
+    if (major_status != GSS_S_COMPLETE || ctx->gc_ctx == GSS_C_NO_CONTEXT)
+       gss_delete_sec_context(&junk, (gss_ctx_id_t *)&ctx, GSS_C_NO_BUFFER);
+
+    *context_handle = (gss_ctx_id_t) ctx;
+
+    _gss_mg_log(10, "gss-emd: return %d/%d", (int)major_status, (int)*minor_status);
+
+    return major_status;
+}
diff --git a/lib/gssapi/mech/gssspi_query_mechanism_info.c b/lib/gssapi/mech/gssspi_query_mechanism_info.c
new file mode 100644 (file)
index 0000000..82fe9d9
--- /dev/null
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 2019 AuriStor, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "mech_locl.h"
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gssspi_query_mechanism_info(
+    OM_uint32 *minor_status,
+    gss_const_OID mech_type,
+    unsigned char auth_scheme[16])
+{
+    OM_uint32 major_status;
+    gssapi_mech_interface m;
+
+    *minor_status = 0;
+
+    if (mech_type == GSS_C_NO_OID)
+       return GSS_S_BAD_MECH;
+
+    m = __gss_get_mechanism(mech_type);
+    if (m == NULL || m->gm_query_mechanism_info == NULL)
+       return GSS_S_BAD_MECH;
+
+    major_status = m->gm_query_mechanism_info(minor_status,
+                                             mech_type,
+                                             auth_scheme);
+
+    if (major_status != GSS_S_COMPLETE)
+       _gss_mg_error(m, *minor_status);
+
+    return major_status;
+}
diff --git a/lib/gssapi/mech/gssspi_query_meta_data.c b/lib/gssapi/mech/gssspi_query_meta_data.c
new file mode 100644 (file)
index 0000000..daf679a
--- /dev/null
@@ -0,0 +1,117 @@
+/*-
+ * Copyright (c) 2005 Doug Rabson
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ * Portions Copyright (c) 2019 AuriStor, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "mech_locl.h"
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gssspi_query_meta_data(
+    OM_uint32 *minor_status,
+    gss_const_OID input_mech_type,
+    gss_cred_id_t input_cred_handle,
+    gss_ctx_id_t *context_handle,
+    gss_const_name_t target_name,
+    OM_uint32 req_flags,
+    gss_buffer_t meta_data)
+{
+    OM_uint32 major_status, junk;
+    gssapi_mech_interface m;
+    struct _gss_name *name = (struct _gss_name *) target_name;
+    struct _gss_mechanism_name *mn;
+    struct _gss_context *ctx = (struct _gss_context *) *context_handle;
+    gss_cred_id_t cred_handle;
+    int allocated_ctx;
+    gss_const_OID mech_type = input_mech_type;
+
+    *minor_status = 0;
+
+    _mg_buffer_zero(meta_data);
+
+    if (mech_type == GSS_C_NO_OID)
+       return GSS_S_BAD_MECH;
+
+    if (ctx == NULL) {
+       ctx = calloc(1, sizeof(struct _gss_context));
+       if (ctx == NULL) {
+           *minor_status = ENOMEM;
+           return GSS_S_FAILURE;
+       }
+
+       m = ctx->gc_mech = __gss_get_mechanism(mech_type);
+       if (m == NULL) {
+           free(ctx);
+           return GSS_S_BAD_MECH;
+       }
+       allocated_ctx = 1;
+    } else {
+       m = ctx->gc_mech;
+       mech_type = &m->gm_mech_oid;
+       allocated_ctx = 0;
+    }
+
+    if (m->gm_query_meta_data == NULL) {
+       major_status = GSS_S_BAD_MECH;
+       goto cleanup;
+    }
+
+    major_status = _gss_find_mn(minor_status, name, mech_type, &mn);
+    if (major_status != GSS_S_COMPLETE)
+       goto cleanup;
+
+    if (m->gm_flags & GM_USE_MG_CRED)
+       cred_handle = input_cred_handle;
+    else
+       cred_handle = _gss_mg_find_mech_cred(input_cred_handle, mech_type);
+
+    if (input_cred_handle != GSS_C_NO_CREDENTIAL &&
+       cred_handle == NULL) {
+       major_status = GSS_S_NO_CRED;
+       goto cleanup;
+    }
+
+    /* note: mechanism is not obligated to allocate a context on success */
+    major_status = m->gm_query_meta_data(minor_status,
+           mech_type,
+           cred_handle,
+           &ctx->gc_ctx,
+           mn ? mn->gmn_name : GSS_C_NO_NAME,
+           req_flags,
+           meta_data);
+    if (major_status != GSS_S_COMPLETE)
+       _gss_mg_error(m, *minor_status);
+
+cleanup:
+    if (major_status != GSS_S_COMPLETE || ctx->gc_ctx == GSS_C_NO_CONTEXT)
+       gss_delete_sec_context(&junk, (gss_ctx_id_t *)&ctx, GSS_C_NO_BUFFER);
+
+    *context_handle = (gss_ctx_id_t) ctx;
+
+    _gss_mg_log(10, "gss-qmd: return %d/%d", (int)major_status, (int)*minor_status);
+
+    return major_status;
+}
index 5e65a0786fc9bb755d86388bcd601b3f18b95572..0d74091e054c2b68090b6b1e329477f918b00124 100644 (file)
@@ -56,6 +56,7 @@
 #include <gssapi.h>
 #include <gssapi_mech.h>
 #include <gssapi_krb5.h>
+#include <gssapi_spnego.h>
 
 #include <heimqueue.h>
 
index 77e308f28128e4a24e593fb59aec93f56ac87553..b0c9ead1d91f24c8a65f39133d1d7eb660a13312 100644 (file)
@@ -31,3 +31,13 @@ OM_uint32 _gss_free_oid(OM_uint32 *, gss_OID);
 OM_uint32 _gss_intern_oid(OM_uint32 *, gss_const_OID, gss_OID *);
 OM_uint32 _gss_copy_buffer(OM_uint32 *minor_status,
     const gss_buffer_t from_buf, gss_buffer_t to_buf);
+
+void _gss_mg_encode_le_uint32(uint32_t n, uint8_t *p);
+void _gss_mg_decode_le_uint32(const void *ptr, uint32_t *n);
+void _gss_mg_encode_be_uint32(uint32_t n, uint8_t *p);
+void _gss_mg_decode_be_uint32(const void *ptr, uint32_t *n);
+
+void _gss_mg_encode_le_uint16(uint16_t n, uint8_t *p);
+void _gss_mg_decode_le_uint16(const void *ptr, uint16_t *n);
+void _gss_mg_encode_be_uint16(uint16_t n, uint8_t *p);
+void _gss_mg_decode_be_uint16(const void *ptr, uint16_t *n);
index 3c0fba86c7e2d1a2de076e340bd138ca5f9d2a6c..01162797537d64b5c711783ccadceeef1e22061c 100644 (file)
@@ -127,6 +127,9 @@ static gssapi_mech_interface_desc ntlm_mech = {
     NULL, /* gm_store_cred_into */
     NULL, /* gm_set_neg_mechs */
     NULL, /* gm_get_neg_mechs */
+    NULL, /* gm_query_mechanism_info */
+    NULL, /* gm_query_meta_data */
+    NULL, /* gm_exchange_meta_data */
     NULL, /* gm_compat */
 };
 
index 5eaa4f58573b71d8f44a3efdd39a267efb2f83c3..20b749b41b5beb87b450e09c2daa670a5c34c4d9 100644 (file)
@@ -52,6 +52,8 @@ oid   base    GSS_NETLOGON_NT_NETBIOS_DNS_NAME        1.2.752.43.14.5
 #/* GSS_KRB5_EXTRACT_AUTHZ_DATA_FROM_SEC_CONTEXT_X.128 */
 oid    base    GSS_C_INQ_WIN2K_PAC_X                   1.2.752.43.13.3.128
 oid    base    GSS_C_INQ_SSPI_SESSION_KEY              1.2.840.113554.1.2.2.5.5
+oid    base    GSS_C_INQ_NEGOEX_KEY                    1.2.840.113554.1.2.2.5.16
+oid    base    GSS_C_INQ_NEGOEX_VERIFY_KEY             1.2.840.113554.1.2.2.5.17
 
 #/*
 # * "Standard" mechs
@@ -65,6 +67,7 @@ oid   base    GSS_SPNEGO_MECHANISM                    1.3.6.1.5.5.2
 
 oid    base    GSS_C_PEER_HAS_UPDATED_SPNEGO           1.3.6.1.4.1.5322.19.5
 oid    base    GSS_C_NTLM_RESET_CRYPTO                 1.3.6.1.4.1.7165.655.1.3
+oid    base    GSS_NEGOEX_MECHANISM                    1.3.6.1.4.1.311.2.2.30
 
 
 #/*
@@ -110,6 +113,7 @@ oid base    GSS_C_MA_CBINDINGS                      1.3.6.1.5.5.13.24
 oid    base    GSS_C_MA_PFS                            1.3.6.1.5.5.13.25
 oid    base    GSS_C_MA_COMPRESS                       1.3.6.1.5.5.13.26
 oid    base    GSS_C_MA_CTX_TRANS                      1.3.6.1.5.5.13.27
+oid    base    GSS_C_MA_NEGOEX_AND_SPNEGO              1.2.840.113554.1.2.2.5.18
 
 desc   ma      GSS_C_MA_MECH_CONCRETE  "concrete-mech" "Indicates that a mech is neither a pseudo-mechanism nor a composite mechanism"
 desc   ma      GSS_C_MA_MECH_PSEUDO "pseudo-mech" ""
@@ -138,3 +142,4 @@ desc        ma      GSS_C_MA_CBINDINGS "channel-bindings" ""
 desc   ma      GSS_C_MA_PFS "pfs" ""
 desc   ma      GSS_C_MA_COMPRESS "compress" ""
 desc   ma      GSS_C_MA_CTX_TRANS "context-transfer" ""
+desc   ma      GSS_C_MA_NEGOEX_AND_SPNEGO "negoex-and-spnego" "Indicates that a mechanism supports both NegoEx and SPNEGO"
index 889d1636f1f9da966559e7b4849dc71bd4fc26bd..b4384fd25c7162221ff6e7e69be4dd3ef184e459 100644 (file)
@@ -63,37 +63,60 @@ send_reject (OM_uint32 *minor_status,
 }
 
 static OM_uint32
-acceptor_approved(gss_const_cred_id_t input_cred,
-                 gss_name_t target_name,
+acceptor_approved(OM_uint32 *minor_status,
+                 void *userptr,
+                 gss_const_name_t target_name,
+                 gss_const_cred_id_t cred_handle,
                  gss_OID mech)
 {
     gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
-    gss_OID_set oidset;
+    gss_OID_set oidset = GSS_C_NO_OID_SET;
     OM_uint32 junk, ret;
 
     if (target_name == GSS_C_NO_NAME)
        return GSS_S_COMPLETE;
 
-    if (input_cred != GSS_C_NO_CREDENTIAL) {
-       return gss_inquire_cred_by_mech(&junk, input_cred, mech,
-                                       NULL, NULL, NULL, NULL);
-    }
+    if (gss_oid_equal(mech, GSS_NEGOEX_MECHANISM)) {
+       size_t i;
+
+       ret = _gss_spnego_indicate_mechs(minor_status, &oidset);
+       if (ret != GSS_S_COMPLETE)
+           return ret;
 
-    gss_create_empty_oid_set(&junk, &oidset);
-    gss_add_oid_set_member(&junk, mech, &oidset);
+       /* before committing to NegoEx, check we can negotiate a mech */
+       for (i = 0; i < oidset->count; i++) {
+           gss_OID inner_mech = &oidset->elements[i];
+
+           if (_gss_negoex_mech_p(inner_mech)) {
+               ret = acceptor_approved(minor_status, userptr,
+                                       target_name, cred_handle,
+                                       inner_mech);
+               if (ret == GSS_S_COMPLETE)
+                   break;
+           }
+       }
+    } else if (cred_handle != GSS_C_NO_CREDENTIAL) {
+       ret = gss_inquire_cred_by_mech(minor_status, cred_handle, mech,
+                                      NULL, NULL, NULL, NULL);
+    } else {
+       ret = gss_create_empty_oid_set(minor_status, &oidset);
+       if (ret == GSS_S_COMPLETE)
+           ret = gss_add_oid_set_member(minor_status, mech, &oidset);
+       if (ret == GSS_S_COMPLETE)
+           ret = gss_acquire_cred(minor_status, target_name,
+                                  GSS_C_INDEFINITE, oidset,
+                              GSS_C_ACCEPT, &cred, NULL, NULL);
+    }
 
-    ret = gss_acquire_cred(&junk, target_name, GSS_C_INDEFINITE, oidset,
-                          GSS_C_ACCEPT, &cred, NULL, NULL);
     gss_release_oid_set(&junk, &oidset);
-    if (ret != GSS_S_COMPLETE)
-       return ret;
     gss_release_cred(&junk, &cred);
 
-    return GSS_S_COMPLETE;
+    return ret;
 }
 
 static OM_uint32
 send_supported_mechs (OM_uint32 *minor_status,
+                     gssspnego_ctx ctx,
                      gss_const_cred_id_t acceptor_cred,
                      gss_buffer_t output_token)
 {
@@ -109,8 +132,8 @@ send_supported_mechs (OM_uint32 *minor_status,
     nt.u.negTokenInit.mechToken = NULL;
     nt.u.negTokenInit.negHints = NULL;
 
-    ret = _gss_spnego_indicate_mechtypelist(minor_status, GSS_C_NO_NAME,
-                                           acceptor_approved, 1, acceptor_cred,
+    ret = _gss_spnego_indicate_mechtypelist(minor_status, NULL,
+                                           acceptor_approved, ctx, 1, acceptor_cred,
                                            &nt.u.negTokenInit.mechTypes, NULL);
     if (ret != GSS_S_COMPLETE) {
        return ret;
@@ -160,13 +183,15 @@ send_supported_mechs (OM_uint32 *minor_status,
 static OM_uint32
 send_accept (OM_uint32 *minor_status,
             gssspnego_ctx context_handle,
+            int optimistic_mech_ok,
             gss_buffer_t mech_token,
-            int initial_response,
+            gss_const_OID selected_mech, /* valid on initial response only */
             gss_buffer_t mech_buf,
             gss_buffer_t output_token)
 {
+    int initial_response = (selected_mech != GSS_C_NO_OID);
     NegotiationToken nt;
-    OM_uint32 ret;
+    OM_uint32 ret, minor;
     gss_buffer_desc mech_mic_buf;
     size_t size;
 
@@ -180,7 +205,7 @@ send_accept (OM_uint32 *minor_status,
        return GSS_S_FAILURE;
     }
 
-    if (context_handle->open) {
+    if (context_handle->flags.open) {
        if (mech_token != GSS_C_NO_BUFFER
            && mech_token->length != 0
            && mech_buf != GSS_C_NO_BUFFER)
@@ -188,7 +213,7 @@ send_accept (OM_uint32 *minor_status,
        else
            *(nt.u.negTokenResp.negResult)  = accept_completed;
     } else {
-       if (initial_response && context_handle->require_mic)
+       if (initial_response && !optimistic_mech_ok)
            *(nt.u.negTokenResp.negResult)  = request_mic;
        else
            *(nt.u.negTokenResp.negResult)  = accept_incomplete;
@@ -197,20 +222,22 @@ send_accept (OM_uint32 *minor_status,
     if (initial_response) {
        ALLOC(nt.u.negTokenResp.supportedMech, 1);
        if (nt.u.negTokenResp.supportedMech == NULL) {
-           free_NegotiationToken(&nt);
            *minor_status = ENOMEM;
-           return GSS_S_FAILURE;
+           ret = GSS_S_FAILURE;
+           goto out;
        }
 
-       ret = der_get_oid(context_handle->preferred_mech_type->elements,
-                         context_handle->preferred_mech_type->length,
+       ret = der_get_oid(selected_mech->elements,
+                         selected_mech->length,
                          nt.u.negTokenResp.supportedMech,
                          NULL);
        if (ret) {
-           free_NegotiationToken(&nt);
            *minor_status = ENOMEM;
-           return GSS_S_FAILURE;
+           ret = GSS_S_FAILURE;
+           goto out;
        }
+
+       _gss_spnego_log_mech("acceptor sending selected mech", selected_mech);
     } else {
        nt.u.negTokenResp.supportedMech = NULL;
     }
@@ -218,9 +245,9 @@ send_accept (OM_uint32 *minor_status,
     if (mech_token != GSS_C_NO_BUFFER && mech_token->length != 0) {
        ALLOC(nt.u.negTokenResp.responseToken, 1);
        if (nt.u.negTokenResp.responseToken == NULL) {
-           free_NegotiationToken(&nt);
            *minor_status = ENOMEM;
-           return GSS_S_FAILURE;
+           ret = GSS_S_FAILURE;
+           goto out;
        }
        nt.u.negTokenResp.responseToken->length = mech_token->length;
        nt.u.negTokenResp.responseToken->data   = mech_token->value;
@@ -236,25 +263,22 @@ send_accept (OM_uint32 *minor_status,
                          0,
                          mech_buf,
                          &mech_mic_buf);
-       if (ret == GSS_S_COMPLETE &&
-           gss_oid_equal(context_handle->negotiated_mech_type,
-                         GSS_NTLM_MECHANISM))
-           _gss_spnego_ntlm_reset_crypto(minor_status, context_handle, 0);
        if (ret == GSS_S_COMPLETE) {
+           _gss_spnego_ntlm_reset_crypto(&minor, context_handle, FALSE);
+
            ALLOC(nt.u.negTokenResp.mechListMIC, 1);
            if (nt.u.negTokenResp.mechListMIC == NULL) {
                gss_release_buffer(minor_status, &mech_mic_buf);
-               free_NegotiationToken(&nt);
                *minor_status = ENOMEM;
-               return GSS_S_FAILURE;
+               ret = GSS_S_FAILURE;
+               goto out;
            }
            nt.u.negTokenResp.mechListMIC->length = mech_mic_buf.length;
            nt.u.negTokenResp.mechListMIC->data   = mech_mic_buf.value;
        } else if (ret == GSS_S_UNAVAILABLE) {
            nt.u.negTokenResp.mechListMIC = NULL;
        } else {
-           free_NegotiationToken(&nt);
-           return ret;
+           goto out;
        }
 
     } else
@@ -264,9 +288,9 @@ send_accept (OM_uint32 *minor_status,
                       output_token->value, output_token->length,
                       &nt, &size, ret);
     if (ret) {
-       free_NegotiationToken(&nt);
-       *minor_status = ret;
-       return GSS_S_FAILURE;
+       *minor_status = ENOMEM;
+       ret = GSS_S_FAILURE;
+       goto out;
     }
 
     /*
@@ -279,136 +303,165 @@ send_accept (OM_uint32 *minor_status,
        ret = GSS_S_COMPLETE;
     else
        ret = GSS_S_CONTINUE_NEEDED;
+
+ out:
     free_NegotiationToken(&nt);
     return ret;
 }
 
+/*
+ * Return the default acceptor identity based on the local hostname
+ * or the GSSAPI_SPNEGO_NAME environment variable.
+ */
 
 static OM_uint32
-verify_mechlist_mic
-          (OM_uint32 *minor_status,
-           gssspnego_ctx context_handle,
-           gss_buffer_t mech_buf,
-           heim_octet_string *mechListMIC
-          )
+default_acceptor_name(OM_uint32 *minor_status,
+                     gss_name_t *namep)
 {
-    OM_uint32 ret;
-    gss_buffer_desc mic_buf;
+    OM_uint32 major_status;
+    gss_buffer_desc namebuf;
+    char *str = NULL, *host, hostname[MAXHOSTNAMELEN];
 
-    if (context_handle->verified_mic) {
-       /* This doesn't make sense, we've already verified it? */
-       *minor_status = 0;
-       return GSS_S_DUPLICATE_TOKEN;
-    }
+    *namep = GSS_C_NO_NAME;
 
-    if (mechListMIC == NULL) {
-       *minor_status = 0;
-       return GSS_S_DEFECTIVE_TOKEN;
+    host = secure_getenv("GSSAPI_SPNEGO_NAME");
+    if (host == NULL) {
+       int rv;
+
+       if (gethostname(hostname, sizeof(hostname)) != 0) {
+           *minor_status = errno;
+           return GSS_S_FAILURE;
+       }
+
+       rv = asprintf(&str, "host@%s", hostname);
+       if (rv < 0 || str == NULL) {
+           *minor_status = ENOMEM;
+           return GSS_S_FAILURE;
+       }
+       host = str;
     }
 
-    mic_buf.length = mechListMIC->length;
-    mic_buf.value  = mechListMIC->data;
+    namebuf.length = strlen(host);
+    namebuf.value = host;
 
-    ret = gss_verify_mic(minor_status,
-                        context_handle->negotiated_ctx_id,
-                        mech_buf,
-                        &mic_buf,
-                        NULL);
+    major_status = gss_import_name(minor_status, &namebuf,
+                                  GSS_C_NT_HOSTBASED_SERVICE, namep);
 
-    if (ret != GSS_S_COMPLETE)
-       ret = GSS_S_DEFECTIVE_TOKEN;
+    free(str);
 
-    return ret;
+    return major_status;
 }
 
+/*
+ * Determine whether the mech in mechType can be negotiated. If the
+ * mech is NegoEx, make NegoEx mechanisms available for negotiation.
+ */
+
 static OM_uint32
-select_mech(OM_uint32 *minor_status, MechType *mechType, int verify_p,
-           gss_OID *mech_p)
+select_mech(OM_uint32 *minor_status,
+           gssspnego_ctx ctx,
+           gss_const_cred_id_t cred,
+           gss_const_OID_set supported_mechs,
+           MechType *mechType,
+           int verify_p, /* set on non-optimistic tokens */
+           gss_const_OID *advertised_mech_p)
 {
     char mechbuf[64];
     size_t mech_len;
     gss_OID_desc oid;
-    gss_OID oidp;
-    gss_OID_set mechs;
-    size_t i;
+    gss_OID selected_mech = GSS_C_NO_OID;
     OM_uint32 ret, junk;
+    int negoex_proposed = FALSE, negoex_selected = FALSE;
+    int includeMSCompatOID = FALSE;
+    size_t i;
+
+    *minor_status = 0;
+    *advertised_mech_p = GSS_C_NO_OID; /* deals with broken MS OID */
+
+    ctx->selected_mech_type = GSS_C_NO_OID;
 
     ret = der_put_oid ((unsigned char *)mechbuf + sizeof(mechbuf) - 1,
                       sizeof(mechbuf),
                       mechType,
                       &mech_len);
-    if (ret) {
+    if (ret)
        return GSS_S_DEFECTIVE_TOKEN;
-    }
 
-    oid.length   = mech_len;
+    oid.length   = (OM_uint32)mech_len;
     oid.elements = mechbuf + sizeof(mechbuf) - mech_len;
 
-    if (gss_oid_equal(&oid, GSS_SPNEGO_MECHANISM)) {
-       return GSS_S_BAD_MECH;
-    }
-
-    *minor_status = 0;
+    if (gss_oid_equal(&oid, GSS_NEGOEX_MECHANISM))
+       negoex_proposed = TRUE;
+    else if (gss_oid_equal(&oid, &_gss_spnego_mskrb_mechanism_oid_desc))
+       includeMSCompatOID = TRUE;
 
-    /* Translate broken MS Kebreros OID */
-    if (gss_oid_equal(&oid, &_gss_spnego_mskrb_mechanism_oid_desc))
-           oidp = &_gss_spnego_krb5_mechanism_oid_desc;
-    else
-           oidp = &oid;
+    for (i = 0; i < supported_mechs->count; i++) {
+       gss_OID iter = &supported_mechs->elements[i];
+       auth_scheme scheme;
+       int is_negoex_mech = /* mechanism is negotiable under NegoEx */
+           gssspi_query_mechanism_info(&junk, iter, scheme) == GSS_S_COMPLETE;
 
+       if (is_negoex_mech && negoex_proposed) {
+           ret = _gss_negoex_add_auth_mech(minor_status, ctx, iter, scheme);
+           if (ret != GSS_S_COMPLETE)
+               break;
 
-    ret = gss_indicate_mechs(&junk, &mechs);
-    if (ret)
-           return (ret);
+           negoex_selected = TRUE;
+       }
 
-    for (i = 0; i < mechs->count; i++)
-           if (gss_oid_equal(&mechs->elements[i], oidp))
-                   break;
+       if (gss_oid_equal(includeMSCompatOID ? GSS_KRB5_MECHANISM : &oid, iter)) {
+           ret = _gss_intern_oid(minor_status, iter, &selected_mech);
+           if (ret != GSS_S_COMPLETE)
+               return ret;
 
-    if (i == mechs->count) {
-           gss_release_oid_set(&junk, &mechs);
-           return GSS_S_BAD_MECH;
+           break;
+       }
     }
-    gss_release_oid_set(&junk, &mechs);
 
-    ret = gss_duplicate_oid(minor_status,
-                           &oid, /* possibly this should be oidp */
-                           mech_p);
+    /* always prefer NegoEx if a mechanism supported both */
+    if (negoex_selected)
+       selected_mech = GSS_NEGOEX_MECHANISM;
+    if (selected_mech == GSS_C_NO_OID)
+       ret = GSS_S_BAD_MECH;
+    if (ret != GSS_S_COMPLETE)
+       return ret;
+
+    heim_assert(!gss_oid_equal(selected_mech, GSS_SPNEGO_MECHANISM),
+               "SPNEGO should not be able to negotiate itself");
 
     if (verify_p) {
        gss_name_t name = GSS_C_NO_NAME;
-       gss_buffer_desc namebuf;
-       char *str = NULL, *host, hostname[MAXHOSTNAMELEN];
-
-       host = secure_getenv("GSSAPI_SPNEGO_NAME");
-       if (host == NULL) {
-           int rv;
-           if (gethostname(hostname, sizeof(hostname)) != 0) {
-               *minor_status = errno;
-               return GSS_S_FAILURE;
-           }
-           rv = asprintf(&str, "host@%s", hostname);
-           if (rv < 0 || str == NULL) {
-               *minor_status = ENOMEM;
-               return GSS_S_FAILURE;
-           }
-           host = str;
-       }
 
-       namebuf.length = strlen(host);
-       namebuf.value = host;
+       /*
+        * If we do not have a credential, acquire a default name as a hint
+        * to acceptor_approved() so it can attempt to acquire a default
+        * credential.
+        */
+       if (cred == GSS_C_NO_CREDENTIAL) {
+           ret = default_acceptor_name(minor_status, &name);
+           if (ret != GSS_S_COMPLETE)
+               return ret;
+       }
 
-       ret = gss_import_name(minor_status, &namebuf,
-                             GSS_C_NT_HOSTBASED_SERVICE, &name);
-       if (str)
-           free(str);
-       if (ret != GSS_S_COMPLETE)
-           return ret;
+       ret = acceptor_approved(minor_status, ctx, name, cred, selected_mech);
 
-       ret = acceptor_approved(GSS_C_NO_CREDENTIAL, name, *mech_p);
        gss_release_name(&junk, &name);
     }
 
+    /* Stash optimistic mech for use by _gss_spnego_require_mechlist_mic() */
+    if (ret == GSS_S_COMPLETE && !verify_p)
+       ret = gss_duplicate_oid(minor_status, &oid, &ctx->preferred_mech_type);
+
+    if (ret == GSS_S_COMPLETE) {
+       *minor_status = 0;
+
+       *advertised_mech_p = ctx->selected_mech_type = selected_mech;
+
+       /* if the initiator used the broken MS OID, send that instead */
+       if (includeMSCompatOID && gss_oid_equal(selected_mech, GSS_KRB5_MECHANISM))
+           *advertised_mech_p = &_gss_spnego_mskrb_mechanism_oid_desc;
+    }
+
     return ret;
 }
 
@@ -417,25 +470,19 @@ static OM_uint32
 acceptor_complete(OM_uint32 * minor_status,
                  gssspnego_ctx ctx,
                  int *get_mic,
-                 gss_buffer_t mech_buf,
                  gss_buffer_t mech_input_token,
                  gss_buffer_t mech_output_token,
                  heim_octet_string *mic,
                  gss_buffer_t output_token)
 {
+    gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
     OM_uint32 ret;
-    int require_mic, verify_mic;
-
-    ret = _gss_spnego_require_mechlist_mic(minor_status, ctx, &require_mic);
-    if (ret)
-       return ret;
-
-    ctx->require_mic = require_mic;
+    int verify_mic;
 
-    if (mic != NULL)
-       require_mic = 1;
+    ctx->flags.require_mic = 1;
+    ctx->flags.safe_omit = _gss_spnego_safe_omit_mechlist_mic(ctx);
 
-    if (ctx->open && require_mic) {
+    if (ctx->flags.open) {
        if (mech_input_token == GSS_C_NO_BUFFER) { /* Even/One */
            verify_mic = 1;
            *get_mic = 0;
@@ -447,29 +494,20 @@ acceptor_complete(OM_uint32 * minor_status,
            *get_mic = 1;
        }
 
-       if (verify_mic || *get_mic) {
-           int eret;
-           size_t buf_len = 0;
-
-           ASN1_MALLOC_ENCODE(MechTypeList,
-                              mech_buf->value, mech_buf->length,
-                              &ctx->initiator_mech_types, &buf_len, eret);
-           if (eret) {
-               *minor_status = eret;
-               return GSS_S_FAILURE;
-           }
-           heim_assert(mech_buf->length == buf_len, "Internal ASN.1 error");
-           UNREACHABLE(return GSS_S_FAILURE);
-       }
-
-       if (verify_mic) {
-           ret = verify_mechlist_mic(minor_status, ctx, mech_buf, mic);
+       if (verify_mic && mic == NULL && ctx->flags.safe_omit) {
+           /*
+            * Peer is old and didn't send a mic while we expected
+            * one, but since it safe to omit, let do that
+            */
+       } else if (verify_mic) {
+           ret = _gss_spnego_verify_mechtypes_mic(minor_status, ctx, mic);
            if (ret) {
                if (*get_mic)
-                   send_reject (minor_status, output_token);
+                   send_reject(minor_status, output_token);
+               if (buf.value)
+                   free(buf.value);
                return ret;
            }
-           ctx->verified_mic = 1;
        }
     } else
        *get_mic = 0;
@@ -477,6 +515,57 @@ acceptor_complete(OM_uint32 * minor_status,
     return GSS_S_COMPLETE;
 }
 
+/*
+ * Call gss_accept_sec_context() via mechglue or NegoEx, depending on
+ * whether mech_oid is NegoEx.
+ */
+
+static OM_uint32
+mech_accept(OM_uint32 *minor_status,
+           gssspnego_ctx ctx,
+           gss_const_cred_id_t acceptor_cred_handle,
+           gss_const_buffer_t input_token_buffer,
+           const gss_channel_bindings_t input_chan_bindings,
+           gss_buffer_t output_token,
+           gss_cred_id_t *delegated_cred_handle)
+{
+    OM_uint32 ret, junk;
+
+    heim_assert(ctx->selected_mech_type != GSS_C_NO_OID,
+               "mech_accept called with no selected mech");
+
+    if (gss_oid_equal(ctx->selected_mech_type, GSS_NEGOEX_MECHANISM)) {
+       ret = _gss_negoex_accept(minor_status,
+                                ctx,
+                                (gss_cred_id_t)acceptor_cred_handle,
+                                input_token_buffer,
+                                input_chan_bindings,
+                                output_token,
+                                delegated_cred_handle);
+    } else {
+       if (ctx->mech_src_name != GSS_C_NO_NAME)
+           gss_release_name(&junk, &ctx->mech_src_name);
+
+       ret = gss_accept_sec_context(minor_status,
+                                    &ctx->negotiated_ctx_id,
+                                    acceptor_cred_handle,
+                                    (gss_buffer_t)input_token_buffer,
+                                    input_chan_bindings,
+                                    &ctx->mech_src_name,
+                                    &ctx->negotiated_mech_type,
+                                    output_token,
+                                    &ctx->mech_flags,
+                                    &ctx->mech_time_rec,
+                                    delegated_cred_handle);
+       if (GSS_ERROR(ret))
+           gss_mg_collect_error(ctx->negotiated_mech_type, ret, *minor_status);
+       else if (ctx->negotiated_mech_type != GSS_C_NO_OID &&
+           !gss_oid_equal(ctx->negotiated_mech_type, ctx->selected_mech_type))
+           _gss_mg_log(1, "spnego client didn't send the mech they said they would");
+    }
+
+    return ret;
+}
 
 static OM_uint32 GSSAPI_CALLCONV
 acceptor_start
@@ -495,23 +584,24 @@ acceptor_start
 {
     OM_uint32 ret, junk;
     NegotiationToken nt;
-    size_t nt_len;
+    gss_OID_set supported_mechs = GSS_C_NO_OID_SET;
+    size_t size;
     NegTokenInit *ni;
     gss_buffer_desc data;
     gss_buffer_t mech_input_token = GSS_C_NO_BUFFER;
     gss_buffer_desc mech_output_token;
-    gss_buffer_desc mech_buf;
-    gss_OID preferred_mech_type = GSS_C_NO_OID;
     gssspnego_ctx ctx;
     int get_mic = 0;
     int first_ok = 0;
+    gss_const_OID advertised_mech = GSS_C_NO_OID;
+
+    memset(&nt, 0, sizeof(nt));
 
     mech_output_token.value = NULL;
     mech_output_token.length = 0;
-    mech_buf.value = NULL;
 
     if (input_token_buffer->length == 0)
-       return send_supported_mechs (minor_status,
+       return send_supported_mechs (minor_status, NULL,
                                     acceptor_cred_handle, output_token);
 
     ret = _gss_spnego_alloc_sec_context(minor_status, context_handle);
@@ -520,6 +610,8 @@ acceptor_start
 
     ctx = (gssspnego_ctx)*context_handle;
 
+    HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex);
+
     /*
      * The GSS-API encapsulation is only present on the initial
      * context token (negTokenInit).
@@ -528,36 +620,58 @@ acceptor_start
                                 GSS_SPNEGO_MECHANISM,
                                 &data);
     if (ret)
-       return ret;
+       goto out;
 
-    ret = decode_NegotiationToken(data.value, data.length, &nt, &nt_len);
+    ret = decode_NegotiationToken(data.value, data.length, &nt, &size);
     gss_release_buffer(minor_status, &data);
     if (ret) {
        *minor_status = ret;
-       return GSS_S_DEFECTIVE_TOKEN;
+       ret = GSS_S_DEFECTIVE_TOKEN;
+       goto out;
     }
     if (nt.element != choice_NegotiationToken_negTokenInit) {
        *minor_status = 0;
-       return GSS_S_DEFECTIVE_TOKEN;
+       ret = GSS_S_DEFECTIVE_TOKEN;
+       goto out;
     }
     ni = &nt.u.negTokenInit;
 
     if (ni->mechTypes.len < 1) {
        free_NegotiationToken(&nt);
        *minor_status = 0;
-       return GSS_S_DEFECTIVE_TOKEN;
+       ret = GSS_S_DEFECTIVE_TOKEN;
+       goto out;
     }
 
-    HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex);
+    _gss_spnego_log_mechTypes(&ni->mechTypes);
 
-    ret = copy_MechTypeList(&ni->mechTypes, &ctx->initiator_mech_types);
-    if (ret) {
-       HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
-       free_NegotiationToken(&nt);
-       *minor_status = ret;
-       return GSS_S_FAILURE;
+    {
+       MechTypeList mt;
+       int kret;
+
+       mt.len = ni->mechTypes.len;
+       mt.val = ni->mechTypes.val;
+
+       ASN1_MALLOC_ENCODE(MechTypeList,
+                          ctx->NegTokenInit_mech_types.value,
+                          ctx->NegTokenInit_mech_types.length,
+                          &mt, &size, kret);
+       if (kret) {
+           *minor_status = kret;
+           ret = GSS_S_FAILURE;
+           goto out;
+       }
     }
 
+    if (acceptor_cred_handle != GSS_C_NO_CREDENTIAL)
+       ret = _gss_spnego_inquire_cred_mechs(minor_status,
+                                            acceptor_cred_handle,
+                                            &supported_mechs);
+    else
+       ret = _gss_spnego_indicate_mechs(minor_status, &supported_mechs);
+    if (ret != GSS_S_COMPLETE)
+       goto out;
+
     /*
      * First we try the opportunistic token if we have support for it,
      * don't try to verify we have credential for the token,
@@ -566,41 +680,39 @@ acceptor_start
      */
 
     ret = select_mech(minor_status,
+                     ctx,
+                     acceptor_cred_handle,
+                     supported_mechs,
                      &ni->mechTypes.val[0],
-                     0,
-                     &preferred_mech_type);
+                     0, /* optimistic token */
+                     &advertised_mech);
 
-    if (ret == 0 && ni->mechToken != NULL) {
+    if (ret == GSS_S_COMPLETE && ni->mechToken != NULL) {
        gss_buffer_desc ibuf;
 
        ibuf.length = ni->mechToken->length;
        ibuf.value = ni->mechToken->data;
        mech_input_token = &ibuf;
 
-       if (ctx->mech_src_name != GSS_C_NO_NAME)
-           gss_release_name(&junk, &ctx->mech_src_name);
-
-       ret = gss_accept_sec_context(minor_status,
-                                    &ctx->negotiated_ctx_id,
-                                    acceptor_cred_handle,
-                                    mech_input_token,
-                                    input_chan_bindings,
-                                    &ctx->mech_src_name,
-                                    &ctx->negotiated_mech_type,
-                                    &mech_output_token,
-                                    &ctx->mech_flags,
-                                    &ctx->mech_time_rec,
-                                    delegated_cred_handle);
+       _gss_spnego_log_mech("acceptor selected opportunistic mech", ctx->selected_mech_type);
 
+       ret = mech_accept(&junk,
+                         ctx,
+                         acceptor_cred_handle,
+                         mech_input_token,
+                         input_chan_bindings,
+                         &mech_output_token,
+                         delegated_cred_handle);
        if (ret == GSS_S_COMPLETE || ret == GSS_S_CONTINUE_NEEDED) {
-           ctx->preferred_mech_type = preferred_mech_type;
-           if (ret == GSS_S_COMPLETE)
-               ctx->open = 1;
+           first_ok = 1;
+       } else {
+           ctx->selected_mech_type = GSS_C_NO_OID;
+       }
 
+       if (ret == GSS_S_COMPLETE) {
            ret = acceptor_complete(minor_status,
                                    ctx,
                                    &get_mic,
-                                   &mech_buf,
                                    mech_input_token,
                                    &mech_output_token,
                                    ni->mechListMIC,
@@ -608,10 +720,14 @@ acceptor_start
            if (ret != GSS_S_COMPLETE)
                goto out;
 
-           first_ok = 1;
-       } else {
-           gss_mg_collect_error(preferred_mech_type, ret, *minor_status);
+           ctx->flags.open = 1;
        }
+    } else {
+       *minor_status = 0;
+       HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
+       return gss_mg_set_error_string(GSS_C_NO_OID, GSS_S_NO_CONTEXT,
+                                      *minor_status,
+                                      "SPNEGO acceptor didn't find a prefered mechanism");
     }
 
     /*
@@ -621,24 +737,25 @@ acceptor_start
     if (!first_ok && ni->mechToken != NULL) {
        size_t j;
 
-       preferred_mech_type = GSS_C_NO_OID;
-
        /* Call glue layer to find first mech we support */
        for (j = 1; j < ni->mechTypes.len; ++j) {
-           ret = select_mech(minor_status,
+           ret = select_mech(&junk,
+                             ctx,
+                             acceptor_cred_handle,
+                             supported_mechs,
                              &ni->mechTypes.val[j],
-                             1,
-                             &preferred_mech_type);
-           if (ret == 0)
+                             1, /* not optimistic token */
+                             &advertised_mech);
+           if (ret == GSS_S_COMPLETE) {
+               _gss_spnego_log_mech("acceptor selected non-opportunistic mech", ctx->selected_mech_type);
                break;
+           }
        }
-       if (preferred_mech_type == GSS_C_NO_OID) {
-           HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
-           free_NegotiationToken(&nt);
-           return ret;
+       if (ctx->selected_mech_type == GSS_C_NO_OID) {
+           heim_assert(ret != GSS_S_COMPLETE, "no oid and no error code?");
+           *minor_status = junk;
+           goto out;
        }
-
-       ctx->preferred_mech_type = preferred_mech_type;
     }
 
     /*
@@ -647,20 +764,18 @@ acceptor_start
 
     ret = send_accept (minor_status,
                       ctx,
+                      first_ok,
                       &mech_output_token,
-                      1,
-                      get_mic ? &mech_buf : NULL,
+                      advertised_mech,
+                      get_mic ? &ctx->NegTokenInit_mech_types : NULL,
                       output_token);
     if (ret)
        goto out;
 
 out:
+    gss_release_oid_set(&junk, &supported_mechs);
     if (mech_output_token.value != NULL)
        gss_release_buffer(&junk, &mech_output_token);
-    if (mech_buf.value != NULL) {
-       free(mech_buf.value);
-       mech_buf.value = NULL;
-    }
     free_NegotiationToken(&nt);
 
 
@@ -705,18 +820,15 @@ acceptor_continue
            gss_cred_id_t *delegated_cred_handle
           )
 {
-    OM_uint32 ret, ret2, minor;
+    OM_uint32 ret, ret2, minor, junk;
     NegotiationToken nt;
     size_t nt_len;
     NegTokenResp *na;
     unsigned int negResult = accept_incomplete;
     gss_buffer_t mech_input_token = GSS_C_NO_BUFFER;
     gss_buffer_t mech_output_token = GSS_C_NO_BUFFER;
-    gss_buffer_desc mech_buf;
     gssspnego_ctx ctx;
 
-    mech_buf.value = NULL;
-
     ctx = (gssspnego_ctx)*context_handle;
 
     /*
@@ -745,9 +857,8 @@ acceptor_continue
 
     {
        gss_buffer_desc ibuf, obuf;
-       int require_mic, get_mic = 0;
+       int get_mic = 0;
        int require_response;
-       heim_octet_string *mic;
 
        if (na->responseToken != NULL) {
            ibuf.length = na->responseToken->length;
@@ -760,53 +871,31 @@ acceptor_continue
 
        if (mech_input_token != GSS_C_NO_BUFFER) {
 
-           if (ctx->mech_src_name != GSS_C_NO_NAME)
-               gss_release_name(&minor, &ctx->mech_src_name);
-
-           ret = gss_accept_sec_context(&minor,
-                                        &ctx->negotiated_ctx_id,
-                                        acceptor_cred_handle,
-                                        mech_input_token,
-                                        input_chan_bindings,
-                                        &ctx->mech_src_name,
-                                        &ctx->negotiated_mech_type,
-                                        &obuf,
-                                        &ctx->mech_flags,
-                                        &ctx->mech_time_rec,
-                                        delegated_cred_handle);
-
+           ret = mech_accept(minor_status,
+                             ctx,
+                             acceptor_cred_handle,
+                             mech_input_token,
+                             input_chan_bindings,
+                             &obuf,
+                             delegated_cred_handle);
            if (ret == GSS_S_COMPLETE || ret == GSS_S_CONTINUE_NEEDED) {
                mech_output_token = &obuf;
            }
            if (ret != GSS_S_COMPLETE && ret != GSS_S_CONTINUE_NEEDED) {
                free_NegotiationToken(&nt);
-               gss_mg_collect_error(ctx->negotiated_mech_type, ret, minor);
-               send_reject (minor_status, output_token);
+               send_reject(&junk, output_token);
                HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
                return ret;
            }
            if (ret == GSS_S_COMPLETE)
-               ctx->open = 1;
+               ctx->flags.open = 1;
        } else
            ret = GSS_S_COMPLETE;
 
-       ret2 = _gss_spnego_require_mechlist_mic(minor_status,
-                                               ctx,
-                                               &require_mic);
-       if (ret2)
-           goto out;
-
-       ctx->require_mic = require_mic;
-
-       mic = na->mechListMIC;
-       if (mic != NULL)
-           require_mic = 1;
-
        if (ret == GSS_S_COMPLETE)
            ret = acceptor_complete(minor_status,
                                    ctx,
                                    &get_mic,
-                                   &mech_buf,
                                    mech_input_token,
                                    mech_output_token,
                                    na->mechListMIC,
@@ -823,26 +912,26 @@ acceptor_continue
         */
        if ((mech_output_token != GSS_C_NO_BUFFER &&
             mech_output_token->length != 0)
-           || (ctx->open && negResult == accept_incomplete)
+           || (ctx->flags.open && negResult == accept_incomplete)
            || require_response
            || get_mic) {
            ret2 = send_accept (minor_status,
                                ctx,
+                               0, /* ignored on subsequent tokens */
                                mech_output_token,
-                               0,
-                               get_mic ? &mech_buf : NULL,
+                               GSS_C_NO_OID,
+                               get_mic ? &ctx->NegTokenInit_mech_types : NULL,
                                output_token);
            if (ret2)
                goto out;
-       }
+       } else
+           ret2 = GSS_S_COMPLETE;
 
      out:
        if (ret2 != GSS_S_COMPLETE)
            ret = ret2;
        if (mech_output_token != NULL)
            gss_release_buffer(&minor, mech_output_token);
-       if (mech_buf.value != NULL)
-           free(mech_buf.value);
        free_NegotiationToken(&nt);
     }
 
index 0dc6425c84c6a8396dc78322491ffba74bc42bdc..576e2745980e1e7923163d801f23cf5102e6709f 100644 (file)
@@ -2,6 +2,8 @@
  * Copyright (c) 2004, PADL Software Pty Ltd.
  * All rights reserved.
  *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
@@ -43,9 +45,6 @@
 gss_OID_desc _gss_spnego_mskrb_mechanism_oid_desc =
     {9, rk_UNCONST("\x2a\x86\x48\x82\xf7\x12\x01\x02\x02")};
 
-gss_OID_desc _gss_spnego_krb5_mechanism_oid_desc =
-    {9, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02")};
-
 /*
  * Allocate a SPNEGO context handle
  */
@@ -61,28 +60,33 @@ _gss_spnego_alloc_sec_context (OM_uint32 * minor_status,
        return GSS_S_FAILURE;
     }
 
-    ctx->initiator_mech_types.len = 0;
-    ctx->initiator_mech_types.val = NULL;
+    ctx->NegTokenInit_mech_types.value = NULL;
+    ctx->NegTokenInit_mech_types.length = 0;
+
     ctx->preferred_mech_type = GSS_C_NO_OID;
+    ctx->selected_mech_type = GSS_C_NO_OID;
     ctx->negotiated_mech_type = GSS_C_NO_OID;
+
     ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT;
 
-    /*
-     * Cache these so we can return them before returning
-     * GSS_S_COMPLETE, even if the mechanism has itself
-     * completed earlier
-     */
     ctx->mech_flags = 0;
     ctx->mech_time_rec = 0;
     ctx->mech_src_name = GSS_C_NO_NAME;
 
-    ctx->open = 0;
-    ctx->local = 0;
-    ctx->require_mic = 0;
-    ctx->verified_mic = 0;
+    ctx->flags.open = 0;
+    ctx->flags.local = 0;
+    ctx->flags.peer_require_mic = 0;
+    ctx->flags.require_mic = 0;
+    ctx->flags.verified_mic = 0;
 
     HEIMDAL_MUTEX_init(&ctx->ctx_id_mutex);
 
+    ctx->negoex_step = 0;
+    ctx->negoex_transcript = NULL;
+    ctx->negoex_seqnum = 0;
+    HEIM_TAILQ_INIT(&ctx->negoex_mechs);
+    memset(ctx->negoex_conv_id, 0, GUID_LENGTH);
+
     *context_handle = (gss_ctx_id_t)ctx;
 
     return GSS_S_COMPLETE;
@@ -119,11 +123,12 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_internal_delete_sec_context
        return GSS_S_NO_CONTEXT;
     }
 
-    if (ctx->initiator_mech_types.val != NULL)
-       free_MechTypeList(&ctx->initiator_mech_types);
+    if (ctx->NegTokenInit_mech_types.value)
+       free(ctx->NegTokenInit_mech_types.value);
 
-    gss_release_oid(&minor, &ctx->preferred_mech_type);
+    ctx->preferred_mech_type = GSS_C_NO_OID;
     ctx->negotiated_mech_type = GSS_C_NO_OID;
+    ctx->selected_mech_type = GSS_C_NO_OID;
 
     gss_release_name(&minor, &ctx->target_name);
     gss_release_name(&minor, &ctx->mech_src_name);
@@ -137,6 +142,8 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_internal_delete_sec_context
        ret = GSS_S_COMPLETE;
     }
 
+    _gss_negoex_release_context(ctx);
+
     HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
     HEIMDAL_MUTEX_destroy(&ctx->ctx_id_mutex);
 
@@ -146,93 +153,121 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_internal_delete_sec_context
 }
 
 /*
- * For compatability with the Windows SPNEGO implementation, the
- * default is to ignore the mechListMIC unless CFX is used and
- * a non-preferred mechanism was negotiated
+ * Returns TRUE if it is safe to omit mechListMIC because the preferred
+ * mechanism was selected, and the peer did not require it.
  */
 
-OM_uint32 GSSAPI_CALLCONV
-_gss_spnego_require_mechlist_mic(OM_uint32 *minor_status,
-                                gssspnego_ctx ctx,
-                                int *require_mic)
+int
+_gss_spnego_safe_omit_mechlist_mic(gssspnego_ctx ctx)
 {
-    gss_buffer_set_t buffer_set = GSS_C_NO_BUFFER_SET;
-    OM_uint32 minor;
+    int safe_omit = 0;
 
-    *minor_status = 0;
-    *require_mic = 0;
+    if (ctx->flags.peer_require_mic == FALSE)
+       safe_omit = gss_oid_equal(ctx->selected_mech_type, ctx->preferred_mech_type);
 
-    if (ctx == NULL) {
-       return GSS_S_COMPLETE;
-    }
+    if (safe_omit)
+       _gss_mg_log(10, "spnego: safe to omit mechListMIC, as preferred mechanism selected");
+    else
+       _gss_mg_log(10, "spnego: mechListMIC required");
 
-    if (ctx->require_mic) {
-       /* Acceptor requested it: mandatory to honour */
-       *require_mic = 1;
-       return GSS_S_COMPLETE;
-    }
+    return safe_omit;
+}
 
-    /*
-     * Check whether peer indicated implicit support for updated SPNEGO
-     * (eg. in the Kerberos case by using CFX)
-     */
-    if (gss_inquire_sec_context_by_oid(&minor, ctx->negotiated_ctx_id,
-                                      GSS_C_PEER_HAS_UPDATED_SPNEGO,
-                                      &buffer_set) == GSS_S_COMPLETE) {
-       *require_mic = 1;
-       gss_release_buffer_set(&minor, &buffer_set);
+
+static OM_uint32
+add_mech_type(OM_uint32 *minor_status,
+             gss_OID mech_type,
+             MechTypeList *mechtypelist)
+{
+    MechType mech;
+    int ret;
+
+    heim_assert(!gss_oid_equal(mech_type, GSS_SPNEGO_MECHANISM),
+               "SPNEGO mechanism not filtered");
+
+    ret = der_get_oid(mech_type->elements, mech_type->length, &mech, NULL);
+    if (ret == 0) {
+       ret = add_MechTypeList(mechtypelist, &mech);
+       free_MechType(&mech);
     }
 
-    /* Safe-to-omit MIC rules follow */
-    if (*require_mic) {
-       if (gss_oid_equal(ctx->negotiated_mech_type, ctx->preferred_mech_type)) {
-           *require_mic = 0;
-       } else if (gss_oid_equal(ctx->negotiated_mech_type, &_gss_spnego_krb5_mechanism_oid_desc) &&
-                  gss_oid_equal(ctx->preferred_mech_type, &_gss_spnego_mskrb_mechanism_oid_desc)) {
-           *require_mic = 0;
-       }
+    if (ret) {
+       *minor_status = ret;
+       return GSS_S_FAILURE;
     }
 
     return GSS_S_COMPLETE;
 }
 
 static int
-add_mech_type(gss_OID mech_type,
-             int includeMSCompatOID,
-             MechTypeList *mechtypelist)
+add_mech_if_approved(OM_uint32 *minor_status,
+                    gss_const_name_t target_name,
+                    OM_uint32 (*func)(OM_uint32 *, void *, gss_const_name_t, gss_const_cred_id_t, gss_OID),
+                    void *userptr,
+                    int includeMSCompatOID,
+                    gss_const_cred_id_t cred_handle,
+                    MechTypeList *mechtypelist,
+                    gss_OID mech_oid,
+                    gss_OID *first_mech,
+                    OM_uint32 *first_major,
+                    OM_uint32 *first_minor,
+                    int *added_negoex)
 {
-    MechType mech;
-    int ret;
+    OM_uint32 major, minor;
 
-    if (gss_oid_equal(mech_type, GSS_SPNEGO_MECHANISM))
-       return 0;
+    /*
+     * Unapproved mechanisms are ignored, but we capture their result
+     * code in case we didn't find any other mechanisms, in which case
+     * we return that to the caller of _gss_spnego_indicate_mechtypelist().
+     */
+    major = (*func)(&minor, userptr, target_name, cred_handle, mech_oid);
+    if (major != GSS_S_COMPLETE) {
+       if (*first_mech == GSS_C_NO_OID) {
+           *first_major = major;
+           *first_minor = minor;
+       }
+       return GSS_S_COMPLETE;
+    }
 
-    if (includeMSCompatOID &&
-       gss_oid_equal(mech_type, &_gss_spnego_krb5_mechanism_oid_desc)) {
-       ret = der_get_oid(_gss_spnego_mskrb_mechanism_oid_desc.elements,
-                         _gss_spnego_mskrb_mechanism_oid_desc.length,
-                         &mech,
-                         NULL);
-       if (ret)
-           return ret;
-       ret = add_MechTypeList(mechtypelist, &mech);
-       free_MechType(&mech);
-       if (ret)
-           return ret;
+    if (_gss_negoex_mech_p(mech_oid)) {
+       if (*added_negoex == FALSE) {
+           major = add_mech_type(minor_status, GSS_NEGOEX_MECHANISM, mechtypelist);
+           if (major != GSS_S_COMPLETE)
+               return major;
+           *added_negoex = TRUE;
+       }
+
+       if (*first_mech == GSS_C_NO_OID)
+           *first_mech = GSS_NEGOEX_MECHANISM;
+
+       /* if NegoEx-only mech, we are done */
+       if (!_gss_negoex_and_spnego_mech_p(mech_oid))
+           return GSS_S_COMPLETE;
     }
-    ret = der_get_oid(mech_type->elements, mech_type->length, &mech, NULL);
-    if (ret)
-       return ret;
-    ret = add_MechTypeList(mechtypelist, &mech);
-    free_MechType(&mech);
-    return ret;
-}
 
+    if (includeMSCompatOID && gss_oid_equal(mech_oid, GSS_KRB5_MECHANISM)) {
+       major = add_mech_type(minor_status,
+                             &_gss_spnego_mskrb_mechanism_oid_desc,
+                             mechtypelist);
+       if (major != GSS_S_COMPLETE)
+           return major;
+    }
+
+    major = add_mech_type(minor_status, mech_oid, mechtypelist);
+    if (major != GSS_S_COMPLETE)
+       return major;
+
+    if (*first_mech == GSS_C_NO_OID)
+       *first_mech = mech_oid;
+
+    return GSS_S_COMPLETE;
+}
 
 OM_uint32 GSSAPI_CALLCONV
 _gss_spnego_indicate_mechtypelist (OM_uint32 *minor_status,
-                                  gss_name_t target_name,
-                                  OM_uint32 (*func)(gss_const_cred_id_t, gss_name_t, gss_OID),
+                                  gss_const_name_t target_name,
+                                  OM_uint32 (*func)(OM_uint32 *, void *, gss_const_name_t, gss_const_cred_id_t, gss_OID),
+                                  void *userptr,
                                   int includeMSCompatOID,
                                   gss_const_cred_id_t cred_handle,
                                   MechTypeList *mechtypelist,
@@ -240,94 +275,297 @@ _gss_spnego_indicate_mechtypelist (OM_uint32 *minor_status,
 {
     gss_OID_set supported_mechs = GSS_C_NO_OID_SET;
     gss_OID first_mech = GSS_C_NO_OID;
-    OM_uint32 ret;
+    OM_uint32 ret, minor;
+    OM_uint32 first_major = GSS_S_BAD_MECH, first_minor = 0;
     size_t i;
+    int present = FALSE;
+    int added_negoex = FALSE;
 
     mechtypelist->len = 0;
     mechtypelist->val = NULL;
 
-    if (cred_handle) {
-       ret = gss_inquire_cred(minor_status,
-                              cred_handle,
-                              NULL,
-                              NULL,
-                              NULL,
-                              &supported_mechs);
-    } else {
-       ret = gss_indicate_mechs(minor_status, &supported_mechs);
-    }
-
-    if (ret != GSS_S_COMPLETE) {
+    if (cred_handle != GSS_C_NO_CREDENTIAL)
+       ret = _gss_spnego_inquire_cred_mechs(minor_status,
+                                            cred_handle, &supported_mechs);
+    else
+       ret = _gss_spnego_indicate_mechs(minor_status, &supported_mechs);
+    if (ret != GSS_S_COMPLETE)
        return ret;
-    }
 
-    if (supported_mechs->count == 0) {
-       *minor_status = ENOENT;
-       gss_release_oid_set(minor_status, &supported_mechs);
-       return GSS_S_FAILURE;
-    }
+    heim_assert(supported_mechs != GSS_C_NO_OID_SET,
+               "NULL mech set returned by SPNEGO inquire/indicate mechs");
+
+    /*
+     * Propose Kerberos mech first if we have Kerberos credentials/supported mechs
+     */
 
-    ret = (*func)(cred_handle, target_name, GSS_KRB5_MECHANISM);
-    if (ret == GSS_S_COMPLETE) {
-       ret = add_mech_type(GSS_KRB5_MECHANISM,
-                           includeMSCompatOID,
-                           mechtypelist);
-       if (!GSS_ERROR(ret))
-           first_mech = GSS_KRB5_MECHANISM;
+    ret = gss_test_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
+                                 supported_mechs, &present);
+    if (ret == GSS_S_COMPLETE && present) {
+       ret = add_mech_if_approved(minor_status, target_name,
+                                  func, userptr, includeMSCompatOID,
+                                  cred_handle, mechtypelist,
+                                  GSS_KRB5_MECHANISM, &first_mech,
+                                  &first_major, &first_minor,
+                                  &added_negoex);
     }
-    ret = GSS_S_COMPLETE;
+
+    /*
+     * Now let's check all other mechs
+     */
 
     for (i = 0; i < supported_mechs->count; i++) {
-       OM_uint32 subret;
-       if (gss_oid_equal(&supported_mechs->elements[i], GSS_SPNEGO_MECHANISM))
-           continue;
        if (gss_oid_equal(&supported_mechs->elements[i], GSS_KRB5_MECHANISM))
            continue;
 
-       subret = (*func)(cred_handle, target_name, &supported_mechs->elements[i]);
-       if (subret != GSS_S_COMPLETE)
-           continue;
-
-       ret = add_mech_type(&supported_mechs->elements[i],
-                           includeMSCompatOID,
-                           mechtypelist);
-       if (ret != 0) {
-           *minor_status = ret;
-           ret = GSS_S_FAILURE;
-           break;
+       ret = add_mech_if_approved(minor_status, target_name,
+                                  func, userptr, FALSE,
+                                  cred_handle, mechtypelist,
+                                  &supported_mechs->elements[i],
+                                  &first_mech,
+                                  &first_major, &first_minor,
+                                  &added_negoex);
+       if (ret != GSS_S_COMPLETE) {
+           gss_release_oid_set(&minor, &supported_mechs);
+           return ret;
        }
-       if (first_mech == GSS_C_NO_OID)
-           first_mech = &supported_mechs->elements[i];
     }
 
-    if (mechtypelist->len == 0) {
-       gss_release_oid_set(minor_status, &supported_mechs);
+    heim_assert(mechtypelist->len == 0 || first_mech != GSS_C_NO_OID,
+               "mechtypelist non-empty but no mech selected");
+
+    if (first_mech != GSS_C_NO_OID)
+       ret = _gss_intern_oid(minor_status, first_mech, &first_mech);
+    else if (GSS_ERROR(first_major)) {
+       ret = first_major;
+       *minor_status = first_minor;
+    } else
+       ret = GSS_S_BAD_MECH;
+
+    if (preferred_mech != NULL)
+       *preferred_mech = first_mech;
+
+    gss_release_oid_set(&minor, &supported_mechs);
+
+    return ret;
+}
+
+/*
+ *
+ */
+
+OM_uint32
+_gss_spnego_verify_mechtypes_mic(OM_uint32 *minor_status,
+                                gssspnego_ctx ctx,
+                                heim_octet_string *mic)
+{
+    gss_buffer_desc mic_buf;
+    OM_uint32 major_status;
+
+    if (mic == NULL) {
+       *minor_status = 0;
+       return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                      GSS_S_DEFECTIVE_TOKEN, 0,
+                                      "SPNEGO peer failed to send mechListMIC");
+    }
+
+    if (ctx->flags.verified_mic) {
+       /* This doesn't make sense, we've already verified it? */
        *minor_status = 0;
-       return GSS_S_BAD_MECH;
+       return GSS_S_DUPLICATE_TOKEN;
     }
 
-    if (preferred_mech != NULL) {
-       ret = gss_duplicate_oid(minor_status, first_mech, preferred_mech);
-       if (ret != GSS_S_COMPLETE)
-           free_MechTypeList(mechtypelist);
+    mic_buf.length = mic->length;
+    mic_buf.value  = mic->data;
+
+    major_status = gss_verify_mic(minor_status,
+                                 ctx->negotiated_ctx_id,
+                                 &ctx->NegTokenInit_mech_types,
+                                 &mic_buf,
+                                 NULL);
+    if (major_status == GSS_S_COMPLETE) {
+       _gss_spnego_ntlm_reset_crypto(minor_status, ctx, TRUE);
+    } else if (major_status == GSS_S_UNAVAILABLE) {
+       _gss_mg_log(10, "mech doesn't support MIC, allowing anyway");   
+    } else if (major_status) {
+       return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                      GSS_S_DEFECTIVE_TOKEN, *minor_status,
+                                      "SPNEGO peer sent invalid mechListMIC");
     }
-    gss_release_oid_set(minor_status, &supported_mechs);
+    ctx->flags.verified_mic = 1;
 
-    return ret;
+    *minor_status = 0;
+
+    return GSS_S_COMPLETE;
 }
 
+/*
+ * According to [MS-SPNG] 3.3.5.1 the crypto state for NTLM is reset
+ * before the completed context is returned to the application.
+ */
+
 OM_uint32
 _gss_spnego_ntlm_reset_crypto(OM_uint32 *minor_status,
                              gssspnego_ctx ctx,
                              OM_uint32 verify)
 {
-    gss_buffer_desc value;
+    if (gss_oid_equal(ctx->negotiated_mech_type, GSS_NTLM_MECHANISM)) {
+       gss_buffer_desc value;
+
+       value.length = sizeof(verify);
+       value.value = &verify;
+
+       return gss_set_sec_context_option(minor_status,
+                                         &ctx->negotiated_ctx_id,
+                                         GSS_C_NTLM_RESET_CRYPTO,
+                                         &value);
+    }
+
+    return GSS_S_COMPLETE;
+}
+
+void
+_gss_spnego_log_mech(const char *prefix, gss_const_OID oid)
+{
+    gss_buffer_desc oidbuf = GSS_C_EMPTY_BUFFER;
+    OM_uint32 junk;
+    const char *name = NULL;
+
+    if (!_gss_mg_log_level(10))
+       return;
+
+    if (oid == GSS_C_NO_OID ||
+       gss_oid_to_str(&junk, (gss_OID)oid, &oidbuf) != GSS_S_COMPLETE) {
+       _gss_mg_log(10, "spnego: %s (null)", prefix);
+       return;
+    }
+
+    if (gss_oid_equal(oid, GSS_NEGOEX_MECHANISM))
+       name = "negoex"; /* not a real mech */
+    else if (gss_oid_equal(oid, &_gss_spnego_mskrb_mechanism_oid_desc))
+       name = "mskrb";
+    else {
+       gssapi_mech_interface m = __gss_get_mechanism(oid);
+       if (m)
+           name = m->gm_name;
+    }
+
+    _gss_mg_log(10, "spnego: %s %s { %.*s }",
+               prefix,
+               name ? name : "unknown",
+               (int)oidbuf.length, (char *)oidbuf.value);
+    gss_release_buffer(&junk, &oidbuf);
+}
+
+void
+_gss_spnego_log_mechTypes(MechTypeList *mechTypes)
+{
+    size_t i;
+    char mechbuf[64];
+    size_t mech_len;
+    gss_OID_desc oid;
+    int ret;
+
+    if (!_gss_mg_log_level(10))
+       return;
+
+    for (i = 0; i < mechTypes->len; i++) {
+       ret = der_put_oid ((unsigned char *)mechbuf + sizeof(mechbuf) - 1,
+                          sizeof(mechbuf),
+                          &mechTypes->val[i],
+                          &mech_len);
+       if (ret)
+           continue;
+
+       oid.length   = (OM_uint32)mech_len;
+       oid.elements = mechbuf + sizeof(mechbuf) - mech_len;
+
+       _gss_spnego_log_mech("initiator proposed mech", &oid);
+    }
+}
+
+/*
+ * Indicate mechs negotiable by SPNEGO
+ */
+
+OM_uint32
+_gss_spnego_indicate_mechs(OM_uint32 *minor_status,
+                          gss_OID_set *mechs_p)
+{
+    gss_OID_desc oids[3];
+    gss_OID_set_desc except;
+
+    *mechs_p = GSS_C_NO_OID_SET;
 
-    value.length = sizeof(verify);
-    value.value = &verify;
+    oids[0] = *GSS_C_MA_DEPRECATED;
+    oids[1] = *GSS_C_MA_NOT_DFLT_MECH;
+    oids[2] = *GSS_C_MA_MECH_NEGO;
 
-    return gss_set_sec_context_option(minor_status,
-                                     &ctx->negotiated_ctx_id,
-                                     GSS_C_NTLM_RESET_CRYPTO,
-                                     &value);
+    except.count = sizeof(oids) / sizeof(oids[0]);
+    except.elements = oids;
+
+    return gss_indicate_mechs_by_attrs(minor_status,
+                                      GSS_C_NO_OID_SET,
+                                      &except,
+                                      GSS_C_NO_OID_SET,
+                                      mechs_p);
 }
+
+/*
+ * Indicate mechs in cred negotiatble by SPNEGO
+ */
+
+OM_uint32
+_gss_spnego_inquire_cred_mechs(OM_uint32 *minor_status,
+                              gss_const_cred_id_t cred,
+                              gss_OID_set *mechs_p)
+{
+    OM_uint32 ret, junk;
+    gss_OID_set cred_mechs = GSS_C_NO_OID_SET;
+    gss_OID_set mechs = GSS_C_NO_OID_SET;
+    size_t i;
+
+    *mechs_p = GSS_C_NO_OID_SET;
+
+    heim_assert(cred != GSS_C_NO_CREDENTIAL, "Invalid null credential handle");
+
+    ret = gss_inquire_cred(minor_status, cred, NULL, NULL, NULL, &cred_mechs);
+    if (ret != GSS_S_COMPLETE)
+       goto out;
+
+    heim_assert(cred_mechs != GSS_C_NO_OID_SET,
+               "gss_inquire_cred succeeded but returned null OID set");
+
+    ret = _gss_spnego_indicate_mechs(minor_status, &mechs);
+    if (ret != GSS_S_COMPLETE)
+       goto out;
+
+    heim_assert(mechs != GSS_C_NO_OID_SET,
+               "_gss_spnego_indicate_mechs succeeded but returned null OID set");
+
+    ret = gss_create_empty_oid_set(minor_status, mechs_p);
+    if (ret != GSS_S_COMPLETE)
+       goto out;
+
+    for (i = 0; i < cred_mechs->count; i++) {
+       gss_OID cred_mech = &cred_mechs->elements[i];
+       int present = 0;
+
+       gss_test_oid_set_member(&junk, cred_mech, mechs, &present);
+       if (!present)
+           continue;
+
+       ret = gss_add_oid_set_member(minor_status, cred_mech, mechs_p);
+       if (ret != GSS_S_COMPLETE)
+           break;
+    }
+
+out:
+    if (ret != GSS_S_COMPLETE)
+       gss_release_oid_set(&junk, mechs_p);
+    gss_release_oid_set(&junk, &cred_mechs);
+    gss_release_oid_set(&junk, &mechs);
+
+    return ret;
+}
+
index e50a81badb9b1fefa36d978011dfe6da4d2a5809..88f37adcd22232e0266895a82bb199b38fe660c0 100644 (file)
 
 #include "spnego_locl.h"
 
-static OM_uint32
-spnego_supported_mechs(OM_uint32 *minor_status, gss_OID_set *mechs)
-{
-    OM_uint32 ret, junk;
-    gss_OID_set m;
-    size_t i;
-
-    ret = gss_indicate_mechs(minor_status, &m);
-    if (ret != GSS_S_COMPLETE)
-       return ret;
-
-    ret = gss_create_empty_oid_set(minor_status, mechs);
-    if (ret != GSS_S_COMPLETE) {
-       gss_release_oid_set(&junk, &m);
-       return ret;
-    }
-
-    for (i = 0; i < m->count; i++) {
-       if (gss_oid_equal(&m->elements[i], GSS_SPNEGO_MECHANISM))
-           continue;
-
-       ret = gss_add_oid_set_member(minor_status, &m->elements[i], mechs);
-       if (ret) {
-           gss_release_oid_set(&junk, &m);
-           gss_release_oid_set(&junk, mechs);
-           return ret;
-       }
-    }
-    gss_release_oid_set(&junk, &m);
-    return ret;
-}
-
-
-
 OM_uint32 GSSAPI_CALLCONV _gss_spnego_process_context_token
            (OM_uint32 *minor_status,
             gss_const_ctx_id_t context_handle,
@@ -450,7 +416,7 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_import_sec_context (
        return ret;
     }
 
-    ctx->open = 1;
+    ctx->flags.open = 1;
     /* don't bother filling in the rest of the fields */
 
     HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
@@ -472,7 +438,7 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_inquire_names_for_mech (
 
     *name_types = NULL;
 
-    ret = spnego_supported_mechs(minor_status, &mechs);
+    ret = _gss_spnego_indicate_mechs(minor_status, &mechs);
     if (ret != GSS_S_COMPLETE)
        return ret;
 
index effba5833ce017929fff674f65ebda47dd9a53d2..7d3399ab9dfd6702b0afa582a0d2d54fe635ab86 100644 (file)
@@ -68,48 +68,20 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_acquire_cred_from
     )
 {
     OM_uint32 ret, tmp;
-    gss_OID_set_desc actual_desired_mechs;
     gss_OID_set mechs;
-    size_t i, j;
 
     *output_cred_handle = GSS_C_NO_CREDENTIAL;
 
-    ret = gss_indicate_mechs(minor_status, &mechs);
+    ret = _gss_spnego_indicate_mechs(minor_status, &mechs);
     if (ret != GSS_S_COMPLETE)
        return ret;
 
-    /* Remove ourselves from this list */
-    actual_desired_mechs.count = mechs->count;
-    actual_desired_mechs.elements = malloc(actual_desired_mechs.count *
-                                          sizeof(gss_OID_desc));
-    if (actual_desired_mechs.elements == NULL) {
-       *minor_status = ENOMEM;
-       ret = GSS_S_FAILURE;
-       goto out;
-    }
-
-    for (i = 0, j = 0; i < mechs->count; i++) {
-       if (gss_oid_equal(&mechs->elements[i], GSS_SPNEGO_MECHANISM))
-           continue;
-
-       actual_desired_mechs.elements[j] = mechs->elements[i];
-       j++;
-    }
-    actual_desired_mechs.count = j;
-
     ret = gss_acquire_cred_from(minor_status, desired_name,
-                               time_req, &actual_desired_mechs,
+                               time_req, mechs,
                                cred_usage, cred_store,
                                output_cred_handle,
                                actual_mechs, time_rec);
     gss_release_oid_set(&tmp, &mechs);
-    if (actual_desired_mechs.elements != NULL) {
-       free(actual_desired_mechs.elements);
-    }
-
-    if (ret != GSS_S_COMPLETE) {
-       _gss_spnego_release_cred(&tmp, output_cred_handle);
-    }
 
     return ret;
 }
@@ -235,9 +207,6 @@ _gss_spnego_set_neg_mechs (OM_uint32 *minor_status,
                    break;
            }
        }
-
-       /* for inner negotiation mechs, such as NegoEx */
-       (void) gss_set_neg_mechs(&minor, cred_handle, mech_list);
     } else {
        /*
         * RFC 4178 says that GSS_Set_neg_mechs() on NULL credential sets
index add000562256710cec26eaea47c719c9ac37a1c4..e0744f4b2079cf74789fb1d0d0ccf6b5350825ff 100644 (file)
@@ -151,6 +151,9 @@ static gssapi_mech_interface_desc spnego_mech = {
     NULL, /* gm_store_cred_into */
     _gss_spnego_set_neg_mechs,
     _gss_spnego_get_neg_mechs,
+    NULL, /* gm_query_mechanism_info */
+    NULL, /* gm_query_meta_data */
+    NULL, /* gm_exchange_meta_data */
     NULL  /* gm_compat */
 };
 
@@ -159,3 +162,4 @@ __gss_spnego_initialize(void)
 {
        return &spnego_mech;
 }
+
index 0d275a36bdcf73f60b769c437511202016904e24..6cef4c87f3d09fb7fca9b4335e0b37ed9b6a7541 100644 (file)
 
 #include "spnego_locl.h"
 
-/*
- * Is target_name an sane target for `mech´.
- */
+#define GSISC(name) \
+static                                                            \
+OM_uint32 name(OM_uint32 *, gss_const_cred_id_t, gssspnego_ctx,           \
+              gss_const_name_t, gss_const_OID,                    \
+              OM_uint32, OM_uint32, const gss_channel_bindings_t, \
+              gss_const_buffer_t, gss_buffer_t,                   \
+              OM_uint32 *, OM_uint32 *)
+
+GSISC(spnego_initial);
+GSISC(spnego_reply);
+GSISC(wait_server_mic);
+GSISC(step_completed);
+
+
+ /*
+  * Is target_name an sane target for `mech´.
+  */
 
 static OM_uint32
-initiator_approved(gss_const_cred_id_t cred,
-                  gss_name_t target_name,
+initiator_approved(OM_uint32 *minor_status,
+                  void *userptr,
+                  gss_const_name_t target_name,
+                  gss_const_cred_id_t cred,
                   gss_OID mech)
 {
     OM_uint32 min_stat, maj_stat;
     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
     gss_buffer_desc out;
+    struct gssspnego_optimistic_ctx *sel = userptr;
+    gss_OID negotiated_mech_type = GSS_C_NO_OID;
+    OM_uint32 flags = 0, time_rec = 0;
+    auth_scheme scheme;
+    int negoex = 0;
 
     maj_stat = gss_init_sec_context(&min_stat,
                                    cred,
                                    &ctx,
-                                   target_name,
+                                   sel->target_name,
                                    mech,
-                                   0,
-                                   GSS_C_INDEFINITE,
-                                   GSS_C_NO_CHANNEL_BINDINGS,
+                                   sel->req_flags,
+                                   sel->time_req,
+                                   sel->input_chan_bindings,
                                    GSS_C_NO_BUFFER,
-                                   NULL,
+                                   &negotiated_mech_type,
                                    &out,
-                                   NULL,
-                                   NULL);
+                                   &flags,
+                                   &time_rec);
     if (GSS_ERROR(maj_stat)) {
        gss_mg_collect_error(mech, maj_stat, min_stat);
-       return GSS_S_BAD_MECH;
+       *minor_status = min_stat;
+       return maj_stat;
     }
-    gss_release_buffer(&min_stat, &out);
-    gss_delete_sec_context(&min_stat, &ctx, NULL);
 
-    return GSS_S_COMPLETE;
+    if (gssspi_query_mechanism_info(&min_stat, mech, scheme) == GSS_S_COMPLETE)
+       negoex = 1;
+
+    if (sel->preferred_mech_type == GSS_C_NO_OID) {
+       sel->preferred_mech_type = mech;
+       sel->negotiated_mech_type = negotiated_mech_type;
+       sel->optimistic_token = out;
+       sel->optimistic_flags = flags;
+       sel->optimistic_time_rec = time_rec;
+       sel->gssctx = ctx;
+       if (maj_stat == GSS_S_COMPLETE)
+           sel->complete = 1;
+       if (negoex)
+           memcpy(sel->scheme, scheme, GUID_LENGTH);
+    } else {
+       gss_release_buffer(&min_stat, &out);
+       gss_delete_sec_context(&min_stat, &ctx, NULL);
+    }
+
+    maj_stat = GSS_S_COMPLETE;
+
+    if (negoex) {
+       maj_stat = _gss_negoex_add_auth_mech(minor_status, sel->spnegoctx,
+                                            mech, scheme);
+    }
+
+    return maj_stat;
 }
 
 /*
@@ -78,42 +124,40 @@ initiator_approved(gss_const_cred_id_t cred,
  * must return GSS_S_CONTINUE_NEEDED if a token was generated.
  */
 static OM_uint32
-spnego_reply_internal(OM_uint32 *minor_status,
-                     gssspnego_ctx context_handle,
-                     const gss_buffer_t mech_buf,
-                     gss_buffer_t mech_token,
-                     gss_buffer_t output_token)
+make_reply(OM_uint32 *minor_status,
+          gssspnego_ctx ctx,
+          gss_buffer_t mech_token,
+          gss_buffer_t output_token)
 {
     NegotiationToken nt;
     gss_buffer_desc mic_buf;
-    OM_uint32 ret;
+    OM_uint32 ret, minor;
     size_t size;
-
-    if (mech_buf == GSS_C_NO_BUFFER && mech_token->length == 0) {
-       output_token->length = 0;
-       output_token->value = NULL;
-
-       return context_handle->open ? GSS_S_COMPLETE : GSS_S_FAILURE;
-    }
+    NegResultEnum result;
 
     memset(&nt, 0, sizeof(nt));
 
     nt.element = choice_NegotiationToken_negTokenResp;
 
-    ALLOC(nt.u.negTokenResp.negResult, 1);
-    if (nt.u.negTokenResp.negResult == NULL) {
-       *minor_status = ENOMEM;
-       return GSS_S_FAILURE;
-    }
-
+    nt.u.negTokenResp.negResult = NULL;
     nt.u.negTokenResp.supportedMech = NULL;
 
     output_token->length = 0;
     output_token->value = NULL;
 
+    /* figure out our status */
+
+    if (ctx->flags.open) {
+       if (ctx->flags.verified_mic == 1 || ctx->flags.require_mic == 0)
+           result = accept_completed;
+       else
+           result = accept_incomplete;
+    } else  {
+       result = accept_incomplete;
+    }
+
     if (mech_token->length == 0) {
        nt.u.negTokenResp.responseToken = NULL;
-       *(nt.u.negTokenResp.negResult)  = accept_completed;
     } else {
        ALLOC(nt.u.negTokenResp.responseToken, 1);
        if (nt.u.negTokenResp.responseToken == NULL) {
@@ -125,21 +169,23 @@ spnego_reply_internal(OM_uint32 *minor_status,
        nt.u.negTokenResp.responseToken->data   = mech_token->value;
        mech_token->length = 0;
        mech_token->value  = NULL;
-
-       *(nt.u.negTokenResp.negResult)  = accept_incomplete;
     }
 
-    if (mech_buf != GSS_C_NO_BUFFER) {
+    /*
+     * XXX should limit when we send the MIC ?
+     */
+    if (ctx->flags.open && ctx->flags.sent_mic == 0) {
+
+       ctx->flags.sent_mic = 1;
 
        ret = gss_get_mic(minor_status,
-                         context_handle->negotiated_ctx_id,
+                         ctx->negotiated_ctx_id,
                          0,
-                         mech_buf,
+                         &ctx->NegTokenInit_mech_types,
                          &mic_buf);
-       if (ret == GSS_S_COMPLETE &&
-           gss_oid_equal(context_handle->negotiated_mech_type, GSS_NTLM_MECHANISM))
-           _gss_spnego_ntlm_reset_crypto(minor_status, context_handle, 0);
        if (ret == GSS_S_COMPLETE) {
+           _gss_spnego_ntlm_reset_crypto(&minor, ctx, FALSE);
+
            ALLOC(nt.u.negTokenResp.mechListMIC, 1);
            if (nt.u.negTokenResp.mechListMIC == NULL) {
                gss_release_buffer(minor_status, &mic_buf);
@@ -150,270 +196,245 @@ spnego_reply_internal(OM_uint32 *minor_status,
 
            nt.u.negTokenResp.mechListMIC->length = mic_buf.length;
            nt.u.negTokenResp.mechListMIC->data   = mic_buf.value;
+           /* mic_buf free()d with nt */
        } else if (ret == GSS_S_UNAVAILABLE) {
+           /* lets hope that its ok to not send te mechListMIC for broken mechs */
            nt.u.negTokenResp.mechListMIC = NULL;
-       } if (ret) {
+           ctx->flags.require_mic = 0;
+       } else {
            free_NegotiationToken(&nt);
            *minor_status = ENOMEM;
-           return GSS_S_FAILURE;
+           return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                          ret, *minor_status,
+                                          "SPNEGO failed to sign MIC");
        }
     } else {
        nt.u.negTokenResp.mechListMIC = NULL;
     }
 
+    ALLOC(nt.u.negTokenResp.negResult, 1);
+    if (nt.u.negTokenResp.negResult == NULL) {
+       free_NegotiationToken(&nt);
+       *minor_status = ENOMEM;
+       return GSS_S_FAILURE;
+    }
+    *nt.u.negTokenResp.negResult = result;
+
     ASN1_MALLOC_ENCODE(NegotiationToken,
                       output_token->value, output_token->length,
                       &nt, &size, ret);
+    free_NegotiationToken(&nt);
     if (ret) {
-       free_NegotiationToken(&nt);
        *minor_status = ret;
        return GSS_S_FAILURE;
     }
 
-    if (*(nt.u.negTokenResp.negResult) == accept_completed)
-       ret = GSS_S_COMPLETE;
-    else
-       ret = GSS_S_CONTINUE_NEEDED;
+    if (result != accept_completed)
+       return GSS_S_CONTINUE_NEEDED;
 
-    free_NegotiationToken(&nt);
-    return ret;
+    return GSS_S_COMPLETE;
 }
 
 static OM_uint32
-spnego_initial
-           (OM_uint32 * minor_status,
-           gss_const_cred_id_t cred,
-            gss_ctx_id_t * context_handle,
-            gss_const_name_t target_name,
-            const gss_OID mech_type,
-            OM_uint32 req_flags,
-            OM_uint32 time_req,
-            const gss_channel_bindings_t input_chan_bindings,
-            const gss_buffer_t input_token,
-            gss_OID * actual_mech_type,
-            gss_buffer_t output_token,
-            OM_uint32 * ret_flags,
-            OM_uint32 * time_rec
-    )
+spnego_initial(OM_uint32 * minor_status,
+              gss_const_cred_id_t cred,
+              gssspnego_ctx ctx,
+              gss_const_name_t target_name,
+              gss_const_OID mech_type,
+              OM_uint32 req_flags,
+              OM_uint32 time_req,
+              const gss_channel_bindings_t input_chan_bindings,
+              gss_const_buffer_t input_token,
+              gss_buffer_t output_token,
+              OM_uint32 * ret_flags,
+              OM_uint32 * time_rec)
 {
-    NegTokenInit ni;
+    NegotiationToken nt;
     int ret;
     OM_uint32 sub, minor;
     gss_buffer_desc mech_token;
-    u_char *buf;
-    size_t buf_size, buf_len;
+    size_t size = 0;
     gss_buffer_desc data;
-    size_t ni_len;
-    gss_ctx_id_t context;
-    gssspnego_ctx ctx;
+    struct gssspnego_optimistic_ctx sel;
 
     *minor_status = 0;
 
-    memset (&ni, 0, sizeof(ni));
-
-    *context_handle = GSS_C_NO_CONTEXT;
+    memset(&nt, 0, sizeof(nt));
 
     if (target_name == GSS_C_NO_NAME)
        return GSS_S_BAD_NAME;
 
-    sub = _gss_spnego_alloc_sec_context(&minor, &context);
+    sub = gss_duplicate_name(&minor, target_name, &ctx->target_name);
     if (GSS_ERROR(sub)) {
        *minor_status = minor;
        return sub;
     }
-    ctx = (gssspnego_ctx)context;
 
-    HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex);
+    nt.element = choice_NegotiationToken_negTokenInit;
 
-    ctx->local = 1;
+    ctx->flags.local = 1;
 
-    sub = gss_duplicate_name(&minor, target_name, &ctx->target_name);
-    if (GSS_ERROR(sub)) {
-       *minor_status = minor;
-       _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER);
-       return sub;
-    }
+    memset(&sel, 0, sizeof(sel));
+
+    sel.spnegoctx = ctx;
+    sel.target_name = ctx->target_name;
+    sel.preferred_mech_type = GSS_C_NO_OID;
+    sel.req_flags = req_flags;
+    sel.time_req = time_req;
+    sel.input_chan_bindings = (gss_channel_bindings_t)input_chan_bindings;
 
     sub = _gss_spnego_indicate_mechtypelist(&minor,
                                            ctx->target_name,
                                            initiator_approved,
+                                           &sel,
                                            0,
                                            cred,
-                                           &ni.mechTypes,
+                                           &nt.u.negTokenInit.mechTypes,
                                            &ctx->preferred_mech_type);
     if (GSS_ERROR(sub)) {
        *minor_status = minor;
-       _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER);
        return sub;
     }
 
-    ni.reqFlags = NULL;
+    _gss_spnego_log_mechTypes(&nt.u.negTokenInit.mechTypes);
 
-    /*
-     * If we have a credential handle, use it to select the mechanism
-     * that we will use
-     */
+    nt.u.negTokenInit.reqFlags = NULL;
+
+    if (gss_oid_equal(ctx->preferred_mech_type, GSS_NEGOEX_MECHANISM)) {
+       struct negoex_auth_mech *mech;
 
-    /* generate optimistic token */
-    sub = gss_init_sec_context(&minor,
-                              cred,
-                              &ctx->negotiated_ctx_id,
-                              ctx->target_name,
-                              ctx->preferred_mech_type,
+       sub = _gss_negoex_init(&minor,
+                              &sel,
+                              ctx,
+                              (gss_cred_id_t)cred,
                               req_flags,
                               time_req,
                               input_chan_bindings,
-                              input_token,
-                              &ctx->negotiated_mech_type,
-                              &mech_token,
-                              &ctx->mech_flags,
-                              &ctx->mech_time_rec);
-    if (GSS_ERROR(sub)) {
-       free_NegTokenInit(&ni);
-       *minor_status = minor;
-       gss_mg_collect_error(ctx->preferred_mech_type, sub, minor);
-       _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER);
-       return sub;
+                              GSS_C_NO_BUFFER,
+                              &mech_token);
+       if (GSS_ERROR(sub)) {
+           free_NegotiationToken(&nt);
+           return gss_mg_set_error_string(GSS_C_NO_OID, sub, minor,
+                                          "NegoEx could not generate a context token");
+       }
+       mech = _gss_negoex_negotiated_mech(ctx);
+       ctx->flags.maybe_open = mech && mech->complete;
+       gss_release_buffer(&minor, &sel.optimistic_token);
+    } else {
+       /* optimistic token from selection context */
+       mech_token = sel.optimistic_token;
+       ctx->mech_flags = sel.optimistic_flags;
+       ctx->mech_time_rec = sel.optimistic_time_rec;
+       ctx->negotiated_mech_type = sel.negotiated_mech_type;
+       ctx->negotiated_ctx_id = sel.gssctx;
+       ctx->flags.maybe_open = sel.complete;
+    }
+
+    if (ctx->preferred_mech_type == GSS_C_NO_OID) {
+       free_NegotiationToken(&nt);
+       *minor_status = 0;
+       return gss_mg_set_error_string(GSS_C_NO_OID, GSS_S_NO_CONTEXT, 0,
+                                      "SPNEGO could not find a preferred mechanism");
     }
-    if (sub == GSS_S_COMPLETE)
-       ctx->maybe_open = 1;
+
 
     if (mech_token.length != 0) {
-       ALLOC(ni.mechToken, 1);
-       if (ni.mechToken == NULL) {
-           free_NegTokenInit(&ni);
+       ALLOC(nt.u.negTokenInit.mechToken, 1);
+       if (nt.u.negTokenInit.mechToken == NULL) {
+           free_NegotiationToken(&nt);
            gss_release_buffer(&minor, &mech_token);
-           _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER);
            *minor_status = ENOMEM;
            return GSS_S_FAILURE;
        }
-       ni.mechToken->length = mech_token.length;
-       ni.mechToken->data = malloc(mech_token.length);
-       if (ni.mechToken->data == NULL && mech_token.length != 0) {
-           free_NegTokenInit(&ni);
+       nt.u.negTokenInit.mechToken->length = mech_token.length;
+       nt.u.negTokenInit.mechToken->data = malloc(mech_token.length);
+       if (nt.u.negTokenInit.mechToken->data == NULL && mech_token.length != 0) {
+           free_NegotiationToken(&nt);
            gss_release_buffer(&minor, &mech_token);
            *minor_status = ENOMEM;
-           _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER);
            return GSS_S_FAILURE;
        }
-       memcpy(ni.mechToken->data, mech_token.value, mech_token.length);
+       memcpy(nt.u.negTokenInit.mechToken->data, mech_token.value, mech_token.length);
        gss_release_buffer(&minor, &mech_token);
     } else
-       ni.mechToken = NULL;
-
-    ni.mechListMIC = NULL;
+       nt.u.negTokenInit.mechToken = NULL;
 
-    ni_len = length_NegTokenInit(&ni);
-    buf_size = 1 + der_length_len(ni_len) + ni_len;
+    nt.u.negTokenInit.mechListMIC = NULL;
 
-    buf = malloc(buf_size);
-    if (buf == NULL) {
-       free_NegTokenInit(&ni);
-       *minor_status = ENOMEM;
-       _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER);
-       return GSS_S_FAILURE;
-    }
+    {
+       MechTypeList mt;
 
-    ret = encode_NegTokenInit(buf + buf_size - 1,
-                             ni_len,
-                             &ni, &buf_len);
-    if (ret == 0 && ni_len != buf_len)
-       abort();
+       mt.len = nt.u.negTokenInit.mechTypes.len;
+       mt.val = nt.u.negTokenInit.mechTypes.val;
 
-    if (ret == 0) {
-       size_t tmp;
-
-       ret = der_put_length_and_tag(buf + buf_size - buf_len - 1,
-                                    buf_size - buf_len,
-                                    buf_len,
-                                    ASN1_C_CONTEXT,
-                                    CONS,
-                                    0,
-                                    &tmp);
-       if (ret == 0 && tmp + buf_len != buf_size)
-           abort();
+       ASN1_MALLOC_ENCODE(MechTypeList,
+                          ctx->NegTokenInit_mech_types.value,
+                          ctx->NegTokenInit_mech_types.length,
+                          &mt, &size, ret);
+       if (ret) {
+           *minor_status = ret;
+           free_NegotiationToken(&nt);
+           return GSS_S_FAILURE;
+       }
     }
+
+    ASN1_MALLOC_ENCODE(NegotiationToken, data.value, data.length, &nt, &size, ret);
+    free_NegotiationToken(&nt);
     if (ret) {
-       *minor_status = ret;
-       free(buf);
-       free_NegTokenInit(&ni);
-       _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER);
        return GSS_S_FAILURE;
     }
-
-    data.value  = buf;
-    data.length = buf_size;
-
-    ctx->initiator_mech_types.len = ni.mechTypes.len;
-    ctx->initiator_mech_types.val = ni.mechTypes.val;
-    ni.mechTypes.len = 0;
-    ni.mechTypes.val = NULL;
-
-    free_NegTokenInit(&ni);
+    if (data.length != size)
+       abort();
 
     sub = gss_encapsulate_token(&data,
                                GSS_SPNEGO_MECHANISM,
                                output_token);
-    free (buf);
+    free (data.value);
 
     if (sub) {
-       _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER);
        return sub;
     }
 
-    if (actual_mech_type)
-       *actual_mech_type = ctx->negotiated_mech_type;
     if (ret_flags)
        *ret_flags = ctx->mech_flags;
     if (time_rec)
        *time_rec = ctx->mech_time_rec;
 
-    HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
-
-    *context_handle = context;
+    ctx->initiator_state = spnego_reply;
 
     return GSS_S_CONTINUE_NEEDED;
 }
 
+/*
+ *
+ */
+
 static OM_uint32
-spnego_reply
-           (OM_uint32 * minor_status,
-           gss_const_cred_id_t cred,
-            gss_ctx_id_t * context_handle,
-            gss_const_name_t target_name,
-            const gss_OID mech_type,
-            OM_uint32 req_flags,
-            OM_uint32 time_req,
-            const gss_channel_bindings_t input_chan_bindings,
-            const gss_buffer_t input_token,
-            gss_OID * actual_mech_type,
-            gss_buffer_t output_token,
-            OM_uint32 * ret_flags,
-            OM_uint32 * time_rec
-    )
+spnego_reply(OM_uint32 * minor_status,
+            gss_const_cred_id_t cred,
+            gssspnego_ctx ctx,
+            gss_const_name_t target_name,
+            gss_const_OID mech_type,
+            OM_uint32 req_flags,
+            OM_uint32 time_req,
+            const gss_channel_bindings_t input_chan_bindings,
+            gss_const_buffer_t input_token,
+            gss_buffer_t output_token,
+            OM_uint32 * ret_flags,
+            OM_uint32 * time_rec)
 {
     OM_uint32 ret, minor;
     NegotiationToken resp;
-    gss_OID_desc mech;
-    int require_mic;
-    size_t buf_len = 0;
-    gss_buffer_desc mic_buf, mech_buf;
     gss_buffer_desc mech_output_token;
-    gssspnego_ctx ctx;
 
     *minor_status = 0;
 
-    ctx = (gssspnego_ctx)*context_handle;
-
     output_token->length = 0;
     output_token->value  = NULL;
 
     mech_output_token.length = 0;
     mech_output_token.value = NULL;
 
-    mech_buf.value = NULL;
-    mech_buf.length = 0;
-
     ret = decode_NegotiationToken(input_token->value, input_token->length,
                                  &resp, NULL);
     if (ret)
@@ -426,57 +447,84 @@ spnego_reply
     }
 
     if (resp.u.negTokenResp.negResult == NULL
-       || *(resp.u.negTokenResp.negResult) == reject
-       /* || resp.u.negTokenResp.supportedMech == NULL */
-       )
+       || *(resp.u.negTokenResp.negResult) == reject)
     {
        free_NegotiationToken(&resp);
        return GSS_S_BAD_MECH;
     }
 
     /*
-     * Pick up the mechanism that the acceptor selected, only allow it
-     * to be sent in packet.
+     * Pick up the mechanism that the acceptor selected, only pick up
+     * the first selection.
      */
 
-    HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex);
+    if (ctx->selected_mech_type == GSS_C_NO_OID && resp.u.negTokenResp.supportedMech) {
+       gss_OID_desc oid;
+       size_t len;
 
-    if (resp.u.negTokenResp.supportedMech) {
+       ctx->flags.seen_supported_mech = 1;
 
-       if (ctx->oidlen) {
+       oid.length = (OM_uint32)der_length_oid(resp.u.negTokenResp.supportedMech);
+       oid.elements = malloc(oid.length);
+       if (oid.elements == NULL) {
            free_NegotiationToken(&resp);
-           HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
            return GSS_S_BAD_MECH;
        }
-       ret = der_put_oid(ctx->oidbuf + sizeof(ctx->oidbuf) - 1,
-                         sizeof(ctx->oidbuf),
+       ret = der_put_oid(((uint8_t *)oid.elements) + oid.length - 1,
+                         oid.length,
                          resp.u.negTokenResp.supportedMech,
-                         &ctx->oidlen);
-       /* Avoid recursively embedded SPNEGO */
-       if (ret || (ctx->oidlen == GSS_SPNEGO_MECHANISM->length &&
-                   memcmp(ctx->oidbuf + sizeof(ctx->oidbuf) - ctx->oidlen,
-                          GSS_SPNEGO_MECHANISM->elements,
-                          ctx->oidlen) == 0))
-       {
+                         &len);
+       if (ret || len != oid.length) {
+           free(oid.elements);
            free_NegotiationToken(&resp);
-           HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
            return GSS_S_BAD_MECH;
        }
 
+       if (gss_oid_equal(GSS_SPNEGO_MECHANISM, &oid)) {
+           free(oid.elements);
+           free_NegotiationToken(&resp);
+           return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                          GSS_S_BAD_MECH, (*minor_status = EINVAL),
+                                          "SPNEGO acceptor picked SPNEGO??");
+       }
+
        /* check if the acceptor took our optimistic token */
-       if (ctx->oidlen != ctx->preferred_mech_type->length ||
-           memcmp(ctx->oidbuf + sizeof(ctx->oidbuf) - ctx->oidlen,
-                  ctx->preferred_mech_type->elements,
-                  ctx->oidlen) != 0)
-       {
+       if (gss_oid_equal(ctx->preferred_mech_type, &oid)) {
+           ctx->selected_mech_type = ctx->preferred_mech_type;
+       } else if (gss_oid_equal(ctx->preferred_mech_type, GSS_KRB5_MECHANISM) &&
+                  gss_oid_equal(&oid, &_gss_spnego_mskrb_mechanism_oid_desc)) {
+           /* mis-encoded asn1 type from msft servers */
+           ctx->selected_mech_type = ctx->preferred_mech_type;
+       } else {
+           /* nope, lets start over */
            gss_delete_sec_context(&minor, &ctx->negotiated_ctx_id,
                                   GSS_C_NO_BUFFER);
            ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT;
+
+           if (gss_oid_equal(&oid, GSS_NEGOEX_MECHANISM))
+               ctx->selected_mech_type = GSS_NEGOEX_MECHANISM;
+           else
+               ctx->selected_mech_type = _gss_mg_support_mechanism(&oid);
+
+           /* XXX check that server pick a mechanism we proposed */
+           if (ctx->selected_mech_type == GSS_C_NO_OID) {
+               free(oid.elements);
+               free_NegotiationToken(&resp);
+               return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                              GSS_S_BAD_MECH, (*minor_status = EINVAL),
+                                              "SPNEGO acceptor sent unsupported supportedMech");
+           }
        }
-    } else if (ctx->oidlen == 0) {
+
+       _gss_spnego_log_mech("initiator selected mechanism", ctx->selected_mech_type);
+
+       free(oid.elements);
+
+    } else if (ctx->selected_mech_type == NULL) {
        free_NegotiationToken(&resp);
-       HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
-       return GSS_S_BAD_MECH;
+       return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                      GSS_S_BAD_MECH, (*minor_status = EINVAL),
+                                      "SPNEGO acceptor didn't send supportedMech");
     }
 
     /* if a token (of non zero length), or no context, pass to underlaying mech */
@@ -492,176 +540,279 @@ spnego_reply
            mech_input_token.value = NULL;
        }
 
-
-       mech.length = ctx->oidlen;
-       mech.elements = ctx->oidbuf + sizeof(ctx->oidbuf) - ctx->oidlen;
-
        /* Fall through as if the negotiated mechanism
           was requested explicitly */
-       ret = gss_init_sec_context(&minor,
-                                  cred,
-                                  &ctx->negotiated_ctx_id,
-                                  ctx->target_name,
-                                  &mech,
+       if (gss_oid_equal(ctx->selected_mech_type, GSS_NEGOEX_MECHANISM)) {
+           ret = _gss_negoex_init(&minor,
+                                  NULL, /* no optimistic token */
+                                  ctx,
+                                  (gss_cred_id_t)cred,
                                   req_flags,
                                   time_req,
                                   input_chan_bindings,
                                   &mech_input_token,
-                                  &ctx->negotiated_mech_type,
-                                  &mech_output_token,
-                                  &ctx->mech_flags,
-                                  &ctx->mech_time_rec);
+                                  &mech_output_token);
+       } else {
+           ret = gss_init_sec_context(&minor,
+                                      cred,
+                                      &ctx->negotiated_ctx_id,
+                                      ctx->target_name,
+                                      ctx->selected_mech_type,
+                                      req_flags,
+                                      time_req,
+                                      input_chan_bindings,
+                                      &mech_input_token,
+                                      &ctx->negotiated_mech_type,
+                                      &mech_output_token,
+                                      &ctx->mech_flags,
+                                      &ctx->mech_time_rec);
+           if (GSS_ERROR(ret))
+               gss_mg_collect_error(ctx->selected_mech_type, ret, minor);
+       }
        if (GSS_ERROR(ret)) {
-           HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
            free_NegotiationToken(&resp);
-           gss_mg_collect_error(&mech, ret, minor);
            *minor_status = minor;
            return ret;
        }
        if (ret == GSS_S_COMPLETE) {
-           ctx->open = 1;
+           ctx->flags.open = 1;
+       }
+    } else if (*resp.u.negTokenResp.negResult == accept_completed) {
+       if (ctx->flags.maybe_open)
+           ctx->flags.open = 1;
+
+       if (!ctx->flags.open) {
+           return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                          GSS_S_BAD_MECH, (*minor_status = EINVAL),
+                                          "SPNEGO acceptor sent acceptor complete, "
+                                          "but we are not complete yet");
        }
-    } else if (*(resp.u.negTokenResp.negResult) == accept_completed) {
-       if (ctx->maybe_open)
-           ctx->open = 1;
     }
 
-    if (*(resp.u.negTokenResp.negResult) == request_mic) {
-       ctx->require_mic = 1;
+    if (*resp.u.negTokenResp.negResult == request_mic) {
+       ctx->flags.peer_require_mic = 1;
     }
 
-    if (ctx->open) {
+    if (ctx->flags.open && ctx->flags.verified_mic == 0) {
+
+       ctx->flags.require_mic = 1; /* default is to require a MIC */
+       ctx->flags.safe_omit = _gss_spnego_safe_omit_mechlist_mic(ctx);
+       
        /*
-        * Verify the mechListMIC if one was provided or CFX was
-        * used and a non-preferred mechanism was selected
+        * If the peer sent mechListMIC, require it to verify ...
         */
-       if (resp.u.negTokenResp.mechListMIC != NULL) {
-           require_mic = 1;
-       } else {
-           ret = _gss_spnego_require_mechlist_mic(minor_status, ctx,
-                                                  &require_mic);
-           if (ret) {
-               HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
-               free_NegotiationToken(&resp);
-               gss_release_buffer(&minor, &mech_output_token);
-               return ret;
+       if (resp.u.negTokenResp.mechListMIC) {
+           heim_octet_string *m = resp.u.negTokenResp.mechListMIC;
+
+           /* ...unless its a windows 2000 server that sends the
+            * responseToken inside the mechListMIC too. We only
+            * accept this condition if would have been safe to omit
+            * anyway. */
+
+           if (ctx->flags.safe_omit
+               && resp.u.negTokenResp.responseToken
+               && der_heim_octet_string_cmp(m, resp.u.negTokenResp.responseToken) == 0)
+           {
+               ctx->flags.require_mic = 0;
            }
        }
+
     } else {
-       require_mic = 0;
+       ctx->flags.require_mic = 0;
     }
 
-    if (require_mic) {
-       ASN1_MALLOC_ENCODE(MechTypeList, mech_buf.value, mech_buf.length,
-                          &ctx->initiator_mech_types, &buf_len, ret);
+    /*
+     * If we are supposed to check mic and have it, force checking now.
+     */
+
+    if (ctx->flags.require_mic && resp.u.negTokenResp.mechListMIC) {
+
+       ret = _gss_spnego_verify_mechtypes_mic(minor_status, ctx,
+                                              resp.u.negTokenResp.mechListMIC);
        if (ret) {
-           HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
            free_NegotiationToken(&resp);
-           gss_release_buffer(&minor, &mech_output_token);
-           *minor_status = ret;
-           return GSS_S_FAILURE;
-       }
-       if (mech_buf.length != buf_len) {
-           abort();
-            UNREACHABLE(return GSS_S_FAILURE);
-        }
-
-       if (resp.u.negTokenResp.mechListMIC == NULL) {
-           HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
-           free(mech_buf.value);
-           free_NegotiationToken(&resp);
-           *minor_status = 0;
-           return GSS_S_DEFECTIVE_TOKEN;
-       }
-       mic_buf.length = resp.u.negTokenResp.mechListMIC->length;
-       mic_buf.value  = resp.u.negTokenResp.mechListMIC->data;
-
-       if (mech_output_token.length == 0) {
-           ret = gss_verify_mic(minor_status,
-                                ctx->negotiated_ctx_id,
-                                &mech_buf,
-                                &mic_buf,
-                                NULL);
-          if (ret == GSS_S_COMPLETE &&
-              gss_oid_equal(ctx->negotiated_mech_type, GSS_NTLM_MECHANISM))
-               _gss_spnego_ntlm_reset_crypto(minor_status, ctx, 1);
-          if (ret) {
-               HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
-               free(mech_buf.value);
-               gss_release_buffer(&minor, &mech_output_token);
-               free_NegotiationToken(&resp);
-               return GSS_S_DEFECTIVE_TOKEN;
-           }
-           ctx->verified_mic = 1;
+           return ret;
        }
     }
 
-    ret = spnego_reply_internal(minor_status, ctx,
-                               require_mic ? &mech_buf : NULL,
-                               &mech_output_token,
-                               output_token);
+    /*
+     * Now that underlaying mech is open (conncted), we can figure out
+     * what nexd step to go to.
+     */
 
-    if (mech_buf.value != NULL)
-       free(mech_buf.value);
+    if (ctx->flags.open) {
+
+       if (*resp.u.negTokenResp.negResult == accept_completed && ctx->flags.safe_omit) {
+           ctx->initiator_state = step_completed;
+           ret = GSS_S_COMPLETE;
+       } else if (ctx->flags.require_mic != 0 && ctx->flags.verified_mic == 0) {
+           ctx->initiator_state = wait_server_mic;
+           ret = GSS_S_CONTINUE_NEEDED;
+       } else {
+           ctx->initiator_state = step_completed;
+           ret = GSS_S_COMPLETE;
+       }
+    }
+
+    if (*resp.u.negTokenResp.negResult != accept_completed ||
+       ctx->initiator_state != step_completed ||
+       mech_output_token.length)
+    {
+       OM_uint32 ret2;
+       ret2 = make_reply(minor_status, ctx,
+                         &mech_output_token,
+                         output_token);
+       if (ret2)
+           ret = ret2;
+    }
 
     free_NegotiationToken(&resp);
+
     gss_release_buffer(&minor, &mech_output_token);
 
-    if (actual_mech_type)
-       *actual_mech_type = ctx->negotiated_mech_type;
     if (ret_flags)
        *ret_flags = ctx->mech_flags;
     if (time_rec)
        *time_rec = ctx->mech_time_rec;
 
-    HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
     return ret;
 }
 
+static OM_uint32
+wait_server_mic(OM_uint32 * minor_status,
+               gss_const_cred_id_t cred,
+               gssspnego_ctx ctx,
+               gss_const_name_t target_name,
+               gss_const_OID mech_type,
+               OM_uint32 req_flags,
+               OM_uint32 time_req,
+               const gss_channel_bindings_t input_chan_bindings,
+               gss_const_buffer_t input_token,
+               gss_buffer_t output_token,
+               OM_uint32 * ret_flags,
+               OM_uint32 * time_rec)
+{
+    OM_uint32 major_status;
+    NegotiationToken resp;
+    int ret;
+
+    ret = decode_NegotiationToken(input_token->value, input_token->length, &resp, NULL);
+    if (ret)
+       return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                      GSS_S_BAD_MECH, ret,
+                                      "Failed to decode NegotiationToken");
+
+    if (resp.element != choice_NegotiationToken_negTokenResp
+       || resp.u.negTokenResp.negResult == NULL
+       || *resp.u.negTokenResp.negResult != accept_completed)
+    {
+       free_NegotiationToken(&resp);
+       return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                      GSS_S_BAD_MECH, (*minor_status = EINVAL),
+                                      "NegToken not accept_completed");
+    }
+
+    if (resp.u.negTokenResp.mechListMIC) {
+       major_status = _gss_spnego_verify_mechtypes_mic(minor_status, ctx,
+                                                       resp.u.negTokenResp.mechListMIC);
+    } else if (ctx->flags.safe_omit == 0) {
+       free_NegotiationToken(&resp);
+       return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                      GSS_S_BAD_MECH, (*minor_status = EINVAL),
+                                      "Waiting for MIC, but its missing in server request");
+    } else {
+       major_status = GSS_S_COMPLETE;
+    }
+
+    free_NegotiationToken(&resp);
+    if (major_status != GSS_S_COMPLETE)
+       return major_status;
+
+    ctx->flags.verified_mic = 1;
+    ctx->initiator_state = step_completed;
+
+    if (ret_flags)
+       *ret_flags = ctx->mech_flags;
+    if (time_rec)
+       *time_rec = ctx->mech_time_rec;
+
+    *minor_status = 0;
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+step_completed(OM_uint32 * minor_status,
+              gss_const_cred_id_t cred,
+              gssspnego_ctx ctx,
+              gss_const_name_t name,
+              gss_const_OID mech_type,
+              OM_uint32 req_flags,
+              OM_uint32 time_req,
+              const gss_channel_bindings_t input_chan_bindings,
+              gss_const_buffer_t input_token,
+              gss_buffer_t output_token,
+              OM_uint32 * ret_flags,
+              OM_uint32 * time_rec)
+{
+    return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                  GSS_S_BAD_STATUS, (*minor_status = EINVAL),
+                                  "SPNEGO called got ISC call one too many");
+}
+
 OM_uint32 GSSAPI_CALLCONV
-_gss_spnego_init_sec_context
-           (OM_uint32 * minor_status,
-            gss_const_cred_id_t initiator_cred_handle,
-            gss_ctx_id_t * context_handle,
-            gss_const_name_t target_name,
-            const gss_OID mech_type,
-            OM_uint32 req_flags,
-            OM_uint32 time_req,
-            const gss_channel_bindings_t input_chan_bindings,
-            const gss_buffer_t input_token,
-            gss_OID * actual_mech_type,
-            gss_buffer_t output_token,
-            OM_uint32 * ret_flags,
-            OM_uint32 * time_rec
-           )
+_gss_spnego_init_sec_context(OM_uint32 * minor_status,
+                            gss_const_cred_id_t initiator_cred_handle,
+                            gss_ctx_id_t * context_handle,
+                            gss_const_name_t target_name,
+                            const gss_OID mech_type,
+                            OM_uint32 req_flags,
+                            OM_uint32 time_req,
+                            const gss_channel_bindings_t input_chan_bindings,
+                            const gss_buffer_t input_token,
+                            gss_OID * actual_mech_type,
+                            gss_buffer_t output_token,
+                            OM_uint32 * ret_flags,
+                            OM_uint32 * time_rec)
 {
-    if (*context_handle == GSS_C_NO_CONTEXT)
-       return spnego_initial (minor_status,
-                              initiator_cred_handle,
-                              context_handle,
-                              target_name,
-                              mech_type,
-                              req_flags,
-                              time_req,
-                              input_chan_bindings,
-                              input_token,
-                              actual_mech_type,
-                              output_token,
-                              ret_flags,
-                              time_rec);
-    else
-       return spnego_reply (minor_status,
-                            initiator_cred_handle,
-                            context_handle,
-                            target_name,
-                            mech_type,
-                            req_flags,
-                            time_req,
-                            input_chan_bindings,
-                            input_token,
-                            actual_mech_type,
-                            output_token,
-                            ret_flags,
-                            time_rec);
+    gssspnego_ctx ctx;
+    OM_uint32 ret;
+
+    if (*context_handle == GSS_C_NO_CONTEXT) {
+       ret = _gss_spnego_alloc_sec_context(minor_status, context_handle);
+       if (GSS_ERROR(ret))
+           return ret;
+
+       ctx = (gssspnego_ctx)*context_handle;
+
+       ctx->initiator_state = spnego_initial;
+    } else {
+       ctx = (gssspnego_ctx)*context_handle;
+    }
+
+
+    HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex);
+
+    do {
+       ret = ctx->initiator_state(minor_status, initiator_cred_handle, ctx, target_name,
+                                  mech_type, req_flags, time_req, input_chan_bindings, input_token,
+                                  output_token, ret_flags, time_rec);
+
+    } while (ret == GSS_S_COMPLETE &&
+            ctx->initiator_state != step_completed &&
+            output_token->length == 0);
+
+    /* destroy context in case of error */
+    if (GSS_ERROR(ret)) {
+       OM_uint32 junk;
+       _gss_spnego_internal_delete_sec_context(&junk, context_handle, GSS_C_NO_BUFFER);
+    } else {
+
+       HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
+
+       if (actual_mech_type)
+           *actual_mech_type = ctx->negotiated_mech_type;
+    }
+
+    return ret;
 }
 
diff --git a/lib/gssapi/spnego/negoex_ctx.c b/lib/gssapi/spnego/negoex_ctx.c
new file mode 100644 (file)
index 0000000..1f94089
--- /dev/null
@@ -0,0 +1,1018 @@
+/*
+ * Copyright (C) 2011-2019 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 "spnego_locl.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 tmpMinor;
+    gss_buffer_set_t buffers = *pBuffers;
+    size_t i;
+
+    if (buffers != GSS_C_NO_BUFFER_SET) {
+       for (i = 0; i < buffers->count; i++)
+           memset_s(buffers->elements[i].value,
+                    buffers->elements[i].length, 0,
+                    buffers->elements[i].length);
+
+       gss_release_buffer_set(&tmpMinor, &buffers);
+    }
+
+    *pBuffers = GSS_C_NO_BUFFER_SET;
+}
+
+static OM_uint32
+buffer_set_to_crypto(OM_uint32 *minor,
+                    krb5_context context,
+                    gss_buffer_set_t buffers,
+                    krb5_crypto *crypto)
+{
+    krb5_error_code ret;
+    krb5_keyblock keyblock;
+    OM_uint32 tmp;
+
+    /*
+     * 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 != sizeof(tmp)) {
+       *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY;
+       return GSS_S_FAILURE;
+    }
+
+    if (*crypto != NULL) {
+       krb5_crypto_destroy(context, *crypto);
+       *crypto = NULL;
+    }
+
+    keyblock.keyvalue.data = buffers->elements[0].value;
+    keyblock.keyvalue.length = buffers->elements[0].length;
+    _gss_mg_decode_le_uint32(buffers->elements[1].value, &tmp);
+    keyblock.keytype = tmp;
+
+    ret = krb5_crypto_init(context, &keyblock, 0, crypto);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_FAILURE;
+    }
+
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+get_session_keys(OM_uint32 *minor,
+                krb5_context context,
+                struct negoex_auth_mech *mech)
+{
+    OM_uint32 major, tmpMinor;
+    gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET;
+
+    major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context,
+                                          GSS_C_INQ_NEGOEX_KEY, &buffers);
+    if (major == GSS_S_COMPLETE) {
+       major = buffer_set_to_crypto(minor, context,
+                                    buffers, &mech->crypto);
+       zero_and_release_buffer_set(&buffers);
+       if (major != GSS_S_COMPLETE)
+           return major;
+    }
+
+    major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context,
+                                          GSS_C_INQ_NEGOEX_VERIFY_KEY,
+                                          &buffers);
+    if (major == GSS_S_COMPLETE) {
+       major = buffer_set_to_crypto(minor, context,
+                                    buffers, &mech->verify_crypto);
+       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, gssspnego_ctx ctx)
+{
+    uint8_t random[32];
+    struct negoex_auth_mech *mech;
+    size_t i = 0;
+
+    krb5_generate_random_block(random, sizeof(random));
+
+    HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
+       _gss_negoex_log_auth_scheme(ctx->flags.local, ++i, mech->scheme);
+
+    return _gss_negoex_add_nego_message(minor, ctx, INITIATOR_NEGO, random);
+}
+
+static OM_uint32
+process_initiator_nego(OM_uint32 *minor,
+                      gssspnego_ctx ctx,
+                      struct negoex_message *messages,
+                      size_t nmessages)
+{
+    struct nego_message *msg;
+    size_t i;
+
+    heim_assert(!ctx->flags.local && ctx->negoex_step == 1,
+               "NegoEx INITIATOR_NEGO token received after first leg");
+
+    msg = _gss_negoex_locate_nego_message(messages, nmessages, INITIATOR_NEGO);
+    if (msg == NULL) {
+       *minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    for (i = 0; i < msg->nschemes; i++)
+       _gss_negoex_log_auth_scheme(ctx->flags.local, i + 1, &msg->schemes[i * GUID_LENGTH]);
+
+    _gss_negoex_restrict_auth_schemes(ctx, msg->schemes, msg->nschemes);
+
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+emit_acceptor_nego(OM_uint32 *minor, gssspnego_ctx ctx)
+{
+    uint8_t random[32];
+
+    krb5_generate_random_block(random, 32);
+
+    return _gss_negoex_add_nego_message(minor, ctx, ACCEPTOR_NEGO, random);
+}
+
+static OM_uint32
+process_acceptor_nego(OM_uint32 *minor,
+                     gssspnego_ctx ctx,
+                     struct negoex_message *messages,
+                     size_t nmessages)
+{
+    struct nego_message *msg;
+
+    msg = _gss_negoex_locate_nego_message(messages, nmessages, ACCEPTOR_NEGO);
+    if (msg == NULL) {
+       *minor = (OM_uint32)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).
+     */
+    _gss_negoex_common_auth_schemes(ctx, msg->schemes, msg->nschemes);
+
+    return GSS_S_COMPLETE;
+}
+
+static void
+query_meta_data(gssspnego_ctx ctx,
+               struct gssspnego_optimistic_ctx *opt,
+               gss_cred_id_t cred,
+               OM_uint32 req_flags)
+{
+    OM_uint32 major, minor;
+    struct negoex_auth_mech *p, *next;
+
+    /*
+     * Note that if we received an optimistic context token from SPNEGO,
+     * then we will call QMD after ISC, rather than before. Mechanisms
+     * must be prepared to handle this and must not assume the context
+     * will be NULL on entry.
+     */
+    HEIM_TAILQ_FOREACH_SAFE(p, &ctx->negoex_mechs, links, next) {
+       if (opt != NULL && memcmp(opt->scheme, p->scheme, GUID_LENGTH) == 0)
+           p->mech_context = opt->gssctx;;
+
+       major = gssspi_query_meta_data(&minor, p->oid, cred, &p->mech_context,
+                                      ctx->target_name, req_flags, &p->metadata);
+       /* GSS_Query_meta_data failure removes mechanism from list. */
+       if (major != GSS_S_COMPLETE)
+           _gss_negoex_delete_auth_mech(ctx, p);
+    }
+}
+
+static void
+exchange_meta_data(gssspnego_ctx ctx,
+                  gss_cred_id_t cred,
+                  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->flags.local ? ACCEPTOR_META_DATA : INITIATOR_META_DATA;
+
+    for (i = 0; i < nmessages; i++) {
+       if (messages[i].type != type)
+           continue;
+       msg = &messages[i].u.e;
+
+       mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
+       if (mech == NULL)
+           continue;
+
+       major = gssspi_exchange_meta_data(&minor, mech->oid, cred,
+                                         &mech->mech_context,
+                                         ctx->target_name,
+                                         req_flags, &msg->token);
+       /* GSS_Exchange_meta_data failure removes mechanism from list. */
+       if (major != GSS_S_COMPLETE)
+           _gss_negoex_delete_auth_mech(ctx, mech);
+    }
+}
+
+static void
+release_mech_crypto(struct negoex_auth_mech *mech)
+{
+    krb5_context context = NULL;
+
+    if (mech->crypto || mech->verify_crypto)
+       context = _gss_mg_krb5_context();
+
+    if (mech->crypto) {
+       krb5_crypto_destroy(context, mech->crypto);
+       mech->crypto = NULL;
+    }
+
+    if (mech->verify_crypto) {
+       krb5_crypto_destroy(context, mech->verify_crypto);
+       mech->verify_crypto = NULL;
+    }
+
+    mech->sent_checksum = FALSE;
+}
+
+/*
+ * 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(gssspnego_ctx ctx,
+                       struct negoex_message *messages,
+                       size_t nmessages)
+{
+    struct negoex_auth_mech *mech;
+    OM_uint32 tmpMinor;
+
+    heim_assert(ctx->flags.local && ctx->negoex_step == 2,
+               "NegoEx optimistic result should only be checked in second leg");
+
+    /* Do nothing if we didn't make an optimistic context. */
+    mech = HEIM_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 (_gss_negoex_locate_exchange_message(messages, nmessages,
+                                           CHALLENGE) != NULL ||
+       _gss_negoex_locate_verify_message(messages, nmessages) != NULL) {
+       /*
+        * The acceptor continued the optimistic mech, and metadata exchange
+        * didn't remove it. Commit to this mechanism.
+        */
+       _gss_negoex_select_auth_mech(ctx, mech);
+    } else {
+       /*
+        * The acceptor ignored the optimistic token. Restart the mech.
+        */
+       gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER);
+       release_mech_crypto(mech);
+       mech->complete = FALSE;
+    }
+}
+
+/* Perform an initiator step of the underlying mechanism exchange. */
+static OM_uint32
+mech_init(OM_uint32 *minor,
+         struct gssspnego_optimistic_ctx *opt,
+         gssspnego_ctx ctx,
+         gss_cred_id_t cred,
+         OM_uint32 req_flags,
+         OM_uint32 time_req,
+         const gss_channel_bindings_t input_chan_bindings,
+         struct negoex_message *messages,
+         size_t nmessages,
+         gss_buffer_t output_token,
+         int *mech_error)
+{
+    OM_uint32 major, first_major = GSS_S_COMPLETE, 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;
+    krb5_context context = _gss_mg_krb5_context();
+
+    output_token->value = NULL;
+    output_token->length = 0;
+
+    *mech_error = FALSE;
+
+    /* Allow disabling of optimistic token for testing. */
+    if (ctx->negoex_step == 1 &&
+       secure_getenv("NEGOEX_NO_OPTIMISTIC_TOKEN") != NULL)
+       return GSS_S_COMPLETE;
+
+    if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
+       *minor = (OM_uint32)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 = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
+    msg = _gss_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;
+    major = GSS_S_BAD_MECH;
+
+    while (!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
+       mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
+
+       /*
+        * If SPNEGO generated an optimistic token when probing available
+        * mechanisms, we can reuse it here. This avoids a potentially
+        * expensive and redundant call to GSS_Init_sec_context();
+        */
+       if (opt != NULL && memcmp(opt->scheme, mech->scheme, GUID_LENGTH) == 0) {
+           heim_assert(ctx->negoex_step == 1,
+                       "SPNEGO optimistic token only valid for NegoEx first leg");
+
+           major = _gss_copy_buffer(minor, &opt->optimistic_token, output_token);
+           if (GSS_ERROR(major))
+               return major;
+
+           ctx->negotiated_mech_type = opt->negotiated_mech_type;
+           ctx->mech_flags = opt->optimistic_flags;
+           ctx->mech_time_rec = opt->optimistic_time_rec;
+
+           mech->mech_context = opt->gssctx;
+           opt->gssctx = NULL; /* steal it */
+
+           mech->complete = opt->complete;
+           major = GSS_S_COMPLETE;
+       } else {
+           major = gss_init_sec_context(minor, cred, &mech->mech_context,
+                                        ctx->target_name, mech->oid,
+                                        req_flags, time_req,
+                                        input_chan_bindings, input_token,
+                                        &ctx->negotiated_mech_type, output_token,
+                                        &ctx->mech_flags, &ctx->mech_time_rec);
+           if (major == GSS_S_COMPLETE)
+               mech->complete = 1;
+           else if (GSS_ERROR(major)) {
+               gss_mg_collect_error(mech->oid, major, *minor);
+               *mech_error = TRUE;
+           }
+       }
+       if (!GSS_ERROR(major))
+           return get_session_keys(minor, context, 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. */
+       _gss_negoex_delete_auth_mech(ctx, mech);
+       first_mech = FALSE;
+       input_token = GSS_C_NO_BUFFER;
+    }
+
+    if (HEIM_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,
+           gssspnego_ctx ctx,
+           gss_cred_id_t cred,
+           const gss_channel_bindings_t input_chan_bindings,
+           struct negoex_message *messages,
+           size_t nmessages,
+           gss_buffer_t output_token,
+           gss_cred_id_t *deleg_cred,
+           int *mech_error)
+{
+    OM_uint32 major, tmpMinor;
+    struct negoex_auth_mech *mech;
+    struct exchange_message *msg;
+    krb5_context context = _gss_mg_krb5_context();
+
+    heim_assert(!ctx->flags.local && !HEIM_TAILQ_EMPTY(&ctx->negoex_mechs),
+               "Acceptor NegoEx function called in wrong sequence");
+
+    *mech_error = FALSE;
+
+    msg = _gss_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 ||
+           HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->complete)
+           return GSS_S_COMPLETE;
+       *minor = (OM_uint32)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 = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
+       if (!GUID_EQ(msg->scheme, mech->scheme)) {
+           _gss_mg_log(10, "negoex ignored optimistic token as not for preferred mech");
+           return GSS_S_COMPLETE;
+       }
+    } else {
+       /* The initiator has selected a mech; discard other entries. */
+       mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
+       if (mech == NULL) {
+           *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS;
+           return GSS_S_FAILURE;
+       }
+       _gss_negoex_select_auth_mech(ctx, mech);
+    }
+
+    if (mech->complete)
+       return GSS_S_COMPLETE;
+
+    if (ctx->mech_src_name != GSS_C_NO_NAME)
+       gss_release_name(&tmpMinor, &ctx->mech_src_name);
+    if (deleg_cred && *deleg_cred != GSS_C_NO_CREDENTIAL)
+       gss_release_cred(&tmpMinor, deleg_cred);
+
+    major = gss_accept_sec_context(minor, &mech->mech_context, cred,
+                                  &msg->token, input_chan_bindings,
+                                  &ctx->mech_src_name, &ctx->negotiated_mech_type,
+                                  output_token, &ctx->mech_flags,
+                                  &ctx->mech_time_rec, deleg_cred);
+    if (major == GSS_S_COMPLETE)
+       mech->complete = 1;
+
+    if (!GSS_ERROR(major)) {
+       if (major == GSS_S_COMPLETE &&
+           !gss_oid_equal(ctx->negotiated_mech_type, mech->oid))
+           _gss_mg_log(1, "negoex client didn't send the mech they said they would");
+
+       major = get_session_keys(minor, context, mech);
+    } else if (ctx->negoex_step == 1) {
+       gss_mg_collect_error(ctx->negotiated_mech_type, major, *minor);
+       *mech_error = TRUE;
+
+       /* This was an optimistic token; pretend this never happened. */
+       major = GSS_S_COMPLETE;
+       *minor = 0;
+       gss_release_buffer(&tmpMinor, output_token);
+       gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER);
+    }
+
+    return major;
+}
+
+static krb5_keyusage
+verify_keyusage(gssspnego_ctx ctx, int make_checksum)
+{
+    /* Of course, these are the wrong way around in the spec. */
+    return (ctx->flags.local ^ !make_checksum) ?
+       NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM : NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM;
+}
+
+static OM_uint32
+verify_checksum(OM_uint32 *minor,
+               gssspnego_ctx ctx,
+               struct negoex_message *messages,
+               size_t nmessages,
+               gss_const_buffer_t input_token,
+               int *send_alert_out)
+{
+    krb5_error_code ret;
+    struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
+    struct verify_message *msg;
+    krb5_context context = _gss_mg_krb5_context();
+    krb5_crypto_iov iov[3];
+    krb5_keyusage usage = verify_keyusage(ctx, FALSE);
+
+    *send_alert_out = FALSE;
+    heim_assert(mech != NULL, "Invalid null mech when verifying NegoEx checksum");
+
+    /*
+     * 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 = _gss_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_crypto == NULL) {
+       *send_alert_out = TRUE;
+       return GSS_S_COMPLETE;
+    }
+
+    if (!krb5_checksum_is_keyed(context, msg->cksum_type)) {
+       *minor = (OM_uint32)NEGOEX_INVALID_CHECKSUM;
+       return GSS_S_BAD_SIG;
+    }
+
+    /*
+     * 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;
+    ret = krb5_storage_to_data(ctx->negoex_transcript, &iov[0].data);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_FAILURE;
+    }
+
+    iov[1].flags = KRB5_CRYPTO_TYPE_DATA;
+    iov[1].data.data = input_token->value;
+    iov[1].data.length = msg->offset_in_token;
+
+    iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
+    iov[2].data.data = (uint8_t *)msg->cksum;
+    iov[2].data.length = msg->cksum_len;
+
+    ret = krb5_verify_checksum_iov(context, mech->verify_crypto, usage,
+                                  iov, sizeof(iov) / sizeof(iov[0]), NULL);
+    if (ret == 0)
+       mech->verified_checksum = TRUE;
+    else
+       *minor = ret;
+
+    krb5_data_free(&iov[0].data);
+
+    return (ret == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE;
+}
+
+static OM_uint32
+make_checksum(OM_uint32 *minor, gssspnego_ctx ctx)
+{
+    krb5_error_code ret;
+    krb5_context context = _gss_mg_krb5_context();
+    krb5_data d;
+    krb5_keyusage usage = verify_keyusage(ctx, TRUE);
+    krb5_checksum cksum;
+    struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
+    OM_uint32 major;
+
+    heim_assert(mech != NULL, "Invalid null mech when making NegoEx checksum");
+
+    if (mech->crypto == NULL) {
+       if (mech->complete) {
+           *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY;
+           return GSS_S_UNAVAILABLE;
+       } else {
+           return GSS_S_COMPLETE;
+       }
+    }
+
+    ret = krb5_storage_to_data(ctx->negoex_transcript, &d);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_FAILURE;
+    }
+
+    ret = krb5_create_checksum(context, mech->crypto,
+                              usage, 0, d.data, d.length, &cksum);
+    krb5_data_free(&d);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_FAILURE;
+    }
+
+    major = _gss_negoex_add_verify_message(minor, ctx, mech->scheme,
+                                          cksum.cksumtype,
+                                          cksum.checksum.data,
+                                          cksum.checksum.length);
+    free_Checksum(&cksum);
+
+    if (major == GSS_S_COMPLETE)
+       mech->sent_checksum = TRUE;
+
+    return major;
+}
+
+/*
+ * 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(gssspnego_ctx ctx,
+              struct negoex_message *messages,
+              uint32_t nmessages)
+{
+    struct alert_message *msg;
+    struct negoex_auth_mech *mech;
+
+    msg = _gss_negoex_locate_alert_message(messages, nmessages);
+    if (msg != NULL && msg->verify_no_key) {
+       mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
+       if (mech != NULL)
+           release_mech_crypto(mech);
+    }
+}
+
+static OM_uint32
+make_output_token(OM_uint32 *minor,
+                 gssspnego_ctx ctx,
+                 gss_buffer_t mech_output_token,
+                 int send_alert,
+                 gss_buffer_t output_token)
+{
+    OM_uint32 major, tmpMinor;
+    struct negoex_auth_mech *mech;
+    enum message_type type;
+    off_t old_transcript_len;
+
+    output_token->length = 0;
+    output_token->value = NULL;
+
+    old_transcript_len = krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR);
+
+    /*
+     * 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 &&
+       HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->sent_checksum)
+       return GSS_S_COMPLETE;
+
+    if (ctx->negoex_step == 1) {
+       if (ctx->flags.local)
+           major = emit_initiator_nego(minor, ctx);
+       else
+           major = emit_acceptor_nego(minor, ctx);
+       if (major != GSS_S_COMPLETE)
+           return major;
+
+       type = ctx->flags.local ? INITIATOR_META_DATA : ACCEPTOR_META_DATA;
+       HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
+           if (mech->metadata.length > 0) {
+               major = _gss_negoex_add_exchange_message(minor, ctx,
+                                                        type, mech->scheme,
+                                                        &mech->metadata);
+               if (major != GSS_S_COMPLETE)
+                   return major;
+           }
+       }
+    }
+
+    mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
+
+    if (mech_output_token->length > 0) {
+       type = ctx->flags.local ? AP_REQUEST : CHALLENGE;
+       major = _gss_negoex_add_exchange_message(minor, ctx,
+                                                type, mech->scheme,
+                                                mech_output_token);
+       if (major != GSS_S_COMPLETE)
+           return major;
+    }
+
+    if (send_alert) {
+       major = _gss_negoex_add_verify_no_key_alert(minor, ctx, mech->scheme);
+       if (major != GSS_S_COMPLETE)
+           return major;
+    }
+
+    /* 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;
+    }
+
+    heim_assert(ctx->negoex_transcript != NULL, "NegoEx context uninitialized");
+
+    output_token->length =
+       krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR) - old_transcript_len;
+    output_token->value = malloc(output_token->length);
+    if (output_token->value == NULL) {
+       *minor = ENOMEM;
+       return GSS_S_FAILURE;
+    }
+
+    krb5_storage_seek(ctx->negoex_transcript, old_transcript_len, SEEK_SET);
+
+    if (krb5_storage_read(ctx->negoex_transcript,
+                         output_token->value,
+                         output_token->length) != output_token->length) {
+       *minor = ERANGE;
+       gss_release_buffer(&tmpMinor, output_token);
+       return GSS_S_FAILURE;
+    }
+
+    krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_END);
+
+    return GSS_S_COMPLETE;
+}
+
+OM_uint32
+_gss_negoex_init(OM_uint32 *minor,
+                struct gssspnego_optimistic_ctx *opt,
+                gssspnego_ctx ctx,
+                gss_cred_id_t cred,
+                OM_uint32 req_flags,
+                OM_uint32 time_req,
+                const gss_channel_bindings_t input_chan_bindings,
+                gss_const_buffer_t input_token,
+                gss_buffer_t output_token)
+{
+    OM_uint32 major, tmpMinor;
+    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, mech_error = FALSE;
+
+    if (ctx->negoex_step == 0 && input_token != GSS_C_NO_BUFFER &&
+       input_token->length != 0)
+       return GSS_S_DEFECTIVE_TOKEN;
+
+    major = _gss_negoex_begin(minor, ctx);
+    if (major != GSS_S_COMPLETE)
+       goto cleanup;
+
+    ctx->negoex_step++;
+
+    if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) {
+       major = _gss_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. */
+       krb5_generate_random_block(ctx->negoex_conv_id, GUID_LENGTH);
+
+       /* Query each mech for its metadata (this may prune the mech list). */
+       query_meta_data(ctx, opt, cred, 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, 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, opt, ctx, cred, req_flags, time_req,
+                     input_chan_bindings, messages, nmessages,
+                     &mech_output_token, &mech_error);
+    if (major != GSS_S_COMPLETE)
+       goto cleanup;
+    heim_assert(!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs),
+               "Invalid empty NegoEx mechanism list");
+
+    /*
+     * 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)
+       _gss_negoex_select_auth_mech(ctx, HEIM_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) {
+       if (krb5_storage_write(ctx->negoex_transcript,
+                              input_token->value,
+                              input_token->length) != input_token->length) {
+           major = GSS_S_FAILURE;
+           *minor = ENOMEM;
+           goto cleanup;
+       }
+    }
+
+    major = make_output_token(minor, ctx, &mech_output_token, send_alert,
+                             output_token);
+    if (major != GSS_S_COMPLETE)
+       goto cleanup;
+
+    mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
+    major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
+       GSS_S_CONTINUE_NEEDED;
+
+cleanup:
+    free(messages);
+    gss_release_buffer(&tmpMinor, &mech_output_token);
+    _gss_negoex_end(ctx);
+
+    if (GSS_ERROR(major)) {
+       if (!mech_error) {
+          krb5_context context = _gss_mg_krb5_context();
+
+          gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                  major, *minor,
+                                  "NegoEx failed to initialize security context: %s",
+                                  krb5_get_error_message(context, *minor));
+       }
+
+       _gss_negoex_release_context(ctx);
+    }
+
+    return major;
+}
+
+OM_uint32
+_gss_negoex_accept(OM_uint32 *minor,
+                  gssspnego_ctx ctx,
+                  gss_cred_id_t cred,
+                  gss_const_buffer_t input_token,
+                  const gss_channel_bindings_t input_chan_bindings,
+                  gss_buffer_t output_token,
+                  gss_cred_id_t *deleg_cred)
+{
+    OM_uint32 major, tmpMinor;
+    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, mech_error = FALSE;
+
+    if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) {
+       major = GSS_S_DEFECTIVE_TOKEN;
+       goto cleanup;
+    }
+
+    major = _gss_negoex_begin(minor, ctx);
+    if (major != GSS_S_COMPLETE)
+       goto cleanup;
+
+    ctx->negoex_step++;
+
+    major = _gss_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, 0, messages, nmessages);
+       query_meta_data(ctx, NULL, cred, 0);
+
+       if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
+           *minor = (OM_uint32)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, input_chan_bindings,
+                       messages, nmessages, &mech_output_token,
+                       deleg_cred, &mech_error);
+    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;
+    }
+
+    if (krb5_storage_write(ctx->negoex_transcript,
+                          input_token->value,
+                          input_token->length) != input_token->length) {
+       major = GSS_S_FAILURE;
+       *minor = ENOMEM;
+       goto cleanup;
+    }
+
+    major = make_output_token(minor, ctx, &mech_output_token, send_alert,
+                             output_token);
+    if (major != GSS_S_COMPLETE)
+       goto cleanup;
+
+    mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
+    major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
+       GSS_S_CONTINUE_NEEDED;
+
+cleanup:
+    free(messages);
+    gss_release_buffer(&tmpMinor, &mech_output_token);
+    _gss_negoex_end(ctx);
+
+    if (GSS_ERROR(major)) {
+       if (!mech_error) {
+          krb5_context context = _gss_mg_krb5_context();
+
+          gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
+                                  major, *minor,
+                                  "NegoEx failed to accept security context: %s",
+                                  krb5_get_error_message(context, *minor));
+       }
+
+       _gss_negoex_release_context(ctx);
+    }
+
+    return major;
+}
diff --git a/lib/gssapi/spnego/negoex_err.et b/lib/gssapi/spnego/negoex_err.et
new file mode 100644 (file)
index 0000000..99a8a2e
--- /dev/null
@@ -0,0 +1,25 @@
+#
+# NegoEx error messages
+#
+
+id "$Id$"
+
+error_table ngex
+
+prefix NEGOEX
+
+error_code INVALID_MESSAGE_SIGNATURE, "Invalid NegoEx signature"
+error_code INVALID_MESSAGE_TYPE, "Invalid NegoEx message type"
+error_code INVALID_MESSAGE_SIZE, "Invalid NegoEx message size"
+error_code INVALID_CONVERSATION_ID, "Invalid NegoEx conversation ID"
+error_code AUTH_SCHEME_NOT_FOUND, "NegoEx authentication scheme not found"
+error_code MISSING_NEGO_MESSAGE, "Missing NegoEx negotiate message"
+error_code MISSING_AP_REQUEST_MESSAGE, "Missing NegoEx authentication protocol request message"
+error_code NO_AVAILABLE_MECHS, "No mutually supported NegoEx authentication schemes"
+error_code NO_VERIFY_KEY, "No NegoEx verify key"
+error_code UNKNOWN_CHECKSUM_SCHEME, "Unknown NegoEx checksum scheme"
+error_code INVALID_CHECKSUM, "Invalid NegoEx checksum"
+error_code UNSUPPORTED_CRITICAL_EXTENSION, "Unsupported critical NegoEx extension"
+error_code UNSUPPORTED_VERSION, "Unsupported NegoEx version"
+error_code MESSAGE_OUT_OF_SEQUENCE, "NegoEx message out of sequence"
+
diff --git a/lib/gssapi/spnego/negoex_locl.h b/lib/gssapi/spnego/negoex_locl.h
new file mode 100644 (file)
index 0000000..3e0d29a
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2011-2019 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.
+ */
+
+#ifndef NEGOEX_LOCL_H
+#define NEGOEX_LOCL_H
+
+#include <negoex_err.h>
+
+struct gssspnego_ctx_desc;
+
+#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 {
+    HEIM_TAILQ_ENTRY(negoex_auth_mech) links;
+    gss_OID oid;
+    auth_scheme scheme;
+    gss_ctx_id_t mech_context;
+    gss_buffer_desc metadata;
+    krb5_crypto crypto;
+    krb5_crypto verify_crypto;
+    int complete;
+    int sent_checksum;
+    int verified_checksum;
+};
+
+#define NEGOEX_LOG_LEVEL                   10
+
+#endif /* NEGOEX_LOCL_H */
diff --git a/lib/gssapi/spnego/negoex_util.c b/lib/gssapi/spnego/negoex_util.c
new file mode 100644 (file)
index 0000000..5886f41
--- /dev/null
@@ -0,0 +1,1059 @@
+/*
+ * Copyright (C) 2011-2019 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 "spnego_locl.h"
+
+static void
+release_auth_mech(krb5_context context, struct negoex_auth_mech *mech);
+
+/*
+ * SPNEGO expects to find the active mech context in ctx->negotiated_ctx_id,
+ * 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->negotiated_ctx_id at the end of NegoEx processing.
+ */
+void
+_gss_negoex_end(gssspnego_ctx ctx)
+{
+    struct negoex_auth_mech *mech;
+
+    mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
+    if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT)
+       return;
+
+    heim_assert(ctx->negotiated_ctx_id == GSS_C_NO_CONTEXT,
+               "SPNEGO/NegoEx context mismatch");
+    ctx->negotiated_ctx_id = mech->mech_context;
+    mech->mech_context = GSS_C_NO_CONTEXT;
+}
+
+OM_uint32
+_gss_negoex_begin(OM_uint32 *minor, gssspnego_ctx ctx)
+{
+    struct negoex_auth_mech *mech;
+
+    if (ctx->negoex_transcript != NULL) {
+       /*
+        * The context is already initialized for NegoEx; undo what
+        * _gss_negoex_end() did, if applicable.
+        */
+       if (ctx->negotiated_ctx_id != GSS_C_NO_CONTEXT) {
+           mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
+           heim_assert(mech != NULL && mech->mech_context == GSS_C_NO_CONTEXT,
+                       "NegoEx/SPNEGO context mismatch");
+           mech->mech_context = ctx->negotiated_ctx_id;
+           ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT;
+       }
+       return GSS_S_COMPLETE;
+    }
+
+    ctx->negoex_transcript = krb5_storage_emem();
+    if (ctx->negoex_transcript == NULL) {
+       *minor = ENOMEM;
+       return GSS_S_FAILURE;
+    }
+
+    krb5_storage_set_byteorder(ctx->negoex_transcript,
+                              KRB5_STORAGE_BYTEORDER_LE);
+
+    return GSS_S_COMPLETE;
+}
+
+static void
+release_all_mechs(gssspnego_ctx ctx, krb5_context context)
+{
+    struct negoex_auth_mech *mech, *next;
+
+    HEIM_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next) {
+       release_auth_mech(context, mech);
+    }
+
+    HEIM_TAILQ_INIT(&ctx->negoex_mechs);
+}
+
+void
+_gss_negoex_release_context(gssspnego_ctx ctx)
+{
+    krb5_context context = _gss_mg_krb5_context();
+
+    if (ctx->negoex_transcript != NULL) {
+       krb5_storage_free(ctx->negoex_transcript);
+       ctx->negoex_transcript = NULL;
+    }
+
+    release_all_mechs(ctx, context);
+}
+
+static int
+guid_to_string(const uint8_t guid[16], char *buffer, size_t bufsiz)
+{
+    uint32_t data1;
+    uint16_t data2, data3;
+
+    _gss_mg_decode_le_uint32(&guid[0], &data1);
+    _gss_mg_decode_le_uint16(&guid[4], &data2);
+    _gss_mg_decode_le_uint16(&guid[6], &data3);
+
+    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]);
+}
+
+void
+_gss_negoex_log_auth_scheme(int initiator,
+                           int index,
+                           const auth_scheme scheme)
+{
+    char scheme_str[37];
+
+    guid_to_string(scheme, scheme_str, sizeof(scheme_str));
+
+    _gss_mg_log(NEGOEX_LOG_LEVEL,
+               "negoex: %s authentication scheme %d %s",
+               initiator ? "proposing" : "received", index, scheme_str);
+}
+
+void
+_gss_negoex_log_message(int direction,
+                       enum message_type type,
+                       const conversation_id conv_id,
+                       unsigned int seqnum,
+                       unsigned int header_len,
+                       unsigned int msg_len)
+{
+    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));
+    _gss_mg_log(NEGOEX_LOG_LEVEL,
+               "negoex: %s (%d)%s conversation %s",
+               direction ? "received" : "sending",
+               seqnum, typestr, conv_str);
+}
+
+/*
+ * 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;
+}
+
+static OM_uint32
+parse_nego_message(OM_uint32 *minor, krb5_storage *sp,
+                  const uint8_t *msg_base, size_t msg_len,
+                  struct nego_message *msg)
+{
+    krb5_error_code ret;
+    const uint8_t *p;
+    uint64_t protocol_version;
+    uint32_t extension_type, offset;
+    uint16_t count;
+    size_t i;
+
+    if (krb5_storage_read(sp, msg->random,
+                         sizeof(msg->random)) != sizeof(msg->random)) {
+       *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    ret = krb5_ret_uint64(sp, &protocol_version);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    if (protocol_version != 0) {
+       *minor = (OM_uint32)NEGOEX_UNSUPPORTED_VERSION;
+       return GSS_S_UNAVAILABLE;
+    }
+
+    ret = krb5_ret_uint32(sp, &offset);
+    if (ret == 0)
+       ret = krb5_ret_uint16(sp, &count);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    msg->schemes = vector_base(offset, count, GUID_LENGTH, msg_base, msg_len);
+    msg->nschemes = count;
+    if (msg->schemes == NULL) {
+       *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    ret = krb5_ret_uint32(sp, &offset);
+    if (ret == 0)
+       ret = krb5_ret_uint16(sp, &count);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+    p = vector_base(offset, count, EXTENSION_LENGTH, msg_base, msg_len);
+    for (i = 0; i < count; i++) {
+       _gss_mg_decode_le_uint32(p + i * EXTENSION_LENGTH, &extension_type);
+       if (extension_type & EXTENSION_FLAG_CRITICAL) {
+           *minor = (OM_uint32)NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION;
+           return GSS_S_UNAVAILABLE;
+       }
+    }
+
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+parse_exchange_message(OM_uint32 *minor, krb5_storage *sp,
+                      const uint8_t *msg_base, size_t msg_len,
+                      struct exchange_message *msg)
+{
+    krb5_error_code ret;
+    const uint8_t *p;
+    uint32_t offset;
+    uint16_t len;
+
+    if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) != GUID_LENGTH) {
+       *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    ret = krb5_ret_uint32(sp, &offset);
+    if (ret == 0)
+       ret = krb5_ret_uint16(sp, &len);
+    if (ret) {
+       *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    p = vector_base(offset, len, 1, msg_base, msg_len);
+    if (p == NULL) {
+       *minor = (OM_uint32)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, krb5_storage *sp,
+                    const uint8_t *msg_base, size_t msg_len,
+                    size_t token_offset, struct verify_message *msg)
+{
+    krb5_error_code ret;
+    uint32_t hdrlen, cksum_scheme;
+    uint32_t offset, len;
+
+    if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) == GUID_LENGTH)
+       ret = 0;
+    else
+       ret = NEGOEX_INVALID_MESSAGE_SIZE;
+    if (ret == 0)
+       ret = krb5_ret_uint32(sp, &hdrlen);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    if (hdrlen != CHECKSUM_HEADER_LENGTH) {
+       *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    ret = krb5_ret_uint32(sp, &cksum_scheme);
+    if (ret == 0)
+       ret = krb5_ret_uint32(sp, &msg->cksum_type);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    if (cksum_scheme != CHECKSUM_SCHEME_RFC3961) {
+       *minor = (OM_uint32)NEGOEX_UNKNOWN_CHECKSUM_SCHEME;
+       return GSS_S_UNAVAILABLE;
+    }
+
+    ret = krb5_ret_uint32(sp, &offset);
+    if (ret == 0)
+       ret = krb5_ret_uint32(sp, &len);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    msg->cksum = vector_base(offset, len, 1, msg_base, msg_len);
+    msg->cksum_len = len;
+    if (msg->cksum == NULL) {
+       *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    msg->offset_in_token = token_offset;
+    return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+storage_from_memory(OM_uint32 *minor,
+                   const uint8_t *data,
+                   size_t length,
+                   krb5_storage **sp)
+{
+    *sp = krb5_storage_from_readonly_mem(data, length);
+    if (sp == NULL) {
+       *minor = ENOMEM;
+       return GSS_S_FAILURE;
+    }
+
+    krb5_storage_set_byteorder(*sp, KRB5_STORAGE_BYTEORDER_LE);
+    krb5_storage_set_eof_code(*sp, NEGOEX_INVALID_MESSAGE_SIZE);
+
+    return 0;
+}
+
+static OM_uint32
+parse_alert_message(OM_uint32 *minor, krb5_storage *sp,
+                   const uint8_t *msg_base, size_t msg_len,
+                   struct alert_message *msg)
+{
+    OM_uint32 major;
+    krb5_error_code ret;
+    const uint8_t *p;
+    uint32_t error_code, atype;
+    uint32_t alerts_offset, nalerts, value_offset, value_len;
+    size_t i;
+    krb5_storage *alerts;
+
+    if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) == GUID_LENGTH)
+       ret = 0;
+    else
+       ret = NEGOEX_INVALID_MESSAGE_SIZE;
+    if (ret == 0)
+       ret = krb5_ret_uint32(sp, &error_code);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    ret = krb5_ret_uint32(sp, &alerts_offset);
+    if (ret == 0)
+       ret = krb5_ret_uint32(sp, &nalerts);
+    if (ret) {
+       *minor = ret;
+       return GSS_S_DEFECTIVE_TOKEN;
+    }
+
+    p = vector_base(alerts_offset, nalerts, ALERT_LENGTH, msg_base, msg_len);
+    if (p == NULL) {
+       *minor = (OM_uint32)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;
+
+    major = storage_from_memory(minor, p, nalerts * ALERT_LENGTH, &alerts);
+    if (major != GSS_S_COMPLETE)
+       return major;
+
+    for (i = 0; i < nalerts; i++) {
+       ret = krb5_ret_uint32(alerts, &atype);
+       if (ret == 0)
+           ret = krb5_ret_uint32(alerts, &value_offset);
+       if (ret == 0)
+           ret = krb5_ret_uint32(alerts, &value_len);
+       if (ret) {
+           *minor = ret;
+           major = GSS_S_DEFECTIVE_TOKEN;
+           break;
+       }
+
+       p = vector_base(value_offset, value_len, 1, msg_base, msg_len);
+       if (p == NULL) {
+           *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
+           major = GSS_S_DEFECTIVE_TOKEN;
+           break;
+       }
+
+       if (atype == ALERT_TYPE_PULSE && value_len >= ALERT_PULSE_LENGTH) {
+           krb5_storage *pulse;
+           uint32_t hdrlen, reason;
+
+           major = storage_from_memory(minor, p, value_len, &pulse);
+           if (major != GSS_S_COMPLETE)
+               break;
+
+           ret = krb5_ret_uint32(pulse, &hdrlen);
+           if (ret == 0)
+               ret = krb5_ret_uint32(pulse, &reason);
+           krb5_storage_free(pulse);
+           if (ret) {
+               *minor = ret;
+               major = GSS_S_DEFECTIVE_TOKEN;
+               break;
+           }
+
+           if (reason == ALERT_VERIFY_NO_KEY)
+               msg->verify_no_key = TRUE;
+       }
+    }
+
+    krb5_storage_free(alerts);
+
+    return major;
+}
+
+static OM_uint32
+parse_message(OM_uint32 *minor,
+             gssspnego_ctx ctx,
+             gss_const_buffer_t token,
+             size_t *token_offset,
+             struct negoex_message *msg)
+{
+    OM_uint32 major;
+    krb5_error_code ret;
+    krb5_storage *sp;
+    uint64_t signature;
+    uint32_t header_len, msg_len;
+    uint32_t type, seqnum;
+    conversation_id conv_id;
+    size_t token_remaining = token->length - *token_offset;
+    const uint8_t *msg_base = (uint8_t *)token->value + *token_offset;
+
+    major = storage_from_memory(minor, msg_base, token_remaining, &sp);
+    if (major != GSS_S_COMPLETE)
+       return major;
+
+    major = GSS_S_DEFECTIVE_TOKEN;
+
+    ret = krb5_ret_uint64(sp, &signature);
+    if (ret == 0)
+       ret = krb5_ret_uint32(sp, &type);
+    if (ret == 0)
+       ret = krb5_ret_uint32(sp, &seqnum);
+    if (ret == 0)
+       ret = krb5_ret_uint32(sp, &header_len);
+    if (ret == 0)
+       ret = krb5_ret_uint32(sp, &msg_len);
+    if (ret == 0) {
+       if (krb5_storage_read(sp, conv_id, GUID_LENGTH) != GUID_LENGTH)
+           ret = NEGOEX_INVALID_MESSAGE_SIZE;
+    }
+    if (ret) {
+       *minor = ret;
+       goto cleanup;
+    }
+
+    if (msg_len > token_remaining || header_len > msg_len) {
+       *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
+       goto cleanup;
+    }
+    if (signature != MESSAGE_SIGNATURE) {
+       *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIGNATURE;
+       goto cleanup;
+    }
+    if (seqnum != ctx->negoex_seqnum) {
+       *minor = (OM_uint32)NEGOEX_MESSAGE_OUT_OF_SEQUENCE;
+       goto cleanup;
+    }
+    if (seqnum == 0) {
+       memcpy(ctx->negoex_conv_id, conv_id, GUID_LENGTH);
+    } else if (!GUID_EQ(conv_id, ctx->negoex_conv_id)) {
+       *minor = (OM_uint32)NEGOEX_INVALID_CONVERSATION_ID;
+       goto cleanup;
+    }
+
+    krb5_storage_truncate(sp, msg_len);
+
+    msg->type = type;
+    if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO) {
+       major = parse_nego_message(minor, sp, 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, sp, msg_base, msg_len,
+                                      &msg->u.e);
+    } else if (type == VERIFY) {
+       major = parse_verify_message(minor, sp, msg_base, msg_len,
+                                    msg_base - (uint8_t *)token->value,
+                                    &msg->u.v);
+    } else if (type == ALERT) {
+       major = parse_alert_message(minor, sp, msg_base, msg_len, &msg->u.a);
+    } else {
+       *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_TYPE;
+       goto cleanup;
+    }
+
+cleanup:
+    krb5_storage_free(sp);
+
+    if (major == GSS_S_COMPLETE) {
+       _gss_negoex_log_message(1, msg->type,
+                               ctx->negoex_conv_id, ctx->negoex_seqnum,
+                               header_len, msg_len);
+       ctx->negoex_seqnum++;
+       *token_offset += msg_len;
+    }
+
+    return major;
+}
+
+/*
+ * 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
+_gss_negoex_parse_token(OM_uint32 *minor,
+                       gssspnego_ctx ctx,
+                       gss_const_buffer_t token,
+                       struct negoex_message **messages_out,
+                       size_t *count_out)
+{
+    OM_uint32 major = GSS_S_DEFECTIVE_TOKEN;
+    size_t count = 0;
+    size_t token_offset = 0;
+    struct negoex_message *messages = NULL, *newptr;
+
+    *messages_out = NULL;
+    *count_out = 0;
+    heim_assert(token != GSS_C_NO_BUFFER, "Invalid null NegoEx input token");
+
+    while (token_offset < token->length) {
+       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, token, &token_offset,
+                             &messages[count]);
+       if (major != GSS_S_COMPLETE)
+           break;
+
+       count++;
+    }
+
+    if (token_offset != token->length) {
+       *minor = (OM_uint32)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 *
+_gss_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 *
+_gss_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 *
+_gss_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 *
+_gss_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;
+}
+
+#define CHECK(ret, x) do { (ret) = (x); if (ret) goto fail; } while (0)
+
+static krb5_error_code
+store_bytes(krb5_storage *sp, const void *bytes, size_t length)
+{
+    ssize_t ssize;
+
+    ssize = krb5_storage_write(sp, bytes, length);
+    if (ssize != length)
+       return ENOMEM;
+
+    return 0;
+}
+
+/*
+ * 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 OM_uint32
+put_message_header(OM_uint32 *minor, gssspnego_ctx ctx,
+                  enum message_type type, uint32_t payload_len,
+                  uint32_t *payload_start_out)
+{
+    krb5_error_code ret;
+    size_t header_len = 0;
+
+    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
+       heim_assert(0, "Invalid NegoEx message type");
+
+    /* Signature */
+    CHECK(ret, krb5_store_uint64(ctx->negoex_transcript, MESSAGE_SIGNATURE));
+    /* MessageType */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, type));
+    /* SequenceNum */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ctx->negoex_seqnum));
+    /* cbHeaderLength */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, header_len));
+    /* cbMessageLength */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, header_len + payload_len));
+    /* ConversationId */
+    CHECK(ret, store_bytes(ctx->negoex_transcript, ctx->negoex_conv_id, GUID_LENGTH));
+
+    _gss_negoex_log_message(0, type,
+                           ctx->negoex_conv_id, ctx->negoex_seqnum,
+                           header_len,
+                           header_len + payload_len);
+
+    ctx->negoex_seqnum++;
+
+    *payload_start_out = header_len;
+    return GSS_S_COMPLETE;
+
+fail:
+    *minor = ret;
+    return GSS_S_FAILURE;
+}
+
+OM_uint32
+_gss_negoex_add_nego_message(OM_uint32 *minor,
+                            gssspnego_ctx ctx,
+                            enum message_type type,
+                            uint8_t random[32])
+{
+    OM_uint32 major;
+    krb5_error_code ret;
+    struct negoex_auth_mech *mech;
+    uint32_t payload_start;
+    uint16_t nschemes;
+
+    nschemes = 0;
+    HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
+       nschemes++;
+
+    major = put_message_header(minor, ctx, type,
+                              nschemes * GUID_LENGTH, &payload_start);
+    if (major != GSS_S_COMPLETE)
+       return major;
+
+    CHECK(ret, store_bytes(ctx->negoex_transcript, random, 32));
+    /* ProtocolVersion */
+    CHECK(ret, krb5_store_uint64(ctx->negoex_transcript, 0));
+    /* AuthSchemes vector */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start));
+    CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, nschemes));
+    /* Extensions vector */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start));
+    CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, 0));
+    /* Four bytes of padding to reach a multiple of 8 bytes. */
+    CHECK(ret, store_bytes(ctx->negoex_transcript, "\0\0\0\0", 4));
+
+    /* Payload (auth schemes) */
+    HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
+       CHECK(ret, store_bytes(ctx->negoex_transcript, mech->scheme, GUID_LENGTH));
+    }
+
+    return GSS_S_COMPLETE;
+
+fail:
+    *minor = ret;
+    return GSS_S_FAILURE;
+}
+
+OM_uint32
+_gss_negoex_add_exchange_message(OM_uint32 *minor,
+                                gssspnego_ctx ctx,
+                                enum message_type type,
+                                const auth_scheme scheme,
+                                gss_buffer_t token)
+{
+    OM_uint32 major;
+    krb5_error_code ret;
+    uint32_t payload_start;
+
+    major = put_message_header(minor, ctx, type, token->length, &payload_start);
+    if (major != GSS_S_COMPLETE)
+       return major;
+
+    CHECK(ret, store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH));
+    /* Exchange byte vector */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start));
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, token->length));
+    /* Payload (token) */
+    CHECK(ret, store_bytes(ctx->negoex_transcript, token->value, token->length));
+
+    return GSS_S_COMPLETE;
+
+fail:
+    *minor = ret;
+    return GSS_S_FAILURE;
+}
+
+OM_uint32
+_gss_negoex_add_verify_message(OM_uint32 *minor,
+                              gssspnego_ctx ctx,
+                              const auth_scheme scheme,
+                              uint32_t cksum_type,
+                              const uint8_t *cksum,
+                              uint32_t cksum_len)
+{
+    OM_uint32 major;
+    krb5_error_code ret;
+    uint32_t payload_start;
+
+    major = put_message_header(minor, ctx, VERIFY, cksum_len, &payload_start);
+    if (major != GSS_S_COMPLETE)
+       return major;
+
+    CHECK(ret, store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH));
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, CHECKSUM_HEADER_LENGTH));
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, CHECKSUM_SCHEME_RFC3961));
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, cksum_type));
+    /* ChecksumValue vector */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start));
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, cksum_len));
+    /* Four bytes of padding to reach a multiple of 8 bytes. */
+    CHECK(ret, store_bytes(ctx->negoex_transcript, "\0\0\0\0", 4));
+    /* Payload (checksum contents) */
+    CHECK(ret, store_bytes(ctx->negoex_transcript, cksum, cksum_len));
+
+    return GSS_S_COMPLETE;
+
+fail:
+    *minor = ret;
+    return GSS_S_FAILURE;
+}
+
+/*
+ * Add an ALERT_MESSAGE containing a single ALERT_TYPE_PULSE alert with the
+ * reason ALERT_VERIFY_NO_KEY.
+ */
+OM_uint32
+_gss_negoex_add_verify_no_key_alert(OM_uint32 *minor,
+                                   gssspnego_ctx ctx,
+                                   const auth_scheme scheme)
+{
+    OM_uint32 major;
+    krb5_error_code ret;
+    uint32_t payload_start;
+
+    major = put_message_header(minor, ctx,
+                              ALERT, ALERT_LENGTH + ALERT_PULSE_LENGTH,
+                              &payload_start);
+    if (major != GSS_S_COMPLETE)
+       return major;
+
+    CHECK(ret, store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH));
+    /* ErrorCode */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, 0));
+    /* Alerts vector */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start));
+    CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, 1));
+    /* Six bytes of padding to reach a multiple of 8 bytes. */
+    CHECK(ret, store_bytes(ctx->negoex_transcript, "\0\0\0\0\0\0", 6));
+    /* Payload part 1: a single ALERT element */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_TYPE_PULSE));
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript,
+                                payload_start + ALERT_LENGTH));
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_PULSE_LENGTH));
+    /* Payload part 2: ALERT_PULSE */
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_PULSE_LENGTH));
+    CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_VERIFY_NO_KEY));
+
+    return GSS_S_COMPLETE;
+
+fail:
+    *minor = ret;
+    return GSS_S_FAILURE;
+}
+
+
+static void
+release_auth_mech(krb5_context context,
+                 struct negoex_auth_mech *mech)
+{
+    OM_uint32 tmpmin;
+
+    if (mech == NULL)
+       return;
+
+    gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL);
+    gss_release_oid(&tmpmin, &mech->oid);
+    gss_release_buffer(&tmpmin, &mech->metadata);
+    if (mech->crypto)
+       krb5_crypto_destroy(context, mech->crypto);
+    if (mech->verify_crypto)
+       krb5_crypto_destroy(context, mech->verify_crypto);
+
+    free(mech);
+}
+
+void
+_gss_negoex_delete_auth_mech(gssspnego_ctx ctx,
+                            struct negoex_auth_mech *mech)
+{
+    krb5_context context = _gss_mg_krb5_context();
+
+    HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
+    release_auth_mech(context, mech);
+}
+
+/* Remove all auth mech entries except for mech from ctx->mechs. */
+void
+_gss_negoex_select_auth_mech(gssspnego_ctx ctx,
+                            struct negoex_auth_mech *mech)
+{
+    krb5_context context = _gss_mg_krb5_context();
+
+    heim_assert(mech != NULL, "Invalid null NegoEx mech");
+    HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
+    release_all_mechs(ctx, context);
+    HEIM_TAILQ_INSERT_HEAD(&ctx->negoex_mechs, mech, links);
+}
+
+OM_uint32
+_gss_negoex_add_auth_mech(OM_uint32 *minor,
+                         gssspnego_ctx 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 = gss_duplicate_oid(minor, (gss_OID)oid, &mech->oid);
+    if (major != GSS_S_COMPLETE) {
+       free(mech);
+       return major;
+    }
+
+    memcpy(mech->scheme, scheme, GUID_LENGTH);
+
+    HEIM_TAILQ_INSERT_TAIL(&ctx->negoex_mechs, mech, links);
+
+    *minor = 0;
+    return GSS_S_COMPLETE;
+}
+
+struct negoex_auth_mech *
+_gss_negoex_locate_auth_scheme(gssspnego_ctx ctx,
+                              const auth_scheme scheme)
+{
+    struct negoex_auth_mech *mech;
+
+    HEIM_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
+_gss_negoex_common_auth_schemes(gssspnego_ctx ctx,
+                               const uint8_t *schemes,
+                               uint16_t nschemes)
+{
+    struct negoex_mech_list list;
+    struct negoex_auth_mech *mech;
+    uint16_t i;
+    krb5_context context = _gss_mg_krb5_context();
+
+    /* Construct a new list in the order of schemes. */
+    HEIM_TAILQ_INIT(&list);
+    for (i = 0; i < nschemes; i++) {
+       mech = _gss_negoex_locate_auth_scheme(ctx, schemes + i * GUID_LENGTH);
+       if (mech == NULL)
+           continue;
+       HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
+       HEIM_TAILQ_INSERT_TAIL(&list, mech, links);
+    }
+
+    /* Release any leftover entries and replace the context list. */
+    release_all_mechs(ctx, context);
+    HEIM_TAILQ_CONCAT(&ctx->negoex_mechs, &list, links);
+}
+
+/*
+ * Prune ctx->mechs to the schemes present in schemes, but do not change
+ * their order.
+ */
+void
+_gss_negoex_restrict_auth_schemes(gssspnego_ctx ctx,
+                                 const uint8_t *schemes,
+                                 uint16_t nschemes)
+{
+    struct negoex_auth_mech *mech, *next;
+    uint16_t i;
+    int found;
+
+    HEIM_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)
+           _gss_negoex_delete_auth_mech(ctx, mech);
+    }
+}
+
+/*
+ * Return the OID of the current NegoEx mechanism.
+ */
+struct negoex_auth_mech *
+_gss_negoex_negotiated_mech(gssspnego_ctx ctx)
+{
+    return HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
+}
+
+/*
+ * Returns TRUE if mechanism can be negotiated by both NegoEx and SPNEGO
+ */
+
+int
+_gss_negoex_and_spnego_mech_p(gss_const_OID mech)
+{
+    OM_uint32 major, minor;
+    gss_OID_set attrs = GSS_C_NO_OID_SET;
+    int negoex_and_spnego = FALSE;
+
+    major = gss_inquire_attrs_for_mech(&minor, mech, &attrs, NULL);
+    if (major == GSS_S_COMPLETE) {
+       gss_test_oid_set_member(&minor, GSS_C_MA_NEGOEX_AND_SPNEGO,
+                               attrs, &negoex_and_spnego);
+       gss_release_oid_set(&minor, &attrs);
+    }
+
+    return negoex_and_spnego;
+}
+
+int
+_gss_negoex_mech_p(gss_const_OID mech)
+{
+    OM_uint32 minor;
+    auth_scheme scheme;
+
+    return gssspi_query_mechanism_info(&minor, mech,
+                                      scheme) == GSS_S_COMPLETE;
+}
+
index 048e86bb43d5e20ba899381e87c62464cdb7156f..79ac038a02d0fe5040b1fafe3450c001c42feb90 100644 (file)
@@ -37,14 +37,17 @@ NegTokenInit ::= SEQUENCE {
     ...
 }
 
+NegResultEnum ::= ENUMERATED {
+    accept_completed(0),
+    accept_incomplete(1),
+    reject(2),
+    request-mic(3)
+}
+
 -- NB: negResult is not OPTIONAL in the new SPNEGO spec but
 -- Windows clients do not always send it
 NegTokenResp ::= SEQUENCE {
-    negResult      [0] ENUMERATED {
-                            accept_completed    (0),
-                            accept_incomplete   (1),
-                            reject              (2),
-                            request-mic         (3) }          OPTIONAL,
+    negResult      [0] NegResultEnum                           OPTIONAL,
     supportedMech  [1] MechType                                OPTIONAL,
     responseToken  [2] OCTET STRING                            OPTIONAL,
     mechListMIC    [3] OCTET STRING                            OPTIONAL,
index f8b69827a98371179be68e1eaa2953989c8b2dcd..9b0e3310f7bea7014baf66716117c3371bce85e4 100644 (file)
@@ -50,6 +50,7 @@
 #include <pthread.h>
 #endif
 
+#include <krb5.h>
 #include <gssapi.h>
 #include <gssapi_krb5.h>
 #include <gssapi_spnego.h>
 #endif
 
 #include <heim_threads.h>
+#include <heimqueue.h>
 #include <asn1_err.h>
 
 #include <gssapi_mech.h>
 
 #include "spnego_asn1.h"
+#include "negoex_locl.h"
 #include "utils.h"
 #include <der.h>
 
 
 #define ALLOC(X, N) (X) = calloc((N), sizeof(*(X)))
 
-typedef struct {
-       MechTypeList            initiator_mech_types;
+struct gssspnego_ctx_desc;
+typedef struct gssspnego_ctx_desc *gssspnego_ctx;
+
+typedef OM_uint32
+(*gssspnego_initiator_state)(OM_uint32 * minor_status,
+                            gss_const_cred_id_t cred,
+                            gssspnego_ctx ctx,
+                            gss_const_name_t name,
+                            gss_const_OID mech_type,
+                            OM_uint32 req_flags,
+                            OM_uint32 time_req,
+                            const gss_channel_bindings_t input_chan_bindings,
+                            gss_const_buffer_t input_token,
+                            gss_buffer_t output_token,
+                            OM_uint32 * ret_flags,
+                            OM_uint32 * time_rec);
+
+struct gssspnego_ctx_desc {
+       gss_buffer_desc         NegTokenInit_mech_types;
        gss_OID                 preferred_mech_type;
+       gss_OID                 selected_mech_type;
        gss_OID                 negotiated_mech_type;
        gss_ctx_id_t            negotiated_ctx_id;
        OM_uint32               mech_flags;
        OM_uint32               mech_time_rec;
        gss_name_t              mech_src_name;
-       unsigned int            open : 1;
-       unsigned int            local : 1;
-       unsigned int            require_mic : 1;
-       unsigned int            verified_mic : 1;
-       unsigned int            maybe_open : 1;
+       struct spnego_flags {
+           unsigned int                open : 1;
+           unsigned int                local : 1;
+           unsigned int                require_mic : 1;
+           unsigned int                peer_require_mic : 1;
+           unsigned int                sent_mic : 1;
+           unsigned int                verified_mic : 1;
+           unsigned int                safe_omit : 1;
+           unsigned int                maybe_open : 1;
+           unsigned int                seen_supported_mech : 1;
+       } flags;
        HEIMDAL_MUTEX           ctx_id_mutex;
 
        gss_name_t              target_name;
+       gssspnego_initiator_state   initiator_state;
 
-       u_char                  oidbuf[17];
-       size_t                  oidlen;
-
-} *gssspnego_ctx;
+       int                     negoex_step;
+       krb5_storage            *negoex_transcript;
+       uint32_t                negoex_seqnum;
+       conversation_id         negoex_conv_id;
+       HEIM_TAILQ_HEAD(negoex_mech_list, negoex_auth_mech) negoex_mechs;
+};
 
 extern gss_OID_desc _gss_spnego_mskrb_mechanism_oid_desc;
-extern gss_OID_desc _gss_spnego_krb5_mechanism_oid_desc;
+
+struct gssspnego_optimistic_ctx {
+    gssspnego_ctx spnegoctx;
+    OM_uint32 req_flags;
+    gss_name_t target_name;
+    OM_uint32 time_req;
+    gss_channel_bindings_t input_chan_bindings;
+    /* out */
+    gss_OID preferred_mech_type;
+    gss_OID negotiated_mech_type;
+    gss_buffer_desc optimistic_token;
+    OM_uint32 optimistic_flags, optimistic_time_rec;
+    gss_ctx_id_t gssctx;
+    int complete;
+    auth_scheme scheme;
+};
 
 #include "spnego-private.h"
 
index 7cbe73b8b4271c47aba90f5bc3f81740ecd3553b..ab0b346b7640b021fe39e7f05033642ee181d0f6 100644 (file)
@@ -72,6 +72,9 @@ static int help_flag  = 0;
 static krb5_context context;
 static krb5_enctype limit_enctype = 0;
 
+static gss_OID_desc test_negoex_1_mech = { 6, "\x69\x85\xa2\xc0\xac\x66" };
+static gss_OID_desc test_negoex_2_mech = { 6, "\x69\x84\xb0\xd1\xa8\x2c" };
+
 static struct {
     const char *name;
     gss_OID oid;
@@ -79,7 +82,9 @@ static struct {
     { "krb5", NULL /* GSS_KRB5_MECHANISM */ },
     { "spnego", NULL /* GSS_SPNEGO_MECHANISM */ },
     { "ntlm", NULL /* GSS_NTLM_MECHANISM */ },
-    { "sasl-digest-md5", NULL /* GSS_SASL_DIGEST_MD5_MECHANISM */ }
+    { "sasl-digest-md5", NULL /* GSS_SASL_DIGEST_MD5_MECHANISM */ },
+    { "test_negoex_1", NULL },
+    { "test_negoex_2", NULL },
 };
 
 static void
@@ -89,6 +94,8 @@ init_o2n(void)
     o2n[1].oid = GSS_SPNEGO_MECHANISM;
     o2n[2].oid = GSS_NTLM_MECHANISM;
     o2n[3].oid = GSS_SASL_DIGEST_MD5_MECHANISM;
+    o2n[4].oid = &test_negoex_1_mech;
+    o2n[5].oid = &test_negoex_2_mech;
 }
 
 static gss_OID
@@ -98,7 +105,7 @@ string_to_oid(const char *name)
     for (i = 0; i < sizeof(o2n)/sizeof(o2n[0]); i++)
        if (strcasecmp(name, o2n[i].name) == 0)
            return o2n[i].oid;
-    errx(1, "name '%s' not unknown", name);
+    errx(1, "name '%s' not known", name);
 }
 
 static void
@@ -612,7 +619,7 @@ main(int argc, char **argv)
     gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL, deleg_cred = GSS_C_NO_CREDENTIAL;
     gss_name_t cname = GSS_C_NO_NAME;
     gss_buffer_desc credential_data = GSS_C_EMPTY_BUFFER;
-    gss_OID_desc oids[4];
+    gss_OID_desc oids[6];
     gss_OID_set_desc mechoid_descs;
     gss_OID_set mechoids = GSS_C_NO_OID_SET;
     gss_key_value_element_desc client_cred_elements[2];
diff --git a/lib/gssapi/test_negoex_mech.c b/lib/gssapi/test_negoex_mech.c
new file mode 100644 (file)
index 0000000..f9d6c52
--- /dev/null
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <roken.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <krb5.h>
+#include <der.h>
+#include <gssapi_asn1.h>
+
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_spnego.h>
+
+struct test_context {
+    int initiator;
+    uint8_t hops;             /* hops remaining; 0 means established */
+};
+
+OM_uint32 GSSAPI_CALLCONV
+gss_init_sec_context(OM_uint32 *minor_status,
+                    gss_const_cred_id_t claimant_cred_handle,
+                    gss_ctx_id_t *context_handle, gss_const_name_t target_name,
+                    const gss_OID mech_type, OM_uint32 req_flags,
+                    OM_uint32 time_req,
+                    const gss_channel_bindings_t input_chan_bindings,
+                    const gss_buffer_t input_token, gss_OID *actual_mech,
+                    gss_buffer_t output_token, OM_uint32 *ret_flags,
+                    OM_uint32 *time_rec)
+{
+    struct test_context *ctx = (struct test_context *)*context_handle;
+    OM_uint32 major;
+    gss_buffer_desc tok;
+    const char *envstr;
+    uint8_t hops, mech_last_octet;
+
+    major = gss_duplicate_oid(minor_status, mech_type, actual_mech);
+    if (major != GSS_S_COMPLETE)
+       return major;
+
+    if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) {
+       envstr = getenv("HOPS");
+       hops = (envstr != NULL) ? atoi(envstr) : 1;
+       assert(hops > 0);
+    } else if (input_token->length == 4 &&
+              memcmp(input_token->value, "fail", 4) == 0) {
+       *minor_status = 12345;
+       return GSS_S_FAILURE;
+    } else {
+       hops = ((uint8_t *)input_token->value)[0];
+    }
+
+    mech_last_octet = ((uint8_t *)mech_type->elements)[mech_type->length - 1];
+    envstr = getenv("INIT_FAIL");
+    if (envstr != NULL && atoi(envstr) == mech_last_octet)
+       return GSS_S_FAILURE;
+
+    if (ctx == NULL) {
+       ctx = malloc(sizeof(*ctx));
+       assert(ctx != NULL);
+       ctx->initiator = 1;
+       ctx->hops = hops;
+       *context_handle = (gss_ctx_id_t)ctx;
+    } else if (ctx != NULL) {
+       assert(ctx->initiator);
+       ctx->hops--;
+       assert(ctx->hops == hops);
+    }
+
+    if (ctx->hops > 0) {
+       /* Generate a token containing the remaining hop count. */
+       ctx->hops--;
+       tok.value = &ctx->hops;
+       tok.length = 1;
+       major = gss_encapsulate_token(&tok, mech_type, output_token);
+       assert(major == GSS_S_COMPLETE);
+    }
+
+    return (ctx->hops > 0) ? GSS_S_CONTINUE_NEEDED : GSS_S_COMPLETE;
+}
+
+OM_uint32 GSSAPI_CALLCONV
+gss_accept_sec_context(OM_uint32 *minor_status, gss_ctx_id_t *context_handle,
+                      gss_const_cred_id_t verifier_cred_handle,
+                      const gss_buffer_t input_token,
+                      const gss_channel_bindings_t input_chan_bindings,
+                      gss_name_t *src_name, gss_OID *mech_type,
+                      gss_buffer_t output_token, OM_uint32 *ret_flags,
+                      OM_uint32 *time_rec,
+                      gss_cred_id_t *delegated_cred_handle)
+{
+    struct test_context *ctx = (struct test_context *)*context_handle;
+    uint8_t hops, mech_last_octet;
+    const char *envstr;
+    unsigned char mechbuf[64];
+    GSSAPIContextToken ct;
+    gss_OID_desc oid;
+    int ret;
+    size_t mech_len;
+
+    ret = decode_GSSAPIContextToken(input_token->value, input_token->length,
+                                   &ct, NULL);
+    if (ret == 0) {
+       ret = der_put_oid ((unsigned char *)mechbuf + sizeof(mechbuf) - 1,
+                          sizeof(mechbuf),
+                          &ct.thisMech,
+                          &mech_len);
+    }
+    if (ret) {
+       *minor_status = ret;
+       return GSS_S_FAILURE;
+    }
+
+    oid.length   = (OM_uint32)mech_len;
+    oid.elements = mechbuf + sizeof(mechbuf) - mech_len;
+
+    gss_duplicate_oid(minor_status, &oid, mech_type);
+
+    /*
+     * The unwrapped token sits at the end and is just one byte giving the
+     * remaining number of hops.  The final octet of the mech encoding should
+     * be just prior to it.
+     */
+    assert(input_token->length >= 2);
+    hops = ((uint8_t *)input_token->value)[input_token->length - 1];
+    mech_last_octet = ((uint8_t *)input_token->value)[input_token->length - 2];
+
+    envstr = getenv("ACCEPT_FAIL");
+    if (envstr != NULL && atoi(envstr) == mech_last_octet) {
+       output_token->value = strdup("fail");
+       assert(output_token->value != NULL);
+       output_token->length = 4;
+       return GSS_S_FAILURE;
+    }
+
+    if (*context_handle == GSS_C_NO_CONTEXT) {
+       ctx = malloc(sizeof(*ctx));
+       assert(ctx != NULL);
+       ctx->initiator = 0;
+       ctx->hops = hops;
+       *context_handle = (gss_ctx_id_t)ctx;
+    } else {
+       assert(!ctx->initiator);
+       ctx->hops--;
+       assert(ctx->hops == hops);
+    }
+
+    if (ctx->hops > 0) {
+       /* Generate a token containing the remaining hop count. */
+       ctx->hops--;
+       output_token->value = malloc(1);
+       assert(output_token->value != NULL);
+       memcpy(output_token->value, &ctx->hops, 1);
+       output_token->length = 1;
+    }
+
+    return (ctx->hops > 0) ? GSS_S_CONTINUE_NEEDED : GSS_S_COMPLETE;
+}
+
+OM_uint32 GSSAPI_CALLCONV
+gss_delete_sec_context(OM_uint32 *minor_status, gss_ctx_id_t *context_handle,
+                      gss_buffer_t output_token)
+{
+    free(*context_handle);
+    *context_handle = GSS_C_NO_CONTEXT;
+    return GSS_S_COMPLETE;
+}
+
+static int dummy_cred;
+
+OM_uint32 GSSAPI_CALLCONV
+gss_acquire_cred(OM_uint32 *minor_status, gss_const_name_t desired_name,
+                OM_uint32 time_req, const gss_OID_set desired_mechs,
+                gss_cred_usage_t cred_usage,
+                gss_cred_id_t *output_cred_handle, gss_OID_set *actual_mechs,
+                OM_uint32 *time_rec)
+{
+    *minor_status = 0;
+    *output_cred_handle = (gss_cred_id_t)&dummy_cred;
+    return GSS_S_COMPLETE;
+}
+
+OM_uint32 GSSAPI_CALLCONV
+gss_acquire_cred_with_password(OM_uint32 *minor_status,
+                              gss_const_name_t desired_name,
+                              const gss_buffer_t password, OM_uint32 time_req,
+                              const gss_OID_set desired_mechs,
+                              gss_cred_usage_t cred_usage,
+                              gss_cred_id_t *output_cred_handle,
+                              gss_OID_set *actual_mechs, OM_uint32 *time_rec)
+{
+    *minor_status = 0;
+    *output_cred_handle = (gss_cred_id_t)&dummy_cred;
+    return GSS_S_COMPLETE;
+}
+
+OM_uint32 GSSAPI_CALLCONV
+gss_release_cred(OM_uint32 *minor_status, gss_cred_id_t *cred_handle)
+{
+    return GSS_S_COMPLETE;
+}
+
+static int dummy_name;
+
+OM_uint32 GSSAPI_CALLCONV
+gss_import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer,
+               gss_OID input_name_type, gss_name_t *output_name)
+{
+    /*
+     * We don't need to remember anything about names, but we do need to
+     * distinguish them from GSS_C_NO_NAME (to determine the direction of
+     * gss_query_meta_data() and gss_exchange_meta_data()), so assign an
+     * arbitrary data pointer.
+     */
+    *output_name = (gss_name_t)&dummy_name;
+    return GSS_S_COMPLETE;
+}
+
+OM_uint32 GSSAPI_CALLCONV
+gss_release_name(OM_uint32 *minor_status, gss_name_t *input_name)
+{
+    return GSS_S_COMPLETE;
+}
+
+OM_uint32 GSSAPI_CALLCONV
+gss_display_status(OM_uint32 *minor_status, OM_uint32 status_value,
+                  int status_type, gss_OID mech_type,
+                  OM_uint32 *message_context, gss_buffer_t status_string)
+{
+    if (status_type == GSS_C_MECH_CODE && status_value == 12345) {
+       status_string->value = strdup("failure from acceptor");
+       assert(status_string->value != NULL);
+       status_string->length = strlen(status_string->value);
+       return GSS_S_COMPLETE;
+    }
+    return GSS_S_BAD_STATUS;
+}
+
+OM_uint32 GSSAPI_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,
+                      gss_const_name_t targ_name, OM_uint32 req_flags,
+                      gss_buffer_t meta_data)
+{
+    const char *envstr;
+    uint8_t mech_last_octet;
+    int initiator = (targ_name != GSS_C_NO_NAME);
+
+    mech_last_octet = ((uint8_t *)mech_oid->elements)[mech_oid->length - 1];
+    envstr = getenv(initiator ? "INIT_QUERY_FAIL" : "ACCEPT_QUERY_FAIL");
+    if (envstr != NULL && atoi(envstr) == mech_last_octet)
+       return GSS_S_FAILURE;
+    envstr = getenv(initiator ? "INIT_QUERY_NONE" : "ACCEPT_QUERY_NONE");
+    if (envstr != NULL && atoi(envstr) == mech_last_octet)
+       return GSS_S_COMPLETE;
+
+    meta_data->value = strdup("X");
+    meta_data->length = 1;
+    return GSS_S_COMPLETE;
+}
+
+OM_uint32 GSSAPI_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,
+                         gss_const_name_t targ_name, OM_uint32 req_flags,
+                         gss_const_buffer_t meta_data)
+{
+    const char *envstr;
+    uint8_t mech_last_octet;
+    int initiator = (targ_name != GSS_C_NO_NAME);
+
+    mech_last_octet = ((uint8_t *)mech_oid->elements)[mech_oid->length - 1];
+    envstr = getenv(initiator ? "INIT_EXCHANGE_FAIL" : "ACCEPT_EXCHANGE_FAIL");
+    if (envstr != NULL && atoi(envstr) == mech_last_octet)
+       return GSS_S_FAILURE;
+
+    assert(meta_data->length == 1 && memcmp(meta_data->value, "X", 1) == 0);
+    return GSS_S_COMPLETE;
+}
+
+OM_uint32 GSSAPI_CALLCONV
+gssspi_query_mechanism_info(OM_uint32 *minor_status, gss_const_OID mech_oid,
+                           unsigned char auth_scheme[16])
+{
+    /* Copy the mech OID encoding and right-pad it with zeros. */
+    memset(auth_scheme, 0, 16);
+    assert(mech_oid->length <= 16);
+    memcpy(auth_scheme, mech_oid->elements, mech_oid->length);
+    return GSS_S_COMPLETE;
+}
+
+OM_uint32 GSSAPI_CALLCONV
+gss_inquire_sec_context_by_oid(OM_uint32 *minor_status,
+                              gss_const_ctx_id_t context_handle,
+                              const gss_OID desired_object,
+                              gss_buffer_set_t *data_set)
+{
+    struct test_context *ctx = (struct test_context *)context_handle;
+    OM_uint32 major;
+    uint8_t keybytes[32] = { 0 };
+    uint8_t typebytes[4];
+    gss_buffer_desc key, type;
+    const char *envstr;
+    int ask_verify;
+
+    if (gss_oid_equal(desired_object, GSS_C_INQ_NEGOEX_KEY))
+       ask_verify = 0;
+    else if (gss_oid_equal(desired_object, GSS_C_INQ_NEGOEX_VERIFY_KEY))
+       ask_verify = 1;
+    else
+       return GSS_S_UNAVAILABLE;
+
+    /*
+     * By default, make a key available only if the context is established.
+     * This can be overridden to "always", "init-always", "accept-always",
+     * or "never".
+     */
+    envstr = getenv("KEY");
+    if (envstr != NULL && strcmp(envstr, "never") == 0) {
+       return GSS_S_UNAVAILABLE;
+    } else if (ctx->hops > 0) {
+       if (envstr == NULL)
+           return GSS_S_UNAVAILABLE;
+       else if (strcmp(envstr, "init-always") == 0 && !ctx->initiator)
+           return GSS_S_UNAVAILABLE;
+       else if (strcmp(envstr, "accept-always") == 0 && ctx->initiator)
+           return GSS_S_UNAVAILABLE;
+    }
+
+    /* Perturb the key so that each side's verifier key is equal to the other's
+     * checksum key. */
+    keybytes[0] = ask_verify ^ ctx->initiator;
+
+    /* Supply an all-zeros aes256-sha1 negoex key. */
+    if (gss_oid_equal(desired_object, GSS_C_INQ_NEGOEX_KEY) ||
+       gss_oid_equal(desired_object, GSS_C_INQ_NEGOEX_VERIFY_KEY)) {
+       OM_uint32 n = ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+
+       typebytes[0] = (n >> 0 ) & 0xFF;
+       typebytes[1] = (n >> 8 ) & 0xFF;
+       typebytes[2] = (n >> 16) & 0xFF;
+       typebytes[3] = (n >> 24) & 0xFF;
+
+       key.value = keybytes;
+       key.length = sizeof(keybytes);
+       type.value = typebytes;
+       type.length = sizeof(typebytes);
+       major = gss_add_buffer_set_member(minor_status, &key, data_set);
+       if (major != GSS_S_COMPLETE)
+           return major;
+       return gss_add_buffer_set_member(minor_status, &type, data_set);
+    }
+
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_process_context_token(OM_uint32 *minor_status,
+    gss_const_ctx_id_t context_handle,
+    const gss_buffer_t token_buffer)
+{
+    return GSS_S_COMPLETE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_context_time(OM_uint32 *minor_status,
+    gss_const_ctx_id_t context_handle,
+    OM_uint32 *time_rec)
+{
+    *time_rec = 0;
+    return GSS_S_COMPLETE;
+}
+
+/*
+ * We also need to supply a fake MIC in case SPNEGO test negotiates
+ * as non-default mechanism
+ */
+#define FAKE_MIC       "negoex-fake-mic"
+#define FAKE_MIC_LEN   (sizeof(FAKE_MIC) - 1)
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_get_mic(OM_uint32 *minor_status,
+    gss_const_ctx_id_t context_handle,
+    gss_qop_t qop_req,
+    const gss_buffer_t message_buffer,
+    gss_buffer_t message_token)
+{
+    message_token->value = strdup(FAKE_MIC);
+    message_token->length = FAKE_MIC_LEN;
+
+    *minor_status = 0;
+    return GSS_S_COMPLETE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_verify_mic(OM_uint32 *minor_status,
+    gss_const_ctx_id_t context_handle,
+    const gss_buffer_t message_buffer,
+    const gss_buffer_t token_buffer,
+    gss_qop_t *qop_state)
+{
+    *minor_status = 0;
+    if (token_buffer->length == FAKE_MIC_LEN &&
+       memcmp(token_buffer->value, FAKE_MIC, FAKE_MIC_LEN) == 0)
+       return GSS_S_COMPLETE;
+    else
+       return GSS_S_BAD_MIC;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_wrap(OM_uint32 *minor_status,
+    gss_const_ctx_id_t context_handle,
+    int conf_req_flag,
+    gss_qop_t qop_req,
+    const gss_buffer_t input_message_buffer,
+    int *conf_state,
+    gss_buffer_t output_message_buffer)
+{
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_unwrap(OM_uint32 *minor_status,
+    gss_const_ctx_id_t context_handle,
+    const gss_buffer_t input_message_buffer,
+    gss_buffer_t output_message_buffer,
+    int *conf_state,
+    gss_qop_t *qop_state)
+{
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_compare_name(OM_uint32 *minor_status,
+    gss_const_name_t name1_arg,
+    gss_const_name_t name2_arg,
+    int *name_equal)
+{
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_display_name(OM_uint32 *minor_status,
+    gss_const_name_t input_name,
+    gss_buffer_t output_name_buffer,
+    gss_OID *output_name_type)
+{
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_export_name(OM_uint32 *minor_status,
+    gss_const_name_t input_name,
+    gss_buffer_t exported_name)
+{
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_inquire_context(OM_uint32 *minor_status,
+    gss_const_ctx_id_t context_handle,
+    gss_name_t *src_name,
+    gss_name_t *targ_name,
+    OM_uint32 *lifetime_rec,
+    gss_OID *mech_type,
+    OM_uint32 *ctx_flags,
+    int *locally_initiated,
+    int *xopen)
+{
+    *lifetime_rec = GSS_C_INDEFINITE;
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_wrap_size_limit(OM_uint32 *minor_status,
+    gss_const_ctx_id_t context_handle,
+    int conf_req_flag,
+    gss_qop_t qop_req,
+    OM_uint32 req_output_size,
+    OM_uint32 *max_input_size)
+{
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_import_sec_context(OM_uint32 *minor_status,
+    const gss_buffer_t interprocess_token,
+    gss_ctx_id_t *context_handle)
+{
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_export_sec_context(OM_uint32 *minor_status,
+    gss_ctx_id_t *context_handle,
+    gss_buffer_t interprocess_token)
+{
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_canonicalize_name(OM_uint32 *minor_status,
+    gss_const_name_t input_name,
+    const gss_OID mech_type,
+    gss_name_t *output_name)
+{
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_duplicate_name(OM_uint32 *minor_status,
+    gss_const_name_t src_name,
+    gss_name_t *dest_name)
+{
+    return GSS_S_UNAVAILABLE;
+}
+
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_inquire_cred(OM_uint32 *minor_status,
+    gss_const_cred_id_t cred_handle,
+    gss_name_t *name_ret,
+    OM_uint32 *lifetime,
+    gss_cred_usage_t *cred_usage,
+    gss_OID_set *mechanisms)
+{
+    if (name_ret)
+       *name_ret = (gss_name_t)&dummy_name;
+    if (lifetime)
+       *lifetime = GSS_C_INDEFINITE;
+    if (cred_usage)
+       *cred_usage = GSS_C_BOTH;
+    if (mechanisms)
+       *mechanisms = GSS_C_NO_OID_SET;
+       
+    return GSS_S_COMPLETE;
+}
+
index ddc42e8ea2b01d5ceff8123b5a9e48cda15f2ee6..635a7b09d6ca56b6ead8d28e77b0189d5eba2863 100644 (file)
@@ -198,6 +198,9 @@ HEIMDAL_GSS_2.0 {
                __gss_c_ma_pfs_oid_desc;
                __gss_c_ma_compress_oid_desc;
                __gss_c_ma_ctx_trans_oid_desc;
+               __gss_c_ma_negoex_and_spnego_oid_desc;
+               __gss_c_inq_negoex_key_oid_desc;
+               __gss_c_inq_negoex_verify_key_oid_desc;
 
        local:
                *;
index 1577834e35dbeab80e583817fdd623fc1e63d6e5..80c119cd20160234430c6fdd70c12ea646e91b75 100644 (file)
@@ -6,6 +6,9 @@ export HEIM_PIDFILE_DIR
 unset KRB5_CONFIG
 unset KRB5CCNAME
 
+unset GSS_MECH_CONFIG
+unset GSSAPI_SPNEGO_NAME
+
 top_builddir="@top_builddir@"
 top_srcdir="@top_srcdir@"
 EGREP="@EGREP@"
index 96e551f5b485bf05836c338a75b9165edcc61321..e7c67faf57869b2a3fa3eee7a2aadbc96e12102b 100644 (file)
@@ -2,9 +2,9 @@
 
 include $(top_srcdir)/Makefile.am.common
 
-noinst_DATA = krb5.conf
+noinst_DATA = krb5.conf mech
 
-SCRIPT_TESTS = check-basic check-gss check-gssmask check-context check-spnego check-ntlm
+SCRIPT_TESTS = check-basic check-gss check-gssmask check-context check-spnego check-ntlm check-negoex
 
 TESTS = $(SCRIPT_TESTS)
 
@@ -49,10 +49,19 @@ check-ntlm: check-ntlm.in Makefile
        chmod +x check-ntlm.tmp && \
        mv check-ntlm.tmp check-ntlm
 
+check-negoex: check-negoex.in Makefile
+       $(do_subst) < $(srcdir)/check-negoex.in > check-negoex.tmp && \
+       chmod +x check-negoex.tmp && \
+       mv check-negoex.tmp check-negoex
+
 krb5.conf: krb5.conf.in Makefile
        $(do_subst) < $(srcdir)/krb5.conf.in > krb5.conf.tmp && \
        mv krb5.conf.tmp krb5.conf
 
+mech: mech.in Makefile
+       $(do_subst) < $(srcdir)/mech.in > mech.tmp && \
+       mv mech.tmp mech
+
 CLEANFILES= \
        $(TESTS) \
        foopassword \
@@ -61,6 +70,7 @@ CLEANFILES= \
        krb5ccfile-ds \
        server.keytab \
        krb5.conf \
+       mech \
        current-db* \
        *.log \
        tempfile \
@@ -80,4 +90,5 @@ EXTRA_DIST = \
        check-ntlm.in \
        check-context.in \
        ntlm-user-file.txt \
-       krb5.conf.in
+       krb5.conf.in \
+       mech.in
index de806b5977aeb26ca92d14a6de287f0bfd121a1e..e4dd1dbec999b2cbd8f43681d0e6a38a2aba54f6 100644 (file)
@@ -61,6 +61,7 @@ gssmaestro="../../appl/gssmask/gssmaestro"
 KRB5_CONFIG="${objdir}/krb5.conf"
 export KRB5_CONFIG
 
+KRB5CCNAME=${cache}
 rm -f ${keytabfile}
 rm -f current-db*
 rm -f out-*
diff --git a/tests/gss/check-negoex.in b/tests/gss/check-negoex.in
new file mode 100644 (file)
index 0000000..a5f9cdc
--- /dev/null
@@ -0,0 +1,183 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Kungliga Tekniska Högskolan
+# (Royal Institute of Technology, Stockholm, Sweden). 
+# All rights reserved. 
+#
+# Redistribution and use in source and binary forms, with or without 
+# modification, are permitted provided that the following conditions 
+# are met: 
+#
+# 1. Redistributions of source code must retain the above copyright 
+#    notice, this list of conditions and the following disclaimer. 
+#
+# 2. Redistributions in binary form must reproduce the above copyright 
+#    notice, this list of conditions and the following disclaimer in the 
+#    documentation and/or other materials provided with the distribution. 
+#
+# 3. Neither the name of the Institute nor the names of its contributors 
+#    may be used to endorse or promote products derived from this software 
+#    without specific prior written permission. 
+#
+# THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
+# SUCH DAMAGE. 
+#
+# $Id$
+#
+
+env_setup="@env_setup@"
+srcdir="@srcdir@"
+objdir="@objdir@"
+
+. ${env_setup}
+
+R=TEST.H5L.SE
+
+port=@port@
+
+keytabfile="${objdir}/server.keytab-no"
+keytab="FILE:${keytabfile}-no"
+cache="FILE:krb5ccfile-no"
+cacheds="FILE:krb5ccfile-ds-no"
+
+context="${TESTS_ENVIRONMENT} ../../lib/gssapi/test_context"
+
+KRB5_CONFIG="${objdir}/krb5.conf"
+export KRB5_CONFIG
+
+KRB5_KTNAME="${keytab}-no"
+export KRB5_KTNAME
+KRB5CCNAME="${cache}-no"
+export KRB5CCNAME
+unset NTLM_ACCEPTOR_CCACHE
+unset NTLM_USER_FILE
+
+GSSAPI_SPNEGO_NAME=host@host.test.h5l.se
+export GSSAPI_SPNEGO_NAME
+
+GSS_MECH_CONFIG="${objdir}/mech"
+export GSS_MECH_CONFIG
+
+> messages.log
+
+exitcode=0
+
+echo "======context building for negoex"
+
+for HOPS in 1 2 3 4 5
+do
+    echo "test_negoex_1 $HOPS hops"
+    ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_1 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se || \
+       { exitcode=1 ; echo test failed; }
+done
+
+for HOPS in 1 2 3 4 5
+do
+    echo "test_negoex_1 $HOPS hops early keys"
+    KEY=always ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_1 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se || \
+       { exitcode=1 ; echo test failed; }
+done
+
+HOPS=1
+echo "test_negoex_1 no keys"
+    KEY=never ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_1 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se 2>/dev/null && \
+       { exitcode=1 ; echo test failed; }
+
+echo "test_negoex_1 no optimistic token"
+    NEGOEX_NO_OPTIMISTIC_TOKEN=1 ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_1 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se || \
+       { exitcode=1 ; echo test failed; }
+
+echo "test_negoex_1 initiator query fail, test_negoex_2 pass"
+    INIT_QUERY_FAIL=102 ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_2 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se 2>/dev/null || \
+       { exitcode=1 ; echo test failed; }
+
+echo "test_negoex_1 acceptor query fail, test_negoex_2 pass"
+    ACCEPT_QUERY_FAIL=102 ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_2 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se 2>/dev/null || \
+       { exitcode=1 ; echo test failed; }
+
+echo "test_negoex_1 acceptor exchange fail, test_negoex_2 pass"
+    ACCEPT_EXCHANGE_FAIL=102 ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_2 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se 2>/dev/null || \
+       { exitcode=1 ; echo test failed; }
+
+echo "test_negoex_1 first mech initiator exchange fail"
+    INIT_EXCHANGE_FAIL=102 ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_1 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se 2>/dev/null && \
+       { exitcode=1 ; echo test failed; }
+
+echo "test_negoex_1 first mech initiator exchange fail, two hops"
+    HOPS=2 INIT_EXCHANGE_FAIL=102 ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_1 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se 2>/dev/null && \
+       { exitcode=1 ; echo test failed; }
+
+echo "test_negoex_1 first mech initiator exchange fail, two hops, early keys"
+    HOPS=2 KEY=always INIT_EXCHANGE_FAIL=102 ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_1 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se 2>/dev/null && \
+       { exitcode=1 ; echo test failed; }
+
+echo "test_negoex_1 first mech init_sec_context fail"
+    INIT_FAIL=102 ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_1 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se 2>/dev/null && \
+       { exitcode=1 ; echo test failed; }
+
+echo "test_negoex_1 first mech accept_sec_context fail"
+    HOPS=2 ACCEPT_FAIL=102 ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_1 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se 2>/dev/null && \
+       { exitcode=1 ; echo test failed; }
+
+echo "test_negoex_1 alert from acceptor to initiator"
+    HOPS=3 KEY=init-always ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_1 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se || \
+       { exitcode=1 ; echo test failed; }
+
+echo "test_negoex_1 alert from initiator to acceptor"
+    HOPS=4 KEY=accept-always ${context} \
+       --mech-type=spnego --ret-mech-type=test_negoex_1 \
+       --name-type=hostbased-service \
+       host@host.test.h5l.se || \
+       { exitcode=1 ; echo test failed; }
+
+trap "" EXIT
+
+exit $exitcode
index 6e03b012f738f439822b93587d963d6a6b443ece..f5bf3446ae617a06c920610a1afbfe1abf2847e5 100644 (file)
@@ -63,6 +63,7 @@ context="${TESTS_ENVIRONMENT} ../../lib/gssapi/test_context"
 KRB5_CONFIG="${objdir}/krb5.conf"
 export KRB5_CONFIG
 
+KRB5CCNAME=${cache}
 KRB5_KTNAME="${keytab}"
 export KRB5_KTNAME
 KRB5CCNAME="${cache}"
index 04ae119ad05de423f479b9d1a0b1d8abfb3ab1f3..cfb515e1d82470ccbb2772e741fdd66ab18531f9 100644 (file)
@@ -62,6 +62,7 @@ context="${TESTS_ENVIRONMENT} ../../lib/gssapi/test_context"
 KRB5_CONFIG="${objdir}/krb5.conf"
 export KRB5_CONFIG
 
+KRB5CCNAME=${cache}
 KRB5_KTNAME="${keytab}"
 export KRB5_KTNAME
 KRB5CCNAME="${cache}"
diff --git a/tests/gss/mech.in b/tests/gss/mech.in
new file mode 100644 (file)
index 0000000..4c4acc9
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# Test GSS-API mechglue configuration file.
+#
+test_negoex_1 2.25.1414534758 @objdir@/../../lib/gssapi/.libs/test_negoex_mech.so
+test_negoex_2 2.25.1175737388 @objdir@/../../lib/gssapi/.libs/test_negoex_mech.so