Sherief, FYI

Websign

The Windows approach to ensuring the integrity of executable code is signing via Authenticode - the executable file is digitally signed, and the signing certificate can be traced back to a trusted root. Recently, however, the de facto trust on Windows and other platforms has moved to be TLS-based, not Authenticode based: consider how you install OpenAI’s Codex CLI:

powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"

or Claude Code:

curl -fsSL https://claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd

or Bun:

powershell -c "irm bun.sh/install.ps1|iex"

Piping a script into your shell allows it to do a lot of things - it can for example add an SSH key to your authorized_keys, which would be disastrous for security, but just like an Authenticode signed executable the trust comes from the source - Authenticode ties the executable code to an organization, the domain (and TLS) serve the same purpose when piping a script from the internet into your shell. While you can audit the script, you can (albeit with more effort) audit the executable - but most people don’t do either, since people base trust on brands and if we had to audit everything we wouldn’t get anything done so we trust these companies not to be malicious, for their own reputational and financial sake, and it works. Domain-based trust has existed for a very long time, and is one of the reasons Windows adds the ZoneInfo alternate NTFS data stream to identify where a file came from though that is easy to remove, tamper with, and cannot be securely verified.

Just like with an installer showing you a UAC prompt, it’s up to you whether to trust that publisher or not, and as shown above TLS has proven that it can serve that same purpose. The issue with TLS is that it validates the script at install time, but that script can still install unsigned binaries that are impossible to verify against origin and tampering. TLS guarantees integrity at install time, Authenticode guarantees integrity at rest.

Authenticode, however, hasn’t been very widely adopted compared to other solutions such as Apple’s Developer ID signing. A lot of Windows software is either unsigned or ships with unsigned DLLs, and comparing the code signing penetration to macOS reveals some interesting points: macOS signing certificates have an approachable barrier to entry: $99, an Apple ID, and no prohibitive requirements over key / cert storage and management. Authenticode on the other hand has much higher barriers on multiple axes: procedural, financial, and hardware - and the parties involved have incentive to increase those barriers across at least two of these axes. Security people in tech, usually oblivious to the social second and third order effects of security policies, seem to believe this makes signed code more secure while in reality what we end up with is lots of developers being excluded by the high friction and a user base conditions to approve UAC prompts from unsigned binaries and bypassing SmartScreen by clicking “Run anyway”. A lot of them seem to believe that a signing certificate not stored on a hardware token makes things less secure while the end result of their policy is less signed code overall. This situation is only bound to get worse at a faster pace as coding LLMs enable more people to write more code (regardless of the reader’s opinion on the quality or utility of said code - it will still get written). It’s possible with a budget of mere pennies for someone to turn their one paragraph idea into a native executable via Bun or a native DLL to, say, mod Quake 2 Remastered and neither of these people will bother with Authenticode in its current state.

Websign is an open standard that extends TLS-based trust to code at rest, ensures the ZoneInfo concept of tying an executable file to a domain can be cryptographically validated, and enables a signing infrastructure backed by domain certificates which lies in the same region on the security / convenience axes as LetsEncrypt TLS certificates. It comes with a reference commercial-friendly open source implementation in addition to Windows shell integration and command line tools to validate signatures. It can transparently coexist with Authenticode.

Websign works in two parts: the first by using an Ed25519 keypair to sign the hash of an executable (same hash used in Authenticode signatures), and embedding that in the PE File. For non-Authenticode-signed files a new entry is added in the PE security directory with wRevision = 0x0300 and wCertificateType = 0x0010. Inside that is a payload containing a magic number, signature, thumbprint of the JSON Web Key (JWK) containing the public part of the generated keypair, and a domain name. For Authenticode-signed files the signature is preserved by embedding this payload as a custom unauthenticated PKCS#7 attribute with OID 1.3.6.1.4.1.66218.1.1 - a Private Enterprise Number where 66218 is assigned to “Sherief Farouk”, the following .1 is dedicated to Websign and the last .1 specifies Websign version 1.

Part two verifies that this binary did come from the domain it claims in the payload: using the JWK thumbprint, a well-known URL is accessed to retrieve the public key used to verify the signature with the format https://<domain>/.well-known/webkeys/v1/<JWK thumbprint>. This MUST happen with TLS, and it is what guarantees that the executable did come from that domain - if the excutable is signed with a private key and the domain hosts its public counterpart then the owner of the domain’s TLS certificate is the person in control of the private key.

This lowers the barrier to PE signing to the point of simply having a DNS domain name and some static storage over HTTPS REST. Key storage is not specified - you can generate the keypair bytes or JWK file and use them for signing, or you can use a hardware token, or you can generate an ephemeral key at the time of signing and throw away the private part meaning the window for exploitation becomes very narrow, as opposed to keeping a signing certificate secure where the private artifact has a lifetime measured in months. The reference implementation includes support for signing PE files using ephemeral keys that do not get written to disk / swap and are securely zeroed from memory once signing is done.

Websign is available as a C library, libwebsign, which you can embed into your code to verify websigned binaries. The project also includes a set of command line tools you can use to generate keys, generate web payloads, verify signatures, and sign executables using pre-specified keys or ephemeral keys. This is intended for developrs, and can be found here.

For end users, Websign has an Explorer shell integration available that shows a property sheet displaying signature validation results: Websign property sheet in Windows shell showing the signature validation for an executable file

The code for the shell integration can be found here. Installers are available for x86 and x64.

I would like to thank Matías Goldberg and Adrian (CookiePLMonster) for valuable feedback during the development of this project.