📄 base64.c
字号:
/****************************************************************************
* *
* cryptlib Base64 Routines *
* Copyright Peter Gutmann 1992-2007 *
* *
****************************************************************************/
#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 character marker */
#define BEOF 0x7F /* EOF marker (padding character or EOL) */
static const char FAR_BSS binToAscii[ 64 ] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
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 base64-encoded data. This is only used for
encoding, for decoding we adjust to whatever size the sender has used */
#define BASE64_LINESIZE 64
/* Basic single-character en/decode functions. We mask the value to 6 or 8
bits both as a range check and 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 ) & 0x3F ]
#define decode( data ) asciiToBin[ ( data ) & 0xFF ]
/* The headers and trailers used for base64-encoded certificate objects */
typedef struct {
const CRYPT_CERTTYPE_TYPE type;
BUFFER_FIXED( headerLen ) \
const char FAR_BSS *header;
const int headerLen;
BUFFER_FIXED( trailerLen ) \
const char FAR_BSS *trailer;
const int trailerLen;
} HEADER_INFO;
static const HEADER_INFO FAR_BSS headerInfo[] = {
{ CRYPT_CERTTYPE_CERTIFICATE,
"-----BEGIN CERTIFICATE-----" EOL, 27 + EOL_LEN,
"-----END CERTIFICATE-----" EOL, 25 + EOL_LEN },
{ CRYPT_CERTTYPE_ATTRIBUTE_CERT,
"-----BEGIN ATTRIBUTE CERTIFICATE-----" EOL, 37 + EOL_LEN,
"-----END ATTRIBUTE CERTIFICATE-----" EOL, 35 + EOL_LEN },
{ CRYPT_CERTTYPE_CERTCHAIN,
"-----BEGIN CERTIFICATE CHAIN-----" EOL, 33 + EOL_LEN,
"-----END CERTIFICATE CHAIN-----" EOL, 31 + EOL_LEN },
{ CRYPT_CERTTYPE_CERTREQUEST,
"-----BEGIN NEW CERTIFICATE REQUEST-----" EOL, 39 + EOL_LEN,
"-----END NEW CERTIFICATE REQUEST-----" EOL, 37 + EOL_LEN },
{ CRYPT_CERTTYPE_REQUEST_CERT,
"-----BEGIN NEW CERTIFICATE REQUEST-----" EOL, 39 + EOL_LEN,
"-----END NEW CERTIFICATE REQUEST-----" EOL, 37 + EOL_LEN },
{ CRYPT_CERTTYPE_CRL,
"-----BEGIN CERTIFICATE REVOCATION LIST-----" EOL, 43 + EOL_LEN,
"-----END CERTIFICATE REVOCATION LIST-----" EOL, 41 + EOL_LEN },
{ CRYPT_CERTTYPE_NONE, /* Universal catch-all */
"-----BEGIN CERTIFICATE OBJECT-----" EOL, 34 + EOL_LEN,
"-----END CERTIFICATE OBJECT-----" EOL, 32 + EOL_LEN },
{ CRYPT_CERTTYPE_NONE, /* Universal catch-all */
"-----BEGIN CERTIFICATE OBJECT-----" EOL, 34 + EOL_LEN,
"-----END CERTIFICATE OBJECT-----" EOL, 32 + EOL_LEN }
};
/****************************************************************************
* *
* Decode Format-checking Functions *
* *
****************************************************************************/
/* Check for raw base64 data. There isn't a 100% reliable check that we can
apply for this but if the first 60 characters (the minimum base64 line
length) are all valid base64 data and the first characters match the
encoded form of data handled by cryptlib then it's reasonably certain
that it's base64 data */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int fixedBase64decode( STREAM *stream,
IN_BUFFER( srcLen ) const char *src,
IN_LENGTH_MIN( 10 ) const int srcLen );
CHECK_RETVAL_BOOL STDC_NONNULL_ARG( ( 1 ) ) \
static BOOLEAN checkBase64( INOUT STREAM *stream )
{
STREAM nullStream;
BYTE buffer[ 60 + 8 ];
int status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
/* Make sure that there's enough data present to perform a reliable
check */
status = sread( stream, buffer, 60 );
if( cryptStatusError( status ) )
return( FALSE );
/* Make sure that the content is some form of encoded key or certificate
data. For certificate 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...
Unfortunately in the case of MIME-encoded data with a MIME header,
the header is likely to begin with "MIME-Version", which happens to
match the base64-encoded form of 30 8x = "MI", so this quick-reject
filter won't catch this one particular case */
if( strCompare( buffer, "MI", 2 ) && \
strCompare( buffer, "AA", 2 ) && \
strCompare( buffer, "mQ", 2 ) )
return( FALSE );
/* Check that we have at least one minimal-length line of base64-encoded
data */
sMemNullOpen( &nullStream );
status = fixedBase64decode( &nullStream, buffer, 60 );
sMemDisconnect( &nullStream );
if( cryptStatusError( status ) )
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) */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int checkPEMHeader( INOUT STREAM *stream,
OUT_LENGTH_Z int *headerLength )
{
BOOLEAN isSSH = FALSE, isPGP = FALSE;
char buffer[ 1024 + 8 ], *bufPtr = buffer;
int length, position = DUMMY_INIT, lineCount, status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isWritePtr( headerLength, sizeof( int ) ) );
/* Clear return value */
*headerLength = 0;
/* Check for the initial 5 dashes and 'BEGIN ' (unless we're SSH, in
which case we use 4 dashes, a space, and 'BEGIN ') */
status = readTextLine( ( READCHARFUNCTION ) sgetc, stream,
buffer, 1024, &length, NULL );
if( cryptStatusError( status ) )
return( status );
if( length < 5 + 6 + 7 + 5 )
{
/* We need room for at least '-----' (5) + 'BEGIN ' (6) +
'ABC XYZ' (7) + '-----' (5) */
return( CRYPT_ERROR_BADDATA );
}
if( strCompare( bufPtr, "-----BEGIN ", 11 ) && /* PEM/PGP form */
strCompare( bufPtr, "---- BEGIN ", 11 ) ) /* SSH form */
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 );
/* If it's not SSH or PGP data, we're done */
if( !isSSH && !isPGP )
{
*headerLength = stell( stream );
return( CRYPT_OK );
}
/* 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. 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, while 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 */
for( lineCount = 0; lineCount < FAILSAFE_ITERATIONS_MED; lineCount++ )
{
position = stell( stream );
status = readTextLine( ( READCHARFUNCTION ) sgetc, stream,
buffer, 1024, &length, NULL );
if( cryptStatusError( status ) )
return( status );
if( isSSH && strFindCh( buffer, length, ':' ) < 0 )
break;
if( isPGP && length <= 0 )
break;
}
if( lineCount >= FAILSAFE_ITERATIONS_MED )
return( CRYPT_ERROR_BADDATA );
if( isSSH )
{
/* Return to the point before the line without the ':' */
sseek( stream, position );
}
*headerLength = stell( stream );
return( CRYPT_OK );
}
/* Look for the EOL marker at the end of a line of text. There's one
problematic special case here in which, 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 either 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 */
CHECK_RETVAL_SPECIAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
static int checkEOL( IN_BUFFER( srcLen ) const char *src,
IN_LENGTH const int srcLen,
OUT_LENGTH_Z int *eolSize,
IN_ENUM( CRYPT_CERTFORMAT ) \
const CRYPT_CERTFORMAT_TYPE format )
{
int srcIndex = 0;
assert( isReadPtr( src, srcLen ) );
assert( isWritePtr( eolSize, sizeof( int ) ) );
REQUIRES( srcLen > 0 && srcLen < MAX_INTLENGTH );
REQUIRES( format > CRYPT_CERTFORMAT_NONE && \
format < CRYPT_CERTFORMAT_LAST );
/* Clear return value */
*eolSize = 0;
/* Check for a '\0' at the end of the data */
if( format == CRYPT_ICERTFORMAT_SMIME_CERTIFICATE && src[ 0 ] == '\0' )
return( OK_SPECIAL ); /* We're at EOF, not just EOL */
/* 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( OK_SPECIAL ); /* We're at EOF, not just EOL */
/* Check for '\0' or EOL (S/MIME) or '----END...' (PEM) after EOL */
if( format == CRYPT_ICERTFORMAT_SMIME_CERTIFICATE )
{
if( src[ srcIndex ] == '\0' || src[ srcIndex ] == '\n' || \
src[ srcIndex ] == '\r' )
return( OK_SPECIAL ); /* We're at EOF, not just EOL */
}
if( format == CRYPT_CERTFORMAT_TEXT_CERTIFICATE )
{
if( srcIndex + 9 <= srcLen && \
!strCompare( src + srcIndex, "-----END ", 9 ) )
return( OK_SPECIAL ); /* We're at EOF, not just EOL */
}
/* If we were expecting an EOL but didn't find one there's a problem
with the data */
if( srcIndex <= 0 )
return( CRYPT_ERROR_BADDATA );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -