Friday, January 31, 2025
Signing Windows binaries using AWS KMS

Tobias Lønnerød Madsen
Moonbase Founder
Tobias is the technical founder of Moonbase, with a long history of building e-commerce for software companies.
Before selling your new application, you need to make sure customers will be able to install it, no matter what device they have. And on Windows devices, signing the binaries is the best way to gain trust, and for enterprise customers; a requirement to even install it.
What happens if I don't sign?
If you try running or installing unsigned binaries on Windows, you'll most likely be greeted by these warnings:

Depending on whether or not you have admin privileges on your device, you can choose to still run the software, and accept the risk. This is a tactic from Microsoft to reduce the amount of malware being installed, requiring an authenticated source for all software.
If your software is targeted towards business users, it's even more important, as enterprise group policies might lock down devices to the point where installing or using unsigned software is not possible at all.
So signing your software with a certificate from your business is critical to be taken seriously. To do this, you need an EV (Extended Validation) Code Signing certificate from a certificate authority to use in your build process.
Actually, you can also get away with a "normal" OV certificate, but you will likely face slightly less scary Windows Defender warnings for the first couple thousand installs per certificate. These certificates have the same storage restrictions as described in this article, but less stringent registration requirements. What we heard from developers is that renewals of these will start the warnings again for some time, until it's a "trusted" certificate again.
Signing software practically means using a secret key that you control, together with a certificate bound to the key to add an authenticated signature to the binary blob. This signature is checked by Windows against known certificate authorities.
Gone are the days of USB dongles
Over time, the method for getting and using certificates have changed a lot. It used to be that the only way of getting keys and certificates for code signing was via USB dongles, ones that acted as a so-called HSM (Hardware Security Module), to protect the certificate and key pair, restricting any unauthorized usage or break-ins. Before that, you could even download it straight from the certificate authority, using locally installed private keys.
Starting in 2023, a new requirement came online that mandated storing all keys for code signing certificates in FIPS 140 Level 2+ certified devices, as another way to reduce signing material leaks.
These two facts, combined with the trend of building more software in automated pipelines have brought up new challenges of signing code in the cloud; where do you store the key pair? You're not gonna plug a USB dongle into GitHub Actions, so how do we sign using our certificates?
The age of cloud signing
The answer to this is of course to store the keys in a cloud-based HSM that can sign our binaries on-demand in a secure fashion. This means that we can have un-attended builds of our applications running in any automated build pipeline you can imagine.
Many certificate authorities offer services for this themselves, but it has historically been quite expensive, especially if you have frequent builds with many signatures a day.
Then Microsoft came along when they in 2024 introduced Trusted Signing, an Azure based managed signing service with a killer price tag and easy usage. It's arguably the best choice for signing software in the cloud today, but it does have some requirements for you to enroll in the service. Since Microsoft issues certificates themselves, they need to validate your business, part of which requires a full 3 years of tax history at the time of writing. This means it's not usable for newly started businesses yet.
Let me be clear; if you meet the requirements, Azure Trusted Signing is probably the best option for you. If you don't, then read on for our recommended alternative.
Using AWS KMS for cloud signing
What we want to do is to have all the same building blocks offered by Azure, but with our own certificate and cloud infrastructure. You might be scared off, but it's not as complicated as you might think, nor extremely expensive. The most expensive part is buying an EV certificate, which typically goes for 400+$ per year, an order of magnitude more than the 9.99$/month of Azure Trusted Signing.
Like mentioned earlier, you can opt for a cheaper OV certificate, but given the tradeoffs in credibility and usability, we think it's not worth it.
Getting the certificate
Given the requirements about secure storage for keys, we cannot chose any random place to store them, but AWS KMS has been FIPS 140 Level 3 certified since 2023. This means we can tell the certificate vendor we are purchasing from, that we will be placing the keys in a certified key store.
The way this works is that we will create a key pair in our KMS instance, and generate a CSR, a Certificate Signing Request. This key pair will be locked to KMS, and you will never be able to extract the private key from the instance, and through the CSR, the certificate authority will issue a certificate identified by the public key of the KMS pair. Using KMS APIs, we will sign the binary, keeping the private key safe.
Start by acquiring your EV code signing certificate from any vendor. During purchase, you should pick the option for customer-provided HSM storage. You will get the question of what make and model the store is, of which you can answer "Amazon Web Services Key Management Service". Once ready for pickup, we can give them our CSR which we can create next.
Crafting the CSR
How to set up an AWS account with credentials along with the CLI is out of scope for this article, but there are many tutorials for this online.
To begin, create the key pair in KMS, this can be done through the console, or using the AWS CLI as we show in this article.
aws kms create-key \ --key-spec RSA_4096 \ --key-usage SIGN_VERIFY
It's important to choose at least 4096 bits of RSA to be compliant with code signing requirements from Microsoft. To make our lives easier, we'll also give an alias to the newly created key:
aws kms create-alias \ --alias-name alias/code-signing \ --target-key-id <id of the new key from previous command>
From now on, we can reference the key using the ID alias/code-signing
.
Normally, to create a CSR, you need direct access to your private key, but with some community provided tools, we can use a dummy key-pair and simply replace the relevant public key and signature of a CSR with the relevant ones from KMS. Start by making the CSR:
openssl req -new -nodes \ -keyout /dev/null \ -newkey rsa:2048 \ -out code-signing-request.temp.csr
We've found the easiest tool to use being aws-kms-sign-csr, which can be set up using the following commands:
git clone git@github.com:g-a-d/aws-kms-sign-csr.git cd aws-kms-sign-csr # From the readme of the tool: python3 -m venv aws-kms-sign-csr . aws-kms-sign-csr/bin/activate pip3 install -r requirements.txt
With the script ready to use, we can sign the intermediate CSR with the keys from KMS:
./aws-kms-sign-csr.py \ --region eu-west-1 \ # Replace with your region --keyid alias/code-signing \ --hashalgo sha256 \ ../code-signing-request.temp.csr > ../code-signing-request.csr
The output of this is a code-signing-request.csr
with the same public key as is in KMS. If you want to double check, run the following:
# This prints the public key used to sign the CSR openssl req -in code-signing-request.csr -noout -pubkey \ | openssl pkey -pubin # This prints the public key of your KMS key aws kms get-public-key \ --key-id alias/code-signing \ --output text --query PublicKey
You may now use this code-signing-request.csr
file to pick up your certificates from your certificate authority, to use in the next steps.
Start signing binaries
At this point, we have:
- A key pair in AWS KMS
- A certificate from the CA that is tied to the KMS key
Which is all we need to start signing!
The path ahead depends on the binaries you are signing, which tools you need to use, for this example we'll use Microsofts SignTool. Normally, SignTool uses the private key directly to sign binaries, and while Microsoft has made a library to use it with Azure services, the same can not be said for Amazon and their KMS. This means we have to split up the process of signing into a couple of steps:
- Create a digest of the binary to sign
- Sign the digest using the AWS CLI
- Use the signature signed digest to sign the binary
- (Optional but recommended) Add a timestamp
Say we have a Application.exe
file to sign, we would do:
# Create digest, will produce .dig and .p7u files signtool.exe sign -dg . \ -fd sha256 -f ./certificate.cer \ Application.exe # Sign the digest and store result in .dig.signed aws kms sign \ --message $(cat Application.exe.dig) \ --message-type DIGEST \ --signing-algorithm "RSASSA_PKCS1_V1_5_SHA_256" \ --key-id alias/code-signing \ --output text \ --query "Signature" > Application.exe.dig.signed # Use the signed digest in the signature signtool.exe sign -di . Application.exe # Add timestamp signtool.exe timestamp \ -tr "http://timestamp.acs.microsoft.com" \ -td sha256 \ Application.exe
And that's all there is to it! You should now have a signed executable, ready for distribution to customers, all usable in the cloud or on your local machine.
Cost comparison
It might be useful to compare the cost between a couple of options:
Option 1: Azure Trusted Signing
$120 / year with 5,000 signatures per month included
$0.005 per extra signature
Total monthly cost for 1,000 signatures: $9.99
Option 2: AWS KMS
$~400 / year for the certificate depending on CA
$12 / year to store the key in KMS with 20,000 signatures a month included
$0.15 per 10,000 additional signatures
Total monthly cost for 1,000 signatures: $34.3
Option 3: SSL.com + eSigner
$349 / year for the certificate
$900 / year for 120 signatures total - or -
$2,700 / year for 1,200 signatures total - or -
$6,300 / year for 12,000 signatures total
Total monthly cost for 1,000 signatures: $554
More resources
The community is still picking up the steam on cloud signing, but there's some really helpful articles around:
Start selling through Moonbase
Sign up today to get started selling your software through Moonbase.