|
|
//+-------------------------------------------------------------------------
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 2001 - 2001
//
// File: asn1util.cpp
//
// Contents: Minimal ASN.1 utility helper functions.
//
// Functions: MinAsn1DecodeLength
// MinAsn1ExtractContent
// MinAsn1ExtractValues
//
// MinAsn1FindExtension
// MinAsn1FindAttribute
// MinAsn1ExtractParsedCertificatesFromSignedData
//
// History: 15-Jan-01 philh created
//--------------------------------------------------------------------------
#include "global.hxx"
//+-------------------------------------------------------------------------
// Get the number of contents octets in a definite-length BER-encoding.
//
// Parameters:
// pcbContent - receives the number of contents octets
// pbLength - points to the first length octet
// cbBER - number of bytes remaining in the BER encoding
//
// Returns:
// success - the number of bytes in the length field, > 0
// failure - < 0
//
// One of the following failure values can be returned:
// MINASN1_LENGTH_TOO_LARGE
// MINASN1_INSUFFICIENT_DATA
// MINASN1_UNSUPPORTED_INDEFINITE_LENGTH
//--------------------------------------------------------------------------
LONG WINAPI MinAsn1DecodeLength( OUT DWORD *pcbContent, IN const BYTE *pbLength, IN DWORD cbBER) { long i; BYTE cbLength; const BYTE *pb;
if (cbBER < 1) goto TooLittleData;
if (0x80 == *pbLength) goto IndefiniteLength;
// determine the number of length octets and contents octets
if ((cbLength = *pbLength) & 0x80) { cbLength &= ~0x80; // low 7 bits have number of bytes
if (cbLength > 4) goto LengthTooLargeError; if (cbLength >= cbBER) goto TooLittleData; *pcbContent = 0; for (i=cbLength, pb=pbLength+1; i>0; i--, pb++) *pcbContent = (*pcbContent << 8) + (const DWORD)*pb; i = cbLength + 1; } else { *pcbContent = (DWORD)cbLength; i = 1; }
CommonReturn: return i; // how many bytes there were in the length field
LengthTooLargeError: i = MINASN1_LENGTH_TOO_LARGE; goto CommonReturn;
IndefiniteLength: i = MINASN1_UNSUPPORTED_INDEFINITE_LENGTH; goto CommonReturn;
TooLittleData: i = MINASN1_INSUFFICIENT_DATA; goto CommonReturn; }
//+-------------------------------------------------------------------------
// Point to the content octets in a definite-length BER-encoded blob.
//
// Returns:
// success - the number of bytes skipped, > 0
// failure - < 0
//
// One of the following failure values can be returned:
// MINASN1_LENGTH_TOO_LARGE
// MINASN1_INSUFFICIENT_DATA
// MINASN1_UNSUPPORTED_INDEFINITE_LENGTH
//
// Assumption: pbData points to a definite-length BER-encoded blob.
// If *pcbContent isn't within cbBER, MINASN1_INSUFFICIENT_DATA
// is returned.
//--------------------------------------------------------------------------
LONG WINAPI MinAsn1ExtractContent( IN const BYTE *pbBER, IN DWORD cbBER, OUT DWORD *pcbContent, OUT const BYTE **ppbContent) { #define TAG_MASK 0x1f
DWORD cbIdentifier; DWORD cbContent; LONG cbLength; LONG lHeader; const BYTE *pb = pbBER;
if (0 == cbBER--) goto TooLittleData;
// Skip over the identifier octet(s)
if (TAG_MASK == (*pb++ & TAG_MASK)) { // high-tag-number form
cbIdentifier = 2; while (TRUE) { if (0 == cbBER--) goto TooLittleData; if (0 == (*pb++ & 0x80)) break; cbIdentifier++; } } else { // low-tag-number form
cbIdentifier = 1; }
if (0 > (cbLength = MinAsn1DecodeLength( &cbContent, pb, cbBER))) { lHeader = cbLength; goto CommonReturn; }
if (cbContent > (cbBER - cbLength)) goto TooLittleData;
pb += cbLength;
*pcbContent = cbContent; *ppbContent = pb;
lHeader = cbLength + cbIdentifier; CommonReturn: return lHeader;
TooLittleData: lHeader = MINASN1_INSUFFICIENT_DATA; goto CommonReturn; }
typedef struct _STEP_INTO_STACK_ENTRY { const BYTE *pb; DWORD cb; BOOL fSkipIntoValues; } STEP_INTO_STACK_ENTRY, *PSTEP_INTO_STACK_ENTRY;
#define MAX_STEP_INTO_DEPTH 8
//+-------------------------------------------------------------------------
// Extract one or more tagged values from the ASN.1 encoded byte array.
//
// Either steps into the value's content octets (MINASN1_STEP_INTO_VALUE_OP or
// MINASN1_OPTIONAL_STEP_INTO_VALUE_OP) or steps over the value's tag,
// length and content octets (MINASN1_STEP_OVER_VALUE_OP or
// MINASN1_OPTIONAL_STEP_OVER_VALUE_OP).
//
// You can step out of a stepped into sequence via MINASN1_STEP_OUT_VALUE_OP.
//
// For tag matching, only supports single byte tags.
//
// Only definite-length ASN.1 is supported.
//
// *pcValue is updated with the number of values successfully extracted.
//
// Returns:
// success - >= 0 => length of all bytes consumed through the last value
// extracted. For STEP_INTO, only the tag and length
// octets.
// failure - < 0 => negative (offset + 1) of first bad tagged value
//
// A non-NULL rgValueBlob[] is updated with the pointer to and length of the
// tagged value or its content octets. The rgValuePara[].dwIndex is used to
// index into rgValueBlob[]. For OPTIONAL_STEP_OVER or
// OPTIONAL_STEP_INTO, if no more bytes in the outer SEQUENCE or if the tag
// isn't found, pbData and cbData are set to 0. Additioanlly, for
// OPTIONAL_STEP_INTO, all subsequent values are skipped and their
// rgValueBlob[] entries zeroed until a STEP_OUT is encountered.
//
// If MINASN1_RETURN_VALUE_BLOB_FLAG is set, pbData points to
// the tag. cbData includes the tag, length and content octets.
//
// If MINASN1_RETURN_CONTENT_BLOB_FLAG is set, pbData points to the content
// octets. cbData includes only the content octets.
//
// If neither BLOB_FLAG is set, rgValueBlob[] isn't updated.
//
// For MINASN1_RETURN_CONTENT_BLOB_FLAG of a BITSTRING, pbData is
// advanced past the first contents octet containing the number of
// unused bits and cbData has been decremented by 1. If cbData > 0, then,
// *(pbData - 1) will contain the number of unused bits.
//--------------------------------------------------------------------------
LONG WINAPI MinAsn1ExtractValues( IN const BYTE *pbEncoded, IN DWORD cbEncoded, IN OUT DWORD *pcValuePara, IN const MINASN1_EXTRACT_VALUE_PARA *rgValuePara, IN DWORD cValueBlob, OUT OPTIONAL PCRYPT_DER_BLOB rgValueBlob ) { DWORD cValue = *pcValuePara; const BYTE *pb = pbEncoded; DWORD cb = cbEncoded; BOOL fSkipIntoValues = FALSE;
DWORD iValue; LONG lAllValues;
STEP_INTO_STACK_ENTRY rgStepIntoStack[MAX_STEP_INTO_DEPTH]; DWORD dwStepIntoDepth = 0;
for (iValue = 0; iValue < cValue; iValue++) { DWORD dwParaFlags = rgValuePara[iValue].dwFlags; DWORD dwOp = dwParaFlags & MINASN1_MASK_VALUE_OP; const BYTE *pbParaTag = rgValuePara[iValue].rgbTag; DWORD dwIndex = rgValuePara[iValue].dwIndex; BOOL fValueBlob = (dwParaFlags & (MINASN1_RETURN_VALUE_BLOB_FLAG | MINASN1_RETURN_CONTENT_BLOB_FLAG)) && rgValueBlob && (dwIndex < cValueBlob); BOOL fSkipValue = FALSE;
LONG lTagLength; DWORD cbContent; const BYTE *pbContent; DWORD cbValue;
if (MINASN1_STEP_OUT_VALUE_OP == dwOp) { // Unstack and advance past the last STEP_INTO
if (0 == dwStepIntoDepth) goto InvalidStepOutOp;
dwStepIntoDepth--; pb = rgStepIntoStack[dwStepIntoDepth].pb; cb = rgStepIntoStack[dwStepIntoDepth].cb; fSkipIntoValues = rgStepIntoStack[dwStepIntoDepth].fSkipIntoValues;
continue; }
if (fSkipIntoValues) { // For an omitted OPTIONAL_STEP_INTO, all of its included values
// are also omitted.
fSkipValue = TRUE; } else if (0 == cb) { if (!(MINASN1_OPTIONAL_STEP_INTO_VALUE_OP == dwOp || MINASN1_OPTIONAL_STEP_OVER_VALUE_OP == dwOp)) goto TooLittleData; fSkipValue = TRUE; } else if (pbParaTag) { // Assumption: single byte tag for doing comparison
// Check if the encoded tag matches one of the expected tags
BYTE bEncodedTag; BYTE bParaTag;
bEncodedTag = *pb; while ((bParaTag = *pbParaTag) && bParaTag != bEncodedTag) pbParaTag++;
if (0 == bParaTag) { if (!(MINASN1_OPTIONAL_STEP_INTO_VALUE_OP == dwOp || MINASN1_OPTIONAL_STEP_OVER_VALUE_OP == dwOp)) goto InvalidTag; fSkipValue = TRUE; } }
if (fSkipValue) { if (fValueBlob) { rgValueBlob[dwIndex].pbData = NULL; rgValueBlob[dwIndex].cbData = 0; }
if (MINASN1_STEP_INTO_VALUE_OP == dwOp || MINASN1_OPTIONAL_STEP_INTO_VALUE_OP == dwOp) { // Stack this skipped STEP_INTO
if (MAX_STEP_INTO_DEPTH <= dwStepIntoDepth) goto ExceededStepIntoDepth; rgStepIntoStack[dwStepIntoDepth].pb = pb; rgStepIntoStack[dwStepIntoDepth].cb = cb; rgStepIntoStack[dwStepIntoDepth].fSkipIntoValues = fSkipIntoValues; dwStepIntoDepth++;
fSkipIntoValues = TRUE; } continue; }
lTagLength = MinAsn1ExtractContent( pb, cb, &cbContent, &pbContent ); if (0 >= lTagLength) goto InvalidTagOrLength;
cbValue = cbContent + lTagLength;
if (fValueBlob) { if (dwParaFlags & MINASN1_RETURN_CONTENT_BLOB_FLAG) { rgValueBlob[dwIndex].pbData = (BYTE *) pbContent; rgValueBlob[dwIndex].cbData = cbContent;
if (MINASN1_TAG_BITSTRING == *pb) { if (0 < cbContent) { // Advance past the first contents octet containing
// the number of unused bits
rgValueBlob[dwIndex].pbData += 1; rgValueBlob[dwIndex].cbData -= 1; } } } else if (dwParaFlags & MINASN1_RETURN_VALUE_BLOB_FLAG) { rgValueBlob[dwIndex].pbData = (BYTE *) pb; rgValueBlob[dwIndex].cbData = cbValue; } }
switch (dwOp) { case MINASN1_STEP_INTO_VALUE_OP: case MINASN1_OPTIONAL_STEP_INTO_VALUE_OP: // Stack this STEP_INTO
if (MAX_STEP_INTO_DEPTH <= dwStepIntoDepth) goto ExceededStepIntoDepth; rgStepIntoStack[dwStepIntoDepth].pb = pb + cbValue; rgStepIntoStack[dwStepIntoDepth].cb = cb - cbValue; assert(!fSkipIntoValues); rgStepIntoStack[dwStepIntoDepth].fSkipIntoValues = FALSE; dwStepIntoDepth++; pb = pbContent; cb = cbContent; break; case MINASN1_STEP_OVER_VALUE_OP: case MINASN1_OPTIONAL_STEP_OVER_VALUE_OP: pb += cbValue; cb -= cbValue; break; default: goto InvalidArg; } }
lAllValues = (LONG)(pb - pbEncoded); assert((DWORD) lAllValues <= cbEncoded);
CommonReturn: *pcValuePara = iValue; return lAllValues;
InvalidStepOutOp: TooLittleData: InvalidTag: ExceededStepIntoDepth: InvalidTagOrLength: InvalidArg: lAllValues = -((LONG)(pb - pbEncoded)) - 1; goto CommonReturn; }
//+-------------------------------------------------------------------------
// Find an extension identified by its Encoded Object Identifier.
//
// Searches the list of parsed extensions returned by
// MinAsn1ParseExtensions().
//
// If found, returns pointer to the rgExtBlob[MINASN1_EXT_BLOB_CNT].
// Otherwise, returns NULL.
//--------------------------------------------------------------------------
PCRYPT_DER_BLOB WINAPI MinAsn1FindExtension( IN PCRYPT_DER_BLOB pEncodedOIDBlob, IN DWORD cExt, IN CRYPT_DER_BLOB rgrgExtBlob[][MINASN1_EXT_BLOB_CNT] ) { DWORD i; DWORD cbOID = pEncodedOIDBlob->cbData; const BYTE *pbOID = pEncodedOIDBlob->pbData;
for (i = 0; i < cExt; i++) { if (cbOID == rgrgExtBlob[i][MINASN1_EXT_OID_IDX].cbData && 0 == memcmp(pbOID, rgrgExtBlob[i][MINASN1_EXT_OID_IDX].pbData, cbOID)) return rgrgExtBlob[i]; }
return NULL; }
//+-------------------------------------------------------------------------
// Find the first attribute identified by its Encoded Object Identifier.
//
// Searches the list of parsed attributes returned by
// MinAsn1ParseAttributes().
//
// If found, returns pointer to the rgAttrBlob[MINASN1_ATTR_BLOB_CNT].
// Otherwise, returns NULL.
//--------------------------------------------------------------------------
PCRYPT_DER_BLOB WINAPI MinAsn1FindAttribute( IN PCRYPT_DER_BLOB pEncodedOIDBlob, IN DWORD cAttr, IN CRYPT_DER_BLOB rgrgAttrBlob[][MINASN1_ATTR_BLOB_CNT] ) { DWORD i; DWORD cbOID = pEncodedOIDBlob->cbData; const BYTE *pbOID = pEncodedOIDBlob->pbData;
for (i = 0; i < cAttr; i++) { if (cbOID == rgrgAttrBlob[i][MINASN1_ATTR_OID_IDX].cbData && 0 == memcmp(pbOID, rgrgAttrBlob[i][MINASN1_ATTR_OID_IDX].pbData, cbOID)) return rgrgAttrBlob[i]; }
return NULL; }
//+-------------------------------------------------------------------------
// Parses an ASN.1 encoded PKCS #7 Signed Data Message to extract and
// parse the X.509 certificates it contains.
//
// Assumes the PKCS #7 message is definite length encoded.
// Assumes PKCS #7 version 1.5, ie, not the newer CMS version.
//
// Upon input, *pcCert contains the maximum number of parsed certificates
// that can be returned. Updated with the number of certificates processed.
//
// If the encoded message was successfully parsed, TRUE is returned
// with *pcCert updated with the number of parsed certificates. Otherwise,
// FALSE is returned for a parse error.
// Returns:
// success - >= 0 => bytes skipped, length of the encoded certificates
// processed.
// failure - < 0 => negative (offset + 1) of first bad tagged value
// from beginning of message.
//
// The rgrgCertBlob[][] is updated with pointer to and length of the
// fields in the encoded certificate. See MinAsn1ParseCertificate for the
// field definitions.
//--------------------------------------------------------------------------
LONG WINAPI MinAsn1ExtractParsedCertificatesFromSignedData( IN const BYTE *pbEncoded, IN DWORD cbEncoded, IN OUT DWORD *pcCert, OUT CRYPT_DER_BLOB rgrgCertBlob[][MINASN1_CERT_BLOB_CNT] ) { LONG lSkipped; CRYPT_DER_BLOB rgSignedDataBlob[MINASN1_SIGNED_DATA_BLOB_CNT];
lSkipped = MinAsn1ParseSignedData( pbEncoded, cbEncoded, rgSignedDataBlob ); if (0 >= lSkipped) goto ParseError;
lSkipped = MinAsn1ParseSignedDataCertificates( &rgSignedDataBlob[MINASN1_SIGNED_DATA_CERTS_IDX], pcCert, rgrgCertBlob );
if (0 > lSkipped) { assert(rgSignedDataBlob[MINASN1_SIGNED_DATA_CERTS_IDX].pbData > pbEncoded); lSkipped -= rgSignedDataBlob[MINASN1_SIGNED_DATA_CERTS_IDX].pbData - pbEncoded;
goto ParseError; }
CommonReturn: return lSkipped;
ParseError: *pcCert = 0; goto CommonReturn; }
|