0

I need to sign a HLKX package created with Hardware Lab Kit using a certificate/key stored in a HSM. I do have the certificate as a file, but it contains only the public key and the private key is stored in the HSM which is not connected to the computer the package needs to be signed on.

Using the Sign function provided in the code sample here: https://learn.microsoft.com/en-us/windows-hardware/test/hlk/user/hlk-signing-with-an-hsm I get the exception: System.Security.Cryptography.CryptographicException: 'Cannot locate the selected digital certificate.'

I assume this is caused because it does not have the private key that belongs to the certificate available. There is some vague information about CSP dlls that provide the actual signing functionality, but I could find any information on how its interface would have to look like - also how to tell the system for which certificates this CSP dll is responsible for. Also, the HSM is connected to Hashicorp Vault and not used directly.

In case of signing binaries using signtool.exe it was easy to create the digest in one call then use that digest in custom code to get the signature and call signtool.exe again to actually add the signature. But it is unclear to me how this is done when signing the HLKX package. Is there any (useful) documentation or code sample on how to achieve that?

ixe013
  • 9,559
  • 3
  • 46
  • 77
Gunnar
  • 38
  • 6

2 Answers2

0

Here is how signature works:

  1. Get a binary somehow. Build from source for example. Can be anything, but its format matters.
  2. Compute a hash of the binary
  3. Compute a digital signature of the hash, using an asymmetric algorithm like RSA. Whatever thing does this step needs direct access to the private key (not the public key nor the certificate that holds it).
  4. Embed the signature in the right binary format, and usually also the hash and certificate in the binary.

Steps 1 and 2 are well known. It gets complicated at step 3 and 4.

If your private key is in an HSM, only the HSM can do step 3: sign the hash.

The fact that Hashicorp Vault (Enterprise version) is connected to the HSM does not help because Vault does not provide a secret engine that can do step 4. Vault does not know how to insert a signature in a HLK binary file. Even worse, Vault, as of July 2022, does not offer a way to sign an arbitrary hash with a private key that is stored in an HSM.

Long story short: you cannot use Vault for your use case.

Since only the HSM has access to the private key, the HSM will do the actual signature. It will never reveal the private key, only return the results to the caller. Whatever called the HSM will have to insert the signature in the package, respecting the format.

Using signtool.exe is out of the question because it does not support the Open Packaging Conventions standard. But it will help you debug your configuration.

I would break the problem down to these steps:

Make the HSM work with Windows.

Read your HSM vendor documentation so that your HSM's DLL is installed and configured to work as a 1st class Windows Cryptographic Service Provider. It could a smart card inserted in your workstation or a networked HSM (via a proprietary protocol, beware of firewalls between you and it).

Import your signing certificate in your Certificate Store

Using the The Hardware Lab Kit to sign your .hlkx package, you must select the "Use the certificate store" option. The "certificate file" option implies that you have the private key, but you don't. The HSM has it and it will never export it.

So for that option to work, the certificate must be in your certificate store. Check your vendor documentation to make sure you import the certificate in a way that tells the certificate store that the private key is held by the HSM.

Some examples have you provide the certificate file, but I suspect it's just to extract its hash and look it up in the certificate store.

Test your configuration

At this point, you need a referee. You don't want to debug your HSM configuration when a pesky bug in your code is the real culprit. So run signtool to sign any old binary you have lying around. You can sign notepad.exe if you want:

copy %windir%\system32\notepad.exe .\my-notepad.exe
signtool sign /f certificate.cer /csp "Hardware Cryptography Module" my-notepad.exe

Replace Hardware Cryptography Module with the name of your HSM's CSP name. Check your vendor documentation for this.

Run the code

There is a sample C# program that will sign using an HSM. You must provide the CSP's name, it is the same as the one you provided when testing with signtool.exe.

ixe013
  • 9,559
  • 3
  • 46
  • 77
  • Thanks for the information but as I hinted to in my question using the HSM on the system (i.e. using a HSM CSP dll) is not possible. Let me elaborate on the architecture in my separate post as the comment is too long. – Gunnar Jul 08 '22 at 12:10
  • We have the HSM hardware which is connected to a Hashicorp Vault server that requires the user to login (via Azure token) to specific roles which Vault uses to determine whether the user is allowed to use a certain key inside the HSM. We have a Go application that handles communication with Vault, so, I could pass a hash to that application which is communicating with vault and returning the signature. The actual call into the HSM for signing is done on the Vault server (in a plugin). – Gunnar Jul 08 '22 at 12:15
  • Signing executable for example is done using signtool.exe with three steps (the certificate in step 1 being available as a file only): 1. signtool.exe sign /dg /f ... 2. Using custom tool to send digest from step 1 to Vault server and sign it 3. signtool.exe sign /di The is no way to use the HSM directly as the login to the HSM is not supposed to exist on the signing workstation (hence the Hashicorp Vault server being the "Signing Provider"). – Gunnar Jul 08 '22 at 12:15
  • It sounds like (lacking the option of using a HSM CSP dll) I need to import the certificate to the locale certificate store in a way that it points to a custom CSP dll (which will talk to the Vault server for signing). But I couldn't find information on how to map a certain certificate to a custom CSP dll and what kind of interface that dll is supposed to provide. There are some vague references to a function called AuthenticodeDigestSign that it might need to export but even for that I couldn't find information on what kind of arguments are provided or what exactly it's supposed to return. – Gunnar Jul 08 '22 at 12:17
  • re ... information on how to map a certain certificate to a custom CSP dll ... >> See the certutil "repairstore" option. Gen the key in the HSM, issue a CSR using the normal tools, have that CSR signed, import the cert onto the signing host computer, make sure the vendor's CSP dll is available, and then "repairstore" the certificate. The tool reads the cert, looks for the CSP, and then verifies that the CSP has access to the reference private key. – rip... Aug 03 '22 at 17:17
0

In order to use the PackageDigitalSignatureManager approach as described here: https://learn.microsoft.com/en-us/windows-hardware/test/hlk/user/hlk-signing-with-an-hsm two preconditions need to be addressed:

  1. A cryptographic service provider dll needs to be created and registered
  2. The certificate needs to be imported to the certificate store and linked to the provider.

Regarding 1: There is a sample project (in C++ to create a provier dll and a command line executable that is handling the registering/unregistering of the dll). For the registration to work this way, the DLL has to be present in the Windows\System folder. The source sample can be found here: https://www.microsoft.com/en-us/download/details.aspx?id=30688

Regarding 2: I found a way to link the certificate with the provider and posted the C# code to do so in my other question here: How to link KSP DLL to Certificate

Remarks: Doing it this way worked for signing hlkx files from within Hardware Lab Kit software or by signing manually using the PackageDigitalSignatureManager class as described in the example linked above. For me it does not (yet) work when trying to use the certificate when signing executables using Microsoft's signtool.exe. It always complains about the private key not being available for the certificate, although the certificate in the store also states that the private key is available. But as I was focusing on hlkx signing I wasn't looking into this issue (yet).

Gunnar
  • 38
  • 6
  • How do I create the custom ksp? can i use c#? I can't compile the c++ example. – Angelru Mar 22 '23 at 15:01
  • I was creating a C++ custom KSP dll based on the sample. It needs to be registered with the system which expects a C API interface. I'm not sure if you could create a C# version. Due to lacking information on various aspects of the functions and data types used it's probably really hard to translate the API into C# instead of building from the sample. It's some time since I build that dll. I'm not sure if I was able to compile the sample as it was or if there was any adjustment required. Depends on what you mean by "can't compile" - what the problem actually is. – Gunnar Mar 23 '23 at 16:58
  • Which methods does the interface implement? Why do I have to connect to an API that is going to "sign in the cloud"? I think the CNG API could be in C# – Angelru Mar 23 '23 at 17:35
  • Given the required information on the details of the API function you should be able to build a C# DLL, but I wasn't able to find the required information so I ended up using the C++ example as a reference and build from that. Marshaling various various function with rather complex structs passed as arguments can be a challenging task when building a C# DLL. – Gunnar Mar 27 '23 at 08:07
  • In the final DLL I ended up using, I implemented the following functions: GetKeyStorageInterface OpenProvider OpenKey FreeProvider CreatePersistedKey GetProviderProperty GetKeyProperty SetProviderProperty SetKeyProperty FinalizeKey DeleteKey FreeKey FreeBuffer Encrypt Decrypt IsAlgSupported EnumAlgorithms EnumKeys ImportKey ExportKey SignHash VerifySignature I added the following just as a hull, returning NTE_NOT_SUPPORTED only: PromptUser NotifyChangeKey SecretAgreement DeriveKey FreeSecret I started with all as a hull and checking which one got called. – Gunnar Mar 27 '23 at 08:15
  • I ended up compiling the project and generating a dll. Associate the certificate to the provider as you put in another post. But when using the certificate to sign in a third party application I get the message "private key not found". The idea is that the dll calls an api to sign, but I want to test that signing with a certificate writes to a text file.. – Angelru Jun 21 '23 at 12:41
  • When adding the certificate (which is the public key) to the certificate store it needs to be linked to the KSP DLL so that the DLL is used by the system when the private key is required. If you try to sign something the system will call the corresponding function in the DLL passing the hash that needs to be signed to the DLL and the DLL is responsible for signing it with the private key (whereever it gets it from). If this is not available or done, the "no private key available" error will occur. – Gunnar Jun 22 '23 at 13:14
  • What method of the dll is called from the KSP? I set it to write to a log, and if it does when creating a key, open it. But when I sign with that certificate, it seems that no method is called (since I set all of them to write to the log). And it's strange, I'm using a certificate from another provider and when I change it (with your code) and do the signature, windows seems to change my provider back to the old one. – Angelru Jun 23 '23 at 05:52
  • When I started I implemented all functions with a log print only to see what is actually called (as documentation is not very helpful). I ended actually using these functions: GetKeyStorageInterface KSPOpenProvider KSPOpenKey KSPFreeProvider KSPCreatePersistedKey KSPGetProviderProperty KSPGetKeyProperty KSPSetProviderProperty KSPSetKeyProperty KSPFinalizeKey KSPDeleteKey KSPFreeKey KSPFreeBuffer KSPEncrypt KSPDecrypt KSPIsAlgSupported KSPEnumAlgorithms KSPEnumKeys KSPImportKey KSPExportKey KSPSignHash KSPVerifySignature – Gunnar Jun 26 '23 at 08:09
  • You might need to add all functions though (even if it is just implemented to return an error as GetKeyStorageInterface returns a function table and the KSP DLL might not be usable if it does not export all functions (or not in the right order). My function table contains: – Gunnar Jun 26 '23 at 08:12
  • #define INTERFACE_VERSION BCRYPT_MAKE_INTERFACE_VERSION(1,0) NCRYPT_KEY_STORAGE_FUNCTION_TABLE KSPFunctionTable = { INTERFACE_VERSION, KSPOpenProvider, KSPOpenKey, KSPCreatePersistedKey, KSPGetProviderProperty, KSPGetKeyProperty, KSPSetProviderProperty, KSPSetKeyProperty, KSPFinalizeKey, KSPDeleteKey, KSPFreeProvider, KSPFreeKey, KSPFreeBuffer, KSPEncrypt, KSPDecrypt, KSPIsAlgSupported, KSPEnumAlgorithms, KSPEnumKeys, KSPImportKey, KSPExportKey, KSPSignHash, KSPVerifySignature, – Gunnar Jun 26 '23 at 08:13
  • KSPPromptUser, KSPNotifyChangeKey, KSPSecretAgreement, KSPDeriveKey, KSPFreeSecret }; – Gunnar Jun 26 '23 at 08:13
  • I change the provider to a certificate with a C# method that you put in another post, but when signing with that certificate, Windows puts the old provider, has this happened to you? – Angelru Jul 14 '23 at 07:41
  • Is the new provider a different file name? If the file name is different you might need to unregister the old one before registering the new one - not just "overwriting" by registering the new file name. – Gunnar Jul 17 '23 at 08:59
  • Let's say the provider is called "NAME PROVIDER" and the dll nameprovider.dll. When doing a CngKey.Open with c# with the cng api, the dll is called. – Angelru Jul 18 '23 at 06:41
  • It will probably call KSPOpenKey? The provider dll is responsible for any operation regarding the key, so key open/close and using the key will always be relayed to the dll. – Gunnar Jul 18 '23 at 09:35
  • Yes, that works fine. So I understand that the dll is well registered. But when signed with a certificate associated with the dll. Windows changes the provider. – Angelru Jul 18 '23 at 09:39
  • What exactly do you mean by "changes the provider"? Does it call a different dll that you registered previously, does it return an error indicating that no private key was available? What is the output of a signtool.exe sign /debug /v call on a binary (trying to sign it) using your cert and provider? – Gunnar Jul 18 '23 at 09:59
  • For example, with the Adobe app, select the certificate and when signing, the document is signed but Windows changes my certificate provider to a default Windows one. I have not tried to sign with signtool.exe. – Angelru Jul 18 '23 at 12:16
  • I don't use any Adobe app, so, I'm not sure what might be happening there. If you use the signtool.exe with the /debug /v arguments you get a list of all certificates that were considered, the one that was chosen etc. So, it might be a better output to see whether your certificate has an issue with the provider or what's actually happening there. – Gunnar Jul 18 '23 at 13:08
  • I'll try and tomorrow I'll tell you, how do you pass the provider to the signtool? – Angelru Jul 18 '23 at 13:48
  • When you register the provider the dll is linked with the certificate in the cert store. Whenever you use the certificate the provider will be used automatically. When calling signtool.exe you can either specify nothing special regarding the cert, then it will used the first certificate that can be used for signing and for which a private key is available or you can use the /f argument specify your cert file (if this cert is available in the store it will be used). signtool.exe sign -? will show you some more options to select a specific cert. – Gunnar Jul 18 '23 at 14:14
  • I can register the provider with: symmclient.exe -register I can see my provider listed with: symmclient.exe -enum I then link a certificate to my provider as samples in your response: https://stackoverflow.com/a/73387769/4976700 But if, for example, I sign a document with the adobe application or another signing application, when I select the certificate that I have previously associated, it does not call my dll. Can you try, for example, with the browser? Use the associated certificate to make a signature from a website that shows the certificate selection popup. – Angelru Jul 19 '23 at 09:00
  • In my case I can use the certificate from other applications like Visual Studio and Microsoft Office applications for signing documents as well as signtool. As I said the question is what "does not call my dll" means. Is it returning an error because it is called in a way it cannot properly handle or is it not called at all because when trying to sign the linkage to the dll is not done properly. Also, there is a difference if a 32bit or a 64bit application is trying to sign. You might need to have a x86 as well as x64 bit dll linked if the application trying to sign can be either. – Gunnar Jul 20 '23 at 13:43
  • I already managed to fix it, the signature program problem. It works fine signing with a certificate associated with the dll and signing with ADOBE. One question, does your dll connect to a backend? I'm using curl to do tests but it already depends on another dll that needs to be copied to system32. – Angelru Jul 20 '23 at 18:49
  • I'm seeing that my dll is called when I sign a document and select a certificate, but how do I get information about the selected certificate? the adobe application uses a certificate that is associated with my dll and it calls the signature method, but within that method I don't see how I can get information from the certificate. – Angelru Jul 24 '23 at 06:28
  • It will call the OpenProvider and OpenKey function first. These contain information on the Provider Name and Key Name expected to be opened/used by the caller. – Gunnar Jul 24 '23 at 08:13
  • pbHashValue is the file hash that the user selected? This variable is in SignHash. I understand that this must be passed to base64 and passed to the api? – Angelru Jul 26 '23 at 12:05
  • The SignHash function in the provider will be called by the system passing the hash that is to be signed. The provider has to create the signature for the hash and return the signature. I don't really get the question. The provider hast to perform the actual signing as it is responsible for doing all actions that involve the private key. – Gunnar Jul 27 '23 at 14:43
  • I don't want the provider to do the signing, this is an intermediary, I want to pass the hash (obtained by the provider) to an api and have the api do the signing and return the signature to the provider and it says "signature ok" – Angelru Jul 27 '23 at 17:40
  • That's what the provider and SignHash function is for. The system will call the function in the provider, the provider will call the corresponding API, do the signing and return the signature. – Gunnar Jul 31 '23 at 08:46
  • Yes I understand. But I have to pass the pbHashValue to the api, which is the hash of the file, right? – Angelru Jul 31 '23 at 09:06
  • 1
    Yes. It's the hash of the data you want to sign. If you want to sign a file, it's the hash of the file. If you want to sign any data blob, it's the hash of that data blob. – Gunnar Aug 15 '23 at 15:21