1

So I am pondering which cryptographic C++ library to use (I already figured out how to do equivalent in C#) for the verification of a licence file signed hash.

Reading the Microsoft documentation for CNG it seems that it is not possible to generate an ECDSA key in code from a stream of bytes despite this being possible for a RSA key (I think, not 100% sure).

Because I wanted in code byte stream generation, I looked at crypto++ and I managed to get the test suite compiled but cryptolib.lib is an outsize 90 megabytes and I am facing a slew of link errors simply trying to do something basic. So I am less keen in crypto++ now.

So I want to turn back to using something shipped by Microsoft in its desktop Windows OS but I am back to my original problem of no in code bytestream generated keys.

Can an expert confirm that this really isn't possible? Also, can they suggest alternative, I am happy to fall back to RSA with a long (2048?) key.

The following code compiles and runs for me. It can be found at the MSDN article - Signing Data with CNG. I was wondering if it could be adapted. Does this code (a) create an ECDSA key on the fly (b) sign a hash (c) save key to a certificate store (d) retrieve the key and verify signed hash. If so then I guess it's demo code. What I would need is an example of a hard coded ECDSA key not one created on the fly.

// CngECDSA.cpp : Defines the entry point for the console application.
// Based on https://msdn.microsoft.com/en-us/library/windows/desktop/aa376304(v=vs.85).aspx

#include "stdafx.h"

#include <Windows.h>
#include <stdint.h>
#include <Bcrypt.h>
#include <ncrypt.h>

#pragma comment(lib, "bcrypt")  
#pragma comment(lib, "ncrypt")


#define NT_SUCCESS(Status)          (((NTSTATUS)(Status)) >= 0)
#define STATUS_UNSUCCESSFUL         ((NTSTATUS)0xC0000001L)

static const  BYTE rgbMsg[] =
{
    0x04, 0x87, 0xec, 0x66, 0xa8, 0xbf, 0x17, 0xa6,
    0xe3, 0x62, 0x6f, 0x1a, 0x55, 0xe2, 0xaf, 0x5e,
    0xbc, 0x54, 0xa4, 0xdc, 0x68, 0x19, 0x3e, 0x94,
};

BYTE value[] =
{ 0x02,0x00,0x00,0x00 };

void __cdecl wmain(
    int                      argc,
    __in_ecount(argc) LPWSTR *wargv)
{
    NCRYPT_PROV_HANDLE      hProv = NULL;
    NCRYPT_KEY_HANDLE       hKey = NULL;
    BCRYPT_KEY_HANDLE       hTmpKey = NULL;
    SECURITY_STATUS         secStatus = ERROR_SUCCESS;
    BCRYPT_ALG_HANDLE       hHashAlg = NULL,
        hSignAlg = NULL;
    BCRYPT_HASH_HANDLE      hHash = NULL;
    NTSTATUS                status = STATUS_UNSUCCESSFUL;
    DWORD                   cbData = 0,
        cbHash = 0,
        cbBlob = 0,
        cbSignature = 0,
        cbHashObject = 0;
    PBYTE                   pbHashObject = NULL;
    PBYTE                   pbHash = NULL,
        pbBlob = NULL,
        pbSignature = NULL;

    UNREFERENCED_PARAMETER(argc);
    UNREFERENCED_PARAMETER(wargv);

    //open an algorithm handle
    if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(
        &hHashAlg,
        BCRYPT_SHA1_ALGORITHM,
        NULL,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptOpenAlgorithmProvider\n", status);
        goto Cleanup;
    }

    if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(
        &hSignAlg,
        BCRYPT_ECDSA_P256_ALGORITHM,
        NULL,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptOpenAlgorithmProvider\n", status);
        goto Cleanup;
    }

    //calculate the size of the buffer to hold the hash object
    if (!NT_SUCCESS(status = BCryptGetProperty(
        hHashAlg,
        BCRYPT_OBJECT_LENGTH,
        (PBYTE)&cbHashObject,
        sizeof(DWORD),
        &cbData,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
        goto Cleanup;
    }

    //allocate the hash object on the heap
    pbHashObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbHashObject);
    if (NULL == pbHashObject)
    {
        wprintf(L"**** memory allocation failed\n");
        goto Cleanup;
    }

    //calculate the length of the hash
    if (!NT_SUCCESS(status = BCryptGetProperty(
        hHashAlg,
        BCRYPT_HASH_LENGTH,
        (PBYTE)&cbHash,
        sizeof(DWORD),
        &cbData,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
        goto Cleanup;
    }

    //allocate the hash buffer on the heap
    pbHash = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbHash);
    if (NULL == pbHash)
    {
        wprintf(L"**** memory allocation failed\n");
        goto Cleanup;
    }

    //create a hash
    if (!NT_SUCCESS(status = BCryptCreateHash(
        hHashAlg,
        &hHash,
        pbHashObject,
        cbHashObject,
        NULL,
        0,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptCreateHash\n", status);
        goto Cleanup;
    }


    //hash some data
    if (!NT_SUCCESS(status = BCryptHashData(
        hHash,
        (PBYTE)rgbMsg,
        sizeof(rgbMsg),
        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptHashData\n", status);
        goto Cleanup;
    }

    //close the hash
    if (!NT_SUCCESS(status = BCryptFinishHash(
        hHash,
        pbHash,
        cbHash,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptFinishHash\n", status);
        goto Cleanup;
    }

    //open handle to KSP
    if (FAILED(secStatus = NCryptOpenStorageProvider(
        &hProv,
        MS_KEY_STORAGE_PROVIDER,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by NCryptOpenStorageProvider\n", secStatus);
        goto Cleanup;
    }

    //create a persisted key
    if (FAILED(secStatus = NCryptCreatePersistedKey(
        hProv,
        &hKey,
        NCRYPT_ECDSA_P256_ALGORITHM,
        L"my ecc key",
        0,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by NCryptCreatePersistedKey\n", secStatus);
        goto Cleanup;
    }

    //create key on disk
    if (FAILED(secStatus = NCryptFinalizeKey(hKey, 0)))
    {
        wprintf(L"**** Error 0x%x returned by NCryptFinalizeKey\n", secStatus);
        goto Cleanup;
    }

    //sign the hash
    if (FAILED(secStatus = NCryptSignHash(
        hKey,
        NULL,
        pbHash,
        cbHash,
        NULL,
        0,
        &cbSignature,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by NCryptSignHash\n", secStatus);
        goto Cleanup;
    }


    //allocate the signature buffer
    pbSignature = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbSignature);
    if (NULL == pbSignature)
    {
        wprintf(L"**** memory allocation failed\n");
        goto Cleanup;
    }

    if (FAILED(secStatus = NCryptSignHash(
        hKey,
        NULL,
        pbHash,
        cbHash,
        pbSignature,
        cbSignature,
        &cbSignature,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by NCryptSignHash\n", secStatus);
        goto Cleanup;
    }

    if (FAILED(secStatus = NCryptExportKey(
        hKey,
        NULL,
        BCRYPT_ECCPUBLIC_BLOB,
        NULL,
        NULL,
        0,
        &cbBlob,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by NCryptExportKey\n", secStatus);
        goto Cleanup;
    }

    pbBlob = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbBlob);
    if (NULL == pbBlob)
    {
        wprintf(L"**** memory allocation failed\n");
        goto Cleanup;
    }

    if (FAILED(secStatus = NCryptExportKey(
        hKey,
        NULL,
        BCRYPT_ECCPUBLIC_BLOB,
        NULL,
        pbBlob,
        cbBlob,
        &cbBlob,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by NCryptExportKey\n", secStatus);
        goto Cleanup;
    }

    if (!NT_SUCCESS(status = BCryptImportKeyPair(
        hSignAlg,
        NULL,
        BCRYPT_ECCPUBLIC_BLOB,
        &hTmpKey,
        pbBlob,
        cbBlob,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptImportKeyPair\n", status);
        goto Cleanup;
    }

    if (!NT_SUCCESS(status = BCryptVerifySignature(
        hTmpKey,
        NULL,
        pbHash,
        cbHash,
        pbSignature,
        cbSignature,
        0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptVerifySignature\n", status);
        goto Cleanup;
    }

    wprintf(L"Success!\n");

Cleanup:

    if (hHashAlg)
    {
        BCryptCloseAlgorithmProvider(hHashAlg, 0);
    }

    if (hSignAlg)
    {
        BCryptCloseAlgorithmProvider(hSignAlg, 0);
    }

    if (hHash)
    {
        BCryptDestroyHash(hHash);
    }

    if (pbHashObject)
    {
        HeapFree(GetProcessHeap(), 0, pbHashObject);
    }

    if (pbHash)
    {
        HeapFree(GetProcessHeap(), 0, pbHash);
    }

    if (pbSignature)
    {
        HeapFree(GetProcessHeap(), 0, pbSignature);
    }

    if (pbBlob)
    {
        HeapFree(GetProcessHeap(), 0, pbBlob);
    }

    if (hTmpKey)
    {
        BCryptDestroyKey(hTmpKey);
    }

    if (hKey)
    {
        NCryptDeleteKey(hKey, 0);
    }

    if (hProv)
    {
        NCryptFreeObject(hProv);
    }
}

Just to be clear, I aiming at 384 bit and for compatibility with OpenSsl and C# curvename is NIST recommended curve secp384r1 – {1.3.132.0.34} And I will be using SHA256 hash twice (just like BitCoin).

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
S Meaden
  • 8,050
  • 3
  • 34
  • 65
  • It is certainly possible to serialize an EC key. What problem are you having? – stark Aug 15 '17 at 18:18
  • @stark: hi, I have added code (from MSDN) and whilst it runs, it does meet my use case. I need to hard code the (public only) key, presumably as an array of bytes. – S Meaden Aug 15 '17 at 21:57

1 Answers1

3

Does this code (a) create an ECDSA key on the fly (b) sign a hash (c) save key to a certificate store (d) retrieve the key and verify signed hash. If so then I guess it's demo code.

A) Yes.

B) Yes.

C) Yes. It saves the key as a persisted object named "my ec key". But since it never asked for it by name again, nullptr/NULL could have been passed to make it be an ephemeral key. (And it saved it to the "key store", not a "certificate store".

D) Yes to verify, no to retrieve.


Getting your publicKey data (this is the output of openssl ec -in eckey.pem -pubout -outform der | xxd -g1 translated into the C array) and signature (in the IEEE P1363 format) is an exercise left to the reader.

static const BYTE data[] =
{
    '1', '2', '3', '4',
};

static const BYTE publicKey[] =
{
    0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
    0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
    0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
    0x42, 0x00, 0x04, 0x23, 0x63, 0xdd, 0x13, 0x1d,
    0xa6, 0x5e, 0x89, 0x9a, 0x2e, 0x63, 0xe9, 0xe0,
    0x5e, 0x50, 0xc8, 0x30, 0xd4, 0x99, 0x46, 0x62,
    0xff, 0xe8, 0x83, 0xdb, 0x2b, 0x9a, 0x76, 0x7d,
    0xcc, 0xab, 0xa2, 0xf0, 0x70, 0x81, 0xb5, 0x71,
    0x1b, 0xe1, 0xde, 0xe9, 0x0d, 0xfc, 0x8d, 0xe1,
    0x79, 0x70, 0xc2, 0xd9, 0x37, 0xa1, 0x6c, 0xd3,
    0x45, 0x81, 0xf5, 0x2b, 0x8d, 0x59, 0xc9, 0xe9,
    0x53, 0x2d, 0x13,
};

static const BYTE signature[] =
{
    // r
    0xc6, 0x4c, 0x14, 0x55, 0xfe, 0xc0, 0x2f, 0xe7,
    0x4a, 0x25, 0x87, 0xe7, 0x0c, 0x10, 0x4e, 0x73,
    0xf0, 0x28, 0x86, 0x18, 0x28, 0xae, 0xef, 0x4f,
    0xe5, 0xa0, 0xcc, 0x7a, 0xa8, 0xe4, 0x1f, 0xbf,

    // s
    0x35, 0x9f, 0x23, 0xfd, 0xc3, 0xd6, 0x33, 0xfb,
    0x52, 0x47, 0x9b, 0xef, 0x2b, 0x2a, 0x48, 0xa8,
    0x6f, 0x37, 0x04, 0xd0, 0x8c, 0xc3, 0x49, 0x04,
    0x21, 0x53, 0xb8, 0x3c, 0x9d, 0x8c, 0x6c, 0xf5,
};

#define NT_SUCCESS(Status)          (((NTSTATUS)(Status)) >= 0)

int main()
{
    NTSTATUS status = NTE_BAD_DATA;
    int exitCode = ERROR_INVALID_FUNCTION;
    BCRYPT_KEY_HANDLE importedPublicKey = nullptr;
    PCERT_PUBLIC_KEY_INFO subjectPublicKeyInfo = (PCERT_PUBLIC_KEY_INFO)LocalAlloc(0, 2048);
    BCRYPT_ALG_HANDLE sha256 = nullptr;
    BCRYPT_HASH_HANDLE hHash = nullptr;
    BYTE dataHash[256 >> 3];

    DWORD structSize = 2048;
    if (!CryptDecodeObject(
        X509_ASN_ENCODING,
        X509_PUBLIC_KEY_INFO,
        publicKey,
        sizeof(publicKey),
        0,
        subjectPublicKeyInfo,
        &structSize))
    {
        wprintf(L"**** Error 0x%x returned by CryptDecodeObject\n", GetLastError());
        goto Cleanup;
    }

    if (!CryptImportPublicKeyInfoEx2(
        X509_ASN_ENCODING,
        subjectPublicKeyInfo,
        0,
        nullptr,
        &importedPublicKey))
    {
        wprintf(L"**** Error 0x%x returned by CryptImportPublicKeyInfoEx2\n", GetLastError());
        goto Cleanup;
    }

    if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&sha256, BCRYPT_SHA256_ALGORITHM, nullptr, 0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptOpenAlgorithmProvider\n", status);
        goto Cleanup;
    }

    if (!NT_SUCCESS(status = BCryptCreateHash(sha256, &hHash, nullptr, 0, nullptr, 0, 0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptCreateHash\n", status);
        goto Cleanup;
    }

    if (!NT_SUCCESS(status = BCryptHashData(hHash, (PUCHAR)data, sizeof(data), 0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptHashData\n", status);
        goto Cleanup;
    }

    if (!NT_SUCCESS(status = BCryptFinishHash(hHash, (PUCHAR)dataHash, sizeof(dataHash), 0)))
    {
        wprintf(L"**** Error 0x%x returned by BCryptFinishHash\n", status);
        goto Cleanup;
    }

    status = BCryptVerifySignature(
        importedPublicKey,
        nullptr,
        (PUCHAR)dataHash,
        sizeof(dataHash),
        (PUCHAR)signature,
        sizeof(signature),
        0);

    switch (status)
    {
        case STATUS_SUCCESS:
            wprintf(L"Signature verified successfully\n");
            exitCode = ERROR_SUCCESS;
            break;
        case STATUS_INVALID_SIGNATURE:
            wprintf(L"Signature did not verify\n");
            exitCode = ERROR_INVALID_DATA;
            break;
        default:
            wprintf(L"**** Error 0x%x returned by BCryptVerifySignature\n", status);
            goto Cleanup;
    }



Cleanup:
    if (hHash != nullptr)
        BCryptDestroyHash(hHash);

    if (sha256 != nullptr)
        BCryptCloseAlgorithmProvider(sha256, 0);

    if (importedPublicKey != nullptr)
        BCryptDestroyKey(importedPublicKey);

    LocalFree(subjectPublicKeyInfo);

    return exitCode;
}
bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • thanks! reading your profile and your highest voted answers clearly you are the right person to reach! I'll do the openssl exercise. I'll also look into .Net core crypto as per your best answers. – S Meaden Aug 16 '17 at 17:56