HTTP-Based Cross-Platform Authentication by Using the Negotiate Protocol

 

Sanj Surati & Michael Muckin
Microsoft Consulting Services

December 2002

Applies to:
   Windows® 2000
   Non-Windows web servers
   Kerberos-capable clients

Summary: This article describes the negotiate protocol and binary layouts of information sent over the wire. It is the second article in a series that describes the infrastructure and code required to implement Kerberos-based authentication in a cross-platform environment.

The series consists of three parts:

  • "Network Infrastructure"—Describes the infrastructure required to enable the solution.
  • "SPNEGO Tokens and the Negotiate Protocol"—Describes the negotiate protocol and binary layouts of information sent over the wire.
  • "SPNEGO Token Handler API"—Describes and provides the C source code for an API that will parse and create SPNEGO tokens.

Download Spnegotokenhandler.exe.

Contents

Introduction
SPNEGO Tokens
Summary
Appendix A - References

Introduction

Now that you have read the article "Network Infrastructure", this article will describe the binary formats mentioned in that article.

Assumptions

Intranet web-based applications

This solution assumes the following to be true:

  • The use of this cross-platform authentication solution is intended for intranet applications only.
  • Microsoft's Active Directory/Kerberos implementation is the single user and authentication store.
  • MIT V5 Kerberos is implemented on the UNIX hosts.
  • This solution was verified on Solaris 2.8.

This article does NOT cover:

  • Custom Kerberos error handling
  • Ticket/Session expiration handling

This article assumes that the reader is familiar with Kerberos, the HTTP protocol and C. For an overview of Kerberos authentication in the Windows® 2000 operating system, as well as definitions of important Kerberos-related terminology such as Key Distribution Center (KDC), Ticket Granting Service (TGS), keytab files, and so on, see Windows 2000 Kerberos Authentication. Additional resources are outlined in Appendix A.

Although this article describes a general cross-platform solution, for ease of reference, we will assume non-Windows 2000 web servers to be running a flavor of UNIX with MIT Kerberos V5—, which is the environment used to develop this solution. Additionally, although we reference Windows 2000 in this article, the same information is applicable to later versions of Windows (for example, Windows 7 and Windows Server 2008 R2).

SPNEGO Tokens

After a network is available with the capability to support cross-platform authentication, code must be introduced on the UNIX web servers to authenticate users. This section describes the makeup of SPNEGO tokens, and then shows how they are passed between an Internet Explorer browser client and a web server by using HTTP headers. For cross-platform authentication to work, non-Windows web servers will need to parse SPNEGO tokens to extract Kerberos tokens and later build response tokens to send back to the browser (the next article, "SPNEGO Token Handler API", describes a set of functions which encapsulate the "grunt work").

SPNEGO Token Layout

The following information is taken from RFC 2478 (The Simple and Protected GSS-API Negotiation Mechanism). The binary layout of the tokens is built by using ASN.1 DER. DER refers to Distinguished Encoding Rules. These are a set of common rules for creating binary encodings in a platform independent manner.

The primary intention of SPNEGO is to allow a client and server to negotiate a security mechanism for authentication. SPNEGO tokens wrap mechanism-specific data as follows:

ms995330.http-sso-2-fig01(en-us,MSDN.10).gif

Figure 1. SPNEGO Token Format

This section will outline some of the basic pieces that make up in SPNEGO encodings.

ASN.1 DER Identifiers

When interpreting ASN.1 DER encodings, each element is distinguished by a leading byte that indicates the makeup of the following data. The 3 high bits indicate whether an element is a primitive, context-specific, and constructed. The remaining bits indicate the actual type. For example, when encountering a lead byte of 0x06, this indicates that the data element is an OID. Following are some sample DER types that may be encountered in a SPNEGO encoding:

Table 1. Sample DER Types

Type Tag number(hex)
Integer 0x02.
Bit String 0x03
Octet String 0x04.
Object Identifier(OID) 0x06
Sequence and Sequence Of 0x10

In some cases, we encounter elements that are not primitive types and are context-specific (such as a SPNEGO Token), in which case, uppermost bits will be set, showing a hex value of 0xa0, or similar values.

ASN.1 DER Lengths

When interpreting ASN.1 DER Length encodings, one must be aware of the rules. Lengths can be described using anywhere from 1 to 128 bytes. The leading byte will either indicate the actual length, or how many bytes following it contain the length. In the latter case, lengths are in Network Byte Order (Big-Endian).

If a length is < 127, the lower 7 bits of the start byte will describe the length, and the uppermost bit will be zero (that is, the byte will be <= 0x7F). If the length is > 127, the uppermost bit will be 1 and the lower 7 bits will indicate how many following bytes are used to indicate the length (that is, 0x82 means that the following 2 bytes describe the length).

Table 2. ASN.1 DER Lengths

Length (hex) Example Meaning
0x0b 0x0b Since the upper bit is not set, the length is 0x0b bytes.
0xFE 0x81, 0xFE The first byte indicates that the following byte is the length, therefore the length is 0xFE bytes.
0x05bc 0x82, 0x05, 0xbc The first byte indicates that the following 2 bytes are the length, therefore the length is 0x5bc bytes.

OIDs

Object identifiers (OIDs) are globally-unique identifiers that are used in SPNEGO encodings to refer to GSS mechanisms. OIDs are specified by using a DER encoding. They have a Universal Type of 0x06, and pack their field data by using the following formula:

  1. The first octet has a value of 40 x Value1 + Value2. (This is unambiguous, because Value1 is limited to values 0, 1 and 2; Value2 is limited to the range 0 to 39 when Value1 is 0 or 1; and, according to X.208, n is always at least 2.)
  2. The following octets, if any, encode Value3, …, ValueN. Each value is encoded base 128, most significant digit first, with as few digits as possible, and the most significant bit of each octet except the last in the value's encoding is set to "1."

With regards to the source code provided with this article, there are only three well-known OIDs we recognize, and each has a clearly-defined binary pattern:

Table 3. Recognized Object Identifiers (OIDs)

OID Mechanism Binary representation
1.3.6.1.5.5.2 SPNEGO 0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02
1.2.840.48018.1.2.2 Kerberos V5 Legacy (same as Kerberos V5, but off by 1 bit required for legacy compatibility) 0x06, 0x09, 0x2a, 0x86, 0x48, 0x82, 0xf7, 0x12, 0x01, 0x02, 0x02
1.2.840.113554.1.2.2 Kerberos V5 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02

In SPNEGO encodings, the OID is referred to as a MechType as follows:

MechType ::= OBJECT IDENTIFIER

In each of the encodings in the previous table, the initial byte, 0x06 indicates the element is an OID, and the second byte indicates length (length is < 127 bytes).

There are times in SPNEGO Tokens when multiple mechanism OIDs must be specified as a single value. This is called a MechTypeList:

MechTypeList ::= SEQUENCE of MechType

A sequence identifier is byte 0x30, followed by the length of the sequence (in this case, the length of all following MechTypes added together). A sample DER encoding for a MechList would be as follows:

0x30, 0x16, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x82, 0xf7, 0x12, 0x01, 0x02, 0x02, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02

This is interpreted as follows:

0x30, 0x16 Constructed Sequence that is 22 bytes long
0x06, 0x09, 0x2a, 0x86, 0x48, 0x82, 0xf7, 0x12, 0x01, 0x02, 0x02 Kerberos V5 Legacy OID (11 bytes—1 for identifier, 1 for length, and 9 for actual OID)
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02 Kerberos V5 OID (11 bytes)

Basic Token Types

SPNEGO Tokens consist of two types: an Init Token (NegTokenInit) and a Response Token (NegTokenTarg).

These are defined as follows:

NegotiationToken ::= CHOICE {
                             negTokenInit    [0]  NegTokenInit,
                             negTokenTarg    [1]  NegTokenTarg
                            }

Note that in this case, CHOICE really only indicates that the element as it appears in the stream can be one of the following types. The actual element as it appears in the stream will define which one it is.

Specifiers for these, following DER are:

Table 4. Basic Token Types

Token type Binary representation Example
NegTokenInit 0xa0 <followed by length> 0xa0, 0x82, 0x05, 0xd9
NegTokenTarg 0xa1 <followed by Length> 0xa1, 0x81, 0x85

What this boils down to is that when you receive a SPNEGO encoding, the lead byte will be either 0xa0 or 0xa1, indicating that the encoding describes either a NegTokenInit or a NegTokenTarg.

NegTokenInit

This is the Negotiation token sent from the client to the server. It details one or more of the following:

  1. The Security Mechanisms supported by the client

  2. Flags passed to gss_init_sec_context() that the server must pass to gss_accept_sec_context()

  3. An initial MechToken—this is the initial GSS Token that is retrieved from GSSAPI for the initial MechType in the MechTypeList.

  4. A MechListMIC which is a Message Integrity value generated by passing the contents of MechList into a gss_GetMIC() call of the selected MechType.

    Note This is only documented here for completeness. This field is not used when negotiating Kerberos tokens with Internet Explorer.

Context Flags are specified as follows:

ContextFlags ::= BIT_STRING {
   delegFlag     (0),
   mutualFlag    (1),
   replayFlag    (2),
   sequenceFlag  (3),
   anonFlag      (4),
   confFlag      (5),
   integFlag     (6)
}

The full DER definition is as follows:

NegTokenInit ::= SEQUENCE {
   mechTypes     [0]  MechTypeList  OPTIONAL,
   reqFlags      [1]  ContextFlags  OPTIONAL,
   mechToken     [2]  OCTET STRING  OPTIONAL,
   mechListMIC   [3]  OCTET STRING  OPTIONAL
}

The Octet String values have a Universal Type of 0x04, followed by a length, and the actual octet (for example, 0x04, 0x06, 0x73, 0x61, 0x6E, 0x6A, 0x65, 0x73, 0x00—would describe the 6-byte value of "sanjes\0").

Additionally, when an InitToken is sent, it is prepended by an Application Constructed Object specifier (0x60), and the OID for SPNEGO (see value in OID table above). This is the generic GSSAPI header. For more information, see the SPNEGO and GSSAPI RFCs in Appendix A.

A sample Init token could be as follows:

0x60, 0x82, 0x05, 0xe4, 0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02, 0xa0, 0x82, 0x05, 0xd8, 0x30, 0x82, 0x05, 0xd4, 0xA0, 0x18, 0x30, 0x16, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x82, 0xf7, 0x12, 0x01, 0x02, 0x02, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, 0xA2, 0x82, 0x05, 0xb6, 0x04, 0x82, 0x05, 0xb2, ...

Broken down:

0x60, 0x82, 0x05, 0xe4 Application Constructed Object, length 0x5e4
0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02 SPNEGO OID
0xa0, 0x82, 0x05, 0xd8 NegTokenInit (0xa0), length 0x5d8
0x30, 0x82, 0x05, 0xd4 Constructed Sequence, length 0x05d4
0xA0, 0x18 Seq. Element 0, MechTypeList, length 0x18
0x30, 0x16 Sequence length 0x16
0x06, 0x09, 0x2a, 0x86, 0x48, 0x82, 0xf7, 0x12, 0x01, 0x02, 0x02 Microsoft Kerberos OID
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02 Kerberos V5 OID
0xA2, 0x82, 0x05, 0xb6 Seq. Element 2, MechToken, length 0x5b6
0x04, 0x82, 0x05, 0xb2 OCTET STRING length 0x5b2
OCTET STRING data.

The MechToken, if available, corresponds to the first OID in the MechTypesList.

NegTokenTarg

After a NegTokenInit has been sent from the client to the server, all further communication between the client and server consists of NegTokenTarg tokens. The server's response to NegTokenInit is a NegTokenTarg, and if the client must respond to that, this is also done with a NegTokenTarg. A NegTokenTarg consists of one or more of the following fields:

  1. a Negotiation Result enumeration

  2. a single, supported MechType

  3. a Response MechToken generated by GSSAPI calls

  4. a MechListMIC, which is a Message Integrity value that is generated by passing the contents of MechList (from NegTokenInit) into a gss_GetMIC() call of the selected MechType

    Note   This is only documented here for completeness. This field is not used when negotiating Kerberos tokens with Internet Explorer.

The full DER definition is as follows:

NegTokenTarg      ::=  SEQUENCE {
   negResult      [0]  ENUMERATED {
                            accept_completed (0),
                            accept_incomplete (1),
                            rejected (2) }  OPTIONAL,
   supportedMech  [1]  MechType             OPTIONAL,
   responseToken  [2]  OCTET STRING         OPTIONAL,
   mechListMIC    [3]  OCTET STRING         OPTIONAL
}

With regards to the negResult value, this indicates if the receiver of a NegTokenInit request accepts any of the mechanisms specified in the mechTypes. If the receiver accepts an OID, supportedMech must be filled out, and negResult is set to accept_completed if a context was successfully established, or accept_incomplete if additional exchanges are required. If none of the OIDs in mechTypes are supported by the receiver, then negResult should be set to rejected. Neither negResult nor supportedMech are necessary in subsequent exchanges of NegTokenTarg tokens.

Unlike when a NegTokenInit is sent, the NegTokenTarg is not prepended by a generic GSS header. It is assumed that the receiver will know what to do with the data.

A sample Response token could be as follows:

0xa1, 0x82, 0x01, 0x2c, 0x30, 0x82, 0x01, 0x28, 0xA0, 0x03, 0x0A, 0x01, 0x01, 0xa1, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x82, 0xf7, 0x12, 0x01, 0x02, 0x02, 0xa2, 0x81, 0x88, 0x04, 0x81, 0x85, …

Broken down:

0xa1, 0x82, 0x01, 0x2c NegTokenTarg (0xa1), length 0x12c
0x30, 0x82, 0x01, 0x28 Constructed Sequence, length 0x05d4
0xA0, 0x03, 0x0A, 0x01 Seq. Element 0, negResult, length 1
0x0A, 0x01, 0x01 ENUMERATED, length 1, accept_incomplete
0xa1, 0x0b Seq. Element 1, supportedMech length 0x0b
0x06, 0x09, 0x2a, 0x86, 0x48, 0x82, 0xf7, 0x12, 0x01, 0x02, 0x02 Microsoft Kerberos OID
0xA2, 0x81, 0x88 Seq. Element 2, responseToken, length 0x88
0x04, 0x81, 0x85 OCTET STRING length 0x85
OCTET STRING data.

The supportedMech, if specified, must be one of the types in NegTokenInit's MechTypeList.

SPNEGO Token Handshake by Using HTTP Headers

To authenticate between Internet Explorer on Windows 2000 and later, and a UNIX web server using GSS-API and Kerberos, the two SPNEGO tokens, NegTokenInit and NegTokenTarg are used to perform a SPNEGO mechanism handshake for Kerberos V5. The actual tokens are passed back and forth by using HTTP headers and this is done as follows:

  1. Client web browser does HTTP Get for resource.
  2. Web server returns HTTP 401 (Unauthorized) status and the following header: "WWW-Authenticate: Negotiate".
  3. Client calls InitializeSecurityContext() and generates a NegTokenInit, does a base64 encoding of it, and resends the Get with the following header: "Authorization: Negotiate <base64 encoding>" (for example, Authorization: Negotiate YIIGUQY<remainder of base64 encoded string>).
  4. Server decodes the NegTokenInit, extracts the supported MechTypes (the one at the front of the MechTypeList should be either Kerberos Legacy or Kerberos V5), ensures it is one of the expected ones, and then extracts the MechToken and authenticates using gss_accept_security_context.
  5. If gss_accept_security_context returns GSS_S_CONTINUE_NEEDED, the web server should return HTTP 401 (Unauthorized) status, and the response token as "WWW-Authenticate: Negotiate <base64 encoding>" (for example, WWW-Authenticate: Negotiate oYIBLj<remainder of base64 encoded string>).
  6. If gss_accept_security_context returns GSS_S_COMPLETE, the web server should return HTTP 200 (Success) status, the final buffer from gss_accept_security_context() in the WWW-Authenticate header as WWW-Authenticate: Negotiate oYIBLj<remainder of base64 encoded string> and the requested web page.
  7. In the client case, if #5 above occurs, it should extract the responseToken from NegTokenTarg, and pass it to the server using the header "Authorization: Negotiate <base64 encoding>". This should continue until a GSS_S_COMPLETE occurs.

Note that for the server side handler to be complete, it should gracefully handle the following conditions:

  1. ContextFlags being passed in the NegTokenInit—server should convert the flags into the proper GSS flags and pass them into gss_accept_sec_context.
  2. NegTokenInit arrives, but Kerberos OID is in MechList and not in first position (meaning the client supports Kerberos, but the initial token, if available, will not be optimized for Kerberos—remember the optimized token corresponds to the first OID in the MechTypeList), or no initial MechToken is available—server should send a response token that indicates that it supports the Kerberos MechType and negResult of accept_incomplete. This will prompt the client to send over a Kerberos token for authentication.

Summary

The binary formats and HTTP Header exchange described here are all based on published standards. This is particularly important for this solution because it requires communication between different computing platforms. As you are no doubt now aware, a fair amount of code is required to parse and create SPNEGO Tokens. However, because we've already written the code, it is the intention of the next article in the series, "SPNEGO Token Handler API", to give you a set of C functions that can be used for this purpose.

Appendix A—References