/*
 * crypt32 Crypt*Object functions
 *
 * Copyright 2007 Juan Lang
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */
#include <stdarg.h>
#define NONAMELESSUNION
#include "windef.h"
#include "winbase.h"
#include "wincrypt.h"
#include "mssip.h"
#include "winuser.h"
#include "wintrust.h"
#include "crypt32_private.h"
#include "cryptres.h"
#include "wine/unicode.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(crypt);

static BOOL CRYPT_ReadBlobFromFile(LPCWSTR fileName, PCERT_BLOB blob)
{
    BOOL ret = FALSE;
    HANDLE file;

    TRACE("%s\n", debugstr_w(fileName));

    file = CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, NULL,
     OPEN_EXISTING, 0, NULL);
    if (file != INVALID_HANDLE_VALUE)
    {
        ret = TRUE;
        blob->cbData = GetFileSize(file, NULL);
        if (blob->cbData)
        {
            blob->pbData = CryptMemAlloc(blob->cbData);
            if (blob->pbData)
            {
                DWORD read;

                ret = ReadFile(file, blob->pbData, blob->cbData, &read, NULL) && read == blob->cbData;
                if (!ret) CryptMemFree(blob->pbData);
            }
            else
                ret = FALSE;
        }
        CloseHandle(file);
    }
    TRACE("returning %d\n", ret);
    return ret;
}

static BOOL CRYPT_QueryContextBlob(const CERT_BLOB *blob,
 DWORD dwExpectedContentTypeFlags, HCERTSTORE store,
 DWORD *contentType, const void **ppvContext)
{
    BOOL ret = FALSE;

    if (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_CERT)
    {
        ret = pCertInterface->addEncodedToStore(store, X509_ASN_ENCODING,
         blob->pbData, blob->cbData, CERT_STORE_ADD_ALWAYS, ppvContext);
        if (ret && contentType)
            *contentType = CERT_QUERY_CONTENT_CERT;
    }
    if (!ret && (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_CRL))
    {
        ret = pCRLInterface->addEncodedToStore(store, X509_ASN_ENCODING,
         blob->pbData, blob->cbData, CERT_STORE_ADD_ALWAYS, ppvContext);
        if (ret && contentType)
            *contentType = CERT_QUERY_CONTENT_CRL;
    }
    if (!ret && (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_CTL))
    {
        ret = pCTLInterface->addEncodedToStore(store, X509_ASN_ENCODING,
         blob->pbData, blob->cbData, CERT_STORE_ADD_ALWAYS, ppvContext);
        if (ret && contentType)
            *contentType = CERT_QUERY_CONTENT_CTL;
    }
    return ret;
}

static BOOL CRYPT_QueryContextObject(DWORD dwObjectType, const void *pvObject,
 DWORD dwExpectedContentTypeFlags, DWORD dwExpectedFormatTypeFlags,
 DWORD *pdwMsgAndCertEncodingType, DWORD *pdwContentType, DWORD *pdwFormatType,
 HCERTSTORE *phCertStore, const void **ppvContext)
{
    CERT_BLOB fileBlob;
    const CERT_BLOB *blob;
    HCERTSTORE store;
    BOOL ret;
    DWORD formatType = 0;

    switch (dwObjectType)
    {
    case CERT_QUERY_OBJECT_FILE:
        /* Cert, CRL, and CTL contexts can't be "embedded" in a file, so
         * just read the file directly
         */
        ret = CRYPT_ReadBlobFromFile(pvObject, &fileBlob);
        blob = &fileBlob;
        break;
    case CERT_QUERY_OBJECT_BLOB:
        blob = pvObject;
        ret = TRUE;
        break;
    default:
        SetLastError(E_INVALIDARG); /* FIXME: is this the correct error? */
        ret = FALSE;
    }
    if (!ret)
        return FALSE;

    ret = FALSE;
    store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0,
     CERT_STORE_CREATE_NEW_FLAG, NULL);
    if (dwExpectedFormatTypeFlags & CERT_QUERY_FORMAT_FLAG_BINARY)
    {
        ret = CRYPT_QueryContextBlob(blob, dwExpectedContentTypeFlags, store,
         pdwContentType, ppvContext);
        if (ret)
            formatType = CERT_QUERY_FORMAT_BINARY;
    }
    if (!ret &&
     (dwExpectedFormatTypeFlags & CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED))
    {
        CRYPT_DATA_BLOB trimmed = { blob->cbData, blob->pbData };
        CRYPT_DATA_BLOB decoded;

        while (trimmed.cbData && !trimmed.pbData[trimmed.cbData - 1])
            trimmed.cbData--;
        ret = CryptStringToBinaryA((LPSTR)trimmed.pbData, trimmed.cbData,
         CRYPT_STRING_BASE64_ANY, NULL, &decoded.cbData, NULL, NULL);
        if (ret)
        {
            decoded.pbData = CryptMemAlloc(decoded.cbData);
            if (decoded.pbData)
            {
                ret = CryptStringToBinaryA((LPSTR)trimmed.pbData,
                 trimmed.cbData, CRYPT_STRING_BASE64_ANY, decoded.pbData,
                 &decoded.cbData, NULL, NULL);
                if (ret)
                {
                    ret = CRYPT_QueryContextBlob(&decoded,
                     dwExpectedContentTypeFlags, store, pdwContentType,
                     ppvContext);
                    if (ret)
                        formatType = CERT_QUERY_FORMAT_BASE64_ENCODED;
                }
                CryptMemFree(decoded.pbData);
            }
            else
                ret = FALSE;
        }
    }
    if (ret)
    {
        if (pdwMsgAndCertEncodingType)
            *pdwMsgAndCertEncodingType = X509_ASN_ENCODING;
        if (pdwFormatType)
            *pdwFormatType = formatType;
        if (phCertStore)
            *phCertStore = CertDuplicateStore(store);
    }
    CertCloseStore(store, 0);
    if (blob == &fileBlob)
        CryptMemFree(blob->pbData);
    TRACE("returning %d\n", ret);
    return ret;
}

static BOOL CRYPT_QuerySerializedContextObject(DWORD dwObjectType,
 const void *pvObject, DWORD dwExpectedContentTypeFlags,
 DWORD *pdwMsgAndCertEncodingType, DWORD *pdwContentType,
 HCERTSTORE *phCertStore, const void **ppvContext)
{
    CERT_BLOB fileBlob;
    const CERT_BLOB *blob;
    const WINE_CONTEXT_INTERFACE *contextInterface = NULL;
    const void *context;
    DWORD contextType;
    BOOL ret;

    switch (dwObjectType)
    {
    case CERT_QUERY_OBJECT_FILE:
        /* Cert, CRL, and CTL contexts can't be "embedded" in a file, so
         * just read the file directly
         */
        ret = CRYPT_ReadBlobFromFile(pvObject, &fileBlob);
        blob = &fileBlob;
        break;
    case CERT_QUERY_OBJECT_BLOB:
        blob = pvObject;
        ret = TRUE;
        break;
    default:
        SetLastError(E_INVALIDARG); /* FIXME: is this the correct error? */
        ret = FALSE;
    }
    if (!ret)
        return FALSE;

    ret = FALSE;
    context = CRYPT_ReadSerializedElement(blob->pbData, blob->cbData,
     CERT_STORE_ALL_CONTEXT_FLAG, &contextType);
    if (context)
    {
        DWORD contentType, certStoreOffset;

        ret = TRUE;
        switch (contextType)
        {
        case CERT_STORE_CERTIFICATE_CONTEXT:
            contextInterface = pCertInterface;
            contentType = CERT_QUERY_CONTENT_SERIALIZED_CERT;
            certStoreOffset = offsetof(CERT_CONTEXT, hCertStore);
            if (!(dwExpectedContentTypeFlags &
             CERT_QUERY_CONTENT_FLAG_SERIALIZED_CERT))
            {
                SetLastError(ERROR_INVALID_DATA);
                ret = FALSE;
                goto end;
            }
            break;
        case CERT_STORE_CRL_CONTEXT:
            contextInterface = pCRLInterface;
            contentType = CERT_QUERY_CONTENT_SERIALIZED_CRL;
            certStoreOffset = offsetof(CRL_CONTEXT, hCertStore);
            if (!(dwExpectedContentTypeFlags &
             CERT_QUERY_CONTENT_FLAG_SERIALIZED_CRL))
            {
                SetLastError(ERROR_INVALID_DATA);
                ret = FALSE;
                goto end;
            }
            break;
        case CERT_STORE_CTL_CONTEXT:
            contextInterface = pCTLInterface;
            contentType = CERT_QUERY_CONTENT_SERIALIZED_CTL;
            certStoreOffset = offsetof(CTL_CONTEXT, hCertStore);
            if (!(dwExpectedContentTypeFlags &
             CERT_QUERY_CONTENT_FLAG_SERIALIZED_CTL))
            {
                SetLastError(ERROR_INVALID_DATA);
                ret = FALSE;
                goto end;
            }
            break;
        default:
            SetLastError(ERROR_INVALID_DATA);
            ret = FALSE;
            goto end;
        }
        if (pdwMsgAndCertEncodingType)
            *pdwMsgAndCertEncodingType = X509_ASN_ENCODING;
        if (pdwContentType)
            *pdwContentType = contentType;
        if (phCertStore)
            *phCertStore = CertDuplicateStore(
             *(HCERTSTORE *)((const BYTE *)context + certStoreOffset));
        if (ppvContext)
        {
            *ppvContext = context;
            Context_AddRef(context_from_ptr(context));
        }
    }

end:
    if (contextInterface && context)
        Context_Release(context_from_ptr(context));
    if (blob == &fileBlob)
        CryptMemFree(blob->pbData);
    TRACE("returning %d\n", ret);
    return ret;
}

static BOOL CRYPT_QuerySerializedStoreFromFile(LPCWSTR fileName,
 DWORD *pdwMsgAndCertEncodingType, DWORD *pdwContentType,
 HCERTSTORE *phCertStore, HCRYPTMSG *phMsg)
{
    HANDLE file;
    BOOL ret = FALSE;

    TRACE("%s\n", debugstr_w(fileName));
    file = CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, NULL,
     OPEN_EXISTING, 0, NULL);
    if (file != INVALID_HANDLE_VALUE)
    {
        HCERTSTORE store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0,
         CERT_STORE_CREATE_NEW_FLAG, NULL);

        ret = CRYPT_ReadSerializedStoreFromFile(file, store);
        if (ret)
        {
            if (pdwMsgAndCertEncodingType)
                *pdwMsgAndCertEncodingType = X509_ASN_ENCODING;
            if (pdwContentType)
                *pdwContentType = CERT_QUERY_CONTENT_SERIALIZED_STORE;
            if (phCertStore)
                *phCertStore = CertDuplicateStore(store);
        }
        CertCloseStore(store, 0);
        CloseHandle(file);
    }
    TRACE("returning %d\n", ret);
    return ret;
}

static BOOL CRYPT_QuerySerializedStoreFromBlob(const CRYPT_DATA_BLOB *blob,
 DWORD *pdwMsgAndCertEncodingType, DWORD *pdwContentType,
 HCERTSTORE *phCertStore, HCRYPTMSG *phMsg)
{
    HCERTSTORE store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0,
     CERT_STORE_CREATE_NEW_FLAG, NULL);
    BOOL ret;

    TRACE("(%d, %p)\n", blob->cbData, blob->pbData);

    ret = CRYPT_ReadSerializedStoreFromBlob(blob, store);
    if (ret)
    {
        if (pdwMsgAndCertEncodingType)
            *pdwMsgAndCertEncodingType = X509_ASN_ENCODING;
        if (pdwContentType)
            *pdwContentType = CERT_QUERY_CONTENT_SERIALIZED_STORE;
        if (phCertStore)
            *phCertStore = CertDuplicateStore(store);
    }
    CertCloseStore(store, 0);
    TRACE("returning %d\n", ret);
    return ret;
}

static BOOL CRYPT_QuerySerializedStoreObject(DWORD dwObjectType,
 const void *pvObject, DWORD *pdwMsgAndCertEncodingType, DWORD *pdwContentType,
 HCERTSTORE *phCertStore, HCRYPTMSG *phMsg)
{
    switch (dwObjectType)
    {
    case CERT_QUERY_OBJECT_FILE:
        return CRYPT_QuerySerializedStoreFromFile(pvObject,
         pdwMsgAndCertEncodingType, pdwContentType, phCertStore, phMsg);
    case CERT_QUERY_OBJECT_BLOB:
        return CRYPT_QuerySerializedStoreFromBlob(pvObject,
         pdwMsgAndCertEncodingType, pdwContentType, phCertStore, phMsg);
    default:
        FIXME("unimplemented for type %d\n", dwObjectType);
        SetLastError(E_INVALIDARG); /* FIXME: is this the correct error? */
        return FALSE;
    }
}

static BOOL CRYPT_QuerySignedMessage(const CRYPT_DATA_BLOB *blob,
 DWORD *pdwMsgAndCertEncodingType, DWORD *pdwContentType, HCRYPTMSG *phMsg)
{
    DWORD encodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    BOOL ret = FALSE;
    HCRYPTMSG msg;

    if ((msg = CryptMsgOpenToDecode(encodingType, 0, 0, 0, NULL, NULL)))
    {
        ret = CryptMsgUpdate(msg, blob->pbData, blob->cbData, TRUE);
        if (ret)
        {
            DWORD type, len = sizeof(type);

            ret = CryptMsgGetParam(msg, CMSG_TYPE_PARAM, 0, &type, &len);
            if (ret)
            {
                if (type != CMSG_SIGNED)
                {
                    SetLastError(ERROR_INVALID_DATA);
                    ret = FALSE;
                }
            }
        }
        if (!ret)
        {
            CryptMsgClose(msg);
            msg = CryptMsgOpenToDecode(encodingType, 0, CMSG_SIGNED, 0, NULL,
             NULL);
            if (msg)
            {
                ret = CryptMsgUpdate(msg, blob->pbData, blob->cbData, TRUE);
                if (!ret)
                {
                    CryptMsgClose(msg);
                    msg = NULL;
                }
            }
        }
    }
    if (ret)
    {
        if (pdwMsgAndCertEncodingType)
            *pdwMsgAndCertEncodingType = encodingType;
        if (pdwContentType)
            *pdwContentType = CERT_QUERY_CONTENT_PKCS7_SIGNED;
        if (phMsg)
            *phMsg = msg;
    }
    return ret;
}

static BOOL CRYPT_QueryUnsignedMessage(const CRYPT_DATA_BLOB *blob,
 DWORD *pdwMsgAndCertEncodingType, DWORD *pdwContentType, HCRYPTMSG *phMsg)
{
    DWORD encodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    BOOL ret = FALSE;
    HCRYPTMSG msg;

    if ((msg = CryptMsgOpenToDecode(encodingType, 0, 0, 0, NULL, NULL)))
    {
        ret = CryptMsgUpdate(msg, blob->pbData, blob->cbData, TRUE);
        if (ret)
        {
            DWORD type, len = sizeof(type);

            ret = CryptMsgGetParam(msg, CMSG_TYPE_PARAM, 0, &type, &len);
            if (ret)
            {
                if (type != CMSG_DATA)
                {
                    SetLastError(ERROR_INVALID_DATA);
                    ret = FALSE;
                }
            }
        }
        if (!ret)
        {
            CryptMsgClose(msg);
            msg = CryptMsgOpenToDecode(encodingType, 0, CMSG_DATA, 0,
             NULL, NULL);
            if (msg)
            {
                ret = CryptMsgUpdate(msg, blob->pbData, blob->cbData, TRUE);
                if (!ret)
                {
                    CryptMsgClose(msg);
                    msg = NULL;
                }
            }
        }
    }
    if (ret)
    {
        if (pdwMsgAndCertEncodingType)
            *pdwMsgAndCertEncodingType = encodingType;
        if (pdwContentType)
            *pdwContentType = CERT_QUERY_CONTENT_PKCS7_SIGNED;
        if (phMsg)
            *phMsg = msg;
    }
    return ret;
}

/* Used to decode non-embedded messages */
static BOOL CRYPT_QueryMessageObject(DWORD dwObjectType, const void *pvObject,
 DWORD dwExpectedContentTypeFlags, DWORD dwExpectedFormatTypeFlags,
 DWORD *pdwMsgAndCertEncodingType, DWORD *pdwContentType, DWORD *pdwFormatType,
 HCERTSTORE *phCertStore, HCRYPTMSG *phMsg)
{
    CERT_BLOB fileBlob;
    const CERT_BLOB *blob;
    BOOL ret;
    HCRYPTMSG msg = NULL;
    DWORD encodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    DWORD formatType = 0;

    TRACE("(%d, %p, %08x, %08x, %p, %p, %p, %p, %p)\n", dwObjectType, pvObject,
     dwExpectedContentTypeFlags, dwExpectedFormatTypeFlags,
     pdwMsgAndCertEncodingType, pdwContentType, pdwFormatType, phCertStore,
     phMsg);

    switch (dwObjectType)
    {
    case CERT_QUERY_OBJECT_FILE:
        /* This isn't an embedded PKCS7 message, so just read the file
         * directly
         */
        ret = CRYPT_ReadBlobFromFile(pvObject, &fileBlob);
        blob = &fileBlob;
        break;
    case CERT_QUERY_OBJECT_BLOB:
        blob = pvObject;
        ret = TRUE;
        break;
    default:
        SetLastError(E_INVALIDARG); /* FIXME: is this the correct error? */
        ret = FALSE;
    }
    if (!ret)
        return FALSE;

    ret = FALSE;
    if (dwExpectedFormatTypeFlags & CERT_QUERY_FORMAT_FLAG_BINARY)
    {
        /* Try it first as a signed message */
        if (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED)
            ret = CRYPT_QuerySignedMessage(blob, pdwMsgAndCertEncodingType,
             pdwContentType, &msg);
        /* Failing that, try as an unsigned message */
        if (!ret &&
         (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED))
            ret = CRYPT_QueryUnsignedMessage(blob, pdwMsgAndCertEncodingType,
             pdwContentType, &msg);
        if (ret)
            formatType = CERT_QUERY_FORMAT_BINARY;
    }
    if (!ret &&
     (dwExpectedFormatTypeFlags & CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED))
    {
        CRYPT_DATA_BLOB trimmed = { blob->cbData, blob->pbData };
        CRYPT_DATA_BLOB decoded;

        while (trimmed.cbData && !trimmed.pbData[trimmed.cbData - 1])
            trimmed.cbData--;
        ret = CryptStringToBinaryA((LPSTR)trimmed.pbData, trimmed.cbData,
         CRYPT_STRING_BASE64_ANY, NULL, &decoded.cbData, NULL, NULL);
        if (ret)
        {
            decoded.pbData = CryptMemAlloc(decoded.cbData);
            if (decoded.pbData)
            {
                ret = CryptStringToBinaryA((LPSTR)trimmed.pbData,
                 trimmed.cbData, CRYPT_STRING_BASE64_ANY, decoded.pbData,
                 &decoded.cbData, NULL, NULL);
                if (ret)
                {
                    /* Try it first as a signed message */
                    if (dwExpectedContentTypeFlags &
                     CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED)
                        ret = CRYPT_QuerySignedMessage(&decoded,
                         pdwMsgAndCertEncodingType, pdwContentType, &msg);
                    /* Failing that, try as an unsigned message */
                    if (!ret && (dwExpectedContentTypeFlags &
                     CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED))
                        ret = CRYPT_QueryUnsignedMessage(&decoded,
                         pdwMsgAndCertEncodingType, pdwContentType, &msg);
                    if (ret)
                        formatType = CERT_QUERY_FORMAT_BASE64_ENCODED;
                }
                CryptMemFree(decoded.pbData);
            }
            else
                ret = FALSE;
        }
        if (!ret && !(blob->cbData % sizeof(WCHAR)))
        {
            CRYPT_DATA_BLOB decoded;
            LPWSTR str = (LPWSTR)blob->pbData;
            DWORD strLen = blob->cbData / sizeof(WCHAR);

            /* Try again, assuming the input string is UTF-16 base64 */
            while (strLen && !str[strLen - 1])
                strLen--;
            ret = CryptStringToBinaryW(str, strLen, CRYPT_STRING_BASE64_ANY,
             NULL, &decoded.cbData, NULL, NULL);
            if (ret)
            {
                decoded.pbData = CryptMemAlloc(decoded.cbData);
                if (decoded.pbData)
                {
                    ret = CryptStringToBinaryW(str, strLen,
                     CRYPT_STRING_BASE64_ANY, decoded.pbData, &decoded.cbData,
                     NULL, NULL);
                    if (ret)
                    {
                        /* Try it first as a signed message */
                        if (dwExpectedContentTypeFlags &
                         CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED)
                            ret = CRYPT_QuerySignedMessage(&decoded,
                             pdwMsgAndCertEncodingType, pdwContentType, &msg);
                        /* Failing that, try as an unsigned message */
                        if (!ret && (dwExpectedContentTypeFlags &
                         CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED))
                            ret = CRYPT_QueryUnsignedMessage(&decoded,
                             pdwMsgAndCertEncodingType, pdwContentType, &msg);
                        if (ret)
                            formatType = CERT_QUERY_FORMAT_BASE64_ENCODED;
                    }
                    CryptMemFree(decoded.pbData);
                }
                else
                    ret = FALSE;
            }
        }
    }
    if (ret)
    {
        if (pdwFormatType)
            *pdwFormatType = formatType;
        if (phCertStore)
            *phCertStore = CertOpenStore(CERT_STORE_PROV_MSG, encodingType, 0,
             0, msg);
        if (phMsg)
            *phMsg = msg;
        else
            CryptMsgClose(msg);
    }
    if (blob == &fileBlob)
        CryptMemFree(blob->pbData);
    TRACE("returning %d\n", ret);
    return ret;
}

static BOOL CRYPT_QueryEmbeddedMessageObject(DWORD dwObjectType,
 const void *pvObject, DWORD dwExpectedContentTypeFlags,
 DWORD *pdwMsgAndCertEncodingType, DWORD *pdwContentType,
 HCERTSTORE *phCertStore, HCRYPTMSG *phMsg)
{
    HANDLE file;
    GUID subject;
    BOOL ret = FALSE;

    TRACE("%s\n", debugstr_w(pvObject));

    if (dwObjectType != CERT_QUERY_OBJECT_FILE)
    {
        WARN("don't know what to do for type %d embedded signed messages\n",
         dwObjectType);
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    file = CreateFileW(pvObject, GENERIC_READ, FILE_SHARE_READ,
     NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file != INVALID_HANDLE_VALUE)
    {
        ret = CryptSIPRetrieveSubjectGuid(pvObject, file, &subject);
        if (ret)
        {
            SIP_DISPATCH_INFO sip;

            memset(&sip, 0, sizeof(sip));
            sip.cbSize = sizeof(sip);
            ret = CryptSIPLoad(&subject, 0, &sip);
            if (ret)
            {
                SIP_SUBJECTINFO subjectInfo;
                CERT_BLOB blob;
                DWORD encodingType;

                memset(&subjectInfo, 0, sizeof(subjectInfo));
                subjectInfo.cbSize = sizeof(subjectInfo);
                subjectInfo.pgSubjectType = &subject;
                subjectInfo.hFile = file;
                subjectInfo.pwsFileName = pvObject;
                ret = sip.pfGet(&subjectInfo, &encodingType, 0, &blob.cbData,
                 NULL);
                if (ret)
                {
                    blob.pbData = CryptMemAlloc(blob.cbData);
                    if (blob.pbData)
                    {
                        ret = sip.pfGet(&subjectInfo, &encodingType, 0,
                         &blob.cbData, blob.pbData);
                        if (ret)
                        {
                            ret = CRYPT_QueryMessageObject(
                             CERT_QUERY_OBJECT_BLOB, &blob,
                             CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED,
                             CERT_QUERY_FORMAT_FLAG_BINARY,
                             pdwMsgAndCertEncodingType, NULL, NULL,
                             phCertStore, phMsg);
                            if (ret && pdwContentType)
                                *pdwContentType = CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED;
                        }
                        CryptMemFree(blob.pbData);
                    }
                    else
                    {
                        SetLastError(ERROR_OUTOFMEMORY);
                        ret = FALSE;
                    }
                }
            }
        }
        CloseHandle(file);
    }
    TRACE("returning %d\n", ret);
    return ret;
}

BOOL WINAPI CryptQueryObject(DWORD dwObjectType, const void *pvObject,
 DWORD dwExpectedContentTypeFlags, DWORD dwExpectedFormatTypeFlags,
 DWORD dwFlags, DWORD *pdwMsgAndCertEncodingType, DWORD *pdwContentType,
 DWORD *pdwFormatType, HCERTSTORE *phCertStore, HCRYPTMSG *phMsg,
 const void **ppvContext)
{
    static const DWORD unimplementedTypes =
     CERT_QUERY_CONTENT_FLAG_PKCS10 | CERT_QUERY_CONTENT_FLAG_PFX |
     CERT_QUERY_CONTENT_FLAG_CERT_PAIR;
    BOOL ret = TRUE;

    TRACE("(%08x, %p, %08x, %08x, %08x, %p, %p, %p, %p, %p, %p)\n",
     dwObjectType, pvObject, dwExpectedContentTypeFlags,
     dwExpectedFormatTypeFlags, dwFlags, pdwMsgAndCertEncodingType,
     pdwContentType, pdwFormatType, phCertStore, phMsg, ppvContext);

    if (dwObjectType != CERT_QUERY_OBJECT_BLOB &&
     dwObjectType != CERT_QUERY_OBJECT_FILE)
    {
        WARN("unsupported type %d\n", dwObjectType);
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if (!pvObject)
    {
        WARN("missing required argument\n");
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if (dwExpectedContentTypeFlags & unimplementedTypes)
        WARN("unimplemented for types %08x\n",
         dwExpectedContentTypeFlags & unimplementedTypes);

    if (pdwFormatType)
        *pdwFormatType = CERT_QUERY_FORMAT_BINARY;
    if (phCertStore)
        *phCertStore = NULL;
    if (phMsg)
        *phMsg = NULL;
    if (ppvContext)
        *ppvContext = NULL;

    ret = FALSE;
    if ((dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_CERT) ||
     (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_CRL) ||
     (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_CTL))
    {
        ret = CRYPT_QueryContextObject(dwObjectType, pvObject,
         dwExpectedContentTypeFlags, dwExpectedFormatTypeFlags,
         pdwMsgAndCertEncodingType, pdwContentType, pdwFormatType, phCertStore,
         ppvContext);
    }
    if (!ret &&
     (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_SERIALIZED_STORE))
    {
        ret = CRYPT_QuerySerializedStoreObject(dwObjectType, pvObject,
         pdwMsgAndCertEncodingType, pdwContentType, phCertStore, phMsg);
    }
    if (!ret &&
     ((dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_SERIALIZED_CERT) ||
     (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_SERIALIZED_CRL) ||
     (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_SERIALIZED_CTL)))
    {
        ret = CRYPT_QuerySerializedContextObject(dwObjectType, pvObject,
         dwExpectedContentTypeFlags, pdwMsgAndCertEncodingType, pdwContentType,
         phCertStore, ppvContext);
    }
    if (!ret &&
     ((dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED) ||
     (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED)))
    {
        ret = CRYPT_QueryMessageObject(dwObjectType, pvObject,
         dwExpectedContentTypeFlags, dwExpectedFormatTypeFlags,
         pdwMsgAndCertEncodingType, pdwContentType, pdwFormatType,
         phCertStore, phMsg);
    }
    if (!ret &&
     (dwExpectedContentTypeFlags & CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED))
    {
        ret = CRYPT_QueryEmbeddedMessageObject(dwObjectType, pvObject,
         dwExpectedContentTypeFlags, pdwMsgAndCertEncodingType, pdwContentType,
         phCertStore, phMsg);
    }
    if (!ret)
        SetLastError(CRYPT_E_NO_MATCH);
    TRACE("returning %d\n", ret);
    return ret;
}

static BOOL WINAPI CRYPT_FormatHexString(DWORD dwCertEncodingType,
 DWORD dwFormatType, DWORD dwFormatStrType, void *pFormatStruct,
 LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat,
 DWORD *pcbFormat)
{
    BOOL ret;
    DWORD bytesNeeded;

    if (cbEncoded)
        bytesNeeded = (cbEncoded * 3) * sizeof(WCHAR);
    else
        bytesNeeded = sizeof(WCHAR);
    if (!pbFormat)
    {
        *pcbFormat = bytesNeeded;
        ret = TRUE;
    }
    else if (*pcbFormat < bytesNeeded)
    {
        *pcbFormat = bytesNeeded;
        SetLastError(ERROR_MORE_DATA);
        ret = FALSE;
    }
    else
    {
        static const WCHAR fmt[] = { '%','0','2','x',' ',0 };
        static const WCHAR endFmt[] = { '%','0','2','x',0 };
        DWORD i;
        LPWSTR ptr = pbFormat;

        *pcbFormat = bytesNeeded;
        if (cbEncoded)
        {
            for (i = 0; i < cbEncoded; i++)
            {
                if (i < cbEncoded - 1)
                    ptr += sprintfW(ptr, fmt, pbEncoded[i]);
                else
                    ptr += sprintfW(ptr, endFmt, pbEncoded[i]);
            }
        }
        else
            *ptr = 0;
        ret = TRUE;
    }
    return ret;
}

#define MAX_STRING_RESOURCE_LEN 128

static const WCHAR commaSpace[] = { ',',' ',0 };

struct BitToString
{
    BYTE bit;
    int id;
    WCHAR str[MAX_STRING_RESOURCE_LEN];
};

static BOOL CRYPT_FormatBits(BYTE bits, const struct BitToString *map,
 DWORD mapEntries, void *pbFormat, DWORD *pcbFormat, BOOL *first)
{
    DWORD bytesNeeded = sizeof(WCHAR);
    unsigned int i;
    BOOL ret = TRUE, localFirst = *first;

    for (i = 0; i < mapEntries; i++)
        if (bits & map[i].bit)
        {
            if (!localFirst)
                bytesNeeded += strlenW(commaSpace) * sizeof(WCHAR);
            localFirst = FALSE;
            bytesNeeded += strlenW(map[i].str) * sizeof(WCHAR);
        }
    if (!pbFormat)
    {
        *first = localFirst;
        *pcbFormat = bytesNeeded;
    }
    else if (*pcbFormat < bytesNeeded)
    {
        *first = localFirst;
        *pcbFormat = bytesNeeded;
        SetLastError(ERROR_MORE_DATA);
        ret = FALSE;
    }
    else
    {
        LPWSTR str = pbFormat;

        localFirst = *first;
        *pcbFormat = bytesNeeded;
        for (i = 0; i < mapEntries; i++)
            if (bits & map[i].bit)
            {
                if (!localFirst)
                {
                    strcpyW(str, commaSpace);
                    str += strlenW(commaSpace);
                }
                localFirst = FALSE;
                strcpyW(str, map[i].str);
                str += strlenW(map[i].str);
            }
        *first = localFirst;
    }
    return ret;
}

static struct BitToString keyUsageByte0Map[] = {
 { CERT_DIGITAL_SIGNATURE_KEY_USAGE, IDS_DIGITAL_SIGNATURE, { 0 } },
 { CERT_NON_REPUDIATION_KEY_USAGE, IDS_NON_REPUDIATION, { 0 } },
 { CERT_KEY_ENCIPHERMENT_KEY_USAGE, IDS_KEY_ENCIPHERMENT, { 0 } },
 { CERT_DATA_ENCIPHERMENT_KEY_USAGE, IDS_DATA_ENCIPHERMENT, { 0 } },
 { CERT_KEY_AGREEMENT_KEY_USAGE, IDS_KEY_AGREEMENT, { 0 } },
 { CERT_KEY_CERT_SIGN_KEY_USAGE, IDS_CERT_SIGN, { 0 } },
 { CERT_OFFLINE_CRL_SIGN_KEY_USAGE, IDS_OFFLINE_CRL_SIGN, { 0 } },
 { CERT_CRL_SIGN_KEY_USAGE, IDS_CRL_SIGN, { 0 } },
 { CERT_ENCIPHER_ONLY_KEY_USAGE, IDS_ENCIPHER_ONLY, { 0 } },
};
static struct BitToString keyUsageByte1Map[] = {
 { CERT_DECIPHER_ONLY_KEY_USAGE, IDS_DECIPHER_ONLY, { 0 } },
};

static BOOL WINAPI CRYPT_FormatKeyUsage(DWORD dwCertEncodingType,
 DWORD dwFormatType, DWORD dwFormatStrType, void *pFormatStruct,
 LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat,
 DWORD *pcbFormat)
{
    DWORD size;
    CRYPT_BIT_BLOB *bits;
    BOOL ret;

    if (!cbEncoded)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if ((ret = CryptDecodeObjectEx(dwCertEncodingType, X509_KEY_USAGE,
     pbEncoded, cbEncoded, CRYPT_DECODE_ALLOC_FLAG, NULL, &bits, &size)))
    {
        WCHAR infoNotAvailable[MAX_STRING_RESOURCE_LEN];
        DWORD bytesNeeded = sizeof(WCHAR);

        LoadStringW(hInstance, IDS_INFO_NOT_AVAILABLE, infoNotAvailable,
         sizeof(infoNotAvailable) / sizeof(infoNotAvailable[0]));
        if (!bits->cbData || bits->cbData > 2)
        {
            bytesNeeded += strlenW(infoNotAvailable) * sizeof(WCHAR);
            if (!pbFormat)
                *pcbFormat = bytesNeeded;
            else if (*pcbFormat < bytesNeeded)
            {
                *pcbFormat = bytesNeeded;
                SetLastError(ERROR_MORE_DATA);
                ret = FALSE;
            }
            else
            {
                LPWSTR str = pbFormat;

                *pcbFormat = bytesNeeded;
                strcpyW(str, infoNotAvailable);
            }
        }
        else
        {
            static BOOL stringsLoaded = FALSE;
            unsigned int i;
            DWORD bitStringLen;
            BOOL first = TRUE;

            if (!stringsLoaded)
            {
                for (i = 0;
                 i < sizeof(keyUsageByte0Map) / sizeof(keyUsageByte0Map[0]);
                 i++)
                    LoadStringW(hInstance, keyUsageByte0Map[i].id,
                     keyUsageByte0Map[i].str, MAX_STRING_RESOURCE_LEN);
                for (i = 0;
                 i < sizeof(keyUsageByte1Map) / sizeof(keyUsageByte1Map[0]);
                 i++)
                    LoadStringW(hInstance, keyUsageByte1Map[i].id,
                     keyUsageByte1Map[i].str, MAX_STRING_RESOURCE_LEN);
                stringsLoaded = TRUE;
            }
            CRYPT_FormatBits(bits->pbData[0], keyUsageByte0Map,
             sizeof(keyUsageByte0Map) / sizeof(keyUsageByte0Map[0]),
             NULL, &bitStringLen, &first);
            bytesNeeded += bitStringLen;
            if (bits->cbData == 2)
            {
                CRYPT_FormatBits(bits->pbData[1], keyUsageByte1Map,
                 sizeof(keyUsageByte1Map) / sizeof(keyUsageByte1Map[0]),
                 NULL, &bitStringLen, &first);
                bytesNeeded += bitStringLen;
            }
            bytesNeeded += 3 * sizeof(WCHAR); /* " (" + ")" */
            CRYPT_FormatHexString(0, 0, 0, NULL, NULL, bits->pbData,
             bits->cbData, NULL, &size);
            bytesNeeded += size;
            if (!pbFormat)
                *pcbFormat = bytesNeeded;
            else if (*pcbFormat < bytesNeeded)
            {
                *pcbFormat = bytesNeeded;
                SetLastError(ERROR_MORE_DATA);
                ret = FALSE;
            }
            else
            {
                LPWSTR str = pbFormat;

                bitStringLen = bytesNeeded;
                first = TRUE;
                CRYPT_FormatBits(bits->pbData[0], keyUsageByte0Map,
                 sizeof(keyUsageByte0Map) / sizeof(keyUsageByte0Map[0]),
                 str, &bitStringLen, &first);
                str += bitStringLen / sizeof(WCHAR) - 1;
                if (bits->cbData == 2)
                {
                    bitStringLen = bytesNeeded;
                    CRYPT_FormatBits(bits->pbData[1], keyUsageByte1Map,
                     sizeof(keyUsageByte1Map) / sizeof(keyUsageByte1Map[0]),
                     str, &bitStringLen, &first);
                    str += bitStringLen / sizeof(WCHAR) - 1;
                }
                *str++ = ' ';
                *str++ = '(';
                CRYPT_FormatHexString(0, 0, 0, NULL, NULL, bits->pbData,
                 bits->cbData, str, &size);
                str += size / sizeof(WCHAR) - 1;
                *str++ = ')';
                *str = 0;
            }
        }
        LocalFree(bits);
    }
    return ret;
}

static const WCHAR crlf[] = { '\r','\n',0 };

static WCHAR subjectTypeHeader[MAX_STRING_RESOURCE_LEN];
static WCHAR subjectTypeCA[MAX_STRING_RESOURCE_LEN];
static WCHAR subjectTypeEndCert[MAX_STRING_RESOURCE_LEN];
static WCHAR pathLengthHeader[MAX_STRING_RESOURCE_LEN];

static BOOL WINAPI CRYPT_FormatBasicConstraints2(DWORD dwCertEncodingType,
 DWORD dwFormatType, DWORD dwFormatStrType, void *pFormatStruct,
 LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat,
 DWORD *pcbFormat)
{
    DWORD size;
    CERT_BASIC_CONSTRAINTS2_INFO *info;
    BOOL ret;

    if (!cbEncoded)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if ((ret = CryptDecodeObjectEx(dwCertEncodingType, X509_BASIC_CONSTRAINTS2,
     pbEncoded, cbEncoded, CRYPT_DECODE_ALLOC_FLAG, NULL, &info, &size)))
    {
        static const WCHAR pathFmt[] = { '%','d',0 };
        static BOOL stringsLoaded = FALSE;
        DWORD bytesNeeded = sizeof(WCHAR); /* space for the NULL terminator */
        WCHAR pathLength[MAX_STRING_RESOURCE_LEN];
        LPCWSTR sep, subjectType;
        DWORD sepLen;

        if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
        {
            sep = crlf;
            sepLen = strlenW(crlf) * sizeof(WCHAR);
        }
        else
        {
            sep = commaSpace;
            sepLen = strlenW(commaSpace) * sizeof(WCHAR);
        }

        if (!stringsLoaded)
        {
            LoadStringW(hInstance, IDS_SUBJECT_TYPE, subjectTypeHeader,
             sizeof(subjectTypeHeader) / sizeof(subjectTypeHeader[0]));
            LoadStringW(hInstance, IDS_SUBJECT_TYPE_CA, subjectTypeCA,
             sizeof(subjectTypeCA) / sizeof(subjectTypeCA[0]));
            LoadStringW(hInstance, IDS_SUBJECT_TYPE_END_CERT,
             subjectTypeEndCert,
             sizeof(subjectTypeEndCert) / sizeof(subjectTypeEndCert[0]));
            LoadStringW(hInstance, IDS_PATH_LENGTH, pathLengthHeader,
             sizeof(pathLengthHeader) / sizeof(pathLengthHeader[0]));
            stringsLoaded = TRUE;
        }
        bytesNeeded += strlenW(subjectTypeHeader) * sizeof(WCHAR);
        if (info->fCA)
            subjectType = subjectTypeCA;
        else
            subjectType = subjectTypeEndCert;
        bytesNeeded += strlenW(subjectType) * sizeof(WCHAR);
        bytesNeeded += sepLen;
        bytesNeeded += strlenW(pathLengthHeader) * sizeof(WCHAR);
        if (info->fPathLenConstraint)
            sprintfW(pathLength, pathFmt, info->dwPathLenConstraint);
        else
            LoadStringW(hInstance, IDS_PATH_LENGTH_NONE, pathLength,
             sizeof(pathLength) / sizeof(pathLength[0]));
        bytesNeeded += strlenW(pathLength) * sizeof(WCHAR);
        if (!pbFormat)
            *pcbFormat = bytesNeeded;
        else if (*pcbFormat < bytesNeeded)
        {
            *pcbFormat = bytesNeeded;
            SetLastError(ERROR_MORE_DATA);
            ret = FALSE;
        }
        else
        {
            LPWSTR str = pbFormat;

            *pcbFormat = bytesNeeded;
            strcpyW(str, subjectTypeHeader);
            str += strlenW(subjectTypeHeader);
            strcpyW(str, subjectType);
            str += strlenW(subjectType);
            strcpyW(str, sep);
            str += sepLen / sizeof(WCHAR);
            strcpyW(str, pathLengthHeader);
            str += strlenW(pathLengthHeader);
            strcpyW(str, pathLength);
        }
        LocalFree(info);
    }
    return ret;
}

static BOOL CRYPT_FormatHexStringWithPrefix(const CRYPT_DATA_BLOB *blob, int id,
 LPWSTR str, DWORD *pcbStr)
{
    WCHAR buf[MAX_STRING_RESOURCE_LEN];
    DWORD bytesNeeded;
    BOOL ret;

    LoadStringW(hInstance, id, buf, sizeof(buf) / sizeof(buf[0]));
    CRYPT_FormatHexString(X509_ASN_ENCODING, 0, 0, NULL, NULL,
     blob->pbData, blob->cbData, NULL, &bytesNeeded);
    bytesNeeded += strlenW(buf) * sizeof(WCHAR);
    if (!str)
    {
        *pcbStr = bytesNeeded;
        ret = TRUE;
    }
    else if (*pcbStr < bytesNeeded)
    {
        *pcbStr = bytesNeeded;
        SetLastError(ERROR_MORE_DATA);
        ret = FALSE;
    }
    else
    {
        *pcbStr = bytesNeeded;
        strcpyW(str, buf);
        str += strlenW(str);
        bytesNeeded -= strlenW(str) * sizeof(WCHAR);
        ret = CRYPT_FormatHexString(X509_ASN_ENCODING, 0, 0, NULL, NULL,
         blob->pbData, blob->cbData, str, &bytesNeeded);
    }
    return ret;
}

static BOOL CRYPT_FormatKeyId(const CRYPT_DATA_BLOB *keyId, LPWSTR str,
 DWORD *pcbStr)
{
    return CRYPT_FormatHexStringWithPrefix(keyId, IDS_KEY_ID, str, pcbStr);
}

static BOOL CRYPT_FormatCertSerialNumber(const CRYPT_DATA_BLOB *serialNum, LPWSTR str,
 DWORD *pcbStr)
{
    return CRYPT_FormatHexStringWithPrefix(serialNum, IDS_CERT_SERIAL_NUMBER,
     str, pcbStr);
}

static const WCHAR indent[] = { ' ',' ',' ',' ',' ',0 };
static const WCHAR colonCrlf[] = { ':','\r','\n',0 };

static BOOL CRYPT_FormatAltNameEntry(DWORD dwFormatStrType, DWORD indentLevel,
 const CERT_ALT_NAME_ENTRY *entry, LPWSTR str, DWORD *pcbStr)
{
    BOOL ret;
    WCHAR buf[MAX_STRING_RESOURCE_LEN];
    WCHAR mask[MAX_STRING_RESOURCE_LEN];
    WCHAR ipAddrBuf[32];
    WCHAR maskBuf[16];
    DWORD bytesNeeded = sizeof(WCHAR);
    DWORD strType = CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG;

    if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
        bytesNeeded += indentLevel * strlenW(indent) * sizeof(WCHAR);
    switch (entry->dwAltNameChoice)
    {
    case CERT_ALT_NAME_RFC822_NAME:
        LoadStringW(hInstance, IDS_ALT_NAME_RFC822_NAME, buf,
         sizeof(buf) / sizeof(buf[0]));
        bytesNeeded += strlenW(entry->u.pwszRfc822Name) * sizeof(WCHAR);
        ret = TRUE;
        break;
    case CERT_ALT_NAME_DNS_NAME:
        LoadStringW(hInstance, IDS_ALT_NAME_DNS_NAME, buf,
         sizeof(buf) / sizeof(buf[0]));
        bytesNeeded += strlenW(entry->u.pwszDNSName) * sizeof(WCHAR);
        ret = TRUE;
        break;
    case CERT_ALT_NAME_DIRECTORY_NAME:
    {
        DWORD directoryNameLen;

        if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
            strType |= CERT_NAME_STR_CRLF_FLAG;
        directoryNameLen = cert_name_to_str_with_indent(X509_ASN_ENCODING,
         indentLevel + 1, &entry->u.DirectoryName, strType, NULL, 0);
        LoadStringW(hInstance, IDS_ALT_NAME_DIRECTORY_NAME, buf,
         sizeof(buf) / sizeof(buf[0]));
        bytesNeeded += (directoryNameLen - 1) * sizeof(WCHAR);
        if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
            bytesNeeded += strlenW(colonCrlf) * sizeof(WCHAR);
        else
            bytesNeeded += sizeof(WCHAR); /* '=' */
        ret = TRUE;
        break;
    }
    case CERT_ALT_NAME_URL:
        LoadStringW(hInstance, IDS_ALT_NAME_URL, buf,
         sizeof(buf) / sizeof(buf[0]));
        bytesNeeded += strlenW(entry->u.pwszURL) * sizeof(WCHAR);
        ret = TRUE;
        break;
    case CERT_ALT_NAME_IP_ADDRESS:
    {
        static const WCHAR ipAddrWithMaskFmt[] = { '%','d','.','%','d','.',
         '%','d','.','%','d','/','%','d','.','%','d','.','%','d','.','%','d',0
        };
        static const WCHAR ipAddrFmt[] = { '%','d','.','%','d','.','%','d',
         '.','%','d',0 };

        LoadStringW(hInstance, IDS_ALT_NAME_IP_ADDRESS, buf,
         sizeof(buf) / sizeof(buf[0]));
        if (entry->u.IPAddress.cbData == 8)
        {
            if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
            {
                LoadStringW(hInstance, IDS_ALT_NAME_MASK, mask,
                 sizeof(mask) / sizeof(mask[0]));
                bytesNeeded += strlenW(mask) * sizeof(WCHAR);
                sprintfW(ipAddrBuf, ipAddrFmt,
                 entry->u.IPAddress.pbData[0],
                 entry->u.IPAddress.pbData[1],
                 entry->u.IPAddress.pbData[2],
                 entry->u.IPAddress.pbData[3]);
                bytesNeeded += strlenW(ipAddrBuf) * sizeof(WCHAR);
                /* indent again, for the mask line */
                bytesNeeded += indentLevel * strlenW(indent) * sizeof(WCHAR);
                sprintfW(maskBuf, ipAddrFmt,
                 entry->u.IPAddress.pbData[4],
                 entry->u.IPAddress.pbData[5],
                 entry->u.IPAddress.pbData[6],
                 entry->u.IPAddress.pbData[7]);
                bytesNeeded += strlenW(maskBuf) * sizeof(WCHAR);
                bytesNeeded += strlenW(crlf) * sizeof(WCHAR);
            }
            else
            {
                sprintfW(ipAddrBuf, ipAddrWithMaskFmt,
                 entry->u.IPAddress.pbData[0],
                 entry->u.IPAddress.pbData[1],
                 entry->u.IPAddress.pbData[2],
                 entry->u.IPAddress.pbData[3],
                 entry->u.IPAddress.pbData[4],
                 entry->u.IPAddress.pbData[5],
                 entry->u.IPAddress.pbData[6],
                 entry->u.IPAddress.pbData[7]);
                bytesNeeded += (strlenW(ipAddrBuf) + 1) * sizeof(WCHAR);
            }
            ret = TRUE;
        }
        else
        {
            FIXME("unknown IP address format (%d bytes)\n",
             entry->u.IPAddress.cbData);
            ret = FALSE;
        }
        break;
    }
    default:
        FIXME("unimplemented for %d\n", entry->dwAltNameChoice);
        ret = FALSE;
    }
    if (ret)
    {
        bytesNeeded += strlenW(buf) * sizeof(WCHAR);
        if (!str)
            *pcbStr = bytesNeeded;
        else if (*pcbStr < bytesNeeded)
        {
            *pcbStr = bytesNeeded;
            SetLastError(ERROR_MORE_DATA);
            ret = FALSE;
        }
        else
        {
            DWORD i;

            *pcbStr = bytesNeeded;
            if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
            {
                for (i = 0; i < indentLevel; i++)
                {
                    strcpyW(str, indent);
                    str += strlenW(indent);
                }
            }
            strcpyW(str, buf);
            str += strlenW(str);
            switch (entry->dwAltNameChoice)
            {
            case CERT_ALT_NAME_RFC822_NAME:
            case CERT_ALT_NAME_DNS_NAME:
            case CERT_ALT_NAME_URL:
                strcpyW(str, entry->u.pwszURL);
                break;
            case CERT_ALT_NAME_DIRECTORY_NAME:
                if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
                {
                    strcpyW(str, colonCrlf);
                    str += strlenW(colonCrlf);
                }
                else
                    *str++ = '=';
                cert_name_to_str_with_indent(X509_ASN_ENCODING,
                 indentLevel + 1, &entry->u.DirectoryName, strType, str,
                 bytesNeeded / sizeof(WCHAR));
                break;
            case CERT_ALT_NAME_IP_ADDRESS:
                if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
                {
                    strcpyW(str, ipAddrBuf);
                    str += strlenW(ipAddrBuf);
                    strcpyW(str, crlf);
                    str += strlenW(crlf);
                    if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
                    {
                        for (i = 0; i < indentLevel; i++)
                        {
                            strcpyW(str, indent);
                            str += strlenW(indent);
                        }
                    }
                    strcpyW(str, mask);
                    str += strlenW(mask);
                    strcpyW(str, maskBuf);
                }
                else
                    strcpyW(str, ipAddrBuf);
                break;
            }
        }
    }
    return ret;
}

static BOOL CRYPT_FormatAltNameInfo(DWORD dwFormatStrType, DWORD indentLevel,
 const CERT_ALT_NAME_INFO *name, LPWSTR str, DWORD *pcbStr)
{
    DWORD i, size, bytesNeeded = 0;
    BOOL ret = TRUE;
    LPCWSTR sep;
    DWORD sepLen;

    if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
    {
        sep = crlf;
        sepLen = strlenW(crlf) * sizeof(WCHAR);
    }
    else
    {
        sep = commaSpace;
        sepLen = strlenW(commaSpace) * sizeof(WCHAR);
    }

    for (i = 0; ret && i < name->cAltEntry; i++)
    {
        ret = CRYPT_FormatAltNameEntry(dwFormatStrType, indentLevel,
         &name->rgAltEntry[i], NULL, &size);
        if (ret)
        {
            bytesNeeded += size - sizeof(WCHAR);
            if (i < name->cAltEntry - 1)
                bytesNeeded += sepLen;
        }
    }
    if (ret)
    {
        bytesNeeded += sizeof(WCHAR);
        if (!str)
            *pcbStr = bytesNeeded;
        else if (*pcbStr < bytesNeeded)
        {
            *pcbStr = bytesNeeded;
            SetLastError(ERROR_MORE_DATA);
            ret = FALSE;
        }
        else
        {
            *pcbStr = bytesNeeded;
            for (i = 0; ret && i < name->cAltEntry; i++)
            {
                ret = CRYPT_FormatAltNameEntry(dwFormatStrType, indentLevel,
                 &name->rgAltEntry[i], str, &size);
                if (ret)
                {
                    str += size / sizeof(WCHAR) - 1;
                    if (i < name->cAltEntry - 1)
                    {
                        strcpyW(str, sep);
                        str += sepLen / sizeof(WCHAR);
                    }
                }
            }
        }
    }
    return ret;
}

static const WCHAR colonSep[] = { ':',' ',0 };

static BOOL WINAPI CRYPT_FormatAltName(DWORD dwCertEncodingType,
 DWORD dwFormatType, DWORD dwFormatStrType, void *pFormatStruct,
 LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat,
 DWORD *pcbFormat)
{
    BOOL ret;
    CERT_ALT_NAME_INFO *info;
    DWORD size;

    if ((ret = CryptDecodeObjectEx(dwCertEncodingType, X509_ALTERNATE_NAME,
     pbEncoded, cbEncoded, CRYPT_DECODE_ALLOC_FLAG, NULL, &info, &size)))
    {
        ret = CRYPT_FormatAltNameInfo(dwFormatStrType, 0, info, pbFormat, pcbFormat);
        LocalFree(info);
    }
    return ret;
}

static BOOL CRYPT_FormatCertIssuer(DWORD dwFormatStrType,
 const CERT_ALT_NAME_INFO *issuer, LPWSTR str, DWORD *pcbStr)
{
    WCHAR buf[MAX_STRING_RESOURCE_LEN];
    DWORD bytesNeeded, sepLen;
    LPCWSTR sep;
    BOOL ret;

    LoadStringW(hInstance, IDS_CERT_ISSUER, buf, sizeof(buf) / sizeof(buf[0]));
    ret = CRYPT_FormatAltNameInfo(dwFormatStrType, 1, issuer, NULL,
     &bytesNeeded);
    bytesNeeded += strlenW(buf) * sizeof(WCHAR);
    if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
    {
        sep = colonCrlf;
        sepLen = strlenW(colonCrlf) * sizeof(WCHAR);
    }
    else
    {
        sep = colonSep;
        sepLen = strlenW(colonSep) * sizeof(WCHAR);
    }
    bytesNeeded += sepLen;
    if (ret)
    {
        if (!str)
            *pcbStr = bytesNeeded;
        else if (*pcbStr < bytesNeeded)
        {
            *pcbStr = bytesNeeded;
            SetLastError(ERROR_MORE_DATA);
            ret = FALSE;
        }
        else
        {
            *pcbStr = bytesNeeded;
            strcpyW(str, buf);
            bytesNeeded -= strlenW(str) * sizeof(WCHAR);
            str += strlenW(str);
            strcpyW(str, sep);
            str += sepLen / sizeof(WCHAR);
            ret = CRYPT_FormatAltNameInfo(dwFormatStrType, 1, issuer, str,
             &bytesNeeded);
        }
    }
    return ret;
}

static BOOL WINAPI CRYPT_FormatAuthorityKeyId2(DWORD dwCertEncodingType,
 DWORD dwFormatType, DWORD dwFormatStrType, void *pFormatStruct,
 LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat,
 DWORD *pcbFormat)
{
    CERT_AUTHORITY_KEY_ID2_INFO *info;
    DWORD size;
    BOOL ret = FALSE;

    if (!cbEncoded)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if ((ret = CryptDecodeObjectEx(dwCertEncodingType, X509_AUTHORITY_KEY_ID2,
     pbEncoded, cbEncoded, CRYPT_DECODE_ALLOC_FLAG, NULL, &info, &size)))
    {
        DWORD bytesNeeded = sizeof(WCHAR); /* space for the NULL terminator */
        LPCWSTR sep;
        DWORD sepLen;
        BOOL needSeparator = FALSE;

        if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
        {
            sep = crlf;
            sepLen = strlenW(crlf) * sizeof(WCHAR);
        }
        else
        {
            sep = commaSpace;
            sepLen = strlenW(commaSpace) * sizeof(WCHAR);
        }

        if (info->KeyId.cbData)
        {
            needSeparator = TRUE;
            ret = CRYPT_FormatKeyId(&info->KeyId, NULL, &size);
            if (ret)
            {
                /* don't include NULL-terminator more than once */
                bytesNeeded += size - sizeof(WCHAR);
            }
        }
        if (info->AuthorityCertIssuer.cAltEntry)
        {
            if (needSeparator)
                bytesNeeded += sepLen;
            needSeparator = TRUE;
            ret = CRYPT_FormatCertIssuer(dwFormatStrType,
             &info->AuthorityCertIssuer, NULL, &size);
            if (ret)
            {
                /* don't include NULL-terminator more than once */
                bytesNeeded += size - sizeof(WCHAR);
            }
        }
        if (info->AuthorityCertSerialNumber.cbData)
        {
            if (needSeparator)
                bytesNeeded += sepLen;
            ret = CRYPT_FormatCertSerialNumber(
             &info->AuthorityCertSerialNumber, NULL, &size);
            if (ret)
            {
                /* don't include NULL-terminator more than once */
                bytesNeeded += size - sizeof(WCHAR);
            }
        }
        if (ret)
        {
            if (!pbFormat)
                *pcbFormat = bytesNeeded;
            else if (*pcbFormat < bytesNeeded)
            {
                *pcbFormat = bytesNeeded;
                SetLastError(ERROR_MORE_DATA);
                ret = FALSE;
            }
            else
            {
                LPWSTR str = pbFormat;

                *pcbFormat = bytesNeeded;
                needSeparator = FALSE;
                if (info->KeyId.cbData)
                {
                    needSeparator = TRUE;
                    /* Overestimate size available, it's already been checked
                     * above.
                     */
                    size = bytesNeeded;
                    ret = CRYPT_FormatKeyId(&info->KeyId, str, &size);
                    if (ret)
                        str += size / sizeof(WCHAR) - 1;
                }
                if (info->AuthorityCertIssuer.cAltEntry)
                {
                    if (needSeparator)
                    {
                        strcpyW(str, sep);
                        str += sepLen / sizeof(WCHAR);
                    }
                    needSeparator = TRUE;
                    /* Overestimate size available, it's already been checked
                     * above.
                     */
                    size = bytesNeeded;
                    ret = CRYPT_FormatCertIssuer(dwFormatStrType,
                     &info->AuthorityCertIssuer, str, &size);
                    if (ret)
                        str += size / sizeof(WCHAR) - 1;
                }
                if (info->AuthorityCertSerialNumber.cbData)
                {
                    if (needSeparator)
                    {
                        strcpyW(str, sep);
                        str += sepLen / sizeof(WCHAR);
                    }
                    /* Overestimate size available, it's already been checked
                     * above.
                     */
                    size = bytesNeeded;
                    ret = CRYPT_FormatCertSerialNumber(
                     &info->AuthorityCertSerialNumber, str, &size);
                }
            }
        }
        LocalFree(info);
    }
    return ret;
}

static WCHAR aia[MAX_STRING_RESOURCE_LEN];
static WCHAR accessMethod[MAX_STRING_RESOURCE_LEN];
static WCHAR ocsp[MAX_STRING_RESOURCE_LEN];
static WCHAR caIssuers[MAX_STRING_RESOURCE_LEN];
static WCHAR unknown[MAX_STRING_RESOURCE_LEN];
static WCHAR accessLocation[MAX_STRING_RESOURCE_LEN];

static BOOL WINAPI CRYPT_FormatAuthorityInfoAccess(DWORD dwCertEncodingType,
 DWORD dwFormatType, DWORD dwFormatStrType, void *pFormatStruct,
 LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat,
 DWORD *pcbFormat)
{
    CERT_AUTHORITY_INFO_ACCESS *info;
    DWORD size;
    BOOL ret = FALSE;

    if (!cbEncoded)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if ((ret = CryptDecodeObjectEx(dwCertEncodingType,
     X509_AUTHORITY_INFO_ACCESS, pbEncoded, cbEncoded, CRYPT_DECODE_ALLOC_FLAG,
     NULL, &info, &size)))
    {
        DWORD bytesNeeded = sizeof(WCHAR);

        if (!info->cAccDescr)
        {
            WCHAR infoNotAvailable[MAX_STRING_RESOURCE_LEN];

            LoadStringW(hInstance, IDS_INFO_NOT_AVAILABLE, infoNotAvailable,
             sizeof(infoNotAvailable) / sizeof(infoNotAvailable[0]));
            bytesNeeded += strlenW(infoNotAvailable) * sizeof(WCHAR);
            if (!pbFormat)
                *pcbFormat = bytesNeeded;
            else if (*pcbFormat < bytesNeeded)
            {
                *pcbFormat = bytesNeeded;
                SetLastError(ERROR_MORE_DATA);
                ret = FALSE;
            }
            else
            {
                *pcbFormat = bytesNeeded;
                strcpyW(pbFormat, infoNotAvailable);
            }
        }
        else
        {
            static const WCHAR numFmt[] = { '%','d',0 };
            static const WCHAR equal[] = { '=',0 };
            static BOOL stringsLoaded = FALSE;
            DWORD i;
            LPCWSTR headingSep, accessMethodSep, locationSep;
            WCHAR accessDescrNum[11];

            if (!stringsLoaded)
            {
                LoadStringW(hInstance, IDS_AIA, aia,
                 sizeof(aia) / sizeof(aia[0]));
                LoadStringW(hInstance, IDS_ACCESS_METHOD, accessMethod,
                 sizeof(accessMethod) / sizeof(accessMethod[0]));
                LoadStringW(hInstance, IDS_ACCESS_METHOD_OCSP, ocsp,
                 sizeof(ocsp) / sizeof(ocsp[0]));
                LoadStringW(hInstance, IDS_ACCESS_METHOD_CA_ISSUERS, caIssuers,
                 sizeof(caIssuers) / sizeof(caIssuers[0]));
                LoadStringW(hInstance, IDS_ACCESS_METHOD_UNKNOWN, unknown,
                 sizeof(unknown) / sizeof(unknown[0]));
                LoadStringW(hInstance, IDS_ACCESS_LOCATION, accessLocation,
                 sizeof(accessLocation) / sizeof(accessLocation[0]));
                stringsLoaded = TRUE;
            }
            if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
            {
                headingSep = crlf;
                accessMethodSep = crlf;
                locationSep = colonCrlf;
            }
            else
            {
                headingSep = colonSep;
                accessMethodSep = commaSpace;
                locationSep = equal;
            }

            for (i = 0; ret && i < info->cAccDescr; i++)
            {
                /* Heading */
                bytesNeeded += sizeof(WCHAR); /* left bracket */
                sprintfW(accessDescrNum, numFmt, i + 1);
                bytesNeeded += strlenW(accessDescrNum) * sizeof(WCHAR);
                bytesNeeded += sizeof(WCHAR); /* right bracket */
                bytesNeeded += strlenW(aia) * sizeof(WCHAR);
                bytesNeeded += strlenW(headingSep) * sizeof(WCHAR);
                /* Access method */
                bytesNeeded += strlenW(accessMethod) * sizeof(WCHAR);
                if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
                    bytesNeeded += strlenW(indent) * sizeof(WCHAR);
                if (!strcmp(info->rgAccDescr[i].pszAccessMethod,
                 szOID_PKIX_OCSP))
                    bytesNeeded += strlenW(ocsp) * sizeof(WCHAR);
                else if (!strcmp(info->rgAccDescr[i].pszAccessMethod,
                 szOID_PKIX_CA_ISSUERS))
                    bytesNeeded += strlenW(caIssuers) * sizeof(caIssuers);
                else
                    bytesNeeded += strlenW(unknown) * sizeof(WCHAR);
                bytesNeeded += sizeof(WCHAR); /* space */
                bytesNeeded += sizeof(WCHAR); /* left paren */
                bytesNeeded += strlen(info->rgAccDescr[i].pszAccessMethod)
                 * sizeof(WCHAR);
                bytesNeeded += sizeof(WCHAR); /* right paren */
                /* Delimiter between access method and location */
                bytesNeeded += strlenW(accessMethodSep) * sizeof(WCHAR);
                if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
                    bytesNeeded += strlenW(indent) * sizeof(WCHAR);
                bytesNeeded += strlenW(accessLocation) * sizeof(WCHAR);
                bytesNeeded += strlenW(locationSep) * sizeof(WCHAR);
                ret = CRYPT_FormatAltNameEntry(dwFormatStrType, 2,
                 &info->rgAccDescr[i].AccessLocation, NULL, &size);
                if (ret)
                    bytesNeeded += size - sizeof(WCHAR);
                /* Need extra delimiter between access method entries */
                if (i < info->cAccDescr - 1)
                    bytesNeeded += strlenW(accessMethodSep) * sizeof(WCHAR);
            }
            if (ret)
            {
                if (!pbFormat)
                    *pcbFormat = bytesNeeded;
                else if (*pcbFormat < bytesNeeded)
                {
                    *pcbFormat = bytesNeeded;
                    SetLastError(ERROR_MORE_DATA);
                    ret = FALSE;
                }
                else
                {
                    LPWSTR str = pbFormat;
                    DWORD altNameEntrySize;

                    *pcbFormat = bytesNeeded;
                    for (i = 0; ret && i < info->cAccDescr; i++)
                    {
                        LPCSTR oidPtr;

                        *str++ = '[';
                        sprintfW(accessDescrNum, numFmt, i + 1);
                        strcpyW(str, accessDescrNum);
                        str += strlenW(accessDescrNum);
                        *str++ = ']';
                        strcpyW(str, aia);
                        str += strlenW(aia);
                        strcpyW(str, headingSep);
                        str += strlenW(headingSep);
                        if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
                        {
                            strcpyW(str, indent);
                            str += strlenW(indent);
                        }
                        strcpyW(str, accessMethod);
                        str += strlenW(accessMethod);
                        if (!strcmp(info->rgAccDescr[i].pszAccessMethod,
                         szOID_PKIX_OCSP))
                        {
                            strcpyW(str, ocsp);
                            str += strlenW(ocsp);
                        }
                        else if (!strcmp(info->rgAccDescr[i].pszAccessMethod,
                         szOID_PKIX_CA_ISSUERS))
                        {
                            strcpyW(str, caIssuers);
                            str += strlenW(caIssuers);
                        }
                        else
                        {
                            strcpyW(str, unknown);
                            str += strlenW(unknown);
                        }
                        *str++ = ' ';
                        *str++ = '(';
                        for (oidPtr = info->rgAccDescr[i].pszAccessMethod;
                         *oidPtr; oidPtr++, str++)
                            *str = *oidPtr;
                        *str++ = ')';
                        strcpyW(str, accessMethodSep);
                        str += strlenW(accessMethodSep);
                        if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
                        {
                            strcpyW(str, indent);
                            str += strlenW(indent);
                        }
                        strcpyW(str, accessLocation);
                        str += strlenW(accessLocation);
                        strcpyW(str, locationSep);
                        str += strlenW(locationSep);
                        /* This overestimates the size available, but that
                         * won't matter since we checked earlier whether enough
                         * space for the entire string was available.
                         */
                        altNameEntrySize = bytesNeeded;
                        ret = CRYPT_FormatAltNameEntry(dwFormatStrType, 2,
                         &info->rgAccDescr[i].AccessLocation, str,
                         &altNameEntrySize);
                        if (ret)
                            str += altNameEntrySize / sizeof(WCHAR) - 1;
                        if (i < info->cAccDescr - 1)
                        {
                            strcpyW(str, accessMethodSep);
                            str += strlenW(accessMethodSep);
                        }
                    }
                }
            }
        }
        LocalFree(info);
    }
    return ret;
}

static WCHAR keyCompromise[MAX_STRING_RESOURCE_LEN];
static WCHAR caCompromise[MAX_STRING_RESOURCE_LEN];
static WCHAR affiliationChanged[MAX_STRING_RESOURCE_LEN];
static WCHAR superseded[MAX_STRING_RESOURCE_LEN];
static WCHAR operationCeased[MAX_STRING_RESOURCE_LEN];
static WCHAR certificateHold[MAX_STRING_RESOURCE_LEN];

struct reason_map_entry
{
    BYTE   reasonBit;
    LPWSTR reason;
    int    id;
};
static struct reason_map_entry reason_map[] = {
 { CRL_REASON_KEY_COMPROMISE_FLAG, keyCompromise, IDS_REASON_KEY_COMPROMISE },
 { CRL_REASON_CA_COMPROMISE_FLAG, caCompromise, IDS_REASON_CA_COMPROMISE },
 { CRL_REASON_AFFILIATION_CHANGED_FLAG, affiliationChanged,
   IDS_REASON_AFFILIATION_CHANGED },
 { CRL_REASON_SUPERSEDED_FLAG, superseded, IDS_REASON_SUPERSEDED },
 { CRL_REASON_CESSATION_OF_OPERATION_FLAG, operationCeased,
   IDS_REASON_CESSATION_OF_OPERATION },
 { CRL_REASON_CERTIFICATE_HOLD_FLAG, certificateHold,
   IDS_REASON_CERTIFICATE_HOLD },
};

static BOOL CRYPT_FormatReason(DWORD dwFormatStrType,
 const CRYPT_BIT_BLOB *reasonFlags, LPWSTR str, DWORD *pcbStr)
{
    static const WCHAR sep[] = { ',',' ',0 };
    static const WCHAR bitsFmt[] = { ' ','(','%','0','2','x',')',0 };
    static BOOL stringsLoaded = FALSE;
    unsigned int i, numReasons = 0;
    BOOL ret = TRUE;
    DWORD bytesNeeded = sizeof(WCHAR);
    WCHAR bits[6];

    if (!stringsLoaded)
    {
        for (i = 0; i < sizeof(reason_map) / sizeof(reason_map[0]); i++)
            LoadStringW(hInstance, reason_map[i].id, reason_map[i].reason,
             MAX_STRING_RESOURCE_LEN);
        stringsLoaded = TRUE;
    }
    /* No need to check reasonFlags->cbData, we already know it's positive.
     * Ignore any other bytes, as they're for undefined bits.
     */
    for (i = 0; i < sizeof(reason_map) / sizeof(reason_map[0]); i++)
    {
        if (reasonFlags->pbData[0] & reason_map[i].reasonBit)
        {
            bytesNeeded += strlenW(reason_map[i].reason) * sizeof(WCHAR);
            if (numReasons++)
                bytesNeeded += strlenW(sep) * sizeof(WCHAR);
        }
    }
    sprintfW(bits, bitsFmt, reasonFlags->pbData[0]);
    bytesNeeded += strlenW(bits);
    if (!str)
        *pcbStr = bytesNeeded;
    else if (*pcbStr < bytesNeeded)
    {
        *pcbStr = bytesNeeded;
        SetLastError(ERROR_MORE_DATA);
        ret = FALSE;
    }
    else
    {
        *pcbStr = bytesNeeded;
        for (i = 0; i < sizeof(reason_map) / sizeof(reason_map[0]); i++)
        {
            if (reasonFlags->pbData[0] & reason_map[i].reasonBit)
            {
                strcpyW(str, reason_map[i].reason);
                str += strlenW(reason_map[i].reason);
                if (i < sizeof(reason_map) / sizeof(reason_map[0]) - 1 &&
                 numReasons)
                {
                    strcpyW(str, sep);
                    str += strlenW(sep);
                }
            }
        }
        strcpyW(str, bits);
    }
    return ret;
}

static WCHAR crlDistPoint[MAX_STRING_RESOURCE_LEN];
static WCHAR distPointName[MAX_STRING_RESOURCE_LEN];
static WCHAR fullName[MAX_STRING_RESOURCE_LEN];
static WCHAR rdnName[MAX_STRING_RESOURCE_LEN];
static WCHAR reason[MAX_STRING_RESOURCE_LEN];
static WCHAR issuer[MAX_STRING_RESOURCE_LEN];

static BOOL WINAPI CRYPT_FormatCRLDistPoints(DWORD dwCertEncodingType,
 DWORD dwFormatType, DWORD dwFormatStrType, void *pFormatStruct,
 LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat,
 DWORD *pcbFormat)
{
    CRL_DIST_POINTS_INFO *info;
    DWORD size;
    BOOL ret = FALSE;

    if (!cbEncoded)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if ((ret = CryptDecodeObjectEx(dwCertEncodingType, X509_CRL_DIST_POINTS,
     pbEncoded, cbEncoded, CRYPT_DECODE_ALLOC_FLAG, NULL, &info, &size)))
    {
        static const WCHAR numFmt[] = { '%','d',0 };
        static const WCHAR colon[] = { ':',0 };
        static BOOL stringsLoaded = FALSE;
        DWORD bytesNeeded = sizeof(WCHAR); /* space for NULL terminator */
        BOOL haveAnEntry = FALSE;
        LPCWSTR headingSep, nameSep;
        WCHAR distPointNum[11];
        DWORD i;

        if (!stringsLoaded)
        {
            LoadStringW(hInstance, IDS_CRL_DIST_POINT, crlDistPoint,
             sizeof(crlDistPoint) / sizeof(crlDistPoint[0]));
            LoadStringW(hInstance, IDS_CRL_DIST_POINT_NAME, distPointName,
             sizeof(distPointName) / sizeof(distPointName[0]));
            LoadStringW(hInstance, IDS_CRL_DIST_POINT_FULL_NAME, fullName,
             sizeof(fullName) / sizeof(fullName[0]));
            LoadStringW(hInstance, IDS_CRL_DIST_POINT_RDN_NAME, rdnName,
             sizeof(rdnName) / sizeof(rdnName[0]));
            LoadStringW(hInstance, IDS_CRL_DIST_POINT_REASON, reason,
             sizeof(reason) / sizeof(reason[0]));
            LoadStringW(hInstance, IDS_CRL_DIST_POINT_ISSUER, issuer,
             sizeof(issuer) / sizeof(issuer[0]));
            stringsLoaded = TRUE;
        }
        if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
        {
            headingSep = crlf;
            nameSep = colonCrlf;
        }
        else
        {
            headingSep = colonSep;
            nameSep = colon;
        }

        for (i = 0; ret && i < info->cDistPoint; i++)
        {
            CRL_DIST_POINT *distPoint = &info->rgDistPoint[i];

            if (distPoint->DistPointName.dwDistPointNameChoice !=
             CRL_DIST_POINT_NO_NAME)
            {
                bytesNeeded += strlenW(distPointName) * sizeof(WCHAR);
                bytesNeeded += strlenW(nameSep) * sizeof(WCHAR);
                if (distPoint->DistPointName.dwDistPointNameChoice ==
                 CRL_DIST_POINT_FULL_NAME)
                    bytesNeeded += strlenW(fullName) * sizeof(WCHAR);
                else
                    bytesNeeded += strlenW(rdnName) * sizeof(WCHAR);
                bytesNeeded += strlenW(nameSep) * sizeof(WCHAR);
                if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
                    bytesNeeded += 2 * strlenW(indent) * sizeof(WCHAR);
                /* The indent level (3) is higher than when used as the issuer,
                 * because the name is subordinate to the name type (full vs.
                 * RDN.)
                 */
                ret = CRYPT_FormatAltNameInfo(dwFormatStrType, 3,
                 &distPoint->DistPointName.u.FullName, NULL, &size);
                if (ret)
                    bytesNeeded += size - sizeof(WCHAR);
                haveAnEntry = TRUE;
            }
            else if (distPoint->ReasonFlags.cbData)
            {
                bytesNeeded += strlenW(reason) * sizeof(WCHAR);
                ret = CRYPT_FormatReason(dwFormatStrType,
                 &distPoint->ReasonFlags, NULL, &size);
                if (ret)
                    bytesNeeded += size - sizeof(WCHAR);
                haveAnEntry = TRUE;
            }
            else if (distPoint->CRLIssuer.cAltEntry)
            {
                bytesNeeded += strlenW(issuer) * sizeof(WCHAR);
                bytesNeeded += strlenW(nameSep) * sizeof(WCHAR);
                ret = CRYPT_FormatAltNameInfo(dwFormatStrType, 2,
                 &distPoint->CRLIssuer, NULL, &size);
                if (ret)
                    bytesNeeded += size - sizeof(WCHAR);
                haveAnEntry = TRUE;
            }
            if (haveAnEntry)
            {
                bytesNeeded += sizeof(WCHAR); /* left bracket */
                sprintfW(distPointNum, numFmt, i + 1);
                bytesNeeded += strlenW(distPointNum) * sizeof(WCHAR);
                bytesNeeded += sizeof(WCHAR); /* right bracket */
                bytesNeeded += strlenW(crlDistPoint) * sizeof(WCHAR);
                bytesNeeded += strlenW(headingSep) * sizeof(WCHAR);
                if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
                    bytesNeeded += strlenW(indent) * sizeof(WCHAR);
            }
        }
        if (!haveAnEntry)
        {
            WCHAR infoNotAvailable[MAX_STRING_RESOURCE_LEN];

            LoadStringW(hInstance, IDS_INFO_NOT_AVAILABLE, infoNotAvailable,
             sizeof(infoNotAvailable) / sizeof(infoNotAvailable[0]));
            bytesNeeded += strlenW(infoNotAvailable) * sizeof(WCHAR);
            if (!pbFormat)
                *pcbFormat = bytesNeeded;
            else if (*pcbFormat < bytesNeeded)
            {
                *pcbFormat = bytesNeeded;
                SetLastError(ERROR_MORE_DATA);
                ret = FALSE;
            }
            else
            {
                *pcbFormat = bytesNeeded;
                strcpyW(pbFormat, infoNotAvailable);
            }
        }
        else
        {
            if (!pbFormat)
                *pcbFormat = bytesNeeded;
            else if (*pcbFormat < bytesNeeded)
            {
                *pcbFormat = bytesNeeded;
                SetLastError(ERROR_MORE_DATA);
                ret = FALSE;
            }
            else
            {
                LPWSTR str = pbFormat;

                *pcbFormat = bytesNeeded;
                for (i = 0; ret && i < info->cDistPoint; i++)
                {
                    CRL_DIST_POINT *distPoint = &info->rgDistPoint[i];

                    *str++ = '[';
                    sprintfW(distPointNum, numFmt, i + 1);
                    strcpyW(str, distPointNum);
                    str += strlenW(distPointNum);
                    *str++ = ']';
                    strcpyW(str, crlDistPoint);
                    str += strlenW(crlDistPoint);
                    strcpyW(str, headingSep);
                    str += strlenW(headingSep);
                    if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
                    {
                        strcpyW(str, indent);
                        str += strlenW(indent);
                    }
                    if (distPoint->DistPointName.dwDistPointNameChoice !=
                     CRL_DIST_POINT_NO_NAME)
                    {
                        DWORD altNameSize = bytesNeeded;

                        strcpyW(str, distPointName);
                        str += strlenW(distPointName);
                        strcpyW(str, nameSep);
                        str += strlenW(nameSep);
                        if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
                        {
                            strcpyW(str, indent);
                            str += strlenW(indent);
                            strcpyW(str, indent);
                            str += strlenW(indent);
                        }
                        if (distPoint->DistPointName.dwDistPointNameChoice ==
                         CRL_DIST_POINT_FULL_NAME)
                        {
                            strcpyW(str, fullName);
                            str += strlenW(fullName);
                        }
                        else
                        {
                            strcpyW(str, rdnName);
                            str += strlenW(rdnName);
                        }
                        strcpyW(str, nameSep);
                        str += strlenW(nameSep);
                        ret = CRYPT_FormatAltNameInfo(dwFormatStrType, 3,
                         &distPoint->DistPointName.u.FullName, str,
                         &altNameSize);
                        if (ret)
                            str += altNameSize / sizeof(WCHAR) - 1;
                    }
                    else if (distPoint->ReasonFlags.cbData)
                    {
                        DWORD reasonSize = bytesNeeded;

                        strcpyW(str, reason);
                        str += strlenW(reason);
                        ret = CRYPT_FormatReason(dwFormatStrType,
                         &distPoint->ReasonFlags, str, &reasonSize);
                        if (ret)
                            str += reasonSize / sizeof(WCHAR) - 1;
                    }
                    else if (distPoint->CRLIssuer.cAltEntry)
                    {
                        DWORD crlIssuerSize = bytesNeeded;

                        strcpyW(str, issuer);
                        str += strlenW(issuer);
                        strcpyW(str, nameSep);
                        str += strlenW(nameSep);
                        ret = CRYPT_FormatAltNameInfo(dwFormatStrType, 2,
                         &distPoint->CRLIssuer, str,
                         &crlIssuerSize);
                        if (ret)
                            str += crlIssuerSize / sizeof(WCHAR) - 1;
                    }
                }
            }
        }
        LocalFree(info);
    }
    return ret;
}

static BOOL WINAPI CRYPT_FormatEnhancedKeyUsage(DWORD dwCertEncodingType,
 DWORD dwFormatType, DWORD dwFormatStrType, void *pFormatStruct,
 LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat,
 DWORD *pcbFormat)
{
    CERT_ENHKEY_USAGE *usage;
    DWORD size;
    BOOL ret = FALSE;

    if (!cbEncoded)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if ((ret = CryptDecodeObjectEx(dwCertEncodingType, X509_ENHANCED_KEY_USAGE,
     pbEncoded, cbEncoded, CRYPT_DECODE_ALLOC_FLAG, NULL, &usage, &size)))
    {
        WCHAR unknown[MAX_STRING_RESOURCE_LEN];
        DWORD i;
        DWORD bytesNeeded = sizeof(WCHAR); /* space for the NULL terminator */
        LPCWSTR sep;
        DWORD sepLen;

        if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
        {
            sep = crlf;
            sepLen = strlenW(crlf) * sizeof(WCHAR);
        }
        else
        {
            sep = commaSpace;
            sepLen = strlenW(commaSpace) * sizeof(WCHAR);
        }

        LoadStringW(hInstance, IDS_USAGE_UNKNOWN, unknown,
         sizeof(unknown) / sizeof(unknown[0]));
        for (i = 0; i < usage->cUsageIdentifier; i++)
        {
            PCCRYPT_OID_INFO info = CryptFindOIDInfo(CRYPT_OID_INFO_OID_KEY,
             usage->rgpszUsageIdentifier[i], CRYPT_ENHKEY_USAGE_OID_GROUP_ID);

            if (info)
                bytesNeeded += strlenW(info->pwszName) * sizeof(WCHAR);
            else
                bytesNeeded += strlenW(unknown) * sizeof(WCHAR);
            bytesNeeded += sizeof(WCHAR); /* space */
            bytesNeeded += sizeof(WCHAR); /* left paren */
            bytesNeeded += strlen(usage->rgpszUsageIdentifier[i]) *
             sizeof(WCHAR);
            bytesNeeded += sizeof(WCHAR); /* right paren */
            if (i < usage->cUsageIdentifier - 1)
                bytesNeeded += sepLen;
        }
        if (!pbFormat)
            *pcbFormat = bytesNeeded;
        else if (*pcbFormat < bytesNeeded)
        {
            *pcbFormat = bytesNeeded;
            SetLastError(ERROR_MORE_DATA);
            ret = FALSE;
        }
        else
        {
            LPWSTR str = pbFormat;

            *pcbFormat = bytesNeeded;
            for (i = 0; i < usage->cUsageIdentifier; i++)
            {
                PCCRYPT_OID_INFO info = CryptFindOIDInfo(CRYPT_OID_INFO_OID_KEY,
                 usage->rgpszUsageIdentifier[i],
                 CRYPT_ENHKEY_USAGE_OID_GROUP_ID);
                LPCSTR oidPtr;

                if (info)
                {
                    strcpyW(str, info->pwszName);
                    str += strlenW(info->pwszName);
                }
                else
                {
                    strcpyW(str, unknown);
                    str += strlenW(unknown);
                }
                *str++ = ' ';
                *str++ = '(';
                for (oidPtr = usage->rgpszUsageIdentifier[i]; *oidPtr; oidPtr++)
                    *str++ = *oidPtr;
                *str++ = ')';
                *str = 0;
                if (i < usage->cUsageIdentifier - 1)
                {
                    strcpyW(str, sep);
                    str += sepLen / sizeof(WCHAR);
                }
            }
        }
        LocalFree(usage);
    }
    return ret;
}

static struct BitToString netscapeCertTypeMap[] = {
 { NETSCAPE_SSL_CLIENT_AUTH_CERT_TYPE, IDS_NETSCAPE_SSL_CLIENT, { 0 } },
 { NETSCAPE_SSL_SERVER_AUTH_CERT_TYPE, IDS_NETSCAPE_SSL_SERVER, { 0 } },
 { NETSCAPE_SMIME_CERT_TYPE, IDS_NETSCAPE_SMIME, { 0 } },
 { NETSCAPE_SIGN_CERT_TYPE, IDS_NETSCAPE_SIGN, { 0 } },
 { NETSCAPE_SSL_CA_CERT_TYPE, IDS_NETSCAPE_SSL_CA, { 0 } },
 { NETSCAPE_SMIME_CA_CERT_TYPE, IDS_NETSCAPE_SMIME_CA, { 0 } },
 { NETSCAPE_SIGN_CA_CERT_TYPE, IDS_NETSCAPE_SIGN_CA, { 0 } },
};

static BOOL WINAPI CRYPT_FormatNetscapeCertType(DWORD dwCertEncodingType,
 DWORD dwFormatType, DWORD dwFormatStrType, void *pFormatStruct,
 LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat,
 DWORD *pcbFormat)
{
    DWORD size;
    CRYPT_BIT_BLOB *bits;
    BOOL ret;

    if (!cbEncoded)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if ((ret = CryptDecodeObjectEx(dwCertEncodingType, X509_BITS,
     pbEncoded, cbEncoded, CRYPT_DECODE_ALLOC_FLAG, NULL, &bits, &size)))
    {
        WCHAR infoNotAvailable[MAX_STRING_RESOURCE_LEN];
        DWORD bytesNeeded = sizeof(WCHAR);

        LoadStringW(hInstance, IDS_INFO_NOT_AVAILABLE, infoNotAvailable,
         sizeof(infoNotAvailable) / sizeof(infoNotAvailable[0]));
        if (!bits->cbData || bits->cbData > 1)
        {
            bytesNeeded += strlenW(infoNotAvailable) * sizeof(WCHAR);
            if (!pbFormat)
                *pcbFormat = bytesNeeded;
            else if (*pcbFormat < bytesNeeded)
            {
                *pcbFormat = bytesNeeded;
                SetLastError(ERROR_MORE_DATA);
                ret = FALSE;
            }
            else
            {
                LPWSTR str = pbFormat;

                *pcbFormat = bytesNeeded;
                strcpyW(str, infoNotAvailable);
            }
        }
        else
        {
            static BOOL stringsLoaded = FALSE;
            unsigned int i;
            DWORD bitStringLen;
            BOOL first = TRUE;

            if (!stringsLoaded)
            {
                for (i = 0; i < sizeof(netscapeCertTypeMap) /
                 sizeof(netscapeCertTypeMap[0]); i++)
                    LoadStringW(hInstance, netscapeCertTypeMap[i].id,
                     netscapeCertTypeMap[i].str, MAX_STRING_RESOURCE_LEN);
                stringsLoaded = TRUE;
            }
            CRYPT_FormatBits(bits->pbData[0], netscapeCertTypeMap,
             sizeof(netscapeCertTypeMap) / sizeof(netscapeCertTypeMap[0]),
             NULL, &bitStringLen, &first);
            bytesNeeded += bitStringLen;
            bytesNeeded += 3 * sizeof(WCHAR); /* " (" + ")" */
            CRYPT_FormatHexString(0, 0, 0, NULL, NULL, bits->pbData,
             bits->cbData, NULL, &size);
            bytesNeeded += size;
            if (!pbFormat)
                *pcbFormat = bytesNeeded;
            else if (*pcbFormat < bytesNeeded)
            {
                *pcbFormat = bytesNeeded;
                SetLastError(ERROR_MORE_DATA);
                ret = FALSE;
            }
            else
            {
                LPWSTR str = pbFormat;

                bitStringLen = bytesNeeded;
                first = TRUE;
                CRYPT_FormatBits(bits->pbData[0], netscapeCertTypeMap,
                 sizeof(netscapeCertTypeMap) / sizeof(netscapeCertTypeMap[0]),
                 str, &bitStringLen, &first);
                str += bitStringLen / sizeof(WCHAR) - 1;
                *str++ = ' ';
                *str++ = '(';
                CRYPT_FormatHexString(0, 0, 0, NULL, NULL, bits->pbData,
                 bits->cbData, str, &size);
                str += size / sizeof(WCHAR) - 1;
                *str++ = ')';
                *str = 0;
            }
        }
        LocalFree(bits);
    }
    return ret;
}

static WCHAR financialCriteria[MAX_STRING_RESOURCE_LEN];
static WCHAR available[MAX_STRING_RESOURCE_LEN];
static WCHAR notAvailable[MAX_STRING_RESOURCE_LEN];
static WCHAR meetsCriteria[MAX_STRING_RESOURCE_LEN];
static WCHAR yes[MAX_STRING_RESOURCE_LEN];
static WCHAR no[MAX_STRING_RESOURCE_LEN];

static BOOL WINAPI CRYPT_FormatSpcFinancialCriteria(DWORD dwCertEncodingType,
 DWORD dwFormatType, DWORD dwFormatStrType, void *pFormatStruct,
 LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat,
 DWORD *pcbFormat)
{
    SPC_FINANCIAL_CRITERIA criteria;
    DWORD size = sizeof(criteria);
    BOOL ret = FALSE;

    if (!cbEncoded)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if ((ret = CryptDecodeObjectEx(dwCertEncodingType,
     SPC_FINANCIAL_CRITERIA_STRUCT, pbEncoded, cbEncoded, 0, NULL, &criteria,
     &size)))
    {
        static BOOL stringsLoaded = FALSE;
        DWORD bytesNeeded = sizeof(WCHAR);
        LPCWSTR sep;
        DWORD sepLen;

        if (!stringsLoaded)
        {
            LoadStringW(hInstance, IDS_FINANCIAL_CRITERIA, financialCriteria,
             sizeof(financialCriteria) / sizeof(financialCriteria[0]));
            LoadStringW(hInstance, IDS_FINANCIAL_CRITERIA_AVAILABLE, available,
             sizeof(available) / sizeof(available[0]));
            LoadStringW(hInstance, IDS_FINANCIAL_CRITERIA_NOT_AVAILABLE,
             notAvailable, sizeof(notAvailable) / sizeof(notAvailable[0]));
            LoadStringW(hInstance, IDS_FINANCIAL_CRITERIA_MEETS_CRITERIA,
             meetsCriteria, sizeof(meetsCriteria) / sizeof(meetsCriteria[0]));
            LoadStringW(hInstance, IDS_YES, yes, sizeof(yes) / sizeof(yes[0]));
            LoadStringW(hInstance, IDS_NO, no, sizeof(no) / sizeof(no[0]));
            stringsLoaded = TRUE;
        }
        if (dwFormatStrType & CRYPT_FORMAT_STR_MULTI_LINE)
        {
            sep = crlf;
            sepLen = strlenW(crlf) * sizeof(WCHAR);
        }
        else
        {
            sep = commaSpace;
            sepLen = strlenW(commaSpace) * sizeof(WCHAR);
        }
        bytesNeeded += strlenW(financialCriteria) * sizeof(WCHAR);
        if (criteria.fFinancialInfoAvailable)
        {
            bytesNeeded += strlenW(available) * sizeof(WCHAR);
            bytesNeeded += sepLen;
            bytesNeeded += strlenW(meetsCriteria) * sizeof(WCHAR);
            if (criteria.fMeetsCriteria)
                bytesNeeded += strlenW(yes) * sizeof(WCHAR);
            else
                bytesNeeded += strlenW(no) * sizeof(WCHAR);
        }
        else
            bytesNeeded += strlenW(notAvailable) * sizeof(WCHAR);
        if (!pbFormat)
            *pcbFormat = bytesNeeded;
        else if (*pcbFormat < bytesNeeded)
        {
            *pcbFormat = bytesNeeded;
            SetLastError(ERROR_MORE_DATA);
            ret = FALSE;
        }
        else
        {
            LPWSTR str = pbFormat;

            *pcbFormat = bytesNeeded;
            strcpyW(str, financialCriteria);
            str += strlenW(financialCriteria);
            if (criteria.fFinancialInfoAvailable)
            {
                strcpyW(str, available);
                str += strlenW(available);
                strcpyW(str, sep);
                str += sepLen / sizeof(WCHAR);
                strcpyW(str, meetsCriteria);
                str += strlenW(meetsCriteria);
                if (criteria.fMeetsCriteria)
                    strcpyW(str, yes);
                else
                    strcpyW(str, no);
            }
            else
            {
                strcpyW(str, notAvailable);
            }
        }
    }
    return ret;
}

static BOOL WINAPI CRYPT_FormatUnicodeString(DWORD dwCertEncodingType,
 DWORD dwFormatType, DWORD dwFormatStrType, void *pFormatStruct,
 LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat,
 DWORD *pcbFormat)
{
    CERT_NAME_VALUE *value;
    DWORD size;
    BOOL ret;

    if (!cbEncoded)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if ((ret = CryptDecodeObjectEx(dwCertEncodingType, X509_UNICODE_ANY_STRING,
     pbEncoded, cbEncoded, CRYPT_DECODE_ALLOC_FLAG, NULL, &value, &size)))
    {
        if (!pbFormat)
            *pcbFormat = value->Value.cbData;
        else if (*pcbFormat < value->Value.cbData)
        {
            *pcbFormat = value->Value.cbData;
            SetLastError(ERROR_MORE_DATA);
            ret = FALSE;
        }
        else
        {
            LPWSTR str = pbFormat;

            *pcbFormat = value->Value.cbData;
            strcpyW(str, (LPWSTR)value->Value.pbData);
        }
    }
    return ret;
}

typedef BOOL (WINAPI *CryptFormatObjectFunc)(DWORD, DWORD, DWORD, void *,
 LPCSTR, const BYTE *, DWORD, void *, DWORD *);

static CryptFormatObjectFunc CRYPT_GetBuiltinFormatFunction(DWORD encodingType,
 DWORD formatStrType, LPCSTR lpszStructType)
{
    CryptFormatObjectFunc format = NULL;

    if ((encodingType & CERT_ENCODING_TYPE_MASK) != X509_ASN_ENCODING)
    {
        SetLastError(ERROR_FILE_NOT_FOUND);
        return NULL;
    }
    if (IS_INTOID(lpszStructType))
    {
        switch (LOWORD(lpszStructType))
        {
        case LOWORD(X509_KEY_USAGE):
            format = CRYPT_FormatKeyUsage;
            break;
        case LOWORD(X509_ALTERNATE_NAME):
            format = CRYPT_FormatAltName;
            break;
        case LOWORD(X509_BASIC_CONSTRAINTS2):
            format = CRYPT_FormatBasicConstraints2;
            break;
        case LOWORD(X509_AUTHORITY_KEY_ID2):
            format = CRYPT_FormatAuthorityKeyId2;
            break;
        case LOWORD(X509_AUTHORITY_INFO_ACCESS):
            format = CRYPT_FormatAuthorityInfoAccess;
            break;
        case LOWORD(X509_CRL_DIST_POINTS):
            format = CRYPT_FormatCRLDistPoints;
            break;
        case LOWORD(X509_ENHANCED_KEY_USAGE):
            format = CRYPT_FormatEnhancedKeyUsage;
            break;
        case LOWORD(SPC_FINANCIAL_CRITERIA_STRUCT):
            format = CRYPT_FormatSpcFinancialCriteria;
            break;
        }
    }
    else if (!strcmp(lpszStructType, szOID_SUBJECT_ALT_NAME))
        format = CRYPT_FormatAltName;
    else if (!strcmp(lpszStructType, szOID_ISSUER_ALT_NAME))
        format = CRYPT_FormatAltName;
    else if (!strcmp(lpszStructType, szOID_KEY_USAGE))
        format = CRYPT_FormatKeyUsage;
    else if (!strcmp(lpszStructType, szOID_SUBJECT_ALT_NAME2))
        format = CRYPT_FormatAltName;
    else if (!strcmp(lpszStructType, szOID_ISSUER_ALT_NAME2))
        format = CRYPT_FormatAltName;
    else if (!strcmp(lpszStructType, szOID_BASIC_CONSTRAINTS2))
        format = CRYPT_FormatBasicConstraints2;
    else if (!strcmp(lpszStructType, szOID_AUTHORITY_INFO_ACCESS))
        format = CRYPT_FormatAuthorityInfoAccess;
    else if (!strcmp(lpszStructType, szOID_AUTHORITY_KEY_IDENTIFIER2))
        format = CRYPT_FormatAuthorityKeyId2;
    else if (!strcmp(lpszStructType, szOID_CRL_DIST_POINTS))
        format = CRYPT_FormatCRLDistPoints;
    else if (!strcmp(lpszStructType, szOID_ENHANCED_KEY_USAGE))
        format = CRYPT_FormatEnhancedKeyUsage;
    else if (!strcmp(lpszStructType, szOID_NETSCAPE_CERT_TYPE))
        format = CRYPT_FormatNetscapeCertType;
    else if (!strcmp(lpszStructType, szOID_NETSCAPE_BASE_URL) ||
     !strcmp(lpszStructType, szOID_NETSCAPE_REVOCATION_URL) ||
     !strcmp(lpszStructType, szOID_NETSCAPE_CA_REVOCATION_URL) ||
     !strcmp(lpszStructType, szOID_NETSCAPE_CERT_RENEWAL_URL) ||
     !strcmp(lpszStructType, szOID_NETSCAPE_CA_POLICY_URL) ||
     !strcmp(lpszStructType, szOID_NETSCAPE_SSL_SERVER_NAME) ||
     !strcmp(lpszStructType, szOID_NETSCAPE_COMMENT))
        format = CRYPT_FormatUnicodeString;
    else if (!strcmp(lpszStructType, SPC_FINANCIAL_CRITERIA_OBJID))
        format = CRYPT_FormatSpcFinancialCriteria;
    return format;
}

BOOL WINAPI CryptFormatObject(DWORD dwCertEncodingType, DWORD dwFormatType,
 DWORD dwFormatStrType, void *pFormatStruct, LPCSTR lpszStructType,
 const BYTE *pbEncoded, DWORD cbEncoded, void *pbFormat, DWORD *pcbFormat)
{
    CryptFormatObjectFunc format = NULL;
    HCRYPTOIDFUNCADDR hFunc = NULL;
    BOOL ret = FALSE;

    TRACE("(%08x, %d, %08x, %p, %s, %p, %d, %p, %p)\n", dwCertEncodingType,
     dwFormatType, dwFormatStrType, pFormatStruct, debugstr_a(lpszStructType),
     pbEncoded, cbEncoded, pbFormat, pcbFormat);

    if (!(format = CRYPT_GetBuiltinFormatFunction(dwCertEncodingType,
     dwFormatStrType, lpszStructType)))
    {
        static HCRYPTOIDFUNCSET set = NULL;

        if (!set)
            set = CryptInitOIDFunctionSet(CRYPT_OID_FORMAT_OBJECT_FUNC, 0);
        CryptGetOIDFunctionAddress(set, dwCertEncodingType, lpszStructType, 0,
         (void **)&format, &hFunc);
    }
    if (!format && (dwCertEncodingType & CERT_ENCODING_TYPE_MASK) ==
     X509_ASN_ENCODING && !(dwFormatStrType & CRYPT_FORMAT_STR_NO_HEX))
        format = CRYPT_FormatHexString;
    if (format)
        ret = format(dwCertEncodingType, dwFormatType, dwFormatStrType,
         pFormatStruct, lpszStructType, pbEncoded, cbEncoded, pbFormat,
         pcbFormat);
    if (hFunc)
        CryptFreeOIDFunctionAddress(hFunc, 0);
    return ret;
}
