//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1995 - 1999
//
//  File:       oidinfo.cpp
//
//  Contents:   Cryptographic Object ID (OID) Info Functions
//
//  Functions:  I_CryptOIDInfoDllMain
//              CryptFindOIDInfo
//              CryptRegisterOIDInfo
//              CryptUnregisterOIDInfo
//              CryptEnumOIDInfo
//              CryptFindLocalizedName
//
//  Comments:
//
//  History:    24-May-97    philh   created
//--------------------------------------------------------------------------

#include "global.hxx"
#include <dbgdef.h>
#include "wintrust.h"   // wintrust.h is needed for SPC_ oids
#include "chain.h"      // chain.h is needed for ChainRetrieveObjectByUrlW()
                        // and ChainIsConnected()
#include "certca.h"     // certca.h is needed for CAOIDGetLdapURL()

// Initialized in I_CryptOIDInfoDllMain at ProcessAttach
static HMODULE hOIDInfoInst;

#define MAX_RESOURCE_OID_NAME_LENGTH    256
static LPCWSTR pwszNullName = L"";

#define LEN_ALIGN(Len)  ((Len + 7) & ~7)

#define CONST_OID_GROUP_PREFIX_CHAR    '!'
#define OID_INFO_ENCODING_TYPE          0
#define OID_INFO_NAME_VALUE_NAME        L"Name"
#define OID_INFO_ALGID_VALUE_NAME       L"Algid"
#define OID_INFO_EXTRA_INFO_VALUE_NAME  L"ExtraInfo"
#define OID_INFO_FLAGS_VALUE_NAME       L"Flags"



//+=========================================================================
//  OID Information Tables (by GROUP_ID)
//==========================================================================

#define OID_INFO_LEN sizeof(CRYPT_OID_INFO)

//+-------------------------------------------------------------------------
//  Hash Algorithm Table
//--------------------------------------------------------------------------
#define HASH_ALG_ENTRY(pszOID, pwszName, Algid) \
    OID_INFO_LEN, pszOID, pwszName, CRYPT_HASH_ALG_OID_GROUP_ID, Algid, 0, NULL

static CCRYPT_OID_INFO HashAlgTable[] = {
    HASH_ALG_ENTRY(szOID_OIWSEC_sha1, L"sha1", CALG_SHA1),
    HASH_ALG_ENTRY(szOID_OIWSEC_sha1, L"sha", CALG_SHA1),
    HASH_ALG_ENTRY(szOID_OIWSEC_sha, L"sha", CALG_SHA),
    HASH_ALG_ENTRY(szOID_RSA_MD5, L"md5", CALG_MD5),
    HASH_ALG_ENTRY(szOID_RSA_MD4, L"md4", CALG_MD4),
    HASH_ALG_ENTRY(szOID_RSA_MD2, L"md2", CALG_MD2)
};
#define HASH_ALG_CNT (sizeof(HashAlgTable) / sizeof(HashAlgTable[0]))


//+-------------------------------------------------------------------------
//  Encryption Algorithm Table
//--------------------------------------------------------------------------
#define ENCRYPT_ALG_ENTRY(pszOID, pwszName, Algid) \
    OID_INFO_LEN, pszOID, pwszName, CRYPT_ENCRYPT_ALG_OID_GROUP_ID, \
    Algid, 0, NULL

static CCRYPT_OID_INFO EncryptAlgTable[] = {
    ENCRYPT_ALG_ENTRY(szOID_OIWSEC_desCBC, L"des", CALG_DES),
    ENCRYPT_ALG_ENTRY(szOID_RSA_DES_EDE3_CBC, L"3des", CALG_3DES),
    ENCRYPT_ALG_ENTRY(szOID_RSA_RC2CBC, L"rc2", CALG_RC2),
    ENCRYPT_ALG_ENTRY(szOID_RSA_RC4, L"rc4", CALG_RC4),
#ifdef CMS_PKCS7
    ENCRYPT_ALG_ENTRY(szOID_RSA_SMIMEalgCMS3DESwrap, L"CMS3DESwrap", CALG_3DES),
    ENCRYPT_ALG_ENTRY(szOID_RSA_SMIMEalgCMSRC2wrap, L"CMSRC2wrap", CALG_RC2),
#endif  // CMS_PKCS7
};
#define ENCRYPT_ALG_CNT (sizeof(EncryptAlgTable) / sizeof(EncryptAlgTable[0]))


//+-------------------------------------------------------------------------
//  Public Key Algorithm Table
//--------------------------------------------------------------------------
static const DWORD dwMosaicFlags = CRYPT_OID_INHIBIT_SIGNATURE_FORMAT_FLAG |
                                        CRYPT_OID_NO_NULL_ALGORITHM_PARA_FLAG;

static const DWORD dwNoNullParaFlag = CRYPT_OID_NO_NULL_ALGORITHM_PARA_FLAG;

#define PUBKEY_ALG_ENTRY(pszOID, pwszName, Algid) \
    OID_INFO_LEN, pszOID, pwszName, CRYPT_PUBKEY_ALG_OID_GROUP_ID, \
    Algid, 0, NULL

#define PUBKEY_EXTRA_ALG_ENTRY(pszOID, pwszName, Algid, dwFlags) \
    OID_INFO_LEN, pszOID, pwszName, CRYPT_PUBKEY_ALG_OID_GROUP_ID, \
    Algid, sizeof(dwFlags), (BYTE *) &dwFlags

#define DSA_PUBKEY_ALG_ENTRY(pszOID, pwszName) \
    PUBKEY_EXTRA_ALG_ENTRY(pszOID, pwszName, CALG_DSS_SIGN, dwNoNullParaFlag)

#define DH_PUBKEY_ALG_ENTRY(pszOID, pwszName) \
    PUBKEY_EXTRA_ALG_ENTRY(pszOID, pwszName, CALG_DH_SF, dwNoNullParaFlag)

#ifdef CMS_PKCS7
#define ESDH_PUBKEY_ALG_ENTRY(pszOID, pwszName) \
    PUBKEY_EXTRA_ALG_ENTRY(pszOID, pwszName, CALG_DH_EPHEM, dwNoNullParaFlag)
#endif  // CMS_PKCS7

static CCRYPT_OID_INFO PubKeyAlgTable[] = {
    PUBKEY_ALG_ENTRY(szOID_RSA_RSA, L"RSA", CALG_RSA_KEYX),
    DSA_PUBKEY_ALG_ENTRY(szOID_X957_DSA, L"DSA"),
    DH_PUBKEY_ALG_ENTRY(szOID_ANSI_X942_DH, L"DH"),
    PUBKEY_ALG_ENTRY(szOID_RSA_RSA, L"RSA_KEYX", CALG_RSA_KEYX),
    PUBKEY_ALG_ENTRY(szOID_RSA_RSA, L"RSA", CALG_RSA_SIGN),
    PUBKEY_ALG_ENTRY(szOID_RSA_RSA, L"RSA_SIGN", CALG_RSA_SIGN),
    DSA_PUBKEY_ALG_ENTRY(szOID_OIWSEC_dsa, L"DSA"),
    DSA_PUBKEY_ALG_ENTRY(szOID_OIWSEC_dsa, L"DSS"),
    DSA_PUBKEY_ALG_ENTRY(szOID_OIWSEC_dsa, L"DSA_SIGN"),
    DH_PUBKEY_ALG_ENTRY(szOID_RSA_DH, L"DH"),
    PUBKEY_ALG_ENTRY(szOID_OIWSEC_rsaXchg, L"RSA_KEYX", CALG_RSA_KEYX),
    PUBKEY_EXTRA_ALG_ENTRY(szOID_INFOSEC_mosaicKMandUpdSig,
        L"mosaicKMandUpdSig", CALG_DSS_SIGN, dwMosaicFlags),
#ifdef CMS_PKCS7
    ESDH_PUBKEY_ALG_ENTRY(szOID_RSA_SMIMEalgESDH, L"ESDH"),
#endif  // CMS_PKCS7
    PUBKEY_ALG_ENTRY(szOID_PKIX_NO_SIGNATURE, L"NO_SIGN", CALG_NO_SIGN),
};
#define PUBKEY_ALG_CNT (sizeof(PubKeyAlgTable) / sizeof(PubKeyAlgTable[0]))


//+-------------------------------------------------------------------------
//  Signature Algorithm Table
//--------------------------------------------------------------------------
static const ALG_ID aiRsaPubKey = CALG_RSA_SIGN;
static const DWORD rgdwRsaSha2Sign[] = {
	CALG_RSA_SIGN, 0, PROV_RSA_AES
};
static const DWORD rgdwMosaicSign[] = {
    CALG_DSS_SIGN,
    CRYPT_OID_INHIBIT_SIGNATURE_FORMAT_FLAG |
        CRYPT_OID_NO_NULL_ALGORITHM_PARA_FLAG
};
static const DWORD rgdwDssSign[] = {
    CALG_DSS_SIGN,
    CRYPT_OID_NO_NULL_ALGORITHM_PARA_FLAG
	//OID_INFO_LEN, pszOID, pwszName, CRYPT_SIGN_ALG_OID_GROUP_ID, aiHash, \ sizeof(rgdwExtra), (BYTE *) rgdwExtra
};

#define SIGN_ALG_ENTRY(pszOID, pwszName, aiHash, aiPubKey) \
    OID_INFO_LEN, pszOID, pwszName, CRYPT_SIGN_ALG_OID_GROUP_ID, aiHash, \
    sizeof(aiPubKey), (BYTE *) &aiPubKey

#define RSA_SIGN_ALG_ENTRY(pszOID, pwszName, aiHash) \
    SIGN_ALG_ENTRY(pszOID, pwszName, aiHash, aiRsaPubKey)

#define SIGN_EXTRA_ALG_ENTRY(pszOID, pwszName, aiHash, rgdwExtra) \
    OID_INFO_LEN, pszOID, pwszName, CRYPT_SIGN_ALG_OID_GROUP_ID, aiHash, \
    sizeof(rgdwExtra), (BYTE *) rgdwExtra

#define RSA_SHA2_SIGN_ALG_ENTRY(pszOID, pwszName, aiHash) \
	SIGN_EXTRA_ALG_ENTRY(pszOID, pwszName, aiHash, rgdwRsaSha2Sign)
	
#define DSS_SIGN_ALG_ENTRY(pszOID, pwszName) \
    SIGN_EXTRA_ALG_ENTRY(pszOID, pwszName, CALG_SHA1, rgdwDssSign)

static CCRYPT_OID_INFO SignAlgTable[] = {
    RSA_SIGN_ALG_ENTRY(szOID_RSA_SHA1RSA, L"sha1RSA", CALG_SHA1),
    RSA_SIGN_ALG_ENTRY(szOID_RSA_MD5RSA, L"md5RSA", CALG_MD5),
	RSA_SHA2_SIGN_ALG_ENTRY("1.2.840.113549.1.1.11", L"sha256RSA", CALG_SHA_256),
	RSA_SHA2_SIGN_ALG_ENTRY("1.2.840.113549.1.1.12", L"sha384RSA", CALG_SHA_384),
	RSA_SHA2_SIGN_ALG_ENTRY("1.2.840.113549.1.1.13", L"sha512RSA", CALG_SHA_512),
    DSS_SIGN_ALG_ENTRY(szOID_X957_SHA1DSA, L"sha1DSA"),
    RSA_SIGN_ALG_ENTRY(szOID_OIWSEC_sha1RSASign, L"sha1RSA", CALG_SHA1),
    RSA_SIGN_ALG_ENTRY(szOID_OIWSEC_sha1RSASign, L"shaRSA", CALG_SHA1),
    RSA_SIGN_ALG_ENTRY(szOID_OIWSEC_shaRSA, L"shaRSA", CALG_SHA),
    RSA_SIGN_ALG_ENTRY(szOID_OIWSEC_md5RSA, L"md5RSA", CALG_MD5),
    RSA_SIGN_ALG_ENTRY(szOID_RSA_MD2RSA, L"md2RSA", CALG_MD2),
    RSA_SIGN_ALG_ENTRY(szOID_RSA_MD4RSA, L"md4RSA", CALG_MD4),
    RSA_SIGN_ALG_ENTRY(szOID_OIWSEC_md4RSA, L"md4RSA", CALG_MD4),
    RSA_SIGN_ALG_ENTRY(szOID_OIWSEC_md4RSA2, L"md4RSA", CALG_MD4),
    RSA_SIGN_ALG_ENTRY(szOID_OIWDIR_md2RSA, L"md2RSA", CALG_MD2),
    DSS_SIGN_ALG_ENTRY(szOID_OIWSEC_shaDSA, L"sha1DSA"),
    DSS_SIGN_ALG_ENTRY(szOID_OIWSEC_shaDSA, L"shaDSA"),
    DSS_SIGN_ALG_ENTRY(szOID_OIWSEC_dsaSHA1,L"dsaSHA1"),
    SIGN_EXTRA_ALG_ENTRY(szOID_INFOSEC_mosaicUpdatedSig, L"mosaicUpdatedSig",
        CALG_SHA, rgdwMosaicSign),
};
#define SIGN_ALG_CNT (sizeof(SignAlgTable) / sizeof(SignAlgTable[0]))


//+-------------------------------------------------------------------------
//  RDN Attribute Table
//--------------------------------------------------------------------------

// PLEASE UPDATE the following define in certstr.cpp if you add a new entry
// with a longer pwszName
// #define MAX_X500_KEY_LEN    64

// Ordered lists of acceptable RDN attribute value types. 0 terminates.
static const DWORD rgdwPrintableValueType[] = { CERT_RDN_PRINTABLE_STRING, 0 };
static const DWORD rgdwIA5ValueType[] = { CERT_RDN_IA5_STRING, 0 };
static const DWORD rgdwNumericValueType[] = { CERT_RDN_NUMERIC_STRING, 0 };
static const DWORD rgdwIA5orUTF8ValueType[] = { CERT_RDN_IA5_STRING,
                                                CERT_RDN_UTF8_STRING, 0 };

#define RDN_ATTR_ENTRY(pszOID, pwszName, rgdwValueType) \
    OID_INFO_LEN, pszOID, pwszName, CRYPT_RDN_ATTR_OID_GROUP_ID, 0, \
    sizeof(rgdwValueType), (BYTE *) rgdwValueType
#define DEFAULT_RDN_ATTR_ENTRY(pszOID, pwszName) \
    OID_INFO_LEN, pszOID, pwszName, CRYPT_RDN_ATTR_OID_GROUP_ID, 0, 0, NULL

static CCRYPT_OID_INFO RDNAttrTable[] = {
    // Ordered with most commonly used key names at the beginning

    // Labeling attribute types:
    DEFAULT_RDN_ATTR_ENTRY(szOID_COMMON_NAME, L"CN"),
    // Geographic attribute types:
    DEFAULT_RDN_ATTR_ENTRY(szOID_LOCALITY_NAME, L"L"),
    // Organizational attribute types:
    DEFAULT_RDN_ATTR_ENTRY(szOID_ORGANIZATION_NAME, L"O"),
    DEFAULT_RDN_ATTR_ENTRY(szOID_ORGANIZATIONAL_UNIT_NAME, L"OU"),

    // Verisign sticks the following in their cert names. Netscape uses the
    // "E" instead of the "Email". Will let "E" take precedence
    RDN_ATTR_ENTRY(szOID_RSA_emailAddr, L"E", rgdwIA5ValueType),
    RDN_ATTR_ENTRY(szOID_RSA_emailAddr, L"Email", rgdwIA5ValueType),

    // The following aren't used in Verisign's certs

    // Geographic attribute types:
    RDN_ATTR_ENTRY(szOID_COUNTRY_NAME, L"C", rgdwPrintableValueType),
    DEFAULT_RDN_ATTR_ENTRY(szOID_STATE_OR_PROVINCE_NAME, L"S"),
    DEFAULT_RDN_ATTR_ENTRY(szOID_STATE_OR_PROVINCE_NAME, L"ST"),
    DEFAULT_RDN_ATTR_ENTRY(szOID_STREET_ADDRESS, L"STREET"),

    // Organizational attribute types:
    DEFAULT_RDN_ATTR_ENTRY(szOID_TITLE, L"T"),
    DEFAULT_RDN_ATTR_ENTRY(szOID_TITLE, L"Title"),

    DEFAULT_RDN_ATTR_ENTRY(szOID_GIVEN_NAME, L"G"),
    DEFAULT_RDN_ATTR_ENTRY(szOID_GIVEN_NAME, L"GN"),
    DEFAULT_RDN_ATTR_ENTRY(szOID_GIVEN_NAME, L"GivenName"),
    DEFAULT_RDN_ATTR_ENTRY(szOID_INITIALS, L"I"),
    DEFAULT_RDN_ATTR_ENTRY(szOID_INITIALS, L"Initials"),

    // Labeling attribute types:
    DEFAULT_RDN_ATTR_ENTRY(szOID_SUR_NAME, L"SN"),
//    RDN_ATTR_ENTRY(szOID_DEVICE_SERIAL_NUMBER, L"", rgdwPrintableValueType),

    // Pilot user attribute types:
    RDN_ATTR_ENTRY(szOID_DOMAIN_COMPONENT, L"DC", rgdwIA5orUTF8ValueType),

    // Explanatory attribute types:
    DEFAULT_RDN_ATTR_ENTRY(szOID_DESCRIPTION, L"Description"),
//  szOID_SEARCH_GUIDE                  "2.5.4.14"
//    DEFAULT_RDN_ATTR_ENTRY(szOID_BUSINESS_CATEGORY, L""),

    // Postal addressing attribute types:
//  szOID_POSTAL_ADDRESS                "2.5.4.16"
    DEFAULT_RDN_ATTR_ENTRY(szOID_POSTAL_CODE, L"PostalCode"),
    DEFAULT_RDN_ATTR_ENTRY(szOID_POST_OFFICE_BOX, L"POBox"),
//    DEFAULT_RDN_ATTR_ENTRY(szOID_PHYSICAL_DELIVERY_OFFICE_NAME, L""),

    // Telecommunications addressing attribute types:
    RDN_ATTR_ENTRY(szOID_TELEPHONE_NUMBER, L"Phone", rgdwPrintableValueType),
//  szOID_TELEX_NUMBER                  "2.5.4.21"
//  szOID_TELETEXT_TERMINAL_IDENTIFIER  "2.5.4.22"
//  szOID_FACSIMILE_TELEPHONE_NUMBER    "2.5.4.23"

//  Following is used as a test case for a Numeric value
    RDN_ATTR_ENTRY(szOID_X21_ADDRESS, L"X21Address", rgdwNumericValueType),
//    RDN_ATTR_ENTRY(szOID_INTERNATIONAL_ISDN_NUMBER, L"", rgdwNumericValueType),
//  szOID_REGISTERED_ADDRESS            "2.5.4.26"
//    RDN_ATTR_ENTRY(szOID_DESTINATION_INDICATOR, L"", rgdwPrintableValueType)

    // Preference attribute types:
//  szOID_PREFERRED_DELIVERY_METHOD     "2.5.4.28"

    // OSI application attribute types:
//  szOID_PRESENTATION_ADDRESS          "2.5.4.29"
//  szOID_SUPPORTED_APPLICATION_CONTEXT "2.5.4.30"

    // Relational application attribute types:
//  szOID_MEMBER                        "2.5.4.31"
//  szOID_OWNER                         "2.5.4.32"
//  szOID_ROLE_OCCUPANT                 "2.5.4.33"
//  szOID_SEE_ALSO                      "2.5.4.34"

    // Security attribute types:
//  szOID_USER_PASSWORD                 "2.5.4.35"
//  szOID_USER_CERTIFICATE              "2.5.4.36"
//  szOID_CA_CERTIFICATE                "2.5.4.37"
//  szOID_AUTHORITY_REVOCATION_LIST     "2.5.4.38"
//  szOID_CERTIFICATE_REVOCATION_LIST   "2.5.4.39"
//  szOID_CROSS_CERTIFICATE_PAIR        "2.5.4.40"

    // Undocumented attribute types???
//#define szOID_???                         "2.5.4.41"

    DEFAULT_RDN_ATTR_ENTRY(szOID_DN_QUALIFIER, L"dnQualifier"),
};
#define RDN_ATTR_CNT (sizeof(RDNAttrTable) / sizeof(RDNAttrTable[0]))

//+-------------------------------------------------------------------------
//  Extension or Attribute Table (Localized via resource strings)
//--------------------------------------------------------------------------
#define EXT_ATTR_ENTRY(pszOID, ResourceIdORpwszName) \
    OID_INFO_LEN, pszOID, (LPCWSTR) ResourceIdORpwszName, \
        CRYPT_EXT_OR_ATTR_OID_GROUP_ID, 0, 0, NULL

static CRYPT_OID_INFO ExtAttrTable[] = {
    EXT_ATTR_ENTRY(szOID_AUTHORITY_KEY_IDENTIFIER2,
        IDS_EXT_AUTHORITY_KEY_IDENTIFIER),
    EXT_ATTR_ENTRY(szOID_AUTHORITY_KEY_IDENTIFIER,
        IDS_EXT_AUTHORITY_KEY_IDENTIFIER),
    EXT_ATTR_ENTRY(szOID_KEY_ATTRIBUTES,
        IDS_EXT_KEY_ATTRIBUTES),
    EXT_ATTR_ENTRY(szOID_KEY_USAGE_RESTRICTION,
        IDS_EXT_KEY_USAGE_RESTRICTION),
    EXT_ATTR_ENTRY(szOID_SUBJECT_ALT_NAME2,
        IDS_EXT_SUBJECT_ALT_NAME),
    EXT_ATTR_ENTRY(szOID_SUBJECT_ALT_NAME,
        IDS_EXT_SUBJECT_ALT_NAME),
    EXT_ATTR_ENTRY(szOID_ISSUER_ALT_NAME2,
        IDS_EXT_ISSUER_ALT_NAME),
    EXT_ATTR_ENTRY(szOID_ISSUER_ALT_NAME,
        IDS_EXT_ISSUER_ALT_NAME),
    EXT_ATTR_ENTRY(szOID_BASIC_CONSTRAINTS2,
        IDS_EXT_BASIC_CONSTRAINTS),
    EXT_ATTR_ENTRY(szOID_BASIC_CONSTRAINTS,
        IDS_EXT_BASIC_CONSTRAINTS),
    EXT_ATTR_ENTRY(szOID_KEY_USAGE,
        IDS_EXT_KEY_USAGE),
    EXT_ATTR_ENTRY(szOID_CERT_POLICIES,
        IDS_EXT_CERT_POLICIES),
    EXT_ATTR_ENTRY(szOID_SUBJECT_KEY_IDENTIFIER,
        IDS_EXT_SUBJECT_KEY_IDENTIFIER),
    EXT_ATTR_ENTRY(szOID_CRL_REASON_CODE,
        IDS_EXT_CRL_REASON_CODE),
    EXT_ATTR_ENTRY(szOID_CRL_DIST_POINTS,
        IDS_EXT_CRL_DIST_POINTS),
    EXT_ATTR_ENTRY(szOID_ENHANCED_KEY_USAGE,
        IDS_EXT_ENHANCED_KEY_USAGE),
    EXT_ATTR_ENTRY(szOID_AUTHORITY_INFO_ACCESS,
        IDS_EXT_AUTHORITY_INFO_ACCESS),
    EXT_ATTR_ENTRY(szOID_CERT_EXTENSIONS,
        IDS_EXT_CERT_EXTENSIONS),
    EXT_ATTR_ENTRY(szOID_RSA_certExtensions,
        IDS_EXT_CERT_EXTENSIONS),
    EXT_ATTR_ENTRY(szOID_NEXT_UPDATE_LOCATION,
        IDS_EXT_NEXT_UPDATE_LOCATION),
    EXT_ATTR_ENTRY(szOID_YESNO_TRUST_ATTR,
        IDS_EXT_YESNO_TRUST_ATTR),
    EXT_ATTR_ENTRY(szOID_RSA_emailAddr,
        IDS_EXT_RSA_emailAddr),
    EXT_ATTR_ENTRY(szOID_RSA_unstructName,
        IDS_EXT_RSA_unstructName),
    EXT_ATTR_ENTRY(szOID_RSA_contentType,
        IDS_EXT_RSA_contentType),
    EXT_ATTR_ENTRY(szOID_RSA_messageDigest,
        IDS_EXT_RSA_messageDigest),
    EXT_ATTR_ENTRY(szOID_RSA_signingTime,
        IDS_EXT_RSA_signingTime),
    EXT_ATTR_ENTRY(szOID_RSA_counterSign,
        IDS_EXT_RSA_counterSign),
    EXT_ATTR_ENTRY(szOID_RSA_challengePwd,
        IDS_EXT_RSA_challengePwd),
    EXT_ATTR_ENTRY(szOID_RSA_unstructAddr,
        IDS_EXT_RSA_unstructAddr),
    EXT_ATTR_ENTRY(szOID_RSA_extCertAttrs, L""),
    EXT_ATTR_ENTRY(szOID_RSA_SMIMECapabilities,
        IDS_EXT_RSA_SMIMECapabilities),
    EXT_ATTR_ENTRY(szOID_RSA_preferSignedData,
        IDS_EXT_RSA_preferSignedData),
    EXT_ATTR_ENTRY(szOID_PKIX_POLICY_QUALIFIER_CPS,
        IDS_EXT_PKIX_POLICY_QUALIFIER_CPS),
    EXT_ATTR_ENTRY(szOID_PKIX_POLICY_QUALIFIER_USERNOTICE,
        IDS_EXT_PKIX_POLICY_QUALIFIER_USERNOTICE),
    EXT_ATTR_ENTRY(szOID_PKIX_OCSP,
        IDS_EXT_PKIX_OCSP),
    EXT_ATTR_ENTRY(szOID_PKIX_CA_ISSUERS,
        IDS_EXT_PKIX_CA_ISSUERS),
    EXT_ATTR_ENTRY(szOID_ENROLL_CERTTYPE_EXTENSION,
        IDS_EXT_MS_CERTIFICATE_TEMPLATE),
    EXT_ATTR_ENTRY(szOID_ENROLL_CERTTYPE_EXTENSION,
        IDS_EXT_ENROLL_CERTTYPE),
    EXT_ATTR_ENTRY(szOID_CERT_MANIFOLD,
        IDS_EXT_CERT_MANIFOLD),
    EXT_ATTR_ENTRY(szOID_NETSCAPE_CERT_TYPE,
        IDS_EXT_NETSCAPE_CERT_TYPE),
    EXT_ATTR_ENTRY(szOID_NETSCAPE_BASE_URL,
        IDS_EXT_NETSCAPE_BASE_URL),
    EXT_ATTR_ENTRY(szOID_NETSCAPE_REVOCATION_URL,
        IDS_EXT_NETSCAPE_REVOCATION_URL),
    EXT_ATTR_ENTRY(szOID_NETSCAPE_CA_REVOCATION_URL,
        IDS_EXT_NETSCAPE_CA_REVOCATION_URL),
    EXT_ATTR_ENTRY(szOID_NETSCAPE_CERT_RENEWAL_URL,
        IDS_EXT_NETSCAPE_CERT_RENEWAL_URL),
    EXT_ATTR_ENTRY(szOID_NETSCAPE_CA_POLICY_URL,
        IDS_EXT_NETSCAPE_CA_POLICY_URL),
    EXT_ATTR_ENTRY(szOID_NETSCAPE_SSL_SERVER_NAME,
        IDS_EXT_NETSCAPE_SSL_SERVER_NAME),
    EXT_ATTR_ENTRY(szOID_NETSCAPE_COMMENT,
        IDS_EXT_NETSCAPE_COMMENT),
    EXT_ATTR_ENTRY(SPC_SP_AGENCY_INFO_OBJID,
        IDS_EXT_SPC_SP_AGENCY_INFO_OBJID),
    EXT_ATTR_ENTRY(SPC_FINANCIAL_CRITERIA_OBJID,
        IDS_EXT_SPC_FINANCIAL_CRITERIA_OBJID),
    EXT_ATTR_ENTRY(SPC_MINIMAL_CRITERIA_OBJID,
        IDS_EXT_SPC_MINIMAL_CRITERIA_OBJID),
    EXT_ATTR_ENTRY(szOID_COUNTRY_NAME,
        IDS_EXT_COUNTRY_NAME),
    EXT_ATTR_ENTRY(szOID_ORGANIZATION_NAME,
        IDS_EXT_ORGANIZATION_NAME),
    EXT_ATTR_ENTRY(szOID_ORGANIZATIONAL_UNIT_NAME,
        IDS_EXT_ORGANIZATIONAL_UNIT_NAME),
    EXT_ATTR_ENTRY(szOID_COMMON_NAME,
        IDS_EXT_COMMON_NAME),
    EXT_ATTR_ENTRY(szOID_LOCALITY_NAME,
        IDS_EXT_LOCALITY_NAME),
    EXT_ATTR_ENTRY(szOID_STATE_OR_PROVINCE_NAME,
        IDS_EXT_STATE_OR_PROVINCE_NAME),
    EXT_ATTR_ENTRY(szOID_TITLE,
        IDS_EXT_TITLE),
    EXT_ATTR_ENTRY(szOID_GIVEN_NAME,
        IDS_EXT_GIVEN_NAME),
    EXT_ATTR_ENTRY(szOID_INITIALS,
        IDS_EXT_INITIALS),
    EXT_ATTR_ENTRY(szOID_SUR_NAME,
        IDS_EXT_SUR_NAME),
    EXT_ATTR_ENTRY(szOID_DOMAIN_COMPONENT,
        IDS_EXT_DOMAIN_COMPONENT),
    EXT_ATTR_ENTRY(szOID_STREET_ADDRESS,
        IDS_EXT_STREET_ADDRESS),
    EXT_ATTR_ENTRY(szOID_DEVICE_SERIAL_NUMBER,
        IDS_EXT_DEVICE_SERIAL_NUMBER),
    EXT_ATTR_ENTRY(szOID_CERTSRV_CA_VERSION,
        IDS_EXT_CA_VERSION),
    EXT_ATTR_ENTRY(szOID_CERTSRV_CROSSCA_VERSION,
        IDS_EXT_CROSSCA_VERSION),
    EXT_ATTR_ENTRY(szOID_SERIALIZED,
        IDS_EXT_SERIALIZED),
    EXT_ATTR_ENTRY(szOID_NT_PRINCIPAL_NAME,
        IDS_EXT_NT_PRINCIPAL_NAME),
    EXT_ATTR_ENTRY(szOID_PRODUCT_UPDATE,
        IDS_EXT_PRODUCT_UPDATE),
    EXT_ATTR_ENTRY(szOID_ENROLLMENT_NAME_VALUE_PAIR,
        IDS_EXT_ENROLLMENT_NAME_VALUE_PAIR),
    EXT_ATTR_ENTRY(szOID_OS_VERSION,
        IDS_EXT_OS_VERSION),
    EXT_ATTR_ENTRY(szOID_ENROLLMENT_CSP_PROVIDER,
        IDS_EXT_ENROLLMENT_CSP_PROVIDER),
    EXT_ATTR_ENTRY(szOID_CRL_NUMBER,
        IDS_EXT_CRL_NUMBER),
    EXT_ATTR_ENTRY(szOID_DELTA_CRL_INDICATOR,
        IDS_EXT_DELTA_CRL_INDICATOR),
    EXT_ATTR_ENTRY(szOID_ISSUING_DIST_POINT,
        IDS_EXT_ISSUING_DIST_POINT),
    EXT_ATTR_ENTRY(szOID_FRESHEST_CRL,
        IDS_EXT_FRESHEST_CRL),
    EXT_ATTR_ENTRY(szOID_NAME_CONSTRAINTS,
        IDS_EXT_NAME_CONSTRAINTS),
    EXT_ATTR_ENTRY(szOID_POLICY_MAPPINGS,
        IDS_EXT_POLICY_MAPPINGS),
    EXT_ATTR_ENTRY(szOID_LEGACY_POLICY_MAPPINGS,
        IDS_EXT_POLICY_MAPPINGS),
    EXT_ATTR_ENTRY(szOID_POLICY_CONSTRAINTS,
        IDS_EXT_POLICY_CONSTRAINTS),
    EXT_ATTR_ENTRY(szOID_CROSS_CERT_DIST_POINTS,
        IDS_EXT_CROSS_CERT_DIST_POINTS),
    EXT_ATTR_ENTRY(szOID_APPLICATION_CERT_POLICIES,
        IDS_EXT_APP_POLICIES),
    EXT_ATTR_ENTRY(szOID_APPLICATION_POLICY_MAPPINGS,
        IDS_EXT_APP_POLICY_MAPPINGS),
    EXT_ATTR_ENTRY(szOID_APPLICATION_POLICY_CONSTRAINTS,
        IDS_EXT_APP_POLICY_CONSTRAINTS),


    // DSIE: Post Win2K, 8/2/2000.
    EXT_ATTR_ENTRY(szOID_CT_PKI_DATA,
        IDS_EXT_CT_PKI_DATA),
    EXT_ATTR_ENTRY(szOID_CT_PKI_RESPONSE,
        IDS_EXT_CT_PKI_RESPONSE),
    EXT_ATTR_ENTRY(szOID_CMC,
        IDS_EXT_CMC),
    EXT_ATTR_ENTRY(szOID_CMC_STATUS_INFO,
        IDS_EXT_CMC_STATUS_INFO),
    EXT_ATTR_ENTRY(szOID_CMC_ADD_EXTENSIONS,
        IDS_EXT_CMC_ADD_EXTENSIONS),
    EXT_ATTR_ENTRY(szOID_CMC_ADD_ATTRIBUTES,
        IDS_EXT_CMC_ADD_ATTRIBUTES),
    EXT_ATTR_ENTRY(szOID_CMC_ADD_ATTRIBUTES,
        IDS_EXT_CMC_ADD_ATTRIBUTES),

    EXT_ATTR_ENTRY(szOID_PKCS_7_DATA,
        IDS_EXT_PKCS_7_DATA),
    EXT_ATTR_ENTRY(szOID_PKCS_7_SIGNED,
        IDS_EXT_PKCS_7_SIGNED),
    EXT_ATTR_ENTRY(szOID_PKCS_7_ENVELOPED,
        IDS_EXT_PKCS_7_ENVELOPED),
    EXT_ATTR_ENTRY(szOID_PKCS_7_SIGNEDANDENVELOPED,
        IDS_EXT_PKCS_7_SIGNEDANDENVELOPED),
    EXT_ATTR_ENTRY(szOID_PKCS_7_DIGESTED,
        IDS_EXT_PKCS_7_DIGESTED),
    EXT_ATTR_ENTRY(szOID_PKCS_7_ENCRYPTED,
        IDS_EXT_PKCS_7_ENCRYPTED),

    EXT_ATTR_ENTRY(szOID_CERTSRV_PREVIOUS_CERT_HASH,
        IDS_EXT_CERTSRV_PREVIOUS_CERT_HASH),
    EXT_ATTR_ENTRY(szOID_CRL_VIRTUAL_BASE,
        IDS_EXT_CRL_VIRTUAL_BASE),
    EXT_ATTR_ENTRY(szOID_CRL_NEXT_PUBLISH,
        IDS_EXT_CRL_NEXT_PUBLISH),
    EXT_ATTR_ENTRY(szOID_KP_CA_EXCHANGE,
        IDS_EXT_KP_CA_EXCHANGE),
    EXT_ATTR_ENTRY(szOID_KP_KEY_RECOVERY_AGENT,
        IDS_EXT_KP_KEY_RECOVERY_AGENT),
    EXT_ATTR_ENTRY(szOID_CERTIFICATE_TEMPLATE,
        IDS_EXT_CERTIFICATE_TEMPLATE),
    EXT_ATTR_ENTRY(szOID_ENTERPRISE_OID_ROOT,
        IDS_EXT_ENTERPRISE_OID_ROOT),
    EXT_ATTR_ENTRY(szOID_RDN_DUMMY_SIGNER,
        IDS_EXT_RDN_DUMMY_SIGNER),

    EXT_ATTR_ENTRY(szOID_ARCHIVED_KEY_ATTR,
        IDS_EXT_ARCHIVED_KEY_ATTR),
    EXT_ATTR_ENTRY(szOID_CRL_SELF_CDP,
        IDS_EXT_CRL_SELF_CDP),
    EXT_ATTR_ENTRY(szOID_REQUIRE_CERT_CHAIN_POLICY,
        IDS_EXT_REQUIRE_CERT_CHAIN_POLICY),

    EXT_ATTR_ENTRY(szOID_CMC_TRANSACTION_ID,
	IDS_EXT_CMC_TRANSACTION_ID),
    EXT_ATTR_ENTRY(szOID_CMC_SENDER_NONCE,
	IDS_EXT_CMC_SENDER_NONCE),
    EXT_ATTR_ENTRY(szOID_CMC_RECIPIENT_NONCE,
	IDS_EXT_CMC_RECIPIENT_NONCE),
    EXT_ATTR_ENTRY(szOID_CMC_REG_INFO,
	IDS_EXT_CMC_REG_INFO),
    EXT_ATTR_ENTRY(szOID_CMC_GET_CERT,
	IDS_EXT_CMC_GET_CERT),
    EXT_ATTR_ENTRY(szOID_CMC_GET_CRL,
	IDS_EXT_CMC_GET_CRL),
    EXT_ATTR_ENTRY(szOID_CMC_REVOKE_REQUEST,
	IDS_EXT_CMC_REVOKE_REQUEST),
    EXT_ATTR_ENTRY(szOID_CMC_QUERY_PENDING,
	IDS_EXT_CMC_QUERY_PENDING),
    EXT_ATTR_ENTRY(szOID_CTL,
	IDS_EXT_CTL),
    EXT_ATTR_ENTRY(szOID_ARCHIVED_KEY_CERT_HASH,
	IDS_EXT_ARCHIVED_KEY_CERT_HASH),
    EXT_ATTR_ENTRY(szOID_PRIVATEKEY_USAGE_PERIOD,
	IDS_EXT_PRIVATEKEY_USAGE_PERIOD),
    EXT_ATTR_ENTRY(szOID_REQUEST_CLIENT_INFO,
	IDS_EXT_REQUEST_CLIENT_INFO),
};


#define EXT_ATTR_CNT (sizeof(ExtAttrTable) / sizeof(ExtAttrTable[0]))

//+-------------------------------------------------------------------------
//  Enhanced Key Usage Table (Localized via resource strings)
//--------------------------------------------------------------------------
#define ENHKEY_ENTRY(pszOID, ResourceIdORpwszName) \
    OID_INFO_LEN, pszOID, (LPCWSTR) ResourceIdORpwszName, \
        CRYPT_ENHKEY_USAGE_OID_GROUP_ID, 0, 0, NULL

static CRYPT_OID_INFO EnhKeyTable[] = {
    ENHKEY_ENTRY(szOID_PKIX_KP_SERVER_AUTH,
        IDS_ENHKEY_PKIX_KP_SERVER_AUTH),
    ENHKEY_ENTRY(szOID_PKIX_KP_CLIENT_AUTH,
        IDS_ENHKEY_PKIX_KP_CLIENT_AUTH),
    ENHKEY_ENTRY(szOID_PKIX_KP_CODE_SIGNING,
        IDS_ENHKEY_PKIX_KP_CODE_SIGNING),
    ENHKEY_ENTRY(szOID_PKIX_KP_EMAIL_PROTECTION,
        IDS_ENHKEY_PKIX_KP_EMAIL_PROTECTION),
    ENHKEY_ENTRY(szOID_PKIX_KP_TIMESTAMP_SIGNING,
        IDS_ENHKEY_PKIX_KP_TIMESTAMP_SIGNING),
    ENHKEY_ENTRY(szOID_KP_CTL_USAGE_SIGNING,
        IDS_ENHKEY_KP_CTL_USAGE_SIGNING),
    ENHKEY_ENTRY(szOID_KP_TIME_STAMP_SIGNING,
        IDS_ENHKEY_KP_TIME_STAMP_SIGNING),
    ENHKEY_ENTRY(szOID_PKIX_KP_IPSEC_END_SYSTEM,
        IDS_ENHKEY_PKIX_KP_IPSEC_END_SYSTEM),
    ENHKEY_ENTRY(szOID_PKIX_KP_IPSEC_TUNNEL,
        IDS_ENHKEY_PKIX_KP_IPSEC_TUNNEL),
    ENHKEY_ENTRY(szOID_PKIX_KP_IPSEC_USER,
        IDS_ENHKEY_PKIX_KP_IPSEC_USER),
   // ENHKEY_ENTRY(szOID_SERVER_GATED_CRYPTO,
   //     IDS_ENHKEY_SERVER_GATED_CRYPTO),
   // ENHKEY_ENTRY(szOID_SGC_NETSCAPE,
   //     IDS_ENHKEY_SGC_NETSCAPE),
    ENHKEY_ENTRY(szOID_KP_EFS,
        IDS_ENHKEY_KP_EFS),
    ENHKEY_ENTRY(szOID_WHQL_CRYPTO,
        IDS_ENHKEY_KP_WHQL),
    ENHKEY_ENTRY(szOID_NT5_CRYPTO,
        IDS_ENHKEY_KP_NT5),
    ENHKEY_ENTRY(szOID_OEM_WHQL_CRYPTO,
        IDS_ENHKEY_KP_OEM_WHQL),
    ENHKEY_ENTRY(szOID_EMBEDDED_NT_CRYPTO,
        IDS_ENHKEY_KP_EMBEDDED_NT),
    ENHKEY_ENTRY(szOID_LICENSES,
	IDS_ENHKEY_LICENSES),
    ENHKEY_ENTRY(szOID_LICENSE_SERVER,
	IDS_ENHKEY_LICENSES_SERVER),
    ENHKEY_ENTRY(szOID_KP_SMARTCARD_LOGON,
	IDS_ENHKEY_SMARTCARD_LOGON),
    ENHKEY_ENTRY(szOID_DRM,
	IDS_ENHKEY_DRM),
    ENHKEY_ENTRY(szOID_KP_QUALIFIED_SUBORDINATION,
        IDS_ENHKEY_KP_QUALIFIED_SUBORDINATION),
    ENHKEY_ENTRY(szOID_KP_KEY_RECOVERY,
        IDS_ENHKEY_KP_KEY_RECOVERY),
    ENHKEY_ENTRY(szOID_KP_DOCUMENT_SIGNING,
        IDS_ENHKEY_KP_CODE_SIGNING),
    ENHKEY_ENTRY(szOID_IPSEC_KP_IKE_INTERMEDIATE,
        IDS_ENHKEY_KP_IPSEC_IKE_INTERMEDIATE),
    ENHKEY_ENTRY(szOID_EFS_RECOVERY,
        IDS_ENHKEY_EFS_RECOVERY),

    //DSIE: 8/2/2000, Post Win2K.
    ENHKEY_ENTRY(szOID_ROOT_LIST_SIGNER,
        IDS_ENHKEY_ROOT_LIST_SIGNER),
    ENHKEY_ENTRY(szOID_ANY_APPLICATION_POLICY,
        IDS_ENHKEY_ANY_POLICY),
    ENHKEY_ENTRY(szOID_DS_EMAIL_REPLICATION,
        IDS_ENHKEY_DS_EMAIL_REPLICATION),
    ENHKEY_ENTRY(szOID_ENROLLMENT_AGENT,
        IDS_ENHKEY_ENROLLMENT_AGENT),
    ENHKEY_ENTRY(szOID_KP_KEY_RECOVERY_AGENT,
        IDS_ENHKEY_KP_KEY_RECOVERY_AGENT),
    ENHKEY_ENTRY(szOID_KP_CA_EXCHANGE,
        IDS_ENHKEY_KP_CA_EXCHANGE),
    ENHKEY_ENTRY(szOID_KP_LIFETIME_SIGNING,
        IDS_ENHKEY_KP_LIFETIME_SIGNING),
};
#define ENHKEY_CNT (sizeof(EnhKeyTable) / sizeof(EnhKeyTable[0]))

//+-------------------------------------------------------------------------
//  Policy Table (Localized via resource strings)
//--------------------------------------------------------------------------
#define POLICY_ENTRY(pszOID, ResourceIdORpwszName) \
    OID_INFO_LEN, pszOID, (LPCWSTR) ResourceIdORpwszName, \
        CRYPT_POLICY_OID_GROUP_ID, 0, 0, NULL

static CRYPT_OID_INFO PolicyTable[] = {
    POLICY_ENTRY(szOID_ANY_CERT_POLICY,
        IDS_POLICY_ANY_POLICY),
};
#define POLICY_CNT (sizeof(PolicyTable) / sizeof(PolicyTable[0]))

#if 0

//+-------------------------------------------------------------------------
//  Template Table (Localized via resource strings)
//--------------------------------------------------------------------------
#define TEMPLATE_ENTRY(pszOID, ResourceIdORpwszName) \
    OID_INFO_LEN, pszOID, (LPCWSTR) ResourceIdORpwszName, \
        CRYPT_TEMPLATE_OID_GROUP_ID, 0, 0, NULL

static CRYPT_OID_INFO TemplateTable[] = {
};
#define TEMPLATE_CNT (sizeof(TemplateTable) / sizeof(TemplateTable[0]))

#endif


//+=========================================================================
//  OID Group Tables
//
//  fLocalize is set to TRUE, if the CRYPT_OID_INFO's pwszName may be
//  a Resource ID that is used to get the localized name via LoadStringU().
//
//  Assumption, Resource ID's <= 0xFFFF.
//==========================================================================
typedef struct _GROUP_ENTRY {
    DWORD               cInfo;
    PCCRYPT_OID_INFO    rgInfo;
    BOOL                fLocalize;
} GROUP_ENTRY, *PGROUP_ENTRY;
typedef const GROUP_ENTRY CGROUP_ENTRY, *PCGROUP_ENTRY;

static CGROUP_ENTRY GroupTable[CRYPT_LAST_OID_GROUP_ID + 1] = {
    0, NULL, FALSE,                             // 0
    HASH_ALG_CNT, HashAlgTable, FALSE,          // 1
    ENCRYPT_ALG_CNT, EncryptAlgTable, FALSE,    // 2
    PUBKEY_ALG_CNT, PubKeyAlgTable, FALSE,      // 3
    SIGN_ALG_CNT, SignAlgTable, FALSE,          // 4
    RDN_ATTR_CNT, RDNAttrTable, FALSE,          // 5
    EXT_ATTR_CNT, ExtAttrTable, TRUE,           // 6
    ENHKEY_CNT, EnhKeyTable, TRUE,              // 7
    POLICY_CNT, PolicyTable, TRUE,              // 8
#if 0
    TEMPLATE_CNT, TemplateTable, TRUE,          // 9
#else
    0, NULL, FALSE,                             // 9
#endif
};


//+-------------------------------------------------------------------------
//  The following groups are dynamically updated from the registry on
//  CryptFindOIDInfo's first call.
//--------------------------------------------------------------------------
static GROUP_ENTRY RegBeforeGroup;
static GROUP_ENTRY RegAfterGroup;

// Do the load once within a critical section
static BOOL fLoadedFromRegAndResources = FALSE;
static CRITICAL_SECTION LoadFromRegCriticalSection;


//+=========================================================================
//  DS Group Definitions and Data Structures
//==========================================================================

typedef struct _DS_GROUP_ENTRY {
    DWORD               cInfo;
    PCCRYPT_OID_INFO    *rgpInfo;
} DS_GROUP_ENTRY, *PDS_GROUP_ENTRY;
typedef const DS_GROUP_ENTRY CDS_GROUP_ENTRY, *PCDS_GROUP_ENTRY;

// The DS group is dynamically updated from the DS on the first call
// to CryptFindOIDInfo or CryptEnumOIDInfo for the ENHKEY, POLICY or
// TEMPLATE groups.
static DS_GROUP_ENTRY DsGroup;

// The above DsGroup is updated every DS_RETRIEVAL_DELTA_SECONDS. Since
// any returned PCCRYPT_OID_INFO can not be freed, the following group contains
// PCCRYPT_OID_INFO entries that may have been deleted from the above DS group.
static DS_GROUP_ENTRY DsDeletedGroup;

static CRITICAL_SECTION DsCriticalSection;

// For a successful DS retrieval, the following is updated with
// CurrentTime + DS_RETRIEVAL_DELTA_SECONDS.
static FILETIME DsNextUpdateTime;

#define DS_RETRIEVAL_DELTA_SECONDS  (60 * 60 * 8)
#define DS_LDAP_TIMEOUT             (10 * 1000)


static void FreeDsGroups();

static PCCRYPT_OID_INFO SearchDsGroup(
    IN DWORD dwKeyType,
    IN void *pvKey,
    IN DWORD dwGroupId
    );

static BOOL EnumDsGroup(
    IN DWORD dwGroupId,
    IN void *pvArg,
    IN PFN_CRYPT_ENUM_OID_INFO pfnEnumOIDInfo
    );


//+=========================================================================
//  Localized Name Definitions and Data Structures
//==========================================================================

//+-------------------------------------------------------------------------
//  Localized Name Information
//--------------------------------------------------------------------------
typedef struct _LOCALIZED_NAME_INFO {
    LPCWSTR         pwszCryptName;
    union {
        UINT            uIDLocalizedName;
        LPCWSTR         pwszLocalizedName;
    };
} LOCALIZED_NAME_INFO, *PLOCALIZED_NAME_INFO;


//+-------------------------------------------------------------------------
//  Predefined Localized Names Table (Localized via resource strings)
//--------------------------------------------------------------------------
static LOCALIZED_NAME_INFO PredefinedNameTable[] = {
    // System store names
    L"Root",        IDS_SYS_NAME_ROOT,
    L"My",          IDS_SYS_NAME_MY,
    L"Trust",       IDS_SYS_NAME_TRUST,
    L"CA",          IDS_SYS_NAME_CA,
    L"UserDS",      IDS_SYS_NAME_USERDS,
    L"SmartCard",   IDS_SYS_NAME_SMARTCARD,
    L"AddressBook", IDS_SYS_NAME_ADDRESSBOOK,
    L"TrustedPublisher", IDS_SYS_NAME_TRUST_PUB,
    L"Disallowed",  IDS_SYS_NAME_DISALLOWED,
    L"AuthRoot",    IDS_SYS_NAME_AUTH_ROOT,
    L"Request",     IDS_SYS_NAME_REQUEST,
    L"TrustedPeople", IDS_SYS_NAME_TRUST_PEOPLE,

    // Physical store names
    CERT_PHYSICAL_STORE_DEFAULT_NAME,           IDS_PHY_NAME_DEFAULT,
    CERT_PHYSICAL_STORE_GROUP_POLICY_NAME,      IDS_PHY_NAME_GROUP_POLICY,
    CERT_PHYSICAL_STORE_LOCAL_MACHINE_NAME,     IDS_PHY_NAME_LOCAL_MACHINE,
    CERT_PHYSICAL_STORE_DS_USER_CERTIFICATE_NAME, IDS_PHY_NAME_DS_USER_CERT,
    CERT_PHYSICAL_STORE_ENTERPRISE_NAME,        IDS_PHY_NAME_ENTERPRISE,
    CERT_PHYSICAL_STORE_AUTH_ROOT_NAME,         IDS_PHY_NAME_AUTH_ROOT,
};
#define PREDEFINED_NAME_CNT  (sizeof(PredefinedNameTable) / \
                                    sizeof(PredefinedNameTable[0]))

//+-------------------------------------------------------------------------
//  Localized Name Group Table
//--------------------------------------------------------------------------
typedef struct _LOCALIZED_GROUP_ENTRY {
    DWORD                   cInfo;
    PLOCALIZED_NAME_INFO    rgInfo;
} LOCALIZED_GROUP_ENTRY, *PLOCALIZED_GROUP_ENTRY;

#define REG_LOCALIZED_GROUP             0
#define PREDEFINED_LOCALIZED_GROUP      1
static LOCALIZED_GROUP_ENTRY LocalizedGroupTable[] = {
    // 0 - Loaded from registry
    0, NULL,
    // 1 - Predefined list of names
    PREDEFINED_NAME_CNT, PredefinedNameTable
};
#define LOCALIZED_GROUP_CNT  (sizeof(LocalizedGroupTable) / \
                                    sizeof(LocalizedGroupTable[0]))

// The localized names are loaded once. Uses the above
// LoadFromRegCriticalSection;
static BOOL fLoadedLocalizedNames = FALSE;


//+-------------------------------------------------------------------------
//  OIDInfo allocation and free functions
//--------------------------------------------------------------------------
static void *OIDInfoAlloc(
    IN size_t cbBytes
    )
{
    void *pv;
    pv = malloc(cbBytes);
    if (pv == NULL)
        SetLastError((DWORD) E_OUTOFMEMORY);
    return pv;
}

static void *OIDInfoRealloc(
    IN void *pvOrg,
    IN size_t cb
    )
{
    void *pv;
    if (NULL == (pv = pvOrg ? realloc(pvOrg, cb) : malloc(cb)))
        SetLastError((DWORD) E_OUTOFMEMORY);
    return pv;
}

static void OIDInfoFree(
    IN void *pv
    )
{
    free(pv);
}

//+-------------------------------------------------------------------------
//  Functions called at ProcessDetach to free the groups updated from the
//  registry during CryptFindOIDInfo's first call.
//--------------------------------------------------------------------------
static void FreeGroup(
    PGROUP_ENTRY pGroup
    )
{
    DWORD cInfo = pGroup->cInfo;
    PCCRYPT_OID_INFO pInfo = pGroup->rgInfo;
    for ( ; cInfo > 0; cInfo--, pInfo++)
        OIDInfoFree((LPSTR)pInfo->pszOID);

    OIDInfoFree((PCRYPT_OID_INFO) pGroup->rgInfo);
}

static void FreeRegGroups()
{
    FreeGroup(&RegBeforeGroup);
    FreeGroup(&RegAfterGroup);
}

//+-------------------------------------------------------------------------
//  Free resource strings allocated in groups having localized pwszName's.
//--------------------------------------------------------------------------
static void FreeGroupResources()
{
    DWORD i;
    if (!fLoadedFromRegAndResources)
        // No resource strings allocated
        return;

    for (i = 1; i <= CRYPT_LAST_OID_GROUP_ID; i++) {
        if (GroupTable[i].fLocalize) {
            DWORD cInfo = GroupTable[i].cInfo;
            PCRYPT_OID_INFO pInfo = (PCRYPT_OID_INFO) GroupTable[i].rgInfo;

            for ( ; cInfo > 0; cInfo--, pInfo++) {
                // pwszName is set to pwszNullName if the allocation failed
                if (pwszNullName != pInfo->pwszName) {
                    OIDInfoFree((LPWSTR) pInfo->pwszName);
                    pInfo->pwszName = pwszNullName;
                }
            }
        }
    }
}

//+-------------------------------------------------------------------------
//  Free memory allocated for localized names
//--------------------------------------------------------------------------
static void FreeLocalizedNames()
{
    if (!fLoadedLocalizedNames)
        // No resource strings allocated
        return;

    for (DWORD i = 0; i < LOCALIZED_GROUP_CNT; i++) {
        DWORD cInfo = LocalizedGroupTable[i].cInfo;
        PLOCALIZED_NAME_INFO pInfo = LocalizedGroupTable[i].rgInfo;

        for ( ; cInfo > 0; cInfo--, pInfo++) {
            LPWSTR pwszLocalizedName = (LPWSTR) pInfo->pwszLocalizedName;
            if (pwszNullName != pwszLocalizedName)
                OIDInfoFree(pwszLocalizedName);
        }
    }

    OIDInfoFree(LocalizedGroupTable[REG_LOCALIZED_GROUP].rgInfo);
}

//+-------------------------------------------------------------------------
//  Dll initialization
//--------------------------------------------------------------------------
BOOL
WINAPI
I_CryptOIDInfoDllMain(
        HMODULE hInst,
        ULONG  ulReason,
        LPVOID lpReserved)
{
    BOOL fRet = TRUE;

    switch (ulReason) {
    case DLL_PROCESS_ATTACH:
        hOIDInfoInst = hInst;
        fRet = Pki_InitializeCriticalSection(&LoadFromRegCriticalSection);
        if (fRet) {
            fRet = Pki_InitializeCriticalSection(&DsCriticalSection);
            if (!fRet)
                DeleteCriticalSection(&LoadFromRegCriticalSection);
        }
        break;

    case DLL_PROCESS_DETACH:
        FreeRegGroups();
        FreeGroupResources();
        FreeLocalizedNames();
        DeleteCriticalSection(&LoadFromRegCriticalSection);

        FreeDsGroups();
        DeleteCriticalSection(&DsCriticalSection);
        break;
    case DLL_THREAD_DETACH:
    default:
        break;
    }

    return fRet;
}


//+-------------------------------------------------------------------------
//  Allocated and format the string consisting of the OID and GROUP_ID:
//
//  For example: 1.2.3.4!6
//--------------------------------------------------------------------------
static LPSTR FormatOIDGroupString(
    IN PCCRYPT_OID_INFO pInfo
    )
{
    LPSTR pszOIDGroupString;
    DWORD cchOIDGroupString;
    char szGroupId[34];

    if (NULL == pInfo || pInfo->cbSize < sizeof(CRYPT_OID_INFO) ||
            (DWORD_PTR) pInfo->pszOID <= 0xFFFF) {
        SetLastError((DWORD) E_INVALIDARG);
        return NULL;
    }

    szGroupId[0] = CONST_OID_GROUP_PREFIX_CHAR;
    _ltoa((long) pInfo->dwGroupId, &szGroupId[1], 10);

    cchOIDGroupString = strlen(pInfo->pszOID) +
        strlen(szGroupId) +
        1;

    if (pszOIDGroupString = (LPSTR) OIDInfoAlloc(cchOIDGroupString)) {
        strcpy(pszOIDGroupString, pInfo->pszOID);
        strcat(pszOIDGroupString, szGroupId);
    }

    return pszOIDGroupString;
}

//+-------------------------------------------------------------------------
//  Wrapper function for calling CryptSetOIDFunctionValue using OID info's
//  encoding type and function name.
//--------------------------------------------------------------------------
static BOOL SetOIDInfoRegValue(
    IN LPCSTR pszOIDGroupString,
    IN LPCWSTR pwszValueName,
    IN DWORD dwValueType,
    IN const BYTE *pbValueData,
    IN DWORD cbValueData
    )
{
    return CryptSetOIDFunctionValue(
        OID_INFO_ENCODING_TYPE,
        CRYPT_OID_FIND_OID_INFO_FUNC,
        pszOIDGroupString,
        pwszValueName,
        dwValueType,
        pbValueData,
        cbValueData
        );
}

//+-------------------------------------------------------------------------
//  Register OID information.
//--------------------------------------------------------------------------
BOOL
WINAPI
CryptRegisterOIDInfo(
    IN PCCRYPT_OID_INFO pInfo,
    IN DWORD dwFlags
    )
{
    BOOL fResult;
    LPSTR pszOIDGroupString = NULL;

    if (NULL == (pszOIDGroupString = FormatOIDGroupString(pInfo)))
        goto FormatOIDGroupStringError;

    if (pInfo->pwszName && L'\0' != *pInfo->pwszName) {
        if (!SetOIDInfoRegValue(
                pszOIDGroupString,
                OID_INFO_NAME_VALUE_NAME,
                REG_SZ,
                (const BYTE *) pInfo->pwszName,
                (wcslen(pInfo->pwszName) + 1) * sizeof(WCHAR)
                )) goto SetOIDInfoRegValueError;
    }
    if (0 != pInfo->Algid) {
        if (!SetOIDInfoRegValue(
                pszOIDGroupString,
                OID_INFO_ALGID_VALUE_NAME,
                REG_DWORD,
                (const BYTE *) &pInfo->Algid,
                sizeof(pInfo->Algid)
                )) goto SetOIDInfoRegValueError;
    }
    if (0 != pInfo->ExtraInfo.cbData) {
        if (!SetOIDInfoRegValue(
                pszOIDGroupString,
                OID_INFO_EXTRA_INFO_VALUE_NAME,
                REG_BINARY,
                pInfo->ExtraInfo.pbData,
                pInfo->ExtraInfo.cbData
                )) goto SetOIDInfoRegValueError;
    }

    if (0 != dwFlags) {
        if (!SetOIDInfoRegValue(
                pszOIDGroupString,
                OID_INFO_FLAGS_VALUE_NAME,
                REG_DWORD,
                (const BYTE *) &dwFlags,
                sizeof(dwFlags)
                )) goto SetOIDInfoRegValueError;
    }

    fResult = TRUE;
CommonReturn:
    OIDInfoFree(pszOIDGroupString);
    return fResult;
ErrorReturn:
    fResult = FALSE;
    goto CommonReturn;
TRACE_ERROR(FormatOIDGroupStringError)
TRACE_ERROR(SetOIDInfoRegValueError)
}

//+-------------------------------------------------------------------------
//  Unregister OID information. Only the pszOID and dwGroupId fields are
//  used to identify the OID information to be unregistered.
//--------------------------------------------------------------------------
BOOL
WINAPI
CryptUnregisterOIDInfo(
    IN PCCRYPT_OID_INFO pInfo
    )
{
    BOOL fResult;
    LPSTR pszOIDGroupString = NULL;

    if (NULL == (pszOIDGroupString = FormatOIDGroupString(pInfo)))
        goto FormatOIDGroupStringError;
    if (!CryptUnregisterOIDFunction(
            OID_INFO_ENCODING_TYPE,
            CRYPT_OID_FIND_OID_INFO_FUNC,
            pszOIDGroupString
            ))
        goto UnregisterOIDFunctionError;
    fResult = TRUE;
CommonReturn:
    OIDInfoFree(pszOIDGroupString);
    return fResult;
ErrorReturn:
    fResult = FALSE;
    goto CommonReturn;
TRACE_ERROR(FormatOIDGroupStringError)
TRACE_ERROR(UnregisterOIDFunctionError)
}

//+-------------------------------------------------------------------------
//  Called by CryptEnumOIDFunction to enumerate through all the
//  registered OID information.
//
//  Called within critical section
//--------------------------------------------------------------------------
static BOOL WINAPI EnumRegistryCallback(
    IN DWORD dwEncodingType,
    IN LPCSTR pszFuncName,
    IN LPCSTR pszOID,
    IN DWORD cValue,
    IN const DWORD rgdwValueType[],
    IN LPCWSTR const rgpwszValueName[],
    IN const BYTE * const rgpbValueData[],
    IN const DWORD rgcbValueData[],
    IN void *pvArg
    )
{
    DWORD cchOID;
    LPCWSTR pwszName = NULL;
    DWORD cchName = 0;
    LPCSTR pszGroupId;
    DWORD dwGroupId = 0;
    ALG_ID Algid = 0;
    CRYPT_DATA_BLOB ExtraInfo = {0, NULL};
    DWORD dwFlags = 0;
    DWORD cbExtra;
    BYTE *pbExtra;
    PGROUP_ENTRY pGroup;
    PCRYPT_OID_INFO pInfo;

    // The pszOID consists of OID!<dwGroupId>, for example, 1.2.3!1
    // Start at the end and search for the '!'
    cchOID = strlen(pszOID);
    pszGroupId = pszOID + cchOID;
    while (pszGroupId > pszOID && CONST_OID_GROUP_PREFIX_CHAR != *pszGroupId)
        pszGroupId--;

    if (CONST_OID_GROUP_PREFIX_CHAR == *pszGroupId) {
        cchOID = (DWORD)(pszGroupId - pszOID);
        dwGroupId = (DWORD) atol(pszGroupId + 1);
    } else
        // Name is missing "!". Skip It.
        return TRUE;

    while (cValue--) {
        LPCWSTR pwszValueName = rgpwszValueName[cValue];
        DWORD dwValueType = rgdwValueType[cValue];
        const BYTE *pbValueData = rgpbValueData[cValue];
        DWORD cbValueData = rgcbValueData[cValue];

        if (0 == _wcsicmp(pwszValueName, OID_INFO_NAME_VALUE_NAME)) {
            if (REG_SZ == dwValueType) {
                pwszName = (LPWSTR) pbValueData;
                cchName = wcslen(pwszName);
            }
        } else if (0 == _wcsicmp(pwszValueName, OID_INFO_ALGID_VALUE_NAME)) {
            if (REG_DWORD == dwValueType &&
                    cbValueData >= sizeof(Algid))
                memcpy(&Algid, pbValueData, sizeof(Algid));
        } else if (0 == _wcsicmp(pwszValueName,
                OID_INFO_EXTRA_INFO_VALUE_NAME)) {
            if (REG_BINARY == dwValueType) {
                ExtraInfo.cbData = cbValueData;
                ExtraInfo.pbData = (BYTE *) pbValueData;
            }
        } else if (0 == _wcsicmp(pwszValueName, OID_INFO_FLAGS_VALUE_NAME)) {
            if (REG_DWORD == dwValueType &&
                    cbValueData >= sizeof(dwFlags))
                memcpy(&dwFlags, pbValueData, sizeof(dwFlags));
        }
    }

    cbExtra = LEN_ALIGN(cchOID + 1) +
        LEN_ALIGN((cchName + 1) * sizeof(WCHAR)) +
        ExtraInfo.cbData;
    if (NULL == (pbExtra = (BYTE *) OIDInfoAlloc(cbExtra)))
        return FALSE;

    if (dwFlags & CRYPT_INSTALL_OID_INFO_BEFORE_FLAG)
        pGroup = &RegBeforeGroup;
    else
        pGroup = &RegAfterGroup;

    if (NULL == (pInfo = (PCRYPT_OID_INFO) OIDInfoRealloc(
            (PCRYPT_OID_INFO) pGroup->rgInfo,
            (pGroup->cInfo + 1) * sizeof(CRYPT_OID_INFO)))) {
        OIDInfoFree(pbExtra);
        return FALSE;
    }
    pGroup->rgInfo = pInfo;
    pInfo = &pInfo[pGroup->cInfo++];

    pInfo->cbSize = sizeof(CRYPT_OID_INFO);
    pInfo->pszOID = (LPCSTR) pbExtra;
    if (cchOID)
        memcpy(pbExtra, pszOID, cchOID);
    *( ((LPSTR) pbExtra) + cchOID) = '\0';
    pbExtra += LEN_ALIGN(cchOID + 1);

    pInfo->pwszName = (LPCWSTR) pbExtra;
    if (cchName)
        memcpy(pbExtra, pwszName, (cchName + 1) * sizeof(WCHAR));
    else
        *((LPWSTR) pbExtra) = L'\0';
    pbExtra += LEN_ALIGN((cchName + 1) * sizeof(WCHAR));

    pInfo->dwGroupId = dwGroupId;
    pInfo->Algid = Algid;
    pInfo->ExtraInfo.cbData = ExtraInfo.cbData;
    if (ExtraInfo.cbData > 0) {
        pInfo->ExtraInfo.pbData = pbExtra;
        memcpy(pbExtra, ExtraInfo.pbData, ExtraInfo.cbData);
    } else
        pInfo->ExtraInfo.pbData = NULL;

    return TRUE;
}

//+-------------------------------------------------------------------------
//  Allocate and load the string for the specified resource.
//
//  If LoadString or allocation fails, returns predefined pointer to an
//  empty string.
//--------------------------------------------------------------------------
static LPWSTR AllocAndLoadOIDNameString(
    IN UINT uID
    )
{
    WCHAR wszResource[MAX_RESOURCE_OID_NAME_LENGTH + 1];
    int cchResource;
    int cbResource;
    LPWSTR pwszDst;

    cchResource = LoadStringU(hOIDInfoInst, uID, wszResource,
        MAX_RESOURCE_OID_NAME_LENGTH);
    assert(0 < cchResource);
    if (0 >= cchResource)
        return (LPWSTR) pwszNullName;

    cbResource = (cchResource + 1) * sizeof(WCHAR);
    pwszDst = (LPWSTR) OIDInfoAlloc(cbResource);
    assert(pwszDst);
    if (NULL == pwszDst)
        return (LPWSTR) pwszNullName;
    memcpy((BYTE *) pwszDst, (BYTE *) wszResource, cbResource);
    return pwszDst;
}

//+-------------------------------------------------------------------------
//  Allocate and copy the string.
//
//  If allocation fails, returns predefined pointer to an empty string.
//--------------------------------------------------------------------------
static LPWSTR AllocAndCopyOIDNameString(
    IN LPCWSTR pwszSrc
    )
{
    DWORD cbSrc;
    LPWSTR pwszDst;

    cbSrc = (wcslen(pwszSrc) + 1) * sizeof(WCHAR);
    pwszDst = (LPWSTR) OIDInfoAlloc(cbSrc);
    assert(pwszDst);
    if (NULL == pwszDst)
        return (LPWSTR) pwszNullName;
    memcpy((BYTE *) pwszDst, (BYTE *) pwszSrc, cbSrc);
    return pwszDst;
}

//+-------------------------------------------------------------------------
//  Does a LoadString for pwszName's initialized with resource IDs in groups
//  with fLocalize set.
//--------------------------------------------------------------------------
static void LoadGroupResources()
{
    DWORD i;
    for (i = 1; i <= CRYPT_LAST_OID_GROUP_ID; i++) {
        if (GroupTable[i].fLocalize) {
            DWORD cInfo = GroupTable[i].cInfo;
            PCRYPT_OID_INFO pInfo = (PCRYPT_OID_INFO) GroupTable[i].rgInfo;
            for ( ; cInfo > 0; cInfo--, pInfo++) {
                UINT_PTR uID;
                uID = (UINT_PTR) pInfo->pwszName;
                if (uID <= 0xFFFF)
                    pInfo->pwszName = AllocAndLoadOIDNameString((UINT)uID);
                else
                    // ProcessDetach expects all pwszName's to be allocated
                    pInfo->pwszName = AllocAndCopyOIDNameString(
                        pInfo->pwszName);
            }
        }
    }
}

//+-------------------------------------------------------------------------
//  Load OID Information from the registry. Updates the RegBeforeGroup and
//  RegAfterGroup.
//
//  Loads resource strings in groups enabling localization of pwszName's.
//--------------------------------------------------------------------------
static void LoadFromRegistryAndResources()
{
    if (fLoadedFromRegAndResources)
        return;

    EnterCriticalSection(&LoadFromRegCriticalSection);
    if (!fLoadedFromRegAndResources) {
        CryptEnumOIDFunction(
            OID_INFO_ENCODING_TYPE,
            CRYPT_OID_FIND_OID_INFO_FUNC,
            NULL,                           // pszOID
            0,                              // dwFlags
            NULL,                           // pvArg
            EnumRegistryCallback
            );
        LoadGroupResources();
        fLoadedFromRegAndResources = TRUE;
    }
    LeaveCriticalSection(&LoadFromRegCriticalSection);
}

//+-------------------------------------------------------------------------
//  Compare the OID info according to the specified key and group.
//--------------------------------------------------------------------------
static BOOL CompareOIDInfo(
    IN DWORD dwKeyType,
    IN void *pvKey,
    IN DWORD dwGroupId,
    IN PCCRYPT_OID_INFO pInfo
    )
{
    if (dwGroupId && dwGroupId != pInfo->dwGroupId)
        return FALSE;

    switch (dwKeyType) {
        case CRYPT_OID_INFO_OID_KEY:
            if (0 == _stricmp((LPSTR) pvKey, pInfo->pszOID))
                return TRUE;
            break;
        case CRYPT_OID_INFO_NAME_KEY:
            if (0 == _wcsicmp((LPWSTR) pvKey, pInfo->pwszName))
                return TRUE;
            break;
        case CRYPT_OID_INFO_ALGID_KEY:
            if (*((ALG_ID *) pvKey) == pInfo->Algid)
                return TRUE;
            break;
        case CRYPT_OID_INFO_SIGN_KEY:
            {
                ALG_ID *paiKey = (ALG_ID *) pvKey;
                ALG_ID aiPubKey;

                if (sizeof(ALG_ID) <= pInfo->ExtraInfo.cbData)
                    aiPubKey = *((ALG_ID *) pInfo->ExtraInfo.pbData);
                else
                    aiPubKey = 0;

                if (paiKey[0] == pInfo->Algid &&
                        paiKey[1] == aiPubKey)
                return TRUE;
            }
            break;
        default:
            SetLastError((DWORD) E_INVALIDARG);
            return FALSE;
    }

    return FALSE;
}

//+-------------------------------------------------------------------------
//  Search the group according to the specified dwKeyType.
//
//  Note, the groups updated from the registry, RegBeforeGroup and
//  RegAfterGroup, may contain any GROUP_ID.
//--------------------------------------------------------------------------
static PCCRYPT_OID_INFO SearchGroup(
    IN DWORD dwKeyType,
    IN void *pvKey,
    IN DWORD dwGroupId,
    IN PCGROUP_ENTRY pGroup
    )
{
    DWORD cInfo = pGroup->cInfo;
    PCCRYPT_OID_INFO pInfo = pGroup->rgInfo;
    for ( ; cInfo > 0; cInfo--, pInfo++) {
        if (CompareOIDInfo(
                dwKeyType,
                pvKey,
                dwGroupId,
                pInfo
                ))
            return pInfo;
    }

    return NULL;
}

//+-------------------------------------------------------------------------
//  Find OID information. Returns NULL if unable to find any information
//  for the specified key and group.
//--------------------------------------------------------------------------
PCCRYPT_OID_INFO
WINAPI
CryptFindOIDInfo(
    IN DWORD dwKeyType,
    IN void *pvKey,
    IN DWORD dwGroupId      // 0 => any group
    )
{
    PCCRYPT_OID_INFO pInfo;

    LoadFromRegistryAndResources();

    if (RegBeforeGroup.cInfo && NULL != (pInfo = SearchGroup(
            dwKeyType,
            pvKey,
            dwGroupId,
            &RegBeforeGroup
            ))) return pInfo;
    if (0 == dwGroupId) {
        DWORD i;
        for (i = 1; i <= CRYPT_LAST_OID_GROUP_ID; i++) {
            if (pInfo = SearchGroup(
                dwKeyType,
                pvKey,
                0,
                &GroupTable[i]
                )) return pInfo;
        }
    } else if (dwGroupId <= CRYPT_LAST_OID_GROUP_ID) {
        if (pInfo = SearchGroup(
                dwKeyType,
                pvKey,
                dwGroupId,
                &GroupTable[dwGroupId]
                )) return pInfo;
    }

    if (RegAfterGroup.cInfo && NULL != (pInfo = SearchGroup(
            dwKeyType,
            pvKey,
            dwGroupId,
            &RegAfterGroup
            ))) return pInfo;

    return SearchDsGroup(
            dwKeyType,
            pvKey,
            dwGroupId
            );
}


//+-------------------------------------------------------------------------
//  Enumerate the group.
//--------------------------------------------------------------------------
static BOOL EnumGroup(
    IN DWORD dwGroupId,
    IN PCGROUP_ENTRY pGroup,
    IN void *pvArg,
    IN PFN_CRYPT_ENUM_OID_INFO pfnEnumOIDInfo
    )
{
    DWORD cInfo = pGroup->cInfo;
    PCCRYPT_OID_INFO pInfo = pGroup->rgInfo;
    for ( ; cInfo > 0; cInfo--, pInfo++) {
        if (dwGroupId && dwGroupId != pInfo->dwGroupId)
            continue;

        if (!pfnEnumOIDInfo(pInfo, pvArg))
            return FALSE;
    }
    return TRUE;
}

//+-------------------------------------------------------------------------
//  Enumerate the OID information.
//
//  pfnEnumOIDInfo is called for each OID information entry.
//
//  Setting dwGroupId to 0 matches all groups. Otherwise, only enumerates
//  entries in the specified group.
//
//  dwFlags currently isn't used and must be set to 0.
//--------------------------------------------------------------------------
BOOL
WINAPI
CryptEnumOIDInfo(
    IN DWORD dwGroupId,
    IN DWORD dwFlags,
    IN void *pvArg,
    IN PFN_CRYPT_ENUM_OID_INFO pfnEnumOIDInfo
    )
{
    LoadFromRegistryAndResources();

    if (RegBeforeGroup.cInfo && !EnumGroup(
            dwGroupId,
            &RegBeforeGroup,
            pvArg,
            pfnEnumOIDInfo
            )) return FALSE;
    if (0 == dwGroupId) {
        DWORD i;
        for (i = 1; i <= CRYPT_LAST_OID_GROUP_ID; i++) {
            if (!EnumGroup(
                    0,                  // dwGroupId
                    &GroupTable[i],
                    pvArg,
                    pfnEnumOIDInfo
                    )) return FALSE;
        }
    } else if (dwGroupId <= CRYPT_LAST_OID_GROUP_ID) {
        if (!EnumGroup(
                dwGroupId,
                &GroupTable[dwGroupId],
                pvArg,
                pfnEnumOIDInfo
                )) return FALSE;
    }

    if (RegAfterGroup.cInfo && !EnumGroup(
            dwGroupId,
            &RegAfterGroup,
            pvArg,
            pfnEnumOIDInfo
            )) return FALSE;


    return EnumDsGroup(
            dwGroupId,
            pvArg,
            pfnEnumOIDInfo
            );
}



//+=========================================================================
//  Localized Name Functions
//==========================================================================

//+-------------------------------------------------------------------------
//  Called by CryptEnumOIDFunction to enumerate through all the
//  registered localized name values.
//
//  Called within critical section
//
//  Note at ProcessDetach, the Info entry pwszLocalizedName strings are freed.
//  Therefore, for each Info entry, do a single allocation for both the
//  pwszLocalizedName and pwszCryptName. The pwszCryptName immediately
//  follows the pwszLocalizedName.
//--------------------------------------------------------------------------
static BOOL WINAPI EnumRegLocalizedNamesCallback(
    IN DWORD dwEncodingType,
    IN LPCSTR pszFuncName,
    IN LPCSTR pszOID,
    IN DWORD cValue,
    IN const DWORD rgdwValueType[],
    IN LPCWSTR const rgpwszValueName[],
    IN const BYTE * const rgpbValueData[],
    IN const DWORD rgcbValueData[],
    IN void *pvArg
    )
{
    BOOL fResult;
    DWORD cInfo = LocalizedGroupTable[REG_LOCALIZED_GROUP].cInfo;
    PLOCALIZED_NAME_INFO pInfo =
        LocalizedGroupTable[REG_LOCALIZED_GROUP].rgInfo;

    assert(CRYPT_LOCALIZED_NAME_ENCODING_TYPE == dwEncodingType);
    assert(0 == _stricmp(CRYPT_OID_FIND_LOCALIZED_NAME_FUNC, pszFuncName));
    assert(0 == _stricmp(CRYPT_LOCALIZED_NAME_OID, pszOID));

    while (cValue--) {
        if (REG_SZ == rgdwValueType[cValue]) {
            LPCWSTR pwszLocalizedName = (LPCWSTR) rgpbValueData[cValue];
            DWORD cchLocalizedName;
            DWORD cbLocalizedName;
            LPCWSTR pwszCryptName = rgpwszValueName[cValue];
            DWORD cbCryptName;

            LPWSTR pwszBothNames;
            PLOCALIZED_NAME_INFO pNewInfo;

            // Check for empty name string
            cchLocalizedName = wcslen(pwszLocalizedName);
            if (0 == cchLocalizedName)
                continue;

            cbLocalizedName = (cchLocalizedName + 1) * sizeof(WCHAR);
            cbCryptName = (wcslen(pwszCryptName) + 1) * sizeof(WCHAR);

            if (NULL == (pwszBothNames = (LPWSTR) OIDInfoAlloc(
                    cbLocalizedName + cbCryptName)))
                goto OutOfMemory;

            if (NULL == (pNewInfo = (PLOCALIZED_NAME_INFO) OIDInfoRealloc(
                    pInfo, (cInfo + 1) * sizeof(LOCALIZED_NAME_INFO)))) {
                OIDInfoFree(pwszBothNames);
                goto OutOfMemory;
            }
            pInfo = pNewInfo;
            pInfo[cInfo].pwszLocalizedName = (LPCWSTR) pwszBothNames;
            memcpy(pwszBothNames, pwszLocalizedName, cbLocalizedName);
            pwszBothNames =
                (LPWSTR) ((BYTE *) pwszBothNames + cbLocalizedName);
            pInfo[cInfo].pwszCryptName = (LPCWSTR) pwszBothNames;
            memcpy(pwszBothNames, pwszCryptName, cbCryptName);
            cInfo++;
        }
    }
    fResult = TRUE;

CommonReturn:
    LocalizedGroupTable[REG_LOCALIZED_GROUP].cInfo = cInfo;
    LocalizedGroupTable[REG_LOCALIZED_GROUP].rgInfo = pInfo;
    return fResult;
ErrorReturn:
    fResult = FALSE;
    goto CommonReturn;
TRACE_ERROR(OutOfMemory)
}

static void LoadPredefinedNameResources()
{
    for (DWORD i = 0; i < PREDEFINED_NAME_CNT; i++) {
        LPWSTR pwszLocalizedName;

        // Note, the following always returns a non-NULL string pointer.
        pwszLocalizedName = AllocAndLoadOIDNameString(
            PredefinedNameTable[i].uIDLocalizedName);
        if (L'\0' == *pwszLocalizedName)
            pwszLocalizedName = NULL;
        PredefinedNameTable[i].pwszLocalizedName = (LPCWSTR) pwszLocalizedName;
    }
}

static void LoadLocalizedNamesFromRegAndResources()
{
    if (fLoadedLocalizedNames)
        return;

    EnterCriticalSection(&LoadFromRegCriticalSection);
    if (!fLoadedLocalizedNames) {
        CryptEnumOIDFunction(
            CRYPT_LOCALIZED_NAME_ENCODING_TYPE,
            CRYPT_OID_FIND_LOCALIZED_NAME_FUNC,
            CRYPT_LOCALIZED_NAME_OID,
            0,                              // dwFlags
            NULL,                           // pvArg
            EnumRegLocalizedNamesCallback
            );
        LoadPredefinedNameResources();
        fLoadedLocalizedNames = TRUE;
    }
    LeaveCriticalSection(&LoadFromRegCriticalSection);
}

//+-------------------------------------------------------------------------
//  Find the localized name for the specified name. For example, find the
//  localized name for the "Root" system store name. A case insensitive
//  string comparison is done.
//
//  Returns NULL if unable to find the the specified name.
//--------------------------------------------------------------------------
LPCWSTR
WINAPI
CryptFindLocalizedName(
    IN LPCWSTR pwszCryptName
    )
{
    if (NULL == pwszCryptName || L'\0' == *pwszCryptName)
        return NULL;

    LoadLocalizedNamesFromRegAndResources();

    for (DWORD i = 0; i < LOCALIZED_GROUP_CNT; i++) {
        DWORD cInfo = LocalizedGroupTable[i].cInfo;
        PLOCALIZED_NAME_INFO pInfo = LocalizedGroupTable[i].rgInfo;
        for ( ; cInfo > 0; cInfo--, pInfo++) {
            if (0 == _wcsicmp(pwszCryptName, pInfo->pwszCryptName))
                return pInfo->pwszLocalizedName;
        }
    }

    return NULL;
}


//+=========================================================================
//  DS Group Functions
//==========================================================================

// certcli.dll has the helper function for getting the LDAP URL to the
// OID info stored in the DS.

#define sz_CERTCLI_DLL              "certcli.dll"
#define sz_CAOIDGetLdapURL          "CAOIDGetLdapURL"
#define sz_CAOIDFreeLdapURL         "CAOIDFreeLdapURL"

typedef HRESULT (WINAPI *PFN_CA_OID_GET_LDAP_URL)(
    IN  DWORD   dwType,
    IN  DWORD   dwFlag,
    OUT LPWSTR  *ppwszURL
    );

typedef HRESULT (WINAPI *PFN_CA_OID_FREE_LDAP_URL)(
    IN LPCWSTR pwszURL
    );


#if 1
//+-------------------------------------------------------------------------
//  Gets the LDAP URL for and then uses to retrieve the OID info stored
//  in the DS.
//
//  Returns NULL if unable to do a successful LDAP retrieval.
//
//  If the OID object doesn't exist in the directory, returns FALSE with
//  LastError == ERROR_FILE_NOT_FOUND.
//
//  Assumption: not in DsCriticalSection
//--------------------------------------------------------------------------
static PCRYPT_BLOB_ARRAY RetrieveDsGroupByLdapUrl()
{
    PCRYPT_BLOB_ARRAY pcba = NULL;
    HRESULT hr;
    LPWSTR pwszUrl = NULL;
    HMODULE hDll = NULL;
    PFN_CA_OID_GET_LDAP_URL pfnCAOIDGetLdapURL = NULL;
    PFN_CA_OID_FREE_LDAP_URL pfnCAOIDFreeLdapURL = NULL;

    if (!ChainIsConnected())
        goto NotConnected;

    if (NULL == (hDll = LoadLibraryA(sz_CERTCLI_DLL)))
        goto LoadCertCliDllError;

    if (NULL == (pfnCAOIDGetLdapURL =
            (PFN_CA_OID_GET_LDAP_URL) GetProcAddress(hDll,
                sz_CAOIDGetLdapURL)))
        goto CAOIDGetLdapURLProcAddressError;

    if (NULL == (pfnCAOIDFreeLdapURL =
            (PFN_CA_OID_FREE_LDAP_URL) GetProcAddress(hDll,
                sz_CAOIDFreeLdapURL)))
        goto CAOIDFreeLdapURLProcAddressError;

    hr = pfnCAOIDGetLdapURL(
            CERT_OID_TYPE_ALL,
            0,                      // dwFlags
            &pwszUrl
            );
    if (S_OK != hr)
        goto CAOIDGetLdapURLError;

    if (!ChainRetrieveObjectByUrlW (
            pwszUrl,
            NULL,                   // pszObjectOid,
            CRYPT_RETRIEVE_MULTIPLE_OBJECTS     |
                CRYPT_WIRE_ONLY_RETRIEVAL       |
                CRYPT_DONT_CACHE_RESULT         |
                CRYPT_OFFLINE_CHECK_RETRIEVAL   |
                CRYPT_LDAP_SIGN_RETRIEVAL |
                CRYPT_LDAP_INSERT_ENTRY_ATTRIBUTE,
            DS_LDAP_TIMEOUT,
            (LPVOID*) &pcba,
            NULL,                   // hAsyncRetrieve,
            NULL,                   // pCredentials,
            NULL,                   // pvVerify,
            NULL                    // pAuxInfo
            )) {
        DWORD dwErr = GetLastError();
        if (ERROR_FILE_NOT_FOUND == dwErr || CRYPT_E_NOT_FOUND == dwErr)
            goto NoDsOIDObject;
        else
            goto RetrieveObjectByUrlError;
    }

    assert(pcba);

CommonReturn:
    if (hDll) {
        DWORD dwErr = GetLastError();

        if (pfnCAOIDFreeLdapURL && pwszUrl)
            pfnCAOIDFreeLdapURL(pwszUrl);
        
        FreeLibrary(hDll);
        SetLastError(dwErr);
    }
    return pcba;
ErrorReturn:
    assert(NULL == pcba);
    goto CommonReturn;

SET_ERROR(LoadCertCliDllError, ERROR_FILE_NOT_FOUND)
SET_ERROR(CAOIDGetLdapURLProcAddressError, ERROR_FILE_NOT_FOUND)
SET_ERROR(CAOIDFreeLdapURLProcAddressError, ERROR_FILE_NOT_FOUND)
SET_ERROR_VAR(CAOIDGetLdapURLError, hr)
SET_ERROR(NotConnected, ERROR_NOT_CONNECTED)
SET_ERROR(NoDsOIDObject, ERROR_FILE_NOT_FOUND)
TRACE_ERROR(RetrieveObjectByUrlError)
}

#else

// Hard coded URL and credentials for testing

static PCRYPT_BLOB_ARRAY RetrieveDsGroupByLdapUrl()
{
    PCRYPT_BLOB_ARRAY pcba = NULL;

    CRYPT_CREDENTIALS Credentials;
    CRYPT_PASSWORD_CREDENTIALSA PasswordCredentials;

// This gets overwritten by logic in cryptnet.dll
//    char szUsername[] = "domain\\username";
    char szUsername[] = "jettdom\\administrator";

    PasswordCredentials.cbSize = sizeof( PasswordCredentials );
    PasswordCredentials.pszUsername = szUsername;
//    PasswordCredentials.pszPassword = "password";
    PasswordCredentials.pszPassword = "";

    Credentials.cbSize = sizeof( Credentials );
    Credentials.pszCredentialsOid = CREDENTIAL_OID_PASSWORD_CREDENTIALS_A;
    Credentials.pvCredentials = (LPVOID)&PasswordCredentials;

    if (!ChainIsConnected())
        goto NotConnected;


    if (!ChainRetrieveObjectByUrlW (
            L"ldap://jettdomdc/CN=OID,CN=Public Key Services,CN=Services,CN=Configuration,DC=jettdom,DC=nttest,DC=microsoft,DC=com?msPKI-OIDLocalizedName,displayName,msPKI-Cert-Template-OID,flags?one",
            NULL,                   // pszObjectOid,
            CRYPT_RETRIEVE_MULTIPLE_OBJECTS     |
                CRYPT_WIRE_ONLY_RETRIEVAL       |
                CRYPT_DONT_CACHE_RESULT         |
                CRYPT_OFFLINE_CHECK_RETRIEVAL   |
                CRYPT_LDAP_SIGN_RETRIEVAL       |
                CRYPT_LDAP_INSERT_ENTRY_ATTRIBUTE,
            DS_LDAP_TIMEOUT,
            (LPVOID*) &pcba,
            NULL,                   // hAsyncRetrieve,
            &Credentials,
            NULL,                   // pvVerify,
            NULL                    // pAuxInfo
            )) {
        if (ERROR_FILE_NOT_FOUND == GetLastError())
            goto NoDsOIDObject;
        else
            goto RetrieveObjectByUrlError;
    }

    assert(pcba);

CommonReturn:
    return pcba;
ErrorReturn:
    assert(NULL == pcba);
    goto CommonReturn;

SET_ERROR(NotConnected, ERROR_NOT_CONNECTED)
TRACE_ERROR(NoDsOIDObject)
TRACE_ERROR(RetrieveObjectByUrlError)
}

#endif


//+-------------------------------------------------------------------------
//  Frees the DS groups
//
//  Assumption: only called at ProcessDetach
//--------------------------------------------------------------------------
static void FreeDsGroups()
{
    DWORD cInfo;
    PCCRYPT_OID_INFO *ppInfo;

    cInfo = DsGroup.cInfo;
    ppInfo = DsGroup.rgpInfo;
    for ( ; cInfo > 0; cInfo--, ppInfo++)
        OIDInfoFree((PCRYPT_OID_INFO) *ppInfo);
    OIDInfoFree(DsGroup.rgpInfo);

    cInfo = DsDeletedGroup.cInfo;
    ppInfo = DsDeletedGroup.rgpInfo;
    for ( ; cInfo > 0; cInfo--, ppInfo++)
        OIDInfoFree((PCRYPT_OID_INFO) *ppInfo);
    OIDInfoFree(DsDeletedGroup.rgpInfo);
}


//+-------------------------------------------------------------------------
//  Adds the OID info entries to the specified DS group
//
//  Assumption: already in DsCriticalSection
//--------------------------------------------------------------------------
static BOOL AddDsOIDInfo(
    IN PCCRYPT_OID_INFO *ppAddInfo,
    IN DWORD cAddInfo,
    IN OUT PDS_GROUP_ENTRY pGroup
    )
{
    PCCRYPT_OID_INFO *ppInfo;
    assert(cAddInfo && ppAddInfo && *ppAddInfo);

    if (NULL == (ppInfo = (PCCRYPT_OID_INFO *) OIDInfoRealloc(
            pGroup->rgpInfo,
            (pGroup->cInfo + cAddInfo) * sizeof(PCCRYPT_OID_INFO))))
        return FALSE;

    pGroup->rgpInfo = ppInfo;

    ppInfo = &ppInfo[pGroup->cInfo];
    pGroup->cInfo += cAddInfo;
    for ( ; 0 < cAddInfo; cAddInfo --, ppAddInfo++, ppInfo++)
        *ppInfo = *ppAddInfo;

    return TRUE;
}

//+-------------------------------------------------------------------------
//  Moves the OID info entries from one DS group to another DS group
//
//  Assumption: already in DsCriticalSection
//--------------------------------------------------------------------------
static BOOL MoveDsOIDInfo(
    IN DWORD dwInfoIndex,
    IN DWORD cMoveInfo,
    IN OUT PDS_GROUP_ENTRY pSrcGroup,
    IN OUT PDS_GROUP_ENTRY pDstGroup
    )
{
    DWORD cInfo;
    PCCRYPT_OID_INFO *ppInfo;
    DWORD i, j;

    if (0 == cMoveInfo)
        return TRUE;

    assert(dwInfoIndex + cMoveInfo <= pSrcGroup->cInfo);

    if (!AddDsOIDInfo(
            &pSrcGroup->rgpInfo[dwInfoIndex],
            cMoveInfo,
            pDstGroup
            ))
        return FALSE;

    // Move all remaining infos down
    cInfo = pSrcGroup->cInfo;
    ppInfo = pSrcGroup->rgpInfo;
    for (i = dwInfoIndex, j = i + cMoveInfo; j < cInfo; i++, j++)
        ppInfo[i] = ppInfo[j];

    pSrcGroup->cInfo = cInfo - cMoveInfo;

    return TRUE;
}

//+-------------------------------------------------------------------------
//  Creates the OID info by converting the LDAP DS attribute octets.
//
//  If any of the cb's == 0 or not a valid converted group type, returns TRUE
//  with *ppInfo = NULL
//--------------------------------------------------------------------------
static BOOL CreateDsOIDInfo(
    IN BYTE *pbOID,
    IN DWORD cbOID,
    IN BYTE *pbName,
    IN DWORD cbName,
    IN BYTE *pbGroupId,
    IN DWORD cbGroupId,
    OUT PCCRYPT_OID_INFO *ppInfo
    )
{
    BOOL fResult;
    PCRYPT_OID_INFO pInfo = NULL;
    char szType[2];
    DWORD dwType;
    DWORD dwGroupId;

    int cchName;
    LPWSTR pwszName;
    LPSTR pszOID;
    DWORD cbExtra;

    *ppInfo = NULL;

    if (0 == cbOID || 0 == cbName || 1 != cbGroupId)
        return TRUE;

    // Convert the Type bytes to the GroupId and see if a valid DS OID group
    szType[0] = (char) *pbGroupId;
    szType[1] = '\0';
    dwType = 0;
    dwType = (DWORD) atol(szType);

    switch (dwType) {
        case CERT_OID_TYPE_TEMPLATE:
            dwGroupId = CRYPT_TEMPLATE_OID_GROUP_ID;
            break;
        case CERT_OID_TYPE_ISSUER_POLICY:
            dwGroupId = CRYPT_POLICY_OID_GROUP_ID;
            break;
        case CERT_OID_TYPE_APPLICATION_POLICY:
            dwGroupId = CRYPT_ENHKEY_USAGE_OID_GROUP_ID;
            break;
        default:
            return TRUE;
    }

    // The name is a UTF8 encoded string

    cchName = UTF8ToWideChar(
        (LPSTR) pbName,
        cbName,
        NULL,           // lpWideCharStr
        0               // cchWideChar
        );

    if (1 > cchName)
        return TRUE;

    cbExtra = (cchName + 1) * sizeof(WCHAR) + (cbOID + 1);

    pInfo = (PCRYPT_OID_INFO) OIDInfoAlloc(sizeof(CRYPT_OID_INFO) + cbExtra);
    if (NULL == pInfo)
        goto OutOfMemory;
    memset(pInfo, 0, sizeof(CRYPT_OID_INFO));

    pInfo->cbSize = sizeof(CRYPT_OID_INFO);
    pInfo->dwGroupId = dwGroupId;

    pwszName = (LPWSTR) &pInfo[1];
    pInfo->pwszName = (LPCWSTR) pwszName;
    cchName = UTF8ToWideChar(
        (LPSTR) pbName,
        cbName,
        pwszName,
        cchName
        );

    if (1 > cchName)
        goto UTF8ToWideCharError;

    pwszName[cchName] = L'\0';

    pszOID = (LPSTR) (pwszName + (cchName + 1));
    pInfo->pszOID = (LPCSTR) pszOID;
    memcpy(pszOID, pbOID, cbOID);
    pszOID[cbOID] = '\0';


    *ppInfo = (PCCRYPT_OID_INFO) pInfo;
    fResult = TRUE;
CommonReturn:
    return fResult;

ErrorReturn:
    fResult = FALSE;
    goto CommonReturn;
    
TRACE_ERROR(OutOfMemory)
SET_ERROR(UTF8ToWideCharError, ERROR_INVALID_DATA)
}

//+-------------------------------------------------------------------------
//  Creates the OID info entry corresponding to the LDAP DS attribute octets.
//
//  If the OID info entry already exists in the DsDeletedGroup, then, the
//  OID info entry is moved from DsDeletedGroup to DsGroup. Otherwise, the
//  created OID entry is added to the DsGroup.
//
//  Assumption: already in DsCriticalSection
//--------------------------------------------------------------------------
static BOOL CreateAndAddDsOIDInfo(
    IN BYTE *pbOID,
    IN DWORD cbOID,
    IN BYTE *pbName,
    IN DWORD cbName,
    IN BYTE *pbGroupId,
    IN DWORD cbGroupId
    )
{
    BOOL fResult;
    PCCRYPT_OID_INFO pInfo = NULL;

    DWORD cDeletedInfo;
    PCCRYPT_OID_INFO *ppDeletedInfo;
    DWORD i;

    fResult = CreateDsOIDInfo(
        pbOID,
        cbOID,
        pbName,
        cbName,
        pbGroupId,
        cbGroupId,
        &pInfo
        );

    if (NULL == pInfo)
        // Either the create failed or not a valid DS OID group
        return fResult;

    // See if we have an entry in the DS deleted group

    cDeletedInfo = DsDeletedGroup.cInfo;
    ppDeletedInfo = DsDeletedGroup.rgpInfo;
    for (i = 0; i < cDeletedInfo; i++) {
        PCCRYPT_OID_INFO pDeletedInfo = ppDeletedInfo[i];

        if (pInfo->dwGroupId == pDeletedInfo->dwGroupId &&
                0 == strcmp(pInfo->pszOID, pDeletedInfo->pszOID) &&
                0 == wcscmp(pInfo->pwszName, pDeletedInfo->pwszName))
            break;
    }

    if (i < cDeletedInfo) {
        if (!MoveDsOIDInfo(
                i,
                1,          // cInfo
                &DsDeletedGroup,
                &DsGroup
                ))
            goto MoveDsOIDInfoError;
        OIDInfoFree((PCRYPT_OID_INFO) pInfo);
    } else {
        if (!AddDsOIDInfo(&pInfo, 1, &DsGroup))
            goto AddDsOIDInfoError;
    }

    fResult = TRUE;

CommonReturn:
    return fResult;
    
ErrorReturn:
    OIDInfoFree((PCRYPT_OID_INFO) pInfo);
    fResult = FALSE;
    goto CommonReturn;

TRACE_ERROR(MoveDsOIDInfoError)
TRACE_ERROR(AddDsOIDInfoError)
}

//+-------------------------------------------------------------------------
//  Allocates and converts UNICODE string to ASCII.
//--------------------------------------------------------------------------
static LPSTR AllocAndWszToSz(
    IN LPCWSTR pwsz
    )
{
    int cchMultiByte;
    LPSTR psz = NULL;

    cchMultiByte = WideCharToMultiByte(
        CP_ACP,
        0,                      // dwFlags
        pwsz,
        -1,
        NULL,                   // psz,
        0,                      // cchMultiByte
        NULL,                   // lpDefaultChar
        NULL                    // lpfUsedDefaultChar
        );

    if (1 < cchMultiByte) {
        psz = (LPSTR) OIDInfoAlloc(cchMultiByte);

        if (NULL != psz) {
            cchMultiByte = WideCharToMultiByte(
                CP_ACP,
                0,                      // dwFlags
                pwsz,
                -1,
                psz,
                cchMultiByte,
                NULL,                   // lpDefaultChar
                NULL                    // lpfUsedDefaultChar
                );
            if (1 > cchMultiByte) {
                OIDInfoFree(psz);
                psz = NULL;
            } 
        }
    }

    return psz;
}


//+------------------------------------------------------------------------------
//  Helper functions to thunk APIs which don't exist on downlevels.  
//  oidinfo.obj is statically linked in with xenroll, which has to work on
//  all downlevels (win9x, NT4, W2K, XP).  Any functions which aren't supported
//  on these platforms need to be thunked here to avoid a dependency violation.
//-------------------------------------------------------------------------------

#define sz_KERNEL32_DLL                "kernel32.dll"
#define sz_GetSystemDefaultUILanguage  "GetSystemDefaultUILanguage"
#define sz_GetUserDefaultUILanguage    "GetUserDefaultUILanguage"

typedef LANGID (WINAPI *PFNGetSystemDefaultUILanguage) (
    void
    ); 

typedef LANGID (WINAPI *PFNGetUserDefaultUILanguage) (
    void
    ); 

//+------------------------------------------------------------------------------
//  Helper function to thunk GetSystemDefaultUILanguage().  If this API isn't 
//  present, falls back to GetSystemDefaultLangID(). 
//------------------------------------------------------------------------------
static LANGID WINAPI MyGetSystemDefaultUILanguage(void) 
{ 
    HMODULE                        hKernel32                      = NULL; 
    LANGID                         langidRetval; 
    PFNGetSystemDefaultUILanguage  pfnGetSystemDefaultUILanguage  = NULL; 

    hKernel32 = LoadLibraryA(sz_KERNEL32_DLL); 
    if (NULL != hKernel32) { 
	pfnGetSystemDefaultUILanguage = (PFNGetSystemDefaultUILanguage)GetProcAddress(hKernel32, sz_GetSystemDefaultUILanguage); 
    }

    if (NULL != pfnGetSystemDefaultUILanguage) { 
	langidRetval = pfnGetSystemDefaultUILanguage();
    } else { 
	langidRetval = GetSystemDefaultLangID(); 
    }

    if (NULL != hKernel32) { 
	FreeLibrary(hKernel32); 
    }

    return langidRetval; 
}

//+------------------------------------------------------------------------------
//  Helper function to thunk GetUserDefaultUILanguage().  If this API isn't 
//  present, falls back to GetUserDefaultLangID(). 
//------------------------------------------------------------------------------
static LANGID WINAPI MyGetUserDefaultUILanguage(void) 
{ 
    HMODULE                      hKernel32                    = NULL; 
    LANGID                       langidRetval; 
    PFNGetUserDefaultUILanguage  pfnGetUserDefaultUILanguage  = NULL; 

    hKernel32 = LoadLibraryA(sz_KERNEL32_DLL); 
    if (NULL != hKernel32) { 
	pfnGetUserDefaultUILanguage = (PFNGetUserDefaultUILanguage)GetProcAddress(hKernel32, sz_GetUserDefaultUILanguage); 
    }

    if (NULL != pfnGetUserDefaultUILanguage) { 
	langidRetval = pfnGetUserDefaultUILanguage();
    } else { 
	// Use GetSystemDefaultLangID for fallback, even in the user case.  
	// From weiwu: "GetUserDefaultLangID is mostly for standards and formats; 
	//     it shouldn't be used as the UI language fallback."
	langidRetval = GetSystemDefaultLangID(); 
    }

    if (NULL != hKernel32) { 
	FreeLibrary(hKernel32); 
    }

    return langidRetval; 
}


//+-------------------------------------------------------------------------
//  Loads the DS group by doing an LDAP URL retrieval and
//  converting the DS attribute octets into the OID info entries.
//
//  Do the load on the first call. Do subsequent reloads after
//  DS_RETRIEVAL_DELTA_SECONDS has elapsed since a successful load.
//
//  Assumption: not in DsCriticalSection
//--------------------------------------------------------------------------
static void LoadDsGroup()
{
    FILETIME CurrentTime;
    LONG lCmp;

    PCRYPT_BLOB_ARRAY pcba = NULL;
    LPSTR pszOIDAttr = NULL;
    LPSTR pszNameAttr = NULL;
    LPSTR pszLocalizedNameAttr = NULL;
    LPSTR pszGroupAttr = NULL;

    LANGID SystemDefaultLangID = 0;

    LPCSTR pszPrevIndex;    // not allocated
    BYTE *pbOID;
    DWORD cbOID;
    BYTE *pbName;
    DWORD cbName;
    BYTE *pbLocalizedName;
    DWORD cbLocalizedName;
    BYTE *pbGroupId;
    DWORD cbGroupId;
    DWORD i;

    GetSystemTimeAsFileTime(&CurrentTime);
    EnterCriticalSection(&DsCriticalSection);
    lCmp = CompareFileTime(&DsNextUpdateTime, &CurrentTime);
    LeaveCriticalSection(&DsCriticalSection);

    if (0 < lCmp)
        // Current time is before the next update time
        return;

    if (NULL == (pcba = RetrieveDsGroupByLdapUrl()) &&
            ERROR_FILE_NOT_FOUND != GetLastError())
        return;

    EnterCriticalSection(&DsCriticalSection);

    // Move all the Ds group entries to the deleted list. As we iterate
    // through the retrieved LDAP entries, most if not all of the entries
    // in the deleted group will be moved back.
    if (!MoveDsOIDInfo(
            0,              // dwInfoIndex
            DsGroup.cInfo,
            &DsGroup,
            &DsDeletedGroup
            ))
        goto MoveDsGroupOIDInfoError;

    if (NULL == pcba)
        goto NoOIDObjectReturn;

    pszOIDAttr = AllocAndWszToSz(OID_PROP_OID);
    pszNameAttr = AllocAndWszToSz(OID_PROP_DISPLAY_NAME);
    pszLocalizedNameAttr = AllocAndWszToSz(OID_PROP_LOCALIZED_NAME);
    pszGroupAttr = AllocAndWszToSz(OID_PROP_TYPE);
    if (NULL == pszOIDAttr || NULL == pszNameAttr ||
            NULL == pszLocalizedNameAttr|| NULL == pszGroupAttr)
        goto OutOfMemory;

    pszPrevIndex = "";
    pbOID = NULL;
    cbOID = 0;
    pbName = NULL;
    cbName = 0;
    pbLocalizedName = NULL;
    cbLocalizedName = 0;
    pbGroupId = NULL;
    cbGroupId = 0;
    for (i = 0; i < pcba->cBlob; i++ ) {
        PBYTE pb = pcba->rgBlob[i].pbData;
        DWORD cb = pcba->rgBlob[i].cbData;

        DWORD cbPrefix;
        LPCSTR pszIndex;
        LPCSTR pszAttr;

        pszIndex = (LPCSTR) pb;
        cbPrefix = strlen(pszIndex) + 1;
        pb += cbPrefix;
        cb -= cbPrefix;

        pszAttr = (LPCSTR) pb;
        cbPrefix = strlen(pszAttr) + 1;
        pb += cbPrefix;
        cb -= cbPrefix;

        if (0 != strcmp(pszIndex, pszPrevIndex)) {
            if (!CreateAndAddDsOIDInfo(
                    pbOID,
                    cbOID,
                    cbLocalizedName ? pbLocalizedName : pbName,
                    cbLocalizedName ? cbLocalizedName : cbName,
                    pbGroupId,
                    cbGroupId
                    ))
                goto CreateAndAddDsOIDInfoError;
            pszPrevIndex = pszIndex;
            pbOID = NULL;
            cbOID = 0;
            pbName = NULL;
            cbName = 0;
            pbLocalizedName = NULL;
            cbLocalizedName = 0;
            pbGroupId = NULL;
            cbGroupId = 0;
        }

        if (0 == _stricmp(pszAttr, pszOIDAttr)) {
            pbOID = pb;
            cbOID = cb;
        } else if (0 == _stricmp(pszAttr, pszNameAttr)) {
            pbName = pb;
            cbName = cb;
        } else if (0 == _stricmp(pszAttr, pszLocalizedNameAttr)) {
            // The LocalizedName consists of:
            //      "%d,%s", LangID, pszUTF8Name (Name isn't NULL terminated)
            if (0 == cbLocalizedName) {
                LPCSTR pszLangID;

                // Search for the ',' delimiter and convert to a \0
                pszLangID = (LPCSTR) pb;
                for ( ; 0 < cb; pb++, cb--) {
                    if (',' == *pb) {
                        *pb = 0;
                        pb++;
                        cb--;
                        break;
                    }
                }

                if (0 < cb) {
                    LANGID LangID = 0;

                    LangID = (LANGID) strtoul(pszLangID, NULL, 10);
                    if (0 != LangID) {
                        if (0 == SystemDefaultLangID) {
                            SystemDefaultLangID = MyGetUserDefaultUILanguage();
                            if (0 == SystemDefaultLangID)
                                SystemDefaultLangID =
                                    MyGetSystemDefaultUILanguage();
                        }
                        if (LangID == SystemDefaultLangID) {
                            cbLocalizedName = cb;
                            pbLocalizedName = pb;
                        }
                    }
                }
            }
        } else if (0 == _stricmp(pszAttr, pszGroupAttr)) {
            pbGroupId = pb;
            cbGroupId = cb;
        }
    }

    if (!CreateAndAddDsOIDInfo(
            pbOID,
            cbOID,
            cbLocalizedName ? pbLocalizedName : pbName,
            cbLocalizedName ? cbLocalizedName : cbName,
            pbGroupId,
            cbGroupId
            ))
        goto CreateAndAddDsOIDInfoError;


NoOIDObjectReturn:
    I_CryptIncrementFileTimeBySeconds(
        &CurrentTime,
        DS_RETRIEVAL_DELTA_SECONDS,
        &DsNextUpdateTime
        );

CommonReturn:
    LeaveCriticalSection(&DsCriticalSection);

    OIDInfoFree(pszOIDAttr);
    OIDInfoFree(pszNameAttr);
    OIDInfoFree(pszLocalizedNameAttr);
    OIDInfoFree(pszGroupAttr);
    if (pcba)
        CryptMemFree(pcba);
    return;

ErrorReturn:
    goto CommonReturn;

TRACE_ERROR(OutOfMemory)
TRACE_ERROR(MoveDsGroupOIDInfoError)
TRACE_ERROR(CreateAndAddDsOIDInfoError)
}

//+-------------------------------------------------------------------------
//  The DS only contains the ENHKEY, POLICY and TEMPLATE OID groups.
//--------------------------------------------------------------------------
static inline BOOL IsDsGroup(
    IN DWORD dwGroupId
    )
{
    if (0 == dwGroupId                                      ||
            CRYPT_ENHKEY_USAGE_OID_GROUP_ID == dwGroupId    ||
            CRYPT_POLICY_OID_GROUP_ID == dwGroupId          ||
            CRYPT_TEMPLATE_OID_GROUP_ID == dwGroupId
            )
        return TRUE;
    else
        return FALSE;
}

//+-------------------------------------------------------------------------
//  The DS only contains OID and NAME strings
//--------------------------------------------------------------------------
static inline BOOL IsDsKeyType(
    IN DWORD dwKeyType
    )
{
    if (CRYPT_OID_INFO_OID_KEY == dwKeyType ||
            CRYPT_OID_INFO_NAME_KEY == dwKeyType
            )
        return TRUE;
    else
        return FALSE;
}

//+-------------------------------------------------------------------------
//  Search the DS group according to the specified dwKeyType.
//--------------------------------------------------------------------------
static PCCRYPT_OID_INFO SearchDsGroup(
    IN DWORD dwKeyType,
    IN void *pvKey,
    IN DWORD dwGroupId
    )
{
    DWORD cInfo;
    PCCRYPT_OID_INFO *ppInfo;
    PCCRYPT_OID_INFO pInfo = NULL;

    if (!IsDsGroup(dwGroupId) || !IsDsKeyType(dwKeyType))
        return NULL;

    LoadDsGroup();

    EnterCriticalSection(&DsCriticalSection);

    cInfo = DsGroup.cInfo;
    ppInfo = DsGroup.rgpInfo;
    for ( ; cInfo > 0; cInfo--, ppInfo++) {
        pInfo = *ppInfo;
        if (CompareOIDInfo(
                dwKeyType,
                pvKey,
                dwGroupId,
                pInfo
                ))
            break;
    }

    if (0 == cInfo)
        pInfo = NULL;

    LeaveCriticalSection(&DsCriticalSection);

    return pInfo;
}

//+-------------------------------------------------------------------------
//  Enumerate the DS group.
//--------------------------------------------------------------------------
static BOOL EnumDsGroup(
    IN DWORD dwGroupId,
    IN void *pvArg,
    IN PFN_CRYPT_ENUM_OID_INFO pfnEnumOIDInfo
    )
{
    BOOL fResult;
    DWORD cInfo;
    PCCRYPT_OID_INFO *ppInfo = NULL;
    DWORD i;

    if (!IsDsGroup(dwGroupId))
        return TRUE;

    LoadDsGroup();

    EnterCriticalSection(&DsCriticalSection);

    // Make a copy of DS group OID info pointers while within the
    // DS critical section

    cInfo = DsGroup.cInfo;
    if (0 != cInfo) {
        ppInfo = (PCCRYPT_OID_INFO *) OIDInfoAlloc(
            cInfo * sizeof(PCCRYPT_OID_INFO));

        if (ppInfo)
            memcpy(ppInfo, DsGroup.rgpInfo, cInfo * sizeof(PCCRYPT_OID_INFO));
        else
            cInfo = 0;
    }

    LeaveCriticalSection(&DsCriticalSection);


    for (i = 0; i < cInfo; i++) {
        PCCRYPT_OID_INFO pInfo = ppInfo[i];

        if (dwGroupId && dwGroupId != pInfo->dwGroupId)
            continue;

        if (!pfnEnumOIDInfo(pInfo, pvArg))
            goto EnumOIDInfoError;
    }

    fResult = TRUE;

CommonReturn:
    OIDInfoFree(ppInfo);
    return fResult;
ErrorReturn:
    fResult = FALSE;
    goto CommonReturn;

TRACE_ERROR(EnumOIDInfoError)
}
