Description
openedon Sep 12, 2024
Describe the bug
If you try and sign a clickonce manifest using a certificate B stored in Azure Key Vault, issued by a CA certificate A, certificate A must be installed on the local machine otherwise signing will fail. You'll get a stack trace like this:
fail: Sign.Core.ISigner[0]
Error occurred during a cryptographic operation.
System.ApplicationException: Error occurred during a cryptographic operation.
---> System.Security.Cryptography.CryptographicException: Error occurred during a cryptographic operation.
at System.Deployment.Internal.CodeSigning.SignedCmiManifest2.InsertPublisherIdentity(XmlDocument manifestDom, X509Certificate2 signerCert) in /_/src/Sign.Core/Native/mansign2.cs:line 421
at System.Deployment.Internal.CodeSigning.SignedCmiManifest2.Sign(CmiManifestSigner2 signer, String timeStampUrl, Boolean disallowMansignTimestampFallback) in /_/src/Sign.Core/Native/mansign2.cs:line 351
at Sign.Core.ManifestSigner.Sign(FileInfo file, X509Certificate2 certificate, RSA rsaPrivateKey, SignOptions options) in /_/src/Sign.Core/DataFormatSigners/ManifestSigner.cs:line 42
Looking at the code in mansign2.cs, it's trying to locate the public key of the certificate (A) that issued the certificate (B) that's being used to sign the manifest. This lookup is done using the local cert store, even if certificate A is stored in Azure Key Vault.
This scenario can easily happen if you have an company internal CA that issues a code signing certificate for internal applications which is then stored in a key vault. If you try and run sign
from e.g. an Azure DevOps hosted agent that doesn't have your company CA cert(s) installed on it, then the signing will fail.
Repro steps
# Generate certificate A
New-SelfSignedCertificate -KeyAlgorithm RSA -KeyLength 2048 -Subject "CN=TEST temporary CA" -CertStoreLocation Cert:\CurrentUser\my
# Generate certificate B. Set the signer thumbprint to the one of the CA cert generated above.
New-SelfSignedCertificate -KeyAlgorithm RSA -KeyLength 2048 -Subject "CN=TEST temporary code signing cert" -CertStoreLocation Cert:\CurrentUser\my -signer Cert:\CurrentUser\my\0E07DD3CF22224ED029C62151AEE55CAB5FD73E3 -KeyExportPolicy Exportable
Export certificate B to a .pfx file and upload it to an AKV. Delete certificate A from the Personal and Intermediate Certification Authority stores. You must ensure that when viewing cert B via the certificates mmc snapin that you get the message windows does not have enough information to verify this certificate
, indicating that it does not know the public key of B's issuer.
Now run sign.exe
pointing it at a clickonce .application manifest, using certificate B from your AKV. You should get the above exception.
Expected behavior
Honestly this is kind of the only possible behaviour - the clickonce manifest format requires the issuerKeyHash
property to be set, which is the SKID (public key hash) of certificate A. It's therefore impossible to generate a valid manifest without it. sign
should explicitly check for this though and report a better error than Error occurred during a cryptographic operation
.
Actual behavior
The above exception, with an unhelpful error message that requires you to read the code and perform the same experiment that I did in order to write this report.
Additional context
> sign --version
0.9.1-beta.24406.1+6584f5d081d8a06660d58d1a777b2352ff376a68