diff --git a/doc/setup.texi b/doc/setup.texi index f2fb07ec3..0b3a860ed 100644 --- a/doc/setup.texi +++ b/doc/setup.texi @@ -121,6 +121,12 @@ can point a file with the environment variable @samp{KRB5_CONFIG}. env KRB5_CONFIG=$HOME/etc/krb5.conf kinit user@@REALM @end example +@cindex GSS_MECH_CONFIG +The GSS-API mechanism configuration file can also be changed from the +default with the enviornment variable @samp{GSS_MECH_CONFIG}. Note that +this file only configures additional plugin mechanisms: Kerberos, NTLM +and SPNEGO are built in to the Heimdal GSS-API library. + @node Creating the database, Modifying the database, Configuration file, Setting up a realm @section Creating the database diff --git a/doc/standardisation/draft-zhu-negoex-04.txt b/doc/standardisation/draft-zhu-negoex-04.txt new file mode 100644 index 000000000..17f189fa6 --- /dev/null +++ b/doc/standardisation/draft-zhu-negoex-04.txt @@ -0,0 +1,1345 @@ + + + +NETWORK WORKING GROUP M. Short +Internet-Draft L. Zhu +Updates: 4178 (if approved) K. Damour +Intended status: Standards Track D. McPherson +Expires: July 7, 2011 Microsoft Corporation + January 3, 2011 + + + SPNEGO Extended Negotiation (NEGOEX) Security Mechanism + draft-zhu-negoex-04 + +Abstract + + This document defines the SPNEGO Extended Negotiation (NEGOEX) + Security Mechanism. NEGOEX enhances the capabilities of SPNEGO by + providing a security mechanism which can be negotiated by the SPNEGO + protocol as defined in RFC4178. + + The NEGOEX protocol itself is a security mechanism negotiated by + SPNEGO. When the NEGOEX security mechanism is selected by SPNEGO, + NEGOEX provides a method allowing selection of a common + authentication protocol based on factors beyond just the fact that + both client and server support a given security mechanism. NEGOEX + OPTIONALLY adds a pair of meta-data messages for each negotiated + security mechanism. The meta-data exchange allows security + mechanisms to exchange auxiliary information such as trust + configurations, thus NEGOEX provides more flexibility than just + exchanging security mechanism OIDs in SPNEGO. + + NEGOEX preserves the optimistic token semantics of SPNEGO and applies + that recursively. Consequently a context establishment mechanism + token can be included in the initial NEGOEX message, and NEGOEX does + not require an extra round-trip when the initiator's optimistic token + is accepted by the target. + + Similar to SPNEGO, NEGOEX defines a few new GSS-API extensions that a + security mechanism MUST support in order to be negotiated by NEGOEX. + This document defines these GSS-API extensions. + + Unlike SPNEGO however, NEGOEX defines its own way for signing the + protocol messages in order to protect the protocol negotiation. The + NEGOEX message signing or verification can occur before the security + context for the negotiated real security mechanism is fully + established. + +Status of this Memo + + This Internet-Draft is submitted in full conformance with the + + + +Short, et al. Expires July 7, 2011 [Page 1] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + +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] + + diff --git a/include/Makefile.am b/include/Makefile.am index 7c2e609d1..16dd2250c 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -49,6 +49,7 @@ CLEANFILES = \ getarg.h \ glob.h \ gssapi.h \ + gssapi_asn1.h \ gssapi_mech.h \ hdb-private.h \ hdb-protos.h \ diff --git a/lib/gssapi/Makefile.am b/lib/gssapi/Makefile.am index 8c61450ca..aacb080ae 100644 --- a/lib/gssapi/Makefile.am +++ b/lib/gssapi/Makefile.am @@ -14,7 +14,7 @@ AM_CPPFLAGS += \ -I$(srcdir)/spnego \ $(INCLUDE_libintl) -lib_LTLIBRARIES = libgssapi.la +lib_LTLIBRARIES = libgssapi.la test_negoex_mech.la krb5src = \ krb5/8003.c \ @@ -157,6 +157,9 @@ mechsrc = \ mech/gss_wrap.c \ mech/gss_wrap_size_limit.c \ mech/gss_inquire_sec_context_by_oid.c \ + mech/gssspi_exchange_meta_data.c \ + mech/gssspi_query_mechanism_info.c \ + mech/gssspi_query_meta_data.c \ mech/mech_switch.h \ mech/mech_locl.h \ mech/name.h \ @@ -169,7 +172,10 @@ spnegosrc = \ spnego/cred_stubs.c \ spnego/external.c \ spnego/init_sec_context.c \ + spnego/negoex_ctx.c \ + spnego/negoex_util.c \ spnego/spnego_locl.h \ + spnego/negoex_locl.h \ $(srcdir)/spnego/spnego-private.h ntlmsrc = \ @@ -217,6 +223,8 @@ dist_libgssapi_la_SOURCES = \ nodist_libgssapi_la_SOURCES = \ gkrb5_err.c \ gkrb5_err.h \ + negoex_err.c \ + negoex_err.h \ $(BUILT_SOURCES) libgssapi_la_DEPENDENCIES = version-script.map @@ -239,6 +247,7 @@ man_MANS = gssapi.3 gss_acquire_cred.3 mech/mech.5 gss-token.1 include_HEADERS = gssapi.h noinst_HEADERS = \ + gssapi_asn1.h \ gssapi_mech.h \ $(srcdir)/ntlm/ntlm-private.h \ $(srcdir)/spnego/spnego-private.h \ @@ -252,7 +261,7 @@ nobase_include_HEADERS = \ gssapi/gssapi_spnego.h gssapidir = $(includedir)/gssapi -nodist_gssapi_HEADERS = gkrb5_err.h +nodist_gssapi_HEADERS = gkrb5_err.h negoex_err.h gssapi_files = asn1_GSSAPIContextToken.x @@ -265,7 +274,8 @@ spnego_files = \ asn1_NegHints.x \ asn1_NegTokenInit.x \ asn1_NegTokenInitWin.x \ - asn1_NegTokenResp.x + asn1_NegTokenResp.x \ + asn1_NegResultEnum.x BUILTHEADERS = \ $(srcdir)/krb5/gsskrb5-private.h \ @@ -279,11 +289,12 @@ $(libgssapi_la_OBJECTS): $(srcdir)/version-script.map BUILT_SOURCES = $(spnego_files:.x=.c) $(gssapi_files:.x=.c) -$(libgssapi_la_OBJECTS): gkrb5_err.h +$(libgssapi_la_OBJECTS): gkrb5_err.h negoex_err.h gkrb5_err.h: $(srcdir)/krb5/gkrb5_err.et +negoex_err.h: $(srcdir)/spnego/negoex_err.et CLEANFILES = $(BUILT_SOURCES) \ - gkrb5_err.h gkrb5_err.c \ + gkrb5_err.[ch] negoex_err.[ch] \ $(spnego_files) spnego_asn1*.h* spnego_asn1_files spnego_asn1-template.[cx] \ $(gssapi_files) gssapi_asn1*.h* gssapi_asn1_files gssapi_asn1-template.[cx] \ gss-commands.h gss-commands.c @@ -365,6 +376,8 @@ EXTRA_DIST = \ mech/gssapi.asn1 \ spnego/spnego.asn1 \ spnego/spnego.opt \ + spnego/negoex_err.et \ + test_negoex_mech.c \ version-script.map \ gss-commands.in @@ -375,6 +388,20 @@ $(libgssapi_la_OBJECTS): $(srcdir)/gssapi/gssapi_oid.h gkrb5_err.h gkrb5_err.c: $(srcdir)/krb5/gkrb5_err.et $(COMPILE_ET) $(srcdir)/krb5/gkrb5_err.et +negoex_err.h negoex_err.c: $(srcdir)/spnego/negoex_err.et + $(COMPILE_ET) $(srcdir)/spnego/negoex_err.et + $(srcdir)/gssapi/gssapi_oid.h $(srcdir)/mech/gss_oid.c: perl $(srcdir)/gen-oid.pl -b base -h $(srcdir)/oid.txt > $(srcdir)/gssapi/gssapi_oid.h perl $(srcdir)/gen-oid.pl -b base $(srcdir)/oid.txt > $(srcdir)/mech/gss_oid.c + +# +# NegoEx test mechanism, uses decode_GSSAPIContextToken +# + +test_negoex_mech_la_SOURCES = test_negoex_mech.c $(gssapi_files:.x=.c) +test_negoex_mech_la_LDFLAGS = -module +test_negoex_mech_la_LIBADD = \ + $(top_builddir)/lib/asn1/libasn1.la \ + libgssapi.la + diff --git a/lib/gssapi/NTMakefile b/lib/gssapi/NTMakefile index 0a2bab4af..7d20d8ebc 100644 --- a/lib/gssapi/NTMakefile +++ b/lib/gssapi/NTMakefile @@ -173,6 +173,9 @@ mechsrc = \ mech/gss_wrap.c \ mech/gss_wrap_size_limit.c \ mech/gss_inquire_sec_context_by_oid.c \ + mech/gssspi_exchange_meta_data.c \ + mech/gssspi_query_mechanism_info.c \ + mech/gssspi_query_meta_data.c \ mech/mech_switch.h \ mech/mech_locl.h \ mech/name.h \ @@ -185,7 +188,10 @@ spnegosrc = \ spnego/cred_stubs.c \ spnego/external.c \ spnego/init_sec_context.c \ - spnego/spnego_locl.h + spnego/negoex_ctx.c \ + spnego/negoex_util.c \ + spnego/spnego_locl.h \ + spnego/negoex_locl.h ntlmsrc = \ ntlm/accept_sec_context.c \ @@ -258,6 +264,11 @@ $(OBJ)\gkrb5_err.c $(OBJ)\gkrb5_err.h: krb5\gkrb5_err.et $(BINDIR)\compile_et.exe $(SRCDIR)\krb5\gkrb5_err.et cd $(SRCDIR) +$(OBJ)\negoex_err.c $(OBJ)\negoex_err.h: spnego\negoex_err.et + cd $(OBJ) + $(BINDIR)\compile_et.exe $(SRCDIR)\spnego\negoex_err.et + cd $(SRCDIR) + INCFILES= \ $(INCDIR)\gssapi.h \ $(INCDIR)\gssapi\gssapi.h \ @@ -270,6 +281,7 @@ INCFILES= \ $(OBJ)\spnego\spnego-private.h \ $(OBJ)\krb5\gsskrb5-private.h \ $(OBJ)\gkrb5_err.h \ + $(OBJ)\negoex_err.h \ $(OBJ)\gssapi\gssapi_asn1.h \ $(OBJ)\gssapi\gssapi_asn1-priv.h \ $(OBJ)\spnego\spnego_asn1.h \ @@ -409,12 +421,17 @@ libgssapi_OBJs = \ $(OBJ)\mech/gss_wrap.obj \ $(OBJ)\mech/gss_wrap_size_limit.obj \ $(OBJ)\mech/gss_inquire_sec_context_by_oid.obj \ + $(OBJ)\mech/gssspi_exchange_meta_data.obj \ + $(OBJ)\mech/gssspi_query_mechanism_info.obj \ + $(OBJ)\mech/gssspi_query_meta_data.obj \ $(OBJ)\spnego/accept_sec_context.obj \ $(OBJ)\spnego/compat.obj \ $(OBJ)\spnego/context_stubs.obj \ $(OBJ)\spnego/cred_stubs.obj \ $(OBJ)\spnego/external.obj \ $(OBJ)\spnego/init_sec_context.obj \ + $(OBJ)\spnego/negoex_ctx.obj \ + $(OBJ)\spnego/negoex_util.obj \ $(OBJ)\ntlm/accept_sec_context.obj \ $(OBJ)\ntlm/acquire_cred.obj \ $(OBJ)\ntlm/add_cred.obj \ @@ -447,6 +464,7 @@ libgssapi_OBJs = \ $(OBJ)\ntlm/set_sec_context_option.obj \ $(OBJ)\ntlm/kdc.obj \ $(OBJ)\gkrb5_err.obj \ + $(OBJ)\negoex_err.obj \ $(spnego_files:.x=.obj) \ $(gssapi_files:.x=.obj) @@ -588,6 +606,7 @@ $(OBJ)\gss-commands.c $(OBJ)\gss-commands.h: gss-commands.in (generate-obj-macro "libgssapi_OBJs" (concat "\t$(OBJ)\\gkrb5_err.obj \\\n" + "\t$(OBJ)\\negoex_err.obj \\\n" "\t$(spnego_files:.x=.obj) \\\n" "\t$(gssapi_files:.x=.obj)") "krb5src" "mechsrc" "spnegosrc" "ntlmsrc") diff --git a/lib/gssapi/gssapi/gssapi_oid.h b/lib/gssapi/gssapi/gssapi_oid.h index 30ffbe4ad..268271861 100644 --- a/lib/gssapi/gssapi/gssapi_oid.h +++ b/lib/gssapi/gssapi/gssapi_oid.h @@ -132,6 +132,12 @@ extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_inq_win2k_pac_x_oid_desc; extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_inq_sspi_session_key_oid_desc; #define GSS_C_INQ_SSPI_SESSION_KEY (&__gss_c_inq_sspi_session_key_oid_desc) +extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_inq_negoex_key_oid_desc; +#define GSS_C_INQ_NEGOEX_KEY (&__gss_c_inq_negoex_key_oid_desc) + +extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_inq_negoex_verify_key_oid_desc; +#define GSS_C_INQ_NEGOEX_VERIFY_KEY (&__gss_c_inq_negoex_verify_key_oid_desc) + /* * "Standard" mechs */ @@ -151,6 +157,9 @@ extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_peer_has_updated_spnego_oid_desc extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_ntlm_reset_crypto_oid_desc; #define GSS_C_NTLM_RESET_CRYPTO (&__gss_c_ntlm_reset_crypto_oid_desc) +extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_negoex_mechanism_oid_desc; +#define GSS_NEGOEX_MECHANISM (&__gss_negoex_mechanism_oid_desc) + /* * OID mappings with name and short description and and slightly longer description */ @@ -238,4 +247,7 @@ extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_ma_compress_oid_desc; extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_ma_ctx_trans_oid_desc; #define GSS_C_MA_CTX_TRANS (&__gss_c_ma_ctx_trans_oid_desc) +extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_c_ma_negoex_and_spnego_oid_desc; +#define GSS_C_MA_NEGOEX_AND_SPNEGO (&__gss_c_ma_negoex_and_spnego_oid_desc) + #endif /* GSSAPI_GSSAPI_OID */ diff --git a/lib/gssapi/gssapi/gssapi_spnego.h b/lib/gssapi/gssapi/gssapi_spnego.h index dd3b2a5c3..8b4519e75 100644 --- a/lib/gssapi/gssapi/gssapi_spnego.h +++ b/lib/gssapi/gssapi/gssapi_spnego.h @@ -50,6 +50,38 @@ extern GSSAPI_LIB_VARIABLE gss_OID_desc __gss_spnego_mechanism_oid_desc; #define GSS_SPNEGO_MECHANISM (&__gss_spnego_mechanism_oid_desc) #define gss_mech_spnego GSS_SPNEGO_MECHANISM +/* + * NegoEx extensions, to be implemented by mechanisms + */ +GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL +gssspi_query_mechanism_info( + OM_uint32 * /* minor_status */, + gss_const_OID /* mech_oid */, + unsigned char[16] /* auth_scheme */ +); + +GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL +gssspi_query_meta_data( + OM_uint32 * /* minor_status */, + gss_const_OID /* mech_oid */, + gss_cred_id_t /* cred_handle */, + gss_ctx_id_t * /* context_handle */, + gss_const_name_t /* targ_name */, + OM_uint32 /* req_flags */, + gss_buffer_t /* meta_data */ +); + +GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL +gssspi_exchange_meta_data( + OM_uint32 * /* minor_status */, + gss_const_OID /* mech_oid */, + gss_cred_id_t /* cred_handle */, + gss_ctx_id_t * /* context_handle */, + gss_const_name_t /* targ_name */, + OM_uint32 /* req_flags */, + gss_const_buffer_t /* meta_data */ +); + GSSAPI_CPP_END #endif /* GSSAPI_SPNEGO_H_ */ diff --git a/lib/gssapi/gssapi_mech.h b/lib/gssapi/gssapi_mech.h index 6afc7be96..68732f7f7 100644 --- a/lib/gssapi/gssapi_mech.h +++ b/lib/gssapi/gssapi_mech.h @@ -484,6 +484,29 @@ _gss_get_neg_mechs_t(OM_uint32 *minor_status, gss_const_cred_id_t cred_handle, gss_OID_set *mechs); +typedef OM_uint32 GSSAPI_CALLCONV +_gss_query_mechanism_info_t(OM_uint32 *minor_status, + gss_const_OID mech_oid, + unsigned char auth_scheme[16]); + +typedef OM_uint32 GSSAPI_CALLCONV +_gss_query_meta_data_t(OM_uint32 *minor_status, + gss_const_OID mech_oid, + gss_cred_id_t cred_handle, + gss_ctx_id_t *ctx_handle, + gss_const_name_t targ_name, + OM_uint32 req_flags, + gss_buffer_t meta_data); + +typedef OM_uint32 GSSAPI_CALLCONV +_gss_exchange_meta_data_t(OM_uint32 *minor_status, + gss_const_OID mech_oid, + gss_cred_id_t cred_handle, + gss_ctx_id_t *ctx_handle, + gss_const_name_t targ_name, + OM_uint32 req_flags, + gss_const_buffer_t meta_data); + /* * */ @@ -597,6 +620,9 @@ typedef struct gssapi_mech_interface_desc { _gss_store_cred_into_t *gm_store_cred_into; _gss_set_neg_mechs_t *gm_set_neg_mechs; _gss_get_neg_mechs_t *gm_get_neg_mechs; + _gss_query_mechanism_info_t *gm_query_mechanism_info; + _gss_query_meta_data_t *gm_query_meta_data; + _gss_exchange_meta_data_t *gm_exchange_meta_data; struct gss_mech_compat_desc_struct *gm_compat; } gssapi_mech_interface_desc, *gssapi_mech_interface; @@ -669,4 +695,17 @@ gss_mg_set_error_string(gss_OID mech, OM_uint32 maj, OM_uint32 min, const char *fmt, ...); +gss_cred_id_t +_gss_mg_find_mech_cred(gss_const_cred_id_t cred_handle, + gss_const_OID mech_type); + +#include + +/* + * 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 */ diff --git a/lib/gssapi/krb5/external.c b/lib/gssapi/krb5/external.c index a3928fed5..71fc3974d 100644 --- a/lib/gssapi/krb5/external.c +++ b/lib/gssapi/krb5/external.c @@ -401,6 +401,9 @@ static gssapi_mech_interface_desc krb5_mech = { _gsskrb5_store_cred_into, NULL, /* gm_set_neg_mechs */ NULL, /* gm_get_neg_mechs */ + NULL, /* gm_query_mechanism_info */ + NULL, /* gm_query_meta_data */ + NULL, /* gm_exchange_meta_data */ NULL /* gm_compat */ }; diff --git a/lib/gssapi/libgssapi-exports.def b/lib/gssapi/libgssapi-exports.def index 6d0f8bb4a..992e8eb3f 100644 --- a/lib/gssapi/libgssapi-exports.def +++ b/lib/gssapi/libgssapi-exports.def @@ -195,3 +195,6 @@ EXPORTS __gss_c_ma_pfs_oid_desc DATA __gss_c_ma_compress_oid_desc DATA __gss_c_ma_ctx_trans_oid_desc DATA + __gss_c_ma_negoex_and_spnego_oid_desc DATA + __gss_c_inq_negoex_key_oid_desc DATA + __gss_c_inq_negoex_verify_key_oid_desc DATA diff --git a/lib/gssapi/mech/context.c b/lib/gssapi/mech/context.c index 889ed6160..5a029b79a 100644 --- a/lib/gssapi/mech/context.c +++ b/lib/gssapi/mech/context.c @@ -37,6 +37,7 @@ #include "heim_threads.h" #include #include "krb5_locl.h" +#include "negoex_err.h" struct mg_thread_ctx { gss_OID mech; @@ -99,6 +100,8 @@ _gss_mechglue_thread(void) return NULL; } + krb5_add_et_list(ctx->context, initialize_ngex_error_table_r); + HEIMDAL_setspecific(context_key, ctx, ret); if (ret) { krb5_free_context(ctx->context); @@ -109,6 +112,16 @@ _gss_mechglue_thread(void) return ctx; } +krb5_context +_gss_mg_krb5_context(void) +{ + struct mg_thread_ctx *mg; + + mg = _gss_mechglue_thread(); + + return mg ? mg->context : NULL; +} + OM_uint32 _gss_mg_get_error(const gss_OID mech, OM_uint32 value, diff --git a/lib/gssapi/mech/gss_init_sec_context.c b/lib/gssapi/mech/gss_init_sec_context.c index 0970513f9..250eafef2 100644 --- a/lib/gssapi/mech/gss_init_sec_context.c +++ b/lib/gssapi/mech/gss_init_sec_context.c @@ -30,8 +30,10 @@ #include "mech_locl.h" -static gss_cred_id_t -_gss_mech_cred_find(gss_const_cred_id_t cred_handle, gss_OID mech_type) +gss_cred_id_t +_gss_mg_find_mech_cred( + gss_const_cred_id_t cred_handle, + gss_const_OID mech_type) { struct _gss_cred *cred = (struct _gss_cred *)cred_handle; struct _gss_mechanism_cred *mc; @@ -227,7 +229,7 @@ gss_init_sec_context(OM_uint32 * minor_status, if (m->gm_flags & GM_USE_MG_CRED) cred_handle = initiator_cred_handle; else - cred_handle = _gss_mech_cred_find(initiator_cred_handle, mech_type); + cred_handle = _gss_mg_find_mech_cred(initiator_cred_handle, mech_type); if (initiator_cred_handle != GSS_C_NO_CREDENTIAL && cred_handle == NULL) { diff --git a/lib/gssapi/mech/gss_inquire_cred.c b/lib/gssapi/mech/gss_inquire_cred.c index 5e2348faa..f7408c84c 100644 --- a/lib/gssapi/mech/gss_inquire_cred.c +++ b/lib/gssapi/mech/gss_inquire_cred.c @@ -96,8 +96,8 @@ gss_inquire_cred(OM_uint32 *minor_status, struct _gss_mechanism_cred *mc; HEIM_TAILQ_FOREACH(mc, &cred->gc_mc, gmc_link) { - gss_name_t mc_name; - OM_uint32 mc_lifetime; + gss_name_t mc_name = GSS_C_NO_NAME; + OM_uint32 mc_lifetime = GSS_C_INDEFINITE; if (mc->gmc_mech->gm_inquire_cred == NULL) continue; diff --git a/lib/gssapi/mech/gss_mech_switch.c b/lib/gssapi/mech/gss_mech_switch.c index 6bad68611..e4a56f3ce 100644 --- a/lib/gssapi/mech/gss_mech_switch.c +++ b/lib/gssapi/mech/gss_mech_switch.c @@ -179,11 +179,20 @@ do { \ m->gm_mech.gm_ ## name = NULL; \ } while (0) +/* mech exports gssspi_XXX, internally referred to as gss_XXX */ #define OPTSPISYM(name) \ do { \ m->gm_mech.gm_ ## name = (_gss_##name##_t *)dlsym(so, "gssspi_" #name); \ } while (0) +/* mech exports gssspi_XXX, internally referred to as gssspi_XXX */ +#define OPTSPISPISYM(name) \ +do { \ + m->gm_mech.gm_ ## name = (_gss_##name##_t *)dlsym(so, "gssspi_" #name); \ + if (m->gm_mech.gm_ ## name == gssspi_ ## name) \ + m->gm_mech.gm_ ## name = NULL; \ +} while (0) + #define COMPATSYM(name) \ do { \ m->gm_mech.gm_compat->gmc_ ## name = (_gss_##name##_t *)dlsym(so, "gss_" #name); \ @@ -262,6 +271,7 @@ _gss_load_mech(void) void *so; gss_OID mech_oid; int found; + const char *conf = secure_getenv("GSS_MECH_CONFIG"); #endif heim_base_once_f(&once, &_gss_mechs, init_mech_switch_list); @@ -285,7 +295,7 @@ _gss_load_mech(void) add_builtin(__gss_ntlm_initialize()); #ifdef HAVE_DLOPEN - fp = fopen(_PATH_GSS_MECH, "r"); + fp = fopen(conf ? conf : _PATH_GSS_MECH, "r"); if (!fp) { HEIMDAL_MUTEX_unlock(&_gss_mech_mutex); return; @@ -410,6 +420,9 @@ _gss_load_mech(void) OPTSYM(set_neg_mechs); OPTSYM(get_neg_mechs); OPTSPISYM(authorize_localname); + OPTSPISPISYM(query_mechanism_info); + OPTSPISPISYM(query_meta_data); + OPTSPISPISYM(exchange_meta_data); mi = (_gss_mo_init *)dlsym(so, "gss_mo_init"); if (mi != NULL) { diff --git a/lib/gssapi/mech/gss_oid.c b/lib/gssapi/mech/gss_oid.c index 5b9bd3623..14d53c977 100644 --- a/lib/gssapi/mech/gss_oid.c +++ b/lib/gssapi/mech/gss_oid.c @@ -124,6 +124,12 @@ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_inq_win2k_pac_x_oid_desc = { 8, rk_UNCO /* GSS_C_INQ_SSPI_SESSION_KEY - 1.2.840.113554.1.2.2.5.5 */ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_inq_sspi_session_key_oid_desc = { 11, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x05") }; +/* GSS_C_INQ_NEGOEX_KEY - 1.2.840.113554.1.2.2.5.16 */ +gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_inq_negoex_key_oid_desc = { 11, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x10") }; + +/* GSS_C_INQ_NEGOEX_VERIFY_KEY - 1.2.840.113554.1.2.2.5.17 */ +gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_inq_negoex_verify_key_oid_desc = { 11, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x11") }; + /* GSS_KRB5_MECHANISM - 1.2.840.113554.1.2.2 */ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_krb5_mechanism_oid_desc = { 9, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02") }; @@ -139,6 +145,9 @@ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_peer_has_updated_spnego_oid_desc = { 9, /* GSS_C_NTLM_RESET_CRYPTO - 1.3.6.1.4.1.7165.655.1.3 */ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_ntlm_reset_crypto_oid_desc = { 11, rk_UNCONST("\x2b\x06\x01\x04\x01\xb7\x7d\x85\x0f\x01\x03") }; +/* GSS_NEGOEX_MECHANISM - 1.3.6.1.4.1.311.2.2.30 */ +gss_OID_desc GSSAPI_LIB_VARIABLE __gss_negoex_mechanism_oid_desc = { 10, rk_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e") }; + /* GSS_C_MA_MECH_CONCRETE - 1.3.6.1.5.5.13.1 */ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_ma_mech_concrete_oid_desc = { 7, rk_UNCONST("\x2b\x06\x01\x05\x05\x0d\x01") }; @@ -220,6 +229,9 @@ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_ma_compress_oid_desc = { 7, rk_UNCONST( /* GSS_C_MA_CTX_TRANS - 1.3.6.1.5.5.13.27 */ gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_ma_ctx_trans_oid_desc = { 7, rk_UNCONST("\x2b\x06\x01\x05\x05\x0d\x1b") }; +/* GSS_C_MA_NEGOEX_AND_SPNEGO - 1.2.840.113554.1.2.2.5.18 */ +gss_OID_desc GSSAPI_LIB_VARIABLE __gss_c_ma_negoex_and_spnego_oid_desc = { 11, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x12") }; + struct _gss_oid_name_table _gss_ont_ma[] = { { GSS_C_MA_AUTH_INIT, "GSS_C_MA_AUTH_INIT", "auth-init-princ", "" }, { GSS_C_MA_AUTH_INIT_ANON, "GSS_C_MA_AUTH_INIT_ANON", "auth-init-princ-anon", "" }, @@ -243,6 +255,7 @@ struct _gss_oid_name_table _gss_ont_ma[] = { { GSS_C_MA_MECH_NEGO, "GSS_C_MA_MECH_NEGO", "mech-negotiation-mech", "" }, { GSS_C_MA_MECH_PSEUDO, "GSS_C_MA_MECH_PSEUDO", "pseudo-mech", "" }, { GSS_C_MA_MIC, "GSS_C_MA_MIC", "mic", "" }, + { GSS_C_MA_NEGOEX_AND_SPNEGO, "GSS_C_MA_NEGOEX_AND_SPNEGO", "negoex-and-spnego", "Indicates that a mechanism supports both NegoEx and SPNEGO" }, { GSS_C_MA_NOT_DFLT_MECH, "GSS_C_MA_NOT_DFLT_MECH", "mech-not-default", "" }, { GSS_C_MA_NOT_MECH, "GSS_C_MA_NOT_MECH", "not-mech", "" }, { GSS_C_MA_OOS_DET, "GSS_C_MA_OOS_DET", "oos-detection", "" }, @@ -303,11 +316,14 @@ gss_OID _gss_ot_internal[] = { &__gss_netlogon_nt_netbios_dns_name_oid_desc, &__gss_c_inq_win2k_pac_x_oid_desc, &__gss_c_inq_sspi_session_key_oid_desc, + &__gss_c_inq_negoex_key_oid_desc, + &__gss_c_inq_negoex_verify_key_oid_desc, &__gss_krb5_mechanism_oid_desc, &__gss_ntlm_mechanism_oid_desc, &__gss_spnego_mechanism_oid_desc, &__gss_c_peer_has_updated_spnego_oid_desc, &__gss_c_ntlm_reset_crypto_oid_desc, + &__gss_negoex_mechanism_oid_desc, &__gss_c_ma_mech_concrete_oid_desc, &__gss_c_ma_mech_pseudo_oid_desc, &__gss_c_ma_mech_composite_oid_desc, @@ -335,6 +351,7 @@ gss_OID _gss_ot_internal[] = { &__gss_c_ma_pfs_oid_desc, &__gss_c_ma_compress_oid_desc, &__gss_c_ma_ctx_trans_oid_desc, + &__gss_c_ma_negoex_and_spnego_oid_desc, }; size_t _gss_ot_internal_count = sizeof(_gss_ot_internal) / sizeof(_gss_ot_internal[0]); diff --git a/lib/gssapi/mech/gss_utils.c b/lib/gssapi/mech/gss_utils.c index 571382b78..f9e79c144 100644 --- a/lib/gssapi/mech/gss_utils.c +++ b/lib/gssapi/mech/gss_utils.c @@ -147,3 +147,62 @@ _gss_copy_buffer(OM_uint32 *minor_status, return (GSS_S_COMPLETE); } +void +_gss_mg_encode_le_uint32(uint32_t n, uint8_t *p) +{ + p[0] = (n >> 0 ) & 0xFF; + p[1] = (n >> 8 ) & 0xFF; + p[2] = (n >> 16) & 0xFF; + p[3] = (n >> 24) & 0xFF; +} + +void +_gss_mg_decode_le_uint32(const void *ptr, uint32_t *n) +{ + const uint8_t *p = ptr; + *n = (p[0] << 0) | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); +} + +void +_gss_mg_encode_be_uint32(uint32_t n, uint8_t *p) +{ + p[0] = (n >> 24) & 0xFF; + p[1] = (n >> 16) & 0xFF; + p[2] = (n >> 8 ) & 0xFF; + p[3] = (n >> 0 ) & 0xFF; +} + +void +_gss_mg_decode_be_uint32(const void *ptr, uint32_t *n) +{ + const uint8_t *p = ptr; + *n = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3] << 0); +} + +void +_gss_mg_encode_le_uint16(uint16_t n, uint8_t *p) +{ + p[0] = (n >> 0 ) & 0xFF; + p[1] = (n >> 8 ) & 0xFF; +} + +void +_gss_mg_decode_le_uint16(const void *ptr, uint16_t *n) +{ + const uint8_t *p = ptr; + *n = (p[0] << 0) | (p[1] << 8); +} + +void +_gss_mg_encode_be_uint16(uint16_t n, uint8_t *p) +{ + p[0] = (n >> 24) & 0xFF; + p[1] = (n >> 16) & 0xFF; +} + +void +_gss_mg_decode_be_uint16(const void *ptr, uint16_t *n) +{ + const uint8_t *p = ptr; + *n = (p[0] << 24) | (p[1] << 16); +} diff --git a/lib/gssapi/mech/gssspi_exchange_meta_data.c b/lib/gssapi/mech/gssspi_exchange_meta_data.c new file mode 100644 index 000000000..1a83bf6cd --- /dev/null +++ b/lib/gssapi/mech/gssspi_exchange_meta_data.c @@ -0,0 +1,115 @@ +/*- + * Copyright (c) 2005 Doug Rabson + * All rights reserved. + * + * Portions Copyright (c) 2009 Apple Inc. All rights reserved. + * Portions Copyright (c) 2019 AuriStor, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "mech_locl.h" + +GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL +gssspi_exchange_meta_data( + OM_uint32 *minor_status, + gss_const_OID input_mech_type, + gss_cred_id_t input_cred_handle, + gss_ctx_id_t *context_handle, + gss_const_name_t target_name, + OM_uint32 req_flags, + gss_const_buffer_t meta_data) +{ + OM_uint32 major_status, junk; + gssapi_mech_interface m; + struct _gss_name *name = (struct _gss_name *) target_name; + struct _gss_mechanism_name *mn; + struct _gss_context *ctx = (struct _gss_context *) *context_handle; + gss_cred_id_t cred_handle; + int allocated_ctx; + gss_const_OID mech_type = input_mech_type; + + *minor_status = 0; + + if (mech_type == GSS_C_NO_OID) + return GSS_S_BAD_MECH; + + if (ctx == NULL) { + ctx = calloc(1, sizeof(struct _gss_context)); + if (ctx == NULL) { + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + + m = ctx->gc_mech = __gss_get_mechanism(mech_type); + if (m == NULL) { + free(ctx); + return GSS_S_BAD_MECH; + } + allocated_ctx = 1; + } else { + m = ctx->gc_mech; + mech_type = &m->gm_mech_oid; + allocated_ctx = 0; + } + + if (m->gm_exchange_meta_data == NULL) { + major_status = GSS_S_BAD_MECH; + goto cleanup; + } + + major_status = _gss_find_mn(minor_status, name, mech_type, &mn); + if (major_status != GSS_S_COMPLETE) + goto cleanup; + + if (m->gm_flags & GM_USE_MG_CRED) + cred_handle = input_cred_handle; + else + cred_handle = _gss_mg_find_mech_cred(input_cred_handle, mech_type); + + if (input_cred_handle != GSS_C_NO_CREDENTIAL && + cred_handle == NULL) { + major_status = GSS_S_NO_CRED; + goto cleanup; + } + + /* note: mechanism is not obligated to allocate a context on success */ + major_status = m->gm_exchange_meta_data(minor_status, + mech_type, + cred_handle, + &ctx->gc_ctx, + mn ? mn->gmn_name : GSS_C_NO_NAME, + req_flags, + meta_data); + if (major_status != GSS_S_COMPLETE) + _gss_mg_error(m, *minor_status); + +cleanup: + if (major_status != GSS_S_COMPLETE || ctx->gc_ctx == GSS_C_NO_CONTEXT) + gss_delete_sec_context(&junk, (gss_ctx_id_t *)&ctx, GSS_C_NO_BUFFER); + + *context_handle = (gss_ctx_id_t) ctx; + + _gss_mg_log(10, "gss-emd: return %d/%d", (int)major_status, (int)*minor_status); + + return major_status; +} diff --git a/lib/gssapi/mech/gssspi_query_mechanism_info.c b/lib/gssapi/mech/gssspi_query_mechanism_info.c new file mode 100644 index 000000000..82fe9d9b5 --- /dev/null +++ b/lib/gssapi/mech/gssspi_query_mechanism_info.c @@ -0,0 +1,55 @@ +/*- + * Copyright (c) 2019 AuriStor, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "mech_locl.h" + +GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL +gssspi_query_mechanism_info( + OM_uint32 *minor_status, + gss_const_OID mech_type, + unsigned char auth_scheme[16]) +{ + OM_uint32 major_status; + gssapi_mech_interface m; + + *minor_status = 0; + + if (mech_type == GSS_C_NO_OID) + return GSS_S_BAD_MECH; + + m = __gss_get_mechanism(mech_type); + if (m == NULL || m->gm_query_mechanism_info == NULL) + return GSS_S_BAD_MECH; + + major_status = m->gm_query_mechanism_info(minor_status, + mech_type, + auth_scheme); + + if (major_status != GSS_S_COMPLETE) + _gss_mg_error(m, *minor_status); + + return major_status; +} diff --git a/lib/gssapi/mech/gssspi_query_meta_data.c b/lib/gssapi/mech/gssspi_query_meta_data.c new file mode 100644 index 000000000..daf679af9 --- /dev/null +++ b/lib/gssapi/mech/gssspi_query_meta_data.c @@ -0,0 +1,117 @@ +/*- + * Copyright (c) 2005 Doug Rabson + * All rights reserved. + * + * Portions Copyright (c) 2009 Apple Inc. All rights reserved. + * Portions Copyright (c) 2019 AuriStor, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "mech_locl.h" + +GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL +gssspi_query_meta_data( + OM_uint32 *minor_status, + gss_const_OID input_mech_type, + gss_cred_id_t input_cred_handle, + gss_ctx_id_t *context_handle, + gss_const_name_t target_name, + OM_uint32 req_flags, + gss_buffer_t meta_data) +{ + OM_uint32 major_status, junk; + gssapi_mech_interface m; + struct _gss_name *name = (struct _gss_name *) target_name; + struct _gss_mechanism_name *mn; + struct _gss_context *ctx = (struct _gss_context *) *context_handle; + gss_cred_id_t cred_handle; + int allocated_ctx; + gss_const_OID mech_type = input_mech_type; + + *minor_status = 0; + + _mg_buffer_zero(meta_data); + + if (mech_type == GSS_C_NO_OID) + return GSS_S_BAD_MECH; + + if (ctx == NULL) { + ctx = calloc(1, sizeof(struct _gss_context)); + if (ctx == NULL) { + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + + m = ctx->gc_mech = __gss_get_mechanism(mech_type); + if (m == NULL) { + free(ctx); + return GSS_S_BAD_MECH; + } + allocated_ctx = 1; + } else { + m = ctx->gc_mech; + mech_type = &m->gm_mech_oid; + allocated_ctx = 0; + } + + if (m->gm_query_meta_data == NULL) { + major_status = GSS_S_BAD_MECH; + goto cleanup; + } + + major_status = _gss_find_mn(minor_status, name, mech_type, &mn); + if (major_status != GSS_S_COMPLETE) + goto cleanup; + + if (m->gm_flags & GM_USE_MG_CRED) + cred_handle = input_cred_handle; + else + cred_handle = _gss_mg_find_mech_cred(input_cred_handle, mech_type); + + if (input_cred_handle != GSS_C_NO_CREDENTIAL && + cred_handle == NULL) { + major_status = GSS_S_NO_CRED; + goto cleanup; + } + + /* note: mechanism is not obligated to allocate a context on success */ + major_status = m->gm_query_meta_data(minor_status, + mech_type, + cred_handle, + &ctx->gc_ctx, + mn ? mn->gmn_name : GSS_C_NO_NAME, + req_flags, + meta_data); + if (major_status != GSS_S_COMPLETE) + _gss_mg_error(m, *minor_status); + +cleanup: + if (major_status != GSS_S_COMPLETE || ctx->gc_ctx == GSS_C_NO_CONTEXT) + gss_delete_sec_context(&junk, (gss_ctx_id_t *)&ctx, GSS_C_NO_BUFFER); + + *context_handle = (gss_ctx_id_t) ctx; + + _gss_mg_log(10, "gss-qmd: return %d/%d", (int)major_status, (int)*minor_status); + + return major_status; +} diff --git a/lib/gssapi/mech/mech_locl.h b/lib/gssapi/mech/mech_locl.h index 5e65a0786..0d74091e0 100644 --- a/lib/gssapi/mech/mech_locl.h +++ b/lib/gssapi/mech/mech_locl.h @@ -56,6 +56,7 @@ #include #include #include +#include #include diff --git a/lib/gssapi/mech/utils.h b/lib/gssapi/mech/utils.h index 77e308f28..b0c9ead1d 100644 --- a/lib/gssapi/mech/utils.h +++ b/lib/gssapi/mech/utils.h @@ -31,3 +31,13 @@ OM_uint32 _gss_free_oid(OM_uint32 *, gss_OID); OM_uint32 _gss_intern_oid(OM_uint32 *, gss_const_OID, gss_OID *); OM_uint32 _gss_copy_buffer(OM_uint32 *minor_status, const gss_buffer_t from_buf, gss_buffer_t to_buf); + +void _gss_mg_encode_le_uint32(uint32_t n, uint8_t *p); +void _gss_mg_decode_le_uint32(const void *ptr, uint32_t *n); +void _gss_mg_encode_be_uint32(uint32_t n, uint8_t *p); +void _gss_mg_decode_be_uint32(const void *ptr, uint32_t *n); + +void _gss_mg_encode_le_uint16(uint16_t n, uint8_t *p); +void _gss_mg_decode_le_uint16(const void *ptr, uint16_t *n); +void _gss_mg_encode_be_uint16(uint16_t n, uint8_t *p); +void _gss_mg_decode_be_uint16(const void *ptr, uint16_t *n); diff --git a/lib/gssapi/ntlm/external.c b/lib/gssapi/ntlm/external.c index 3c0fba86c..011627975 100644 --- a/lib/gssapi/ntlm/external.c +++ b/lib/gssapi/ntlm/external.c @@ -127,6 +127,9 @@ static gssapi_mech_interface_desc ntlm_mech = { NULL, /* gm_store_cred_into */ NULL, /* gm_set_neg_mechs */ NULL, /* gm_get_neg_mechs */ + NULL, /* gm_query_mechanism_info */ + NULL, /* gm_query_meta_data */ + NULL, /* gm_exchange_meta_data */ NULL, /* gm_compat */ }; diff --git a/lib/gssapi/oid.txt b/lib/gssapi/oid.txt index 5eaa4f585..20b749b41 100644 --- a/lib/gssapi/oid.txt +++ b/lib/gssapi/oid.txt @@ -52,6 +52,8 @@ oid base GSS_NETLOGON_NT_NETBIOS_DNS_NAME 1.2.752.43.14.5 #/* GSS_KRB5_EXTRACT_AUTHZ_DATA_FROM_SEC_CONTEXT_X.128 */ oid base GSS_C_INQ_WIN2K_PAC_X 1.2.752.43.13.3.128 oid base GSS_C_INQ_SSPI_SESSION_KEY 1.2.840.113554.1.2.2.5.5 +oid base GSS_C_INQ_NEGOEX_KEY 1.2.840.113554.1.2.2.5.16 +oid base GSS_C_INQ_NEGOEX_VERIFY_KEY 1.2.840.113554.1.2.2.5.17 #/* # * "Standard" mechs @@ -65,6 +67,7 @@ oid base GSS_SPNEGO_MECHANISM 1.3.6.1.5.5.2 oid base GSS_C_PEER_HAS_UPDATED_SPNEGO 1.3.6.1.4.1.5322.19.5 oid base GSS_C_NTLM_RESET_CRYPTO 1.3.6.1.4.1.7165.655.1.3 +oid base GSS_NEGOEX_MECHANISM 1.3.6.1.4.1.311.2.2.30 #/* @@ -110,6 +113,7 @@ oid base GSS_C_MA_CBINDINGS 1.3.6.1.5.5.13.24 oid base GSS_C_MA_PFS 1.3.6.1.5.5.13.25 oid base GSS_C_MA_COMPRESS 1.3.6.1.5.5.13.26 oid base GSS_C_MA_CTX_TRANS 1.3.6.1.5.5.13.27 +oid base GSS_C_MA_NEGOEX_AND_SPNEGO 1.2.840.113554.1.2.2.5.18 desc ma GSS_C_MA_MECH_CONCRETE "concrete-mech" "Indicates that a mech is neither a pseudo-mechanism nor a composite mechanism" desc ma GSS_C_MA_MECH_PSEUDO "pseudo-mech" "" @@ -138,3 +142,4 @@ desc ma GSS_C_MA_CBINDINGS "channel-bindings" "" desc ma GSS_C_MA_PFS "pfs" "" desc ma GSS_C_MA_COMPRESS "compress" "" desc ma GSS_C_MA_CTX_TRANS "context-transfer" "" +desc ma GSS_C_MA_NEGOEX_AND_SPNEGO "negoex-and-spnego" "Indicates that a mechanism supports both NegoEx and SPNEGO" diff --git a/lib/gssapi/spnego/accept_sec_context.c b/lib/gssapi/spnego/accept_sec_context.c index 889d1636f..b4384fd25 100644 --- a/lib/gssapi/spnego/accept_sec_context.c +++ b/lib/gssapi/spnego/accept_sec_context.c @@ -63,37 +63,60 @@ send_reject (OM_uint32 *minor_status, } static OM_uint32 -acceptor_approved(gss_const_cred_id_t input_cred, - gss_name_t target_name, +acceptor_approved(OM_uint32 *minor_status, + void *userptr, + gss_const_name_t target_name, + gss_const_cred_id_t cred_handle, gss_OID mech) { gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; - gss_OID_set oidset; + gss_OID_set oidset = GSS_C_NO_OID_SET; OM_uint32 junk, ret; if (target_name == GSS_C_NO_NAME) return GSS_S_COMPLETE; - if (input_cred != GSS_C_NO_CREDENTIAL) { - return gss_inquire_cred_by_mech(&junk, input_cred, mech, - NULL, NULL, NULL, NULL); + if (gss_oid_equal(mech, GSS_NEGOEX_MECHANISM)) { + size_t i; + + ret = _gss_spnego_indicate_mechs(minor_status, &oidset); + if (ret != GSS_S_COMPLETE) + return ret; + + /* 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); } - gss_create_empty_oid_set(&junk, &oidset); - gss_add_oid_set_member(&junk, mech, &oidset); - - ret = gss_acquire_cred(&junk, target_name, GSS_C_INDEFINITE, oidset, - GSS_C_ACCEPT, &cred, NULL, NULL); gss_release_oid_set(&junk, &oidset); - if (ret != GSS_S_COMPLETE) - return ret; gss_release_cred(&junk, &cred); - return GSS_S_COMPLETE; + return ret; } static OM_uint32 send_supported_mechs (OM_uint32 *minor_status, + gssspnego_ctx ctx, gss_const_cred_id_t acceptor_cred, gss_buffer_t output_token) { @@ -109,8 +132,8 @@ send_supported_mechs (OM_uint32 *minor_status, nt.u.negTokenInit.mechToken = NULL; nt.u.negTokenInit.negHints = NULL; - ret = _gss_spnego_indicate_mechtypelist(minor_status, GSS_C_NO_NAME, - acceptor_approved, 1, acceptor_cred, + ret = _gss_spnego_indicate_mechtypelist(minor_status, NULL, + acceptor_approved, ctx, 1, acceptor_cred, &nt.u.negTokenInit.mechTypes, NULL); if (ret != GSS_S_COMPLETE) { return ret; @@ -160,13 +183,15 @@ send_supported_mechs (OM_uint32 *minor_status, static OM_uint32 send_accept (OM_uint32 *minor_status, gssspnego_ctx context_handle, + int optimistic_mech_ok, gss_buffer_t mech_token, - int initial_response, + gss_const_OID selected_mech, /* valid on initial response only */ gss_buffer_t mech_buf, gss_buffer_t output_token) { + int initial_response = (selected_mech != GSS_C_NO_OID); NegotiationToken nt; - OM_uint32 ret; + OM_uint32 ret, minor; gss_buffer_desc mech_mic_buf; size_t size; @@ -180,7 +205,7 @@ send_accept (OM_uint32 *minor_status, return GSS_S_FAILURE; } - if (context_handle->open) { + if (context_handle->flags.open) { if (mech_token != GSS_C_NO_BUFFER && mech_token->length != 0 && mech_buf != GSS_C_NO_BUFFER) @@ -188,7 +213,7 @@ send_accept (OM_uint32 *minor_status, else *(nt.u.negTokenResp.negResult) = accept_completed; } else { - if (initial_response && context_handle->require_mic) + if (initial_response && !optimistic_mech_ok) *(nt.u.negTokenResp.negResult) = request_mic; else *(nt.u.negTokenResp.negResult) = accept_incomplete; @@ -197,20 +222,22 @@ send_accept (OM_uint32 *minor_status, if (initial_response) { ALLOC(nt.u.negTokenResp.supportedMech, 1); if (nt.u.negTokenResp.supportedMech == NULL) { - free_NegotiationToken(&nt); *minor_status = ENOMEM; - return GSS_S_FAILURE; + ret = GSS_S_FAILURE; + goto out; } - ret = der_get_oid(context_handle->preferred_mech_type->elements, - context_handle->preferred_mech_type->length, + ret = der_get_oid(selected_mech->elements, + selected_mech->length, nt.u.negTokenResp.supportedMech, NULL); if (ret) { - free_NegotiationToken(&nt); *minor_status = ENOMEM; - return GSS_S_FAILURE; + ret = GSS_S_FAILURE; + goto out; } + + _gss_spnego_log_mech("acceptor sending selected mech", selected_mech); } else { nt.u.negTokenResp.supportedMech = NULL; } @@ -218,9 +245,9 @@ send_accept (OM_uint32 *minor_status, if (mech_token != GSS_C_NO_BUFFER && mech_token->length != 0) { ALLOC(nt.u.negTokenResp.responseToken, 1); if (nt.u.negTokenResp.responseToken == NULL) { - free_NegotiationToken(&nt); *minor_status = ENOMEM; - return GSS_S_FAILURE; + ret = GSS_S_FAILURE; + goto out; } nt.u.negTokenResp.responseToken->length = mech_token->length; nt.u.negTokenResp.responseToken->data = mech_token->value; @@ -236,25 +263,22 @@ send_accept (OM_uint32 *minor_status, 0, mech_buf, &mech_mic_buf); - if (ret == GSS_S_COMPLETE && - gss_oid_equal(context_handle->negotiated_mech_type, - GSS_NTLM_MECHANISM)) - _gss_spnego_ntlm_reset_crypto(minor_status, context_handle, 0); if (ret == GSS_S_COMPLETE) { + _gss_spnego_ntlm_reset_crypto(&minor, context_handle, FALSE); + ALLOC(nt.u.negTokenResp.mechListMIC, 1); if (nt.u.negTokenResp.mechListMIC == NULL) { gss_release_buffer(minor_status, &mech_mic_buf); - free_NegotiationToken(&nt); *minor_status = ENOMEM; - return GSS_S_FAILURE; + ret = GSS_S_FAILURE; + goto out; } nt.u.negTokenResp.mechListMIC->length = mech_mic_buf.length; nt.u.negTokenResp.mechListMIC->data = mech_mic_buf.value; } else if (ret == GSS_S_UNAVAILABLE) { nt.u.negTokenResp.mechListMIC = NULL; } else { - free_NegotiationToken(&nt); - return ret; + goto out; } } else @@ -264,9 +288,9 @@ send_accept (OM_uint32 *minor_status, output_token->value, output_token->length, &nt, &size, ret); if (ret) { - free_NegotiationToken(&nt); - *minor_status = ret; - return GSS_S_FAILURE; + *minor_status = ENOMEM; + ret = GSS_S_FAILURE; + goto out; } /* @@ -279,136 +303,165 @@ send_accept (OM_uint32 *minor_status, ret = GSS_S_COMPLETE; else ret = GSS_S_CONTINUE_NEEDED; + + out: free_NegotiationToken(&nt); return ret; } +/* + * Return the default acceptor identity based on the local hostname + * or the GSSAPI_SPNEGO_NAME environment variable. + */ static OM_uint32 -verify_mechlist_mic - (OM_uint32 *minor_status, - gssspnego_ctx context_handle, - gss_buffer_t mech_buf, - heim_octet_string *mechListMIC - ) +default_acceptor_name(OM_uint32 *minor_status, + gss_name_t *namep) { - OM_uint32 ret; - gss_buffer_desc mic_buf; + OM_uint32 major_status; + gss_buffer_desc namebuf; + char *str = NULL, *host, hostname[MAXHOSTNAMELEN]; - if (context_handle->verified_mic) { - /* This doesn't make sense, we've already verified it? */ - *minor_status = 0; - return GSS_S_DUPLICATE_TOKEN; + *namep = GSS_C_NO_NAME; + + 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; } - if (mechListMIC == NULL) { - *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; - } + namebuf.length = strlen(host); + namebuf.value = host; - mic_buf.length = mechListMIC->length; - mic_buf.value = mechListMIC->data; + major_status = gss_import_name(minor_status, &namebuf, + GSS_C_NT_HOSTBASED_SERVICE, namep); - ret = gss_verify_mic(minor_status, - context_handle->negotiated_ctx_id, - mech_buf, - &mic_buf, - NULL); + free(str); - if (ret != GSS_S_COMPLETE) - ret = GSS_S_DEFECTIVE_TOKEN; - - 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; + 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; + + 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; + + negoex_selected = TRUE; + } + + 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; + + break; + } } - *minor_status = 0; + /* 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; - /* 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; - - - ret = gss_indicate_mechs(&junk, &mechs); - if (ret) - return (ret); - - for (i = 0; i < mechs->count; i++) - if (gss_oid_equal(&mechs->elements[i], oidp)) - break; - - if (i == mechs->count) { - gss_release_oid_set(&junk, &mechs); - return GSS_S_BAD_MECH; - } - gss_release_oid_set(&junk, &mechs); - - ret = gss_duplicate_oid(minor_status, - &oid, /* possibly this should be oidp */ - mech_p); + 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; + /* + * 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; } - namebuf.length = strlen(host); - namebuf.value = host; + ret = acceptor_approved(minor_status, ctx, name, cred, selected_mech); - 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(GSS_C_NO_CREDENTIAL, name, *mech_p); gss_release_name(&junk, &name); } + /* Stash optimistic mech for use by _gss_spnego_require_mechlist_mic() */ + if (ret == GSS_S_COMPLETE && !verify_p) + ret = gss_duplicate_oid(minor_status, &oid, &ctx->preferred_mech_type); + + if (ret == GSS_S_COMPLETE) { + *minor_status = 0; + + *advertised_mech_p = ctx->selected_mech_type = selected_mech; + + /* if the initiator used the broken MS OID, send that instead */ + if (includeMSCompatOID && gss_oid_equal(selected_mech, GSS_KRB5_MECHANISM)) + *advertised_mech_p = &_gss_spnego_mskrb_mechanism_oid_desc; + } + return ret; } @@ -417,25 +470,19 @@ static OM_uint32 acceptor_complete(OM_uint32 * minor_status, gssspnego_ctx ctx, int *get_mic, - gss_buffer_t mech_buf, gss_buffer_t mech_input_token, gss_buffer_t mech_output_token, heim_octet_string *mic, gss_buffer_t output_token) { + gss_buffer_desc buf = GSS_C_EMPTY_BUFFER; OM_uint32 ret; - int require_mic, verify_mic; + int verify_mic; - ret = _gss_spnego_require_mechlist_mic(minor_status, ctx, &require_mic); - if (ret) - return ret; + ctx->flags.require_mic = 1; + ctx->flags.safe_omit = _gss_spnego_safe_omit_mechlist_mic(ctx); - ctx->require_mic = require_mic; - - if (mic != NULL) - require_mic = 1; - - if (ctx->open && require_mic) { + if (ctx->flags.open) { if (mech_input_token == GSS_C_NO_BUFFER) { /* Even/One */ verify_mic = 1; *get_mic = 0; @@ -447,29 +494,20 @@ acceptor_complete(OM_uint32 * minor_status, *get_mic = 1; } - if (verify_mic || *get_mic) { - int eret; - size_t buf_len = 0; - - ASN1_MALLOC_ENCODE(MechTypeList, - mech_buf->value, mech_buf->length, - &ctx->initiator_mech_types, &buf_len, eret); - if (eret) { - *minor_status = eret; - return GSS_S_FAILURE; - } - heim_assert(mech_buf->length == buf_len, "Internal ASN.1 error"); - UNREACHABLE(return GSS_S_FAILURE); - } - - if (verify_mic) { - ret = verify_mechlist_mic(minor_status, ctx, mech_buf, mic); + if (verify_mic && mic == NULL && ctx->flags.safe_omit) { + /* + * Peer is old and didn't send a mic while we expected + * one, but since it safe to omit, let do that + */ + } else if (verify_mic) { + ret = _gss_spnego_verify_mechtypes_mic(minor_status, ctx, mic); if (ret) { if (*get_mic) - send_reject (minor_status, output_token); + send_reject(minor_status, output_token); + if (buf.value) + free(buf.value); return ret; } - ctx->verified_mic = 1; } } else *get_mic = 0; @@ -477,6 +515,57 @@ acceptor_complete(OM_uint32 * minor_status, return GSS_S_COMPLETE; } +/* + * Call gss_accept_sec_context() via mechglue or NegoEx, depending on + * whether mech_oid is NegoEx. + */ + +static OM_uint32 +mech_accept(OM_uint32 *minor_status, + gssspnego_ctx ctx, + gss_const_cred_id_t acceptor_cred_handle, + gss_const_buffer_t input_token_buffer, + const gss_channel_bindings_t input_chan_bindings, + gss_buffer_t output_token, + gss_cred_id_t *delegated_cred_handle) +{ + OM_uint32 ret, junk; + + heim_assert(ctx->selected_mech_type != GSS_C_NO_OID, + "mech_accept called with no selected mech"); + + if (gss_oid_equal(ctx->selected_mech_type, GSS_NEGOEX_MECHANISM)) { + ret = _gss_negoex_accept(minor_status, + ctx, + (gss_cred_id_t)acceptor_cred_handle, + input_token_buffer, + input_chan_bindings, + output_token, + delegated_cred_handle); + } else { + if (ctx->mech_src_name != GSS_C_NO_NAME) + gss_release_name(&junk, &ctx->mech_src_name); + + ret = gss_accept_sec_context(minor_status, + &ctx->negotiated_ctx_id, + acceptor_cred_handle, + (gss_buffer_t)input_token_buffer, + input_chan_bindings, + &ctx->mech_src_name, + &ctx->negotiated_mech_type, + output_token, + &ctx->mech_flags, + &ctx->mech_time_rec, + delegated_cred_handle); + if (GSS_ERROR(ret)) + gss_mg_collect_error(ctx->negotiated_mech_type, ret, *minor_status); + else if (ctx->negotiated_mech_type != GSS_C_NO_OID && + !gss_oid_equal(ctx->negotiated_mech_type, ctx->selected_mech_type)) + _gss_mg_log(1, "spnego client didn't send the mech they said they would"); + } + + return ret; +} static OM_uint32 GSSAPI_CALLCONV acceptor_start @@ -495,23 +584,24 @@ acceptor_start { OM_uint32 ret, junk; NegotiationToken nt; - size_t nt_len; + gss_OID_set supported_mechs = GSS_C_NO_OID_SET; + size_t size; NegTokenInit *ni; gss_buffer_desc data; gss_buffer_t mech_input_token = GSS_C_NO_BUFFER; gss_buffer_desc mech_output_token; - gss_buffer_desc mech_buf; - gss_OID preferred_mech_type = GSS_C_NO_OID; gssspnego_ctx ctx; int get_mic = 0; int first_ok = 0; + gss_const_OID advertised_mech = GSS_C_NO_OID; + + memset(&nt, 0, sizeof(nt)); mech_output_token.value = NULL; mech_output_token.length = 0; - mech_buf.value = NULL; if (input_token_buffer->length == 0) - return send_supported_mechs (minor_status, + return send_supported_mechs (minor_status, NULL, acceptor_cred_handle, output_token); ret = _gss_spnego_alloc_sec_context(minor_status, context_handle); @@ -520,6 +610,8 @@ acceptor_start ctx = (gssspnego_ctx)*context_handle; + HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); + /* * The GSS-API encapsulation is only present on the initial * context token (negTokenInit). @@ -528,36 +620,58 @@ acceptor_start GSS_SPNEGO_MECHANISM, &data); if (ret) - return ret; + goto out; - ret = decode_NegotiationToken(data.value, data.length, &nt, &nt_len); + ret = decode_NegotiationToken(data.value, data.length, &nt, &size); gss_release_buffer(minor_status, &data); if (ret) { *minor_status = ret; - return GSS_S_DEFECTIVE_TOKEN; + ret = GSS_S_DEFECTIVE_TOKEN; + goto out; } if (nt.element != choice_NegotiationToken_negTokenInit) { *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; + ret = GSS_S_DEFECTIVE_TOKEN; + goto out; } ni = &nt.u.negTokenInit; if (ni->mechTypes.len < 1) { free_NegotiationToken(&nt); *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; + ret = GSS_S_DEFECTIVE_TOKEN; + goto out; } - HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); + _gss_spnego_log_mechTypes(&ni->mechTypes); - ret = copy_MechTypeList(&ni->mechTypes, &ctx->initiator_mech_types); - if (ret) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - free_NegotiationToken(&nt); - *minor_status = ret; - return GSS_S_FAILURE; + { + MechTypeList mt; + int kret; + + mt.len = ni->mechTypes.len; + mt.val = ni->mechTypes.val; + + ASN1_MALLOC_ENCODE(MechTypeList, + ctx->NegTokenInit_mech_types.value, + ctx->NegTokenInit_mech_types.length, + &mt, &size, kret); + if (kret) { + *minor_status = kret; + ret = GSS_S_FAILURE; + goto out; + } } + if (acceptor_cred_handle != GSS_C_NO_CREDENTIAL) + ret = _gss_spnego_inquire_cred_mechs(minor_status, + acceptor_cred_handle, + &supported_mechs); + else + ret = _gss_spnego_indicate_mechs(minor_status, &supported_mechs); + if (ret != GSS_S_COMPLETE) + goto out; + /* * First we try the opportunistic token if we have support for it, * don't try to verify we have credential for the token, @@ -566,41 +680,39 @@ acceptor_start */ ret = select_mech(minor_status, + ctx, + acceptor_cred_handle, + supported_mechs, &ni->mechTypes.val[0], - 0, - &preferred_mech_type); + 0, /* optimistic token */ + &advertised_mech); - if (ret == 0 && ni->mechToken != NULL) { + if (ret == GSS_S_COMPLETE && ni->mechToken != NULL) { gss_buffer_desc ibuf; ibuf.length = ni->mechToken->length; ibuf.value = ni->mechToken->data; mech_input_token = &ibuf; - if (ctx->mech_src_name != GSS_C_NO_NAME) - gss_release_name(&junk, &ctx->mech_src_name); - - ret = gss_accept_sec_context(minor_status, - &ctx->negotiated_ctx_id, - acceptor_cred_handle, - mech_input_token, - input_chan_bindings, - &ctx->mech_src_name, - &ctx->negotiated_mech_type, - &mech_output_token, - &ctx->mech_flags, - &ctx->mech_time_rec, - delegated_cred_handle); + _gss_spnego_log_mech("acceptor selected opportunistic mech", ctx->selected_mech_type); + ret = mech_accept(&junk, + ctx, + acceptor_cred_handle, + mech_input_token, + input_chan_bindings, + &mech_output_token, + delegated_cred_handle); if (ret == GSS_S_COMPLETE || ret == GSS_S_CONTINUE_NEEDED) { - ctx->preferred_mech_type = preferred_mech_type; - if (ret == GSS_S_COMPLETE) - ctx->open = 1; + first_ok = 1; + } else { + ctx->selected_mech_type = GSS_C_NO_OID; + } + if (ret == GSS_S_COMPLETE) { ret = acceptor_complete(minor_status, ctx, &get_mic, - &mech_buf, mech_input_token, &mech_output_token, ni->mechListMIC, @@ -608,10 +720,14 @@ acceptor_start if (ret != GSS_S_COMPLETE) goto out; - first_ok = 1; - } else { - gss_mg_collect_error(preferred_mech_type, ret, *minor_status); + ctx->flags.open = 1; } + } else { + *minor_status = 0; + HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); + return gss_mg_set_error_string(GSS_C_NO_OID, GSS_S_NO_CONTEXT, + *minor_status, + "SPNEGO acceptor didn't find a prefered mechanism"); } /* @@ -621,24 +737,25 @@ acceptor_start if (!first_ok && ni->mechToken != NULL) { size_t j; - preferred_mech_type = GSS_C_NO_OID; - /* Call glue layer to find first mech we support */ for (j = 1; j < ni->mechTypes.len; ++j) { - ret = select_mech(minor_status, + ret = select_mech(&junk, + ctx, + acceptor_cred_handle, + supported_mechs, &ni->mechTypes.val[j], - 1, - &preferred_mech_type); - if (ret == 0) + 1, /* not optimistic token */ + &advertised_mech); + if (ret == GSS_S_COMPLETE) { + _gss_spnego_log_mech("acceptor selected non-opportunistic mech", ctx->selected_mech_type); break; + } } - if (preferred_mech_type == GSS_C_NO_OID) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - free_NegotiationToken(&nt); - return ret; + if (ctx->selected_mech_type == GSS_C_NO_OID) { + heim_assert(ret != GSS_S_COMPLETE, "no oid and no error code?"); + *minor_status = junk; + goto out; } - - ctx->preferred_mech_type = preferred_mech_type; } /* @@ -647,20 +764,18 @@ acceptor_start ret = send_accept (minor_status, ctx, + first_ok, &mech_output_token, - 1, - get_mic ? &mech_buf : NULL, + advertised_mech, + get_mic ? &ctx->NegTokenInit_mech_types : NULL, output_token); if (ret) goto out; out: + gss_release_oid_set(&junk, &supported_mechs); if (mech_output_token.value != NULL) gss_release_buffer(&junk, &mech_output_token); - if (mech_buf.value != NULL) { - free(mech_buf.value); - mech_buf.value = NULL; - } free_NegotiationToken(&nt); @@ -705,18 +820,15 @@ acceptor_continue gss_cred_id_t *delegated_cred_handle ) { - OM_uint32 ret, ret2, minor; + OM_uint32 ret, ret2, minor, junk; NegotiationToken nt; size_t nt_len; NegTokenResp *na; unsigned int negResult = accept_incomplete; gss_buffer_t mech_input_token = GSS_C_NO_BUFFER; gss_buffer_t mech_output_token = GSS_C_NO_BUFFER; - gss_buffer_desc mech_buf; gssspnego_ctx ctx; - mech_buf.value = NULL; - ctx = (gssspnego_ctx)*context_handle; /* @@ -745,9 +857,8 @@ acceptor_continue { gss_buffer_desc ibuf, obuf; - int require_mic, get_mic = 0; + int get_mic = 0; int require_response; - heim_octet_string *mic; if (na->responseToken != NULL) { ibuf.length = na->responseToken->length; @@ -760,53 +871,31 @@ acceptor_continue if (mech_input_token != GSS_C_NO_BUFFER) { - if (ctx->mech_src_name != GSS_C_NO_NAME) - gss_release_name(&minor, &ctx->mech_src_name); - - ret = gss_accept_sec_context(&minor, - &ctx->negotiated_ctx_id, - acceptor_cred_handle, - mech_input_token, - input_chan_bindings, - &ctx->mech_src_name, - &ctx->negotiated_mech_type, - &obuf, - &ctx->mech_flags, - &ctx->mech_time_rec, - delegated_cred_handle); - + ret = mech_accept(minor_status, + ctx, + acceptor_cred_handle, + mech_input_token, + input_chan_bindings, + &obuf, + delegated_cred_handle); if (ret == GSS_S_COMPLETE || ret == GSS_S_CONTINUE_NEEDED) { mech_output_token = &obuf; } if (ret != GSS_S_COMPLETE && ret != GSS_S_CONTINUE_NEEDED) { free_NegotiationToken(&nt); - gss_mg_collect_error(ctx->negotiated_mech_type, ret, minor); - send_reject (minor_status, output_token); + send_reject(&junk, output_token); HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); return ret; } if (ret == GSS_S_COMPLETE) - ctx->open = 1; + ctx->flags.open = 1; } else ret = GSS_S_COMPLETE; - ret2 = _gss_spnego_require_mechlist_mic(minor_status, - ctx, - &require_mic); - if (ret2) - goto out; - - ctx->require_mic = require_mic; - - mic = na->mechListMIC; - if (mic != NULL) - require_mic = 1; - if (ret == GSS_S_COMPLETE) ret = acceptor_complete(minor_status, ctx, &get_mic, - &mech_buf, mech_input_token, mech_output_token, na->mechListMIC, @@ -823,26 +912,26 @@ acceptor_continue */ if ((mech_output_token != GSS_C_NO_BUFFER && mech_output_token->length != 0) - || (ctx->open && negResult == accept_incomplete) + || (ctx->flags.open && negResult == accept_incomplete) || require_response || get_mic) { ret2 = send_accept (minor_status, ctx, + 0, /* ignored on subsequent tokens */ mech_output_token, - 0, - get_mic ? &mech_buf : NULL, + GSS_C_NO_OID, + get_mic ? &ctx->NegTokenInit_mech_types : NULL, output_token); if (ret2) goto out; - } + } else + ret2 = GSS_S_COMPLETE; out: if (ret2 != GSS_S_COMPLETE) ret = ret2; if (mech_output_token != NULL) gss_release_buffer(&minor, mech_output_token); - if (mech_buf.value != NULL) - free(mech_buf.value); free_NegotiationToken(&nt); } diff --git a/lib/gssapi/spnego/compat.c b/lib/gssapi/spnego/compat.c index 0dc6425c8..576e27459 100644 --- a/lib/gssapi/spnego/compat.c +++ b/lib/gssapi/spnego/compat.c @@ -2,6 +2,8 @@ * Copyright (c) 2004, PADL Software Pty Ltd. * All rights reserved. * + * Portions Copyright (c) 2009 Apple Inc. All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -43,9 +45,6 @@ gss_OID_desc _gss_spnego_mskrb_mechanism_oid_desc = {9, rk_UNCONST("\x2a\x86\x48\x82\xf7\x12\x01\x02\x02")}; -gss_OID_desc _gss_spnego_krb5_mechanism_oid_desc = - {9, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02")}; - /* * Allocate a SPNEGO context handle */ @@ -61,28 +60,33 @@ _gss_spnego_alloc_sec_context (OM_uint32 * minor_status, return GSS_S_FAILURE; } - ctx->initiator_mech_types.len = 0; - ctx->initiator_mech_types.val = NULL; + ctx->NegTokenInit_mech_types.value = NULL; + ctx->NegTokenInit_mech_types.length = 0; + ctx->preferred_mech_type = GSS_C_NO_OID; + ctx->selected_mech_type = GSS_C_NO_OID; ctx->negotiated_mech_type = GSS_C_NO_OID; + ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT; - /* - * Cache these so we can return them before returning - * GSS_S_COMPLETE, even if the mechanism has itself - * completed earlier - */ ctx->mech_flags = 0; ctx->mech_time_rec = 0; ctx->mech_src_name = GSS_C_NO_NAME; - ctx->open = 0; - ctx->local = 0; - ctx->require_mic = 0; - ctx->verified_mic = 0; + ctx->flags.open = 0; + ctx->flags.local = 0; + ctx->flags.peer_require_mic = 0; + ctx->flags.require_mic = 0; + ctx->flags.verified_mic = 0; HEIMDAL_MUTEX_init(&ctx->ctx_id_mutex); + ctx->negoex_step = 0; + ctx->negoex_transcript = NULL; + ctx->negoex_seqnum = 0; + HEIM_TAILQ_INIT(&ctx->negoex_mechs); + memset(ctx->negoex_conv_id, 0, GUID_LENGTH); + *context_handle = (gss_ctx_id_t)ctx; return GSS_S_COMPLETE; @@ -119,11 +123,12 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_internal_delete_sec_context return GSS_S_NO_CONTEXT; } - if (ctx->initiator_mech_types.val != NULL) - free_MechTypeList(&ctx->initiator_mech_types); + if (ctx->NegTokenInit_mech_types.value) + free(ctx->NegTokenInit_mech_types.value); - gss_release_oid(&minor, &ctx->preferred_mech_type); + ctx->preferred_mech_type = GSS_C_NO_OID; ctx->negotiated_mech_type = GSS_C_NO_OID; + ctx->selected_mech_type = GSS_C_NO_OID; gss_release_name(&minor, &ctx->target_name); gss_release_name(&minor, &ctx->mech_src_name); @@ -137,6 +142,8 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_internal_delete_sec_context ret = GSS_S_COMPLETE; } + _gss_negoex_release_context(ctx); + HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); HEIMDAL_MUTEX_destroy(&ctx->ctx_id_mutex); @@ -146,93 +153,121 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_internal_delete_sec_context } /* - * For compatability with the Windows SPNEGO implementation, the - * default is to ignore the mechListMIC unless CFX is used and - * a non-preferred mechanism was negotiated + * Returns TRUE if it is safe to omit mechListMIC because the preferred + * mechanism was selected, and the peer did not require it. */ -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_require_mechlist_mic(OM_uint32 *minor_status, - gssspnego_ctx ctx, - int *require_mic) +int +_gss_spnego_safe_omit_mechlist_mic(gssspnego_ctx ctx) { - gss_buffer_set_t buffer_set = GSS_C_NO_BUFFER_SET; - OM_uint32 minor; + int safe_omit = 0; - *minor_status = 0; - *require_mic = 0; + if (ctx->flags.peer_require_mic == FALSE) + safe_omit = gss_oid_equal(ctx->selected_mech_type, ctx->preferred_mech_type); - if (ctx == NULL) { - return GSS_S_COMPLETE; + if (safe_omit) + _gss_mg_log(10, "spnego: safe to omit mechListMIC, as preferred mechanism selected"); + else + _gss_mg_log(10, "spnego: mechListMIC required"); + + return safe_omit; +} + + +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); } - if (ctx->require_mic) { - /* Acceptor requested it: mandatory to honour */ - *require_mic = 1; - return GSS_S_COMPLETE; - } - - /* - * 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); - } - - /* 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; - - 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; + /* + * 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; } - 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 (_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; + } + + if (includeMSCompatOID && gss_oid_equal(mech_oid, GSS_KRB5_MECHANISM)) { + major = add_mech_type(minor_status, + &_gss_spnego_mskrb_mechanism_oid_desc, + mechtypelist); + if (major != GSS_S_COMPLETE) + return major; + } + + major = add_mech_type(minor_status, mech_oid, mechtypelist); + if (major != GSS_S_COMPLETE) + return major; + + if (*first_mech == GSS_C_NO_OID) + *first_mech = mech_oid; + + return GSS_S_COMPLETE; +} OM_uint32 GSSAPI_CALLCONV _gss_spnego_indicate_mechtypelist (OM_uint32 *minor_status, - gss_name_t target_name, - OM_uint32 (*func)(gss_const_cred_id_t, gss_name_t, gss_OID), + gss_const_name_t target_name, + OM_uint32 (*func)(OM_uint32 *, void *, gss_const_name_t, gss_const_cred_id_t, gss_OID), + void *userptr, int includeMSCompatOID, gss_const_cred_id_t cred_handle, MechTypeList *mechtypelist, @@ -240,94 +275,297 @@ _gss_spnego_indicate_mechtypelist (OM_uint32 *minor_status, { gss_OID_set supported_mechs = GSS_C_NO_OID_SET; gss_OID first_mech = GSS_C_NO_OID; - OM_uint32 ret; + OM_uint32 ret, minor; + OM_uint32 first_major = GSS_S_BAD_MECH, first_minor = 0; size_t i; + int present = FALSE; + int added_negoex = FALSE; mechtypelist->len = 0; mechtypelist->val = NULL; - if (cred_handle) { - ret = gss_inquire_cred(minor_status, - cred_handle, - NULL, - NULL, - NULL, - &supported_mechs); - } else { - ret = gss_indicate_mechs(minor_status, &supported_mechs); - } - - if (ret != GSS_S_COMPLETE) { + if (cred_handle != GSS_C_NO_CREDENTIAL) + ret = _gss_spnego_inquire_cred_mechs(minor_status, + cred_handle, &supported_mechs); + else + ret = _gss_spnego_indicate_mechs(minor_status, &supported_mechs); + if (ret != GSS_S_COMPLETE) return ret; + + 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 = 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); } - if (supported_mechs->count == 0) { - *minor_status = ENOENT; - gss_release_oid_set(minor_status, &supported_mechs); - return GSS_S_FAILURE; - } - - 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_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); - *minor_status = 0; - return GSS_S_BAD_MECH; - } + heim_assert(mechtypelist->len == 0 || first_mech != GSS_C_NO_OID, + "mechtypelist non-empty but no mech selected"); - if (preferred_mech != NULL) { - ret = gss_duplicate_oid(minor_status, first_mech, preferred_mech); - if (ret != GSS_S_COMPLETE) - free_MechTypeList(mechtypelist); - } - gss_release_oid_set(minor_status, &supported_mechs); + 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_DUPLICATE_TOKEN; + } + + 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"); + } + ctx->flags.verified_mic = 1; + + *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; + 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_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; + + oids[0] = *GSS_C_MA_DEPRECATED; + oids[1] = *GSS_C_MA_NOT_DFLT_MECH; + oids[2] = *GSS_C_MA_MECH_NEGO; + + 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; +} + diff --git a/lib/gssapi/spnego/context_stubs.c b/lib/gssapi/spnego/context_stubs.c index e50a81bad..88f37adcd 100644 --- a/lib/gssapi/spnego/context_stubs.c +++ b/lib/gssapi/spnego/context_stubs.c @@ -32,40 +32,6 @@ #include "spnego_locl.h" -static OM_uint32 -spnego_supported_mechs(OM_uint32 *minor_status, gss_OID_set *mechs) -{ - OM_uint32 ret, junk; - gss_OID_set m; - size_t i; - - ret = gss_indicate_mechs(minor_status, &m); - if (ret != GSS_S_COMPLETE) - return ret; - - ret = gss_create_empty_oid_set(minor_status, mechs); - if (ret != GSS_S_COMPLETE) { - gss_release_oid_set(&junk, &m); - return ret; - } - - for (i = 0; i < m->count; i++) { - if (gss_oid_equal(&m->elements[i], GSS_SPNEGO_MECHANISM)) - continue; - - ret = gss_add_oid_set_member(minor_status, &m->elements[i], mechs); - if (ret) { - gss_release_oid_set(&junk, &m); - gss_release_oid_set(&junk, mechs); - return ret; - } - } - gss_release_oid_set(&junk, &m); - return ret; -} - - - OM_uint32 GSSAPI_CALLCONV _gss_spnego_process_context_token (OM_uint32 *minor_status, gss_const_ctx_id_t context_handle, @@ -450,7 +416,7 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_import_sec_context ( return ret; } - ctx->open = 1; + ctx->flags.open = 1; /* don't bother filling in the rest of the fields */ HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); @@ -472,7 +438,7 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_inquire_names_for_mech ( *name_types = NULL; - ret = spnego_supported_mechs(minor_status, &mechs); + ret = _gss_spnego_indicate_mechs(minor_status, &mechs); if (ret != GSS_S_COMPLETE) return ret; diff --git a/lib/gssapi/spnego/cred_stubs.c b/lib/gssapi/spnego/cred_stubs.c index effba5833..7d3399ab9 100644 --- a/lib/gssapi/spnego/cred_stubs.c +++ b/lib/gssapi/spnego/cred_stubs.c @@ -68,48 +68,20 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_acquire_cred_from ) { OM_uint32 ret, tmp; - gss_OID_set_desc actual_desired_mechs; gss_OID_set mechs; - size_t i, j; *output_cred_handle = GSS_C_NO_CREDENTIAL; - ret = gss_indicate_mechs(minor_status, &mechs); + ret = _gss_spnego_indicate_mechs(minor_status, &mechs); if (ret != GSS_S_COMPLETE) return ret; - /* Remove ourselves from this list */ - actual_desired_mechs.count = mechs->count; - actual_desired_mechs.elements = malloc(actual_desired_mechs.count * - sizeof(gss_OID_desc)); - if (actual_desired_mechs.elements == NULL) { - *minor_status = ENOMEM; - ret = GSS_S_FAILURE; - goto out; - } - - for (i = 0, j = 0; i < mechs->count; i++) { - if (gss_oid_equal(&mechs->elements[i], GSS_SPNEGO_MECHANISM)) - continue; - - actual_desired_mechs.elements[j] = mechs->elements[i]; - j++; - } - actual_desired_mechs.count = j; - ret = gss_acquire_cred_from(minor_status, desired_name, - time_req, &actual_desired_mechs, + time_req, mechs, cred_usage, cred_store, output_cred_handle, actual_mechs, time_rec); gss_release_oid_set(&tmp, &mechs); - if (actual_desired_mechs.elements != NULL) { - free(actual_desired_mechs.elements); - } - - if (ret != GSS_S_COMPLETE) { - _gss_spnego_release_cred(&tmp, output_cred_handle); - } return ret; } @@ -235,9 +207,6 @@ _gss_spnego_set_neg_mechs (OM_uint32 *minor_status, break; } } - - /* for inner negotiation mechs, such as NegoEx */ - (void) gss_set_neg_mechs(&minor, cred_handle, mech_list); } else { /* * RFC 4178 says that GSS_Set_neg_mechs() on NULL credential sets diff --git a/lib/gssapi/spnego/external.c b/lib/gssapi/spnego/external.c index add000562..e0744f4b2 100644 --- a/lib/gssapi/spnego/external.c +++ b/lib/gssapi/spnego/external.c @@ -151,6 +151,9 @@ static gssapi_mech_interface_desc spnego_mech = { NULL, /* gm_store_cred_into */ _gss_spnego_set_neg_mechs, _gss_spnego_get_neg_mechs, + NULL, /* gm_query_mechanism_info */ + NULL, /* gm_query_meta_data */ + NULL, /* gm_exchange_meta_data */ NULL /* gm_compat */ }; @@ -159,3 +162,4 @@ __gss_spnego_initialize(void) { return &spnego_mech; } + diff --git a/lib/gssapi/spnego/init_sec_context.c b/lib/gssapi/spnego/init_sec_context.c index 0d275a36b..6cef4c87f 100644 --- a/lib/gssapi/spnego/init_sec_context.c +++ b/lib/gssapi/spnego/init_sec_context.c @@ -33,40 +33,86 @@ #include "spnego_locl.h" -/* - * Is target_name an sane target for `mech´. - */ +#define GSISC(name) \ +static \ +OM_uint32 name(OM_uint32 *, gss_const_cred_id_t, gssspnego_ctx, \ + gss_const_name_t, gss_const_OID, \ + OM_uint32, OM_uint32, const gss_channel_bindings_t, \ + gss_const_buffer_t, gss_buffer_t, \ + OM_uint32 *, OM_uint32 *) + +GSISC(spnego_initial); +GSISC(spnego_reply); +GSISC(wait_server_mic); +GSISC(step_completed); + + + /* + * Is target_name an sane target for `mech´. + */ static OM_uint32 -initiator_approved(gss_const_cred_id_t cred, - gss_name_t target_name, +initiator_approved(OM_uint32 *minor_status, + void *userptr, + gss_const_name_t target_name, + gss_const_cred_id_t cred, gss_OID mech) { OM_uint32 min_stat, maj_stat; gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; gss_buffer_desc out; + struct gssspnego_optimistic_ctx *sel = userptr; + gss_OID negotiated_mech_type = GSS_C_NO_OID; + OM_uint32 flags = 0, time_rec = 0; + auth_scheme scheme; + int negoex = 0; maj_stat = gss_init_sec_context(&min_stat, cred, &ctx, - target_name, + sel->target_name, mech, - 0, - GSS_C_INDEFINITE, - GSS_C_NO_CHANNEL_BINDINGS, + sel->req_flags, + sel->time_req, + sel->input_chan_bindings, GSS_C_NO_BUFFER, - NULL, + &negotiated_mech_type, &out, - NULL, - NULL); + &flags, + &time_rec); if (GSS_ERROR(maj_stat)) { gss_mg_collect_error(mech, maj_stat, min_stat); - return GSS_S_BAD_MECH; + *minor_status = min_stat; + return maj_stat; } - gss_release_buffer(&min_stat, &out); - gss_delete_sec_context(&min_stat, &ctx, NULL); - return GSS_S_COMPLETE; + if (gssspi_query_mechanism_info(&min_stat, mech, scheme) == GSS_S_COMPLETE) + negoex = 1; + + if (sel->preferred_mech_type == GSS_C_NO_OID) { + sel->preferred_mech_type = mech; + sel->negotiated_mech_type = negotiated_mech_type; + sel->optimistic_token = out; + sel->optimistic_flags = flags; + sel->optimistic_time_rec = time_rec; + sel->gssctx = ctx; + if (maj_stat == GSS_S_COMPLETE) + sel->complete = 1; + if (negoex) + memcpy(sel->scheme, scheme, GUID_LENGTH); + } else { + gss_release_buffer(&min_stat, &out); + gss_delete_sec_context(&min_stat, &ctx, NULL); + } + + maj_stat = GSS_S_COMPLETE; + + if (negoex) { + maj_stat = _gss_negoex_add_auth_mech(minor_status, sel->spnegoctx, + mech, scheme); + } + + return maj_stat; } /* @@ -78,42 +124,40 @@ initiator_approved(gss_const_cred_id_t cred, * must return GSS_S_CONTINUE_NEEDED if a token was generated. */ static OM_uint32 -spnego_reply_internal(OM_uint32 *minor_status, - gssspnego_ctx context_handle, - const gss_buffer_t mech_buf, - gss_buffer_t mech_token, - gss_buffer_t output_token) +make_reply(OM_uint32 *minor_status, + gssspnego_ctx ctx, + gss_buffer_t mech_token, + gss_buffer_t output_token) { NegotiationToken nt; gss_buffer_desc mic_buf; - OM_uint32 ret; + OM_uint32 ret, minor; size_t size; - - if (mech_buf == GSS_C_NO_BUFFER && mech_token->length == 0) { - output_token->length = 0; - output_token->value = NULL; - - return context_handle->open ? GSS_S_COMPLETE : GSS_S_FAILURE; - } + NegResultEnum result; memset(&nt, 0, sizeof(nt)); nt.element = choice_NegotiationToken_negTokenResp; - ALLOC(nt.u.negTokenResp.negResult, 1); - if (nt.u.negTokenResp.negResult == NULL) { - *minor_status = ENOMEM; - return GSS_S_FAILURE; - } - + nt.u.negTokenResp.negResult = NULL; nt.u.negTokenResp.supportedMech = NULL; output_token->length = 0; output_token->value = NULL; + /* figure out our status */ + + if (ctx->flags.open) { + if (ctx->flags.verified_mic == 1 || ctx->flags.require_mic == 0) + result = accept_completed; + else + result = accept_incomplete; + } else { + result = accept_incomplete; + } + if (mech_token->length == 0) { nt.u.negTokenResp.responseToken = NULL; - *(nt.u.negTokenResp.negResult) = accept_completed; } else { ALLOC(nt.u.negTokenResp.responseToken, 1); if (nt.u.negTokenResp.responseToken == NULL) { @@ -125,21 +169,23 @@ spnego_reply_internal(OM_uint32 *minor_status, nt.u.negTokenResp.responseToken->data = mech_token->value; mech_token->length = 0; mech_token->value = NULL; - - *(nt.u.negTokenResp.negResult) = accept_incomplete; } - if (mech_buf != GSS_C_NO_BUFFER) { + /* + * XXX should limit when we send the MIC ? + */ + if (ctx->flags.open && ctx->flags.sent_mic == 0) { + + ctx->flags.sent_mic = 1; ret = gss_get_mic(minor_status, - context_handle->negotiated_ctx_id, + ctx->negotiated_ctx_id, 0, - mech_buf, + &ctx->NegTokenInit_mech_types, &mic_buf); - if (ret == GSS_S_COMPLETE && - gss_oid_equal(context_handle->negotiated_mech_type, GSS_NTLM_MECHANISM)) - _gss_spnego_ntlm_reset_crypto(minor_status, context_handle, 0); if (ret == GSS_S_COMPLETE) { + _gss_spnego_ntlm_reset_crypto(&minor, ctx, FALSE); + ALLOC(nt.u.negTokenResp.mechListMIC, 1); if (nt.u.negTokenResp.mechListMIC == NULL) { gss_release_buffer(minor_status, &mic_buf); @@ -150,270 +196,245 @@ spnego_reply_internal(OM_uint32 *minor_status, nt.u.negTokenResp.mechListMIC->length = mic_buf.length; nt.u.negTokenResp.mechListMIC->data = mic_buf.value; + /* mic_buf free()d with nt */ } else if (ret == GSS_S_UNAVAILABLE) { + /* lets hope that its ok to not send te mechListMIC for broken mechs */ nt.u.negTokenResp.mechListMIC = NULL; - } if (ret) { + ctx->flags.require_mic = 0; + } else { free_NegotiationToken(&nt); *minor_status = ENOMEM; - return GSS_S_FAILURE; + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + ret, *minor_status, + "SPNEGO failed to sign MIC"); } } else { nt.u.negTokenResp.mechListMIC = NULL; } + ALLOC(nt.u.negTokenResp.negResult, 1); + if (nt.u.negTokenResp.negResult == NULL) { + free_NegotiationToken(&nt); + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + *nt.u.negTokenResp.negResult = result; + ASN1_MALLOC_ENCODE(NegotiationToken, output_token->value, output_token->length, &nt, &size, ret); + free_NegotiationToken(&nt); if (ret) { - free_NegotiationToken(&nt); *minor_status = ret; return GSS_S_FAILURE; } - if (*(nt.u.negTokenResp.negResult) == accept_completed) - ret = GSS_S_COMPLETE; - else - ret = GSS_S_CONTINUE_NEEDED; + if (result != accept_completed) + return GSS_S_CONTINUE_NEEDED; - free_NegotiationToken(&nt); - return ret; + return GSS_S_COMPLETE; } static OM_uint32 -spnego_initial - (OM_uint32 * minor_status, - gss_const_cred_id_t cred, - gss_ctx_id_t * context_handle, - gss_const_name_t target_name, - const gss_OID mech_type, - OM_uint32 req_flags, - OM_uint32 time_req, - const gss_channel_bindings_t input_chan_bindings, - const gss_buffer_t input_token, - gss_OID * actual_mech_type, - gss_buffer_t output_token, - OM_uint32 * ret_flags, - OM_uint32 * time_rec - ) +spnego_initial(OM_uint32 * minor_status, + gss_const_cred_id_t cred, + gssspnego_ctx ctx, + gss_const_name_t target_name, + gss_const_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + gss_const_buffer_t input_token, + gss_buffer_t output_token, + OM_uint32 * ret_flags, + OM_uint32 * time_rec) { - NegTokenInit ni; + NegotiationToken nt; int ret; OM_uint32 sub, minor; gss_buffer_desc mech_token; - u_char *buf; - size_t buf_size, buf_len; + size_t size = 0; gss_buffer_desc data; - size_t ni_len; - gss_ctx_id_t context; - gssspnego_ctx ctx; + struct gssspnego_optimistic_ctx sel; *minor_status = 0; - memset (&ni, 0, sizeof(ni)); - - *context_handle = GSS_C_NO_CONTEXT; + memset(&nt, 0, sizeof(nt)); if (target_name == GSS_C_NO_NAME) return GSS_S_BAD_NAME; - sub = _gss_spnego_alloc_sec_context(&minor, &context); - if (GSS_ERROR(sub)) { - *minor_status = minor; - return sub; - } - ctx = (gssspnego_ctx)context; - - HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); - - ctx->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; } + nt.element = choice_NegotiationToken_negTokenInit; + + ctx->flags.local = 1; + + 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; - /* generate optimistic token */ - sub = gss_init_sec_context(&minor, - cred, - &ctx->negotiated_ctx_id, - ctx->target_name, - ctx->preferred_mech_type, + if (gss_oid_equal(ctx->preferred_mech_type, GSS_NEGOEX_MECHANISM)) { + struct negoex_auth_mech *mech; + + 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 (sub == GSS_S_COMPLETE) - ctx->maybe_open = 1; + + 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 (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; + nt.u.negTokenInit.mechToken = NULL; - ni.mechListMIC = NULL; + nt.u.negTokenInit.mechListMIC = NULL; - ni_len = length_NegTokenInit(&ni); - buf_size = 1 + der_length_len(ni_len) + ni_len; + { + MechTypeList mt; - 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; + mt.len = nt.u.negTokenInit.mechTypes.len; + mt.val = nt.u.negTokenInit.mechTypes.val; + + 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; + } } - ret = encode_NegTokenInit(buf + buf_size - 1, - ni_len, - &ni, &buf_len); - if (ret == 0 && ni_len != buf_len) - abort(); - - 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(NegotiationToken, data.value, data.length, &nt, &size, ret); + free_NegotiationToken(&nt); if (ret) { - *minor_status = ret; - free(buf); - free_NegTokenInit(&ni); - _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER); return GSS_S_FAILURE; } - - data.value = buf; - data.length = buf_size; - - ctx->initiator_mech_types.len = ni.mechTypes.len; - ctx->initiator_mech_types.val = ni.mechTypes.val; - ni.mechTypes.len = 0; - ni.mechTypes.val = NULL; - - free_NegTokenInit(&ni); + if (data.length != size) + abort(); sub = gss_encapsulate_token(&data, GSS_SPNEGO_MECHANISM, output_token); - free (buf); + free (data.value); if (sub) { - _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER); return sub; } - if (actual_mech_type) - *actual_mech_type = ctx->negotiated_mech_type; if (ret_flags) *ret_flags = ctx->mech_flags; if (time_rec) *time_rec = ctx->mech_time_rec; - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - - *context_handle = context; + ctx->initiator_state = spnego_reply; return GSS_S_CONTINUE_NEEDED; } +/* + * + */ + static OM_uint32 -spnego_reply - (OM_uint32 * minor_status, - gss_const_cred_id_t cred, - gss_ctx_id_t * context_handle, - gss_const_name_t target_name, - const gss_OID mech_type, - OM_uint32 req_flags, - OM_uint32 time_req, - const gss_channel_bindings_t input_chan_bindings, - const gss_buffer_t input_token, - gss_OID * actual_mech_type, - gss_buffer_t output_token, - OM_uint32 * ret_flags, - OM_uint32 * time_rec - ) +spnego_reply(OM_uint32 * minor_status, + gss_const_cred_id_t cred, + gssspnego_ctx ctx, + gss_const_name_t target_name, + gss_const_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + gss_const_buffer_t input_token, + gss_buffer_t output_token, + OM_uint32 * ret_flags, + OM_uint32 * time_rec) { OM_uint32 ret, minor; NegotiationToken resp; - gss_OID_desc mech; - int require_mic; - size_t buf_len = 0; - gss_buffer_desc mic_buf, mech_buf; gss_buffer_desc mech_output_token; - gssspnego_ctx ctx; *minor_status = 0; - ctx = (gssspnego_ctx)*context_handle; - output_token->length = 0; output_token->value = NULL; mech_output_token.length = 0; mech_output_token.value = NULL; - mech_buf.value = NULL; - mech_buf.length = 0; - ret = decode_NegotiationToken(input_token->value, input_token->length, &resp, NULL); if (ret) @@ -426,57 +447,84 @@ spnego_reply } if (resp.u.negTokenResp.negResult == NULL - || *(resp.u.negTokenResp.negResult) == reject - /* || resp.u.negTokenResp.supportedMech == NULL */ - ) + || *(resp.u.negTokenResp.negResult) == reject) { free_NegotiationToken(&resp); return GSS_S_BAD_MECH; } /* - * Pick up the mechanism that the acceptor selected, only allow it - * to be sent in packet. + * Pick up the mechanism that the acceptor selected, only pick up + * the first selection. */ - HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); + if (ctx->selected_mech_type == GSS_C_NO_OID && resp.u.negTokenResp.supportedMech) { + gss_OID_desc oid; + size_t len; - if (resp.u.negTokenResp.supportedMech) { + ctx->flags.seen_supported_mech = 1; - if (ctx->oidlen) { + oid.length = (OM_uint32)der_length_oid(resp.u.negTokenResp.supportedMech); + oid.elements = malloc(oid.length); + if (oid.elements == NULL) { free_NegotiationToken(&resp); - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); return GSS_S_BAD_MECH; } - ret = der_put_oid(ctx->oidbuf + sizeof(ctx->oidbuf) - 1, - sizeof(ctx->oidbuf), + ret = der_put_oid(((uint8_t *)oid.elements) + oid.length - 1, + oid.length, resp.u.negTokenResp.supportedMech, - &ctx->oidlen); - /* Avoid recursively embedded SPNEGO */ - if (ret || (ctx->oidlen == GSS_SPNEGO_MECHANISM->length && - memcmp(ctx->oidbuf + sizeof(ctx->oidbuf) - ctx->oidlen, - GSS_SPNEGO_MECHANISM->elements, - ctx->oidlen) == 0)) - { + &len); + if (ret || len != oid.length) { + free(oid.elements); free_NegotiationToken(&resp); - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); return GSS_S_BAD_MECH; } + if (gss_oid_equal(GSS_SPNEGO_MECHANISM, &oid)) { + free(oid.elements); + free_NegotiationToken(&resp); + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, (*minor_status = EINVAL), + "SPNEGO acceptor picked SPNEGO??"); + } + /* check if the acceptor took our optimistic token */ - if (ctx->oidlen != ctx->preferred_mech_type->length || - memcmp(ctx->oidbuf + sizeof(ctx->oidbuf) - ctx->oidlen, - ctx->preferred_mech_type->elements, - ctx->oidlen) != 0) - { + if (gss_oid_equal(ctx->preferred_mech_type, &oid)) { + ctx->selected_mech_type = ctx->preferred_mech_type; + } else if (gss_oid_equal(ctx->preferred_mech_type, GSS_KRB5_MECHANISM) && + gss_oid_equal(&oid, &_gss_spnego_mskrb_mechanism_oid_desc)) { + /* mis-encoded asn1 type from msft servers */ + ctx->selected_mech_type = ctx->preferred_mech_type; + } else { + /* nope, lets start over */ gss_delete_sec_context(&minor, &ctx->negotiated_ctx_id, GSS_C_NO_BUFFER); ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT; + + if (gss_oid_equal(&oid, GSS_NEGOEX_MECHANISM)) + ctx->selected_mech_type = GSS_NEGOEX_MECHANISM; + else + ctx->selected_mech_type = _gss_mg_support_mechanism(&oid); + + /* XXX check that server pick a mechanism we proposed */ + if (ctx->selected_mech_type == GSS_C_NO_OID) { + free(oid.elements); + free_NegotiationToken(&resp); + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, (*minor_status = EINVAL), + "SPNEGO acceptor sent unsupported supportedMech"); + } } - } else if (ctx->oidlen == 0) { + + _gss_spnego_log_mech("initiator selected mechanism", ctx->selected_mech_type); + + free(oid.elements); + + } else if (ctx->selected_mech_type == NULL) { free_NegotiationToken(&resp); - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - return GSS_S_BAD_MECH; + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, (*minor_status = EINVAL), + "SPNEGO acceptor didn't send supportedMech"); } /* if a token (of non zero length), or no context, pass to underlaying mech */ @@ -492,176 +540,279 @@ spnego_reply mech_input_token.value = NULL; } - - mech.length = ctx->oidlen; - mech.elements = ctx->oidbuf + sizeof(ctx->oidbuf) - ctx->oidlen; - /* Fall through as if the negotiated mechanism was requested explicitly */ - ret = gss_init_sec_context(&minor, - cred, - &ctx->negotiated_ctx_id, - ctx->target_name, - &mech, + if (gss_oid_equal(ctx->selected_mech_type, GSS_NEGOEX_MECHANISM)) { + ret = _gss_negoex_init(&minor, + NULL, /* no optimistic token */ + ctx, + (gss_cred_id_t)cred, req_flags, time_req, input_chan_bindings, &mech_input_token, - &ctx->negotiated_mech_type, - &mech_output_token, - &ctx->mech_flags, - &ctx->mech_time_rec); + &mech_output_token); + } else { + ret = gss_init_sec_context(&minor, + cred, + &ctx->negotiated_ctx_id, + ctx->target_name, + ctx->selected_mech_type, + req_flags, + time_req, + input_chan_bindings, + &mech_input_token, + &ctx->negotiated_mech_type, + &mech_output_token, + &ctx->mech_flags, + &ctx->mech_time_rec); + if (GSS_ERROR(ret)) + gss_mg_collect_error(ctx->selected_mech_type, ret, minor); + } if (GSS_ERROR(ret)) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); free_NegotiationToken(&resp); - gss_mg_collect_error(&mech, ret, minor); *minor_status = minor; return ret; } if (ret == GSS_S_COMPLETE) { - ctx->open = 1; + ctx->flags.open = 1; + } + } else if (*resp.u.negTokenResp.negResult == accept_completed) { + if (ctx->flags.maybe_open) + ctx->flags.open = 1; + + if (!ctx->flags.open) { + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, (*minor_status = EINVAL), + "SPNEGO acceptor sent acceptor complete, " + "but we are not complete yet"); } - } else if (*(resp.u.negTokenResp.negResult) == accept_completed) { - if (ctx->maybe_open) - ctx->open = 1; } - if (*(resp.u.negTokenResp.negResult) == request_mic) { - ctx->require_mic = 1; + if (*resp.u.negTokenResp.negResult == request_mic) { + ctx->flags.peer_require_mic = 1; } - if (ctx->open) { + if (ctx->flags.open && ctx->flags.verified_mic == 0) { + + ctx->flags.require_mic = 1; /* default is to require a MIC */ + ctx->flags.safe_omit = _gss_spnego_safe_omit_mechlist_mic(ctx); + /* - * Verify the mechListMIC if one was provided or CFX was - * used and a non-preferred mechanism was selected + * If the peer sent mechListMIC, require it to verify ... */ - if (resp.u.negTokenResp.mechListMIC != NULL) { - require_mic = 1; - } else { - ret = _gss_spnego_require_mechlist_mic(minor_status, ctx, - &require_mic); - if (ret) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - free_NegotiationToken(&resp); - gss_release_buffer(&minor, &mech_output_token); - return ret; + if (resp.u.negTokenResp.mechListMIC) { + heim_octet_string *m = resp.u.negTokenResp.mechListMIC; + + /* ...unless its a windows 2000 server that sends the + * responseToken inside the mechListMIC too. We only + * accept this condition if would have been safe to omit + * anyway. */ + + if (ctx->flags.safe_omit + && resp.u.negTokenResp.responseToken + && der_heim_octet_string_cmp(m, resp.u.negTokenResp.responseToken) == 0) + { + ctx->flags.require_mic = 0; } } + } else { - require_mic = 0; + ctx->flags.require_mic = 0; } - if (require_mic) { - ASN1_MALLOC_ENCODE(MechTypeList, mech_buf.value, mech_buf.length, - &ctx->initiator_mech_types, &buf_len, ret); + /* + * If we are supposed to check mic and have it, force checking now. + */ + + if (ctx->flags.require_mic && resp.u.negTokenResp.mechListMIC) { + + ret = _gss_spnego_verify_mechtypes_mic(minor_status, ctx, + resp.u.negTokenResp.mechListMIC); if (ret) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); free_NegotiationToken(&resp); - gss_release_buffer(&minor, &mech_output_token); - *minor_status = ret; - return GSS_S_FAILURE; - } - if (mech_buf.length != buf_len) { - abort(); - UNREACHABLE(return GSS_S_FAILURE); - } - - if (resp.u.negTokenResp.mechListMIC == NULL) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - free(mech_buf.value); - free_NegotiationToken(&resp); - *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; - } - mic_buf.length = resp.u.negTokenResp.mechListMIC->length; - mic_buf.value = resp.u.negTokenResp.mechListMIC->data; - - if (mech_output_token.length == 0) { - ret = gss_verify_mic(minor_status, - ctx->negotiated_ctx_id, - &mech_buf, - &mic_buf, - NULL); - if (ret == GSS_S_COMPLETE && - gss_oid_equal(ctx->negotiated_mech_type, GSS_NTLM_MECHANISM)) - _gss_spnego_ntlm_reset_crypto(minor_status, ctx, 1); - if (ret) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - free(mech_buf.value); - gss_release_buffer(&minor, &mech_output_token); - free_NegotiationToken(&resp); - return GSS_S_DEFECTIVE_TOKEN; - } - ctx->verified_mic = 1; + return ret; } } - ret = spnego_reply_internal(minor_status, ctx, - require_mic ? &mech_buf : NULL, - &mech_output_token, - output_token); + /* + * Now that underlaying mech is open (conncted), we can figure out + * what nexd step to go to. + */ - if (mech_buf.value != NULL) - free(mech_buf.value); + if (ctx->flags.open) { + + if (*resp.u.negTokenResp.negResult == accept_completed && ctx->flags.safe_omit) { + ctx->initiator_state = step_completed; + ret = GSS_S_COMPLETE; + } else if (ctx->flags.require_mic != 0 && ctx->flags.verified_mic == 0) { + ctx->initiator_state = wait_server_mic; + ret = GSS_S_CONTINUE_NEEDED; + } else { + ctx->initiator_state = step_completed; + ret = GSS_S_COMPLETE; + } + } + + if (*resp.u.negTokenResp.negResult != accept_completed || + ctx->initiator_state != step_completed || + mech_output_token.length) + { + OM_uint32 ret2; + ret2 = make_reply(minor_status, ctx, + &mech_output_token, + output_token); + if (ret2) + ret = ret2; + } free_NegotiationToken(&resp); + gss_release_buffer(&minor, &mech_output_token); - if (actual_mech_type) - *actual_mech_type = ctx->negotiated_mech_type; if (ret_flags) *ret_flags = ctx->mech_flags; if (time_rec) *time_rec = ctx->mech_time_rec; - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); return ret; } -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 - ) +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) { - 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); + 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) +{ + gssspnego_ctx ctx; + OM_uint32 ret; + + if (*context_handle == GSS_C_NO_CONTEXT) { + ret = _gss_spnego_alloc_sec_context(minor_status, context_handle); + if (GSS_ERROR(ret)) + return ret; + + ctx = (gssspnego_ctx)*context_handle; + + ctx->initiator_state = spnego_initial; + } else { + ctx = (gssspnego_ctx)*context_handle; + } + + + HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); + + do { + ret = ctx->initiator_state(minor_status, initiator_cred_handle, ctx, target_name, + mech_type, req_flags, time_req, input_chan_bindings, input_token, + output_token, ret_flags, time_rec); + + } while (ret == GSS_S_COMPLETE && + ctx->initiator_state != step_completed && + output_token->length == 0); + + /* destroy context in case of error */ + if (GSS_ERROR(ret)) { + OM_uint32 junk; + _gss_spnego_internal_delete_sec_context(&junk, context_handle, GSS_C_NO_BUFFER); + } else { + + HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); + + if (actual_mech_type) + *actual_mech_type = ctx->negotiated_mech_type; + } + + return ret; } diff --git a/lib/gssapi/spnego/negoex_ctx.c b/lib/gssapi/spnego/negoex_ctx.c new file mode 100644 index 000000000..1f9408901 --- /dev/null +++ b/lib/gssapi/spnego/negoex_ctx.c @@ -0,0 +1,1018 @@ +/* + * Copyright (C) 2011-2019 PADL Software Pty Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spnego_locl.h" + +/* + * The initial context token emitted by the initiator is a INITIATOR_NEGO + * message followed by zero or more INITIATOR_META_DATA tokens, and zero + * or one AP_REQUEST tokens. + * + * Upon receiving this, the acceptor computes the list of mutually supported + * authentication mechanisms and performs the metadata exchange. The output + * token is ACCEPTOR_NEGO followed by zero or more ACCEPTOR_META_DATA tokens, + * and zero or one CHALLENGE tokens. + * + * Once the metadata exchange is complete and a mechanism is selected, the + * selected mechanism's context token exchange continues with AP_REQUEST and + * CHALLENGE messages. + * + * Once the context token exchange is complete, VERIFY messages are sent to + * authenticate the entire exchange. + */ + +static void +zero_and_release_buffer_set(gss_buffer_set_t *pBuffers) +{ + OM_uint32 tmpMinor; + gss_buffer_set_t buffers = *pBuffers; + size_t i; + + if (buffers != GSS_C_NO_BUFFER_SET) { + for (i = 0; i < buffers->count; i++) + memset_s(buffers->elements[i].value, + buffers->elements[i].length, 0, + buffers->elements[i].length); + + gss_release_buffer_set(&tmpMinor, &buffers); + } + + *pBuffers = GSS_C_NO_BUFFER_SET; +} + +static OM_uint32 +buffer_set_to_crypto(OM_uint32 *minor, + krb5_context context, + gss_buffer_set_t buffers, + krb5_crypto *crypto) +{ + krb5_error_code ret; + krb5_keyblock keyblock; + OM_uint32 tmp; + + /* + * Returned keys must be in two buffers, with the key contents in + * the first and the enctype as a 32-bit little-endian integer in + * the second. + */ + if (buffers->count != 2 || + buffers->elements[1].length != sizeof(tmp)) { + *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY; + return GSS_S_FAILURE; + } + + if (*crypto != NULL) { + krb5_crypto_destroy(context, *crypto); + *crypto = NULL; + } + + keyblock.keyvalue.data = buffers->elements[0].value; + keyblock.keyvalue.length = buffers->elements[0].length; + _gss_mg_decode_le_uint32(buffers->elements[1].value, &tmp); + keyblock.keytype = tmp; + + ret = krb5_crypto_init(context, &keyblock, 0, crypto); + if (ret) { + *minor = ret; + return GSS_S_FAILURE; + } + + return GSS_S_COMPLETE; +} + +static OM_uint32 +get_session_keys(OM_uint32 *minor, + krb5_context context, + struct negoex_auth_mech *mech) +{ + OM_uint32 major, tmpMinor; + gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET; + + major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context, + GSS_C_INQ_NEGOEX_KEY, &buffers); + if (major == GSS_S_COMPLETE) { + major = buffer_set_to_crypto(minor, context, + buffers, &mech->crypto); + zero_and_release_buffer_set(&buffers); + if (major != GSS_S_COMPLETE) + return major; + } + + major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context, + GSS_C_INQ_NEGOEX_VERIFY_KEY, + &buffers); + if (major == GSS_S_COMPLETE) { + major = buffer_set_to_crypto(minor, context, + buffers, &mech->verify_crypto); + zero_and_release_buffer_set(&buffers); + if (major != GSS_S_COMPLETE) + return major; + } + + return GSS_S_COMPLETE; +} + +static OM_uint32 +emit_initiator_nego(OM_uint32 *minor, gssspnego_ctx ctx) +{ + uint8_t random[32]; + struct negoex_auth_mech *mech; + size_t i = 0; + + krb5_generate_random_block(random, sizeof(random)); + + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) + _gss_negoex_log_auth_scheme(ctx->flags.local, ++i, mech->scheme); + + return _gss_negoex_add_nego_message(minor, ctx, INITIATOR_NEGO, random); +} + +static OM_uint32 +process_initiator_nego(OM_uint32 *minor, + gssspnego_ctx ctx, + struct negoex_message *messages, + size_t nmessages) +{ + struct nego_message *msg; + size_t i; + + heim_assert(!ctx->flags.local && ctx->negoex_step == 1, + "NegoEx INITIATOR_NEGO token received after first leg"); + + msg = _gss_negoex_locate_nego_message(messages, nmessages, INITIATOR_NEGO); + if (msg == NULL) { + *minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE; + return GSS_S_DEFECTIVE_TOKEN; + } + + for (i = 0; i < msg->nschemes; i++) + _gss_negoex_log_auth_scheme(ctx->flags.local, i + 1, &msg->schemes[i * GUID_LENGTH]); + + _gss_negoex_restrict_auth_schemes(ctx, msg->schemes, msg->nschemes); + + return GSS_S_COMPLETE; +} + +static OM_uint32 +emit_acceptor_nego(OM_uint32 *minor, gssspnego_ctx ctx) +{ + uint8_t random[32]; + + krb5_generate_random_block(random, 32); + + return _gss_negoex_add_nego_message(minor, ctx, ACCEPTOR_NEGO, random); +} + +static OM_uint32 +process_acceptor_nego(OM_uint32 *minor, + gssspnego_ctx ctx, + struct negoex_message *messages, + size_t nmessages) +{ + struct nego_message *msg; + + msg = _gss_negoex_locate_nego_message(messages, nmessages, ACCEPTOR_NEGO); + if (msg == NULL) { + *minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE; + return GSS_S_DEFECTIVE_TOKEN; + } + + /* + * Reorder and prune our mech list to match the acceptor's list (or a + * subset of it). + */ + _gss_negoex_common_auth_schemes(ctx, msg->schemes, msg->nschemes); + + return GSS_S_COMPLETE; +} + +static void +query_meta_data(gssspnego_ctx ctx, + struct gssspnego_optimistic_ctx *opt, + gss_cred_id_t cred, + OM_uint32 req_flags) +{ + OM_uint32 major, minor; + struct negoex_auth_mech *p, *next; + + /* + * Note that if we received an optimistic context token from SPNEGO, + * then we will call QMD after ISC, rather than before. Mechanisms + * must be prepared to handle this and must not assume the context + * will be NULL on entry. + */ + HEIM_TAILQ_FOREACH_SAFE(p, &ctx->negoex_mechs, links, next) { + if (opt != NULL && memcmp(opt->scheme, p->scheme, GUID_LENGTH) == 0) + p->mech_context = opt->gssctx;; + + major = gssspi_query_meta_data(&minor, p->oid, cred, &p->mech_context, + ctx->target_name, req_flags, &p->metadata); + /* GSS_Query_meta_data failure removes mechanism from list. */ + if (major != GSS_S_COMPLETE) + _gss_negoex_delete_auth_mech(ctx, p); + } +} + +static void +exchange_meta_data(gssspnego_ctx ctx, + gss_cred_id_t cred, + OM_uint32 req_flags, + struct negoex_message *messages, + size_t nmessages) +{ + OM_uint32 major, minor; + struct negoex_auth_mech *mech; + enum message_type type; + struct exchange_message *msg; + uint32_t i; + + type = ctx->flags.local ? ACCEPTOR_META_DATA : INITIATOR_META_DATA; + + for (i = 0; i < nmessages; i++) { + if (messages[i].type != type) + continue; + msg = &messages[i].u.e; + + mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme); + if (mech == NULL) + continue; + + major = gssspi_exchange_meta_data(&minor, mech->oid, cred, + &mech->mech_context, + ctx->target_name, + req_flags, &msg->token); + /* GSS_Exchange_meta_data failure removes mechanism from list. */ + if (major != GSS_S_COMPLETE) + _gss_negoex_delete_auth_mech(ctx, mech); + } +} + +static void +release_mech_crypto(struct negoex_auth_mech *mech) +{ + krb5_context context = NULL; + + if (mech->crypto || mech->verify_crypto) + context = _gss_mg_krb5_context(); + + if (mech->crypto) { + krb5_crypto_destroy(context, mech->crypto); + mech->crypto = NULL; + } + + if (mech->verify_crypto) { + krb5_crypto_destroy(context, mech->verify_crypto); + mech->verify_crypto = NULL; + } + + mech->sent_checksum = FALSE; +} + +/* + * In the initiator, if we are processing the acceptor's first reply, discard + * the optimistic context if the acceptor ignored the optimistic token. If the + * acceptor continued the optimistic mech, discard all other mechs. + */ +static void +check_optimistic_result(gssspnego_ctx ctx, + struct negoex_message *messages, + size_t nmessages) +{ + struct negoex_auth_mech *mech; + OM_uint32 tmpMinor; + + heim_assert(ctx->flags.local && ctx->negoex_step == 2, + "NegoEx optimistic result should only be checked in second leg"); + + /* Do nothing if we didn't make an optimistic context. */ + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT) + return; + + /* + * If the acceptor used the optimistic token, it will send an acceptor + * token or a checksum (or both) in its first reply. + */ + if (_gss_negoex_locate_exchange_message(messages, nmessages, + CHALLENGE) != NULL || + _gss_negoex_locate_verify_message(messages, nmessages) != NULL) { + /* + * The acceptor continued the optimistic mech, and metadata exchange + * didn't remove it. Commit to this mechanism. + */ + _gss_negoex_select_auth_mech(ctx, mech); + } else { + /* + * The acceptor ignored the optimistic token. Restart the mech. + */ + gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER); + release_mech_crypto(mech); + mech->complete = FALSE; + } +} + +/* Perform an initiator step of the underlying mechanism exchange. */ +static OM_uint32 +mech_init(OM_uint32 *minor, + struct gssspnego_optimistic_ctx *opt, + gssspnego_ctx ctx, + gss_cred_id_t cred, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + struct negoex_message *messages, + size_t nmessages, + gss_buffer_t output_token, + int *mech_error) +{ + OM_uint32 major, first_major = GSS_S_COMPLETE, first_minor = 0; + struct negoex_auth_mech *mech = NULL; + gss_buffer_t input_token = GSS_C_NO_BUFFER; + struct exchange_message *msg; + int first_mech; + krb5_context context = _gss_mg_krb5_context(); + + output_token->value = NULL; + output_token->length = 0; + + *mech_error = FALSE; + + /* Allow disabling of optimistic token for testing. */ + if (ctx->negoex_step == 1 && + secure_getenv("NEGOEX_NO_OPTIMISTIC_TOKEN") != NULL) + return GSS_S_COMPLETE; + + if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { + *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS; + return GSS_S_FAILURE; + } + + /* + * Get the input token. The challenge could be for the optimistic mech, + * which we might have discarded in metadata exchange, so ignore the + * challenge if it doesn't match the first auth mech. + */ + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + msg = _gss_negoex_locate_exchange_message(messages, nmessages, CHALLENGE); + if (msg != NULL && GUID_EQ(msg->scheme, mech->scheme)) + input_token = &msg->token; + + if (mech->complete) + return GSS_S_COMPLETE; + + first_mech = TRUE; + major = GSS_S_BAD_MECH; + + while (!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + + /* + * If SPNEGO generated an optimistic token when probing available + * mechanisms, we can reuse it here. This avoids a potentially + * expensive and redundant call to GSS_Init_sec_context(); + */ + if (opt != NULL && memcmp(opt->scheme, mech->scheme, GUID_LENGTH) == 0) { + heim_assert(ctx->negoex_step == 1, + "SPNEGO optimistic token only valid for NegoEx first leg"); + + major = _gss_copy_buffer(minor, &opt->optimistic_token, output_token); + if (GSS_ERROR(major)) + return major; + + ctx->negotiated_mech_type = opt->negotiated_mech_type; + ctx->mech_flags = opt->optimistic_flags; + ctx->mech_time_rec = opt->optimistic_time_rec; + + mech->mech_context = opt->gssctx; + opt->gssctx = NULL; /* steal it */ + + mech->complete = opt->complete; + major = GSS_S_COMPLETE; + } else { + major = gss_init_sec_context(minor, cred, &mech->mech_context, + ctx->target_name, mech->oid, + req_flags, time_req, + input_chan_bindings, input_token, + &ctx->negotiated_mech_type, output_token, + &ctx->mech_flags, &ctx->mech_time_rec); + if (major == GSS_S_COMPLETE) + mech->complete = 1; + else if (GSS_ERROR(major)) { + gss_mg_collect_error(mech->oid, major, *minor); + *mech_error = TRUE; + } + } + if (!GSS_ERROR(major)) + return get_session_keys(minor, context, mech); + + /* Remember the error we got from the first mech. */ + if (first_mech) { + first_major = major; + first_minor = *minor; + } + + /* If we still have multiple mechs to try, move on to the next one. */ + _gss_negoex_delete_auth_mech(ctx, mech); + first_mech = FALSE; + input_token = GSS_C_NO_BUFFER; + } + + if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { + major = first_major; + *minor = first_minor; + } + + return major; +} + +/* Perform an acceptor step of the underlying mechanism exchange. */ +static OM_uint32 +mech_accept(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_cred_id_t cred, + const gss_channel_bindings_t input_chan_bindings, + struct negoex_message *messages, + size_t nmessages, + gss_buffer_t output_token, + gss_cred_id_t *deleg_cred, + int *mech_error) +{ + OM_uint32 major, tmpMinor; + struct negoex_auth_mech *mech; + struct exchange_message *msg; + krb5_context context = _gss_mg_krb5_context(); + + heim_assert(!ctx->flags.local && !HEIM_TAILQ_EMPTY(&ctx->negoex_mechs), + "Acceptor NegoEx function called in wrong sequence"); + + *mech_error = FALSE; + + msg = _gss_negoex_locate_exchange_message(messages, nmessages, AP_REQUEST); + if (msg == NULL) { + /* + * No input token is okay on the first request or if the mech is + * complete. + */ + if (ctx->negoex_step == 1 || + HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->complete) + return GSS_S_COMPLETE; + *minor = (OM_uint32)NEGOEX_MISSING_AP_REQUEST_MESSAGE; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (ctx->negoex_step == 1) { + /* + * Ignore the optimistic token if it isn't for our most preferred + * mech. + */ + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + if (!GUID_EQ(msg->scheme, mech->scheme)) { + _gss_mg_log(10, "negoex ignored optimistic token as not for preferred mech"); + return GSS_S_COMPLETE; + } + } else { + /* The initiator has selected a mech; discard other entries. */ + mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme); + if (mech == NULL) { + *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS; + return GSS_S_FAILURE; + } + _gss_negoex_select_auth_mech(ctx, mech); + } + + if (mech->complete) + return GSS_S_COMPLETE; + + if (ctx->mech_src_name != GSS_C_NO_NAME) + gss_release_name(&tmpMinor, &ctx->mech_src_name); + if (deleg_cred && *deleg_cred != GSS_C_NO_CREDENTIAL) + gss_release_cred(&tmpMinor, deleg_cred); + + major = gss_accept_sec_context(minor, &mech->mech_context, cred, + &msg->token, input_chan_bindings, + &ctx->mech_src_name, &ctx->negotiated_mech_type, + output_token, &ctx->mech_flags, + &ctx->mech_time_rec, deleg_cred); + if (major == GSS_S_COMPLETE) + mech->complete = 1; + + if (!GSS_ERROR(major)) { + if (major == GSS_S_COMPLETE && + !gss_oid_equal(ctx->negotiated_mech_type, mech->oid)) + _gss_mg_log(1, "negoex client didn't send the mech they said they would"); + + major = get_session_keys(minor, context, mech); + } else if (ctx->negoex_step == 1) { + gss_mg_collect_error(ctx->negotiated_mech_type, major, *minor); + *mech_error = TRUE; + + /* This was an optimistic token; pretend this never happened. */ + major = GSS_S_COMPLETE; + *minor = 0; + gss_release_buffer(&tmpMinor, output_token); + gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER); + } + + return major; +} + +static krb5_keyusage +verify_keyusage(gssspnego_ctx ctx, int make_checksum) +{ + /* Of course, these are the wrong way around in the spec. */ + return (ctx->flags.local ^ !make_checksum) ? + NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM : NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM; +} + +static OM_uint32 +verify_checksum(OM_uint32 *minor, + gssspnego_ctx ctx, + struct negoex_message *messages, + size_t nmessages, + gss_const_buffer_t input_token, + int *send_alert_out) +{ + krb5_error_code ret; + struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + struct verify_message *msg; + krb5_context context = _gss_mg_krb5_context(); + krb5_crypto_iov iov[3]; + krb5_keyusage usage = verify_keyusage(ctx, FALSE); + + *send_alert_out = FALSE; + heim_assert(mech != NULL, "Invalid null mech when verifying NegoEx checksum"); + + /* + * The other party may not be ready to send a verify token yet, or (in the + * first initiator step) may send one for a mechanism we don't support. + */ + msg = _gss_negoex_locate_verify_message(messages, nmessages); + if (msg == NULL || !GUID_EQ(msg->scheme, mech->scheme)) + return GSS_S_COMPLETE; + + /* + * A recoverable error may cause us to be unable to verify a token from the + * other party. In this case we should send an alert. + */ + if (mech->verify_crypto == NULL) { + *send_alert_out = TRUE; + return GSS_S_COMPLETE; + } + + if (!krb5_checksum_is_keyed(context, msg->cksum_type)) { + *minor = (OM_uint32)NEGOEX_INVALID_CHECKSUM; + return GSS_S_BAD_SIG; + } + + /* + * Verify the checksum over the existing transcript and the portion of the + * input token leading up to the verify message. + */ + iov[0].flags = KRB5_CRYPTO_TYPE_DATA; + ret = krb5_storage_to_data(ctx->negoex_transcript, &iov[0].data); + if (ret) { + *minor = ret; + return GSS_S_FAILURE; + } + + iov[1].flags = KRB5_CRYPTO_TYPE_DATA; + iov[1].data.data = input_token->value; + iov[1].data.length = msg->offset_in_token; + + iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM; + iov[2].data.data = (uint8_t *)msg->cksum; + iov[2].data.length = msg->cksum_len; + + ret = krb5_verify_checksum_iov(context, mech->verify_crypto, usage, + iov, sizeof(iov) / sizeof(iov[0]), NULL); + if (ret == 0) + mech->verified_checksum = TRUE; + else + *minor = ret; + + krb5_data_free(&iov[0].data); + + return (ret == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE; +} + +static OM_uint32 +make_checksum(OM_uint32 *minor, gssspnego_ctx ctx) +{ + krb5_error_code ret; + krb5_context context = _gss_mg_krb5_context(); + krb5_data d; + krb5_keyusage usage = verify_keyusage(ctx, TRUE); + krb5_checksum cksum; + struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + OM_uint32 major; + + heim_assert(mech != NULL, "Invalid null mech when making NegoEx checksum"); + + if (mech->crypto == NULL) { + if (mech->complete) { + *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY; + return GSS_S_UNAVAILABLE; + } else { + return GSS_S_COMPLETE; + } + } + + ret = krb5_storage_to_data(ctx->negoex_transcript, &d); + if (ret) { + *minor = ret; + return GSS_S_FAILURE; + } + + ret = krb5_create_checksum(context, mech->crypto, + usage, 0, d.data, d.length, &cksum); + krb5_data_free(&d); + if (ret) { + *minor = ret; + return GSS_S_FAILURE; + } + + major = _gss_negoex_add_verify_message(minor, ctx, mech->scheme, + cksum.cksumtype, + cksum.checksum.data, + cksum.checksum.length); + free_Checksum(&cksum); + + if (major == GSS_S_COMPLETE) + mech->sent_checksum = TRUE; + + return major; +} + +/* + * If the other side sent a VERIFY_NO_KEY pulse alert, clear the checksum state + * on the mechanism so that we send another VERIFY message. + */ +static void +process_alerts(gssspnego_ctx ctx, + struct negoex_message *messages, + uint32_t nmessages) +{ + struct alert_message *msg; + struct negoex_auth_mech *mech; + + msg = _gss_negoex_locate_alert_message(messages, nmessages); + if (msg != NULL && msg->verify_no_key) { + mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme); + if (mech != NULL) + release_mech_crypto(mech); + } +} + +static OM_uint32 +make_output_token(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_buffer_t mech_output_token, + int send_alert, + gss_buffer_t output_token) +{ + OM_uint32 major, tmpMinor; + struct negoex_auth_mech *mech; + enum message_type type; + off_t old_transcript_len; + + output_token->length = 0; + output_token->value = NULL; + + old_transcript_len = krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR); + + /* + * If the mech is complete and we previously sent a checksum, we just + * processed the last leg and don't need to send another token. + */ + if (mech_output_token->length == 0 && + HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->sent_checksum) + return GSS_S_COMPLETE; + + if (ctx->negoex_step == 1) { + if (ctx->flags.local) + major = emit_initiator_nego(minor, ctx); + else + major = emit_acceptor_nego(minor, ctx); + if (major != GSS_S_COMPLETE) + return major; + + type = ctx->flags.local ? INITIATOR_META_DATA : ACCEPTOR_META_DATA; + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) { + if (mech->metadata.length > 0) { + major = _gss_negoex_add_exchange_message(minor, ctx, + type, mech->scheme, + &mech->metadata); + if (major != GSS_S_COMPLETE) + return major; + } + } + } + + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + + if (mech_output_token->length > 0) { + type = ctx->flags.local ? AP_REQUEST : CHALLENGE; + major = _gss_negoex_add_exchange_message(minor, ctx, + type, mech->scheme, + mech_output_token); + if (major != GSS_S_COMPLETE) + return major; + } + + if (send_alert) { + major = _gss_negoex_add_verify_no_key_alert(minor, ctx, mech->scheme); + if (major != GSS_S_COMPLETE) + return major; + } + + /* Try to add a VERIFY message if we haven't already done so. */ + if (!mech->sent_checksum) { + major = make_checksum(minor, ctx); + if (major != GSS_S_COMPLETE) + return major; + } + + heim_assert(ctx->negoex_transcript != NULL, "NegoEx context uninitialized"); + + output_token->length = + krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR) - old_transcript_len; + output_token->value = malloc(output_token->length); + if (output_token->value == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + krb5_storage_seek(ctx->negoex_transcript, old_transcript_len, SEEK_SET); + + if (krb5_storage_read(ctx->negoex_transcript, + output_token->value, + output_token->length) != output_token->length) { + *minor = ERANGE; + gss_release_buffer(&tmpMinor, output_token); + return GSS_S_FAILURE; + } + + krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_END); + + return GSS_S_COMPLETE; +} + +OM_uint32 +_gss_negoex_init(OM_uint32 *minor, + struct gssspnego_optimistic_ctx *opt, + gssspnego_ctx ctx, + gss_cred_id_t cred, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + gss_const_buffer_t input_token, + gss_buffer_t output_token) +{ + OM_uint32 major, tmpMinor; + gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER; + struct negoex_message *messages = NULL; + struct negoex_auth_mech *mech; + size_t nmessages = 0; + int send_alert = FALSE, mech_error = FALSE; + + if (ctx->negoex_step == 0 && input_token != GSS_C_NO_BUFFER && + input_token->length != 0) + return GSS_S_DEFECTIVE_TOKEN; + + major = _gss_negoex_begin(minor, ctx); + if (major != GSS_S_COMPLETE) + goto cleanup; + + ctx->negoex_step++; + + if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) { + major = _gss_negoex_parse_token(minor, ctx, input_token, + &messages, &nmessages); + if (major != GSS_S_COMPLETE) + goto cleanup; + } + + process_alerts(ctx, messages, nmessages); + + if (ctx->negoex_step == 1) { + /* Choose a random conversation ID. */ + krb5_generate_random_block(ctx->negoex_conv_id, GUID_LENGTH); + + /* Query each mech for its metadata (this may prune the mech list). */ + query_meta_data(ctx, opt, cred, req_flags); + } else if (ctx->negoex_step == 2) { + /* See if the mech processed the optimistic token. */ + check_optimistic_result(ctx, messages, nmessages); + + /* Pass the acceptor metadata to each mech to prune the list. */ + exchange_meta_data(ctx, cred, req_flags, messages, nmessages); + + /* Process the ACCEPTOR_NEGO message. */ + major = process_acceptor_nego(minor, ctx, messages, nmessages); + if (major != GSS_S_COMPLETE) + goto cleanup; + } + + /* + * Process the input token and/or produce an output token. This may prune + * the mech list, but on success there will be at least one mech entry. + */ + major = mech_init(minor, opt, ctx, cred, req_flags, time_req, + input_chan_bindings, messages, nmessages, + &mech_output_token, &mech_error); + if (major != GSS_S_COMPLETE) + goto cleanup; + heim_assert(!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs), + "Invalid empty NegoEx mechanism list"); + + /* + * At this point in step 2 we have performed the metadata exchange and + * chosen a mech we can use, so discard any fallback mech entries. + */ + if (ctx->negoex_step == 2) + _gss_negoex_select_auth_mech(ctx, HEIM_TAILQ_FIRST(&ctx->negoex_mechs)); + + major = verify_checksum(minor, ctx, messages, nmessages, input_token, + &send_alert); + if (major != GSS_S_COMPLETE) + goto cleanup; + + if (input_token != GSS_C_NO_BUFFER) { + if (krb5_storage_write(ctx->negoex_transcript, + input_token->value, + input_token->length) != input_token->length) { + major = GSS_S_FAILURE; + *minor = ENOMEM; + goto cleanup; + } + } + + major = make_output_token(minor, ctx, &mech_output_token, send_alert, + output_token); + if (major != GSS_S_COMPLETE) + goto cleanup; + + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE : + GSS_S_CONTINUE_NEEDED; + +cleanup: + free(messages); + gss_release_buffer(&tmpMinor, &mech_output_token); + _gss_negoex_end(ctx); + + if (GSS_ERROR(major)) { + if (!mech_error) { + krb5_context context = _gss_mg_krb5_context(); + + gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + major, *minor, + "NegoEx failed to initialize security context: %s", + krb5_get_error_message(context, *minor)); + } + + _gss_negoex_release_context(ctx); + } + + return major; +} + +OM_uint32 +_gss_negoex_accept(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_cred_id_t cred, + gss_const_buffer_t input_token, + const gss_channel_bindings_t input_chan_bindings, + gss_buffer_t output_token, + gss_cred_id_t *deleg_cred) +{ + OM_uint32 major, tmpMinor; + gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER; + struct negoex_message *messages = NULL; + struct negoex_auth_mech *mech; + size_t nmessages; + int send_alert = FALSE, mech_error = FALSE; + + if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) { + major = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + + major = _gss_negoex_begin(minor, ctx); + if (major != GSS_S_COMPLETE) + goto cleanup; + + ctx->negoex_step++; + + major = _gss_negoex_parse_token(minor, ctx, input_token, + &messages, &nmessages); + if (major != GSS_S_COMPLETE) + goto cleanup; + + process_alerts(ctx, messages, nmessages); + + if (ctx->negoex_step == 1) { + /* + * Read the INITIATOR_NEGO message to prune the candidate mech list. + */ + major = process_initiator_nego(minor, ctx, messages, nmessages); + if (major != GSS_S_COMPLETE) + goto cleanup; + + /* + * Pass the initiator metadata to each mech to prune the list, and + * query each mech for its acceptor metadata (which may also prune the + * list). + */ + exchange_meta_data(ctx, cred, 0, messages, nmessages); + query_meta_data(ctx, NULL, cred, 0); + + if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { + *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS; + major = GSS_S_FAILURE; + goto cleanup; + } + } + + /* + * Process the input token and possibly produce an output token. This may + * prune the list to a single mech. Continue on error if an output token + * is generated, so that we send the token to the initiator. + */ + major = mech_accept(minor, ctx, cred, input_chan_bindings, + messages, nmessages, &mech_output_token, + deleg_cred, &mech_error); + if (major != GSS_S_COMPLETE && mech_output_token.length == 0) + goto cleanup; + + if (major == GSS_S_COMPLETE) { + major = verify_checksum(minor, ctx, messages, nmessages, input_token, + &send_alert); + if (major != GSS_S_COMPLETE) + goto cleanup; + } + + if (krb5_storage_write(ctx->negoex_transcript, + input_token->value, + input_token->length) != input_token->length) { + major = GSS_S_FAILURE; + *minor = ENOMEM; + goto cleanup; + } + + major = make_output_token(minor, ctx, &mech_output_token, send_alert, + output_token); + if (major != GSS_S_COMPLETE) + goto cleanup; + + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE : + GSS_S_CONTINUE_NEEDED; + +cleanup: + free(messages); + gss_release_buffer(&tmpMinor, &mech_output_token); + _gss_negoex_end(ctx); + + if (GSS_ERROR(major)) { + if (!mech_error) { + krb5_context context = _gss_mg_krb5_context(); + + gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + major, *minor, + "NegoEx failed to accept security context: %s", + krb5_get_error_message(context, *minor)); + } + + _gss_negoex_release_context(ctx); + } + + return major; +} diff --git a/lib/gssapi/spnego/negoex_err.et b/lib/gssapi/spnego/negoex_err.et new file mode 100644 index 000000000..99a8a2ec3 --- /dev/null +++ b/lib/gssapi/spnego/negoex_err.et @@ -0,0 +1,25 @@ +# +# NegoEx error messages +# + +id "$Id$" + +error_table ngex + +prefix NEGOEX + +error_code INVALID_MESSAGE_SIGNATURE, "Invalid NegoEx signature" +error_code INVALID_MESSAGE_TYPE, "Invalid NegoEx message type" +error_code INVALID_MESSAGE_SIZE, "Invalid NegoEx message size" +error_code INVALID_CONVERSATION_ID, "Invalid NegoEx conversation ID" +error_code AUTH_SCHEME_NOT_FOUND, "NegoEx authentication scheme not found" +error_code MISSING_NEGO_MESSAGE, "Missing NegoEx negotiate message" +error_code MISSING_AP_REQUEST_MESSAGE, "Missing NegoEx authentication protocol request message" +error_code NO_AVAILABLE_MECHS, "No mutually supported NegoEx authentication schemes" +error_code NO_VERIFY_KEY, "No NegoEx verify key" +error_code UNKNOWN_CHECKSUM_SCHEME, "Unknown NegoEx checksum scheme" +error_code INVALID_CHECKSUM, "Invalid NegoEx checksum" +error_code UNSUPPORTED_CRITICAL_EXTENSION, "Unsupported critical NegoEx extension" +error_code UNSUPPORTED_VERSION, "Unsupported NegoEx version" +error_code MESSAGE_OUT_OF_SEQUENCE, "NegoEx message out of sequence" + diff --git a/lib/gssapi/spnego/negoex_locl.h b/lib/gssapi/spnego/negoex_locl.h new file mode 100644 index 000000000..3e0d29a31 --- /dev/null +++ b/lib/gssapi/spnego/negoex_locl.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2011-2019 PADL Software Pty Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NEGOEX_LOCL_H +#define NEGOEX_LOCL_H + +#include + +struct gssspnego_ctx_desc; + +#define MESSAGE_SIGNATURE 0x535458454F47454EULL + +#define EXTENSION_LENGTH 12 + +#define EXTENSION_FLAG_CRITICAL 0x80000000 + +#define CHECKSUM_SCHEME_RFC3961 1 + +#define NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM 23 +#define NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM 25 + +#define CHECKSUM_HEADER_LENGTH 20 + +#define GUID_LENGTH 16 + +typedef uint8_t auth_scheme[GUID_LENGTH]; +typedef uint8_t conversation_id[GUID_LENGTH]; +#define GUID_EQ(a, b) (memcmp(a, b, GUID_LENGTH) == 0) + +#define NEGO_MESSAGE_HEADER_LENGTH 96 +#define EXCHANGE_MESSAGE_HEADER_LENGTH 64 +#define VERIFY_MESSAGE_HEADER_LENGTH 80 +#define ALERT_MESSAGE_HEADER_LENGTH 72 +#define ALERT_LENGTH 12 +#define ALERT_PULSE_LENGTH 8 + +#define ALERT_TYPE_PULSE 1 +#define ALERT_VERIFY_NO_KEY 1 + +enum message_type { + INITIATOR_NEGO = 0, /* NEGO_MESSAGE */ + ACCEPTOR_NEGO, /* NEGO_MESSAGE */ + INITIATOR_META_DATA, /* EXCHANGE_MESSAGE */ + ACCEPTOR_META_DATA, /* EXCHANGE_MESSAGE */ + CHALLENGE, /* EXCHANGE_MESSAGE */ + AP_REQUEST, /* EXCHANGE_MESSAGE */ + VERIFY, /* VERIFY_MESSAGE */ + ALERT, /* ALERT */ +}; + +struct nego_message { + uint8_t random[32]; + const uint8_t *schemes; + uint16_t nschemes; +}; + +struct exchange_message { + auth_scheme scheme; + gss_buffer_desc token; +}; + +struct verify_message { + auth_scheme scheme; + uint32_t cksum_type; + const uint8_t *cksum; + size_t cksum_len; + size_t offset_in_token; +}; + +struct alert_message { + auth_scheme scheme; + int verify_no_key; +}; + +struct negoex_message { + uint32_t type; + union { + struct nego_message n; + struct exchange_message e; + struct verify_message v; + struct alert_message a; + } u; +}; + +struct negoex_auth_mech { + HEIM_TAILQ_ENTRY(negoex_auth_mech) links; + gss_OID oid; + auth_scheme scheme; + gss_ctx_id_t mech_context; + gss_buffer_desc metadata; + krb5_crypto crypto; + krb5_crypto verify_crypto; + int complete; + int sent_checksum; + int verified_checksum; +}; + +#define NEGOEX_LOG_LEVEL 10 + +#endif /* NEGOEX_LOCL_H */ diff --git a/lib/gssapi/spnego/negoex_util.c b/lib/gssapi/spnego/negoex_util.c new file mode 100644 index 000000000..5886f41ea --- /dev/null +++ b/lib/gssapi/spnego/negoex_util.c @@ -0,0 +1,1059 @@ +/* + * Copyright (C) 2011-2019 PADL Software Pty Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spnego_locl.h" + +static void +release_auth_mech(krb5_context context, struct negoex_auth_mech *mech); + +/* + * SPNEGO expects to find the active mech context in ctx->negotiated_ctx_id, + * but the metadata exchange APIs force us to have one mech context per mech + * entry. To address this mismatch, move the active mech context (if we have + * one) to ctx->negotiated_ctx_id at the end of NegoEx processing. + */ +void +_gss_negoex_end(gssspnego_ctx ctx) +{ + struct negoex_auth_mech *mech; + + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT) + return; + + heim_assert(ctx->negotiated_ctx_id == GSS_C_NO_CONTEXT, + "SPNEGO/NegoEx context mismatch"); + ctx->negotiated_ctx_id = mech->mech_context; + mech->mech_context = GSS_C_NO_CONTEXT; +} + +OM_uint32 +_gss_negoex_begin(OM_uint32 *minor, gssspnego_ctx ctx) +{ + struct negoex_auth_mech *mech; + + if (ctx->negoex_transcript != NULL) { + /* + * The context is already initialized for NegoEx; undo what + * _gss_negoex_end() did, if applicable. + */ + if (ctx->negotiated_ctx_id != GSS_C_NO_CONTEXT) { + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + heim_assert(mech != NULL && mech->mech_context == GSS_C_NO_CONTEXT, + "NegoEx/SPNEGO context mismatch"); + mech->mech_context = ctx->negotiated_ctx_id; + ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT; + } + return GSS_S_COMPLETE; + } + + ctx->negoex_transcript = krb5_storage_emem(); + if (ctx->negoex_transcript == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + krb5_storage_set_byteorder(ctx->negoex_transcript, + KRB5_STORAGE_BYTEORDER_LE); + + return GSS_S_COMPLETE; +} + +static void +release_all_mechs(gssspnego_ctx ctx, krb5_context context) +{ + struct negoex_auth_mech *mech, *next; + + HEIM_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next) { + release_auth_mech(context, mech); + } + + HEIM_TAILQ_INIT(&ctx->negoex_mechs); +} + +void +_gss_negoex_release_context(gssspnego_ctx ctx) +{ + krb5_context context = _gss_mg_krb5_context(); + + if (ctx->negoex_transcript != NULL) { + krb5_storage_free(ctx->negoex_transcript); + ctx->negoex_transcript = NULL; + } + + release_all_mechs(ctx, context); +} + +static int +guid_to_string(const uint8_t guid[16], char *buffer, size_t bufsiz) +{ + uint32_t data1; + uint16_t data2, data3; + + _gss_mg_decode_le_uint32(&guid[0], &data1); + _gss_mg_decode_le_uint16(&guid[4], &data2); + _gss_mg_decode_le_uint16(&guid[6], &data3); + + return snprintf(buffer, bufsiz, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + data1, data2, data3, guid[8], guid[9], guid[10], guid[11], + guid[12], guid[13], guid[14], guid[15]); +} + +void +_gss_negoex_log_auth_scheme(int initiator, + int index, + const auth_scheme scheme) +{ + char scheme_str[37]; + + guid_to_string(scheme, scheme_str, sizeof(scheme_str)); + + _gss_mg_log(NEGOEX_LOG_LEVEL, + "negoex: %s authentication scheme %d %s", + initiator ? "proposing" : "received", index, scheme_str); +} + +void +_gss_negoex_log_message(int direction, + enum message_type type, + const conversation_id conv_id, + unsigned int seqnum, + unsigned int header_len, + unsigned int msg_len) +{ + char conv_str[37]; + char *typestr; + + if (type == INITIATOR_NEGO) + typestr = "INITIATOR_NEGO"; + else if (type == ACCEPTOR_NEGO) + typestr = "ACCEPTOR_NEGO"; + else if (type == INITIATOR_META_DATA) + typestr = "INITIATOR_META_DATA"; + else if (type == ACCEPTOR_META_DATA) + typestr = "ACCEPTOR_META_DATA"; + else if (type == CHALLENGE) + typestr = "CHALLENGE"; + else if (type == AP_REQUEST) + typestr = "AP_REQUEST"; + else if (type == VERIFY) + typestr = "VERIFY"; + else if (type == ALERT) + typestr = "ALERT"; + else + typestr = "UNKNOWN"; + + guid_to_string(conv_id, conv_str, sizeof(conv_str)); + _gss_mg_log(NEGOEX_LOG_LEVEL, + "negoex: %s (%d)%s conversation %s", + direction ? "received" : "sending", + seqnum, typestr, conv_str); +} + +/* + * Check that the described vector lies within the message, and return a + * pointer to its first element. + */ +static inline const uint8_t * +vector_base(size_t offset, size_t count, size_t width, + const uint8_t *msg_base, size_t msg_len) +{ + if (offset > msg_len || count > (msg_len - offset) / width) + return NULL; + return msg_base + offset; +} + +static OM_uint32 +parse_nego_message(OM_uint32 *minor, krb5_storage *sp, + const uint8_t *msg_base, size_t msg_len, + struct nego_message *msg) +{ + krb5_error_code ret; + const uint8_t *p; + uint64_t protocol_version; + uint32_t extension_type, offset; + uint16_t count; + size_t i; + + if (krb5_storage_read(sp, msg->random, + sizeof(msg->random)) != sizeof(msg->random)) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + ret = krb5_ret_uint64(sp, &protocol_version); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (protocol_version != 0) { + *minor = (OM_uint32)NEGOEX_UNSUPPORTED_VERSION; + return GSS_S_UNAVAILABLE; + } + + ret = krb5_ret_uint32(sp, &offset); + if (ret == 0) + ret = krb5_ret_uint16(sp, &count); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + msg->schemes = vector_base(offset, count, GUID_LENGTH, msg_base, msg_len); + msg->nschemes = count; + if (msg->schemes == NULL) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + ret = krb5_ret_uint32(sp, &offset); + if (ret == 0) + ret = krb5_ret_uint16(sp, &count); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + p = vector_base(offset, count, EXTENSION_LENGTH, msg_base, msg_len); + for (i = 0; i < count; i++) { + _gss_mg_decode_le_uint32(p + i * EXTENSION_LENGTH, &extension_type); + if (extension_type & EXTENSION_FLAG_CRITICAL) { + *minor = (OM_uint32)NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION; + return GSS_S_UNAVAILABLE; + } + } + + return GSS_S_COMPLETE; +} + +static OM_uint32 +parse_exchange_message(OM_uint32 *minor, krb5_storage *sp, + const uint8_t *msg_base, size_t msg_len, + struct exchange_message *msg) +{ + krb5_error_code ret; + const uint8_t *p; + uint32_t offset; + uint16_t len; + + if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) != GUID_LENGTH) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + ret = krb5_ret_uint32(sp, &offset); + if (ret == 0) + ret = krb5_ret_uint16(sp, &len); + if (ret) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + p = vector_base(offset, len, 1, msg_base, msg_len); + if (p == NULL) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + msg->token.value = (void *)p; + msg->token.length = len; + + return GSS_S_COMPLETE; +} + +static OM_uint32 +parse_verify_message(OM_uint32 *minor, krb5_storage *sp, + const uint8_t *msg_base, size_t msg_len, + size_t token_offset, struct verify_message *msg) +{ + krb5_error_code ret; + uint32_t hdrlen, cksum_scheme; + uint32_t offset, len; + + if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) == GUID_LENGTH) + ret = 0; + else + ret = NEGOEX_INVALID_MESSAGE_SIZE; + if (ret == 0) + ret = krb5_ret_uint32(sp, &hdrlen); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (hdrlen != CHECKSUM_HEADER_LENGTH) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + ret = krb5_ret_uint32(sp, &cksum_scheme); + if (ret == 0) + ret = krb5_ret_uint32(sp, &msg->cksum_type); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (cksum_scheme != CHECKSUM_SCHEME_RFC3961) { + *minor = (OM_uint32)NEGOEX_UNKNOWN_CHECKSUM_SCHEME; + return GSS_S_UNAVAILABLE; + } + + ret = krb5_ret_uint32(sp, &offset); + if (ret == 0) + ret = krb5_ret_uint32(sp, &len); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + msg->cksum = vector_base(offset, len, 1, msg_base, msg_len); + msg->cksum_len = len; + if (msg->cksum == NULL) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + msg->offset_in_token = token_offset; + return GSS_S_COMPLETE; +} + +static OM_uint32 +storage_from_memory(OM_uint32 *minor, + const uint8_t *data, + size_t length, + krb5_storage **sp) +{ + *sp = krb5_storage_from_readonly_mem(data, length); + if (sp == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + krb5_storage_set_byteorder(*sp, KRB5_STORAGE_BYTEORDER_LE); + krb5_storage_set_eof_code(*sp, NEGOEX_INVALID_MESSAGE_SIZE); + + return 0; +} + +static OM_uint32 +parse_alert_message(OM_uint32 *minor, krb5_storage *sp, + const uint8_t *msg_base, size_t msg_len, + struct alert_message *msg) +{ + OM_uint32 major; + krb5_error_code ret; + const uint8_t *p; + uint32_t error_code, atype; + uint32_t alerts_offset, nalerts, value_offset, value_len; + size_t i; + krb5_storage *alerts; + + if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) == GUID_LENGTH) + ret = 0; + else + ret = NEGOEX_INVALID_MESSAGE_SIZE; + if (ret == 0) + ret = krb5_ret_uint32(sp, &error_code); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + ret = krb5_ret_uint32(sp, &alerts_offset); + if (ret == 0) + ret = krb5_ret_uint32(sp, &nalerts); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + p = vector_base(alerts_offset, nalerts, ALERT_LENGTH, msg_base, msg_len); + if (p == NULL) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + /* Look for a VERIFY_NO_KEY pulse alert in the alerts vector. */ + msg->verify_no_key = FALSE; + + major = storage_from_memory(minor, p, nalerts * ALERT_LENGTH, &alerts); + if (major != GSS_S_COMPLETE) + return major; + + for (i = 0; i < nalerts; i++) { + ret = krb5_ret_uint32(alerts, &atype); + if (ret == 0) + ret = krb5_ret_uint32(alerts, &value_offset); + if (ret == 0) + ret = krb5_ret_uint32(alerts, &value_len); + if (ret) { + *minor = ret; + major = GSS_S_DEFECTIVE_TOKEN; + break; + } + + p = vector_base(value_offset, value_len, 1, msg_base, msg_len); + if (p == NULL) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + major = GSS_S_DEFECTIVE_TOKEN; + break; + } + + if (atype == ALERT_TYPE_PULSE && value_len >= ALERT_PULSE_LENGTH) { + krb5_storage *pulse; + uint32_t hdrlen, reason; + + major = storage_from_memory(minor, p, value_len, &pulse); + if (major != GSS_S_COMPLETE) + break; + + ret = krb5_ret_uint32(pulse, &hdrlen); + if (ret == 0) + ret = krb5_ret_uint32(pulse, &reason); + krb5_storage_free(pulse); + if (ret) { + *minor = ret; + major = GSS_S_DEFECTIVE_TOKEN; + break; + } + + if (reason == ALERT_VERIFY_NO_KEY) + msg->verify_no_key = TRUE; + } + } + + krb5_storage_free(alerts); + + return major; +} + +static OM_uint32 +parse_message(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_const_buffer_t token, + size_t *token_offset, + struct negoex_message *msg) +{ + OM_uint32 major; + krb5_error_code ret; + krb5_storage *sp; + uint64_t signature; + uint32_t header_len, msg_len; + uint32_t type, seqnum; + conversation_id conv_id; + size_t token_remaining = token->length - *token_offset; + const uint8_t *msg_base = (uint8_t *)token->value + *token_offset; + + major = storage_from_memory(minor, msg_base, token_remaining, &sp); + if (major != GSS_S_COMPLETE) + return major; + + major = GSS_S_DEFECTIVE_TOKEN; + + ret = krb5_ret_uint64(sp, &signature); + if (ret == 0) + ret = krb5_ret_uint32(sp, &type); + if (ret == 0) + ret = krb5_ret_uint32(sp, &seqnum); + if (ret == 0) + ret = krb5_ret_uint32(sp, &header_len); + if (ret == 0) + ret = krb5_ret_uint32(sp, &msg_len); + if (ret == 0) { + if (krb5_storage_read(sp, conv_id, GUID_LENGTH) != GUID_LENGTH) + ret = NEGOEX_INVALID_MESSAGE_SIZE; + } + if (ret) { + *minor = ret; + goto cleanup; + } + + if (msg_len > token_remaining || header_len > msg_len) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + goto cleanup; + } + if (signature != MESSAGE_SIGNATURE) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIGNATURE; + goto cleanup; + } + if (seqnum != ctx->negoex_seqnum) { + *minor = (OM_uint32)NEGOEX_MESSAGE_OUT_OF_SEQUENCE; + goto cleanup; + } + if (seqnum == 0) { + memcpy(ctx->negoex_conv_id, conv_id, GUID_LENGTH); + } else if (!GUID_EQ(conv_id, ctx->negoex_conv_id)) { + *minor = (OM_uint32)NEGOEX_INVALID_CONVERSATION_ID; + goto cleanup; + } + + krb5_storage_truncate(sp, msg_len); + + msg->type = type; + if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO) { + major = parse_nego_message(minor, sp, msg_base, msg_len, &msg->u.n); + } else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA || + type == CHALLENGE || type == AP_REQUEST) { + major = parse_exchange_message(minor, sp, msg_base, msg_len, + &msg->u.e); + } else if (type == VERIFY) { + major = parse_verify_message(minor, sp, msg_base, msg_len, + msg_base - (uint8_t *)token->value, + &msg->u.v); + } else if (type == ALERT) { + major = parse_alert_message(minor, sp, msg_base, msg_len, &msg->u.a); + } else { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_TYPE; + goto cleanup; + } + +cleanup: + krb5_storage_free(sp); + + if (major == GSS_S_COMPLETE) { + _gss_negoex_log_message(1, msg->type, + ctx->negoex_conv_id, ctx->negoex_seqnum, + header_len, msg_len); + ctx->negoex_seqnum++; + *token_offset += msg_len; + } + + return major; +} + +/* + * Parse token into an array of negoex_message structures. All pointer fields + * within the parsed messages are aliases into token, so the result can be + * freed with free(). An unknown protocol version, a critical extension, or an + * unknown checksum scheme will cause a parsing failure. Increment the + * sequence number in ctx for each message, and record and check the + * conversation ID in ctx as appropriate. + */ +OM_uint32 +_gss_negoex_parse_token(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_const_buffer_t token, + struct negoex_message **messages_out, + size_t *count_out) +{ + OM_uint32 major = GSS_S_DEFECTIVE_TOKEN; + size_t count = 0; + size_t token_offset = 0; + struct negoex_message *messages = NULL, *newptr; + + *messages_out = NULL; + *count_out = 0; + heim_assert(token != GSS_C_NO_BUFFER, "Invalid null NegoEx input token"); + + while (token_offset < token->length) { + newptr = realloc(messages, (count + 1) * sizeof(*newptr)); + if (newptr == NULL) { + free(messages); + *minor = ENOMEM; + return GSS_S_FAILURE; + } + messages = newptr; + + major = parse_message(minor, ctx, token, &token_offset, + &messages[count]); + if (major != GSS_S_COMPLETE) + break; + + count++; + } + + if (token_offset != token->length) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + major = GSS_S_DEFECTIVE_TOKEN; + } + if (major != GSS_S_COMPLETE) { + free(messages); + return major; + } + + *messages_out = messages; + *count_out = count; + return GSS_S_COMPLETE; +} + +static struct negoex_message * +locate_message(struct negoex_message *messages, size_t nmessages, + enum message_type type) +{ + uint32_t i; + + for (i = 0; i < nmessages; i++) { + if (messages[i].type == type) + return &messages[i]; + } + + return NULL; +} + +struct nego_message * +_gss_negoex_locate_nego_message(struct negoex_message *messages, + size_t nmessages, + enum message_type type) +{ + struct negoex_message *msg = locate_message(messages, nmessages, type); + + return (msg == NULL) ? NULL : &msg->u.n; +} + +struct exchange_message * +_gss_negoex_locate_exchange_message(struct negoex_message *messages, + size_t nmessages, + enum message_type type) +{ + struct negoex_message *msg = locate_message(messages, nmessages, type); + + return (msg == NULL) ? NULL : &msg->u.e; +} + +struct verify_message * +_gss_negoex_locate_verify_message(struct negoex_message *messages, + size_t nmessages) +{ + struct negoex_message *msg = locate_message(messages, nmessages, VERIFY); + + return (msg == NULL) ? NULL : &msg->u.v; +} + +struct alert_message * +_gss_negoex_locate_alert_message(struct negoex_message *messages, + size_t nmessages) +{ + struct negoex_message *msg = locate_message(messages, nmessages, ALERT); + + return (msg == NULL) ? NULL : &msg->u.a; +} + +#define CHECK(ret, x) do { (ret) = (x); if (ret) goto fail; } while (0) + +static krb5_error_code +store_bytes(krb5_storage *sp, const void *bytes, size_t length) +{ + ssize_t ssize; + + ssize = krb5_storage_write(sp, bytes, length); + if (ssize != length) + return ENOMEM; + + return 0; +} + +/* + * Add the encoding of a MESSAGE_HEADER structure to buf, given the number of + * bytes of the payload following the full header. Increment the sequence + * number in ctx. Set *payload_start_out to the position of the payload within + * the message. + */ +static OM_uint32 +put_message_header(OM_uint32 *minor, gssspnego_ctx ctx, + enum message_type type, uint32_t payload_len, + uint32_t *payload_start_out) +{ + krb5_error_code ret; + size_t header_len = 0; + + if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO) + header_len = NEGO_MESSAGE_HEADER_LENGTH; + else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA || + type == CHALLENGE || type == AP_REQUEST) + header_len = EXCHANGE_MESSAGE_HEADER_LENGTH; + else if (type == VERIFY) + header_len = VERIFY_MESSAGE_HEADER_LENGTH; + else if (type == ALERT) + header_len = ALERT_MESSAGE_HEADER_LENGTH; + else + heim_assert(0, "Invalid NegoEx message type"); + + /* Signature */ + CHECK(ret, krb5_store_uint64(ctx->negoex_transcript, MESSAGE_SIGNATURE)); + /* MessageType */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, type)); + /* SequenceNum */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ctx->negoex_seqnum)); + /* cbHeaderLength */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, header_len)); + /* cbMessageLength */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, header_len + payload_len)); + /* ConversationId */ + CHECK(ret, store_bytes(ctx->negoex_transcript, ctx->negoex_conv_id, GUID_LENGTH)); + + _gss_negoex_log_message(0, type, + ctx->negoex_conv_id, ctx->negoex_seqnum, + header_len, + header_len + payload_len); + + ctx->negoex_seqnum++; + + *payload_start_out = header_len; + return GSS_S_COMPLETE; + +fail: + *minor = ret; + return GSS_S_FAILURE; +} + +OM_uint32 +_gss_negoex_add_nego_message(OM_uint32 *minor, + gssspnego_ctx ctx, + enum message_type type, + uint8_t random[32]) +{ + OM_uint32 major; + krb5_error_code ret; + struct negoex_auth_mech *mech; + uint32_t payload_start; + uint16_t nschemes; + + nschemes = 0; + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) + nschemes++; + + major = put_message_header(minor, ctx, type, + nschemes * GUID_LENGTH, &payload_start); + if (major != GSS_S_COMPLETE) + return major; + + CHECK(ret, store_bytes(ctx->negoex_transcript, random, 32)); + /* ProtocolVersion */ + CHECK(ret, krb5_store_uint64(ctx->negoex_transcript, 0)); + /* AuthSchemes vector */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start)); + CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, nschemes)); + /* Extensions vector */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start)); + CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, 0)); + /* Four bytes of padding to reach a multiple of 8 bytes. */ + CHECK(ret, store_bytes(ctx->negoex_transcript, "\0\0\0\0", 4)); + + /* Payload (auth schemes) */ + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) { + CHECK(ret, store_bytes(ctx->negoex_transcript, mech->scheme, GUID_LENGTH)); + } + + return GSS_S_COMPLETE; + +fail: + *minor = ret; + return GSS_S_FAILURE; +} + +OM_uint32 +_gss_negoex_add_exchange_message(OM_uint32 *minor, + gssspnego_ctx ctx, + enum message_type type, + const auth_scheme scheme, + gss_buffer_t token) +{ + OM_uint32 major; + krb5_error_code ret; + uint32_t payload_start; + + major = put_message_header(minor, ctx, type, token->length, &payload_start); + if (major != GSS_S_COMPLETE) + return major; + + CHECK(ret, store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH)); + /* Exchange byte vector */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, token->length)); + /* Payload (token) */ + CHECK(ret, store_bytes(ctx->negoex_transcript, token->value, token->length)); + + return GSS_S_COMPLETE; + +fail: + *minor = ret; + return GSS_S_FAILURE; +} + +OM_uint32 +_gss_negoex_add_verify_message(OM_uint32 *minor, + gssspnego_ctx ctx, + const auth_scheme scheme, + uint32_t cksum_type, + const uint8_t *cksum, + uint32_t cksum_len) +{ + OM_uint32 major; + krb5_error_code ret; + uint32_t payload_start; + + major = put_message_header(minor, ctx, VERIFY, cksum_len, &payload_start); + if (major != GSS_S_COMPLETE) + return major; + + CHECK(ret, store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, CHECKSUM_HEADER_LENGTH)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, CHECKSUM_SCHEME_RFC3961)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, cksum_type)); + /* ChecksumValue vector */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, cksum_len)); + /* Four bytes of padding to reach a multiple of 8 bytes. */ + CHECK(ret, store_bytes(ctx->negoex_transcript, "\0\0\0\0", 4)); + /* Payload (checksum contents) */ + CHECK(ret, store_bytes(ctx->negoex_transcript, cksum, cksum_len)); + + return GSS_S_COMPLETE; + +fail: + *minor = ret; + return GSS_S_FAILURE; +} + +/* + * Add an ALERT_MESSAGE containing a single ALERT_TYPE_PULSE alert with the + * reason ALERT_VERIFY_NO_KEY. + */ +OM_uint32 +_gss_negoex_add_verify_no_key_alert(OM_uint32 *minor, + gssspnego_ctx ctx, + const auth_scheme scheme) +{ + OM_uint32 major; + krb5_error_code ret; + uint32_t payload_start; + + major = put_message_header(minor, ctx, + ALERT, ALERT_LENGTH + ALERT_PULSE_LENGTH, + &payload_start); + if (major != GSS_S_COMPLETE) + return major; + + CHECK(ret, store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH)); + /* ErrorCode */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, 0)); + /* Alerts vector */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start)); + CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, 1)); + /* Six bytes of padding to reach a multiple of 8 bytes. */ + CHECK(ret, store_bytes(ctx->negoex_transcript, "\0\0\0\0\0\0", 6)); + /* Payload part 1: a single ALERT element */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_TYPE_PULSE)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, + payload_start + ALERT_LENGTH)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_PULSE_LENGTH)); + /* Payload part 2: ALERT_PULSE */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_PULSE_LENGTH)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_VERIFY_NO_KEY)); + + return GSS_S_COMPLETE; + +fail: + *minor = ret; + return GSS_S_FAILURE; +} + + +static void +release_auth_mech(krb5_context context, + struct negoex_auth_mech *mech) +{ + OM_uint32 tmpmin; + + if (mech == NULL) + return; + + gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL); + gss_release_oid(&tmpmin, &mech->oid); + gss_release_buffer(&tmpmin, &mech->metadata); + if (mech->crypto) + krb5_crypto_destroy(context, mech->crypto); + if (mech->verify_crypto) + krb5_crypto_destroy(context, mech->verify_crypto); + + free(mech); +} + +void +_gss_negoex_delete_auth_mech(gssspnego_ctx ctx, + struct negoex_auth_mech *mech) +{ + krb5_context context = _gss_mg_krb5_context(); + + HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links); + release_auth_mech(context, mech); +} + +/* Remove all auth mech entries except for mech from ctx->mechs. */ +void +_gss_negoex_select_auth_mech(gssspnego_ctx ctx, + struct negoex_auth_mech *mech) +{ + krb5_context context = _gss_mg_krb5_context(); + + heim_assert(mech != NULL, "Invalid null NegoEx mech"); + HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links); + release_all_mechs(ctx, context); + HEIM_TAILQ_INSERT_HEAD(&ctx->negoex_mechs, mech, links); +} + +OM_uint32 +_gss_negoex_add_auth_mech(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_const_OID oid, + auth_scheme scheme) +{ + OM_uint32 major; + struct negoex_auth_mech *mech; + + mech = calloc(1, sizeof(*mech)); + if (mech == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + major = gss_duplicate_oid(minor, (gss_OID)oid, &mech->oid); + if (major != GSS_S_COMPLETE) { + free(mech); + return major; + } + + memcpy(mech->scheme, scheme, GUID_LENGTH); + + HEIM_TAILQ_INSERT_TAIL(&ctx->negoex_mechs, mech, links); + + *minor = 0; + return GSS_S_COMPLETE; +} + +struct negoex_auth_mech * +_gss_negoex_locate_auth_scheme(gssspnego_ctx ctx, + const auth_scheme scheme) +{ + struct negoex_auth_mech *mech; + + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) { + if (GUID_EQ(mech->scheme, scheme)) + return mech; + } + + return NULL; +} + +/* + * Prune ctx->mechs to the schemes present in schemes, and reorder them to + * match its order. + */ +void +_gss_negoex_common_auth_schemes(gssspnego_ctx ctx, + const uint8_t *schemes, + uint16_t nschemes) +{ + struct negoex_mech_list list; + struct negoex_auth_mech *mech; + uint16_t i; + krb5_context context = _gss_mg_krb5_context(); + + /* Construct a new list in the order of schemes. */ + HEIM_TAILQ_INIT(&list); + for (i = 0; i < nschemes; i++) { + mech = _gss_negoex_locate_auth_scheme(ctx, schemes + i * GUID_LENGTH); + if (mech == NULL) + continue; + HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links); + HEIM_TAILQ_INSERT_TAIL(&list, mech, links); + } + + /* Release any leftover entries and replace the context list. */ + release_all_mechs(ctx, context); + HEIM_TAILQ_CONCAT(&ctx->negoex_mechs, &list, links); +} + +/* + * Prune ctx->mechs to the schemes present in schemes, but do not change + * their order. + */ +void +_gss_negoex_restrict_auth_schemes(gssspnego_ctx ctx, + const uint8_t *schemes, + uint16_t nschemes) +{ + struct negoex_auth_mech *mech, *next; + uint16_t i; + int found; + + HEIM_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next) { + found = FALSE; + for (i = 0; i < nschemes && !found; i++) { + if (GUID_EQ(mech->scheme, schemes + i * GUID_LENGTH)) + found = TRUE; + } + + if (!found) + _gss_negoex_delete_auth_mech(ctx, mech); + } +} + +/* + * Return the OID of the current NegoEx mechanism. + */ +struct negoex_auth_mech * +_gss_negoex_negotiated_mech(gssspnego_ctx ctx) +{ + return HEIM_TAILQ_FIRST(&ctx->negoex_mechs); +} + +/* + * Returns TRUE if mechanism can be negotiated by both NegoEx and SPNEGO + */ + +int +_gss_negoex_and_spnego_mech_p(gss_const_OID mech) +{ + OM_uint32 major, minor; + gss_OID_set attrs = GSS_C_NO_OID_SET; + int negoex_and_spnego = FALSE; + + major = gss_inquire_attrs_for_mech(&minor, mech, &attrs, NULL); + if (major == GSS_S_COMPLETE) { + gss_test_oid_set_member(&minor, GSS_C_MA_NEGOEX_AND_SPNEGO, + attrs, &negoex_and_spnego); + gss_release_oid_set(&minor, &attrs); + } + + return negoex_and_spnego; +} + +int +_gss_negoex_mech_p(gss_const_OID mech) +{ + OM_uint32 minor; + auth_scheme scheme; + + return gssspi_query_mechanism_info(&minor, mech, + scheme) == GSS_S_COMPLETE; +} + diff --git a/lib/gssapi/spnego/spnego.asn1 b/lib/gssapi/spnego/spnego.asn1 index 048e86bb4..79ac038a0 100644 --- a/lib/gssapi/spnego/spnego.asn1 +++ b/lib/gssapi/spnego/spnego.asn1 @@ -37,14 +37,17 @@ NegTokenInit ::= SEQUENCE { ... } +NegResultEnum ::= ENUMERATED { + accept_completed(0), + accept_incomplete(1), + reject(2), + request-mic(3) +} + -- NB: negResult is not OPTIONAL in the new SPNEGO spec but -- Windows clients do not always send it NegTokenResp ::= SEQUENCE { - negResult [0] ENUMERATED { - accept_completed (0), - accept_incomplete (1), - reject (2), - request-mic (3) } OPTIONAL, + negResult [0] NegResultEnum OPTIONAL, supportedMech [1] MechType OPTIONAL, responseToken [2] OCTET STRING OPTIONAL, mechListMIC [3] OCTET STRING OPTIONAL, diff --git a/lib/gssapi/spnego/spnego_locl.h b/lib/gssapi/spnego/spnego_locl.h index f8b69827a..9b0e3310f 100644 --- a/lib/gssapi/spnego/spnego_locl.h +++ b/lib/gssapi/spnego/spnego_locl.h @@ -50,6 +50,7 @@ #include #endif +#include #include #include #include @@ -63,11 +64,13 @@ #endif #include +#include #include #include #include "spnego_asn1.h" +#include "negoex_locl.h" #include "utils.h" #include @@ -75,30 +78,72 @@ #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" diff --git a/lib/gssapi/test_context.c b/lib/gssapi/test_context.c index 7cbe73b8b..ab0b346b7 100644 --- a/lib/gssapi/test_context.c +++ b/lib/gssapi/test_context.c @@ -72,6 +72,9 @@ static int help_flag = 0; static krb5_context context; static krb5_enctype limit_enctype = 0; +static gss_OID_desc test_negoex_1_mech = { 6, "\x69\x85\xa2\xc0\xac\x66" }; +static gss_OID_desc test_negoex_2_mech = { 6, "\x69\x84\xb0\xd1\xa8\x2c" }; + static struct { const char *name; gss_OID oid; @@ -79,7 +82,9 @@ static struct { { "krb5", NULL /* GSS_KRB5_MECHANISM */ }, { "spnego", NULL /* GSS_SPNEGO_MECHANISM */ }, { "ntlm", NULL /* GSS_NTLM_MECHANISM */ }, - { "sasl-digest-md5", NULL /* GSS_SASL_DIGEST_MD5_MECHANISM */ } + { "sasl-digest-md5", NULL /* GSS_SASL_DIGEST_MD5_MECHANISM */ }, + { "test_negoex_1", NULL }, + { "test_negoex_2", NULL }, }; static void @@ -89,6 +94,8 @@ init_o2n(void) o2n[1].oid = GSS_SPNEGO_MECHANISM; o2n[2].oid = GSS_NTLM_MECHANISM; o2n[3].oid = GSS_SASL_DIGEST_MD5_MECHANISM; + o2n[4].oid = &test_negoex_1_mech; + o2n[5].oid = &test_negoex_2_mech; } static gss_OID @@ -98,7 +105,7 @@ string_to_oid(const char *name) for (i = 0; i < sizeof(o2n)/sizeof(o2n[0]); i++) if (strcasecmp(name, o2n[i].name) == 0) return o2n[i].oid; - errx(1, "name '%s' not unknown", name); + errx(1, "name '%s' not known", name); } static void @@ -612,7 +619,7 @@ main(int argc, char **argv) gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL, deleg_cred = GSS_C_NO_CREDENTIAL; gss_name_t cname = GSS_C_NO_NAME; gss_buffer_desc credential_data = GSS_C_EMPTY_BUFFER; - gss_OID_desc oids[4]; + gss_OID_desc oids[6]; gss_OID_set_desc mechoid_descs; gss_OID_set mechoids = GSS_C_NO_OID_SET; gss_key_value_element_desc client_cred_elements[2]; diff --git a/lib/gssapi/test_negoex_mech.c b/lib/gssapi/test_negoex_mech.c new file mode 100644 index 000000000..f9d6c5246 --- /dev/null +++ b/lib/gssapi/test_negoex_mech.c @@ -0,0 +1,572 @@ +/* + * Copyright (C) 2019 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +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; +} + diff --git a/lib/gssapi/version-script.map b/lib/gssapi/version-script.map index ddc42e8ea..635a7b09d 100644 --- a/lib/gssapi/version-script.map +++ b/lib/gssapi/version-script.map @@ -198,6 +198,9 @@ HEIMDAL_GSS_2.0 { __gss_c_ma_pfs_oid_desc; __gss_c_ma_compress_oid_desc; __gss_c_ma_ctx_trans_oid_desc; + __gss_c_ma_negoex_and_spnego_oid_desc; + __gss_c_inq_negoex_key_oid_desc; + __gss_c_inq_negoex_verify_key_oid_desc; local: *; diff --git a/tests/bin/setup-env.in b/tests/bin/setup-env.in index 1577834e3..80c119cd2 100644 --- a/tests/bin/setup-env.in +++ b/tests/bin/setup-env.in @@ -6,6 +6,9 @@ export HEIM_PIDFILE_DIR unset KRB5_CONFIG unset KRB5CCNAME +unset GSS_MECH_CONFIG +unset GSSAPI_SPNEGO_NAME + top_builddir="@top_builddir@" top_srcdir="@top_srcdir@" EGREP="@EGREP@" diff --git a/tests/gss/Makefile.am b/tests/gss/Makefile.am index 96e551f5b..e7c67faf5 100644 --- a/tests/gss/Makefile.am +++ b/tests/gss/Makefile.am @@ -2,9 +2,9 @@ include $(top_srcdir)/Makefile.am.common -noinst_DATA = krb5.conf +noinst_DATA = krb5.conf mech -SCRIPT_TESTS = check-basic check-gss check-gssmask check-context check-spnego check-ntlm +SCRIPT_TESTS = check-basic check-gss check-gssmask check-context check-spnego check-ntlm check-negoex TESTS = $(SCRIPT_TESTS) @@ -49,10 +49,19 @@ check-ntlm: check-ntlm.in Makefile chmod +x check-ntlm.tmp && \ mv check-ntlm.tmp check-ntlm +check-negoex: check-negoex.in Makefile + $(do_subst) < $(srcdir)/check-negoex.in > check-negoex.tmp && \ + chmod +x check-negoex.tmp && \ + mv check-negoex.tmp check-negoex + krb5.conf: krb5.conf.in Makefile $(do_subst) < $(srcdir)/krb5.conf.in > krb5.conf.tmp && \ mv krb5.conf.tmp krb5.conf +mech: mech.in Makefile + $(do_subst) < $(srcdir)/mech.in > mech.tmp && \ + mv mech.tmp mech + CLEANFILES= \ $(TESTS) \ foopassword \ @@ -61,6 +70,7 @@ CLEANFILES= \ krb5ccfile-ds \ server.keytab \ krb5.conf \ + mech \ current-db* \ *.log \ tempfile \ @@ -80,4 +90,5 @@ EXTRA_DIST = \ check-ntlm.in \ check-context.in \ ntlm-user-file.txt \ - krb5.conf.in + krb5.conf.in \ + mech.in diff --git a/tests/gss/check-gssmask.in b/tests/gss/check-gssmask.in index de806b597..e4dd1dbec 100644 --- a/tests/gss/check-gssmask.in +++ b/tests/gss/check-gssmask.in @@ -61,6 +61,7 @@ gssmaestro="../../appl/gssmask/gssmaestro" KRB5_CONFIG="${objdir}/krb5.conf" export KRB5_CONFIG +KRB5CCNAME=${cache} rm -f ${keytabfile} rm -f current-db* rm -f out-* diff --git a/tests/gss/check-negoex.in b/tests/gss/check-negoex.in new file mode 100644 index 000000000..a5f9cdc31 --- /dev/null +++ b/tests/gss/check-negoex.in @@ -0,0 +1,183 @@ +#!/bin/sh +# +# Copyright (c) 2006 Kungliga Tekniska Högskolan +# (Royal Institute of Technology, Stockholm, Sweden). +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the Institute nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $Id$ +# + +env_setup="@env_setup@" +srcdir="@srcdir@" +objdir="@objdir@" + +. ${env_setup} + +R=TEST.H5L.SE + +port=@port@ + +keytabfile="${objdir}/server.keytab-no" +keytab="FILE:${keytabfile}-no" +cache="FILE:krb5ccfile-no" +cacheds="FILE:krb5ccfile-ds-no" + +context="${TESTS_ENVIRONMENT} ../../lib/gssapi/test_context" + +KRB5_CONFIG="${objdir}/krb5.conf" +export KRB5_CONFIG + +KRB5_KTNAME="${keytab}-no" +export KRB5_KTNAME +KRB5CCNAME="${cache}-no" +export KRB5CCNAME +unset NTLM_ACCEPTOR_CCACHE +unset NTLM_USER_FILE + +GSSAPI_SPNEGO_NAME=host@host.test.h5l.se +export GSSAPI_SPNEGO_NAME + +GSS_MECH_CONFIG="${objdir}/mech" +export GSS_MECH_CONFIG + +> messages.log + +exitcode=0 + +echo "======context building for negoex" + +for HOPS in 1 2 3 4 5 +do + echo "test_negoex_1 $HOPS hops" + ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_1 \ + --name-type=hostbased-service \ + host@host.test.h5l.se || \ + { exitcode=1 ; echo test failed; } +done + +for HOPS in 1 2 3 4 5 +do + echo "test_negoex_1 $HOPS hops early keys" + KEY=always ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_1 \ + --name-type=hostbased-service \ + host@host.test.h5l.se || \ + { exitcode=1 ; echo test failed; } +done + +HOPS=1 +echo "test_negoex_1 no keys" + KEY=never ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_1 \ + --name-type=hostbased-service \ + host@host.test.h5l.se 2>/dev/null && \ + { exitcode=1 ; echo test failed; } + +echo "test_negoex_1 no optimistic token" + NEGOEX_NO_OPTIMISTIC_TOKEN=1 ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_1 \ + --name-type=hostbased-service \ + host@host.test.h5l.se || \ + { exitcode=1 ; echo test failed; } + +echo "test_negoex_1 initiator query fail, test_negoex_2 pass" + INIT_QUERY_FAIL=102 ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_2 \ + --name-type=hostbased-service \ + host@host.test.h5l.se 2>/dev/null || \ + { exitcode=1 ; echo test failed; } + +echo "test_negoex_1 acceptor query fail, test_negoex_2 pass" + ACCEPT_QUERY_FAIL=102 ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_2 \ + --name-type=hostbased-service \ + host@host.test.h5l.se 2>/dev/null || \ + { exitcode=1 ; echo test failed; } + +echo "test_negoex_1 acceptor exchange fail, test_negoex_2 pass" + ACCEPT_EXCHANGE_FAIL=102 ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_2 \ + --name-type=hostbased-service \ + host@host.test.h5l.se 2>/dev/null || \ + { exitcode=1 ; echo test failed; } + +echo "test_negoex_1 first mech initiator exchange fail" + INIT_EXCHANGE_FAIL=102 ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_1 \ + --name-type=hostbased-service \ + host@host.test.h5l.se 2>/dev/null && \ + { exitcode=1 ; echo test failed; } + +echo "test_negoex_1 first mech initiator exchange fail, two hops" + HOPS=2 INIT_EXCHANGE_FAIL=102 ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_1 \ + --name-type=hostbased-service \ + host@host.test.h5l.se 2>/dev/null && \ + { exitcode=1 ; echo test failed; } + +echo "test_negoex_1 first mech initiator exchange fail, two hops, early keys" + HOPS=2 KEY=always INIT_EXCHANGE_FAIL=102 ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_1 \ + --name-type=hostbased-service \ + host@host.test.h5l.se 2>/dev/null && \ + { exitcode=1 ; echo test failed; } + +echo "test_negoex_1 first mech init_sec_context fail" + INIT_FAIL=102 ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_1 \ + --name-type=hostbased-service \ + host@host.test.h5l.se 2>/dev/null && \ + { exitcode=1 ; echo test failed; } + +echo "test_negoex_1 first mech accept_sec_context fail" + HOPS=2 ACCEPT_FAIL=102 ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_1 \ + --name-type=hostbased-service \ + host@host.test.h5l.se 2>/dev/null && \ + { exitcode=1 ; echo test failed; } + +echo "test_negoex_1 alert from acceptor to initiator" + HOPS=3 KEY=init-always ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_1 \ + --name-type=hostbased-service \ + host@host.test.h5l.se || \ + { exitcode=1 ; echo test failed; } + +echo "test_negoex_1 alert from initiator to acceptor" + HOPS=4 KEY=accept-always ${context} \ + --mech-type=spnego --ret-mech-type=test_negoex_1 \ + --name-type=hostbased-service \ + host@host.test.h5l.se || \ + { exitcode=1 ; echo test failed; } + +trap "" EXIT + +exit $exitcode diff --git a/tests/gss/check-ntlm.in b/tests/gss/check-ntlm.in index 6e03b012f..f5bf3446a 100644 --- a/tests/gss/check-ntlm.in +++ b/tests/gss/check-ntlm.in @@ -63,6 +63,7 @@ context="${TESTS_ENVIRONMENT} ../../lib/gssapi/test_context" KRB5_CONFIG="${objdir}/krb5.conf" export KRB5_CONFIG +KRB5CCNAME=${cache} KRB5_KTNAME="${keytab}" export KRB5_KTNAME KRB5CCNAME="${cache}" diff --git a/tests/gss/check-spnego.in b/tests/gss/check-spnego.in index 04ae119ad..cfb515e1d 100644 --- a/tests/gss/check-spnego.in +++ b/tests/gss/check-spnego.in @@ -62,6 +62,7 @@ context="${TESTS_ENVIRONMENT} ../../lib/gssapi/test_context" KRB5_CONFIG="${objdir}/krb5.conf" export KRB5_CONFIG +KRB5CCNAME=${cache} KRB5_KTNAME="${keytab}" export KRB5_KTNAME KRB5CCNAME="${cache}" diff --git a/tests/gss/mech.in b/tests/gss/mech.in new file mode 100644 index 000000000..4c4acc9ee --- /dev/null +++ b/tests/gss/mech.in @@ -0,0 +1,5 @@ +# +# Test GSS-API mechglue configuration file. +# +test_negoex_1 2.25.1414534758 @objdir@/../../lib/gssapi/.libs/test_negoex_mech.so +test_negoex_2 2.25.1175737388 @objdir@/../../lib/gssapi/.libs/test_negoex_mech.so