Webinar: PKI Insights - Best Practices for Improving your PKI Posture Management Program for your Digital Certificates and Encryption by PKI Solutions
Schedule a Demo
Blog C#, Code Signing, PKI, PowerShell

AzureSignTool incorrectly identifies unsigned files as signed

by Vadims Podāns

Some time ago a fellow I’m following on a Twitter, Marc-André Moreau posted an interesting tweet:

Okay, this is the weirdest thing: we call AzureSignTool with ‘-s’ to skip signing already signed files, and it… incorrectly thinks one of our native DLLs is already code signed when it is not… for *both* x64 and arm64. Get-AuthenticodeSignature returns “NotSigned”

AzureSignTool is a modern open-source .NET-based replacement for legacy and discontinued Microsoft SignTool and adds Azure KeyVault support as code signing certificate storage. We use this tool to sign our binaries as part of DevOps build pipelines as well.

It is quite often when your build artifact contains already authenticode-signed (hereinafter signed) binaries that come from dependencies. It is recommended to not re-sign already signed binaries and keep original signatures from code owners. AzureSignTool includes –s switch that is supposed to check if the file being signed is already signed and skip if so, or attempt to sign otherwise. As I mentioned, Marc-André Moreau found that this switch detected unsigned file as signed and skipped signing as the result. It is very unfortunate and unacceptable in most cases, because you no longer guarantee code integrity. Let’s dig into the root cause and suggestions on how to overcome this problem.

AzureSignTool under the hood

Looking into AzureSignTool sources, we can find a private isSigned method that performs check if file is already signed and contains the following line:

var certificate =  new X509Certificate2(X509Certificate.CreateFromSignedFile(filePath));

This line uses X509Certificate.CreateFromSignedFile method to extract a signing certificate from the signature. This static constructor is as old as .NET itself (introduced in .NET 1.1) and I doubt it was ever revised since then. The method description is as follows:

Creates an X.509v3 certificate from the specified signed file.

The problem is with that “signed” word is quite misleading, because in fact it can mean anything, not only signed certificate. Details in next section.

X509Certificate.CreateFromSignedFile under the hood

Let’s dig into .NET sources and look the [relevant] call sequence that happens when X509Certificate.CreateFromSignedFile method is called:

And here our bus stop. This method calls CryptQueryObject black magic CryptoAPI function and passes CERT_QUERY_CONTENT_FLAG_ALL argument to dwExpectedContentTypeFlags parameter. And this is a problem. The CryptQueryObject function supports different cryptographic objects lookup in a file and in different places. It can find anything that can look like a requested cryptographic object in arbitrary file. This includes authenticode signature, embedded resources and content. That is, the function can extract the certificate even if it is stored as a content in a file.

And it is exactly what Marc-André experienced. They stored some kind of certificate with Code Signing EKU as embedded resource and it was extracted from X509Certificate.CreateFromSignedFile call, thus misleading the caller (AzureSignTool) as the result.

How to detect if file is really authenticode-signed?

As we learned, we cannot trust X509Certificate.CreateFromSignedFile to detect if file is authenticode-signed or not, and we need a better solution. I can quickly propose three viable solutions.

Solution 1 – update CryptQueryObject call

We can modify CryptQueryObject function call to pass CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED argument to dwExpectedContentTypeFlags parameter. I’m using this approach in my SysadminsLV.PKI.Win project as extension method to FileInfo type. This is the easiest and cheapest way reduce fault-positive signed status results. However, it is not bullet proof, still can fail. And remember, it doesn’t verify if signature is valid.

Solution 2 – use WinVerifyTrust function

The most reliable way to detect if the file is signed is to use WinVerifyTrust function. In fact, it can do both: verify if the file is signed and if the signature is valid (the signed file is not tampered). The downside of this approach is the complexity in properly populating WINTRUST_DATA structure, but yet doable.

Solution 3 – user Get-AuthenticodeSignature cmdlet

You can call Get-AuthenticodeSignature cmdlet against file to detect if needs signing. This cmdlet internally uses WinVerifyTrust function (see Solution 2) behind the scenes and is perfect for automation. For example, as part of our build pipelines, we have a step which executes custom PowerShell script and here is the excerpt from that script:

$excludedExtensions = @("*.pdb", "*.xml", "*.json", "*.txt", "*.config")
Get-ChildItem $(BuildFolder) -Exclude $excludedExtensions -Recurse -File | `
    Where-Object {(Get-AuthenticodeSignature $_.FullName).Status -ne "Valid"} | `
    Foreach-Object {
        azuresigntool sign -kvu $kvu -kvc $kvc -kvt $kvt -kvi $kvi -kvs $(kvs) -tr $tsa -td $hash $_.FullName
}

I recommend either, Solution 2 or 3 to reliably detect if file is signed or not and do actions based on signing status.

Happy automation with Windows PowerShell and AzureSignTool!

Related Resources

  • Blog
    March 7, 2024

    PKI Insights – Avoiding PenTest Pitfalls

    Certificates, PKI, PKI Insights
  • Blog
    July 17, 2023

    PKI Spotlight® now has over 90 Best Practice alerts with its latest release.

    PKI
  • Blog
    June 2, 2023

    Digital Trust and IT Security: Empowering Your Organization

    PKI

Vadims Podāns

PKI Software Architect

View All Posts by Vadims Podāns

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *