📄 asn1_chk.c
字号:
/****************************************************************************
* *
* ASN.1 Checking Routines *
* Copyright Peter Gutmann 1992-2007 *
* *
****************************************************************************/
#include <ctype.h>
#if defined( INC_ALL )
#include "crypt.h"
#include "bn.h"
#include "asn1.h"
#else
#include "crypt.h"
#include "bn/bn.h"
#include "misc/asn1.h"
#endif /* Compiler-specific includes */
/* The maximum nesting level for constructed or encapsulated objects (this
can get surprisingly high for some of the more complex attributes). This
value is chosen to pass all normal certs while avoiding stack overflows
for artificial bad data */
#define MAX_NESTING_LEVEL 50
/* When we parse a nested data object encapsulated within a larger object,
the length is initially set to a magic value which is adjusted to the
actual length once we start parsing the object. Even CRLs will hopefully
never reach 500MB, the current limits seems to be around 150MB */
#define LENGTH_MAGIC 500000000L
/* Current parse state. This is used to check for potential BIT STRING and
OCTET STRING targets for OCTET/BIT STRING holes, which are always
preceded by an AlgorithmIdentifier. In order to detect these without
having to know every imaginable AlgorithmIdentifier OID, we check for the
following sequence of events:
SEQUENCE { -- STATE_SEQUENCE
OID, -- STATE_HOLE_OID
NULL -- STATE_NULL
},
BIT STRING -- STATE_HOLE_BITSTRING
SEQUENCE { -- STATE_SEQUENCE
OID, -- STATE_HOLE_OID
BOOLEAN OPT, -- STATE_BOOLEAN (following a STATE_HOLE_OID)
OCTET STRING -- STATE_HOLE_OCTETSTRING
Once we reach any of the STATE_HOLE_* states, if we hit a BIT STRING or
OCTET STRING we try and locate encapsulated content within it. This type
of checking is rather awkward in the (otherwise stateless) code, but is
the only way to be sure that it's safe to try burrowing into an OCTET
STRING or BIT STRING to try to find encapsulated data, since otherwise
even with relatively strict checking there's still a very small chance
that random data will look like a nested object */
typedef enum {
/* Generic non-state */
STATE_NONE,
/* States corresponding to ASN.1 primitives */
STATE_BOOLEAN, STATE_NULL, STATE_OID, STATE_SEQUENCE,
/* States corresponding to different parts of a SEQUENCE { OID, optional,
OCTET/BIT STRING } sequence */
STATE_HOLE_OID, STATE_HOLE_BITSTRING, STATE_HOLE_OCTETSTRING,
/* Error state */
STATE_ERROR,
STATE_LAST
} ASN1_STATE;
/* Structure to hold info on an ASN.1 item */
typedef struct {
int tag; /* Tag */
long length; /* Data length */
BOOLEAN indefinite; /* Item has indefinite length */
int headerSize; /* Size of tag+length */
} ASN1_ITEM;
/* Get an ASN.1 object's tag and length */
CHECK_RETVAL_ENUM( STATE ) STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int getItem( INOUT STREAM *stream, INOUT ASN1_ITEM *item )
{
const long offset = stell( stream );
long length;
int status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isWritePtr( item, sizeof( ASN1_ITEM ) ) );
/* Clear return value */
memset( item, 0, sizeof( ASN1_ITEM ) );
/* Read the tag. We can't use peekTag() for this since we may be
reading EOC octets, which would be rejected by peekTag() */
status = item->tag = sPeek( stream );
if( cryptStatusError( status ) )
return( STATE_ERROR );
if( item->tag == BER_EOC )
{
/* It looks like EOC octets, make sure that they're in order */
status = checkEOC( stream );
if( cryptStatusError( status ) )
return( STATE_ERROR );
if( status == TRUE )
{
item->headerSize = 2;
return( STATE_NONE );
}
}
/* It's no an EOC, read the tag and length as a generic hole */
status = readLongGenericHole( stream, &length, item->tag );
if( cryptStatusError( status ) )
return( STATE_ERROR );
item->headerSize = stell( stream ) - offset;
if( length == CRYPT_UNUSED )
item->indefinite = TRUE;
else
item->length = length;
return( STATE_NONE );
}
/* Check whether an ASN.1 object is encapsulated inside an OCTET STRING or
BIT STRING. After performing the various checks we have to explicitly
clear the stream error state since the probing for valid data could have
set the error indicator if nothing valid was found */
CHECK_RETVAL_BOOL STDC_NONNULL_ARG( ( 1 ) ) \
static BOOLEAN checkEncapsulation( INOUT STREAM *stream,
IN_LENGTH const int length,
const BOOLEAN isBitstring,
IN_ENUM_OPT( ASN1_STATE ) \
const ASN1_STATE state )
{
BOOLEAN isEncapsulated = TRUE;
const long streamPos = stell( stream );
const int tag = peekTag( stream );
int innerLength, status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
REQUIRES_B( length > 0 && length < MAX_INTLENGTH );
REQUIRES_B( state >= STATE_NONE && state < STATE_ERROR );
/* Make sure that the tag is in order */
if( cryptStatusError( tag ) )
{
sClearError( stream );
sseek( stream, streamPos );
return( FALSE );
}
/* Make sure that there's an encapsulated object present. This is a
reasonably effective check, but unfortunately this same effectiveness
means that it'll reject nested objects with incorrect lengths. It's
not really possible to fix this, either there'll be false positives
due to true OCTET/BIT STRINGs that look like they might contain
nested data, or there'll be no false positives but nested content
with slightly incorrect encodings will be missed */
status = readGenericHole( stream, &innerLength, 1, DEFAULT_TAG );
if( cryptStatusError( status ) || \
( stell( stream ) - streamPos ) + innerLength != length )
{
sClearError( stream );
sseek( stream, streamPos );
return( FALSE );
}
/* A BIT STRING that encapsulates something only ever contains
{ SEQUENCE { INTEGER, ... } } */
if( isBitstring )
{
/* Make sure that there's a SEQUENCE containing an INTEGER present */
if( tag != BER_SEQUENCE || peekTag( stream ) != BER_INTEGER || \
cryptStatusError( readGenericHole( stream, &innerLength, 1,
BER_INTEGER ) ) || \
innerLength > length - 4 )
isEncapsulated = FALSE;
sClearError( stream );
sseek( stream, streamPos );
return( isEncapsulated );
}
/* An OCTET STRING is more complex. This could encapsulate any of:
BIT STRING: keyUsage, crlReason, Netscape certType, must be
<= 16 bits and a valid bitstring.
GeneralisedTime: invalidityDate: Not possible to check directly
since the obvious check for a valid length will also fail
invalid-length encodings, missing the very thing we usually
want to check for, so all that we can check for is a vaguely
valid length.
IA5String: Netscape extensions, the most that we can do is perform
an approximate length range check
INTEGER: deltaCRLIndicator, crlNumber, must be <= 16 bits.
OCTET STRING: keyID, again the most that we can do is perform an
approximate length range check.
OID: holdInstructionCode, again just an approximate length range
check.
SEQUENCE: most extensions, a bit difficult to check but again we can
make sure that the length is right for strict encapsulation.
Note that we want these checks to be as liberal as possible since
we're only checking for the *possibility* of encapsulated data at
this point. Once we're fairly certain that it's encapsulated data
then we recurse down into it with checkASN1(). If we rejected too
many things at this level then it'd never get checked via
checkASN1() */
switch( tag )
{
case BER_BITSTRING:
if( innerLength < 0 || innerLength > 2 )
isEncapsulated = FALSE;
else
{
int ch = sgetc( stream );
if( ch < 0 || ch > 7 )
isEncapsulated = FALSE;
}
break;
case BER_TIME_GENERALIZED:
if( innerLength < 10 || innerLength > 20 )
isEncapsulated = FALSE;
break;
case BER_INTEGER:
if( innerLength < 0 || innerLength > 2 )
isEncapsulated = FALSE;
break;
case BER_STRING_IA5:
case BER_OCTETSTRING:
if( innerLength < 2 || innerLength > 256 )
isEncapsulated = FALSE;
break;
case BER_OBJECT_IDENTIFIER:
if( innerLength < MIN_OID_SIZE - 2 || \
innerLength > MAX_OID_SIZE )
isEncapsulated = FALSE;
break;
case BER_SEQUENCE:
break;
default:
isEncapsulated = FALSE;
}
sClearError( stream );
sseek( stream, streamPos );
return( isEncapsulated );
}
/* Check a primitive ASN.1 object */
CHECK_RETVAL_ENUM( STATE ) STDC_NONNULL_ARG( ( 1 ) ) \
static ASN1_STATE checkASN1( INOUT STREAM *stream,
IN_LENGTH const long length,
const BOOLEAN isIndefinite,
IN_RANGE( 1, MAX_NESTING_LEVEL ) const int level,
IN_ENUM_OPT( ASN1_STATE ) const ASN1_STATE state,
const BOOLEAN checkDataElements );
CHECK_RETVAL_ENUM( STATE ) STDC_NONNULL_ARG( ( 1, 2 ) ) \
static ASN1_STATE checkPrimitive( INOUT STREAM *stream, const ASN1_ITEM *item,
IN_RANGE( 1, MAX_NESTING_LEVEL ) const int level,
IN_ENUM_OPT( ASN1_STATE ) const ASN1_STATE state )
{
int length = ( int ) item->length, ch, i;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isReadPtr( item, sizeof( ASN1_ITEM ) ) );
REQUIRES( level > 0 && level <= MAX_NESTING_LEVEL );
REQUIRES( state >= STATE_NONE && state < STATE_ERROR );
REQUIRES( length >= 0 && length < MAX_INTLENGTH );
/* Make sure that we're not processing suspiciosly deeply nested data */
if( level >= MAX_NESTING_LEVEL )
return( STATE_ERROR );
/* In theory only NULL and EOC elements (BER_RESERVED) are allowed to
have a zero length, but some broken implementations (Netscape, Van
Dyke) encode numeric zero values as a zero-length element so we have
to accept these as well */
if( length <= 0 && item->tag != BER_NULL && \
item->tag != BER_RESERVED && \
item->tag != BER_INTEGER )
return( STATE_ERROR );
/* Perform a general check that everything is OK. We don't check for
invalid content except where it would impede decoding of the data in
order to avoid failing on all of the broken certs out there */
switch( item->tag )
{
case BER_BOOLEAN:
return( cryptStatusError( sgetc( stream ) ) ? \
STATE_ERROR : STATE_BOOLEAN );
case BER_INTEGER:
case BER_ENUMERATED:
if( length > 0 && /* May be encoded as a zero-length value */
cryptStatusError( sSkip( stream, length ) ) )
return( STATE_ERROR );
return( STATE_NONE );
case BER_BITSTRING:
/* Check the number of unused bits */
ch = sgetc( stream );
length--;
if( length < 0 || length >= MAX_INTLENGTH || \
ch < 0 || ch > 7 )
{
/* Invalid number of unused bits */
return( STATE_ERROR );
}
/* If it's short enough to be a bit flag, it's just a sequence
of bits */
if( length <= 4 )
{
if( length > 0 && \
cryptStatusError( sSkip( stream, length ) ) )
return( STATE_ERROR );
return( STATE_NONE );
}
/* Fall through */
case BER_OCTETSTRING:
{
const BOOLEAN isBitstring = ( item->tag == BER_BITSTRING ) ? \
TRUE : FALSE;
/* Check to see whether an OCTET STRING or BIT STRING hole is
allowed at this point (a BIT STRING must be preceded by
{ SEQ, OID, NULL }, an OCTET STRING must be preceded by
{ SEQ, OID, {BOOLEAN} }), and if it's something encapsulated
inside the string, handle it as a constructed item */
if( ( ( isBitstring && state == STATE_HOLE_BITSTRING ) || \
( !isBitstring && ( state == STATE_HOLE_OID || \
state == STATE_HOLE_OCTETSTRING ) ) ) && \
checkEncapsulation( stream, length, isBitstring, state ) )
{
ASN1_STATE encapsState;
encapsState = checkASN1( stream, length, item->indefinite,
level + 1, STATE_NONE, TRUE );
return( ( encapsState < STATE_NONE || \
encapsState >= STATE_ERROR ) ? \
STATE_ERROR : STATE_NONE );
}
/* Skip the data */
return( cryptStatusError( sSkip( stream, length ) ) ? \
STATE_ERROR : STATE_NONE );
}
case BER_OBJECT_IDENTIFIER:
if( length > MAX_OID_SIZE - 2 )
{
/* Total OID size (including tag and length, since they're
treated as a blob) should be less than a sane limit */
return( STATE_ERROR );
}
return( cryptStatusError( sSkip( stream, length ) ) ? \
STATE_ERROR : STATE_OID );
case BER_RESERVED:
break; /* EOC */
case BER_NULL:
return( STATE_NULL );
case BER_STRING_BMP:
case BER_STRING_GENERAL: /* Produced by Entrust software */
case BER_STRING_IA5:
case BER_STRING_ISO646:
case BER_STRING_NUMERIC:
case BER_STRING_PRINTABLE:
case BER_STRING_T61:
case BER_STRING_UTF8:
return( cryptStatusError( sSkip( stream, length ) ) ? \
STATE_ERROR : STATE_NONE );
case BER_TIME_UTC:
case BER_TIME_GENERALIZED:
if( item->tag == BER_TIME_GENERALIZED )
{
if( length != 15 )
return( STATE_ERROR );
}
else
{
if( length != 13 )
return( STATE_ERROR );
}
for( i = 0; i < length - 1; i++ )
{
ch = sgetc( stream );
if( cryptStatusError( ch ) || !isDigit( ch ) )
return( STATE_ERROR );
}
if( sgetc( stream ) != 'Z' )
return( STATE_ERROR );
return( STATE_NONE );
default:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -