Code Signing Certificate Resource Code Signing Best Practices & Security Resources

How to Sign Windows Binaries Using AWS KMS & AWS Signer (Step-by-Step Guide)

Code signing is a non-negotiable requirement for distributing Windows executables. Whether you're publishing an installer, distributing internal enterprise tools, or deploying signed binaries for CI/CD, Windows requires Authenticode signatures to establish trust and prevent tampering.

But traditional code-signing workflows come with a major challenge: your private key must remain secure — yet legacy signing tools require storing a PFX file on build servers.

This is where AWS KMS + AWS Signer gives you a modern, cloud-secure way to sign Windows binaries without ever exposing your private key.

In this detailed guide, you’ll learn:

  • How AWS Signer works with KMS
  • How to prepare a signing certificate
  • How to create a signing profile
  • How to sign a Windows binary (EXE/MSI) end-to-end
  • How to verify the signature on Windows
  • CI/CD examples for GitHub Actions & CodeBuild

Let’s dive in.

Understanding Windows Code Signing in AWS

To sign a Windows binary, you need:

  • A private key – used to generate the digital signature
  • A signing certificate – identifies your organization
  • A timestamp – preserves validity after certificate expiration
  • A tool that produces an Authenticode signature

AWS solves this with two services:

Need AWS Service
Secure, non-exportable private key AWS KMS
Managed code signing workflow AWS Signer

AWS Signer creates a signing job, signs the binary, and returns a fully signed Windows executable—no crypto engineering needed.

Architecture: How AWS Signer Uses KMS

Here’s the simplified flow:

Developer / CI
     |
     | Upload unsigned.exe to S3
     v
AWS Signer  ----> KMS private key (non-exportable)
     |
     | Produces Authenticode signature
     v
Signed.exe → Output S3 bucket

You never access the private key — only AWS Signer calls KMS internally.

Prerequisites

Before signing Windows executables, you need:

1. A Windows Code-Signing Certificate

Options:

  • Import an External Code-Signing Certificate (from DigiCert, Sectigo, etc.)
  • Issue certificates internally via ACM Private CA
  • Use existing certificates in your organization

2. Create a Signing Profile in AWS Signer

A signing profile defines:

  • What platform you are signing for
  • What signing certificate to use
  • What hash algorithms are allowed

3. S3 Buckets

You need:

  • Input bucket → store unsigned binaries
  • Output bucket → store signed binaries

Step-by-Step: Signing Windows Binaries with AWS Signer

Step 1 — Create or Import a Signing Certificate

If you are using a certificate from DigiCert / Sectigo:

aws signer put-signing-profile \
    --profile-name WinCodeSignProfile \
    --signing-material certificateArn=arn:aws:acm:us-east-1:111122223333:certificate/abcd1234 \
    --platform-id "AWSWindowsCodeSigning"
  

If using ACM PCA, issue a certificate using the public key AWS Signer manages.

Step 2 — Create a Signing Profile

aws signer put-signing-profile \
    --profile-name WindowsCodeSign \
    --platform-id AWSWindowsCodeSigning \
    --signing-material certificateArn=arn:aws:acm:us-east-1:111122223333:certificate/abcd-1234 \
    --signature-validity period=365,type=DAYS
  

Confirm the profile exists:

aws signer list-signing-profiles
  

Step 3 — Upload the Unsigned Binary to S3

Example:

aws s3 cp MyApp.exe s3://my-code-signing-input/MyApp.exe
  

Step 4 — Start a Signing Job

aws signer start-signing-job \
    --profile-name WindowsCodeSign \
    --source s3={bucketName=my-code-signing-input,key=MyApp.exe} \
    --destination s3={bucketName=my-code-signing-output,prefix=signed/}
  

This returns a JSON response:

{
  "jobId": "123abcde-456f-7890-1122-334455667788"
}
  

Step 5 — Check Signing Job Status

aws signer describe-signing-job --job-id 123abcde-456f-7890-1122-334455667788
  

Look for:

"status": "Succeeded"
  

Step 6 — Download the Signed Windows Binary

aws s3 cp s3://my-code-signing-output/signed/MyApp.exe ./MyApp-signed.exe
  

Verifying the Signature on Windows

Using signtool.exe

signtool verify /pa /v MyApp-signed.exe
  

Expected output:

Successfully verified: MyApp-signed.exe
 Signer Certificate: CN=YourCompany
 Timestamp: Valid
  

Using PowerShell

Get-AuthenticodeSignature MyApp-signed.exe
  

Output example:

Status: Valid
SignerCertificate: CN=YourCompany
  

CI/CD Integration Examples

GitHub Actions Example

name: Sign Binary

on: [push]

jobs:
  sign:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Upload unsigned binary
        run: aws s3 cp build/MyApp.exe s3://my-code-signing-input/

      - name: Start signing job
        id: sign
        run: |
          JOB=$(aws signer start-signing-job \
            --profile-name WindowsCodeSign \
            --source s3={bucketName=my-code-signing-input,key=MyApp.exe} \
            --destination s3={bucketName=my-code-signing-output,prefix=signed/})
          echo "jobid=$(echo $JOB | jq -r '.jobId')" >> $GITHUB_OUTPUT

      - name: Wait for signing job to finish
        run: |
          aws signer wait successful-signing-job \
            --job-id ${{ steps.sign.outputs.jobid }}

      - name: Download signed binary
        run: aws s3 cp s3://my-code-signing-output/signed/MyApp.exe MyApp-signed.exe
  

AWS CodeBuild Example

buildspec.yml

version: 0.2

phases:
  build:
    commands:
      - aws s3 cp MyApp.exe s3://my-code-signing-input/
      - >
        JOBID=$(aws signer start-signing-job
        --profile-name WindowsCodeSign
        --source s3={bucketName=my-code-signing-input,key=MyApp.exe}
        --destination s3={bucketName=my-code-signing-output,prefix=signed/}
        --query jobId --output text)
      - aws signer wait successful-signing-job --job-id $JOBID
      - aws s3 cp s3://my-code-signing-output/signed/MyApp.exe ./MyApp-signed.exe

artifacts:
  files:
    - MyApp-signed.exe
  

Troubleshooting Common Issues

Issue Cause Fix
Certificate not trusted Certificate not from a trusted CA Use DigiCert/Sectigo or install internal CA root
Signature missing timestamp TSA not configured Use AWS Signer—they add RFC3161 timestamps
signing-profile not found Wrong profile name Run aws signer list-signing-profiles
AccessDenied IAM lacks permission Add signer:* & kms:Sign permissions

IAM Policies Required

Assign these roles to CI/CD or developer IAM roles:

Signer permissions

{
  "Effect": "Allow",
  "Action": [
    "signer:StartSigningJob",
    "signer:GetSigningProfile",
    "signer:DescribeSigningJob"
  ],
  "Resource": "*"
}
  

KMS permissions

{
  "Effect": "Allow",
  "Action": [
    "kms:Sign",
    "kms:GetPublicKey"
  ],
  "Resource": "arn:aws:kms:us-east-1:111122223333:key/*"
}
  

S3 permissions

{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:PutObject"],
  "Resource": "arn:aws:s3:::my-code-signing-*/*"
}
  

Best Practices for Secure Code Signing

  • Use separate profiles for dev, staging, production
  • Never store certificates or PFX locally
  • Use KMS key rotation (manual for asymmetric keys)
  • Restrict signer:StartSigningJob to CI/CD roles
  • Enable CloudTrail for auditing signing operations

Closing Thoughts

AWS Signer + KMS gives you a secure, scalable, and fully automated way to sign Windows binaries without ever exposing your private key, avoiding PFX distribution risks and strengthening your supply-chain security posture.

This workflow supports:

  • Automated CI/CD signing
  • Enterprise compliance
  • Cryptographic protection against tampering
  • Scalable multi-team usage
Delivery Mode Delivery Mode

FIPS-140 Level 2 USB or Existing HSM

Secure Key Storage Secure Key Storage

Stored on an External Physical Device

Issuance Time Issuance Time

3 to 5 Business Days