📄 asn1_chk.c
字号:
/* Disallowed or unrecognised primitive */
return( STATE_ERROR );
}
return( STATE_NONE );
}
/* Check a single ASN.1 object. checkASN1() and checkASN1Object() are
mutually recursive, the ...Object() version only exists to avoid a
large if... else chain in checkASN1(). A typical checking run is
as follows:
30 nn cASN1 -> cAObj -> cASN1
30 nn cASN1 -> cAObj -> cASN1
04 nn nn cASN1 -> cPrim
30 80 cASN1 -> cAObj -> cASN1
30 80 cASN1 -> cAObj -> cASN1
04 nn nn cASN1 -> cPrim
00 00 cASN1 <- cAObj <- cASN1
00 00 cASN1 <- cAObj <- cASN1
The use of checkASN1Object() leads to an (apparently) excessively deep
call hierarchy, but that's mostly just an artifact of the way that it's
diagrammed here */
CHECK_RETVAL_ENUM( STATE ) STDC_NONNULL_ARG( ( 1, 2 ) ) \
static ASN1_STATE checkASN1Object( 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,
const BOOLEAN checkDataElements )
{
ASN1_STATE newState;
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 );
/* Make sure that we're not processing suspiciosly deeply nested data */
if( level >= MAX_NESTING_LEVEL )
return( STATE_ERROR );
/* If we're checking data elements, check the contents for validity. A
straight data-length check doesn't check nested elements since all it
cares about is finding the overall length with as little effort as
possible */
if( checkDataElements && ( item->tag & BER_CLASS_MASK ) == BER_UNIVERSAL )
{
/* If it's constructed, parse the nested object(s) */
if( ( item->tag & BER_CONSTRUCTED_MASK ) == BER_CONSTRUCTED )
{
/* Special-case for zero-length SEQUENCE/SET */
if( item->length <= 0 && !item->indefinite )
return( STATE_NONE );
return( checkASN1( stream, item->length, item->indefinite,
level + 1, ( item->tag == BER_SEQUENCE ) ? \
STATE_SEQUENCE : STATE_NONE, TRUE ) );
}
/* It's primitive, check the primitive element with optional state
update: SEQ + OID -> HOLE_OID; OID + { NULL | BOOLEAN } ->
HOLE_BITSTRING/HOLE_OCTETSTRING */
newState = checkPrimitive( stream, item, level + 1, state );
if( newState < STATE_NONE || newState >= STATE_ERROR )
return( STATE_ERROR );
if( state == STATE_SEQUENCE && newState == STATE_OID )
return( STATE_HOLE_OID );
if( state == STATE_HOLE_OID )
{
if( newState == STATE_NULL )
return( STATE_HOLE_BITSTRING );
if( newState == STATE_BOOLEAN )
return( STATE_HOLE_OCTETSTRING );
}
return( STATE_NONE );
}
/* Zero-length objects are usually an error, however PKCS #10 has an
attribute-encoding ambiguity that produces zero-length tagged
extensions and OCSP has its braindamaged context-specific tagged
NULLs so we don't complain about them if they have low-valued
context-specific tags */
if( item->length <= 0 && !item->indefinite )
{
return( ( ( item->tag & BER_CLASS_MASK ) == BER_CONTEXT_SPECIFIC && \
EXTRACT_CTAG( item->tag ) <= 3 ) ? \
STATE_NONE : STATE_ERROR );
}
ENSURES( item->length > 0 || item->indefinite );
/* If it's constructed, parse the nested object(s) */
if( ( item->tag & BER_CONSTRUCTED_MASK ) == BER_CONSTRUCTED )
{
newState = checkASN1( stream, item->length, item->indefinite,
level + 1, STATE_NONE, checkDataElements );
return( ( newState < STATE_NONE || newState >= STATE_ERROR ) ? \
STATE_ERROR : STATE_NONE );
}
/* It's a context-specific tagged item that could contain anything, just
skip it */
if( ( item->tag & BER_CLASS_MASK ) != BER_CONTEXT_SPECIFIC || \
item->length <= 0 )
return( STATE_ERROR );
return( cryptStatusError( sSkip( stream, item->length ) ) ? \
STATE_ERROR : STATE_NONE );
}
/* Check a complex ASN.1 object. In order to handle huge CRLs with tens or
hundreds of thousands of individual entries we can't use a fixed loop
failsafe iteration count but have to vary it based on the size of the
input data. Luckily this situation is relatively easy to check for, it
only occurs at a nesting level of 6 (when we find the CRL entries) and we
only have to enable it when the data length is more than 30K since the
default FAILSAFE_ITERATIONS_LARGE will handle anything smaller than that */
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( 0, MAX_NESTING_LEVEL ) const int level,
IN_ENUM_OPT( ASN1_STATE ) const ASN1_STATE state,
const BOOLEAN checkDataElements )
{
ASN1_ITEM item;
ASN1_STATE newState;
const long maxIterationCount = ( level == 6 && length > 30000 ) ? \
length / 25 : FAILSAFE_ITERATIONS_LARGE;
long localLength = length, lastPos = stell( stream );
int iterationCount;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
REQUIRES( ( level > 0 && level <= MAX_NESTING_LEVEL ) || \
( level == 0 && length == LENGTH_MAGIC ) );
REQUIRES( state >= STATE_NONE && state < STATE_ERROR );
REQUIRES( ( isIndefinite && length == 0 ) || \
( !isIndefinite && length >= 0 && length < MAX_INTLENGTH ) );
/* Make sure that we're not processing suspiciosly deeply nested data */
if( level >= MAX_NESTING_LEVEL )
return( STATE_ERROR );
for( iterationCount = 0;
( newState = getItem( stream, &item ) ) == STATE_NONE && \
iterationCount < maxIterationCount;
iterationCount++ )
{
/* If this is the top level (for which the length isn't known in
advance) and the item has a definite length, set the length to
the item's length */
if( level <= 0 && !item.indefinite )
localLength = item.headerSize + item.length;
/* If this is an EOC (tag == BER_RESERVED) for an indefinite item,
we're done */
if( isIndefinite && item.tag == BER_RESERVED )
return( STATE_NONE );
/* Check the object */
if( !checkDataElements && item.length > 0 )
{
/* Shortcut to save a level of recursion, if we're not
interested in the data elements (i.e. if we're just doing a
length check) and the item has a definite length, just skip
over it and continue */
if( cryptStatusError( sSkip( stream, item.length ) ) )
return( STATE_ERROR );
}
else
{
newState = checkASN1Object( stream, &item, level + 1, state,
checkDataElements );
if( newState < STATE_NONE || newState >= STATE_ERROR )
return( STATE_ERROR );
}
/* If it's an indefinite-length object, we have to keep going until
we find the EOC octets */
if( isIndefinite )
continue;
/* If the outermost object was of indefinite length and we've come
back to the top level, exit. The isIndefinite flag won't be set
at this point because we can't know the length status before we
start, but it's implicitly indicated by finding a length of
LENGTH_MAGIC at the topmost level */
if( level == 0 && length == LENGTH_MAGIC )
return( STATE_NONE );
/* Check whether we've reached the end of the current (definite-
length) object */
localLength -= stell( stream ) - lastPos;
lastPos = stell( stream );
if( localLength < 0 || localLength >= MAX_INTLENGTH )
return( STATE_ERROR );
if( localLength == 0 )
{
/* We've reached the end of the object, we're done */
return( newState );
}
}
ENSURES_S( iterationCount < maxIterationCount );
return( ( newState == STATE_NONE ) ? STATE_NONE : STATE_ERROR );
}
/* Check the encoding of a complete object and determine its length */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int checkObjectEncoding( IN_BUFFER( objectLength ) const void *objectPtr,
IN_LENGTH const int objectLength )
{
STREAM stream;
ASN1_STATE state;
int length = DUMMY_INIT;
assert( isReadPtr( objectPtr, objectLength ) );
REQUIRES( objectLength > 0 && objectLength < MAX_INTLENGTH );
sMemConnect( &stream, objectPtr, objectLength );
state = checkASN1( &stream, LENGTH_MAGIC, FALSE, 0, STATE_NONE, TRUE );
if( state >= STATE_NONE && state < STATE_ERROR )
length = stell( &stream );
sMemDisconnect( &stream );
return( ( state < STATE_NONE ) ? CRYPT_ERROR_INTERNAL : \
( state >= STATE_ERROR ) ? CRYPT_ERROR_BADDATA : length );
}
/* Recursively dig into an ASN.1 object as far as we need to to determine
its length */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int findObjectLength( INOUT STREAM *stream,
OUT_LENGTH_Z long *length,
const BOOLEAN isLongObject )
{
const long startPos = stell( stream );
long localLength;
int status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isWritePtr( length, sizeof( long ) ) );
/* Clear return value */
*length = 0;
/* Try for a definite length */
if( isLongObject )
status = readLongGenericHole( stream, &localLength, DEFAULT_TAG );
else
{
int shortLength;
status = readGenericHoleI( stream, &shortLength, 0, DEFAULT_TAG );
localLength = shortLength;
}
if( cryptStatusError( status ) )
return( status );
/* If it's an indefinite-length object, burrow down into it to find its
actual length */
if( localLength == CRYPT_UNUSED )
{
ASN1_STATE state;
/* We have to be a bit careful how we handle error reporting for
this since we can run out of input and hit an underflow while
we're in the process of burrowing through the data. This is
somewhat unfortunate since it leads to non-orthogonal behaviour
because a definite length only requires checking a few bytes at
the start of the data but an indefinite length requires
processing the entire data quantity in order to determine where
it ends */
sseek( stream, startPos );
state = checkASN1( stream, LENGTH_MAGIC, FALSE, 0, STATE_NONE,
FALSE );
if( state < STATE_NONE || state >= STATE_ERROR )
{
return( ( state < STATE_NONE ) ? \
CRYPT_ERROR_INTERNAL : \
( sGetStatus( stream ) == CRYPT_ERROR_UNDERFLOW ) ? \
CRYPT_ERROR_UNDERFLOW : \
CRYPT_ERROR_BADDATA );
}
localLength = stell( stream ) - startPos;
}
else
{
/* It's a definite-length object, add the size of the tag+length */
localLength += stell( stream ) - startPos;
}
/* If it's not a long object, make sure that the length is within bounds.
We have to do this explicitly here because indefinite-length objects
can be arbitrarily large so the length isn't checked as it is for
readGenericHoleI() */
if( !isLongObject && localLength > 32767L )
return( CRYPT_ERROR_OVERFLOW );
*length = localLength;
return( sseek( stream, startPos ) );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int getStreamObjectLength( INOUT STREAM *stream, OUT_LENGTH_Z int *length )
{
long localLength;
int status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isWritePtr( length, sizeof( int ) ) );
/* Clear return value */
*length = 0;
status = findObjectLength( stream, &localLength, FALSE );
if( cryptStatusOK( status ) )
*length = localLength;
return( status );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int getLongStreamObjectLength( INOUT STREAM *stream,
OUT_LENGTH_Z long *length )
{
long localLength;
int status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isWritePtr( length, sizeof( long ) ) );
/* Clear return value */
*length = 0;
status = findObjectLength( stream, &localLength, FALSE );
if( cryptStatusOK( status ) )
*length = localLength;
return( status );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
int getObjectLength( IN_BUFFER( objectLength ) const void *objectPtr,
IN_LENGTH const int objectLength,
OUT_LENGTH_Z int *length )
{
STREAM stream;
long localLength = DUMMY_INIT;
int status;
assert( isReadPtr( objectPtr, objectLength ) );
assert( isWritePtr( length, sizeof( int ) ) );
REQUIRES( objectLength > 0 && objectLength < MAX_INTLENGTH );
/* Clear return value */
*length = 0;
sMemConnect( &stream, objectPtr, objectLength );
if( peekTag( &stream ) == BER_INTEGER )
{
/* Sometimes we're asked to find the length of non-hole items that
will be rejected by findObjectLength(), which calls down to
readGenericHoleI(). Since these items are primitive and non-
constructed (in order to qualify as non-holes), we can process
the item with readUniversal().
An alternative processing mechanism would be to use peekTag() and
readGenericHole() in combination with the peekTag() results */
status = readUniversal( &stream );
if( cryptStatusOK( status ) )
localLength = stell( &stream );
}
else
{
/* Quousque tandem? */
status = findObjectLength( &stream, &localLength, FALSE );
}
sMemDisconnect( &stream );
if( cryptStatusError( status ) )
return( status );
*length = localLength;
return( CRYPT_OK );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
int getLongObjectLength( IN_BUFFER( objectLength ) const void *objectPtr,
IN_LENGTH const long objectLength,
OUT_LENGTH_Z long *length )
{
STREAM stream;
long localLength;
int status;
assert( isReadPtr( objectPtr, objectLength ) );
assert( isWritePtr( length, sizeof( long ) ) );
REQUIRES( objectLength > 0 && objectLength < MAX_INTLENGTH );
/* Clear return value */
*length = 0;
sMemConnect( &stream, objectPtr, objectLength );
status = findObjectLength( &stream, &localLength, TRUE );
sMemDisconnect( &stream );
if( cryptStatusOK( status ) )
*length = localLength;
return( status );
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -