The short answer
Verify a Windows signature with signtool verify /pa /v yourfile.exe, or with Get-AuthenticodeSignature in PowerShell. On Linux and macOS, use osslsigncode verify -in yourfile.exe; for a signed JAR, use jarsigner -verify -verbose -certs app.jar. The /pa flag matters more than anything else on this page: without it SignTool applies the Windows Driver Verification Policy and can report a perfectly good application signature as a failure.
On this page
What does verifying a code signature actually check?
Verification answers three separate questions at once: has the file changed since it was signed, does the signing certificate chain to a root this machine trusts, and was the certificate valid at the moment of signing. A tool prints one verdict, but that verdict is those three results ANDed together. When verification fails, the useful first move is working out which of the three broke, because the fixes have nothing in common.
Microsoft states the second and third checks plainly in the SignTool reference: the verify command "determines whether the signing certificate was issued by a trusted authority, whether the signing certificate has been revoked, and, optionally, whether the signing certificate is valid for a specific policy." Revocation is part of it, which is worth remembering when you verify on an air-gapped build agent and the revocation lookup cannot reach the network.
Which tool you reach for depends on the artifact and the operating system you are standing on, not on which certificate you bought.
| Artifact | Tool | Runs on | Verify command |
|---|---|---|---|
| EXE, DLL, MSI, MSIX, CAB, CAT | SignTool | Windows | signtool verify /pa /v file |
| Any Authenticode file, plus .ps1 and .psm1 | PowerShell | Windows only | Get-AuthenticodeSignature file |
| PE, MSI, CAB, CAT, APPX, signed scripts | osslsigncode | Linux, macOS, Windows | osslsigncode verify -in file |
| JAR, WAR, EAR | jarsigner | Anywhere with a JDK | jarsigner -verify -certs file |
| NuGet package | .NET CLI | Anywhere with .NET | dotnet nuget verify --all file |
How do you verify a signature with SignTool?
Run signtool verify /pa /v MyApp.exe from a Developer Command Prompt. The /pa flag selects the Default Authentication Verification Policy, which is the Authenticode policy ordinary applications are signed under. Microsoft's documentation is direct about the alternative: "If the /pa option isn't specified, SignTool uses the Windows Driver Verification Policy."
That single sentence explains a surprising share of the "my signing failed" reports we see. A developer signs an installer, verifies it with a bare signtool verify MyApp.exe, gets an error, and starts re-issuing certificates. Nothing was wrong with the signature. SignTool was holding it to driver rules.
signtool verify /pa /v MyApp.exe
signtool verify /pa /v /all MyApp.exe REM every signature, not just the first
signtool verify /pa /v /tw MyApp.exe REM warn when there is no timestamp
signtool verify /pa /v /ph MyApp.exe REM also print and verify page hashesThree of those flags earn their place in a release script. /all matters for dual-signed binaries, where checking only the first signature hides a broken second one. /tw turns a missing timestamp into a warning instead of a silent pass, and a missing timestamp is the defect that will not show up until the certificate expires. /v gives you the chain, the serial number, and the timestamp details you will want in the build log when something goes wrong months later.
One behaviour worth knowing before you debug a confusing result: SignTool reports on the file's embedded signature unless you pass an option that sends it looking through catalog databases, such as /a, /ad, /as or /c. Windows components are frequently catalog-signed rather than embedded-signed, so verifying a system file without /a can report nothing at all.
| Exit code | Microsoft's description | What to do with it |
|---|---|---|
| 0 | Execution was successful | Ship it |
| 1 | Execution has failed | Stop the build and read the verbose output |
| 2 | Execution has completed with warnings | Handle explicitly — with /tw, a signature with no timestamp lands here |
How do you check a signature in PowerShell?
Get-AuthenticodeSignature MyApp.exe returns an object whose Status property carries the verdict, and whose SignerCertificate and TimeStamperCertificate properties carry the certificates themselves. The cmdlet is Windows-only, in PowerShell 7 as much as in Windows PowerShell. Because it hands back objects rather than text, it is the right tool for checking a whole output directory at once.
# One file, with the signer and the timestamp
Get-AuthenticodeSignature .\MyApp.exe | Format-List Status, StatusMessage, SignerCertificate, TimeStamperCertificate
# Every binary in a release folder that is not properly signed
Get-ChildItem .\release -Include *.exe,*.dll -Recurse |
Get-AuthenticodeSignature |
Where-Object { $_.Status -ne 'Valid' }
# Anything signed but missing a timestamp — the silent time bomb
Get-ChildItem .\release -Filter *.exe |
Get-AuthenticodeSignature |
Where-Object { $_.Status -eq 'Valid' -and -not $_.TimeStamperCertificate }That last query is the one worth adding to a release checklist. A file can be signed, valid, and still carry no timestamp, and no dialog anywhere in Windows will tell you so until the certificate expires and users start seeing warnings on a build you shipped a year ago.
The Status values are a small, fixed set, and each one points at a different part of the problem.
| Status | What PowerShell means by it | Usual cause |
|---|---|---|
| Valid | Signature verified | Nothing to do |
| NotSigned | The file is not digitally signed | The signing step never ran, or ran on a different copy of the file |
| HashMismatch | The hash of the file does not match the hash stored in the signature | The file was edited, re-encoded, or post-processed after signing |
| NotTrusted | Signed, but the signer is not trusted on this system | Self-signed test certificate, a private CA, or a missing intermediate |
| NotSupportedFileFormat | Signing operations are not supported on this file type | The format carries signatures differently, or has no extension |
| Incompatible | The signature is incompatible with the current system | Commonly a hash algorithm the machine's policy rejects |
HashMismatch deserves a note, because it is almost never an attack. In practice it means something touched the file after the signing step: an installer builder that rewrites the binary, a resource editor that stamps a version number, a text editor that saved a signed .ps1 in a different encoding. Signing has to be the last thing that happens to an artifact.
How do you verify a Windows signature on Linux or macOS?
Use osslsigncode verify -in MyApp.exe. It reads Authenticode signatures on PE, MSI, CAB, CAT and APPX files without needing Windows, and it exits 0 when the signature verifies and 1 when it does not. The one thing it cannot inherit is a trust store, so point it at one with -CAfile — otherwise the chain check has nothing to chain to.
osslsigncode verify \
-in MyApp-signed.exe \
-CAfile /etc/ssl/certs/ca-certificates.crt \
-TSA-CAfile /etc/ssl/certs/ca-certificates.crtThe output is unusually honest about what it did, and reading it line by line is faster than guessing. A successful run tells you the moment it judged the certificate against, that the chain built, that the timestamp itself verified, and how many signatures it found:
Signature verification time: ...
Signing certificate chain verified using:
...
Timestamp Server Signature verification: ok
Signature verification: ok
Number of verified signatures: 1Timestamp is not available in place of that timestamp line is the result to care about. It means the signature will verify today and stop verifying the day the certificate expires. Two other flags are worth knowing: -CRLfile supplies revocation lists when the build agent cannot fetch them over the network, and -require-leaf-hash pins the expected signing certificate by hash, which turns "some trusted certificate signed this" into "our certificate signed this". That distinction matters more than it sounds: without pinning, any publicly trusted code signing certificate passes.
On macOS, note that this verifies Windows Authenticode signatures, not Apple's own code signing. A macOS app bundle is a different trust system with different tooling.
How do you verify a signed JAR?
Run jarsigner -verify -verbose -certs MyApp.jar. That prints each entry alongside the certificate that signed it, which is how you catch a JAR where only some entries are covered. Then add -strict, because without it jarsigner is generous: a JAR signed by an expired, self-signed, or unvalidated certificate still exits 0.
jarsigner -verify -verbose -certs MyApp.jar
jarsigner -verify -strict MyApp.jar
echo $?With -strict, jarsigner's severe warnings stop being advisory and become the exit code — specifically the bitwise OR of every severe warning it raised. Oracle's documentation gives the example directly: a certificate that has expired (code 4) and carries a KeyUsage extension that does not allow code signing (code 8) makes jarsigner exit 12.
Code 4 is the crowded one, covering an expired certificate, a not-yet-valid certificate, a self-signed certificate, a chain that could not be validated, and disabledAlg — an algorithm the JDK now considers a security risk. Legacy JARs signed with SHA-1 land in that last case, and the report reads as a warning rather than an error, which is why they survive in build pipelines for years. The jarsigner signing guide covers what to re-sign with.
One more distinction that catches people migrating between stacks: jarsigner and SignTool implement different trust systems. Windows knows nothing about a JAR signature, and Java knows nothing about Authenticode. Shipping both kinds of artifact means signing twice, usually with the same certificate.
What about NuGet packages, MSIX, and scripts?
dotnet nuget verify --all MyPackage.nupkg checks a NuGet package's signatures, and --certificate-fingerprint pins the SHA-256 fingerprint of the certificate the package must be signed with. MSIX, MSI, CAB, catalog files and signed PowerShell scripts all go through SignTool with the same /pa /v pair. There is no single universal verify command, because each format stores its signature somewhere different.
dotnet nuget verify --all MyPackage.1.4.0.nupkg
dotnet nuget verify MyPackage.1.4.0.nupkg --certificate-fingerprint CE4088...C4E039
signtool verify /pa /v Setup.msi
signtool verify /pa /v Deploy.ps1Fingerprint pinning is the underrated one here. Verifying that a package is signed proves someone with a valid certificate signed it; verifying the fingerprint proves it was your certificate. If you consume internal packages across teams, pinning is what stops a legitimately signed but wrong package from passing the gate.
Does verification fail once the certificate expires?
Not if the signature was timestamped. A timestamp records when the signing happened, and the verifier then judges the certificate as of that moment instead of as of today. Without a timestamp, the same signature stops verifying on the day the certificate expires, and every copy already installed on users' machines fails with it.
You can watch this happen in the tooling rather than take it on faith. When osslsigncode finds a valid timestamp, it sets the verification clock to that moment and prints it as the Signature verification time line before it builds the chain. The certificate is checked against that date. Remove the timestamp and the clock stays on today, which is exactly when an expired certificate starts failing.
This used to be a slow-moving problem, because code signing certificates could run for three years. As of August 2, 2026 it is not: certificates issued on or after March 1, 2026 are capped at 460 days under the CA/Browser Forum's code signing requirements, so an untimestamped signature now has a shelf life measured in months rather than years. Any build script that signs without /tr is writing a dated cheque. The timestamping guide covers the signing side, including which timestamp URLs to use.
There is a limit to what a timestamp rescues. It preserves a signature past expiry; it does not survive revocation. If a certificate is revoked for key compromise, verifiers can reject signatures made before the revocation date too, because the assumption that only you held the key no longer holds.
Why does a file verify but still show "Unknown Publisher"?
Because verification and reputation are separate systems. SignTool can report a valid Authenticode signature while Windows SmartScreen still warns, since SmartScreen weighs how much download and run history Microsoft has seen for that publisher and that file. A certificate issued last week has no history yet, whatever validation level it was issued at.
This is the single most common expectation mismatch we deal with after a code signing purchase. The certificate works, the signature verifies, the warning is still there — and the instinct is that something was configured wrong. Nothing was. Reputation accrues from telemetry over time, and EV certificates tend to accrue it faster rather than skipping the process. Anyone promising that a certificate removes SmartScreen warnings on day one is describing a behaviour Microsoft does not document.
A genuine NotTrusted result is a different animal, and it has three usual causes. The certificate is self-signed, which is fine on your own test machine and useless to anyone else. The certificate came from a private CA whose root is not in the public trust stores. Or the intermediate certificate is missing from the signature, so the chain has a hole in it — the case worth checking first, because it is the one where the file is fine and only the signing step needs fixing.
If the goal is a signature that verifies on machines you do not control, the chain has to reach a root those machines already trust, which means a certificate from a publicly trusted CA. My-SSL issues Standard and EV code signing certificates through Certum, and the Unknown Publisher explainer walks through what actually shortens the warning period.
How do you gate a build on signature verification?
Read the exit code, not the log text. SignTool returns 0 for success, 1 for failure and 2 for completed-with-warnings; osslsigncode returns 0 or 1; jarsigner returns 1 on failure and, with -strict, the OR of its severe warning codes. Parsing stdout for the word "ok" breaks the first time a tool changes its wording, and it breaks silently.
SignTool's exit code 2 is where pipelines go wrong in both directions. A step written as "fail on anything non-zero" blocks a release over a warning. A step written as "fail only on 1" waves through the exact case /tw exists to catch. Decide deliberately which warnings you accept, and write the comparison that says so.
# PowerShell: treat warnings as failures, and say why
signtool verify /pa /v /tw $artifact
switch ($LASTEXITCODE) {
0 { Write-Host "Signature verified" }
2 { throw "Verified with warnings (likely no timestamp) - refusing to publish" }
default { throw "Signature verification failed (exit $LASTEXITCODE)" }
}Two habits make the gate worth having. Verify the artifact you are about to publish, not the one sitting in the build directory, because the interesting failures happen during packaging and copying. And verify on a machine that is not the signing machine: the signing host trusts its own certificates and its own intermediates, so it is the one place a missing intermediate will never show up. Our CI/CD code signing guide covers where the verify step sits in a full pipeline.
Signing something users will download?
A signature only verifies on other people's machines if it chains to a root those machines already trust. My-SSL issues code signing certificates through Certum, a publicly trusted CA, in both cloud and hardware form. Compare the options on the code signing page — then add /tw to your verify step so a missing timestamp can never reach a release.
Related reading
- How to sign an EXE with SignTool — the signing side of the same command, including the flags that decide what you will be verifying later.
- Code signing timestamping — why the timestamp is the part of a signature that outlives the certificate.
- Code signing in CI/CD — where a verify step belongs in an automated pipeline.