Sanj Surati & Michael Muckin
Microsoft Consulting Services
December 2002
Applies to:
Windows® 2000
Non-Windows Web servers
Kerberos-capable clients
Summary: Learn how you can parse and create Simple And Protected Negotiate (SPNEGO) tokens on a non-Windows platform with the set of C functions described in this article. This article is the third and last in a series that describes the infrastructure and code required to implement Kerberos-based authentication in a cross-platform environment. (27 printed pages)
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 Token Handler API
Summary
Appendix A - References
Introduction
Now that you have read the articles on "Network Infrastructure" and "SPNEGO Tokens and the Negotiate Protocol", this article will describe a set of C functions which you can use to parse and create SPNEGO tokens on a non-Windows platform.
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 apps only.
- Microsoft® Active Directory® directory service 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 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, etc., please 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 (e.g. Windows XP, Windows Server 2003).
Although we have supplied you with make files for Microsoft Visual C++® development system, the sample code that is provided with this article makes no platform-specific assumptions and can be compiled into a variety of projects.
SPNEGO Token Handler API
Overview
This section documents a set of function calls that can be used to parse and create SPNEGO tokens. The C source files included with this article implement the functions described here.
The intention of the API set is to provide platform-independent functionality for parsing and creating binary tokens for the Simple And Protected Negotiate (SPNEGO) mechanism. The API calls are documented in alphabetical order, with a parameter list, description, expected return codes and associated comments. Immediately following the function call documentation is an implementation example.
Note The SPNEGO Token Handler API only allows for creation and parsing of SPNEGO tokens. Additional code is required to interact with GSS-API calls, perform the HTTP Header exchanges and encode/decode the actual the SPNEGO tokens using the base64 algorithm. Finally, once the GSS-API handshake is complete, gss_accept_sec_context() returns a name parameter which can be passed to gss_display_name() to determine the client's user-id and ensure access to resources.
Source Files
The source files are broken out as follows:
- spnego.h—Contains prototypes, definitions and enumerations for the SPNEGO Token Handler API. Users of the API must include this file in their projects.
- Spnegoparse.h—Contains definitions and helper routines for handling SPNEGO encodings.
- derparse.h—Contains definitions and helper routines for parsing DER encodings as well as SPNEGO specific definitions.
- spnegodata.h—Contains sample data for testing the API.
- spnego.c—Contains implementations of SPNEGO Token Handler API functions.
- Spnegoparse.c—Contains function implementations for handling SPNEGO BLOBs via ASN.1 DER.
- Derparse.c—Contains function implementations for handling ASN.1 DER.
- main.c—Contains calls to exercise the SPNEGO Token Handler API functions using the sample data from spnegodata.h.
The files are fully self-contained and do not use any calls outside of Standard C Library routines. They have all been compiled and linked using MSVC++ v6.0 SP5 on Windows 2000 (W2KSP2), as well as GCC v2.96 compiler on Redhat Linux v7.2.
When building the files, there is code that handles lengths differently based on byte-ordering. The default is Big-Endian; however, if you wish to compile the code for Little-Endian, you must define __LITTLE_ENDIAN__ (e.g. –D__LITTLE_ENDIAN__).
Parsing SPNEGO Tokens
The SPNEGO Token Handler API function spnegoInitFromBinary() can be passed a BLOB filled with SPNEGO data which it will copy and parse into a SPNEGO_TOKEN_HANDLE that can be passed to helper functions in order to extract the actual data. Without going into heavy detail, the primary internal functions used to parse SPNEGO binary data are the following:
BLOB handlers
- AllocEmptySpnegoToken()
- FreeSpnegoToken(),InitSpnegoTokenElementArray)
- InitSpnegoTokenType()
- InitSpnegoTokenElements()
- GetSpnegoInitTokenMechList()
- InitSpnegoTokenElementFromBasicType()
- InitSpnegoTokenElementFromOID()
- FindMechOIDInMechList()
- ValidateMechList()
- IsValidMechOid()
- IsValidContextFlags()
- IsValidNegResult()
- IsValidSpnegoToken()
- IsValidSpnegoElement()
- CalculateElementArrayIndex()
ASN DER readers
- ASNDerGetLength()
- ASNDerCheckToken()
- ASNDerCheckOID()
The BLOB handlers use the ASN DER readers to extract and verify header tokens. Actual token elements (the ones described in the SPNEGO NegTokenInit and NegTokenTarg Sequences) are saved off in an element array in the SpnegoToken data structure by storing their type, the element name, the length and a pointer into an internally allocated BLOB to the actual data value.
The tokens are parsed per the details in the section entitled SPNEGO Token Layout.
Building SPNEGO Tokens
The SPNEGO Token Handler API functions spnegoBuildNegTokenInit() and spnegoBuildNegTokenTarg() can be passed data for building SPNEGO NegTokenInit and NegTokenTarg tokens allocate and fill out an internal BLOB which will then be used to initialize a SPNEGO_TOKEN_HANDLE. Following are the primary internal functions used by the API functions to generate the BLOBs:
BLOB handlers
- CalculateMinSpnegoInitTokenSize()
- CreateSpnegoInitToken()
- CalculateMinSpnegoTargTokenSize()
- CreateSpnegoTargToken()
ASN DER writers
- ASNDerCalcNumLengthBytes()
- ASNDerCalcTokenLength()
- ASNDerCalcElementLength()
- ASNDerCalcMechListLength()
- ASNDerWriteLength()
- ASNDerWriteToken()
- ASNDerWriteOID()
- ASNDerWriteMechList()
- ASNDerWriteElement()
The basic algorithm used here requires us to calculate the size of BLOB first, allocate the data, then fill it out. Things get a bit tricky when we write out tokens that specify the length of the following data. When this happens, we need to know the length of the following data before we actually write out the length, since the number of bytes used to describe the length is dictated by the actual value being written. Because each element of the token may contain a variable length, tokens at the front need to reflect lengths of the elements with the bytes needed to write the lengths. By calculating token length and then writing the tokens out in reverse, we will always know how many bytes we've written so that everything remains accurate.
Once the tokens have been created (again refer to SPNEGO Token Layout to see exactly how the tokens are laid out), in the allocated buffer, we use InitSpnegoTokenFromBinary() to initialize the final SPNEGO_TOKEN_HANDLE.
spnegoCreateNegTokenInit
Prototype
int spnegoCreateNegTokenInit(
[in] SPNEGO_MECH_OID MechType,
[in] unsigned char ucContextFlags,
[in] unsigned char* pbMechToken,
[in] unsigned long ulMechTokenLen,
[in] unsigned char* pbMechListMIC,
[in] unsigned long ulMechListMICLen,
[out] SPNEGO_TOKEN_HANDLE* phSpnegoToken
)
Parameters
Table 1. spnegoCreateNegTokenInit Parameters
| Parameter | Description | Comments |
| MechType | Indicates the mechtype (per the SPNEGO_MECH_OID enumeration) to specify in the MechTypeList. This is a required parameter. | At this time, the only values that are supported are:
- spnego_mech_oid_Kerberos_V5_Legacy
- spnego_mech_oid_Kerberos_V5
|
| ucContextFlags | Context Flags bit field parameter. This is an optional value and may be set to 0. If non-zero, bits can be any combination as specified in the appropriate definitions. | A value of 0 indicates that no Context Flags should be specified in the token. |
| pbMechToken | Pointer to binary data containing the GSS token corresponding to the MechType parameter. This is an optional parameter and may be set to NULL. | This token is established by calling gss_init_sec_context() (GSS-API) or InitializeSecurityContext() (SSPI). |
| ulMechTokenLen | Length of the binary data pbMechToken points to. If pbMechToken is non-NULL, this value must be non-zero. | |
| pbMechListMIC | Pointer to binary data containing Message Integrity Check data. This is an optional parameter and may be set to NULL. | This is established by calling gss_getMIC() (GSS-API) or MakeSignature() (SSPI) on the MechTypes list. |
| ulMechListMICLen | Length of the binary data pbMechListMIC points to. If pbMechListMIC is non-NULL, this value must be non-zero. | |
| phSpnegoToken | Returns a SPNEGO_TOKEN_HANDLE allocated by the function call. This is a required parameter and must be non-NULL. | The resulting SPNEGO_TOKEN_HANDLE must be freed by calling spnegoFreeData(). |
Description
This function will create a SPNEGO negTokenInit token using the supplied parameters and initialize a SPNEGO_TOKEN data structure with the data. The caller may call spnegoTokenGetBinary() to access the underlying binary data—a GSS token corresponding to the format described in RFC 2478. When the caller is done with the token, spnegoFreeData() must be called in order to free associated resources.
Return values
Table 2. spnegoCreateNegTokenInit Return Values
| Return code | Comments |
| SPNEGO_E_SUCCESS | The operation completed successfully. |
| SPNEGO_E_INVALID_PARAMETER | One of the supplied parameters was invalid. |
| SPNEGO_E_OUT_OF_MEMORY | An internal memory allocation failed. |
spnegoCreateNegTokenTarg
Prototype
int spnegoCreateNegTokenTarg(
[in] SPNEGO_MECH_OID MechType,
[in] SPNEGO_NEGRESULT spnegoNegResult,
[in] unsigned char* pbMechToken,
[in] unsigned long ulMechTokenLen,
[in] unsigned char* pbMechListMIC,
[in] unsigned long ulMechListMICLen,
[out] SPNEGO_TOKEN_HANDLE* phSpnegoToken
)
Parameters
Table 3. spnegoCreateNegTokenTarg Parameters
| Parameter | Description | Comments |
| MechType | Indicates the supported MechType. This is an optional parameter and may be set to spnego_mech_oid_NotUsed. | At this time, the only supported values are:
- spnego_mech_oid_Kerberos_V5_Legacy
- spnego_mech_oid_Kerberos_V5
- spnego_mech_oid_NotUsed
Note spnego_mech_oid_NotUsed cannot be used if spnegoNegResult is set to spnego_negresult_success or spnego_negresult_incomplete |
| spnegoNegResult | NegResult Value. This is an optional parameter and may be set to spnego_negresult_NotUsed. | The supported values are as follows:
- spnego_negresult_success
- spnego_negresult_incomplete
- spnego_negresult_rejected
- spnego_negresult_NotUsed
|
| pbMechToken | Pointer to binary data containing a GSS token corresponding to the MechType parameter. This is an optional parameter and may be set to NULL. | This token is established by calling gss_init_sec_context() (GSS-API) or InitializeSecurityContext() (SSPI) for an Init type token, or gss_accept_sec_context() (GSS-API) or AcceptSecurityContext() (SSPI) for a response token. |
| ulMechTokenLen | Length of the binary data pbMechToken points to. If pbMechToken is non-NULL, this value must be non-zero. | |
| pbMechListMIC | Pointer to binary data containing Message Integrity Check data. This is an optional parameter and may be set to NULL. | This is established by calling gss_getMIC() (GSS-API) or MakeSignature() (SSPI) on the MechTypes list (from the negTokenInit). |
| ulMechListMICLen | Length of the binary data pbMechListMIC points to. If pbMechListMIC is non-NULL, this value must be non-zero. | |
| phSpnegoToken | Returns a SPNEGO_TOKEN_HANDLE allocated by the function call. This is a required parameter and must be non-NULL. | The resulting SPNEGO_TOKEN_HANDLE must be freed by calling spnegoFreeData(). |
Description
This function will create a SPNEGO negTokenTarg token using the supplied parameters and initialize a SPNEGO_TOKEN data structure with the data. The caller may call spnegoTokenGetBinary() to access the underlying binary data—a GSS token corresponding to the format described in RFC 2478. When the caller is done with the token, spnegoFreeData() must be called in order to free associated resources.
Return values
Table 4. spnegoCreateNegTokenTarg Return Values
| Return code | Comments |
| SPNEGO_E_SUCCESS | The operation completed successfully. |
| SPNEGO_E_INVALID_PARAMETER | One of the supplied parameters was invalid. |
| SPNEGO_E_OUT_OF_MEMORY | An internal memory allocation failed. |
spnegoFreeData
Prototype
int spnegoFreeData(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken
)
Parameters
Table 5. spnegoFreeData Parameters
| Parameter | Description | Comments |
| hSpnegoToken | A properly allocated SPNEGO_TOKEN_HANDLE. This parameter is required. | The handle must have been allocated by one of the following calls:
- spnegoCreateNegTokenInit()
- spnegoCreateNegTokenTarg()
- spnegoInitFromBinary()
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE initialized and/or allocated by other means. |
Description
This function will release all resources associated with the supplied token handle.
Return values
Not applicable. Return type is void.
spnegoGetContextFlags
Prototype
int spnegoGetContextFlags(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] unsigned char* pucContextFlags
)
Parameters
Table 6. spnegoGetContextFlags Parameters
| Parameter | Description | Comments |
| hSpnegoToken | A properly allocated SPNEGO_TOKEN_HANDLE. This parameter is required. | The handle must have been allocated by one of the following calls:
- spnegoCreateNegTokenInit()
- spnegoInitFromBinary()
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE initialized and/or allocated by other means. |
| pucContextFlags | Pointer to location to store the ContextFlags value retrieved from the token. This parameter is required. | |
Description
This function will copy the ContextFlags value from a SPNEGO token of type negTokenInit into pucContextFlags. For the function to succeed, the value must exist in the token and must have the proper format and values.
Supported flags for ContextFlags are are any combination of the following:
| SPNEGO_NEGINIT_CONTEXT_DELEG_FLAG | 0x80 |
| SPNEGO_NEGINIT_CONTEXT_MUTUAL_FLAG | 0x40 |
| SPNEGO_NEGINIT_CONTEXT_REPLAY_FLAG | 0x20 |
| SPNEGO_NEGINIT_CONTEXT_SEQUENCE_FLAG | 0x10 |
| SPNEGO_NEGINIT_CONTEXT_ANON_FLAG | 0x8 |
| SPNEGO_NEGINIT_CONTEXT_CONF_FLAG | 0x4 |
| SPNEGO_NEGINIT_CONTEXT_INTEG_FLAG | 0x2 |
These actual flag values must again be translated into appropriate values for calls to gss_accept_security_context().
Return values
Table 7. spnegoGetContextFlags Return Values
| Return code | Comments |
| SPNEGO_E_SUCCESS | The operation completed successfully. |
| SPNEGO_E_INVALID_PARAMETER | One of the supplied parameters was invalid. |
| SPNEGO_E_ELEMENT_UNAVAILABLE | ContextFlags element is not available. |
| SPNEGO_E_INVALID_ELEMENT | ContextFlags element is available but not valid. |
spnegoGetMechListMIC
Prototype
int spnegoGetMechListMIC(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] unsigned char* pbMICData,
[in,out] unsigned long* pulDataLen,
)
Parameters
Table 8. spnegoGetMechListMIC Parameters
| Parameter | Description | Comments |
| hSpnegoToken | A properly allocated SPNEGO_TOKEN_HANDLE. This parameter is required. | The handle must have been allocated by one of the following calls:
- spnegoCreateNegTokenInit()
- spnegoCreateNegTokenTarg()
- spnegoInitFromBinary()
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE initialized and/or allocated by other means. |
| pbMICData | Pointer to copy binary MIC data into. This is an optional parameter and can be NULL. | |
| pulDataLen | Pointer to value containing length of pbMICData. This parameter is required. | If pbMICData is non-NULL, pulDataLen must be set to the length of the data buffer. Upon function return, the value will be set equal to the number of bytes copied into the buffer, or in the case of a buffer too small error, the buffer size required to hold the data. |
Description
This function is used to retrieve the Message Integrity Check (MIC) data from a SPNEGO token of type negTokenInit or negTokenTarg. The token is copied from within the underlying binary SPNEGO data into the pbMICData buffer. If pbMICData is NULL or the value in pulDataLen indicates a buffer size too small to hold the MIC data, the function will return SPNEGO_E_BUFFER_TOO_SMALL and set pulDataLen equal to the size required to hold the MIC data.
Return values
Table 9. spnegoGetMechListMIC Return Values
| Return code | Comments |
| SPNEGO_E_SUCCESS | The operation completed successfully. |
| SPNEGO_E_INVALID_PARAMETER | One of the supplied parameters was invalid. |
| SPNEGO_E_ELEMENT_UNAVAILABLE | MIC Data element is not available. |
| SPNEGO_E_INVALID_ELEMENT | MIC Data element is available but not valid. |
| SPNEGO_E_BUFFER_TOO_SMALL | The supplied buffer is too small to contain the MIC Data. pulDataLen will be set equal to the required value. |
spnegoGetMechToken
Prototype
int spnegoGetMechToken(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] unsigned char* pbTokenData,
[in,out] unsigned long* pulDataLen,
)
Parameter
Table 10. spnegoGetMechToken Parameters
| Parameter | Description | Comments |
| hSpnegoToken | A properly allocated SPNEGO_TOKEN_HANDLE. This parameter is required. | The handle must have been allocated by one of the following calls:
- spnegoCreateNegTokenInit()
- spnegoCreateNegTokenTarg()
- spnegoInitFromBinary()
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE initialized and/or allocated by other means. |
| pbTokenData | Pointer to copy binary Mech token data into. This is an optional parameter and can be NULL. | |
| pulDataLen | Pointer to value containing length of pbTokenData. This parameter is required. | If pbTokenData is non-NULL, pulDataLen must be set to the length of the data buffer. Upon function return, the value will be set equal to the number of bytes copied into the buffer, or in the case of a buffer too small error, the buffer size required to hold the data. |
Description
This function is used to retrieve Mech token data from a SPNEGO token of type negTokenInit or negTokenTarg. The token is copied from within the underlying binary SPNEGO data into the pbTokenData buffer. If pbTokenData is NULL or the value in pulDataLen indicates a buffer size too small to hold the Mech token data, the function will return SPNEGO_E_BUFFER_TOO_SMALL and set pulDataLen equal to the size required to hold the Mech .
In a negTokenInit token, the Mech corresponds to the MechToken element. In a negTokenTarg, the Mech corresponds to the responseToken element.
Return values
Table 11. spnegoGetMechToken Return Values
| Return code | Comments |
| SPNEGO_E_SUCCESS | The operation completed successfully. |
| SPNEGO_E_INVALID_PARAMETER | One of the supplied parameters was invalid. |
| SPNEGO_E_ELEMENT_UNAVAILABLE | Mech element is not available. |
| SPNEGO_E_INVALID_ELEMENT | Mech element is available but not valid. |
| SPNEGO_E_BUFFER_TOO_SMALL | The supplied buffer is too small to contain the Mech. pulDataLen will be set equal to the required value. |
spnegoGetNegotiationResult
Prototype
int spnegoGetNegotiattionResult(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] SPNEGO_NEG_RESULT* pnegResult
)
Parameters
Table 12. spnegoGetNegotiationResult Parameters
| Parameter | Description | Comments |
| hSpnegoToken | A properly allocated SPNEGO_TOKEN_HANDLE. This parameter is required. | The handle must have been allocated by one of the following calls:
- spnegoCreateNegTokenTarg()
- spnegoInitFromBinary()
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE initialized and/or allocated by other means. |
| pnegResult | Pointer to location to store the negResult value retrieved from the token. This parameter is required. | |
Description
This function will copy the negResult value from a SPNEGO token of type negTokenTarg into pnegTarg. For the function to succeed, the value must exist in the token and must have the proper format and values.
Supported values for negResult are as follows:
typedef enum spnego_negResult
{
spnego_negresult_success,
spnego_negresult_incomplete,
spnego_negresult_rejected
}
Return values
Table 13. spnegoGetNegotiationResult Return Values
| Return code | Comments |
| SPNEGO_E_SUCCESS | The operation completed successfully. |
| SPNEGO_E_INVALID_PARAMETER | One of the supplied parameters was invalid. |
| SPNEGO_E_ELEMENT_UNAVAILABLE | negResult element is not available. |
| SPNEGO_E_INVALID_ELEMENT | negResult element is available but not valid. |
spnegoGetSupportedMechType
Prototype
int spnegoGetSupportedMechType(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] SPNEGO_MECH_OID* pMechOID
)
Parameters
Table 14. spnegoGetSupportedMechType Parameters
| Parameter | Description | Comments |
| hSpnegoToken | A properly allocated SPNEGO_TOKEN_HANDLE. This parameter is required. | The handle must have been allocated by one of the following calls:
- spnegoCreateNegTokenTarg()
- spnegoInitFromBinary()
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE initialized and/or allocated by other means. |
| pMechOID | Pointer to location to store the supported mechtype. | |
Description
This function will check the supportedMech element's OID in a negTokenTarg against the known OIDs (spnego_mech_oid_Microsoft_Kerberos, spnego_mech_oid_Kerberos_V5) and if we get a match, sets pMechOID equal to proper value. For the function to succeed, the value must exist in the token and must have the proper format and values.
Return values
Table 15. spnegoGetSupportedMechType Return Values
| Return code | Comments |
| SPNEGO_E_SUCCESS | The operation completed successfully. |
| SPNEGO_E_INVALID_PARAMETER | One of the supplied parameters was invalid. |
| SPNEGO_E_ELEMENT_UNAVAILABLE | supportedMech element is not available. |
| SPNEGO_E_INVALID_ELEMENT | supportedMech element is available but not valid. |
spnegoGetTokenType
Prototype
int spnegoGetTokenType(
[in] SPNEGO_TOKEN_HANDLE pSpnegoToken,
[out] int* piTokenType
)
Parameters
Table 16. spnegoGetTokenType Parameters
| Parameter | Description | Comments |
| hSpnegoToken | A properly allocated SPNEGO_TOKEN_HANDLE. This parameter is required. | The handle must have been allocated by one of the following calls:
- spnegoCreateNegTokenInit()
- spnegoCreateNegTokenTarg()
- spnegoInitFromBinary()
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE initialized and/or allocated by other means. |
| piTokenType | Pointer to location to store the token type. | |
Description
This function will check the underlying data for hSpnegoToken and return the token type in piTokenType. Possible values for piTokenType are:
SPNEGO_TOKEN_INIT 0
SPNEGO_TOKEN_TARG 1
Return values
Table 17. spnegoGetTokenType Return Values
| Return code | Comments |
| SPNEGO_E_SUCCESS | The operation completed successfully. |
| SPNEGO_E_INVALID_PARAMETER | One of the supplied parameters was invalid. |
spnegoInitFromBinary
Prototype
int spnegoInitFromBinary(
[in] unsigned char* pbTokenData,
[in] unsigned long ulLength,
[out] SPNEGO_TOKEN_HANDLE* phSpnegoToken
)
Parameters
Table 18. spnegoInitFromBinary Parameters
| Parameter | Description | Comments |
| pbTokenData | Pointer to a valid binary SPNEGO token This is a required parameter. | The token can be of type negTokenInit and negTokenTarg. |
| ulLength | Length of pbTokenData. This is a required parameter. | |
| phSpnegoToken | Returns a SPNEGO_TOKEN_HANDLE allocated by the function call. This is a required parameter and must be non-NULL. | The resulting SPNEGO_TOKEN_HANDLE must be freed by calling spnegoFreeData(). |
Description
This function will copy the supplied binary data and initialize a SPNEGO_TOKEN_HANDLE on the copy. The token must be a valid negTokenInit or negTokenTarg, as some parsing of the token will take place. The caller may call spnegoTokenGetBinary() or spnegoHiPerfGetRawTokenPtr() to access the underlying binary data—a GSS corresponding to the format described in RFC 2478. When the caller is done with the token, spnegoFreeData() must be called in order to free associated resources.
Return values
Table 19. spnegoInitFromBinary Return Values
| Return code | Comments |
| SPNEGO_E_SUCCESS | The operation completed successfully. |
| SPNEGO_E_INVALID_PARAMETER | One of the supplied parameters was invalid. |
| SPNEGO_E_OUT_OF_MEMORY | An internal memory allocation failed. |
| SPNEGO_E_INVALID_TOKEN | The supplied token is invalid. |
| SPNEGO_E_INVALID_LENGTH | An invalid length was encountered while parsing the token. |
| SPNEGO_E_UNEXPECTED_OID | The token contains an unexpected OID (e.g. the initial OID is not the SPNEGO OID). |
| SPNEGO_E_TOKEN_NOT_FOUND | An expected token inside the buffer could not be found (e.g. the tokenizer failed to parse an expected value). |
| SPNEGO_E_UNEXPECTED_TYPE | An unexpected type was encountered (e.g. an OID was found where an OCTET STRING was expected). |
spnegoIsMechTypeAvailable
Prototype
int spnegoIsMechTypeAvailable(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[in] MechOID MechOID,
[out] int* piMechTypeIndex
)
Parameters
Table 20. spnegoIsMechTypeAvailable Parameters
| Parameter | Description | Comments |
| hSpnegoToken | A properly allocated SPNEGO_TOKEN_HANDLE. This parameter is required. | The handle must have been allocated by one of the following calls:
- spnegoCreateNegTokenInit()
- spnegoInitFromBinary()
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE initialized and/or allocated by other means. |
| MechOID | Mech Type to search the list for. | At this time, the only supported mech types are:
- spnego_mech_oid_Microsoft_Kerberos
- spnego_mech_oid_Kerberos_V5
|
| piMechTypeIndex | Pointer to location to store the index of the MechType in the list. | |
Description
This function will search the negTokenInit's MechTypeList element for the specified Mech OID. If it is found, the zero-based index in the list is returned in piMechTypeIndex.
Return values
Table 21. spnegoIsMechTypeAvailable Return Values
| Return code | Comments |
| SPNEGO_E_SUCCESS | The operation completed successfully. |
| SPNEGO_E_INVALID_PARAMETER | One of the supplied parameters was invalid. |
| SPNEGO_E_NOT_FOUND | The requested Mech Type could not be found. |
| SPNEGO_E_ELEMENT_UNAVAILABLE | MechTypeList element is not available. |
| SPNEGO_E_INVALID_ELEMENT | MechTypeList element is available but not valid. |
spnegoTokenGetBinary
Prototype
int spnegoTokenGetBinary(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] unsigned char* pbTokenData,
[in,out] unsigned long* pulDataLen
)
Parameters
Table 22. spnegoTokenGetBinary Parameters
| Parameter | Description | Comments |
| hSpnegoToken | A properly allocated SPNEGO_TOKEN_HANDLE. This parameter is required. | The handle must have been allocated by one of the following calls:
- spnegoCreateNegTokenInit()
- spnegoCreateNegTokenTarg()
- spnegoInitFromBinary()
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE initialized and/or allocated by other means. |
| pbTokenData | Pointer to copy binary SPNEGO token data into. This is an optional parameter and can be NULL. | |
| pulDataLen | Pointer to value containing length of pbTokenData. This parameter is required. | If pbTokenData is non-NULL, pulDataLen must be set to the length of the data buffer. Upon function return, the value will be set equal to the number of bytes copied into the buffer, or in the case of a buffer too small error, the buffer size required to hold the data. |
Description
This function is used to retrieve a binary copy of the SPNEGO token data from a SPNEGO token of type negTokenInit or negTokenTarg. The token is copied from within the underlying binary SPNEGO data into the pbTokenData buffer. If pbTokenData is NULL or the value in pulDataLen indicates a buffer size too small to hold the MIC data, the function will return SPNEGO_E_BUFFER_TOO_SMALL and set pulDataLen equal to the size required to hold the SPNEGO token.
Return values
Table 23. spnegoTokenGetBinary Return Values
| Return code | Comments |
| SPNEGO_E_SUCCESS | The operation completed successfully. |
| SPNEGO_E_INVALID_PARAMETER | One of the supplied parameters was invalid. |
| SPNEGO_E_BUFFER_TOO_SMALL | The supplied buffer is too small to contain the Mech token. pulDataLen will be set equal to the required value. |
Implementation Example
The following example code shows how the SPNEGO Token Handler API could be used to parse a SPNEGO token, extract the Mech token and generate a response token to send back.
Note This example is intended only to be used as a reference for implementation; it is not complete and represents only one way in which the API may be used.
int HandleSpnegoToken( unsigned char* pbToken, unsigned long,
ulTokenSize, unsigned char** ppbResponseToken,
unsigned long* pulRespTokenLen,
int* pnComplete )
{
int nReturn = -1;
int nError = 0L;
SPNEGO_TOKEN_HANDLE hSpnegoToken = NULL;
SPNEGO_TOKEN_HANDLE hSpnegoResponseToken = NULL;
SPNEGO_MECH_OID spnegoMechOID = spnego_mech_oid_NotUsed;
unsigned char* pbMechToken = NULL;
unsigned long ulMechTokenLen = 0L;
unsigned char* pbRespToken = NULL;
unsigned long ulRespTokenLen = 0L;
int nTokenType = 0L;
int nOIDIndex = 0L;
int nGetMechToken = 1L;
//
// Default to incomplete in case we hit a condition in which we
// have a supported mechtype, but no MechToken
//
SPNEGO_NEGRESULT spnegoNegResult = spnego_negresult_incomplete;
// First parse the token
if ( spnegoInitFromBinary( pbToken, ulTokenSize, &hSpnegoToken )
!= SPNEGO_E_SUCCESS )
{
goto xCleanup;
}
// Check the type. If NegTokenInit, see if Microsoft Keberos is in
// the lead position
if ( spnegoGetTokenType( hSpnegoToken, &nTokenType )
!= SPNEGO_E_SUCCESS )
{
goto xCleanup;
}
if ( SPNEGO_TOKEN_INIT == nTokenType )
{
//
// IF the legacy mechtype is not available, check for the
// standard Kerberos V5 value. If we don't find that, send
// back a rejected response
//
if ( spnegoIsMechTypeAvailable( hSpnegoToken,
spnego_mech_oid_Kerberos_V5_Legacy, &nOIDIndex )
!= SPNEGO_E_SUCCESS )
{
// If we find Kerberos_V5, check if it's in the lead position
// if not, we can't really do anything with the MechToken.
if ( spnegoIsMechTypeAvailable( hSpnegoToken,
spnego_mech_oid_Kerberos_V5, &nOIDIndex )
!= SPNEGO_E_SUCCESS )
{
spnegoNegResult = spnego_negresult_rejected;
}
else if ( 0 != nOIDIndex )
{
// Store the found value
spnegoMechOID = spnego_mech_oid_Kerberos_V5;
nGetMechToken = 0L;
}
}
else
{
// Store the found value
spnegoMechOID = spnego_mech_oid_Kerberos_V5_Legacy;
if ( 0 != nOIDIndex )
{
// If the OID is not in the first position, there's no
// point to trying to retrieve the mechToken, since if it's
// there it will only correspond to the OID in the first
// position.
nGetMechToken = 0L;
}
}
}
// Only retrieve the MechToken if appropriate
if ( nGetMechToken )
{
// Retrieve the MechToken - Token Unavailable is okay if
// nTokenType is an InitToken, otherwise, we expect a
// buffer too small error
nError = spnegoGetMechToken( hSpnegoToken, NULL,
&ulMechTokenLen );
if ( SPNEGO_E_BUFFER_TOO_SMALL == nError )
{
// Allocate a properly sized buffer and retry.
pbMechToken = malloc( ulMechTokenLen );
if ( NULL == pbMechToken )
{
goto xCleanup;
}
if ( spnegoGetMechToken( hSpnegoToken, pbMechToken,
&ulMechTokenLen )
!= SPNEGO_E_SUCCESS )
{
goto xCleanup;
}
}
else if ( SPNEGO_TOKEN_TARG == nTokenType ||
SPNEGO_E_ELEMENT_UNAVAILABLE != nError )
{
goto xCleanup;
}
}
// Only need to pass to GSS if we have a MechToken
if ( NULL != pbMechToken )
{
//
// Make the call to gss_accept_sec_context() here. Note that
// this should record if a Complete status is returned in
// pnComplete and allocate a response token if one is generated
// and spnegoNegResult should be set properly
//
}
// Create the Token and then extract the binary
if ( spnegoCreateNegTokenTarg( spnegoMechOID,
spnegoNegResult, pbRespToken, ulRespTokenLen, NULL,
0L, &hSpnegoResponseToken )
!= SPNEGO_E_SUCCESS )
{
goto xCleanup;
}
// Expect a buffer too small error here.
if ( spnegoTokenGetBinary( hSpnegoResponseToken, NULL,
pulRespTokenLen )
== SPNEGO_E_BUFFER_TOO_SMALL )
{
// Now allocate and extract the buffer
*ppbResponseToken = malloc( *pulRespTokenLen );
if ( NULL == *ppbResponseToken )
{
goto xCleanup;
}
nError = spnegoTokenGetBinary( hSpnegoResponseToken,
ppbResponseToken,
pulRespTokenLen );
if ( SPNEGO_E_SUCCESS == nError )
{
// We're done!
nReturn = 0L;
}
else
{
free( *ppbResponseToken );
*ppbResponseToken = NULL;
}
}
xCleanup:
// Cleanup allocated token info
spnegoFreeData( hSpnegoToken );
spnegoFreeData( hSpnegoResponseToken );
// Cleanup non-NULL allocated memory
if ( NULL != pbRespToken )
{
free( pbRespToken );
}
if ( NULL != pbMechToken )
{
free( pbMechToken );
}
return nReturn;
}
Summary
The API set and sample code described in this article only represent one way to approach the problem of parsing and creating SPNEGO Tokens. For example, at the time of the writing of this article, as far as we are aware there is no existing GSS-API mechanism for SPNEGO Tokens. Such a mechanism would obviate the need for the code provided in this article. However, until such a thing is written, the provided API set should get you well on your way!
Appendix A—References