// Copyright (C) 2002 Microsoft Corporation // All rights reserved. // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" // WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED // OR IMPLIED, INCLUDING BUT NOT LIMITED // TO THE IMPLIED WARRANTIES OF MERCHANTIBILITY // AND/OR FITNESS FOR A PARTICULAR PURPOSE. // // Date - 10/08/2002 // Author - Sanj Surati ///////////////////////////////////////////////////////////// // // DERPARSE.C // // SPNEGO Token Handler Source File // // Contains implementation of ASN.1 DER read/write functions // as defined in DERPARSE.H. // ///////////////////////////////////////////////////////////// /*#include #include #include #include "spnego.h" #include "derparse.h"*/ #include "..\pch.h" #define __LITTLE_ENDIAN__ 1 // // The GSS Mechanism OID enumeration values (SPNEGO_MECH_OID) control which offset in // the array below, that a mechanism can be found. // MECH_OID g_stcMechOIDList[] = { {"\x06\x09\x2a\x86\x48\x82\xf7\x12\x01\x02\x02", 11, 9, spnego_mech_oid_Kerberos_V5_Legacy }, // 1.2.840.48018.1.2.2 {"\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02", 11, 9, spnego_mech_oid_Kerberos_V5 }, // 1.2.840.113554.1.2.2 {"\x06\x06\x2b\x06\x01\x05\x05\x02", 8, 6, spnego_mech_oid_Spnego }, // 1.3.6.1.5.5.2 {"\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a", 12, 10, spnego_mech_oid_NTLMSSP }, // 1.3.6.1.4.1.311.2.2.10 {"", 0, 0, spnego_mech_oid_NotUsed } // Placeholder }; ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerGetLength // // Parameters: // [in] pbLengthData - DER Length Data // [in] nBoundaryLength - Length that value must not exceed. // [out] pnLength - Filled out with length value // [out] pnNumLengthBytes - Filled out with number of bytes // consumed by DER length. // // Returns: // int Success - SPNEGO_E_SUCCESS // Failure - SPNEGO API Error code // // Comments : // Interprets the data at pbLengthData as a DER length. The length must // fit within the bounds of nBoundary length. We do not currently // process lengths that take more than 4 bytes. // //////////////////////////////////////////////////////////////////////////// static int ASNDerGetLength(unsigned char* pbLengthData, unsigned short nBoundaryLength, unsigned short* pnLength, unsigned short* pnNumLengthBytes) { int nReturn = SPNEGO_E_INVALID_LENGTH; unsigned short nNumLengthBytes = 0; // First check if the extended length bit is set if (*pbLengthData & LEN_XTND) { // Lower 7 bits contain the number of trailing bytes that describe the length nNumLengthBytes = *pbLengthData & LEN_MASK; // Check that the number of bytes we are about to read is within our boundary // constraints if (nNumLengthBytes <= nBoundaryLength - 1) { // For now, our handler won't deal with lengths greater than 4 bytes if (nNumLengthBytes >= 1 && nNumLengthBytes <= 4) { // 0 out the initial length *pnLength = 0L; // Bump by 1 byte pbLengthData++; #ifdef __LITTLE_ENDIAN__ // There may be a cleaner way to do this, but for now, this seems to be // an easy way to do the transformation switch (nNumLengthBytes) { case 1: { *(((unsigned char*)pnLength)) = *pbLengthData; break; } case 2: { *(((unsigned char*)pnLength)) = *(pbLengthData + 1); *(((unsigned char*)pnLength) + 1) = *(pbLengthData); break; } case 3: { *(((unsigned char*)pnLength)) = *(pbLengthData + 2); *(((unsigned char*)pnLength) + 2) = *(pbLengthData + 1); *(((unsigned char*)pnLength) + 3) = *(pbLengthData); break; } case 4: { *(((unsigned char*)pnLength)) = *(pbLengthData + 3); *(((unsigned char*)pnLength) + 1) = *(pbLengthData + 2); *(((unsigned char*)pnLength) + 2) = *(pbLengthData + 1); *(((unsigned char*)pnLength) + 3) = *(pbLengthData); break; } } // SWITCH ( nNumLengthBytes ) #else // We are Big-Endian, so the length can be copied in from the source // as is. Ensure that we adjust for the number of bytes we actually // copy. memcpy(((unsigned char*)pnLength) + (4 - nNumLengthBytes), pbLengthData, nNumLengthBytes); #endif // Account for the initial length byte * pnNumLengthBytes = nNumLengthBytes + 1; nReturn = SPNEGO_E_SUCCESS; } // IF Valid Length } // IF num bytes to read is within the boundary length } // IF xtended length else { // Extended bit is not set, so the length is in the value and the one // byte describes the length *pnLength = *pbLengthData & LEN_MASK; *pnNumLengthBytes = 1; nReturn = SPNEGO_E_SUCCESS; } return nReturn; } ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerCheckToken // // Parameters: // [in] pbTokenData - Token Data // [in] nToken - Token identifier to check for // [in] nLengthWithToken - Expected token length (with data) // [in] nBoundaryLength - Length that value must not exceed. // [out] pnLength - Filled out with data length // [out] pnTokenLength - Filled out with number of bytes // consumed by token identifier and length. // // Returns: // int Success - SPNEGO_E_SUCCESS // Failure - SPNEGO API Error code // // Comments : // Checks the data pointed to by pbTokenData for the specified token // identifier and the length that immediately follows. If // nLengthWithToken is > 0, the calculated length must match. The // length must also not exceed the specified boundary length . // //////////////////////////////////////////////////////////////////////////// int ASNDerCheckToken(unsigned char* pbTokenData, unsigned char nToken, unsigned short nLengthWithToken, unsigned short nBoundaryLength, unsigned short* pnLength, unsigned short* pnTokenLength) { int nReturn = SPNEGO_E_INVALID_LENGTH; unsigned short nNumLengthBytes = 0L; // Make sure that we've at least got 2 bytes of room to work with if (nBoundaryLength >= 2) { // The first byte of the token data MUST match the specified token if (*pbTokenData == nToken) { // Next byte indicates the length pbTokenData++; // Get the length described by the token if ((nReturn = ASNDerGetLength(pbTokenData, nBoundaryLength, pnLength, &nNumLengthBytes)) == SPNEGO_E_SUCCESS) { // Verify that the length is LESS THAN the boundary length // (this should prevent us walking out of our buffer) if ((nBoundaryLength - (nNumLengthBytes + 1) < *pnLength)) { nReturn = SPNEGO_E_INVALID_LENGTH; } // If we were passed a length to check, do so now if (nLengthWithToken > 0L) { // Check that the expected length matches if ((nLengthWithToken - (nNumLengthBytes + 1)) != *pnLength) { nReturn = SPNEGO_E_INVALID_LENGTH; } } // IF need to validate length if (SPNEGO_E_SUCCESS == nReturn) { *pnTokenLength = nNumLengthBytes + 1; } } // IF ASNDerGetLength } // IF token matches else { nReturn = SPNEGO_E_TOKEN_NOT_FOUND; } } // IF Boundary Length is at least 2 bytes return nReturn; } ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerCheckOID // // Parameters: // [in] pbTokenData - Token Data // [in] nMechOID - OID we are looking for // [in] nBoundaryLength - Length that value must not exceed. // [out] pnTokenLength - Filled out with number of bytes // consumed by token and data. // // Returns: // int Success - SPNEGO_E_SUCCESS // Failure - SPNEGO API Error code // // Comments : // Checks the data pointed to by pbTokenData for the specified OID. // //////////////////////////////////////////////////////////////////////////// int ASNDerCheckOID(unsigned char* pbTokenData, SPNEGO_MECH_OID nMechOID, unsigned short nBoundaryLength, unsigned short* pnTokenLength) { int nReturn = 0L; unsigned short nLength = 0L; // Verify that we have an OID token if ((nReturn = ASNDerCheckToken(pbTokenData, OID, 0L, nBoundaryLength, &nLength, pnTokenLength)) == SPNEGO_E_SUCCESS) { // Add the data length to the Token Length *pnTokenLength += nLength; // Token Lengths plus the actual length must match the length in our OID list element. // If it doesn't, we're done if (*pnTokenLength == g_stcMechOIDList[nMechOID].iLen) { // Memcompare the token and the expected field if (memcmp(pbTokenData, g_stcMechOIDList[nMechOID].ucOid, *pnTokenLength) != 0) { nReturn = SPNEGO_E_UNEXPECTED_OID; } } else { nReturn = SPNEGO_E_UNEXPECTED_OID; } } // IF OID Token CHecks return nReturn; } ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerCalcNumLengthBytes // // Parameters: // [in] nLength - Length to calculate length bytes for. // // Returns: // int Number of bytes necessary to represent length // // Comments : // Helper function to calculate the number of length bytes necessary to // represent a length value. For our purposes, a 32-bit value should be // enough to describea length. // //////////////////////////////////////////////////////////////////////////// static unsigned short ASNDerCalcNumLengthBytes(unsigned short nLength) { if (nLength <= 0x7F) { // A single byte will be sufficient for describing this length. // The byte will simply contain the length return 1; } else if (nLength <= 0xFF) { // Two bytes are necessary, one to say how many following bytes // describe the length, and one to give the length return 2; } else if (nLength <= 0xFFFF) { // Three bytes are necessary, one to say how many following bytes // describe the length, and two to give the length return 3; } else if (nLength <= 0xFFFFFF) { // Four bytes are necessary, one to say how many following bytes // describe the length, and three to give the length return 4; } else { // Five bytes are necessary, one to say how many following bytes // describe the length, and four to give the length return 5; } } ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerCalcTokenLength // // Parameters: // [in] nLength - Length to calculate length bytes for. // [in] nDataLength - Actual Data length value. // // Returns: // long Number of bytes necessary to represent a token, length and data // // Comments : // Helper function to calculate a token and value size, based on a // supplied length value, and any binary data that will need to be // written out. // //////////////////////////////////////////////////////////////////////////// unsigned short ASNDerCalcTokenLength(unsigned short nLength, unsigned short nDataLength) { // Add a byte to the length size to account for a single byte to // hold the token type. unsigned short nTotalLength = ASNDerCalcNumLengthBytes(nLength) + 1; return nTotalLength + nDataLength; } ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerCalcElementLength // // Parameters: // [in] nDataLength - Length of data. // [out] pnInternalLength - Filled out with length of element // without sequence info. // // Returns: // long Number of bytes necessary to represent an element // // Comments : // Helper function to calculate an element length. An element consists // of a sequence token, a type token and then the data. // //////////////////////////////////////////////////////////////////////////// unsigned short ASNDerCalcElementLength(unsigned short nDataLength, unsigned short* pnInternalLength) { // First the type token and the actual data unsigned short nTotalLength = ASNDerCalcTokenLength(nDataLength, nDataLength); // Internal length is the length without the element sequence token if (NULL != pnInternalLength) { *pnInternalLength = nTotalLength; } // Next add in the element's sequence token (remember that its // length is the total length of the type token and data) nTotalLength += ASNDerCalcTokenLength(nTotalLength, 0L); return nTotalLength; } ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerCalcMechListLength // // Parameters: // [in] mechoid - Mech OID to put in list. // [out] pnInternalLength - Filled out with length of element // without the primary sequence token. // // Returns: // long Number of bytes necessary to represent a mechList // // Comments : // Helper function to calculate a MechList length. A mechlist consists // of a NegTokenInit sequence token, a sequence token for the MechList // and finally a list of OIDs. In our case, we only really have one // OID. // //////////////////////////////////////////////////////////////////////////// unsigned short ASNDerCalcMechListLength(SPNEGO_MECH_OID mechoid, unsigned short* pnInternalLength) { // First the OID unsigned short nTotalLength = g_stcMechOIDList[mechoid].iLen; // Next add in a sequence token nTotalLength += ASNDerCalcTokenLength(nTotalLength, 0L); // Internal length is the length without the element sequence token if (NULL != pnInternalLength) { *pnInternalLength = nTotalLength; } // Finally add in the element's sequence token nTotalLength += ASNDerCalcTokenLength(nTotalLength, 0L); return nTotalLength; } ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerWriteLength // // Parameters: // [out] pbData - Buffer to write into. // [in] nLength - Length to write out. // // Returns: // int Number of bytes written out // // Comments : // Helper function to write out a length value following DER rules . // //////////////////////////////////////////////////////////////////////////// static unsigned short ASNDerWriteLength(unsigned char* pbData, unsigned short nLength) { unsigned short nNumBytesRequired = ASNDerCalcNumLengthBytes(nLength); unsigned short nNumLengthBytes = nNumBytesRequired - 1; if (nNumBytesRequired > 1) { // Write out the number of bytes following which will be used *pbData = (unsigned char)(LEN_XTND | nNumLengthBytes); // Point to where we'll actually write the length pbData++; #ifdef __LITTLE_ENDIAN__ // There may be a cleaner way to do this, but for now, this seems to be // an easy way to do the transformation switch (nNumLengthBytes) { case 1: { // Cast the length to a single byte, since we know that it // is 0x7F or less (or we wouldn't only need a single byte). *pbData = (unsigned char)nLength; break; } case 2: { *pbData = *(((unsigned char*)&nLength) + 1); *(pbData + 1) = *(((unsigned char*)&nLength)); break; } case 3: { *pbData = *(((unsigned char*)&nLength) + 3); *(pbData + 1) = *(((unsigned char*)&nLength) + 2); *(pbData + 2) = *(((unsigned char*)&nLength)); break; } case 4: { *pbData = *(((unsigned char*)&nLength) + 3); *(pbData + 1) = *(((unsigned char*)&nLength) + 2); *(pbData + 2) = *(((unsigned char*)&nLength) + 1); *(pbData + 3) = *(((unsigned char*)&nLength)); break; } } // SWITCH ( nNumLengthBytes ) #else // We are Big-Endian, so the length can be copied in from the source // as is. Ensure that we adjust for the number of bytes we actually // copy. memcpy(pbData, ((unsigned char*)&nLength) + (4 - nNumLengthBytes), nNumLengthBytes); #endif } // IF > 1 byte for length else { // Cast the length to a single byte, since we know that it // is 0x7F or less (or we wouldn't only need a single byte). *pbData = (unsigned char)nLength; } return nNumBytesRequired; } ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerWriteToken // // Parameters: // [out] pbData - Buffer to write into. // [in] ucType - Token Type // [in] pbTokenValue - Actual Value // [in] nLength - Length of Data. // // Returns: // int Number of bytes written out // // Comments : // Helper function to write out a token and any associated data. If // pbTokenValue is non-NULL, then it is written out in addition to the // token identifier and the length bytes. // //////////////////////////////////////////////////////////////////////////// unsigned short ASNDerWriteToken(unsigned char* pbData, unsigned char ucType, unsigned char* pbTokenValue, unsigned short nLength) { unsigned short nTotalBytesWrittenOut = 0L; unsigned short nNumLengthBytesWritten = 0L; // Write out the type *pbData = ucType; // Wrote 1 byte, and move data pointer nTotalBytesWrittenOut++; pbData++; // Now write out the length and adjust the number of bytes written out nNumLengthBytesWritten = ASNDerWriteLength(pbData, nLength); nTotalBytesWrittenOut += nNumLengthBytesWritten; pbData += nNumLengthBytesWritten; // Write out the token value if we got one. The assumption is that the // nLength value indicates how many bytes are in pbTokenValue. if (NULL != pbTokenValue) { memcpy(pbData, pbTokenValue, nLength); nTotalBytesWrittenOut += nLength; } return nTotalBytesWrittenOut; } ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerWriteOID // // Parameters: // [out] pbData - Buffer to write into. // [in] eMechOID - OID to write out. // // Returns: // int Number of bytes written out // // Comments : // Helper function to write out an OID. For these we have the raw bytes // listed in a global structure. The caller simply indicates which OID // should be written and we will splat out the data. // //////////////////////////////////////////////////////////////////////////// int ASNDerWriteOID(unsigned char* pbData, SPNEGO_MECH_OID eMechOID) { memcpy(pbData, g_stcMechOIDList[eMechOID].ucOid, g_stcMechOIDList[eMechOID].iLen); return g_stcMechOIDList[eMechOID].iLen; } ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerWriteMechList // // Parameters: // [out] pbData - Buffer to write into. // [in] eMechOID - OID to put in MechList. // // Returns: // int Number of bytes written out // // Comments : // Helper function to write out a MechList. A MechList consists of the // Init Token Sequence, a sequence token and then the list of OIDs. In // our case the OID is from a global array of known OIDs. // //////////////////////////////////////////////////////////////////////////// unsigned short ASNDerWriteMechList(unsigned char* pbData, SPNEGO_MECH_OID mechoid) { // First get the length unsigned short nInternalLength = 0L; unsigned short nMechListLength = ASNDerCalcMechListLength(mechoid, &nInternalLength); unsigned short nTempLength = 0L; nTempLength = ASNDerWriteToken(pbData, SPNEGO_NEGINIT_ELEMENT_MECHTYPES, NULL, nInternalLength); // Adjust the data pointer pbData += nTempLength; // Now write the Sequence token and the OID (the OID is a BLOB in the global // structure. nTempLength = ASNDerWriteToken(pbData, SPNEGO_CONSTRUCTED_SEQUENCE, g_stcMechOIDList[mechoid].ucOid, g_stcMechOIDList[mechoid].iLen); return nMechListLength; } ///////////////////////////////////////////////////////////////////////////// // // Function: // ASNDerWriteElement // // Parameters: // [out] pbData - Buffer to write into. // [in] ucElementSequence - Sequence Token // [in] ucType - Token Type // [in] pbTokenValue - Actual Value // [in] nLength - Length of Data. // // Returns: // int Number of bytes written out // // Comments : // Helper function to write out a SPNEGO Token element. An element // consists of a sequence token, a type token and the associated data. // //////////////////////////////////////////////////////////////////////////// unsigned short ASNDerWriteElement(unsigned char* pbData, unsigned char ucElementSequence, unsigned char ucType, unsigned char* pbTokenValue, unsigned short nLength) { // First get the length unsigned short nInternalLength = 0L; unsigned short nElementLength = ASNDerCalcElementLength(nLength, &nInternalLength); unsigned short nTempLength = 0L; // Write out the sequence byte and the length of the type and data nTempLength = ASNDerWriteToken(pbData, ucElementSequence, NULL, nInternalLength); // Adjust the data pointer pbData += nTempLength; // Now write the type and the data. nTempLength = ASNDerWriteToken(pbData, ucType, pbTokenValue, nLength); return nElementLength; }