/**************************************************************************** * * * cryptlib Base64 Routines * * Copyright Peter Gutmann 1992-2006 * * * ****************************************************************************/ #if defined( INC_ALL ) #include "crypt.h" #include "stream.h" #include "asn1.h" #else #include "crypt.h" #include "io/stream.h" #include "misc/asn1.h" #endif /* Compiler-specific includes */ /* Base64 encode/decode tables from RFC 1113 */ #define BPAD '=' /* Padding for odd-sized output */ #define BERR 0xFF /* Illegal char marker */ #define BEOF 0x7F /* EOF marker (padding char or EOL) */ static const char FAR_BSS binToAscii[] = \ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const BYTE FAR_BSS asciiToBin[ 256 ] = { BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, /* 00 */ BERR, BERR, BEOF, BERR, BERR, BEOF, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, /* 10 */ BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, /* 20 */ BERR, BERR, BERR, 0x3E, BERR, BERR, BERR, 0x3F, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, /* 30 */ 0x3C, 0x3D, BERR, BERR, BERR, BEOF, BERR, BERR, BERR, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* 40 */ 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* 50 */ 0x17, 0x18, 0x19, BERR, BERR, BERR, BERR, BERR, BERR, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, /* 60 */ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, /* 70 */ 0x31, 0x32, 0x33, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, /* 80 */ BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, /* 90 */ BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, /* A0 */ BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, /* B0 */ BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, /* C0 */ BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, /* D0 */ BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, /* E0 */ BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR, /* F0 */ BERR, BERR, BERR, BERR, BERR, BERR, BERR, BERR }; /* The size of lines for PEM-type formatting. This is only used for encoding, for decoding we adjust to whatever size the sender has used */ #define TEXT_LINESIZE 64 #define BINARY_LINESIZE 48 /* Basic single-char en/decode functions. We mask the value to 8 bits to avoid generating negative array offsets if the sign bit is set, since the strings are passed as char *'s */ #define encode(data) binToAscii[ ( data ) & 0xFF ] #define decode(data) asciiToBin[ ( data ) & 0xFF ] /* The headers and trailers used for base64-encoded certificate objects */ typedef struct { const CRYPT_CERTTYPE_TYPE type; const char FAR_BSS *header, FAR_BSS *trailer; } HEADER_INFO; static const HEADER_INFO FAR_BSS headerInfo[] = { { CRYPT_CERTTYPE_CERTIFICATE, "-----BEGIN CERTIFICATE-----" EOL, "-----END CERTIFICATE-----" EOL }, { CRYPT_CERTTYPE_ATTRIBUTE_CERT, "-----BEGIN ATTRIBUTE CERTIFICATE-----" EOL, "-----END ATTRIBUTE CERTIFICATE-----" EOL }, { CRYPT_CERTTYPE_CERTCHAIN, "-----BEGIN CERTIFICATE CHAIN-----" EOL, "-----END CERTIFICATE CHAIN-----" EOL }, { CRYPT_CERTTYPE_CERTREQUEST, "-----BEGIN NEW CERTIFICATE REQUEST-----" EOL, "-----END NEW CERTIFICATE REQUEST-----" EOL }, { CRYPT_CERTTYPE_REQUEST_CERT, "-----BEGIN NEW CERTIFICATE REQUEST-----" EOL, "-----END NEW CERTIFICATE REQUEST-----" EOL }, { CRYPT_CERTTYPE_CRL, "-----BEGIN CERTIFICATE REVOCATION LIST-----" EOL, "-----END CERTIFICATE REVOCATION LIST-----" EOL }, { CRYPT_CERTTYPE_NONE, /* Universal catch-all */ "-----BEGIN CERTIFICATE OBJECT-----" EOL, "-----END CERTIFICATE OBJECT-----" EOL }, { CRYPT_CERTTYPE_NONE, /* Universal catch-all */ "-----BEGIN CERTIFICATE OBJECT-----" EOL, "-----END CERTIFICATE OBJECT-----" EOL } }; /**************************************************************************** * * * Utility Functions * * * ****************************************************************************/ /* Callback function used by readTextLine() to read characters from a stream */ static int readCharFunction( void *streamPtr ) { return( sgetc( streamPtr ) ); } /* Check for raw base64 data. There isn't a 100% reliable check for this, but if the first 60 chars (the minimum base64 line length) are all valid base64 chars and the first chars match the required values then it's reasonably certain that it's base64 data */ static BOOLEAN checkBase64( STREAM *stream ) { BYTE buffer[ 4 + 8 ], headerBuffer[ 2 + 8 ]; BOOLEAN gotHeader = FALSE; int i, status; assert( isWritePtr( stream, sizeof( STREAM ) ) ); /* Make sure that there's enough data present to perform a reliable check */ if( sMemDataLeft( stream ) < 15 * 4 ) return( FALSE ); /* Check that we have at least 15 lots (60 chars) of base64-encoded data */ for( i = 0; i < 15; i++ ) { BYTE c0, c1, c2, c3, cx; status = sread( stream, buffer, 4 ); if( cryptStatusError( status ) ) return( FALSE ); if( !gotHeader ) { memcpy( headerBuffer, buffer, 2 ); gotHeader = TRUE; } c0 = decode( buffer[ 0 ] ); c1 = decode( buffer[ 1 ] ); c2 = decode( buffer[ 2 ] ); c3 = decode( buffer[ 3 ] ); cx = c0 | c1 | c2 | c3; if( cx == BEOF || cx == BERR ) return( FALSE ); } /* Make sure that the content is some form of encoded key or cert data. For cert data that begins with 30 8x, the corresponding base64 values are MI...; for an SSH public key that begins 00 00 it's AA...; for a PGP public key that begins 99 0x it's mQ... */ if( strCompare( headerBuffer, "MI", 2 ) && \ strCompare( headerBuffer, "AA", 2 ) && \ strCompare( headerBuffer, "mQ", 2 ) ) return( FALSE ); return( TRUE ); } /* Check for PEM-encapsulated data. All that we need to look for is the '-----..' header, which is fairly simple although we also need to handle the SSH '---- ...' variant (4 dashes and a space) */ static int checkPEMHeader( STREAM *stream ) { BOOLEAN isSSH = FALSE, isPGP = FALSE; char buffer[ 1024 + 8 ], *bufPtr = buffer; int length, iterationCount = 0; assert( isWritePtr( stream, sizeof( STREAM ) ) ); /* Check for the initial 5 dashes and 'BEGIN ' (unless we're SSH, in which case we use 4 dashes, a space, and 'BEGIN ') */ length = readTextLine( readCharFunction, stream, buffer, 1024, NULL ); if( cryptStatusError( length ) ) return( length ); if( length < 11 + 5 || \ strCompare( bufPtr, "-----BEGIN ", 11 ) && \ strCompare( bufPtr, "---- BEGIN ", 11 ) ) return( CRYPT_ERROR_BADDATA ); bufPtr += 11; length -= 11; /* Skip the object name */ if( !strCompare( bufPtr, "SSH2 ", 5 ) ) isSSH = TRUE; else if( !strCompare( bufPtr, "PGP ", 4 ) ) isPGP = TRUE; while( length >= 4 ) { if( *bufPtr == '-' ) break; bufPtr++; length--; } if( length != 5 && length != 4 ) return( CRYPT_ERROR_BADDATA ); /* Check the the trailing 5 (4 for SSH) dashes */ if( strCompare( bufPtr, "-----", length ) ) return( CRYPT_ERROR_BADDATA ); /* At this point SSH and PGP can continue with an arbitrary number of type : value pairs that we have to strip before we get to the payload */ if( isSSH ) { int position, i; /* SSH runs the header straight into the body so the only way to tell whether we've hit the body is to check for the absence of the ':' separator */ do { position = stell( stream ); length = readTextLine( readCharFunction, stream, buffer, 1024, NULL ); if( cryptStatusError( length ) ) return( length ); for( i = 0; i < length && buffer[ i ] != ':'; i++ ); } while( i < length && iterationCount++ < FAILSAFE_ITERATIONS_LARGE ); if( iterationCount >= FAILSAFE_ITERATIONS_LARGE ) return( CRYPT_ERROR_BADDATA ); sseek( stream, position ); } if( isPGP ) { /* PGP uses a conventional header format with a blank line as the delimiter so all that we have to do is look for a zero-length line */ do { length = readTextLine( readCharFunction, stream, buffer, 1024, NULL ); if( cryptStatusError( length ) ) return( length ); } while( length > 0 && iterationCount++ < FAILSAFE_ITERATIONS_LARGE ); if( iterationCount >= FAILSAFE_ITERATIONS_LARGE ) return( CRYPT_ERROR_BADDATA ); } return( stell( stream ) ); } /* Look for the EOL marker at the end of a line of text. There's one problematic special case here where, if the encoding has produced bricktext, the end of the data will coincide with the EOL. For CRYPT_CERTFORMAT_TEXT_CERTIFICATE this will give us '-----END...' on the next line which is easy to check for, but for CRYPT_ICERTFORMAT_SMIME_CERTIFICATE what we end up with depends on the calling code, it could truncate immediately at the end of the data (which it isn't supposed to) so we get '\0', it could truncate after the EOL (so we get EOL + '\0'), it could continue with a futher content type after a blank line (so we get EOL + EOL), or it could truncate without the '\0' so we get garbage, which is the caller's problem. Because of this we look for all of these situations and, if any are found, return a 0-count EOL indicator */ static int checkEOL( const char *src, const int srcLen, const CRYPT_CERTFORMAT_TYPE format ) { int srcIndex = 0; assert( isReadPtr( src, srcLen ) ); /* Check for a '\0' at the end of the data */ if( format == CRYPT_ICERTFORMAT_SMIME_CERTIFICATE && !*src ) return( 0 ); /* Check for EOL */ if( *src == '\n' ) srcIndex++; else { if( *src == '\r' ) { srcIndex++; /* Some broken implementations emit two CRs before the LF. Stripping these extra CRs clashes with other broken implementations that emit only CRs, which means that we'll be stripping the EOT blank line in MIME encapsulation, however the two-CR bug (usually from older versions of Netscape) appears to be more prevalent than the CR-only bug (old Mac software) */ if( ( srcIndex < srcLen ) && src[ srcIndex ] == '\r' ) srcIndex++; if( ( srcIndex < srcLen ) && src[ srcIndex ] == '\n' ) srcIndex++; } } if( srcIndex >= srcLen ) return( 0 ); assert( srcIndex < srcLen ); /* Check for '\0' or EOL (S/MIME) or '----END...' (PEM) after EOL */ if( format == CRYPT_ICERTFORMAT_SMIME_CERTIFICATE && \ ( !src[ srcIndex ] || src[ srcIndex ] == '\n' || \ src[ srcIndex ] == '\r' ) ) return( 0 ); if( format == CRYPT_CERTFORMAT_TEXT_CERTIFICATE && \ srcLen - srcIndex >= 9 && \ !strCompare( src + srcIndex, "-----END ", 9 ) ) return( 0 ); /* Make sure that we haven't run off into the weeds */ if( srcIndex >= srcLen ) return( 0 ); return( srcIndex ); } /* Decode a chunk of four base64 characters into three binary characters */ static int decodeBase64chunk( BYTE *dest, const int destLeft, const char *src, const int srcLeft, const BOOLEAN fixedLenData ) { static const int outByteTbl[] = { 0, 0, 1, 2, 3 }; BYTE c0, c1, c2 = 0, c3 = 0, cx; int srcIndex = 0, destIndex = 0, outByteCount; /* Make sure that there's sufficient input left to decode. We need at least two more characters to produce one byte of output */ if( srcLeft < 2 ) return( CRYPT_ERROR_UNDERFLOW ); /* Decode a block of data from the input buffer */ c0 = decode( src[ srcIndex++ ] ); c1 = decode( src[ srcIndex++ ] ); if( srcLeft > 2 ) { c2 = decode( src[ srcIndex++ ] ); if( srcLeft > 3 ) c3 = decode( src[ srcIndex++ ] ); } cx = c0 | c1 | c2 | c3; if( cx == BERR || cx == BEOF ) { /* If we're decoding fixed-length data and the decoding produces an invalid character or an EOF, there's a problem with the input */ if( fixedLenData ) return( CRYPT_ERROR_BADDATA ); /* We're decoding indefinite-length data for which EOF's are valid characters. We have to be a bit careful with the order of checking since hitting an EOF at an earlier character may cause later chars to be decoded as BERR */ if( c0 == BEOF ) /* No more input, we're done */ return( 0 ); if( c0 == BERR || c1 == BEOF || c1 == BERR ) /* We can't produce output with only one char of input, there's a problem with the input */ return( CRYPT_ERROR_BADDATA ); if( c2 == BEOF ) /* Two chars of input, then EOF, resulting in one char of output */ outByteCount = 1; else { if( c2 == BERR || c3 == BERR ) return( CRYPT_ERROR_BADDATA ); assert( c3 == BEOF ); outByteCount = 2; } } else /* All decoded characters are valid */ outByteCount = outByteTbl[ min( srcLeft, 4 ) ]; /* Make sure that there's sufficient space to copy out the decoded bytes */ if( outByteCount > destLeft ) return( CRYPT_ERROR_OVERFLOW ); /* Copy the decoded data to the output buffer */ dest[ destIndex++ ] = ( c0 << 2 ) | ( c1 >> 4 ); if( outByteCount > 1 ) { dest[ destIndex++ ] = ( c1 << 4 ) | ( c2 >> 2); if( outByteCount > 2 ) dest[ destIndex++ ] = ( c2 << 6 ) | ( c3 ); } return( outByteCount ); } /**************************************************************************** * * * Base64 En/Decoding Functions * * * ****************************************************************************/ /* Check whether a data item has a header that identifies it as some form of encoded object and return the start position of the encoded data. For S/MIME certificate data this can in theory get quite complex because there are many possible variations in the headers. Some early S/MIME agents used a content type of "application/x-pkcs7-mime", "application/x-pkcs7-signature", and "application/x-pkcs10", while newer ones use the same without the "x-" at the start. In addition Netscape have their own MIME data types for certificates, "application/x-x509-" "{user-cert|ca-cert|email-cert}, and there are further types in the endless stream of RFCs that PKIX churns out. There are a whole pile of other possible headers as well, none of them terribly relevant for our purposes, so all we check for is the base64 indicator */ int base64checkHeader( const char *data, const int dataLength, int *startPos ) { STREAM stream; BOOLEAN seenTransferEncoding = FALSE, isBinaryEncoding = FALSE; int position, ch, iterationCount, status; assert( isReadPtr( data, dataLength ) ); assert( isWritePtr( startPos, sizeof( int ) ) ); /* Clear return value */ *startPos = 0; /* If the item is too small to contain any useful data, we don't even try and examine it. We don't treat this as a data or underflow error since it may be a short but valid data object like an empty CRL */ if( dataLength < 64 ) return( CRYPT_CERTFORMAT_NONE ); sMemConnect( &stream, data, dataLength ); /* Sometimes the object can be preceded by a few blank lines. We're fairly lenient with this. Note that we can't use readTextLine() at this point because we don't know yet whether we're getting binary or ASCII data */ iterationCount = 0; do ch = sgetc( &stream ); while( ch == '\r' || ch == '\n' && \ iterationCount++ < FAILSAFE_ITERATIONS_LARGE ); if( iterationCount >= FAILSAFE_ITERATIONS_LARGE ) return( CRYPT_ERROR_BADDATA ); if( cryptStatusError( ch ) ) { sMemDisconnect( &stream ); return( ch ); } position = stell( &stream ) - 1; sseek( &stream, position ); /* Perform a quick check to weed out unencoded cert data, which is usually the case */ status = readSequenceI( &stream, NULL ); if( cryptStatusOK( status ) ) { sMemDisconnect( &stream ); return( CRYPT_CERTFORMAT_NONE ); } sClearError( &stream ); sseek( &stream, position ); /* If it starts with a dash, check for PEM header encapsulation */ if( ch == '-' ) { position = checkPEMHeader( &stream ); if( cryptStatusError( position ) ) { sMemDisconnect( &stream ); return( position ); } if( checkBase64( &stream ) ) { sMemDisconnect( &stream ); *startPos = position; return( CRYPT_CERTFORMAT_TEXT_CERTIFICATE ); } sMemDisconnect( &stream ); return( CRYPT_ERROR_BADDATA ); } /* Check for raw base64 data */ if( checkBase64( &stream ) ) { sMemDisconnect( &stream ); *startPos = position; return( CRYPT_CERTFORMAT_TEXT_CERTIFICATE ); } sseek( &stream, position ); /* It doesn't look like raw base64, check for an S/MIME header */ iterationCount = 0; do { char buffer[ 1024 + 8 ]; status = readTextLine( readCharFunction, &stream, buffer, 1024, NULL ); if( !cryptStatusError( status ) && status >= 33 && \ !strCompare( buffer, "Content-Transfer-Encoding:", 26 ) ) { const int length = status; int index; /* Check for a valid content encoding type */ for( index = 26; index < length && buffer[ index ] == ' '; index++ ); if( length - index < 6 ) /* It's too short to be a valid encoding type, skip it */ continue; if( !strCompare( buffer + index, "base64", 6 ) ) seenTransferEncoding = TRUE; else if( !strCompare( buffer + index, "binary", 6 ) ) seenTransferEncoding = isBinaryEncoding = TRUE; } } while( status > 0 && iterationCount++ < FAILSAFE_ITERATIONS_LARGE ); if( iterationCount >= FAILSAFE_ITERATIONS_LARGE ) return( CRYPT_ERROR_BADDATA ); if( cryptStatusError( status ) || !seenTransferEncoding ) { sMemDisconnect( &stream ); return( cryptStatusError( status ) ? status : CRYPT_ERROR_BADDATA ); } /* Skip trailing blank lines */ iterationCount = 0; do ch = sgetc( &stream ); while( ch == '\r' || ch == '\n' && \ iterationCount++ < FAILSAFE_ITERATIONS_LARGE ); if( iterationCount >= FAILSAFE_ITERATIONS_LARGE ) return( CRYPT_ERROR_BADDATA ); if( cryptStatusError( ch ) ) { sMemDisconnect( &stream ); return( ch ); } position = stell( &stream ) - 1; /* Make sure that the content is some form of encoded cert */ *startPos = position; if( isBinaryEncoding ) status = CRYPT_CERTFORMAT_CERTIFICATE; else { sseek( &stream, position ); status = checkBase64( &stream ) ? CRYPT_ICERTFORMAT_SMIME_CERTIFICATE : \ CRYPT_ERROR_BADDATA; } sMemDisconnect( &stream ); return( status ); } /* Encode a block of binary data into the base64 format, returning the total number of output bytes */ int base64encode( char *dest, const int destMaxLen, const void *src, const int srcLen, const CRYPT_CERTTYPE_TYPE certType ) { const BYTE *srcPtr = src; int srcIndex = 0, destIndex = 0, lineByteCount = 0; int remainder = srcLen % 3, headerInfoIndex; assert( destMaxLen > 10 && isWritePtr( dest, destMaxLen ) ); assert( srcLen > 10 && isReadPtr( src, srcLen ) ); /* If it's a certificate object, add the header */ if( certType != CRYPT_CERTTYPE_NONE ) { for( headerInfoIndex = 0; headerInfo[ headerInfoIndex ].type != certType && \ headerInfo[ headerInfoIndex ].type != CRYPT_CERTTYPE_NONE && \ headerInfoIndex < FAILSAFE_ARRAYSIZE( headerInfo, HEADER_INFO ); headerInfoIndex++ ); if( headerInfoIndex >= FAILSAFE_ARRAYSIZE( headerInfo, HEADER_INFO ) ) retIntError(); assert( headerInfo[ headerInfoIndex ].type != CRYPT_CERTTYPE_NONE ); destIndex = strlen( headerInfo[ headerInfoIndex ].header ); if( destIndex >= destMaxLen ) return( CRYPT_ERROR_OVERFLOW ); memcpy( dest, headerInfo[ headerInfoIndex ].header, destIndex ); } /* Encode the data */ while( srcIndex < srcLen ) { const int srcLeft = srcLen - srcIndex; /* If we've reached the end of a line of binary data and it's a certificate, add the EOL marker */ if( certType != CRYPT_CERTTYPE_NONE && \ lineByteCount >= BINARY_LINESIZE ) { if( destIndex + EOL_LEN >= destMaxLen ) return( CRYPT_ERROR_OVERFLOW ); memcpy( dest + destIndex, EOL, EOL_LEN ); destIndex += EOL_LEN; lineByteCount = 0; } /* Encode a block of data from the input buffer */ if( destIndex + 4 >= destMaxLen ) return( CRYPT_ERROR_OVERFLOW ); dest[ destIndex++ ] = encode( srcPtr[ srcIndex ] >> 2 ); if( srcLeft < 2 ) { assert( remainder == 1 ); dest[ destIndex++ ] = encode( ( srcPtr[ srcIndex ] << 4 ) & 0x30 ); break; } dest[ destIndex++ ] = encode( ( ( srcPtr[ srcIndex ] << 4 ) & 0x30 ) | \ ( ( srcPtr[ srcIndex + 1 ] >> 4 ) & 0x0F ) ); srcIndex++; if( srcLeft < 3 ) { assert( remainder == 2 ); dest[ destIndex++ ] = encode( ( srcPtr[ srcIndex ] << 2 ) & 0x3C ); break; } dest[ destIndex++ ] = encode( ( ( srcPtr[ srcIndex ] << 2 ) & 0x3C ) | \ ( ( srcPtr[ srcIndex + 1 ] >> 6 ) & 0x03 ) ); srcIndex++; dest[ destIndex++ ] = encode( srcPtr[ srcIndex++ ] & 0x3F ); lineByteCount += 3; } /* Add padding if it's not raw base64 data. For 0 bytes remainder there's no padding (the data fits exactly), for 1 byte remainder there's 2 bytes padding ("X=="), and for 2 bytes remainder there's 1 byte padding ("XX=") */ if( certType != CRYPT_CERTTYPE_NONE && remainder > 0 ) { dest[ destIndex++ ] = BPAD; if( remainder == 1 ) dest[ destIndex++ ] = BPAD; } /* If it's a certificate object, add the trailer */ if( certType != CRYPT_CERTTYPE_NONE ) { const int length = strlen( headerInfo[ headerInfoIndex ].trailer ); if( destIndex + EOL_LEN + length > destMaxLen ) return( CRYPT_ERROR_OVERFLOW ); memcpy( dest + destIndex, EOL, EOL_LEN ); memcpy( dest + destIndex + EOL_LEN, headerInfo[ headerInfoIndex ].trailer, length ); destIndex += EOL_LEN + length; } /* Return a count of encoded bytes */ return( destIndex ); } /* Decode a block of binary data from the base64 format, returning the total number of decoded bytes */ static int fixedBase64decode( BYTE *dest, const int destMaxLen, const char *src, const int srcLen ) { int srcIndex = 0, destIndex = 0; /* Decode the base64 string as a fixed-length continuous string without padding or newlines */ while( srcIndex < srcLen ) { int status; status = decodeBase64chunk( dest + destIndex, destMaxLen - destIndex, src + srcIndex, srcLen - srcIndex, TRUE ); if( cryptStatusError( status ) ) return( status ); srcIndex += 4; destIndex += status; } /* Return a count of decoded bytes */ return( destIndex ); } int base64decode( void *dest, const int destMaxLen, const char *src, const int srcLen, const CRYPT_CERTFORMAT_TYPE format ) { int srcIndex = 0, destIndex = 0, lineByteCount = 0, lineSize = 0; BYTE *destPtr = dest; assert( destMaxLen > 10 && isWritePtr( dest, destMaxLen ) ); assert( srcLen > 10 && isReadPtr( src, srcLen ) ); /* If it's not a certificate, it's a straight base64 string and we can use the simplified decoding routines */ if( format == CRYPT_CERTFORMAT_NONE ) return( fixedBase64decode( dest, destMaxLen, src, srcLen ) ); /* Decode the encoded object */ while( srcIndex < srcLen ) { int status; /* Depending on implementations, the length of the base64-encoded line can vary from 60 to 72 chars. We adjust for this by checking for the first EOL and setting the line length to the size of the first line of base64 text */ if( lineSize <= 0 && \ src[ srcIndex ] == '\r' || src[ srcIndex ] == '\n' ) { if( lineByteCount < 56 ) /* Suspiciously short text line */ return( CRYPT_ERROR_BADDATA ); lineSize = lineByteCount; } /* If we've reached the end of a line of text, look for the EOL marker */ if( lineSize > 0 && lineByteCount >= lineSize ) { status = checkEOL( src + srcIndex, srcLen - srcIndex, format ); if( status <= 0 ) break; /* End of input reached, exit */ srcIndex += status; lineByteCount = 0; } /* Decode a chunk of data from the input buffer */ status = decodeBase64chunk( destPtr + destIndex, destMaxLen - destIndex, src + srcIndex, srcLen - srcIndex, FALSE ); if( cryptStatusError( status ) ) return( status ); destIndex += status; if( status < 3 ) /* We've reached the end marker on the input data, exit. Note that we can't just wait for srcIndex to pass srcLen as for the fixed-length decode because there could be extra trailer data following the base64 data. In theory we could call checkEOL() here to make sure that the trailer is well-formed, but if the data is truncated right on the bas64 end marker then this would produce an error, so we just stop decoding as soon as we find the end marker */ break; srcIndex += 4; lineByteCount += 4; } /* Return a count of decoded bytes */ return( destIndex ); } /* Calculate the size of a quantity of data once it's en/decoded */ int base64decodeLen( const char *data, const int dataLength ) { STREAM stream; int ch, length, iterationCount = 0; assert( isReadPtr( data, dataLength ) ); /* Skip ahead until we find the end of the decodable data. Note that this ignores errors on the input stream since at this point all that we're interested in is how much we can decode from it, not whether it's valid or not */ sMemConnect( &stream, data, dataLength ); do { ch = sgetc( &stream ); if( cryptStatusError( ch ) || ch == BPAD ) break; ch = decode( ch ); } while( ch != BERR && iterationCount++ < FAILSAFE_ITERATIONS_MAX ); if( iterationCount >= FAILSAFE_ITERATIONS_MAX ) retIntError(); length = stell( &stream ); sMemDisconnect( &stream ); /* Return a rough estimate of how much room the decoded data will occupy. This ignores the EOL size so it always overestimates, but a strict value isn't necessary since it's only used for memory buffer allocation */ return( ( length * 3 ) / 4 ); } int base64encodeLen( const int dataLength, const CRYPT_CERTTYPE_TYPE certType ) { int length = roundUp( ( dataLength * 4 ) / 3, 4 ), headerInfoIndex; for( headerInfoIndex = 0; headerInfo[ headerInfoIndex ].type != certType && \ headerInfo[ headerInfoIndex ].type != CRYPT_CERTTYPE_NONE && \ headerInfoIndex < FAILSAFE_ARRAYSIZE( headerInfo, HEADER_INFO ); headerInfoIndex++ ); if( headerInfoIndex >= FAILSAFE_ARRAYSIZE( headerInfo, HEADER_INFO ) ) retIntError(); assert( headerInfo[ headerInfoIndex ].type != CRYPT_CERTTYPE_NONE ); /* Calculate the extra length due to EOL's */ length += ( ( roundUp( length, TEXT_LINESIZE ) / TEXT_LINESIZE ) * EOL_LEN ); /* Return the total length due to delimiters */ return( strlen( headerInfo[ headerInfoIndex ].header ) + length + \ strlen( headerInfo[ headerInfoIndex ].trailer ) ); } /**************************************************************************** * * * PKI User ID En/Decoding Functions * * * ****************************************************************************/ /* En/decode text representations of binary keys */ static const char codeTable[] = \ "ABCDEFGHJKLMNPQRSTUVWXYZ23456789____"; /* No O/0, I/1 */ static const int hiMask[] = { 0x00, 0x00, 0x00, 0x00, 0x0F, 0x07, 0x03, 0x01 }; static const int loMask[] = { 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xF0 }; BOOLEAN isPKIUserValue( const char *encVal, const int encValLength ) { int i = 0; assert( isReadPtr( encVal, encValLength ) ); /* Check whether a user value is of the form XXXXX-XXXXX-XXXXX{-XXXXX} */ if( ( encValLength != ( 3 * 5 ) + 2 ) && \ ( encValLength != ( 4 * 5 ) + 3 ) ) return( FALSE ); while( i < encValLength ) { int j; /* Decode each character group. We know from the length check above that this won't run off the end of the data, so we don't have to check the index value */ for( j = 0; j < 5; j++ ) { const int ch = encVal[ i++ ]; if( !isAlnum( ch ) ) return( FALSE ); } if( i < encValLength && encVal[ i++ ] != '-' ) return( FALSE ); } return( TRUE ); } int adjustPKIUserValue( BYTE *value, const int noCodeGroups ) { const int noBits = noCodeGroups * 25; const int length = ( roundUp( noBits, 8 ) / 8 ) - 1; assert( isWritePtr( value, roundUp( noCodeGroups * 25, 8 ) / 8 ) ); /* Mask off any bits at the end of the data that can't be encoded using the given number of code groups */ value[ length - 1 ] &= 0xFF << ( 8 - ( noBits % 8 ) ); return( length ); } int encodePKIUserValue( char *encVal, const int encValMaxLen, const BYTE *value, const int noCodeGroups ) { BYTE valBuf[ 128 + 8 ]; const int dataBytes = ( roundUp( noCodeGroups * 25, 8 ) / 8 ); int i, byteCount = 0, bitCount = 0, length; assert( isReadPtr( value, dataBytes ) ); assert( dataBytes < 128 ); /* Copy across the data bytes, leaving a gap at the start for the checksum */ memcpy( valBuf + 1, value, dataBytes ); length = adjustPKIUserValue( valBuf + 1, noCodeGroups ) + 1; /* Calculate the Fletcher checksum and prepend it to the data bytes This is easier than handling the addition of a non-byte-aligned quantity to the end of the data */ valBuf[ 0 ] = checksumData( valBuf + 1, length - 1 ) & 0xFF; /* Encode the binary data as text */ for( length = 0, i = 1; i <= noCodeGroups * 5; i++ ) { int chunkValue; /* Extract the next 5-bit chunk and convert it to text form */ if( bitCount < 3 ) /* Everything's present in one byte, shift it down to the LSB */ chunkValue = ( valBuf[ byteCount ] >> ( 3 - bitCount ) ) & 0x1F; else if( bitCount == 3 ) /* It's the 5 LSB's */ chunkValue = valBuf[ byteCount ] & 0x1F; else /* The data spans two bytes, shift the bits from the high byte up and the bits from the low byte down */ chunkValue = ( ( valBuf[ byteCount ] & \ hiMask[ bitCount ] ) << ( bitCount - 3 ) ) | \ ( ( valBuf[ byteCount + 1 ] & \ loMask[ bitCount ] ) >> ( 11 - bitCount ) ); encVal[ length++ ] = codeTable[ chunkValue ]; if( length < encValMaxLen && !( i % 5 ) && i < noCodeGroups * 5 ) encVal[ length++ ] = '-'; if( length >= encValMaxLen ) { assert( NOTREACHED ); return( CRYPT_ERROR_OVERFLOW ); } /* Advance by 5 bits */ bitCount += 5; if( bitCount >= 8 ) { bitCount -= 8; byteCount++; } } return( length ); } int decodePKIUserValue( BYTE *value, const int valueMaxLen, const char *encVal, const int encValLength ) { BYTE valBuf[ 128 + 8 ]; char encBuf[ CRYPT_MAX_TEXTSIZE + 8 ]; int i = 0, byteCount = 0, bitCount = 0, length = 0; assert( isReadPtr( encVal, encValLength ) ); /* Make sure that the input has a reasonable length (this should have been checked by the caller using isPKIUserValue(), so we throw an exception if the check fails). We return CRYPT_ERROR_BADDATA rather than the more obvious CRYPT_ERROR_OVERFLOW since something returned from this low a level should be a consistent error code indicating that there's a problem with the PKI user value as a whole */ if( encValLength < ( 3 * 5 ) || encValLength > CRYPT_MAX_TEXTSIZE ) { assert( NOTREACHED ); return( CRYPT_ERROR_BADDATA ); } /* Undo the formatting of the encoded value from XXXXX-XXXXX-XXXXX... to XXXXXXXXXXXXXXX... */ while( i < encValLength ) { int j; for( j = 0; j < 5; j++ ) { const int ch = encVal[ i++ ]; /* Note that we've just incremented 'i', so the range check is '>' rather than '>=' */ if( !isAlnum( ch ) || i > encValLength ) return( CRYPT_ERROR_BADDATA ); encBuf[ length++ ] = toUpper( ch ); } if( i < encValLength && encVal[ i++ ] != '-' ) return( CRYPT_ERROR_BADDATA ); } if( length % 5 || length > CRYPT_MAX_TEXTSIZE ) return( CRYPT_ERROR_BADDATA ); /* Decode the text data into binary */ memset( valBuf, 0, 128 ); for( i = 0; i < length; i ++ ) { const int ch = encBuf[ i ]; int chunkValue; for( chunkValue = 0; chunkValue < 0x20; chunkValue++ ) if( codeTable[ chunkValue ] == ch ) break; if( chunkValue >= 0x20 ) return( CRYPT_ERROR_BADDATA ); /* Extract the next 5-bit chunk and convert it to text form */ if( bitCount < 3 ) /* Everything's present in one byte, shift it up into position */ valBuf[ byteCount ] |= chunkValue << ( 3 - bitCount ); else if( bitCount == 3 ) /* It's the 5 LSB's */ valBuf[ byteCount ] |= chunkValue; else { /* The data spans two bytes, shift the bits from the high byte down and the bits from the low byte up */ valBuf[ byteCount ] |= \ ( chunkValue >> ( bitCount - 3 ) ) & hiMask[ bitCount ]; valBuf[ byteCount + 1 ] = \ ( chunkValue << ( 11 - bitCount ) ) & loMask[ bitCount ]; } /* Advance by 5 bits */ bitCount += 5; if( bitCount >= 8 ) { bitCount -= 8; byteCount++; } } /* Calculate the Fletcher checksum and make sure that it matches the value at the start of the data bytes */ if( bitCount > 0 ) byteCount++; /* More bits in the last partial byte */ if( valBuf[ 0 ] != ( checksumData( valBuf + 1, byteCount - 1 ) & 0xFF ) ) return( CRYPT_ERROR_BADDATA ); /* Return the decoded value to the caller */ if( value != NULL ) { if( byteCount - 1 > valueMaxLen ) { assert( NOTREACHED ); return( CRYPT_ERROR_BADDATA ); } memcpy( value, valBuf + 1, byteCount - 1 ); } return( byteCount - 1 ); }