Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
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.
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
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 that you can use to parse and create SPNEGO tokens on a non-Windows platform.
This solution assumes the following to be true:
- The use of this cross-platform authentication solution is intended for intranet applications only.
- The 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, 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).
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.
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 by using the base64 algorithm. Finally, after the GSS-API handshake is complete, gss_accept_sec_context() returns a name parameter that can be passed to gss_display_name() to determine the client's user-id and ensure access to resources.
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 by using 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 by 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 by using MSVC++ v6.0 SP5 on Windows 2000 with Service Pack 2, 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 want to compile the code for Little-Endian, you must define __LITTLE_ENDIAN__ (for example, –D__LITTLE_ENDIAN__).
The SPNEGO Token Handler API function spnegoInitFromBinary() can be passed a BLOB that is filled with SPNEGO data that it will copy and parse into a SPNEGO_TOKEN_HANDLE that can be passed to helper functions to extract the actual data. Without going into a lot of detail, the primary internal functions used to parse SPNEGO binary data are the following:
- AllocEmptySpnegoToken()
- FreeSpnegoToken(),InitSpnegoTokenElementArray)
- InitSpnegoTokenType()
- InitSpnegoTokenElements()
- GetSpnegoInitTokenMechList()
- InitSpnegoTokenElementFromBasicType()
- InitSpnegoTokenElementFromOID()
- FindMechOIDInMechList()
- ValidateMechList()
- IsValidMechOid()
- IsValidContextFlags()
- IsValidNegResult()
- IsValidSpnegoToken()
- IsValidSpnegoElement()
- CalculateElementArrayIndex()
- 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 to an internally allocated BLOB that contains the actual data value.
The tokens are parsed per the details in the section entitled SPNEGO Token Layout.
The SPNEGO Token Handler API functions spnegoBuildNegTokenInit() and spnegoBuildNegTokenTarg() can be passed data for building SPNEGO NegTokenInit and NegTokenTarg tokens that allocate and fill out an internal BLOB that is then used to initialize a SPNEGO_TOKEN_HANDLE. Following are the primary internal functions used by the API functions to generate the BLOBs:
- CalculateMinSpnegoInitTokenSize()
- CreateSpnegoInitToken()
- CalculateMinSpnegoTargTokenSize()
- CreateSpnegoTargToken()
- ASNDerCalcNumLengthBytes()
- ASNDerCalcTokenLength()
- ASNDerCalcElementLength()
- ASNDerCalcMechListLength()
- ASNDerWriteLength()
- ASNDerWriteToken()
- ASNDerWriteOID()
- ASNDerWriteMechList()
- ASNDerWriteElement()
The basic algorithm used here requires us to calculate the size of the 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 because the number of bytes used to describe the length is dictated by the actual value being written. Because each element of the token can 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.
After 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.
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
)
Table 1. spnegoCreateNegTokenInit Parameters
Parameter | Description | Comments |
---|---|---|
MechType | A value of the SPNEGO_MECH_OID enumeration that indicates the mechtype to specify in the MechTypeList. This is a required parameter. | At this time, the only values that are supported are:
|
ucContextFlags | Context flags bit field parameter. This is an optional value, and you can set it to zero. If nonzero, bits can be any combination, as specified in the appropriate definitions. | A value of zero indicates that no context flags should be specified in the token. |
pbMechToken | A pointer to binary data that contains the GSS token that corresponds to the MechType parameter. This is an optional parameter, and you can set it to NULL. | This token is established by calling gss_init_sec_context() (GSS-API) or InitializeSecurityContext() (SSPI). |
ulMechTokenLen | The length of the binary data that the pbMechToken parameter points to. If pbMechToken is non-NULL, this value must be nonzero. | |
pbMechListMIC | A pointer to binary data that contains Message Integrity Check data. This is an optional parameter, and you can set it to NULL. | This is established by calling gss_getMIC() (GSS-API) or MakeSignature() (SSPI) on the MechTypes list. |
ulMechListMICLen | The length of the binary data pbMechListMIC points to. If pbMechListMIC is non-NULL, this value must be nonzero. | |
phSpnegoToken | Returns a SPNEGO_TOKEN_HANDLE allocated by the function call. This is a required parameter and must be non-NULL. | When you have finished using the handle, free it by calling the spnegoFreeData() function. |
This function will create a SPNEGO negTokenInit token by using the supplied parameters and initialize a SPNEGO_TOKEN data structure with the data. The caller can call spnegoTokenGetBinary() to access the underlying binary data—a GSS token that corresponds to the format described in RFC 2478. When you have finished using the token, free it by call the spnegoFreeData() function.
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 not valid. |
SPNEGO_E_OUT_OF_MEMORY | An internal memory allocation failed. |
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
)
Table 3. spnegoCreateNegTokenTarg Parameters
Parameter | Description | Comments |
---|---|---|
MechType | Indicates the supported MechType. This is an optional parameter, and you can set it to spnego_mech_oid_NotUsed. | At this time, the only supported values are:
|
spnegoNegResult | The NegResult value. This is an optional parameter, and you can set it to spnego_negresult_NotUsed. | The supported values are as follows:
|
pbMechToken | A pointer to binary data that contains a GSS token that corresponds to the MechType parameter. This is an optional parameter, and you can set it 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 | The length of the binary data the pbMechToken parameter points to. If pbMechToken is non-NULL, this value must be nonzero. | |
pbMechListMIC | A pointer to binary data that contains Message Integrity Check data. This is an optional parameter, and you can set it to NULL. | This is established by calling gss_getMIC() (GSS-API) or MakeSignature() (SSPI) on the MechTypes list (from the negTokenInit). |
ulMechListMICLen | The length of the binary data pbMechListMIC points to. If pbMechListMIC is non-NULL, this value must be nonzero. | |
phSpnegoToken | A SPNEGO_TOKEN_HANDLE allocated by the function call. This is a required parameter and must be non-NULL. | When you have finished using the resulting SPNEGO_TOKEN_HANDLE handle, free it by calling the spnegoFreeData() function. |
This function will create a SPNEGO negTokenTarg token by using the supplied parameters and initialize a SPNEGO_TOKEN data structure with the data. The caller can call the spnegoTokenGetBinary() function to access the underlying binary data—a GSS token that corresponds to the format described in RFC 2478. When you have finished using the token, free it by calling the spnegoFreeData() function.
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 not valid. |
SPNEGO_E_OUT_OF_MEMORY | An internal memory allocation failed. |
int spnegoFreeData(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken
)
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:
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE handle that was initialized or allocated by other means. |
This function will release all resources associated with the supplied token handle.
Not applicable. Return type is void.
int spnegoGetContextFlags(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] unsigned char* pucContextFlags
)
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:
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE handle that was initialized or allocated by other means. |
pucContextFlags | A pointer to a location to store the ContextFlags value retrieved from the token. This parameter is required. |
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 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().
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 not valid. |
SPNEGO_E_ELEMENT_UNAVAILABLE | The ContextFlags element is not available. |
SPNEGO_E_INVALID_ELEMENT | The ContextFlags element is available but not valid. |
int spnegoGetMechListMIC(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] unsigned char* pbMICData,
[in,out] unsigned long* pulDataLen,
)
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:
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE handle that was initialized or allocated by other means. |
pbMICData | A pointer to copy binary MIC data into. This is an optional parameter, and you can set it to NULL. | |
pulDataLen | A pointer to a variable that contains the length of the binary MIC data pointed to by the pbMICData parameter. This parameter is required. | If pbMICData is non-NULL, pulDataLen must be set to the length of the data buffer. After the function returns, 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. |
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 buffer pointed to by the pbMICData parameter. If pbMICData is NULL or the value in pulDataLen indicates a buffer size too small to hold the MIC data, the function returns SPNEGO_E_BUFFER_TOO_SMALL and sets pulDataLen equal to the size required to hold the MIC data.
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 not valid. |
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. The pulDataLen parameter will be set equal to the required value. |
int spnegoGetMechToken(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] unsigned char* pbTokenData,
[in,out] unsigned long* pulDataLen,
)
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:
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE handle that was initialized or allocated by other means. |
pbTokenData | A pointer to a variable to receive the binary Mech token data. This is an optional parameter, and you can set it to NULL. | |
pulDataLen | A pointer to a variable that contains the length of pbTokenData. This parameter is required. | If the pbTokenData parameter is non-NULL, pulDataLen must be set to the length of the data buffer. After the function returns, the value is 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. |
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 buffer pointed to by the pbTokenData parameter. If pbTokenData is NULL or the value in pulDataLen indicates a buffer that is too small to hold the Mech token data, the function returns SPNEGO_E_BUFFER_TOO_SMALL and sets the pulDataLen parameter 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.
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 not valid. |
SPNEGO_E_ELEMENT_UNAVAILABLE | The Mech element is not available. |
SPNEGO_E_INVALID_ELEMENT | The Mech element is available but not valid. |
SPNEGO_E_BUFFER_TOO_SMALL | The supplied buffer is too small to contain the Mech. The pulDataLen parameter will be set equal to the required value. |
int spnegoGetNegotiattionResult(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] SPNEGO_NEG_RESULT* pnegResult
)
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:
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE handle that was initialized or allocated by other means. |
pnegResult | A pointer to an address to store the negResult value retrieved from the token. This parameter is required. |
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
}
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 not valid. |
SPNEGO_E_ELEMENT_UNAVAILABLE | negResult element is not available. |
SPNEGO_E_INVALID_ELEMENT | negResult element is available but not valid. |
int spnegoGetSupportedMechType(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] SPNEGO_MECH_OID* pMechOID
)
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:
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE handle that was initialized or allocated by other means. |
pMechOID | A pointer to an address in which to store the supported mechtype. |
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 the pMechOID parameter equal to proper value. For the function to succeed, the value must exist in the token and must have the proper format and 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 not valid. |
SPNEGO_E_ELEMENT_UNAVAILABLE | The supportedMech element is not available. |
SPNEGO_E_INVALID_ELEMENT | The supportedMech element is available but not valid. |
int spnegoGetTokenType(
[in] SPNEGO_TOKEN_HANDLE pSpnegoToken,
[out] int* piTokenType
)
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:
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE handle that was initialized or allocated by other means. |
piTokenType | A pointer to an address in which to store the token type. |
This function will check the underlying data for the hSpnegoToken parameter and return the token type in the piTokenType parameter. Possible values for piTokenType are:
SPNEGO_TOKEN_INIT 0
SPNEGO_TOKEN_TARG 1
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 not valid. |
int spnegoInitFromBinary(
[in] unsigned char* pbTokenData,
[in] unsigned long ulLength,
[out] SPNEGO_TOKEN_HANDLE* phSpnegoToken
)
Table 18. spnegoInitFromBinary Parameters
Parameter | Description | Comments |
---|---|---|
pbTokenData | A pointer to a valid binary SPNEGO token. This is a required parameter. | The token can be of type negTokenInit or negTokenTarg. |
ulLength | The length of the token pointed to by the pbTokenData parameter. This is a required parameter. | |
phSpnegoToken | Returns a SPNEGO_TOKEN_HANDLE handle that was allocated by the function call. This is a required parameter and must be non-NULL. | When you have finished using the handle, free it by calling the spnegoFreeData() function. |
This function will copy the supplied binary data and initialize a SPNEGO_TOKEN_HANDLE handle on the copy. The token must be a valid negTokenInit or negTokenTarg because some parsing of the token will take place. The caller can call spnegoTokenGetBinary() or spnegoHiPerfGetRawTokenPtr() to access the underlying binary data, which must be a GSS that corresponds to the format described in RFC 2478. When you have finished using the token, free it by calling the spnegoFreeData() function.
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 not valid. |
SPNEGO_E_OUT_OF_MEMORY | An internal memory allocation failed. |
SPNEGO_E_INVALID_TOKEN | The supplied token is not valid. |
SPNEGO_E_INVALID_LENGTH | A length that is not valid was encountered while parsing the token. |
SPNEGO_E_UNEXPECTED_OID | The token contains an unexpected OID (for example, the initial OID is not the SPNEGO OID). |
SPNEGO_E_TOKEN_NOT_FOUND | An expected token inside the buffer could not be found (for example, the tokenizer failed to parse an expected value). |
SPNEGO_E_UNEXPECTED_TYPE | An unexpected type was encountered (for example, an OID was found where an OCTET STRING was expected). |
int spnegoIsMechTypeAvailable(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[in] MechOID MechOID,
[out] int* piMechTypeIndex
)
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:
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE handle that was initialized or allocated by other means. |
MechOID | Mech Type to search the list for. | At this time, the only supported mech types are:
|
piMechTypeIndex | A pointer to an address in which to store the index of the MechType in the list. |
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 the piMechTypeIndex parameter.
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 not valid. |
SPNEGO_E_NOT_FOUND | The requested Mech Type could not be found. |
SPNEGO_E_ELEMENT_UNAVAILABLE | The MechTypeList element is not available. |
SPNEGO_E_INVALID_ELEMENT | The MechTypeList element is available but not valid. |
int spnegoTokenGetBinary(
[in] SPNEGO_TOKEN_HANDLE hSpnegoToken,
[out] unsigned char* pbTokenData,
[in,out] unsigned long* pulDataLen
)
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:
The behavior of the function is undefined if it is passed a SPNEGO_TOKEN_HANDLE handle that was initialized or allocated by other means. |
pbTokenData | A pointer to copy binary SPNEGO token data into. This is an optional parameter and can be NULL. | |
pulDataLen | A pointer to a variable that contains the length of the token data pointed to by the pbTokenData parameter. This parameter is required. | If pbTokenData is non-NULL, pulDataLen must be set to the length of the data buffer. After the function returns, the value is 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. |
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 buffer pointed to by the pbTokenData parameter. If pbTokenData is NULL or the value in pulDataLen indicates a buffer size too small to hold the MIC data, the function returns SPNEGO_E_BUFFER_TOO_SMALL and sets pulDataLen equal to the size required to hold the SPNEGO token.
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 not valid. |
SPNEGO_E_BUFFER_TOO_SMALL | The supplied buffer is too small to contain the Mech token. The pulDataLen will be set equal to the required value. |
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, confirm that 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 in trying to retrieve the mechToken because 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;
}
The API set and example 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!
- 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
- "A Layman's Guide to ASN.1, BER, and DER" found on RSA Laboratories' Public-Key Cryptography Standards page
- Microsoft Kerberos Web Sites:
- Windows Server, search for "Basic Kerberos."
- Step-by-Step Guide to Kerberos 5 (krb5 1.0) Interoperability
- How a Service Composes its SPNs