// 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 <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include "spnego.h"
#include "derparse.h"

//
// 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 [] =
{
   { (unsigned char*) "\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 
   { (unsigned char*) "\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
   { (unsigned char*) "\x06\x06\x2b\x06\x01\x05\x05\x02",               8, 6, spnego_mech_oid_Spnego              },  //  1.3.6.1.1.5.5.2
   { (unsigned char*) "",												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.
//
////////////////////////////////////////////////////////////////////////////

int ASNDerGetLength( unsigned char* pbLengthData, long nBoundaryLength, long* pnLength,
                     long* pnNumLengthBytes )
{
   int   nReturn = SPNEGO_E_INVALID_LENGTH;
   int   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;

   }
   LOG(("ASNDerGetLength returned %d\n",nReturn));
   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,
                        long nLengthWithToken, long nBoundaryLength,
                        long* pnLength, long* pnTokenLength )
{

   int   nReturn = SPNEGO_E_INVALID_LENGTH;
   long  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 

   LOG(("ASNDerCheckToken returned %d\n",nReturn));
   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, long nBoundaryLength,
                     long* pnTokenLength )
{
   int   nReturn = 0L;
   long  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 )
         {
   	    LOG(("ASNDerCheckOID memcmp failed\n"));
            nReturn = SPNEGO_E_UNEXPECTED_OID;
         }
      }
      else
      {
         LOG(("ASNDerCheckOID token length failed\n"));
         nReturn = SPNEGO_E_UNEXPECTED_OID;
      }

   }  // IF OID Token CHecks

   LOG(("ASNDerCheckOID returned %d\n",nReturn));
   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.
//
////////////////////////////////////////////////////////////////////////////

int ASNDerCalcNumLengthBytes( long 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.
//
////////////////////////////////////////////////////////////////////////////

long ASNDerCalcTokenLength( long nLength, long nDataLength )
{
   // Add a byte to the length size to account for a single byte to
   // hold the token type.
   long  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.
//
////////////////////////////////////////////////////////////////////////////

long ASNDerCalcElementLength( long nDataLength, long* pnInternalLength )
{
   // First the type token and the actual data
   long  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.
//
////////////////////////////////////////////////////////////////////////////

long ASNDerCalcMechListLength( SPNEGO_MECH_OID mechoid, long* pnInternalLength )
{
   // First the OID
   long  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 .
//
////////////////////////////////////////////////////////////////////////////

int ASNDerWriteLength( unsigned char* pbData, long nLength )
{
   int   nNumBytesRequired = ASNDerCalcNumLengthBytes( nLength );
   int   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.
//
////////////////////////////////////////////////////////////////////////////

int ASNDerWriteToken( unsigned char* pbData, unsigned char ucType,
                     unsigned char* pbTokenValue, long nLength )
{
   int   nTotalBytesWrittenOut = 0L;
   int   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.
//
////////////////////////////////////////////////////////////////////////////

long ASNDerWriteMechList( unsigned char* pbData, SPNEGO_MECH_OID mechoid )
{
   // First get the length
   long  nInternalLength = 0L;
   long  nMechListLength = ASNDerCalcMechListLength( mechoid, &nInternalLength );
   long  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.
//
////////////////////////////////////////////////////////////////////////////

int ASNDerWriteElement( unsigned char* pbData, unsigned char ucElementSequence,
                        unsigned char ucType, unsigned char* pbTokenValue, long nLength )
{
   // First get the length
   long  nInternalLength = 0L;
   long  nElementLength = ASNDerCalcElementLength( nLength, &nInternalLength );
   long  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;
}

