0

I want to sign HLKX files using a certificate for which the private key is not available on the local system.

I created a custom Key Storage Provider (basically a shell for testing purposes) based on the code sample for a KSP DLL in "Cryptographic Provider Development Kit" and I'm able to register it and it is shown in the enumeration of KSPs available on the system.

I'm using the sign function that is shown as an example at: https://learn.microsoft.com/en-us/windows-hardware/test/hlk/user/hlk-signing-with-an-hsm in a C# application.

The custom KSP dll is supposed to handle all the sign commands and connect to a backend that allows using the private key which is stored in a HSM behind an additional software layer that is limiting key access to certain users.

When I'm running the application the signing fails due to the missing private key. So, I need to somehow link the certificate (being it the certificate in a file or imported to the system's certificate store) to the KSP causing the calls for signing hashes etc. to end up in the KSP's API, but I couldn't find any suitable information on how to either: a) Add the reference to the KSP to the C# signing call or b) Import the certificate to the certificate store with it referencing the KSP so that it will be used automatically if the certificate is used for signing.

So, how can I do either a) or b) or what other way is there to manually handle this? The signing application is just using C# because that's the only sort-of sample for this use case that I could find from Microsoft. If there would be a sample in C/C++ that would be fine, too. I guess the problem would be the same in case of using a CSP instead of KSP but unfortunately many posts are massively mixing the two.

Braiam
  • 1
  • 11
  • 47
  • 78
Gunnar
  • 38
  • 6
  • You could try `certutil -csp "Microsoft RSA SChannel Cryptographic Provider" -importpfx ` but need a PFX/PKCS#12 file for it. If you do not have that you could set the CspInformation https://stackoverflow.com/questions/45084515/update-x509certificate2-with-correct-cryptographic-service-provider-in-bouncy-ca. When using the SigningAPI you can also add provider information: https://stackoverflow.com/questions/14237289/signersign-error-no-provider-was-specified-for-the-store-or-object – Daniel Fisher lennybacon Jul 18 '22 at 14:13
  • did you use the KeyStorageProviderSample? of the Cryptographic Provider Development Kit that example is to create the DLL that then appears in Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider? – Angelru Feb 22 '23 at 07:24

1 Answers1

0

I found a way to create the link between the certificate in the store and the provider dll (via the name under which the provider dll is registered in the system). The relevant system API functions are CertSetCertificateContextProperty and CertGetCertificateContextProperty in Crypt32.dll. I was able to verify that this works for signing hlkx files (from with Hardware Lab Kit software or via C# code using PackageDigitalSignatureManager) but I still have problems using this way to sign e.g. executables using Microsoft's signtool.exe which complains about the private key not be available for the certificate.

I was using the system API functions from within C# so I have extracted the relevant code fragments from my project on how to link the certificate with the provider and how to read information on the linked provider from a certificate.

    class Program
{
    private const UInt32 CERT_SET_KEY_CONTEXT_PROP_ID = 0x00000001;
    private const UInt32 CERT_SET_KEY_PROV_HANDLE_PROP_ID = 0x00000001;
    private const UInt32 CERT_KEY_PROV_INFO_PROP_ID = 2;

    static void Main(string[] args)
    {
        // Reading certificate from file
        X509Certificate2 certificate = new X509Certificate2("C:\\MyCert.crt");

        // Adding certificate to store
        X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadWrite);
        store.Add(certificate);
        store.Close();

        // Linking certificate with provider
        // ProviderName is the name under which the provider is registered in the system
        // ContainerName is a string that will be passed to the DLL when calls are made it can be used to
        // additional information to the DLL that can be set when linking the certificate with the provider
        SetCertificateProviderInformation("My Provider Name", "MyContainerName", certificate);

        // Read provider information
        GetCertificateProviderInformation(certificate);
    }

    private static void SetCertificateProviderInformation(string providerName, string containerName, X509Certificate2 certificate)
    {
        Crypt32Dll.CRYPT_KEY_PROV_INFO cryptKeyProvInfo = new Crypt32Dll.CRYPT_KEY_PROV_INFO
        {
            pwszProvName = providerName,
            pwszContainerName = containerName,
            dwProvType = 24,
            dwFlags = CERT_SET_KEY_CONTEXT_PROP_ID | CERT_SET_KEY_PROV_HANDLE_PROP_ID,
            cProvParam = 0,
            rgProvParam = IntPtr.Zero,
            dwKeySpec = 2
        };

        IntPtr pvData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Crypt32Dll.CRYPT_KEY_PROV_INFO)));
        Marshal.StructureToPtr(cryptKeyProvInfo, pvData, false);

        if (Crypt32Dll.CertSetCertificateContextProperty(certificate.Handle, CERT_KEY_PROV_INFO_PROP_ID, 0, pvData))
        {
            // succeeded
        }
        else
        {
            Int32 lastError = Marshal.GetLastWin32Error();
            // failed
        }


        if (pvData != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(pvData);
        }
    }

    private static void GetCertificateProviderInformation(X509Certificate2 certificate)
    {
        UInt32 dataSize = 0;

        // Get required size for struct
        if (Crypt32Dll.CertGetCertificateContextProperty(certificate.Handle, CERT_KEY_PROV_INFO_PROP_ID, IntPtr.Zero, ref dataSize))
        {
            // Allocate unmanaged struct memory of required size and query the information
            IntPtr pvData = Marshal.AllocHGlobal((int)dataSize);
            if (Crypt32Dll.CertGetCertificateContextProperty(certificate.Handle, CERT_KEY_PROV_INFO_PROP_ID, pvData, ref dataSize))
            {
                // succeeded
                Crypt32Dll.CRYPT_KEY_PROV_INFO keyProviderInformation = (Crypt32Dll.CRYPT_KEY_PROV_INFO)Marshal.PtrToStructure(pvData, typeof(Crypt32Dll.CRYPT_KEY_PROV_INFO));
                Console.Out.WriteLine("Provider Name: " + keyProviderInformation.pwszProvName);
                Console.Out.WriteLine("Container Name: "  + keyProviderInformation.pwszContainerName);
            }
            else
            {
                int lastError = Marshal.GetLastWin32Error();
                // failed
            }

            // Free unmanaged struct memory
            Marshal.FreeHGlobal(pvData);
        }
        else
        {
            // failed
        }
    }
}

With the code for using the Crypt32.dll being:

    class Crypt32Dll
{
    private const string DLL_NAME = "Crypt32.dll";

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    internal struct CRYPT_KEY_PROV_INFO
    {
        [MarshalAs(UnmanagedType.LPWStr)]
        internal string pwszContainerName;
        [MarshalAs(UnmanagedType.LPWStr)]
        internal string pwszProvName;
        internal UInt32 dwProvType;
        internal UInt32 dwFlags;
        internal UInt32 cProvParam;
        internal IntPtr rgProvParam;
        internal UInt32 dwKeySpec;
    }

    [DllImport(DLL_NAME, EntryPoint = "CertSetCertificateContextProperty", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern bool CertSetCertificateContextProperty(
        IntPtr pCertContext,
        UInt32 dwPropId,
        UInt32 dwFlags,
        IntPtr pvData
        );

    [DllImport(DLL_NAME, EntryPoint = "CertGetCertificateContextProperty", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern bool CertGetCertificateContextProperty(
        IntPtr pCertContext,
        UInt32 dwPropId,
        IntPtr pvData,
        ref UInt32 pcbData
        );
}
Gunnar
  • 38
  • 6