#include "windows.h"
#include "wincrypt.h"
#include "mscat.h"
#include "stdio.h"
#include "stdlib.h"

VOID
DumpBytes(PBYTE pbBuffer, DWORD dwLength)
{
    for (DWORD dw = 0; dw < dwLength; dw++)
    {
        if (dw % 4 == 0 && dw)
            wprintf(L" ");
        if (dw % 32 == 0 && dw)
            wprintf(L"\n");
        wprintf(L"%02x", pbBuffer[dw]);
    }
}


#pragma pack(1)
typedef struct _PublicKeyBlob
{
    unsigned int SigAlgID;
    unsigned int HashAlgID;
    ULONG cbPublicKey;
    BYTE PublicKey[1];
}
PublicKeyBlob, *PPublicKeyBlob;



VOID
GenerateFusionStrongNameAndKeyFromCertificate(PCCERT_CONTEXT pContext)
{
    HCRYPTPROV      hProvider;
    HCRYPTKEY       hKey;
    PBYTE           pbFusionKeyBlob;
    BYTE            pbBlobData[8192];
    DWORD           cbBlobData = sizeof(pbBlobData);
    DWORD           cbFusionKeyBlob, dwTemp;
    PPublicKeyBlob  pFusionKeyStruct;

    if (!::CryptAcquireContextW(
            &hProvider,
            NULL,
            NULL,
            PROV_RSA_FULL,
            CRYPT_VERIFYCONTEXT))
    {
        wprintf(L"Failed opening the crypt context: 0x%08x", ::GetLastError());
        return;
    }

    //
    // Load the public key info into a key to start with
    //
    if (!CryptImportPublicKeyInfo(
        hProvider,
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
        &pContext->pCertInfo->SubjectPublicKeyInfo,
        &hKey))
    {
        wprintf(L"Failed importing public key info from the cert-context, 0x%08x", ::GetLastError());
        return;
    }

    //
    // Export the key info to a public-key blob
    //
    if (!CryptExportKey(
            hKey,
            NULL,
            PUBLICKEYBLOB,
            0,
            pbBlobData,
            &cbBlobData))
    {
        wprintf(L"Failed exporting public key info back from an hcryptkey: 0x%08x\n", ::GetLastError());
        return;
    }

    //
    // Allocate the Fusion public key blob
    //
    cbFusionKeyBlob = sizeof(PublicKeyBlob) + cbBlobData - 1;
    pFusionKeyStruct = (PPublicKeyBlob)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbFusionKeyBlob);

    //
    // Key parameter for the signing algorithm
    //
    dwTemp = sizeof(pFusionKeyStruct->SigAlgID);
    CryptGetKeyParam(hKey, KP_ALGID, (PBYTE)&pFusionKeyStruct->SigAlgID, &dwTemp, 0);

    //
    // Move over the public key bits from CryptExportKey
    //
    pFusionKeyStruct->cbPublicKey = cbBlobData;
    pFusionKeyStruct->HashAlgID = CALG_SHA1;
    memcpy(pFusionKeyStruct->PublicKey, pbBlobData, cbBlobData);

    wprintf(L"\n  Public key structure:\n");
    DumpBytes((PBYTE)pFusionKeyStruct, cbFusionKeyBlob);

    //
    // Now let's go hash it.
    //
    {
        HCRYPTHASH  hKeyHash;
        BYTE        bHashedKeyInfo[8192];
        DWORD       cbHashedKeyInfo = sizeof(bHashedKeyInfo);

        if (!CryptCreateHash(hProvider, pFusionKeyStruct->HashAlgID, NULL, 0, &hKeyHash))
        {
            wprintf(L"Failed creating a hash for this key: 0x%08x\n", ::GetLastError());
            return;
        }

        if (!CryptHashData(hKeyHash, (PBYTE)pFusionKeyStruct, cbFusionKeyBlob, 0))
        {
            wprintf(L"Failed hashing data: 0x%08x\n", ::GetLastError());
            return;
        }

        if (!CryptGetHashParam(hKeyHash, HP_HASHVAL, bHashedKeyInfo, &cbHashedKeyInfo, 0))
        {
            wprintf(L"Can't get hashed key info 0x%08x\n", ::GetLastError());
            return;
        }

        CryptDestroyHash(hKeyHash);

        wprintf(L"\n  Hash of public key bits:       ");
        DumpBytes(bHashedKeyInfo, cbHashedKeyInfo);
        wprintf(L"\n  Fusion-compatible strong name: ");
        DumpBytes(bHashedKeyInfo + (cbHashedKeyInfo - 8), 8);
    }
}



VOID
PrintKeyContextInfo(PCCERT_CONTEXT pContext)
{
    BYTE bHash[8192];
    DWORD cbHash;

    WCHAR wszBuffer[8192];
    DWORD cchBuffer = 8192;

    wprintf(L"\n\n");

    CertGetNameStringW(pContext, CERT_NAME_FRIENDLY_DISPLAY_TYPE,
        0, NULL, wszBuffer, cchBuffer);

    wprintf(L"Certificate owner: %ls\n", wszBuffer);

    //
    // Spit out the key bits
    //
    wprintf(L"Found key info:\n");
    DumpBytes(
        pContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData,
        pContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData);

    //
    // And now the "strong name" (ie: sha1 hash) of the public key bits
    //
    if (CryptHashPublicKeyInfo(
        NULL,
        CALG_SHA1,
        0,
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
        &pContext->pCertInfo->SubjectPublicKeyInfo,
        bHash,
        &(cbHash = sizeof(bHash))))
    {
        wprintf(L"\nPublic key hash: ");
        DumpBytes(bHash, cbHash);
        wprintf(L"\nStrong name is:  ");
        DumpBytes(bHash, cbHash < 8 ? cbHash : 8);
    }
    else
    {
        wprintf(L"Unable to hash public key info: 0x%08x\n", ::GetLastError());
    }


    GenerateFusionStrongNameAndKeyFromCertificate(pContext);

    wprintf(L"\n\n");
}

int __cdecl wmain(int argc, WCHAR* argv[])
{
    HANDLE              hCatalog;
    HANDLE              hMapping;
    PBYTE               pByte;
    SIZE_T              cBytes;
    PCCTL_CONTEXT       pContext;

    hCatalog = CreateFileW(argv[1], GENERIC_READ,
        FILE_SHARE_READ, NULL,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hCatalog == INVALID_HANDLE_VALUE)
    {
        wprintf(L"Ensure that %ls exists.\n", argv[1]);
        return 0;
    }

    hMapping = CreateFileMapping(hCatalog, NULL, PAGE_READONLY, 0, 0, NULL);
    if (!hMapping || (hMapping == INVALID_HANDLE_VALUE))
    {
        CloseHandle(hCatalog);
        wprintf(L"Unable to map file into address space.\n");
        return 1;
    }

    pByte = (PBYTE)MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
    CloseHandle(hMapping);

    if (!pByte)
    {
        wprintf(L"Unable to open view of file.\n");
        CloseHandle(hCatalog);
        return 2;
    }

    if (((cBytes = GetFileSize(hCatalog, NULL)) == -1) || (cBytes < 1))
    {
        wprintf(L"Bad file size %d\n", cBytes);
        return 3;
    }

    if (pByte[0] != 0x30)
    {
        wprintf(L"File is not a catalog.\n");
        return 4;
    }

    pContext = (PCCTL_CONTEXT)CertCreateCTLContext(
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
        pByte,
        cBytes);

    if (pContext)
    {
        BYTE    bIdent[8192];
        DWORD   cbIdent;
        PCERT_ID  cIdent;

        if (!CryptMsgGetParam(
            pContext->hCryptMsg,
            CMSG_SIGNER_CERT_ID_PARAM,
            0,
            bIdent,
            &(cbIdent = sizeof(bIdent))))
        {
            wprintf(L"Unable to get top-level signer's certificate ID: 0x%08x\n", ::GetLastError());
            return 6;
        }


        cIdent = (PCERT_ID)bIdent;
        HCERTSTORE hStore;

        //
        // Maybe it's there in the message?
        //
        {
            PCCERT_CONTEXT pThisContext = NULL;

            hStore = CertOpenStore(
                CERT_STORE_PROV_MSG,
                X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                NULL,
                0,
                pContext->hCryptMsg);

            if (hStore && (hStore != INVALID_HANDLE_VALUE))
            {
                while (pThisContext = CertEnumCertificatesInStore(hStore, pThisContext))
                {
                    PCERT_INFO    pInfo = pThisContext->pCertInfo;
                    WCHAR        wszBuffer[8192];
                    DWORD        cchBuffer = sizeof(wszBuffer)/sizeof(*wszBuffer);

                    PrintKeyContextInfo(pThisContext);

                }
            }
        }

    }
    else
    {
        wprintf(L"Failed creating certificate context: 0x%08x\n", ::GetLastError());
        return 5;
    }

}