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
--- /dev/null
+
+
+
+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
+
getarg.h \
glob.h \
gssapi.h \
+ gssapi_asn1.h \
gssapi_mech.h \
hdb-private.h \
hdb-protos.h \
-I$(srcdir)/spnego \
$(INCLUDE_libintl)
-lib_LTLIBRARIES = libgssapi.la
+lib_LTLIBRARIES = libgssapi.la test_negoex_mech.la
krb5src = \
krb5/8003.c \
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 \
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 = \
nodist_libgssapi_la_SOURCES = \
gkrb5_err.c \
gkrb5_err.h \
+ negoex_err.c \
+ negoex_err.h \
$(BUILT_SOURCES)
libgssapi_la_DEPENDENCIES = version-script.map
include_HEADERS = gssapi.h
noinst_HEADERS = \
+ gssapi_asn1.h \
gssapi_mech.h \
$(srcdir)/ntlm/ntlm-private.h \
$(srcdir)/spnego/spnego-private.h \
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
asn1_NegHints.x \
asn1_NegTokenInit.x \
asn1_NegTokenInitWin.x \
- asn1_NegTokenResp.x
+ asn1_NegTokenResp.x \
+ asn1_NegResultEnum.x
BUILTHEADERS = \
$(srcdir)/krb5/gsskrb5-private.h \
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
mech/gssapi.asn1 \
spnego/spnego.asn1 \
spnego/spnego.opt \
+ spnego/negoex_err.et \
+ test_negoex_mech.c \
version-script.map \
gss-commands.in
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
+
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 \
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 \
$(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 \
$(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 \
$(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 \
$(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)
(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")
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
*/
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
*/
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 */
#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_ */
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);
+
/*
*
*/
_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;
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 */
_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 */
};
__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
#include "heim_threads.h"
#include <krb5.h>
#include "krb5_locl.h"
+#include "negoex_err.h"
struct mg_thread_ctx {
gss_OID mech;
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);
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,
#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;
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) {
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;
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); \
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);
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;
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) {
/* 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") };
/* 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") };
/* 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", "" },
{ 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", "" },
&__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,
&__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]);
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);
+}
--- /dev/null
+/*-
+ * 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;
+}
--- /dev/null
+/*-
+ * 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;
+}
--- /dev/null
+/*-
+ * 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;
+}
#include <gssapi.h>
#include <gssapi_mech.h>
#include <gssapi_krb5.h>
+#include <gssapi_spnego.h>
#include <heimqueue.h>
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);
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 */
};
#/* 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
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
#/*
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" ""
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"
}
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)
{
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;
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;
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)
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;
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;
}
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;
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
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;
}
/*
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;
}
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;
*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;
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
{
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);
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).
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,
*/
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,
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");
}
/*
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;
}
/*
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);
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;
/*
{
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;
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,
*/
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);
}
* 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:
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
*/
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;
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);
ret = GSS_S_COMPLETE;
}
+ _gss_negoex_release_context(ctx);
+
HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
HEIMDAL_MUTEX_destroy(&ctx->ctx_id_mutex);
}
/*
- * 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,
{
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;
+}
+
#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,
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);
*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;
)
{
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;
}
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
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 */
};
{
return &spnego_mech;
}
+
#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;
}
/*
* 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) {
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);
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)
}
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 */
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;
}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#
+# 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"
+
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
+
...
}
+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,
#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"
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;
{ "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
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
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
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];
--- /dev/null
+/*
+ * 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;
+}
+
__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:
*;
unset KRB5_CONFIG
unset KRB5CCNAME
+unset GSS_MECH_CONFIG
+unset GSSAPI_SPNEGO_NAME
+
top_builddir="@top_builddir@"
top_srcdir="@top_srcdir@"
EGREP="@EGREP@"
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)
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 \
krb5ccfile-ds \
server.keytab \
krb5.conf \
+ mech \
current-db* \
*.log \
tempfile \
check-ntlm.in \
check-context.in \
ntlm-user-file.txt \
- krb5.conf.in
+ krb5.conf.in \
+ mech.in
KRB5_CONFIG="${objdir}/krb5.conf"
export KRB5_CONFIG
+KRB5CCNAME=${cache}
rm -f ${keytabfile}
rm -f current-db*
rm -f out-*
--- /dev/null
+#!/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
KRB5_CONFIG="${objdir}/krb5.conf"
export KRB5_CONFIG
+KRB5CCNAME=${cache}
KRB5_KTNAME="${keytab}"
export KRB5_KTNAME
KRB5CCNAME="${cache}"
KRB5_CONFIG="${objdir}/krb5.conf"
export KRB5_CONFIG
+KRB5CCNAME=${cache}
KRB5_KTNAME="${keytab}"
export KRB5_KTNAME
KRB5CCNAME="${cache}"
--- /dev/null
+#
+# 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