By William Woodruff

Sigstore announced the general availability of its free and ecosystem-agnostic software signing service two weeks ago, giving developers a way to sign, verify and protect their software projects and the dependencies they rely on. Trail of Bits is absolutely thrilled to be a part of the project, and we spoke about our work at the inaugural SigstoreCon.

While the opportunity to speak about Sigstore is amazing, we don’t want to stop there. We think Sigstore is a critical and much-needed step towards accessible software signing, which has become a key component of software supply chain management and security.

Here are some of the ways Trail of Bits has contributed (and will continue to contribute!) to the overall growth of the Sigstore ecosystem. Strap in!

What exactly is Sigstore?

For those unfamiliar: Sigstore is a Linux Foundation project, with contributions from big tech companies and well-regarded academic institutions, focused on a simple mission: to enable code signing and verification for everyone.

How it goes about doing that is a little more complicated. Sigstore is composed of two core services:

  • Fulcio, a public root certificate authority that issues short-lived signing certificates for authorized identities and commits those certificates to a Certificate Transparency log;
  • Rekor, a transparency and timestamping service for signed artifacts, with strong integrity guarantees.

Together, Fulcio and Rekor allow programmers to mint short-lived signing certificates, commit to those certificates publicly (making it harder for an attacker to surreptitiously obtain a valid certificate), sign artifacts against those certificates, and then publicly commit to the resulting signatures (again, making it harder for a surreptitious attacker).

The two services use standard formats and protocols (x509v3 and CT) in order to be interoperable with pre-existing verification schemes and machinery. Because of this, Sigstore is already being slotted into pre-existing workflows: you can use gitsign today to sign git commits using Sigstore, without any modifications to git itself!

What makes Sigstore special?

Sigstore is basically a PKI ecosystem, but specialized for short-term signing certificates.

But what gives Sigstore its special sauce is identity: Fulcio is a root CA, but only for developer or machine identities. More precisely, Fulcio won’t let just any certificate signing request go through: requests must be accompanied by an OpenID Connect (OIDC) token, which attests to an intended identity. That identity is then baked into the signing certificate, allowing anybody to confirm that signature against that certificate.

When Fulcio receives an OIDC token (which is really just a JSON Web Token), it verifies it against the service it claims to be from using OIDC’s .well-known lookup protocol. A handful of services (with known-high-quality IdPs) are currently supported, among them:

  • GitHub: Individual email identities (corresponding to GitHub accounts) can be attested to, as can machine identities for workflow actions.
  • Google and Microsoft: Individual email identities are supported, including for non-service accounts. In other words: as long as you have a Google or Microsoft account you can attest to the email that’s been linked to it, even if that email is not controlled by Google or Microsoft.
  • Kubernetes: Cloud-based Kubernetes instances (e.g. those provided by AWS, Azure, and Google Cloud) can attest to their cluster identities.

This identity-first approach turns code-signing on its head: rather than manually establishing trust in the identity behind a public key (which is the norm with PGP-based code signing), a verifier takes a public identity that they trust and simply asks the public signing log whether that identity has signed for the artifact they’d like to use. The end result: strong cryptographic primitives by default, no more brittle key management (on either end), no more broken webs of trust, and a publicly-accessible transparency log that keeps all signing parties honest.

This model additionally enables powerful misuse-resistant techniques, like “keyless” signing: rather than holding onto long-lived signing keys, users can create a short-lived key, request a certificate for it from Fulcio, and discard it once all signing operations are done. The key never leaves memory and is never reused, drastically reducing the threat (and blast radius) of key theft.

How do I use Sigstore?

In the abstract, Sigstore’s “identity-first” model can be a little mind-bending. Here’s an example of how it’s used:

To get started, we’ll install sigstore-python, the official (and Trail of Bits maintained!) Python implementation of Sigstore:

$ python -m pip install sigstore

Once we have it installed, we can use it to sign a local file (you can sign anything, including Python packages or distributions for any language!):

$ python -m sigstore sign README.md
Waiting for browser interaction...
Using ephemeral certificate:
-----BEGIN CERTIFICATE-----
MIICwTCCAkegAwIBAgIUZr4/MflYaUb/SSw0CgNj+qLZDhMwCgYIKoZIzj0EAwMw
NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl
cm1lZGlhdGUwHhcNMjIxMDI4MTYzMjQzWhcNMjIxMDI4MTY0MjQzWjAAMHYwEAYH
KoZIzj0CAQYFK4EEACIDYgAEVBG9SWAO0pkbhrsKtDUN4Il5OK115yp+Ai5GiDYW
V1obpF1Ih+/NrtTDN+tdkop0T6Z8eotVjpnyrFpc4TbA6okIZ2eo6oFwRD3tn/mG
4BFPgm4O4Nvgih+f75M845c1o4IBSTCCAUUwDgYDVR0PAQH/BAQDAgeAMBMGA1Ud
JQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBRE3hH5uBNf4l/EDxedz0aNNAZX+zAf
BgNVHSMEGDAWgBTf0+nPViQRlvmo2OkoVaLGLhhkPzAjBgNVHREBAf8EGTAXgRV3
aWxsaWFtQHlvc3Nhcmlhbi5uZXQwLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRo
dWIuY29tL2xvZ2luL29hdXRoMIGKBgorBgEEAdZ5AgQCBHwEegB4AHYACGCS8ChS
/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3IAAAGEH3BI7gAABAMARzBFAiEAnrGB
RDQMHW26GT4H/nCvTBQ7RzBI3ix8rRewG6Bii10CIBnjNsSYBhNB77nNmAheoxxj
XQWJuQ4n2iQu9FB4AGeKMAoGCCqGSM49BAMDA2gAMGUCMQDaV/a8myBO5yKDBTvS
fM9ziqC1zOiDrXXg+k4lVg02idTHeukbUZTKsROzOsPSRfUCMCsp30CTXrJPBUfN
dCxmp44zCE7/yGkNCu+5waxPhOI7mXrfQ7FqzmZ0Z5cs9H/CiA==
-----END CERTIFICATE-----

Transparency log entry created at index: 6052228
Signature written to file README.md.sig
Certificate written to file README.md.crt

This just works™. Behind the scenes, Sigstore is:

    1. Creating a new local ephemeral keypair;

    2. Retrieving the OIDC identity token, either via an interactive OAuth2 flow or ambient credential detection;

    3. Submitting a Certificate Signing Request to Fulcio, combined with the OIDC token and a proof of possession for the private half of the ephemeral keypair;

    4. Receiving the SCT, Certificate, and intermediate chain from Fulcio, and verifying all three;

    5. Actually signing for the input, using the private key;

    6. Publishing the signature, the input’s hash, and the signing certificate to Rekor;

    7. Saving all necessary verification materials locally, for later distribution and verification.

The end result: for the input README.md, sigstore-python produces a README.md.crt containing the (PEM-encoded) x509 signing certificate, and a README.md.sig containing the (base64-encoded) signature.

We can then use any ordinary x509 inspection tool (like openssl x509) to inspect the certificate, and confirm that its extensions contain Sigstore-specific entries for the identity we signed with. Abbreviated to just the certificate’s extensions:

        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                Code Signing
            X509v3 Subject Key Identifier:
                96:96:0F:0F:FB:19:76:15:15:D8:82:BB:8A:04:07:14:E8:85:EA:DA
            X509v3 Authority Key Identifier:
                DF:D3:E9:CF:56:24:11:96:F9:A8:D8:E9:28:55:A2:C6:2E:18:64:3F
            X509v3 Subject Alternative Name: critical
                email:[email protected]
            1.3.6.1.4.1.57264.1.1:
                https://accounts.google.com
            CT Precertificate SCTs:
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : 08:60:92:F0:28:52:FF:68:45:D1:D1:6B:27:84:9C:45:
                                67:18:AC:16:3D:C3:38:D2:6D:E6:BC:22:06:36:6F:72
                    Timestamp : Oct 28 16:44:57.279 2022 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:44:02:20:1E:FB:5C:97:4D:BB:EC:A2:51:14:9A:A7:
                                FC:EB:59:9B:10:AA:37:5F:13:E0:D0:D3:ED:4D:3D:36:
                                18:E1:53:38:02:20:5C:67:61:F4:2E:15:3D:25:14:79:
                                7F:94:F7:5F:A2:9D:2F:15:71:B9:15:29:AF:7A:9F:3D:
                                09:77:3B:C1:5E:68

Of course, that’s just human inspection. To actually verify the file against its signing artifacts, we can use sigstore verify:

# this automatically discovers README.md.crt and README.md.sig
$ python -m sigstore verify \
    --cert-email [email protected] \
    --cert-oidc-issuer https://accounts.google.com \
    README.md
OK: README.md

Again, this just works™:

    1. We verify that the signing certificate (README.md.crt) was signed by the Sigstore certificate chain, linking it back up to the Fulcio root certificate;

    2. We check that the certificate’s SAN and issuer extension correspond to our expected identity (my email address, attested by Google’s IdP)

    3. We verify that the signature (README.md.sig) was produced from the public key attested by the signing certificate;

    4. We check Rekor for a record matching the current verification materials, and then check the resulting record’s Merkle inclusion proof and Signed Entry Timestamp signature;

    5. Finally, we confirm that the signature was integrated into Rekor at a time when the certificate was valid.

The last two Rekor steps can be additionally visualized through Chainguard’s excellent Rekor web interface:

Put together, these checks provide strong proof that someone with control over my email identity (i.e., me) signed for an artifact at a specific time, all without either me or the verifying party ever having to directly manage key material!

The bright present and future

Now that Sigstore is generally available, we can accelerate our plans to integrate it into ecosystems that currently lack a strong codesigning model. We’ve already made some progress on that, including:

  • CPython itself is now released with Sigstore signatures, created using our very own sigstore-python. You can verify them today, using the exact same sigstore verify command above!
  • A GitHub Action for signing artifacts and automatically attaching signatures to releases, all using GitHub Actions’ built in OIDC provider!

That said, there’s plenty of work that needs to be done:

  • sigstore-python needs plenty of work to reach a 1.0 stable release, including work toward stabilizing an importable Python API.
  • Critical UX work is needed to ensure that users understand what exactly they’re doing when they verify an identity’s signature.
  • As part of the Sigstore project’s overall commitment to availability and resiliency, we’re working on a conformance test suite that every independent client implementation of Sigstore is expected to pass. We’ll be working with each implementation in the coming months, helping them integrate it into their CI systems.
  • Sigstore is already being used successfully in many ecosystems, but we at Trail of Bits are particularly interested in its use on PyPI and eventual end use in package installers like pip. We’re actively working with the Python packaging community to bring Sigstore support to PyPI!

Overall, we think Sigstore has an incredibly bright future, and we’re excited to be a part of it. If you’re as excited about Sigstore as we are, then we’d love to hear from you!