Skip to main content

    How to Verify a Code Signature (Windows, Linux & Java)

    Verify a signature with SignTool, PowerShell, osslsigncode or jarsigner — the exact commands, the exit codes, and what each verdict actually means.

    MS
    My-SSL Team
    ·
    15 min read
    ·
    Published August 2, 2026
    ·
    Last updated August 2, 2026

    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.

    The three checks behind a code signature verdict, and why reputation is not one of themThree boxes across the top feed a single verdict box below. The first box is integrity: the file hash still matches the hash recorded in the signature. The second, highlighted in gold, is trust: the signing certificate chains to a root the system trusts and has not been revoked. The third is time: either the certificate is still inside its validity window, or a timestamp proves it was valid at signing. All three must pass for a tool to report a verified signature. A fourth box, drawn separately in grey and outside the verdict, is publisher reputation, which Windows SmartScreen judges from download history and which no verification command reports on.One verdict, three separate questions1. IntegrityFile hash still matchesthe hash in the signature2. TrustChains to a trusted root,not revoked3. TimeInside validity, or atimestamp proves it wasVerified signatureall three must pass — any one fails, the verdict failsPublisher reputation (SmartScreen)judged from download history — no verify command reports it
    When a verification fails, work out which of the three boxes broke before touching anything — the fix for a hash mismatch has nothing in common with the fix for an untrusted chain. The dashed box below the line is the one people expect a verify command to answer, and it never does.

    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.

    ArtifactToolRuns onVerify command
    EXE, DLL, MSI, MSIX, CAB, CATSignToolWindowssigntool verify /pa /v file
    Any Authenticode file, plus .ps1 and .psm1PowerShellWindows onlyGet-AuthenticodeSignature file
    PE, MSI, CAB, CAT, APPX, signed scriptsosslsigncodeLinux, macOS, Windowsosslsigncode verify -in file
    JAR, WAR, EARjarsignerAnywhere with a JDKjarsigner -verify -certs file
    NuGet package.NET CLIAnywhere with .NETdotnet 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 hashes

    Three 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 codeMicrosoft's descriptionWhat to do with it
    0Execution was successfulShip it
    1Execution has failedStop the build and read the verbose output
    2Execution has completed with warningsHandle 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.

    StatusWhat PowerShell means by itUsual cause
    ValidSignature verifiedNothing to do
    NotSignedThe file is not digitally signedThe signing step never ran, or ran on a different copy of the file
    HashMismatchThe hash of the file does not match the hash stored in the signatureThe file was edited, re-encoded, or post-processed after signing
    NotTrustedSigned, but the signer is not trusted on this systemSelf-signed test certificate, a private CA, or a missing intermediate
    NotSupportedFileFormatSigning operations are not supported on this file typeThe format carries signatures differently, or has no extension
    IncompatibleThe signature is incompatible with the current systemCommonly 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.crt

    The 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: 1

    Timestamp 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.

    How jarsigner -strict combines severe warning codes into one exit codeFive severe warning codes are listed: 4 for an expired, not yet valid, self-signed or unvalidated certificate or a disabled algorithm; 8 for a KeyUsage or ExtendedKeyUsage extension that forbids code signing; 16 for unsigned entries in the JAR; 32 for entries signed by an unexpected alias or an alias missing from the keystore; and 64 for an invalid timestamp authority chain. Two of them, 4 and 8, are highlighted as triggered, and they combine by bitwise OR into a single exit code of 12.jarsigner -strict: severe warnings become the exit code4Expired, not-yet-valid, self-signed, unvalidated chain, or a disabled algorithm8KeyUsage or ExtendedKeyUsage forbids code signing16The JAR contains unsigned entries32Signed by an alias you did not ask for, or not in the keystore64The timestamp authority chain is invalidBoth triggered, OR-ed together:4 | 8exit code 12(without -strict: 0)
    The codes are bit values, so an unfamiliar number like 12 or 20 is telling you about two problems at once. Decompose it before you start debugging the wrong one.

    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.ps1

    Fingerprint 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.

    How a timestamp keeps a signature verifiable after the certificate expiresA horizontal time axis runs from the certificate's issue date, through the signing date, to the certificate's expiry date and then to today. In the upper lane the signature carries a timestamp, so the verifier sets its clock back to the signing moment, which falls inside the validity window, and verification succeeds. In the lower lane the signature has no timestamp, so the verifier uses today's date, which falls after expiry, and verification fails.Which clock the verifier usescertificate validity window (max 460 days)Timestampedsignedclock set back to the timestamp → verifiesNo timestampsignedclock stays on today → expired, failsissuedexpirestoday
    Nothing about the file changes between the two lanes — only which moment the verifier compares the certificate against. That is the whole reason a timestamp is worth the extra flag on every signing command.

    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.

    Gating a build pipeline on the exit code of a signature verification stepA pipeline runs from build to sign to a verify step. The verify step branches on its exit code: zero means the signature verified and the artifact is published; one means verification failed and the build stops; two, highlighted in gold, means SignTool completed with warnings, such as a signature with no timestamp, and is the case scripts most often handle incorrectly.The verify step is the gate — read its exit codeBuildSignVerify0Signature verifiedpublish the artifact2Completed with warningse.g. no timestamp — decide deliberately1Verification failedstop the build
    Exit code 2 is where pipelines quietly go wrong: a check written as "non-zero means broken" blocks a fine release, and one written as "anything but 1 is fine" ships an untimestamped signature.

    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.

    Frequently Asked Questions

    Get instant answers to common questions about SSL certificates and our services.

    Still Have Questions?

    Our SSL experts are available 24/7 to help with any questions about certificates, installation, or technical issues.

    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