gradle / gradle

Adaptable, fast automation for all
https://gradle.org
Apache License 2.0
16.67k stars 4.66k forks source link

sigstore: support signing and verification artifacts via transparency log #19748

Open vlsi opened 2 years ago

vlsi commented 2 years ago

Expected Behavior

Gradle should be able to sign and verify artifact signatures. Of course, it would take time to migrate off PGP for Maven Central, however, having off the shelf sigstore support in Gradle would be awesome for both Gradle users, and for their consumers.

There are multiple ways to skin sigstore, however, I believe it would be awesome to start with the following three directions (feel free to comment):

Signing the artifacts with ephemeral OAuth-based certificates

Long story short, https://github.com/sigstore/fulcio is comparable to "Let's Encrypt for code signing".

Suppose I am sitnikov.vladimir@gmail.com, and I want to release a new version of pgjdbc. My steps would be:

  1. Generate keypair for signing purposes
  2. Ask Fulcio for a signing certificate. Here's the point where they challenge for OAuth (i.e. open a link in browser)
  3. Sign artifacts with the private key part
  4. Upload signatures and the certificate to the transparency log https://github.com/sigstore/rekor
  5. (optional) delete a keypair from step1

These scripts help understanding: https://gist.github.com/bobcallaway/dcd1c1629fa2422f34f60390efac54b2#file-sigstore-demo-script-sh , https://martinheinz.dev/blog/56 The same thing in Java (I find it is better to read bash scripts above, and only then follow Java-based signing): https://github.com/sigstore/sigstore-maven-plugin/blob/d7439b9734f3d68e09b638bdce6f66afc43d30d6/src/main/java/dev/sigstore/plugin/Sign.java#L199-L219

The outcome is: a) Signatures for the artifacts b) Signing certificate. It is an attestation from Fulcio that the signer did own the private key part and the given email id at the time of signing c) Records in transparency log. The certificate and signatures can be queried based on artifact SHA.

The signatures can be published to Central like the current .asc signatures (I'm not sure what are the limitations on the file names though). The certificate is a bit more complicated. The simplest approach would be upload two files for each artifact: signature and the certificate (even though the certificate would be exactly the same for all the artifacts).

An alternative storage format could be $artifactName-$version-sigstore.json payload that includes the certificate and the signatures for all the other artifacts. It would reduce the clutter, however, people won't be able to validate the signatures using the default openssl-like tools.

The certificate does not depend on the artifact, so it would be weird to duplicate the certificate for each uploaded artifact (e.g. sources, javadocs, binary jar, etc).

Ephemeral certificates have good properties: a) They can be verified offline (PGP signatures can't be verified alone, and the verifier needs to grab keys from far from perfect keyservers) b) Certificate has a timestamp, and there are no moot cases like "is the signature still valid if PGP key has expired?" c) Revocation of stolen keys is much easier: it is basically a non-issue. With PGP you have a hard time figuring out if the key has been revoked or whatever. With sigstore, the certificates are valid for ~20 minutes only, so there's no way they can be d) Email identity in the certificate is verified. In PGP everyone can generate, sign, and publish to Central a signature with any email which might easily be misleading

Verifying sigstore certificates

In a nutshell, it would be more-or-less like

  1. Expect that pgjdbc.jar should be signed by sitnikov.vladimir@gmail.com or another mail listed at the official website
  2. Verify that the certificate has a path to the official Sigstore root certificate
  3. Ensure the signing certificate is present in the transparency log.

The Update Framework (TUF)

https://theupdateframework.io/ I am not sure it belongs here, however, it looks like the next step after the basic signing is implemented. As far as I understand, TUF might be a superset of the current Gradle's dependency verification feature: https://theupdateframework.github.io/specification/latest/#goals-to-protect-against-specific-attacks

Current Behavior

Gradle supports PGP signatures only :(

Context

https://www.sigstore.dev/ https://martinheinz.dev/blog/56 https://blog.chainguard.dev/a-fulcio-deep-dive/ https://github.com/sigstore/cosign#keyless https://github.com/sigstore/sigstore-maven-plugin

https://words.filippo.io/giving-up-on-long-term-pgp/

In case you wonder, fulcio generates the following certificate:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            d4:b3:dc:e9:6c:4b:29:c7:e8:80:f0:f4:2e:a2:e4:76:6a:cc:f1
    Signature Algorithm: ecdsa-with-SHA384
        Issuer: O=sigstore.dev, CN=sigstore
        Validity
            Not Before: Jan 28 09:43:05 2022 GMT
            Not After : Jan 28 10:03:04 2022 GMT
        Subject:
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (384 bit)
                pub:
                    04:e9:fb:e1:3b:4b:84:ef:79:2a:22:82:c4:86:34:
                    93:a4:81:be:9b:58:4b:74:22:2d:2d:56:9d:b9:bf:
                    b3:f2:c6:d0:b4:38:69:d3:34:f6:59:60:8e:6c:da:
                    b4:e7:a5:af:b1:93:19:3e:cb:2e:29:39:a8:0a:8e:
                    b6:d0:fa:28:5c:57:77:5e:e9:bb:2a:99:1f:d1:ff:
                    97:e1:2f:2b:78:d3:2f:b3:3d:62:98:48:ac:02:c3:
                    12:7d:45:19:fb:8c:f2
                ASN1 OID: secp384r1
                NIST CURVE: P-384
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                Code Signing
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier:
                95:2E:3A:95:D5:71:28:DB:3F:8A:89:83:08:24:EE:95:7B:47:B5:DD
            X509v3 Authority Key Identifier:
                keyid:C8:C5:1D:00:41:9A:24:29:32:51:24:EB:0D:AE:4A:ED:4A:06:D3:EC

            Authority Information Access:
                CA Issuers - URI:http://privateca-content-603fe7e7-0000-2227-bf75-f4f5e80d2954.storage.googleapis.com/ca36a1e96242b9fcb146/ca.crt

            X509v3 Subject Alternative Name: critical
                email:sitnikov.vladimir@gmail.com
            1.3.6.1.4.1.57264.1.1:
                https://accounts.google.com
    Signature Algorithm: ecdsa-with-SHA384
         30:64:02:2f:7c:b0:5e:a7:af:ee:a2:e8:24:aa:a6:7e:62:92:
         3c:c6:94:51:89:33:22:23:8f:73:17:73:a2:d8:f3:c4:50:91:
         71:57:7d:97:2c:72:01:a0:e3:f6:6a:75:b6:b6:79:02:31:00:
         c3:86:86:5c:05:c2:c4:aa:33:fb:eb:4d:f8:68:98:25:24:68:
         4b:28:57:55:06:2a:e0:4a:c6:55:71:85:60:45:46:f8:99:2a:
         8f:42:54:3b:96:9e:f2:7f:88:e0:4c:0b
-----BEGIN CERTIFICATE-----
MIICxzCCAk6gAwIBAgIUANSz3OlsSynH6IDw9C6i5HZqzPEwCgYIKoZIzj0EAwMw
KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
MjAxMjgwOTQzMDVaFw0yMjAxMjgxMDAzMDRaMAAwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAATp++E7S4TveSoigsSGNJOkgb6bWEt0Ii0tVp25v7PyxtC0OGnTNPZZYI5s
2rTnpa+xkxk+yy4pOagKjrbQ+ihcV3de6bsqmR/R/5fhLyt40y+zPWKYSKwCwxJ9
RRn7jPKjggFdMIIBWTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH
AwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUlS46ldVxKNs/iomDCCTulXtHtd0w
HwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG0+wwgY0GCCsGAQUFBwEBBIGA
MH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRlY2EtY29udGVudC02MDNmZTdl
Ny0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQuc3RvcmFnZS5nb29nbGVhcGlz
LmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5jcnQwKQYDVR0RAQH/BB8wHYEb
c2l0bmlrb3YudmxhZGltaXJAZ21haWwuY29tMCkGCisGAQQBg78wAQEEG2h0dHBz
Oi8vYWNjb3VudHMuZ29vZ2xlLmNvbTAKBggqhkjOPQQDAwNnADBkAi98sF6nr+6i
6CSqpn5ikjzGlFGJMyIjj3MXc6LY88RQkXFXfZcscgGg4/Zqdba2eQIxAMOGhlwF
wsSqM/vrTfhomCUkaEsoV1UGKuBKxlVxhWBFRviZKo9CVDuWnvJ/iOBMCw==
-----END CERTIFICATE-----
rochlefebvre commented 2 years ago

Greetings from Rubyland! This is very cool, @vlsi. FYI, we are proposing something nearly identical to the rubygems.org community. Here is the RFC: https://github.com/rubygems/rfcs/pull/37

Let's chat on the sigstore Slack if you have ideas, questions or comments :)

vlsi commented 2 years ago

https://github.com/rubygems/rfcs/pull/37 is indeed nice, and it looks a much better wording than what I have above :)

Let's chat on the sigstore Slack if you have ideas, questions or comments :)

My idea of posting the issue here was to trigger the discussion and sample implementations in Gradle community (where somebody might not know sigstore yet 😉).


comments

I see rfcs#37 misses "caching the signatures and certificates into rubygems repository" part. It would be nice to align the formats.

rochlefebvre commented 2 years ago

I see rfcs#37 misses "caching the signatures and certificates into rubygems repository" part. It would be nice to align the formats.

Yes, we only briefly mention the storing of signatures in-repo:

The signature verification flow occurs at gem installation time. In our proposed Phase 1, verification is opt-in, and the signature entry is only hosted on Rekor itself. By Phase 2, we anticipate that rubygems.org will keep a copy of the signature as well, and that clients will download it alongside the gem itself, decoupling the install experience from any Rekor queries on the happy path.

We're trying to keep the already-beefy RFC from getting any larger, and this means cutting so much out. But our thinking is: at publish time, once rubygems.org has accepted a new gem version, it will compute the gem digest, query Rekor for any signatures, and download the ones having valid signatures. It will then host a copy of the signature(s).

Without too much research, we're thinking of keeping the rekor log entry as-is. gem clients will simply use rubygems.org as the primary store for signatures, falling back to Rekor if designed/necessary. gem clients can verify the log entry signature using Rekor's public key. A compromised repository would not be able to forge a valid Rekor log entry.

haydentherapper commented 2 years ago

Just to give some context on TUF, this is used to form the chain of trust from the verification artifacts (Fulcio root certificate, Rekor public key, Fulcio CT log public key), down to a root that's been signed through a publicly documented key signing ceremony. TUF also provides the mechanism for rotation of verification material. Assuming you trust an initial TUF root, you can verify all further TUF root rotations and changes in the artifacts that TUF signs off on (the verification keys).

To verify the Rekor log entries and Fulcio certificates, you'll need to figure out how to form the root of trust. I left a comment in Rubygems' RFC about this topic - https://github.com/rubygems/rfcs/pull/37#discussion_r794746364 The tl;dr is that you'll need to either distribute the verification keys/cert with Gradle along with a secure update mechanism, or you could integrate with TUF to handle fetching and updating verification keys.

vlsi commented 2 years ago

I've created a draft https://docs.google.com/document/d/1bJyHOdeK4WC4yuXvDgmvdAIwEG146q2fFkxMjfCbc-0/edit# to figure out the signature format in the repository. Comments welcome.

vlsi commented 2 years ago

News from Sonatype: https://central.sonatype.org/news/20220302_sigstore/

hboutemy commented 2 years ago

The certificate and signatures can be queried based on artifact SHA

I could not find how to do that: I can get from index or uuid from rekor-cli, but not SHA, isn't it? or did I miss something?

vlsi commented 2 years ago

See https://docs.sigstore.dev/rekor/CLI#search I'm not sure how reliable the search is though.

If running a redis instance within Rekor, the search command performs a redis lookup using a file or a public key. This command requires one of an artifact, a public key, or a SHA hash (should be prefixed by sha256:).

haydentherapper commented 2 years ago

Something to consider is support for an offline bundle. A bundle represents proof that an entry was added to the log, and can be verified without querying Rekor directly. The bundle would need to be placed alongside the signature.

See https://github.com/sigstore/cosign/blob/main/specs/SIGNATURE_SPEC.md#properties, search for bundle.

hboutemy commented 2 years ago

thanks to both for the useful feedback

is there a usual file name for such offline "bundle"? notice that the term is so generic and that it conflict with OSGi bundles in my JVM culture :)

notice that I have the same question for the signature: would it be "filename.asc" like PGP detached signature?

haydentherapper commented 2 years ago

There's no standard name for the offline bundle. If you wanted to avoid the word "bundle", you could refer to this as "offline transparency log entry", maybe .tloge or .tlogb?

.sig would be another good option for a signature.

hboutemy commented 2 years ago

thanks for the feedback it's odd that the full description of "bundle" does not contain the word bundle :) "offline transparency log entry", ok it is part of cosign documentation, part of sigstore project: to disambiguate with anybody outside, should it be more "cosign offline transparency log entry" or "sigstore offline transparency log entry"? or even "rekor log entry"?

brianf commented 2 years ago

If anyone following this is interested, we've started working with the sigstore team to think through how to update central to use this instead of pgp. We're at the sigstore slack in #central https://join.slack.com/t/sigstore/shared_invite/zt-mhs55zh0-XmY3bcfWn4XEyMqUUutbUQ It would be great to have representation from the Gradle community here.

vlsi commented 2 years ago

I've implemented a draft plugin that signs artifacts and publishes a signing bundle: https://github.com/vlsi/sigstore-gradle-draft

vlsi commented 2 years ago

It looks like signing within regular Gradle flow would fail to meet SLSA 3 requirements:

Even though a simple "sign via Sigstore" build step just like the current Gradle's PGP signing would make it easier for the users to adopt Sigstore signing, the more secure approach would be to have separate steps for preparing the artifacts and signing them.

In other words:

If the build is reproducible (which is a desirable property anyway), then there's no need to pass full artifact contents across step 1-2-3.

yogurtearl commented 1 year ago

Link to the WIP gradle signing support: https://github.com/sigstore/sigstore-java/blob/main/sigstore-gradle/README.md#sigstore-gradle

But it looks like that plugin covers only signing and not verification of sigstore signed artifacts? Presumably, gradle would need to add sigstore support to the verification-metadata.xml file?

vlsi commented 1 year ago

You are right. Gradle would need to evolve verification-metadata.xml to cover Sigstore verification.

Unfortunately, Sigstore is complicated from the verification point of view as it would probably require hard-coding verification attributes.

Consider junit signs releases with Sigstore. They would probably configure release.yml GitHub Action to produce and sign releases.

Then verification-metadata.xml could be like:

The second claim is somewhat hard to model (it requires hard-coding that GitHub has workflows and branches). Additional complexity comes from a fact that organization names and repository names could be reused, so the verification metadata should better include non-reusable repository id instead of a name. However, if we include both, then there will be no way for the humans to verify if the id corresponds to the repo name in the metadata.

One of the ways out might be a "metadata verification action" that would check if the ids and names in the metadata match.

Apparently, Gitlab, Azure, GCP, ... might have their own set of attributes, which make it non-trivial to model metadata in a nice and user-friendly way.

It might be Sigstore community would release Prolog-based set of rules for the verification and trust declaration. It might help building cross-language, cross-platform Sigstore verifiers. Unfortunately, it is not there yet :'(

big-guy commented 1 month ago

We should revisit when we can integrate sigstore-based verification in Gradle itself or open Gradle's verification mechanism to be pluggable. I've added this to our 9.x milestone to look at this more later.