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:
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:
- 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.)
- 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:
The Security Mechanisms supported by the client
Flags passed to gss_init_sec_context() that the server must pass to gss_accept_sec_context()
An initial MechToken—this is the initial GSS Token that is retrieved from GSSAPI for the initial MechType in the MechTypeList.
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:
a Negotiation Result enumeration
a single, supported MechType
a Response MechToken generated by GSSAPI calls
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:
- Client web browser does HTTP Get for resource.
- Web server returns HTTP 401 (Unauthorized) status and the following header: "WWW-Authenticate: Negotiate".
- 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>).
- 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.
- 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>).
- 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.
- 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:
- ContextFlags being passed in the NegTokenInit—server should convert the flags into the proper GSS flags and pass them into gss_accept_sec_context.
- 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
- RFC 1510 "The Kerberos Network Authentication Service (V5)"
- RFC 1508 "Generic Security Service Application Program Interface"
- RFC 1964 "The Kerberos Version 5 GSS-API Mechanism"
- RFC 2078 "Generic Security Service Application Program Interface, Version 2"
- RFC 2478 "Simple and Protected GSS-API Negotiation Mechanism"
- RFC 4559 "SPNEGO-based Kerberos and NTLM HTTP Authentication in Microsoft Windows"
- MIT Kerberos Web Site
- Microsoft Kerberos links:
- Windows Server, search for "Basic Kerberos."
- Step-by-Step Guide to Kerberos 5 (krb5 1.0) Interoperability
- How a Service Composes its SPNs