A while ago, Claire Novotny and I started exploring the feasibility of doing Authenticode signing with Azure Key Vault. Azure Key Vault lets you do some pretty interesting things, including which lets you treat it as a pseudo network-attached HSM.

A problem with Azure Key Vault though is that it’s an HTTP endpoint. Integrating it in to existing standards like CNG or PKCS#11 hasn’t been done yet, which makes it difficult to use in some cases. Specifically, tools that wanted to use a CSP or CNG provider, like Authenticode signing.

Our first attempt at getting this working was to see if we could use the existing signtool. A while ago, I wrote about using some new options in signtool that let you sign the digest with whatever you want in my post Custom Authenticode Signing.

This made it possible, if not a little unwieldy, to sign things with Authenticode and use Azure Key Vault as the signing source. As I wrote, the main problem with it was you needed to run signtool twice and also develop your own application to sign a file with Azure Key Vault. The steps went something like this.

  1. Run signtool with /dg flag to produce a base64-encoded digest to sign.
  2. Produce signature for that file using Azure Key Vault using custom tool.
  3. Run signtool again with /di to ingest the signature.

This was, in a word, “slow”. The dream was to be able to produce a signing service that could sign files in bulk. While a millisecond or two may not be the metric we care about, this was costing many seconds. It also let us feeling like the solution was held together by shoestrings and bubblegum.

/dlib

However, signtool mysteriously mentions a flag called /dlib. It says it combines /dg and /di in to a single operation. The documentation, in its entirety, is this:

Specifies the DLL implementing the AuthenticodeDigestSign function to sign the digest with. This option is equivalent to using SignTool separately with the /dg, /ds, and /di switches, except this option invokes all three as one atomic operation.

This lacked a lot of detail, but it seems like it is exactly what we want. We can surmise though that the value to this flag is a path to a library that exports a function called AuthenticodeDigestSign. That is easy enough to do. However, it fails to mention what is passed to this function, or what we should return to it.

This is not impossible to figure out if we persist with windbg. To make a long story short, the function looks something like this:

HRESULT WINAPI AuthenticodeDigestSign(
    CERT_CONTEXT* certContext,
    void* unused,
    ALG_ID algId,
    BYTE* pDigestToSign,
    DWORD cDigestToSign,
    CRYPTOAPI_BLOB* signature
);

With this, it was indeed possible to make a library that signtool would call this function for signing the digest. Claire put together a C# library that did exactly that on GitHub under KeyVaultSignToolWrapper. I even made some decent progress on a rust implementation.

This was a big improvement. Instead of multiple invocations to signtool, we can do this all at once. This still presented some problems though. The first being that there was no way to pass any configuration to it with signtool. The best we could come up with was to wrap the invocation of signtool and set environment variables in the signtool process, and let this get its configuration from environment variables, such as which vault to authenticate to, and how to authenticate. A final caveat was that this still depended on signtool. Signtool is part of the Windows SDK, which technically doesn’t allow us to distribute it in pieces. If we wanted to use signtool, we would need to install parts of the entire Windows SDK.

SignerSignEx3

Later, I noticed that Windows 10 includes a new signing API, SignerSignEx3. I happened upon this when I was using windbg in AuthenticodeDigestSign and saw that the caller of it was SignerSignEx3, not signtool. I checked out the exports in mssign32 and did see it as a new export starting in Windows 10. The natural conclusion was that Windows 10 was shipping a new API that is capable of using callbacks for signing the digest and signtool wasn’t doing anything special.

As you may have guessed, SignerSignEx3 is not documented. It doesn’t exist in Microsoft Docs or in the Windows SDK headers. Fortunately, SignerSignEx2 was documented, so we weren’t starting from scratch. If we figured out SignerSignEx3, then we could skip signtool completely and develop our own tool that does this.

SignerSignEx3 looks very similar to SignerSignEx2:

// Not documented
typedef HRESULT (WINAPI *SignCallback)(
    CERT_CONTEXT* certContext,
    PVOID opaque,
    ALG_ID algId,
    BYTE* pDigestToSign,
    DWORD cDigestToSign,
    CRYPT_DATA_BLOB* signature
);

// Not documented
typedef struct _SIGN_CALLBACK_INFO {
    DWORD cbSize;
    SignCallback callback;
    PVOID opaque;
} SIGN_CALLBACK_INFO;

HRESULT WINAPI SignerSignEx3(
    DWORD                  dwFlags,
    SIGNER_SUBJECT_INFO    *pSubjectInfo,
    SIGNER_CERT            *pSignerCert,
    SIGNER_SIGNATURE_INFO  *pSignatureInfo,
    SIGNER_PROVIDER_INFO   *pProviderInfo,
    DWORD                  dwTimestampFlags,
    PCSTR                  pszTimestampAlgorithmOid,
    PCWSTR                 pwszHttpTimeStamp,
    PCRYPT_ATTRIBUTES      psRequest,
    PVOID                  pSipData,
    SIGNER_CONTEXT         **ppSignerContext,
    PCERT_STRONG_SIGN_PARA pCryptoPolicy,
    SIGN_CALLBACK_INFO     *signCallbackInfo,
    PVOID                  pReserved
);

Reminder: These APIs are undocumented. I made a best effort at reverse engineering them, and to my knowledge, function. I do not express any guarantees though.

There’s a little more to it than this. First, in order for the callback parameter to even be used, there’s a new flag that needs to be passed in. The value for this flag is 0x400. If this is not specified, the signCallbackInfo parameter is ignored.

The usage is about what you would expect. A simple invocation might work like this:

HRESULT WINAPI myCallback(
    CERT_CONTEXT* certContext,
    void* opaque,
    ALG_ID algId,
    BYTE* pDigestToSign,
    DWORD cDigestToSign,
    CRYPT_DATA_BLOB* signature)
{
    //Set the signature property
    return 0;
}

int main()
{
    SIGN_CALLBACK_INFO callbackInfo = { 0 };
    callbackInfo.cbSize = sizeof(SIGN_CALLBACK_INFO);
    callbackInfo.callback = myCallback;
    HRESULT blah = SignerSignEx3(0x400, /*omitted*/ callbackInfo, NULL);
    return blah;
}

When the callback is made, the signature parameter must be filled in with the signature. It must be heap allocated, but it can be freed after the call to SignerSignEx3 completes.

APPX

We’re not quite done yet. The solution above works with EXEs, DLLs, etc - it does not work with APPX packages. This is because signing an APPX requires some additional work. Specifically, the APPX Subject Interface Package requires some additional data be supplied in the pSipData parameter.

Once again we are fortunate that there is some documentation on how this works with SignerSignEx2, however the details here are incorrect for SignerSignEx3.

Unfortunately, the struct shape is not documented for SignerSignEx3.

To the best of my understanding, SIGNER_SIGN_EX3_PARAMS structure should look like this:

typedef _SIGNER_SIGN_EX3_PARAMS {
    DWORD                   dwFlags;
    SIGNER_SUBJECT_INFO     *pSubjectInfo;
    SIGNER_CERT             *pSigningCert;
    SIGNER_SIGNATURE_INFO   *pSignatureInfo;
    SIGNER_PROVIDER_INFO    *pProviderInfo;
    DWORD                   dwTimestampFlags;
    PCSTR                   pszTimestampAlgorithmOid;
    PCWSTR                  pwszTimestampURL;
    CRYPT_ATTRIBUTES        *psRequest;
    SIGN_CALLBACK_INFO      *signCallbackInfo;
    SIGNER_CONTEXT          **ppSignerContext;
    CERT_STRONG_SIGN_PARA   *pCryptoPolicy;
    PVOID                   pReserved;
} SIGNER_SIGN_EX3_PARAMS;

If you’re curious about the methodology I use for figuring this out, I documented the process in the GitHub issue for APPX support. I rarely take the time to write down how I learned something, but for once I managed to think of my future self referring to it. Perhaps that is worthy of another post on another day.

Quirks

SignerSignEx3 with a signing callback seems to have one quirk: it cannot be combined with the SIG_APPEND flag, so it cannot be used to append signatures. This seems to be a limitation of SignerSignEx3, as signtool has the same problem when using /dlib with the /as option.

Conclusion

It’s a specific API need, I’ll give you that. However, combining this with Subject Interface Packages, Authenticode is extremely flexible. Not only what it can sign, but now also how it signs.

AzureSignTool’s source is on GitHub, MIT licensed, and has C# bindings.