/**************************************************************************** * * * cryptlib Internal API * * Copyright Peter Gutmann 1992-2007 * * * ****************************************************************************/ /* A generic module that implements a rug under which all problems not solved elsewhere are swept */ #include #include /* Needed on some systems for macro-mapped *printf()'s */ #if defined( INC_ALL ) #include "crypt.h" #ifdef USE_MD2 #include "md2.h" #endif /* USE_MD2 */ #ifdef USE_MD5 #include "md5.h" #endif /* USE_MD5 */ #ifdef USE_RIPEMD160 #include "ripemd.h" #endif /* USE_RIPEMD160 */ #include "sha.h" #ifdef USE_SHA2 #include "sha2.h" #endif /* USE_SHA2 */ #include "stream.h" #else #include "crypt.h" #ifdef USE_MD2 #include "crypt/md2.h" #endif /* USE_MD2 */ #ifdef USE_MD5 #include "crypt/md5.h" #endif /* USE_MD5 */ #ifdef USE_RIPEMD160 #include "crypt/ripemd.h" #endif /* USE_RIPEMD160 */ #include "crypt/sha.h" #ifdef USE_SHA2 #include "crypt/sha2.h" #endif /* USE_SHA2 */ #include "io/stream.h" #endif /* Compiler-specific includes */ /* Perform the FIPS-140 statistical checks that are feasible on a byte string. The full suite of tests assumes that an infinite source of values (and time) is available, the following is a scaled-down version used to sanity-check keys and other short random data blocks. Note that this check requires at least 64 bits of data in order to produce useful results */ BOOLEAN checkEntropy( const BYTE *data, const int dataLength ) { const int delta = ( dataLength < 16 ) ? 1 : 0; int bitCount[ 4 + 8 ] = { 0 }, noOnes, i; assert( isReadPtr( data, dataLength ) ); assert( dataLength >= 8 ); for( i = 0; i < dataLength; i++ ) { const int value = data[ i ]; bitCount[ value & 3 ]++; bitCount[ ( value >> 2 ) & 3 ]++; bitCount[ ( value >> 4 ) & 3 ]++; bitCount[ value >> 6 ]++; } /* Monobit test: Make sure that at least 1/4 of the bits are ones and 1/4 are zeroes */ noOnes = bitCount[ 1 ] + bitCount[ 2 ] + ( 2 * bitCount[ 3 ] ); if( noOnes < dataLength * 2 || noOnes > dataLength * 6 ) return( FALSE ); /* Poker test (almost): Make sure that each bit pair is present at least 1/16 of the time. The FIPS 140 version uses 4-bit values, but the numer of samples available from the keys is far too small for this. This isn't precisely 1/16, for short samples (< 128 bits) we adjust the count by one because of the small sample size, and for odd-length data we're getting four more samples so the actual figure is slightly less than 1/16 */ if( ( bitCount[ 0 ] + delta < dataLength / 2 ) || \ ( bitCount[ 1 ] + delta < dataLength / 2 ) || \ ( bitCount[ 2 ] + delta < dataLength / 2 ) || \ ( bitCount[ 3 ] + delta < dataLength / 2 ) ) return( FALSE ); return( TRUE ); } /* Copy a string attribute to external storage, with various range checks to follow the cryptlib semantics (these will already have been done by the caller, this is just a backup check). There are two forms for this function, one that takes a MESSAGE_DATA parameter containing all of the result parameters in one place and the other that takes distinct result parameters, typically because they've been passed down through several levels of function call beyond the point where they were in a MESSAGE_DATA. We also have a second function that's used internally for data-copying */ int attributeCopyParams( void *dest, const int destMaxLength, int *destLength, const void *source, const int sourceLength ) { assert( dest == NULL || isWritePtr( dest, destMaxLength ) ); assert( sourceLength == 0 || isReadPtr( source, sourceLength ) ); /* Clear return value */ *destLength = 0; if( sourceLength <= 0 ) return( CRYPT_ERROR_NOTFOUND ); if( dest != NULL ) { assert( isReadPtr( source, sourceLength ) ); if( sourceLength > destMaxLength || \ !isWritePtr( dest, sourceLength ) ) return( CRYPT_ARGERROR_STR1 ); memcpy( dest, source, sourceLength ); } *destLength = sourceLength; return( CRYPT_OK ); } int attributeCopy( MESSAGE_DATA *msgData, const void *attribute, const int attributeLength ) { assert( isWritePtr( msgData, sizeof( MESSAGE_DATA ) ) ); assert( attributeLength == 0 || \ isReadPtr( attribute, attributeLength ) ); return( attributeCopyParams( msgData->data, msgData->length, &msgData->length, attribute, attributeLength ) ); } int dataCopy( void *dest, const int destMaxLength, int *destLength, const void *source, const int sourceLength ) { assert( isWritePtr( dest, destMaxLength ) ); assert( isWritePtr( destLength, sizeof( int ) ) ); assert( isReadPtr( source, sourceLength ) ); /* Clear return value */ *destLength = 0; if( sourceLength <= 0 ) return( CRYPT_ERROR_NOTFOUND ); if( sourceLength > destMaxLength ) return( CRYPT_ERROR_OVERFLOW ); memcpy( dest, source, sourceLength ); *destLength = sourceLength; return( CRYPT_OK ); } /* Check whether a given algorithm is available */ BOOLEAN algoAvailable( const CRYPT_ALGO_TYPE cryptAlgo ) { CRYPT_QUERY_INFO queryInfo; assert( cryptAlgo > CRYPT_ALGO_NONE && cryptAlgo < CRYPT_ALGO_LAST ); return( cryptStatusOK( krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_DEV_QUERYCAPABILITY, &queryInfo, cryptAlgo ) ) ? TRUE : FALSE ); } /* For a given algorithm pair, check whether the first is stronger than the second. For hashes the order is: SHA2 > RIPEMD160 > SHA-1 > all others */ BOOLEAN isStrongerHash( const CRYPT_ALGO_TYPE algorithm1, const CRYPT_ALGO_TYPE algorithm2 ) { static const CRYPT_ALGO_TYPE algoPrecedence[] = { CRYPT_ALGO_SHA2, CRYPT_ALGO_RIPEMD160, CRYPT_ALGO_SHA, CRYPT_ALGO_NONE, CRYPT_ALGO_NONE }; int algo1index, algo2index; assert( algorithm1 >= CRYPT_ALGO_FIRST_HASH && \ algorithm1 <= CRYPT_ALGO_LAST_HASH ); assert( algorithm2 >= CRYPT_ALGO_FIRST_HASH && \ algorithm2 <= CRYPT_ALGO_LAST_HASH ); /* Find the relative positions on the scale of the two algorithms */ for( algo1index = 0; algoPrecedence[ algo1index ] != algorithm1 && \ algo1index < FAILSAFE_ARRAYSIZE( algoPrecedence, CRYPT_ALGO_TYPE ); algo1index++ ) { /* If we've reached an unrated algorithm, it can't be stronger than the other one */ if( algoPrecedence[ algo1index ] == CRYPT_ALGO_NONE ) return( FALSE ); } if( algo1index >= FAILSAFE_ARRAYSIZE( algoPrecedence, CRYPT_ALGO_TYPE ) ) retIntError_Boolean(); for( algo2index = 0; algoPrecedence[ algo2index ] != algorithm2 && \ algo2index < FAILSAFE_ARRAYSIZE( algoPrecedence, CRYPT_ALGO_TYPE ); algo2index++ ) { /* If we've reached an unrated algorithm, it's weaker than the other one */ if( algoPrecedence[ algo2index ] == CRYPT_ALGO_NONE ) return( TRUE ); } if( algo2index >= FAILSAFE_ARRAYSIZE( algoPrecedence, CRYPT_ALGO_TYPE ) ) retIntError_Boolean(); /* If the first algorithm has a smaller index than the second, it's a stronger algorithm */ return( ( algo1index < algo2index ) ? TRUE : FALSE ); } /**************************************************************************** * * * Error-handling Functions * * * ****************************************************************************/ /* Exit after recording a detailed error message. This is used by lower- level code to provide more information to the caller than a basic error code */ int retExtFn( ERROR_INFO *errorInfoPtr, const int status, const char *format, ... ) { va_list argPtr; assert( isWritePtr( errorInfoPtr, sizeof( ERROR_INFO ) ) ); assert( isWritePtr( errorInfoPtr->errorString, MAX_ERRMSG_SIZE ) ); assert( isReadPtr( format, 4 ) ); va_start( argPtr, format ); vsprintf_s( errorInfoPtr->errorString, MAX_ERRMSG_SIZE, format, argPtr ); va_end( argPtr ); assert( !cryptArgError( status ) ); /* Catch leaks */ return( cryptArgError( status ) ? CRYPT_ERROR_FAILED : status ); } int retExtObjFn( ERROR_INFO *errorInfoPtr, const int status, const CRYPT_HANDLE extErrorObject, const char *format, ... ) { MESSAGE_DATA msgData; va_list argPtr; int extErrorStatus; assert( isWritePtr( errorInfoPtr, sizeof( ERROR_INFO ) ) ); assert( isWritePtr( errorInfoPtr->errorString, MAX_ERRMSG_SIZE ) ); assert( extErrorObject == DEFAULTUSER_OBJECT_HANDLE || \ isHandleRangeValid( extErrorObject ) ); assert( isReadPtr( format, 4 ) ); /* Check whether there's any additional error information available */ va_start( argPtr, format ); setMessageData( &msgData, NULL, 0 ); extErrorStatus = krnlSendMessage( extErrorObject, MESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_ATTRIBUTE_INT_ERRORMESSAGE ); if( cryptStatusOK( extErrorStatus ) ) { char extraErrorString[ MAX_ERRMSG_SIZE + 1 + 8 ]; /* There's additional information present via the additional object, fetch it and append it to the session-level error message */ setMessageData( &msgData, extraErrorString, MAX_ERRMSG_SIZE ); extErrorStatus = krnlSendMessage( extErrorObject, MESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_ATTRIBUTE_INT_ERRORMESSAGE ); if( cryptStatusOK( extErrorStatus ) ) extraErrorString[ msgData.length ] = '\0'; else strlcpy_s( extraErrorString, MAX_ERRMSG_SIZE, "(None available)" ); vsprintf_s( errorInfoPtr->errorString, MAX_ERRMSG_SIZE, format, argPtr ); if( strlen( errorInfoPtr->errorString ) < MAX_ERRMSG_SIZE - 64 ) { strlcat_s( errorInfoPtr->errorString, MAX_ERRMSG_SIZE, ". Additional information: " ); strlcat_s( errorInfoPtr->errorString, MAX_ERRMSG_SIZE, extraErrorString ); } } else vsprintf_s( errorInfoPtr->errorString, MAX_ERRMSG_SIZE, format, argPtr ); va_end( argPtr ); assert( !cryptArgError( status ) ); /* Catch leaks */ return( cryptArgError( status ) ? CRYPT_ERROR_FAILED : status ); } int retExtStrFn( ERROR_INFO *errorInfoPtr, const int status, const char *extErrorString, const char *format, ... ) { char errorString[ MAX_ERRMSG_SIZE + 1 + 8 ]; va_list argPtr; assert( isWritePtr( errorInfoPtr, sizeof( ERROR_INFO ) ) ); assert( isWritePtr( errorInfoPtr->errorString, MAX_ERRMSG_SIZE ) ); assert( isReadPtr( extErrorString, 4 ) ); assert( isReadPtr( format, 4 ) ); /* This function is typically used when the caller wants to convert something like "Low-level error string" into "High-level error string: Low-level error string". If the low-level error string was generated by a retExt() call then it'll already be in the errorInfo buffer where the high-level error string needs to go. To get around this we copy the string into a temporary buffer from where it can be appended back onto the string in the errorInfo buffer */ strlcpy_s( errorString, MAX_ERRMSG_SIZE, extErrorString ); va_start( argPtr, format ); vsprintf_s( errorInfoPtr->errorString, MAX_ERRMSG_SIZE, format, argPtr ); if( strlen( errorInfoPtr->errorString ) < MAX_ERRMSG_SIZE - 64 ) strlcat_s( errorInfoPtr->errorString, MAX_ERRMSG_SIZE, errorString ); va_end( argPtr ); assert( !cryptArgError( status ) ); /* Catch leaks */ return( cryptArgError( status ) ? CRYPT_ERROR_FAILED : status ); } /**************************************************************************** * * * Time Functions * * * ****************************************************************************/ /* Get the system time safely. The first function implements hard failures, converting invalid time values to zero, which yield a warning date of 1/1/1970 rather than an out-of-bounds or garbage value. The second function implements soft failures, returning an estimate of the approximate current date. The third function is used for operations such as signing certs and timestamping and tries to get the time from a hardware time source if one is available */ time_t getTime( void ) { const time_t theTime = time( NULL ); return( ( theTime <= MIN_TIME_VALUE ) ? 0 : theTime ); } time_t getApproxTime( void ) { const time_t theTime = time( NULL ); return( ( theTime <= MIN_TIME_VALUE ) ? CURRENT_TIME_VALUE : theTime ); } time_t getReliableTime( const CRYPT_HANDLE cryptHandle ) { CRYPT_DEVICE cryptDevice; MESSAGE_DATA msgData; time_t theTime; int status; assert( cryptHandle == SYSTEM_OBJECT_HANDLE || \ isHandleRangeValid( cryptHandle ) ); /* Get the dependent device for the object that needs the time */ status = krnlSendMessage( cryptHandle, IMESSAGE_GETDEPENDENT, &cryptDevice, OBJECT_TYPE_DEVICE ); if( cryptStatusError( status ) ) cryptDevice = SYSTEM_OBJECT_HANDLE; /* Try and get the time from the device */ setMessageData( &msgData, &theTime, sizeof( time_t ) ); status = krnlSendMessage( cryptDevice, IMESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_IATTRIBUTE_TIME ); if( cryptStatusError( status ) && cryptDevice != SYSTEM_OBJECT_HANDLE ) { /* We couldn't get the time from a crypto token, fall back to the system device */ status = krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_IATTRIBUTE_TIME ); } if( cryptStatusError( status ) ) return( 0 ); return( ( theTime <= MIN_TIME_VALUE ) ? 0 : theTime ); } /* Monotonic timer interface that protect against the system clock being changed during a timing operation. Even without deliberate fiddling with the system clock, a timeout during a DST switch can cause something like a 5s wait to turn into a 1hr 5s wait, so we have to abstract the standard time API into a monotonic time API. Since these functions are purely peripheral to other operations (for example handling timeouts for network I/O), they never fail but simply return good-enough results if there's a problem (although they assert in debug mode). This is because we don't want to abort a network session just because we've detected some trivial clock irregularity. The way this works is that we record the following information for each timing interval: endTime ................+-----------------------+............... ^ | | ^ currentTime |<--- timeRemaining --->| currentTime When currentTime falls outside the timeRemaining interval, we know that a clock change has occurred and can try and correct it. Moving forwards by an unexpected amount is a bit more tricky because it's hard to define "unexpected", so we use an estimation method that detects the typical reasons for a clock leap (DST adjust) without yielding false positives */ static void handleTimeOutOfBounds( MONOTIMER_INFO *timerInfo ) { assert( isWritePtr( timerInfo, sizeof( MONOTIMER_INFO ) ) ); assert( NOTREACHED ); /* We've run into an overflow condition, this is a bit tricky to handle because we can't just give up on (say) performing network I/O just because we can't reliably set a timeout. The best that we can do is warn in debug mode and set a zero timeout so that at least one lot of I/O will still take place */ timerInfo->totalTimeout = timerInfo->timeRemaining = 0; } static void correctMonoTimer( MONOTIMER_INFO *timerInfo, const time_t currentTime ) { BOOLEAN needsCorrection = FALSE; assert( isWritePtr( timerInfo, sizeof( MONOTIMER_INFO ) ) ); /* If the clock has been rolled back to before the start time, we need to correct this */ if( currentTime < timerInfo->endTime - timerInfo->timeRemaining ) needsCorrection = TRUE; else { /* If we're past the timer end time, check to see whether it's jumped by a suspicious amount. If we're more than 30 minutes past the timeout (which will catch things like DST changes) and the initial timeout was less then the change (to avoid a false positive if we've been waiting > 30 minutes for a legitimate timeout), we need to correct this */ if( currentTime > timerInfo->endTime ) { const time_t delta = currentTime - timerInfo->endTime; if( delta > ( 30 * 60 ) && \ timerInfo->totalTimeout < delta ) needsCorrection = TRUE; } } if( !needsCorrection ) return; /* The time information has been changed, correct the recorded time information for the new time */ timerInfo->endTime = currentTime + timerInfo->timeRemaining; if( timerInfo->endTime < currentTime + max( timerInfo->timeRemaining, timerInfo->totalTimeout ) ) handleTimeOutOfBounds( timerInfo ); } void setMonoTimer( MONOTIMER_INFO *timerInfo, const int duration ) { const time_t currentTime = getApproxTime(); assert( isWritePtr( timerInfo, sizeof( MONOTIMER_INFO ) ) ); assert( duration >= 0 && duration < INT_MAX ); memset( timerInfo, 0, sizeof( MONOTIMER_INFO ) ); timerInfo->endTime = currentTime + duration; timerInfo->timeRemaining = timerInfo->totalTimeout = duration; if( duration == 0 ) return; /* No-wait I/O, we're done */ correctMonoTimer( timerInfo, currentTime ); } void extendMonoTimer( MONOTIMER_INFO *timerInfo, const int duration ) { const time_t currentTime = getApproxTime(); assert( isWritePtr( timerInfo, sizeof( MONOTIMER_INFO ) ) ); assert( duration >= 0 && duration < INT_MAX ); /* Correct the timer for clock skew if required */ correctMonoTimer( timerInfo, currentTime ); /* Extend the monotonic timer's timeout interval to allow for further data to be processed */ timerInfo->totalTimeout += duration; timerInfo->endTime += duration; timerInfo->timeRemaining = timerInfo->endTime - currentTime; /* Re-correct the timer in case overflow occurred */ correctMonoTimer( timerInfo, currentTime ); } BOOLEAN checkMonoTimerExpired( MONOTIMER_INFO *timerInfo ) { const time_t currentTime = getApproxTime(); int timeRemaining; assert( isWritePtr( timerInfo, sizeof( MONOTIMER_INFO ) ) ); /* If the timeout has expired, don't try doing anything else */ if( timerInfo->timeRemaining <= 0 ) return( TRUE ); /* Correct the monotonic timer for clock skew if required */ correctMonoTimer( timerInfo, currentTime ); /* Check whether the time has expired */ timeRemaining = timerInfo->endTime - currentTime; if( timeRemaining > timerInfo->timeRemaining ) { handleTimeOutOfBounds( timerInfo ); timeRemaining = 0; } timerInfo->timeRemaining = timeRemaining; return( ( timerInfo->timeRemaining <= 0 ) ? TRUE : FALSE ); } BOOLEAN checkMonoTimerExpiryImminent( MONOTIMER_INFO *timerInfo, const int timeLeft ) { const time_t currentTime = getApproxTime(); int timeRemaining; assert( isWritePtr( timerInfo, sizeof( MONOTIMER_INFO ) ) ); /* If the timeout has expired, don't try doing anything else */ if( timerInfo->timeRemaining <= 0 ) return( TRUE ); /* Correct the monotonic timer for clock skew if required */ correctMonoTimer( timerInfo, currentTime ); /* Check whether the time will expire within timeLeft seconds */ timeRemaining = timerInfo->endTime - currentTime; if( timeRemaining > timerInfo->timeRemaining ) { handleTimeOutOfBounds( timerInfo ); timeRemaining = 0; } timerInfo->timeRemaining = timeRemaining; return( ( timerInfo->timeRemaining < timeLeft ) ? TRUE : FALSE ); } /**************************************************************************** * * * Checksum/Hash Functions * * * ****************************************************************************/ /* Calculate a 16-bit Fletcher-like checksum of a block of data. This isn't quite a pure Fletcher checksum because we don't bother keeping the accumulators at 8 bits, and also don't need to set the initial value to nonzero since we'll never see a sequence of zero bytes. This isn't a big deal since all we need is a consistent result. In addition we don't bother with masking to 16 bits during the calculation since it's not being used as a true checksum */ int checksumData( const void *data, const int dataLength ) { const BYTE *dataPtr = data; int sum1 = 0, sum2 = 0, i; assert( isReadPtr( data, dataLength ) ); /* Error handling: If there's a problem, return a zero checksum */ if( data == NULL || dataLength <= 0 ) retIntError(); for( i = 0; i < dataLength; i++ ) { sum1 += dataPtr[ i ]; sum2 += sum1; } return( sum2 & 0xFFFF ); } /* Calculate the hash of a block of data. We use SHA-1 because it's the built-in default, but any algorithm will do since we're only using it to transform a variable-length value to a fixed-length one for easy comparison purposes */ void hashData( BYTE *hash, const int hashMaxLength, const void *data, const int dataLength ) { static HASHFUNCTION hashFunction = NULL; static int hashSize; BYTE hashBuffer[ 20 + 8 ]; assert( isWritePtr( hash, hashMaxLength ) ); assert( hashMaxLength >= HASH_DATA_SIZE ); assert( isReadPtr( data, dataLength ) ); /* Get the hash algorithm information if necessary */ if( hashFunction == NULL ) getHashParameters( CRYPT_ALGO_SHA, &hashFunction, &hashSize ); /* Error handling: If there's a problem, return a zero hash. Note that this can lead to a false-positive match if we've called multiple times with invalid input, in theory we could full the return buffer with nonce data to ensure that we never get a false-positive match, but since this is a should-never-occur condition anyway it's not certain whether forcing a match or forcing a non-match is the preferred behaviour */ if( data == NULL || dataLength <= 0 || hashMaxLength > hashSize || \ hashFunction == NULL ) { memset( hash, 0, hashMaxLength ); retIntError_Void(); } /* Hash the data and copy as many bytes as the caller has requested to the output (typically they'll require only a subset of the full amount, since all that we're doing is transforming a variable-length value to a fixed-length value for easy comparison purposes) */ hashFunction( NULL, hashBuffer, 20, ( BYTE * ) data, dataLength, HASH_ALL ); memcpy( hash, hashBuffer, min( hashMaxLength, hashSize ) ); zeroise( hashBuffer, 20 ); } /* Determine the parameters for a particular hash algorithm */ void md2HashBuffer( HASHINFO hashInfo, BYTE *outBuffer, const int outBufMaxLength, const BYTE *inBuffer, const int inLength, const HASH_STATE hashState ); void md5HashBuffer( HASHINFO hashInfo, BYTE *outBuffer, const int outBufMaxLength, const BYTE *inBuffer, const int inLength, const HASH_STATE hashState ); void ripemd160HashBuffer( HASHINFO hashInfo, BYTE *outBuffer, const int outBufMaxLength, const BYTE *inBuffer, const int inLength, const HASH_STATE hashState ); void shaHashBuffer( HASHINFO hashInfo, BYTE *outBuffer, const int outBufMaxLength, const BYTE *inBuffer, const int inLength, const HASH_STATE hashState ); void sha2HashBuffer( HASHINFO hashInfo, BYTE *outBuffer, const int outBufMaxLength, const BYTE *inBuffer, const int inLength, const HASH_STATE hashState ); void sha2_512HashBuffer( HASHINFO hashInfo, BYTE *outBuffer, const int outBufMaxLength, const BYTE *inBuffer, const int inLength, const HASH_STATE hashState ); void getHashParameters( const CRYPT_ALGO_TYPE hashAlgorithm, HASHFUNCTION *hashFunction, int *hashSize ) { assert( hashAlgorithm >= CRYPT_ALGO_FIRST_HASH && \ hashAlgorithm <= CRYPT_ALGO_LAST_HASH ); assert( isWritePtr( hashFunction, sizeof( HASHFUNCTION ) ) ); assert( ( hashSize == NULL ) || isWritePtr( hashSize, sizeof( int ) ) ); switch( hashAlgorithm ) { #ifdef USE_MD2 case CRYPT_ALGO_MD2: *hashFunction = md2HashBuffer; if( hashSize != NULL ) *hashSize = MD2_DIGEST_LENGTH; return; #endif /* USE_MD2 */ #ifdef USE_MD5 case CRYPT_ALGO_MD5: *hashFunction = md5HashBuffer; if( hashSize != NULL ) *hashSize = MD5_DIGEST_LENGTH; return; #endif /* USE_MD5 */ #ifdef USE_RIPEMD160 case CRYPT_ALGO_RIPEMD160: *hashFunction = ripemd160HashBuffer; if( hashSize != NULL ) *hashSize = RIPEMD160_DIGEST_LENGTH; return; #endif /* USE_RIPEMD160 */ case CRYPT_ALGO_SHA: *hashFunction = shaHashBuffer; if( hashSize != NULL ) *hashSize = SHA_DIGEST_LENGTH; return; #ifdef USE_SHA2 case CRYPT_ALGO_SHA2: *hashFunction = sha2HashBuffer; if( hashSize != NULL ) *hashSize = SHA256_DIGEST_SIZE; return; /* SHA2-512 is only available on systems with 64-bit data type support, at the moment this is only used internally for some PRFs so we have to handle it via a kludge on SHA2 */ #ifdef USE_SHA2_512 case CRYPT_ALGO_SHA2 + 1: *hashFunction = sha2_512HashBuffer; if( hashSize != NULL ) *hashSize = SHA512_DIGEST_SIZE; return; #endif /* USE_SHA2_512 */ #endif /* USE_SHA2 */ } /* Make sure that we always get some sort of hash function rather than just dying. This code always works because the internal self-test has confirmed the availability and functioning of SHA-1 on startup */ *hashFunction = shaHashBuffer; if( hashSize != NULL ) *hashSize = SHA_DIGEST_LENGTH; retIntError_Void(); } /**************************************************************************** * * * String Functions * * * ****************************************************************************/ /* Perform various string-processing operations */ int strFindCh( const char *str, const int strLen, const char findCh ) { int i; assert( isReadPtr( str, strLen ) ); for( i = 0; i < strLen; i++ ) { if( str[ i ] == findCh ) return( i ); } return( -1 ); } int strFindStr( const char *str, const int strLen, const char *findStr, const int findStrLen ) { const char findCh = *findStr; int i; assert( isReadPtr( str, strLen ) ); assert( isReadPtr( findStr, findStrLen ) ); for( i = 0; i < strLen - findStrLen; i++ ) { if( str[ i ] == findCh && \ !strCompare( str + i, findStr, findStrLen ) ) return( i ); } return( -1 ); } int strStripWhitespace( char **newStringPtr, const char *string, const int stringLen ) { int startPos, endPos; assert( isWritePtr( newStringPtr, sizeof( char * ) ) ); assert( isReadPtr( string, stringLen ) ); /* Skip leading and trailing whitespace */ for( startPos = 0; startPos < stringLen && string[ startPos ] <= ' '; startPos++ ); *newStringPtr = ( char * ) string + startPos; for( endPos = stringLen; endPos > startPos && string[ endPos - 1 ] <= ' '; endPos-- ); return( endPos - startPos ); } /* Sanitise a string before passing it back to the user. This is used to clear potential problem characters (for example control characters) from strings passed back from untrusted sources. As well as the length of the string that we want to sanitise, we take an additional totalLength parameter that's used to calculate whether we should append a '[...]' to the string to show that there was further output present that was dropped. Note that only ( string, stringLength ) is valid string data, totalLength may be an arbitrarily large value. The function returns a pointer to the string to allow it to be used in the form printf( "..%s..", sanitiseString( string, stringLength, totalLength ) ) */ char *sanitiseString( char *string, const int stringLength, const int totalLength ) { int i; assert( isWritePtr( string, stringLength ) ); assert( stringLength <= totalLength ); /* Remove any potentially unsafe characters from the string */ for( i = 0; i < stringLength; i++ ) { if( !isPrint( string[ i ] ) ) string[ i ] = '.'; } /* If there was more input than we could fit into the buffer and there's room for a continuation indicator, add this to the output string */ if( ( totalLength > stringLength ) && ( stringLength > 8 ) ) memcpy( string + stringLength - 5, "[...]", 5 ); /* Terminate the string to allow it to be used in printf()-style functions */ string[ stringLength ] = '\0'; return( string ); } /**************************************************************************** * * * TR 24731 Safe stdlib Extensions * * * ****************************************************************************/ #ifndef __STDC_LIB_EXT1__ /* Minimal wrappers for the TR 24731 functions to map them to older stdlib equivalents */ int mbstowcs_s( size_t *retval, wchar_t *dst, size_t dstmax, const char *src, size_t len ) { *retval = mbstowcs( dst, src, len ); return( ( *retval > 0 ) ? 0 : -1 ); } int wcstombs_s( size_t *retval, char *dst, size_t dstmax, const wchar_t *src, size_t len ) { *retval = wcstombs( dst, src, len ); return( ( *retval > 0 ) ? 0 : -1 ); } #endif /* !__STDC_LIB_EXT1__ */ /**************************************************************************** * * * Dynamic Buffer Management Routines * * * ****************************************************************************/ /* Dynamic buffer management functions. When reading variable-length object data we can usually fit the data into a small fixed-length buffer, but occasionally we have to cope with larger data amounts that require a dynamically-allocated buffer. The following routines manage this process, dynamically allocating and freeing a larger buffer if required */ static int getDynData( DYNBUF *dynBuf, const CRYPT_HANDLE cryptHandle, const MESSAGE_TYPE message, const int messageParam ) { MESSAGE_DATA msgData; void *dataPtr = NULL; int status; assert( isWritePtr( dynBuf, sizeof( DYNBUF ) ) ); assert( isHandleRangeValid( cryptHandle ) ); assert( message == IMESSAGE_GETATTRIBUTE_S || \ message == IMESSAGE_CRT_EXPORT ); /* Clear return value. Note that we don't use the usual memset() to clear the value since the structure contains the storage for the fixed-size portion of the buffer appended to it, and using memset() to clear that is just unnecessary overhead */ dynBuf->data = dynBuf->dataBuffer; dynBuf->length = 0; /* Get the data from the object */ setMessageData( &msgData, NULL, 0 ); status = krnlSendMessage( cryptHandle, message, &msgData, messageParam ); if( cryptStatusError( status ) ) return( status ); if( msgData.length > DYNBUF_SIZE ) { /* The data is larger than the built-in buffer size, dynamically allocate a larger buffer */ if( ( dataPtr = clDynAlloc( "dynCreate", msgData.length ) ) == NULL ) return( CRYPT_ERROR_MEMORY ); msgData.data = dataPtr; status = krnlSendMessage( cryptHandle, message, &msgData, messageParam ); if( cryptStatusError( status ) ) { clFree( "dynCreate", dataPtr ); return( status ); } dynBuf->data = dataPtr; } else { /* The data will fit into the built-in buffer, read it directly into the buffer */ msgData.data = dynBuf->data; status = krnlSendMessage( cryptHandle, message, &msgData, messageParam ); if( cryptStatusError( status ) ) return( status ); } dynBuf->length = msgData.length; return( CRYPT_OK ); } int dynCreate( DYNBUF *dynBuf, const CRYPT_HANDLE cryptHandle, const CRYPT_ATTRIBUTE_TYPE attributeType ) { assert( isWritePtr( dynBuf, sizeof( DYNBUF ) ) ); assert( isHandleRangeValid( cryptHandle ) ); assert( isAttribute( attributeType ) || \ isInternalAttribute( attributeType ) ); return( getDynData( dynBuf, cryptHandle, IMESSAGE_GETATTRIBUTE_S, attributeType ) ); } int dynCreateCert( DYNBUF *dynBuf, const CRYPT_HANDLE cryptHandle, const CRYPT_CERTFORMAT_TYPE formatType ) { assert( isWritePtr( dynBuf, sizeof( DYNBUF ) ) ); assert( isHandleRangeValid( cryptHandle ) ); assert( formatType == CRYPT_CERTFORMAT_CERTIFICATE ); return( getDynData( dynBuf, cryptHandle, IMESSAGE_CRT_EXPORT, formatType ) ); } void dynDestroy( DYNBUF *dynBuf ) { assert( isWritePtr( dynBuf, sizeof( DYNBUF ) ) ); assert( isWritePtr( dynBuf->data, dynBuf->length ) ); zeroise( dynBuf->data, dynBuf->length ); if( dynBuf->data != dynBuf->dataBuffer ) clFree( "dynDestroy", dynBuf->data ); } /**************************************************************************** * * * Memory Management Routines * * * ****************************************************************************/ /* Memory pool management functions. When allocating many little blocks of memory, especially in resource-constrained systems, it's better if we pre- allocate a small memory pool ourselves and grab chunks of it as required, falling back to dynamically allocating memory later on if we exhaust the pool. The following functions implement the custom memory pool management */ typedef struct { void *storage; /* Memory pool */ int storagePos, storageSize; /* Current usage and total size of pool */ } MEMPOOL_INFO; void initMemPool( void *statePtr, void *memPool, const int memPoolSize ) { MEMPOOL_INFO *state = ( MEMPOOL_INFO * ) statePtr; assert( isWritePtr( state, sizeof( MEMPOOL_INFO ) ) ); assert( isWritePtr( memPool, memPoolSize ) ); assert( memPoolSize >= 64 ); assert( sizeof( MEMPOOL_STATE ) >= sizeof( MEMPOOL_INFO ) ); memset( state, 0, sizeof( MEMPOOL_INFO ) ); state->storage = memPool; state->storageSize = memPoolSize; } void *getMemPool( void *statePtr, const int size ) { MEMPOOL_INFO *state = ( MEMPOOL_INFO * ) statePtr; BYTE *allocPtr = state->storage; const int allocSize = roundUp( size, sizeof( int ) ); assert( isWritePtr( state, sizeof( MEMPOOL_INFO ) ) ); assert( isWritePtr( state->storage, state->storageSize ) ); /* If we can't satisfy the request from the memory pool, we have to allocate it dynamically */ if( state->storagePos + allocSize > state->storageSize ) return( clDynAlloc( "getMemPool", size ) ); /* We can satisfy the request from the pool */ allocPtr += state->storagePos; state->storagePos += allocSize; return( allocPtr ); } void freeMemPool( void *statePtr, void *memblock ) { MEMPOOL_INFO *state = ( MEMPOOL_INFO * ) statePtr; assert( isWritePtr( state, sizeof( MEMPOOL_INFO ) ) ); assert( isWritePtr( state->storage, state->storageSize ) ); /* If the memory block is within the pool, there's nothing to do */ if( memblock >= state->storage && \ memblock < ( void * ) ( ( BYTE * ) state->storage + \ state->storageSize ) ) return; /* It's outside the pool and therefore dynamically allocated, free it */ clFree( "freeMemPool", memblock ); } /* Debugging malloc() that dumps memory usage diagnostics to stdout */ #ifdef CONFIG_DEBUG_MALLOC #ifdef __WIN32__ #include #endif /* __WIN32__ */ #ifdef __WINCE__ static int wcPrintf( const char *format, ... ) { wchar_t wcBuffer[ 1024 + 8 ]; char buffer[ 1024 + 8 ]; va_list argPtr; int length; va_start( argPtr, format ); length = vsprintf_s( buffer, 1024, format, argPtr ); va_end( argPtr ); mbstowcs( wcBuffer, buffer, length + 1 ); NKDbgPrintfW( wcBuffer ); return( length ); } #define printf wcPrintf #endif /* __WINCE__ */ static int clAllocIndex = 0; void *clAllocFn( const char *fileName, const char *fnName, const int lineNo, size_t size ) { char buffer[ 512 + 8 ]; BYTE *memPtr; int length; /* Strip off the leading path components if we can to reduce the amount of noise in the output */ #if defined( __WIN32__ ) || defined( __UNIX__ ) if( getcwd( buffer, 512 ) != NULL ) { const int pathLen = strlen( buffer ) + 1; /* Leading path + '/' */ assert( pathLen < strlen( fileName ) ); fileName += pathLen; } #endif /* __WIN32__ || __UNIX__ */ length = printf( "ALLOC: %s:%s:%d", fileName, fnName, lineNo ); while( length < 46 ) { putchar( ' ' ); length++; } printf( " %4d - %d bytes.\n", clAllocIndex, size ); if( ( memPtr = malloc( size + sizeof( LONG ) ) ) == NULL ) return( NULL ); mputLong( memPtr, clAllocIndex ); /* Implicit memPtr += sizeof( LONG ) */ clAllocIndex++; return( memPtr ); } void clFreeFn( const char *fileName, const char *fnName, const int lineNo, void *memblock ) { char buffer[ 512 + 8 ]; BYTE *memPtr = ( BYTE * ) memblock - sizeof( LONG ); int index; /* Strip off the leading path components if we can to reduce the amount of noise in the output */ #if defined( __WIN32__ ) || defined( __UNIX__ ) if( getcwd( buffer, 512 ) != NULL ) { const int pathLen = strlen( buffer ) + 1; /* Leading path + '/' */ assert( pathLen < strlen( fileName ) ); fileName += pathLen; } #endif /* __WIN32__ || __UNIX__ */ index = mgetLong( memPtr ); memPtr -= sizeof( LONG ); /* mgetLong() changes memPtr */ length = printf( "FREE : %s:%s:%d", fileName, fnName, lineNo ); while( length < 46 ) { putchar( ' ' ); length++; } printf( " %4d.\n", index ); free( memPtr ); } #endif /* CONFIG_DEBUG_MALLOC */ /**************************************************************************** * * * Stream Export/Import Routines * * * ****************************************************************************/ /* Export attribute or certificate data to a stream. In theory we would have to export this via a dynbuf and then write it to the stream, however we can save some overhead by writing it directly to the stream's buffer. Some attributes have a user-defined size (e.g. CRYPT_IATTRIBUTE_RANDOM_NONCE) so we allow the caller to specify an optional length parameter indicating how much of the attribute should be exported */ static int exportAttr( STREAM *stream, const CRYPT_HANDLE cryptHandle, const CRYPT_ATTRIBUTE_TYPE attributeType, const int length ) { MESSAGE_DATA msgData; int attrLength, status; assert( isWritePtr( stream, sizeof( STREAM ) ) ); assert( sStatusOK( stream ) ); assert( cryptHandle == SYSTEM_OBJECT_HANDLE || \ isHandleRangeValid( cryptHandle ) ); assert( isAttribute( attributeType ) || \ isInternalAttribute( attributeType ) ); assert( ( length == CRYPT_UNUSED ) || \ ( length >= 8 && length <= 16384 ) ); /* Before we try the export, make sure that everything is OK with the stream */ if( !sStatusOK( stream ) ) return( sGetStatus( stream ) ); if( length != CRYPT_UNUSED ) { /* It's an explicit-length attribute, make sure that there's enough room left in the stream for it */ if( sMemDataLeft( stream ) < length ) return( CRYPT_ERROR_OVERFLOW ); attrLength = length; } else { /* It's an implicit-length attribute whose maximum length is defined by the stream size */ attrLength = sMemDataLeft( stream ); } /* Export the attribute directly into the stream buffer */ setMessageData( &msgData, sMemBufPtr( stream ), attrLength ); status = krnlSendMessage( cryptHandle, IMESSAGE_GETATTRIBUTE_S, &msgData, attributeType ); if( cryptStatusOK( status ) ) status = sSkip( stream, msgData.length ); return( status ); } int exportAttributeToStream( void *streamPtr, const CRYPT_HANDLE cryptHandle, const CRYPT_ATTRIBUTE_TYPE attributeType ) { assert( isWritePtr( streamPtr, sizeof( STREAM ) ) ); assert( isHandleRangeValid( cryptHandle ) ); assert( isAttribute( attributeType ) || \ isInternalAttribute( attributeType ) ); return( exportAttr( streamPtr, cryptHandle, attributeType, \ CRYPT_UNUSED ) ); } int exportVarsizeAttributeToStream( void *streamPtr, const CRYPT_HANDLE cryptHandle, const CRYPT_ATTRIBUTE_TYPE attributeType, const int attributeDataLength ) { assert( isWritePtr( streamPtr, sizeof( STREAM ) ) ); assert( cryptHandle == SYSTEM_OBJECT_HANDLE ); assert( attributeType == CRYPT_IATTRIBUTE_RANDOM_NONCE ); assert( attributeDataLength >= 8 && attributeDataLength <= 1024 ); return( exportAttr( streamPtr, cryptHandle, attributeType, attributeDataLength ) ); } int exportCertToStream( void *streamPtr, const CRYPT_CERTIFICATE cryptCertificate, const CRYPT_CERTFORMAT_TYPE certFormatType ) { MESSAGE_DATA msgData; STREAM *stream = streamPtr; int status; assert( isWritePtr( stream, sizeof( STREAM ) ) ); assert( sStatusOK( stream ) ); assert( isHandleRangeValid( cryptCertificate ) ); assert( certFormatType > CRYPT_CERTFORMAT_NONE && \ certFormatType < CRYPT_CERTFORMAT_LAST ); /* Before we try the export, make sure that everything is OK with the stream */ if( !sStatusOK( stream ) ) return( sGetStatus( stream ) ); if( !sIsNullStream( stream ) && \ sMemDataLeft( stream ) < MIN_CRYPT_OBJECTSIZE ) return( CRYPT_ERROR_UNDERFLOW ); /* Export the cert directly into the stream buffer */ setMessageData( &msgData, sMemBufPtr( stream ), sMemDataLeft( stream ) ); status = krnlSendMessage( cryptCertificate, IMESSAGE_CRT_EXPORT, &msgData, certFormatType ); if( cryptStatusOK( status ) ) status = sSkip( stream, msgData.length ); return( status ); } int importCertFromStream( void *streamPtr, CRYPT_CERTIFICATE *cryptCertificate, const CRYPT_CERTTYPE_TYPE certType, const int certDataLength ) { MESSAGE_CREATEOBJECT_INFO createInfo; STREAM *stream = streamPtr; int status; assert( isWritePtr( stream, sizeof( STREAM ) ) ); assert( sStatusOK( stream ) ); assert( isWritePtr( cryptCertificate, sizeof( CRYPT_CERTIFICATE ) ) ); assert( certDataLength > 0 && certDataLength < INT_MAX ); assert( certType > CRYPT_CERTTYPE_NONE && \ certType < CRYPT_CERTTYPE_LAST ); /* Clear return value */ *cryptCertificate = CRYPT_ERROR; /* Before we try the import, make sure that everything is OK with the stream and parameters */ if( !sStatusOK( stream ) ) return( sGetStatus( stream ) ); if( sMemDataLeft( stream ) < MIN_CRYPT_OBJECTSIZE || \ certDataLength > sMemDataLeft( stream ) ) return( CRYPT_ERROR_UNDERFLOW ); /* Export the cert directly from the stream buffer */ setMessageCreateObjectIndirectInfo( &createInfo, sMemBufPtr( stream ), certDataLength, certType ); status = krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_DEV_CREATEOBJECT_INDIRECT, &createInfo, OBJECT_TYPE_CERTIFICATE ); if( cryptStatusOK( status ) ) { status = sSkip( stream, certDataLength ); if( cryptStatusOK( status ) ) *cryptCertificate = createInfo.cryptHandle; else krnlSendNotifier( createInfo.cryptHandle, IMESSAGE_DECREFCOUNT ); } return( status ); } /**************************************************************************** * * * Safe Text-line Read Functions * * * ****************************************************************************/ /* Read a line of text data ending in an EOL. When we read data we're mostly looking for the EOL marker. If we find more data than will fit in the input buffer, we discard it until we find an EOL. As a secondary concern, we want to strip leading, trailing, and repeated whitespace. We handle the former by setting the seen-whitespace flag to true initially, this treats any whitespace at the start of the line as superfluous and strips it. We also handle continued lines, denoted by a semicolon or occasionally a backslash as the last non-whitespace character. Stripping of repeated whitespace is also handled by the seenWhitespace flag, stripping of trailing whitespace is handled by walking back through any final whitespace once we see the EOL, and continued lines are handled by setting the seenContinuation flag if we see a semicolon or backslash as the last non-whitespace character. Finally, we also need to handle generic DoS attacks. If we see more than MAX_LINE_LENGTH chars in a line, we bail out */ #define MAX_LINE_LENGTH 4096 static int retTextLineError( STREAM *stream, const int status, const char *format, const int value1, int value2 ) { assert( isWritePtr( stream, sizeof( STREAM ) ) ); assert( isReadPtr( format, 4 ) ); /* Currently only network streams can report extended error information, so if this is a non-network stream we don't try anything further. Note that we can't check for the absence of the error-info structure because that's not present in some builds */ if( stream->type != STREAM_TYPE_NETWORK ) return( status ); /* This extra level of indirection is necessary to turn the opaque stream pointer back into a STREAM structure for use by the retExt() macro. Normally this is handled via the STREAM_ERRINFO_VOID macro, but in this case the stream really is being used as an I/O stream (rather than just an errorInfo container) so we can't use this macro here */ retExt( STREAM_ERRINFO, CRYPT_ERROR_BADDATA, format, value1, value2 ); } int readTextLine( READCHARFUNCTION readCharFunction, void *streamPtr, char *buffer, const int maxSize, BOOLEAN *localError ) { BOOLEAN seenWhitespace, seenContinuation = FALSE; int totalChars, bufPos = 0; assert( isWritePtr( streamPtr, sizeof( STREAM ) ) ); assert( maxSize > 16 ); assert( isWritePtr( buffer, maxSize ) ); assert( localError == NULL || \ isWritePtr( localError, sizeof( BOOLEAN ) ) ); /* Clear return value */ if( localError != NULL ) *localError = FALSE; /* Set the seen-whitespace flag initially to strip leading whitespace */ seenWhitespace = TRUE; /* Read up to MAX_LINE_LENGTH chars. Anything longer than this is probably a DoS */ for( totalChars = 0; totalChars < MAX_LINE_LENGTH; totalChars++ ) { int ch; /* Get the next input character */ ch = readCharFunction( streamPtr ); if( cryptStatusError( ch ) ) return( ch ); /* If we're over the maximum buffer size, the only character that we recognise is EOL */ if( ( bufPos > maxSize - 8 ) && ( ch != '\n' ) ) { /* If we've run off into the weeds (for example we're reading binary data following the text header), bail out */ if( !isPrint( ch ) && ch != '\r' ) { if( localError != NULL ) *localError = TRUE; return( retTextLineError( streamPtr, CRYPT_ERROR_BADDATA, "Invalid character 0x%02X at " "position %d", ch, totalChars ) ); } continue; } /* Process EOL */ if( ch == '\n' ) { /* Strip trailing whitespace. At this point it's all been canonicalised so we don't need to check for anything other than spaces */ while( bufPos > 0 && buffer[ bufPos - 1 ] == ' ' ) bufPos--; /* If we've seen a continuation marker as the last non- whitespace char, the line continues on the next one */ if( seenContinuation ) { seenContinuation = FALSE; continue; } /* We're done */ buffer[ bufPos ] = '\0'; break; } /* Ignore any additional decoration that may accompany EOLs */ if( ch == '\r' ) continue; /* Process whitespace. We can't use isspace() for this because it includes all sorts of extra control characters */ if( ch == ' ' || ch == '\t' ) { if( seenWhitespace ) /* Ignore leading and repeated whitespace */ continue; ch = ' '; /* Canonicalise whitespace */ } /* Process any remaining chars */ if( !( isPrint( ch ) ) ) { if( localError != NULL ) *localError = TRUE; return( retTextLineError( streamPtr, CRYPT_ERROR_BADDATA, "Invalid character 0x%02X at " "position %d", ch, totalChars ) ); } buffer[ bufPos++ ] = ch; seenWhitespace = ( ch == ' ' ) ? TRUE : FALSE; seenContinuation = ( ch == ';' || ch == '\\' || \ ( seenContinuation && \ seenWhitespace ) ) ? \ TRUE : FALSE; } if( totalChars >= MAX_LINE_LENGTH ) { if( localError != NULL ) *localError = TRUE; return( retTextLineError( streamPtr, CRYPT_ERROR_OVERFLOW, "Text line too long", 0, 0 ) ); } return( bufPos ); }