Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

517 lines
16 KiB

//+-------------------------------------------------------------------------
// 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;
}