//+-------------------------------------------------------------------------
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1995 - 1999
//
//  File:       asn1util.cpp
//
//  Contents:   ASN.1 utility helper functions.
//
//  Functions:  Asn1UtilDecodeLength
//              Asn1UtilExtractContent
//              Asn1UtilIsPKCS7WithoutContentType
//              Asn1UtilAdjustEncodedLength
//              Asn1UtilExtractValues
//              Asn1UtilExtractPKCS7SignedDataContent
//              Asn1UtilExtractCertificateToBeSignedContent
//              Asn1UtilExtractCertificatePublicKeyInfo
//              Asn1UtilExtractKeyIdFromCertInfo
//
//  History:    04-Dec-96    philh   created from kevinr's wincrmsg version
//--------------------------------------------------------------------------

#include "global.hxx"
#include <dbgdef.h>

//+-------------------------------------------------------------------------
//  Get the number of contents octets in a BER encoding.
//
//  Parameters:
//          pcbContent - receives the number of contents octets if definite
//                       encoding, else CMSG_INDEFINITE_LENGTH
//          pbLength   - points to the first length octet
//          cbDER      - number of bytes remaining in the encoding
//
//  Returns:
//          success - the number of bytes in the length field, >0
//          failure - <0
//--------------------------------------------------------------------------
LONG
WINAPI
Asn1UtilDecodeLength(
    OUT DWORD   *pcbContent,
    IN const BYTE *pbLength,
    IN  DWORD   cbEncoded)
{
    long        i;
    BYTE        cbLength;
    const BYTE  *pb;

    if (cbEncoded < 1)
        goto TooLittleData;

    if (0x80 == *pbLength) {
        *pcbContent = CMSG_INDEFINITE_LENGTH;
        i = 1;
        goto CommonReturn;
    }

    // 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 >= cbEncoded)
            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

ErrorReturn:
    i = -1;
    goto CommonReturn;
TooLittleData:
    i = ASN1UTIL_INSUFFICIENT_DATA;
    goto CommonReturn;
TRACE_ERROR(LengthTooLargeError)
}


//+-------------------------------------------------------------------------
//  Point to the content octets in a BER-encoded blob.
//
//  Returns:
//          success - the number of bytes skipped, >=0
//          failure - <0
//
//  NB- If the blob is indefinite-length encoded, *pcbContent is set to
//  CMSG_INDEFINITE_LENGTH
//--------------------------------------------------------------------------
LONG
WINAPI
Asn1UtilExtractContent(
    IN const BYTE *pbEncoded,
    IN DWORD cbEncoded,
    OUT DWORD *pcbContent,
    OUT const BYTE **ppbContent)
{
#define TAG_MASK 0x1f
    DWORD       cbIdentifier;
    DWORD       cbContent;
    LONG        cbLength;
    LONG        lHeader;
    const BYTE  *pb = pbEncoded;

    if (0 == cbEncoded--)
        goto TooLittleData;

    // Skip over the identifier octet(s)
    if (TAG_MASK == (*pb++ & TAG_MASK)) {
        // high-tag-number form
        cbIdentifier = 2;
        while (TRUE) {
            if (0 == cbEncoded--)
                goto TooLittleData;
            if (0 == (*pb++ & 0x80))
                break;
            cbIdentifier++;
        }
    } else {
        // low-tag-number form
        cbIdentifier = 1;
    }

    if (0 > (cbLength = Asn1UtilDecodeLength( &cbContent, pb, cbEncoded))) {
        lHeader = cbLength;
        goto CommonReturn;
    }

    pb += cbLength;

    *pcbContent = cbContent;
    *ppbContent = pb;

    lHeader = cbLength + cbIdentifier;
CommonReturn:
    return lHeader;

TooLittleData:
    lHeader = ASN1UTIL_INSUFFICIENT_DATA;
    goto CommonReturn;
}

//+-------------------------------------------------------------------------
//  Returns TRUE if we believe this is a Bob special that has ommitted the
//  PKCS #7 ContentType.
//
//  For PKCS #7: an Object Identifier tag (0x06) immediately follows the
//  identifier and length octets. For a Bob special: an integer tag (0x02)
//  follows the identifier and length octets.
//--------------------------------------------------------------------------
BOOL
WINAPI
Asn1UtilIsPKCS7WithoutContentType(
    IN const BYTE *pbDER,
    IN DWORD cbDER)
{
    DWORD cbContent;
    const BYTE *pbContent;

  // Handle MappedFile Exceptions
  __try {

    if (0 < Asn1UtilExtractContent(pbDER, cbDER, &cbContent, &pbContent) &&
            (pbContent < pbDER + cbDER) &&
            (0x02 == *pbContent))
        return TRUE;
    else
        return FALSE;

  } __except(EXCEPTION_EXECUTE_HANDLER) {
    SetLastError(GetExceptionCode());
    return FALSE;
  }
}

//+-------------------------------------------------------------------------
//  Decode the Asn1 length bytes to possibly downward adjust the length.
//
//  The returned length is always <= cbDER.
//--------------------------------------------------------------------------
DWORD
WINAPI
Asn1UtilAdjustEncodedLength(
    IN const BYTE *pbDER,
    IN DWORD cbDER
    )
{
    // Decode the header to get the real length. I've seen files with extra
    // stuff.

    LONG lLen;
    DWORD cbLen;
    DWORD cbContent;
    const BYTE *pbContent;
    lLen = Asn1UtilExtractContent(pbDER, cbDER, &cbContent, &pbContent);
    if ((lLen >= 0) && (cbContent != CMSG_INDEFINITE_LENGTH)) {
        cbLen = (DWORD)lLen + cbContent;
        if (cbLen < cbDER)
            cbDER = cbLen;
        // else if (cbLen > cbDER)
        //  DER length exceeds input file
    }
    // else
    //  Can't decode DER length
            
    return cbDER;
}

//+-------------------------------------------------------------------------
//  Extract one or more tagged values from the ASN.1 encoded byte array.
//
//  Either steps into the value's content octets (ASN1UTIL_STEP_INTO_VALUE_OP)
//  or steps over the value's tag, length and content octets 
//  (ASN1UTIL_STEP_OVER_VALUE_OP or ASN1UTIL_OPTIONAL_STEP_OVER_VALUE_OP).
//
//  For tag matching, only supports single byte tags.  STEP_OVER values
//  must be definite-length encoded.
//
//  *pcValue is updated with the number of values successfully extracted.
//
//  Returns:
//      success - >= 0 => length of all values successfully extracted. For
//                        STEP_INTO, only the tag and length octets.
//      failure -  < 0 => negative (offset + 1) of first bad tagged value
//                        LastError is updated with the error.
//
//  A non-NULL rgValueBlob[] is updated with the pointer to and length of the
//  tagged value or its content octets. For OPTIONAL_STEP_OVER, if tag isn't
//  found, pbData and cbData are set to 0.  If a STEP_INTO value is
//  indefinite-length encoded, cbData is set to CMSG_INDEFINITE_LENGTH.
//  If ASN1UTIL_DEFINITE_LENGTH_FLAG is set, then, all returned lengths
//  are definite-length, ie, CMSG_INDEFINITE_LENGTH is never returned.
//
//  If ASN1UTIL_RETURN_VALUE_BLOB_FLAG is set, pbData points to
//  the tag. cbData includes the tag, length and content octets.
//
//  If ASN1UTIL_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.
//--------------------------------------------------------------------------
LONG
WINAPI
Asn1UtilExtractValues(
    IN const BYTE *pbEncoded,
    IN DWORD cbEncoded,
    IN DWORD dwFlags,
    IN OUT DWORD *pcValue,
    IN const ASN1UTIL_EXTRACT_VALUE_PARA *rgValuePara,
    OUT OPTIONAL PCRYPT_DER_BLOB rgValueBlob
    )
{
    DWORD cValue = *pcValue;
    const BYTE *pb = pbEncoded;
    DWORD cb = cbEncoded;

    DWORD iValue;
    LONG lAllValues;

    for (iValue = 0; iValue < cValue; iValue++) {
        DWORD dwParaFlags = rgValuePara[iValue].dwFlags;
        DWORD dwOp = dwParaFlags & ASN1UTIL_MASK_VALUE_OP;
        const BYTE *pbParaTag = rgValuePara[iValue].rgbTag;
        BOOL fValueBlob = (dwParaFlags & (ASN1UTIL_RETURN_VALUE_BLOB_FLAG |
                ASN1UTIL_RETURN_CONTENT_BLOB_FLAG)) && rgValueBlob;

        LONG lTagLength;
        DWORD cbContent;
        const BYTE *pbContent;
        DWORD cbValue;

        if (0 == cb) {
            if (ASN1UTIL_OPTIONAL_STEP_OVER_VALUE_OP != dwOp)
                goto TooLittleData;
            if (fValueBlob) {
                rgValueBlob[iValue].pbData = NULL;
                rgValueBlob[iValue].cbData = 0;
            }
            continue;
        }

        // Assumption: single byte tag for doing comparison
        if (pbParaTag) {
            // 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 (ASN1UTIL_OPTIONAL_STEP_OVER_VALUE_OP != dwOp)
                    goto InvalidTag;
                if (fValueBlob) {
                    rgValueBlob[iValue].pbData = NULL;
                    rgValueBlob[iValue].cbData = 0;
                }
                continue;
            }
        }

        lTagLength = Asn1UtilExtractContent(
            pb,
            cb,
            &cbContent,
            &pbContent
            );
        if (0 >= lTagLength || (DWORD) lTagLength > cb)
            goto InvalidTagOrLength;

        if (CMSG_INDEFINITE_LENGTH == cbContent) {
            if (ASN1UTIL_STEP_INTO_VALUE_OP != dwOp)
                goto UnsupportedIndefiniteLength;
            else if (fValueBlob && (dwFlags & ASN1UTIL_DEFINITE_LENGTH_FLAG))
                goto NotAllowedIndefiniteLength;
            cbValue = CMSG_INDEFINITE_LENGTH;
        } else {
            cbValue = cbContent + lTagLength;
            if (cbValue > cb)
                goto TooLittleData;
        }

        if (fValueBlob) {
            if (dwParaFlags & ASN1UTIL_RETURN_CONTENT_BLOB_FLAG) {
                rgValueBlob[iValue].pbData = (BYTE *) pbContent;
                rgValueBlob[iValue].cbData = cbContent;
            } else if (dwParaFlags & ASN1UTIL_RETURN_VALUE_BLOB_FLAG) {
                rgValueBlob[iValue].pbData = (BYTE *) pb;
                rgValueBlob[iValue].cbData = cbValue;
            }
        }

        switch (dwOp) {
            case ASN1UTIL_STEP_INTO_VALUE_OP:
                pb += lTagLength;
                cb -= lTagLength;
                break;
            case ASN1UTIL_STEP_OVER_VALUE_OP:
            case ASN1UTIL_OPTIONAL_STEP_OVER_VALUE_OP:
                pb += cbValue;
                cb -= cbValue;
                break;
            default:
                goto InvalidArg;

        }
    }

    lAllValues = (LONG)(pb - pbEncoded);
    assert((DWORD) lAllValues <= cbEncoded);

CommonReturn:
    *pcValue = iValue;
    return lAllValues;

ErrorReturn:
    lAllValues = -((LONG)(pb - pbEncoded)) - 1;
    goto CommonReturn;

SET_ERROR(TooLittleData, ERROR_INVALID_DATA)
SET_ERROR(InvalidTag, ERROR_INVALID_DATA)
SET_ERROR(InvalidTagOrLength, ERROR_INVALID_DATA)
SET_ERROR(InvalidArg, E_INVALIDARG)
SET_ERROR(UnsupportedIndefiniteLength, ERROR_INVALID_DATA)
SET_ERROR(NotAllowedIndefiniteLength, ERROR_INVALID_DATA)
}

static const BYTE rgbSeqTag[] = {ASN1UTIL_TAG_SEQ, 0};
static const BYTE rgbSetTag[] = {ASN1UTIL_TAG_SET, 0};
static const BYTE rgbOIDTag[] = {ASN1UTIL_TAG_OID, 0};
static const BYTE rgbIntegerTag[] = {ASN1UTIL_TAG_INTEGER, 0};
static const BYTE rgbBitStringTag[] = {ASN1UTIL_TAG_BITSTRING, 0};
static const BYTE rgbConstructedContext0Tag[] =
    {ASN1UTIL_TAG_CONSTRUCTED_CONTEXT_0, 0};

static const ASN1UTIL_EXTRACT_VALUE_PARA rgExtractSignedDataContentPara[] = {
    // 0 - ContentInfo ::= SEQUENCE {
    ASN1UTIL_STEP_INTO_VALUE_OP, rgbSeqTag,
    //   1 - contentType ContentType,
    ASN1UTIL_RETURN_CONTENT_BLOB_FLAG |
        ASN1UTIL_STEP_OVER_VALUE_OP, rgbOIDTag,
    //   2 - content  [0] EXPLICIT ANY -- OPTIONAL
    ASN1UTIL_STEP_INTO_VALUE_OP, rgbConstructedContext0Tag,
    //     3 - SignedData ::= SEQUENCE {
    ASN1UTIL_STEP_INTO_VALUE_OP, rgbSeqTag,
    //       4 - version             INTEGER,
    ASN1UTIL_RETURN_CONTENT_BLOB_FLAG |
        ASN1UTIL_STEP_OVER_VALUE_OP, rgbIntegerTag,
    //       5 - digestAlgorithms    DigestAlgorithmIdentifiers,
    ASN1UTIL_STEP_OVER_VALUE_OP, rgbSetTag,
    //       6 - ContentInfo ::= SEQUENCE {
    ASN1UTIL_RETURN_CONTENT_BLOB_FLAG |
        ASN1UTIL_STEP_INTO_VALUE_OP, rgbSeqTag,
    //       7 - contentType ContentType,
    ASN1UTIL_RETURN_CONTENT_BLOB_FLAG |
        ASN1UTIL_STEP_OVER_VALUE_OP, rgbOIDTag,
    //       8 - content  [0] EXPLICIT ANY -- OPTIONAL
    ASN1UTIL_RETURN_CONTENT_BLOB_FLAG |
        ASN1UTIL_STEP_INTO_VALUE_OP, rgbConstructedContext0Tag,
};

#define SIGNED_DATA_CONTENT_OUTER_OID_VALUE_INDEX   1
#define SIGNED_DATA_CONTENT_VERSION_VALUE_INDEX     4
#define SIGNED_DATA_CONTENT_INNER_OID_VALUE_INDEX   7
#define SIGNED_DATA_CONTENT_INFO_SEQ_VALUE_INDEX    6
#define SIGNED_DATA_CONTENT_CONTEXT_0_VALUE_INDEX   8
#define SIGNED_DATA_CONTENT_VALUE_COUNT             \
    (sizeof(rgExtractSignedDataContentPara) / \
        sizeof(rgExtractSignedDataContentPara[0]))

static const ASN1UTIL_EXTRACT_VALUE_PARA rgExtractCertSignedContent[] = {
    // 0 - SignedContent ::= SEQUENCE {
    ASN1UTIL_STEP_INTO_VALUE_OP, rgbSeqTag,
    //   1 - toBeSigned          NOCOPYANY,
    ASN1UTIL_RETURN_VALUE_BLOB_FLAG |
        ASN1UTIL_STEP_OVER_VALUE_OP, NULL,
    //   2 - algorithm           AlgorithmIdentifier,
    ASN1UTIL_STEP_OVER_VALUE_OP, rgbSeqTag,
    //   3 - signature           BITSTRING
    ASN1UTIL_STEP_OVER_VALUE_OP, rgbBitStringTag,
};

#define CERT_TO_BE_SIGNED_VALUE_INDEX               1
#define CERT_SIGNED_CONTENT_VALUE_COUNT             \
    (sizeof(rgExtractCertSignedContent) / \
        sizeof(rgExtractCertSignedContent[0]))

static const ASN1UTIL_EXTRACT_VALUE_PARA rgExtractCertPublicKeyInfo[] = {
    // 0 - SignedContent ::= SEQUENCE {
    ASN1UTIL_STEP_INTO_VALUE_OP, rgbSeqTag,
    //   1 - CertificateToBeSigned ::= SEQUENCE {
    ASN1UTIL_STEP_INTO_VALUE_OP, rgbSeqTag,
    //      2 - version                 [0] CertificateVersion DEFAULT v1,
    ASN1UTIL_OPTIONAL_STEP_OVER_VALUE_OP, rgbConstructedContext0Tag,
    //      3 - serialNumber            CertificateSerialNumber,
    ASN1UTIL_STEP_OVER_VALUE_OP, rgbIntegerTag,
    //      4 - signature               AlgorithmIdentifier,
    ASN1UTIL_STEP_OVER_VALUE_OP, rgbSeqTag,
    //      5 - issuer                  NOCOPYANY, -- really Name
    ASN1UTIL_STEP_OVER_VALUE_OP, rgbSeqTag,
    //      6 - validity                Validity,
    ASN1UTIL_STEP_OVER_VALUE_OP, rgbSeqTag,
    //      7 - subject                 NOCOPYANY, -- really Name
    ASN1UTIL_STEP_OVER_VALUE_OP, rgbSeqTag,
    //      8 - subjectPublicKeyInfo    SubjectPublicKeyInfo,
    ASN1UTIL_RETURN_VALUE_BLOB_FLAG |
        ASN1UTIL_STEP_OVER_VALUE_OP, rgbSeqTag
};

#define CERT_PUBLIC_KEY_INFO_VALUE_INDEX    8
#define CERT_PUBLIC_KEY_INFO_VALUE_COUNT        \
    (sizeof(rgExtractCertPublicKeyInfo) / \
        sizeof(rgExtractCertPublicKeyInfo[0]))


// #define szOID_RSA_signedData    "1.2.840.113549.1.7.2"
static const BYTE rgbOIDSignedData[] =
    {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02};

static const CRYPT_DER_BLOB EncodedOIDSignedData = {
    sizeof(rgbOIDSignedData), (BYTE *) rgbOIDSignedData
};

#ifdef CMS_PKCS7

// #define szOID_RSA_Data    "1.2.840.113549.1.7.1"
static const BYTE rgbOIDData[] =
    {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01};

static const CRYPT_DER_BLOB EncodedOIDData = {
    sizeof(rgbOIDData), (BYTE *) rgbOIDData
};

#endif // CMS_PKCS7


// The encoded OID only includes the content octets. Excludes the tag and
// length octets.
static BOOL CompareEncodedOID(
    IN const CRYPT_DER_BLOB *pEncodedOID1,
    IN const CRYPT_DER_BLOB *pEncodedOID2
    )
{
    if (pEncodedOID1->cbData == pEncodedOID2->cbData &&
            0 == memcmp(pEncodedOID1->pbData, pEncodedOID2->pbData,
                    pEncodedOID1->cbData))
        return TRUE;
    else
        return FALSE;
}


//+-------------------------------------------------------------------------
//  Skips past PKCS7 ASN.1 encoded values to get to the SignedData content.
//
//  Checks that the outer ContentType has the SignedData OID and optionally
//  checks the inner SignedData content's ContentType.
//
//  Returns:
//      success - the number of bytes skipped, >=0
//      failure - <0
//
//  If the SignedData content is indefinite-length encoded,
//  *pcbContent is set to CMSG_INDEFINITE_LENGTH
//--------------------------------------------------------------------------
LONG
WINAPI
Asn1UtilExtractPKCS7SignedDataContent(
    IN const BYTE *pbEncoded,
    IN DWORD cbEncoded,
    IN OPTIONAL const CRYPT_DER_BLOB *pEncodedInnerOID,
    OUT DWORD *pcbContent,
    OUT const BYTE **ppbContent
    )
{
    LONG lSkipped;
    DWORD cValue;
    CRYPT_DER_BLOB rgValueBlob[SIGNED_DATA_CONTENT_VALUE_COUNT];

    DWORD cbContent;
    const BYTE *pbContent;
    DWORD cbSeq;
    const BYTE *pbSeq;

    cValue = SIGNED_DATA_CONTENT_VALUE_COUNT;
    if (0 >= (lSkipped = Asn1UtilExtractValues(
            pbEncoded,
            cbEncoded,
            0,                  // dwFlags
            &cValue,
            rgExtractSignedDataContentPara,
            rgValueBlob
            )))
        goto ExtractValuesError;

    pbContent = rgValueBlob[SIGNED_DATA_CONTENT_CONTEXT_0_VALUE_INDEX].pbData;
    cbContent = rgValueBlob[SIGNED_DATA_CONTENT_CONTEXT_0_VALUE_INDEX].cbData;

    // For definite-length encoded, check that the content wasn't
    // omitted.
    //
    // Note, for indefinite-length encoding, if the content was omitted,
    // we would have had a 0 tag instead of the CONTEXT_0 tag.
    cbSeq = rgValueBlob[SIGNED_DATA_CONTENT_INFO_SEQ_VALUE_INDEX].cbData;
    pbSeq = rgValueBlob[SIGNED_DATA_CONTENT_INFO_SEQ_VALUE_INDEX].pbData;
    if (CMSG_INDEFINITE_LENGTH != cbSeq && pbContent >= (pbSeq + cbSeq))
        goto NoSignedDataError;

#ifdef CMS_PKCS7
    // For V3 SignedData, non Data types are wrapped, encapsulated with a
    // OCTET string
    if (1 == rgValueBlob[SIGNED_DATA_CONTENT_VERSION_VALUE_INDEX].cbData &&
            CMSG_SIGNED_DATA_V3 <=
                *(rgValueBlob[SIGNED_DATA_CONTENT_VERSION_VALUE_INDEX].pbData)
                        &&
            !CompareEncodedOID(
                &rgValueBlob[SIGNED_DATA_CONTENT_INNER_OID_VALUE_INDEX],
                &EncodedOIDData)
                        &&
            0 != cbContent && ASN1UTIL_TAG_OCTETSTRING == *pbContent
            ) {
        LONG lTagLength;
        const BYTE *pbInner;
        DWORD cbInner;

        // Advance past the outer OCTET wrapper
        lTagLength = Asn1UtilExtractContent(
            pbContent,
            cbEncoded - lSkipped,
            &cbInner,
            &pbInner
            );
        if (0 < lTagLength) {
            lSkipped += lTagLength;
            cbContent = cbInner;
            pbContent = pbInner;
        }
    }
#endif

    if (CMSG_INDEFINITE_LENGTH == cbContent) {
        // Extract the pbContent and attempt to get its definite length

        LONG lTagLength;
        const BYTE *pbInner;
        DWORD cbInner;

        lTagLength = Asn1UtilExtractContent(
            pbContent,
            cbEncoded - lSkipped,
            &cbInner,
            &pbInner
            );
        if (0 < lTagLength && CMSG_INDEFINITE_LENGTH != cbInner)
            cbContent = cbInner + lTagLength;
    }

    // Verify that the outer ContentType is SignedData and the inner
    // ContentType is the specified type
    if (!CompareEncodedOID(
            &rgValueBlob[SIGNED_DATA_CONTENT_OUTER_OID_VALUE_INDEX],
            &EncodedOIDSignedData
            ))
        goto NotSignedDataContentType;
    if (pEncodedInnerOID && !CompareEncodedOID(
            &rgValueBlob[SIGNED_DATA_CONTENT_INNER_OID_VALUE_INDEX],
            pEncodedInnerOID
            ))
        goto UnexpectedInnerContentTypeError;

    *pcbContent = cbContent;
    *ppbContent = pbContent;

CommonReturn:
    return lSkipped;

ErrorReturn:
    if (0 <= lSkipped)
        lSkipped = -lSkipped - 1;
    *pcbContent = 0;
    *ppbContent = NULL;
    goto CommonReturn;

TRACE_ERROR(ExtractValuesError)
SET_ERROR(NoSignedDataError, ERROR_INVALID_DATA)
SET_ERROR(NotSignedDataContentType, ERROR_INVALID_DATA)
SET_ERROR(UnexpectedInnerContentTypeError, ERROR_INVALID_DATA)
}

//+-------------------------------------------------------------------------
//  Verifies this is a certificate ASN.1 encoded signed content.
//  Returns the pointer to and length of the ToBeSigned content.
//
//  Returns an error if the ToBeSigned content isn't definite length
//  encoded.
//--------------------------------------------------------------------------
BOOL
WINAPI
Asn1UtilExtractCertificateToBeSignedContent(
    IN const BYTE *pbEncoded,
    IN DWORD cbEncoded,
    OUT DWORD *pcbContent,
    OUT const BYTE **ppbContent
    )
{
    BOOL fResult;
    DWORD cValue;
    CRYPT_DER_BLOB rgValueBlob[CERT_SIGNED_CONTENT_VALUE_COUNT];

    cValue = CERT_SIGNED_CONTENT_VALUE_COUNT;
    if (0 >= Asn1UtilExtractValues(
            pbEncoded,
            cbEncoded,
            ASN1UTIL_DEFINITE_LENGTH_FLAG,
            &cValue,
            rgExtractCertSignedContent,
            rgValueBlob
            ))
        goto ExtractValuesError;

    *ppbContent = rgValueBlob[CERT_TO_BE_SIGNED_VALUE_INDEX].pbData;
    *pcbContent = rgValueBlob[CERT_TO_BE_SIGNED_VALUE_INDEX].cbData;

    fResult = TRUE;
CommonReturn:
    return fResult;

ErrorReturn:
    fResult = FALSE;
    *ppbContent = NULL;
    *pcbContent = 0;
    goto CommonReturn;

TRACE_ERROR(ExtractValuesError)
}

//+-------------------------------------------------------------------------
//  Returns the pointer to and length of the SubjectPublicKeyInfo value in
//  a signed and encoded X.509 certificate.
//
//  Returns an error if the value isn't definite length encoded.
//--------------------------------------------------------------------------
BOOL
WINAPI
Asn1UtilExtractCertificatePublicKeyInfo(
    IN const BYTE *pbEncoded,
    IN DWORD cbEncoded,
    OUT DWORD *pcbPublicKeyInfo,
    OUT const BYTE **ppbPublicKeyInfo
    )
{
    BOOL fResult;
    DWORD cValue;
    CRYPT_DER_BLOB rgValueBlob[CERT_PUBLIC_KEY_INFO_VALUE_COUNT];

    cValue = CERT_PUBLIC_KEY_INFO_VALUE_COUNT;
    if (0 >= Asn1UtilExtractValues(
            pbEncoded,
            cbEncoded,
            ASN1UTIL_DEFINITE_LENGTH_FLAG,
            &cValue,
            rgExtractCertPublicKeyInfo,
            rgValueBlob
            ))
        goto ExtractValuesError;

    *ppbPublicKeyInfo = rgValueBlob[CERT_PUBLIC_KEY_INFO_VALUE_INDEX].pbData;
    *pcbPublicKeyInfo = rgValueBlob[CERT_PUBLIC_KEY_INFO_VALUE_INDEX].cbData;

    fResult = TRUE;
CommonReturn:
    return fResult;

ErrorReturn:
    fResult = FALSE;
    *ppbPublicKeyInfo = NULL;
    *pcbPublicKeyInfo = 0;
    goto CommonReturn;

TRACE_ERROR(ExtractValuesError)
}


// Special RDN containing the KEY_ID. Its value type is CERT_RDN_OCTET_STRING.
//#define szOID_KEYID_RDN                     "1.3.6.1.4.1.311.10.7.1"
static const BYTE rgbOIDKeyIdRDN[] =
    {0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0A, 0x07, 0x01};

static const CRYPT_DER_BLOB EncodedOIDKeyIdRDN = {
    sizeof(rgbOIDKeyIdRDN), (BYTE *) rgbOIDKeyIdRDN
};

static const BYTE rgbOctetStringTag[] = {ASN1UTIL_TAG_OCTETSTRING, 0};

static const ASN1UTIL_EXTRACT_VALUE_PARA rgExtractNameKeyIdRDNPara[] = {
    // 0 - Name ::= SEQUENCE OF RelativeDistinguishedName
    ASN1UTIL_STEP_INTO_VALUE_OP, rgbSeqTag,
    //   1 - RelativeDistinguishedName ::= SET OF AttributeTypeValue
    ASN1UTIL_STEP_INTO_VALUE_OP, rgbSetTag,
    //     2 - AttributeTypeValue ::= SEQUENCE
    ASN1UTIL_STEP_INTO_VALUE_OP, rgbSeqTag,
    //       3 - type       EncodedObjectID,
    ASN1UTIL_RETURN_CONTENT_BLOB_FLAG |
        ASN1UTIL_STEP_OVER_VALUE_OP, rgbOIDTag,
    //       4 - value      NOCOPYANY 
    ASN1UTIL_RETURN_CONTENT_BLOB_FLAG |
        ASN1UTIL_STEP_OVER_VALUE_OP, rgbOctetStringTag,
};

#define NAME_KEYID_RDN_OID_VALUE_INDEX              3
#define NAME_KEYID_RDN_OCTET_VALUE_INDEX            4
#define NAME_KEYID_RDN_VALUE_COUNT                  \
    (sizeof(rgExtractNameKeyIdRDNPara) / \
        sizeof(rgExtractNameKeyIdRDNPara[0]))


//+-------------------------------------------------------------------------
//  If the Issuer and SerialNumber in the CERT_INFO contains a special
//  KeyID RDN attribute returns TRUE with pKeyId's cbData and pbData updated
//  with the RDN attribute's OCTET_STRING value. Otherwise, returns FALSE.
//--------------------------------------------------------------------------
BOOL
WINAPI
Asn1UtilExtractKeyIdFromCertInfo(
    IN PCERT_INFO pCertInfo,
    OUT PCRYPT_HASH_BLOB pKeyId
    )
{
    DWORD cValue;
    CRYPT_DER_BLOB rgValueBlob[NAME_KEYID_RDN_VALUE_COUNT];

    if (0 == pCertInfo->SerialNumber.cbData ||
            0 != *pCertInfo->SerialNumber.pbData)
        goto NoKeyId;

    cValue = NAME_KEYID_RDN_VALUE_COUNT;
    if (0 > Asn1UtilExtractValues(
            pCertInfo->Issuer.pbData,
            pCertInfo->Issuer.cbData,
            0,                  // dwFlags
            &cValue,
            rgExtractNameKeyIdRDNPara,
            rgValueBlob
            ))
        goto NoKeyId;

    if (!CompareEncodedOID(
            &rgValueBlob[NAME_KEYID_RDN_OID_VALUE_INDEX],
            &EncodedOIDKeyIdRDN))
        goto NoKeyId;

    if (CMSG_INDEFINITE_LENGTH ==
            rgValueBlob[NAME_KEYID_RDN_OCTET_VALUE_INDEX].cbData)
        goto NoKeyId;

    pKeyId->pbData = rgValueBlob[NAME_KEYID_RDN_OCTET_VALUE_INDEX].pbData;
    pKeyId->cbData = rgValueBlob[NAME_KEYID_RDN_OCTET_VALUE_INDEX].cbData;
    return TRUE;

NoKeyId:
    pKeyId->pbData = NULL;
    pKeyId->cbData = 0;
    return FALSE;
}