sigstore / rekor

Software Supply Chain Transparency Log
https://sigstore.dev
Apache License 2.0
880 stars 163 forks source link

Ability to sign tlog with GPG #532

Closed Dentrax closed 1 year ago

Dentrax commented 2 years ago

TL;DR: We propose signing the tlog entities using GPG to verify source of truth


Description If we sign a file using keyless mode with cosign, we upload a tlog to rekor if we in the experimental mode.

Let's consider the following scenario:

$ COSIGN_EXPERIMENTAL=1 cosign sign-blob cs.txt
$ rekor-cli search --email furkan.turkal@hotmail.com
$ rekor-cli get --uuid 6325f1e47e23be2f93e716f507da3998432f9cf71ec5fc376c6f937e6eadf49a --format json | jq '.Body.RekordObj.signature.publicKey.content' -r | base64 -d | openssl x509 -noout

What we did here, I simply got the UUID of the cs.txt and passed the publicKey to openssl, as you already know. Nothing interesting much here. Here is what I'm curious about:

If my GitHub (or OAuth provider) account get compromised (stolen username, password etc.), anyone who knows my login details can upload a tlog as if they were impersonating me.

Motivation

Let's assume @developer-guy's GitHub account hacked and someone got the login credentials. I still able to search entries by email like the following:

$ rekor-cli search --email furkan.turkal@hotmail.com
Found matching entries (listed by UUID):
6325f1e47e23be2f93e716f507da3998432f9cf71ec5fc376c6f937e6eadf49a
dc11cfc8bbbd12f1b1536b2cc075f7fe9369b6b388157931f1679c26a79f409e
...

In the following about entities, I still do not know much about the author (issuer). Is that really him? Or someone who is impersonating. That said, how we can be sure that entities signed by the author? Wouldn't it be more trustworthy to rely on GPG key (i.e., 7D7E90962D9FD507) rather than trusting the email? I really wouldn't trust the tlog entity whether it's really signed by specified email address.

This is where our proposal coming from.

Proposal

We (@developer-guy) propose adding a new owner object in the signature field of rekord schema:

"owner": {
  "format": "gpg",
  "id": "7D7E90962D9FD507"
}

Overall signature would be like:

"signature": {
  "signed": {
    "format": "gpg",
    "key": "7D7E90962D9FD507"
  }
  "content": "<SIGNATURE>",
  "format": "x509",
  "publicKey": {
    "content": "<BASE64 ENCODED CERT>"
  }
}

By adding a new --gpg flag to rekor-cli, we are able to search entities that uploaded a given GPG key id:

$ rekor-cli search --gpg 7D7E90962D9FD507

By passing both --email and --gpg we only list the matching only(s).

$ rekor-cli search --email furkan.turkal@hotmail.com --gpg 7D7E90962D9FD507
Found matching entries (listed by UUID):
6325f1e47e23be2f93e716f507da3998432f9cf71ec5fc376c6f937e6eadf49a
dc11cfc8bbbd12f1b1536b2cc075f7fe9369b6b388157931f1679c26a79f409e
89331a610e9eed632c3f7f8445ed5348dc5e41573bc68669c5e96450fcf29028 (ASSUME THIS NOT LISTED, SIGNED WITH DIFFERENT GPG)
...

Similar to GitHub's Verified badge, we can show some icons that indicate whether it's verified:

$ rekor-cli search --email foo.bar@test.com --show-gpg
Found matching entries (listed by UUID):
❎ 6325f1e47e23be2f93e716f507da3998432f9cf71ec5fc376c6f937e6eadf49a (7D7E90962D9FD507)
❎ dc11cfc8bbbd12f1b1536b2cc075f7fe9369b6b388157931f1679c26a79f409e (7D7E90962D9FD507)
❌ 89331a610e9eed632c3f7f8445ed5348dc5e41573bc68669c5e96450fcf29028 (4A6G85682G3GA536)
...

We probably have to store the public GPG keys into rekor server in order to verify the author, right?

Solution 1:

paths:
  /api/v1/gpg_keys:
    post:
      summary: Adds a GPG key to the authenticated user's account.
      operationId: addGPG
      parameters:
        - in: body
          name: armored_public_key
          required: true
          description: A GPG key in ASCII-armored format.
          schema:
            string
      responses:
        201:
          description: Created
        304:
          description: Not modified
        401:
          description: Unauthorized
        default:
          description: Forbidden

As you may already notice, an authenticated user is required. An authenticated user could be rekor server admin. Since we do not have a login/register system, I'm not so sure how we can manage this or should really we?

We can not expose this endpoint to the public to serve if we do not want to manage a login system. Probably it had better to make it accessible in internal network.

Solution 2:

Passing hard-coded email:gpg pairs in the config.yaml.

trusted_users:
 - email: foo@bar.com
   gpgs:
     - 123
     - 456
 - email: baz@qux.com
   gpgs:
     - 789

Drawback: there will be tons of user(s) who want to upload tlog to rekor server. Everyone could want to be a trusted user. Probably this solution could not work.

Concerns

Waiting your ideas, thoughts, feedback!

PTAL @asraa @shibumi

shibumi commented 2 years ago

To be honest, I would like to see GPG dead. GPG is broken by design and nobody should use it. The cli is terrible, the openpgp standard is chaotic and GPG's trust features are in 99% of cases useless, because people do not establish a web of trust anyway...:

So, how do we establish a root of trust. Rekor and cosign allow using your own TUF root.

Abou the transparency aspect:

You can do the following to check the issuer in the rekor tlog:

❯ rekor-cli get --uuid 228527476f82c59641e27b8fb9b32f5fadbe47cf42c544ea97dc798490c4c14e --format json | jq -r '.Body.RekordObj.signature.publicKey.content' | base64 -d > pub.crt
❯ openssl x509 -noout -text -in pub.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            5e:70:0d:3d:19:66:85:d7:1d:00:41:b8:9f:74:8c:39:ed:bf:d2
        Signature Algorithm: ecdsa-with-SHA384
        Issuer: O = sigstore.dev, CN = sigstore
        Validity
            Not Before: Nov 13 22:11:53 2021 GMT
            Not After : Nov 13 22:31:52 2021 GMT
        Subject:
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:67:4c:43:f5:e1:04:c5:20:ec:f9:28:c0:bf:80:
                    1c:ce:08:5e:f8:14:5d:88:93:50:be:b0:d8:1a:77:
                    aa:8b:16:f6:d4:ca:bb:7a:2c:f2:22:15:22:6c:83:
                    18:37:13:db:31:0b:ca:13:ba:a5:d6:34:9b:85:cc:
                    6e:21:2b:3a:b6
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                Code Signing
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier:
                08:24:98:BE:E1:A6:99:E7:06:D0:1C:F8:38:5C:87:0D:7D:3B:C6:13
            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
                URI:https://github.com/shibumi/mnemonic/.github/workflows/goreleaser.yml@refs/tags/v0.3.1
            1.3.6.1.4.1.57264.1.1:
                https://token.actions.githubusercontent.com
    Signature Algorithm: ecdsa-with-SHA384
         30:65:02:31:00:94:ff:ce:4e:c5:be:ee:29:01:de:0f:7a:9e:
         d1:fd:0a:c3:22:54:c3:a5:17:1c:8c:d2:8d:e6:88:20:1c:67:
         c9:dd:a8:fd:cc:d5:ac:39:1e:3a:d0:b4:24:c2:5a:5a:b7:02:
         30:5c:74:86:87:bc:5d:e3:5a:b7:49:98:17:9e:1a:e5:8c:ce:
         0a:3f:fb:f8:4b:50:67:e2:16:f4:41:0f:9c:7e:66:22:8d:3a:
         0b:a2:9b:45:3d:9f:80:fc:f7:d6:31:6c:fd

The key information can be found in the 1.3.6.1.4.1.57264.1.1 extension and the x509v3 SAN:

            X509v3 Subject Alternative Name: critical
                URI:https://github.com/shibumi/mnemonic/.github/workflows/goreleaser.yml@refs/tags/v0.3.1
            1.3.6.1.4.1.57264.1.1:
                https://token.actions.githubusercontent.com

This should solve your transparency issues and your trust bootstrapping issues.

But what happens if the Github Account gets hacked? Well, if your Github accounts get hacked this is equal to losing your GPG private key. There is no "fix" for this. You could sign every release manually with a private/public key pair in your cold storage... but this violates SLSA 1 (a release/build process should be automated).

haydentherapper commented 2 years ago

One issue with this is we move away from truly keyless to having users maintain a key. What happens when a key is lost? Do we no longer trust any of the previous entries?

I don't think Rekor should also be a database of users mapped to GPG keys. I don't believe Rekor is meant to be an identity store.

For authenticating users: First, I think it would place a significant burden on maintainers or server admins to maintain a list of users mapped to their GPG keys. Second, how do we trust that user X is meant to be mapped to some key K? Let's say we require OIDC to validate possession of that email. In the example you presented, in which there is compromise of the email, we don't gain anything from adding GPG signatures, because an attacker could trick rekor into uploading a mapping between the compromised email and a key that the attacker possesses.

shibumi commented 2 years ago

For generating keyless signatures we have a few constraints:

  1. The build pipeline / the build environment is secure
  2. The OIDC Issuer is secure
  3. The Github Account should be protected by 2FA (ideally with a hardware token)
  4. The Github Action Workflow is secured properly and nobody can schedule a release with a PR
  5. Github and the OIDC issuer are trusted!

If one of above constraints is violated, the people should not use Github for releasing/building/hosting their code at all. Instead, the SPIRE federation feature can be used and an on-premise pipeline can get setup.

If you want to have an easy "proof of ownership", you could use a private TUF instance to generate keys for releases and manually sign a checksum file over all release files with these TUF release role keys.

However, as I said earlier: If you do something manually you violate SLSA Level 1.

I am sure @dlorenc or @asraa have some additions, I am just a newbie. I might be wrong in a few points, here :) Take my words with a grain of salt.

shibumi commented 2 years ago

Sorry for the double comment:

If you use the newest cosign version 1.4.1 it has support for the above details that I have acquired manually via openssl:

❯ COSIGN_EXPERIMENTAL=1  cosign verify-blob mnemonic_0.3.2_checksums.txt --signature mnemonic_0.3.2_checksums.txt.sig
Certificate is trusted by Fulcio Root CA
Email: []
URI: https://github.com/shibumi/mnemonic/.github/workflows/goreleaser.yml@refs/tags/v0.3.2
Issuer:  https://token.actions.githubusercontent.com
Verified OK
tlog entry verified with uuid: "af23e34ac89aa9bcaef435e0612e7958f8cb6c21d7ec4ce3f9844e7e2964f4bf" index: 932678

EDIT: This should at least give you a hint which issuer has been used and which URI/Github Action was in "action".

shibumi commented 2 years ago

Sorry for the third comment in a row:

If you really want to use GPG. You can upload GPG signature attestations to the rekor tlog. I am not 100% up to date, this feature might be work in progress:

See here: https://github.com/sigstore/rekor/tree/main/pkg/types and here: https://github.com/sigstore/rekor/tree/main/pkg/types

asraa commented 2 years ago

@shibumi and @haydentherapper pretty much summarized all the good points! :D

I really wouldn't trust the tlog entity whether it's really signed by specified email address.

Just to clarify this question particularly: the trust for the email in the certificate is rooted in Fulcio (and the auth provider), not in the tlog. You trust that Fulcio actually verified the token and challenge from that particular email. So long as you trust the Fulcio root CA and the cert chains back to it, the email can be trusted.

Like @shibumi mentioned, if you want to be really careful about whether a Fulcio root correctly verified the claim that the email owns the private signing key you could use your own root of trust, and that Fulcio root could also allowlist only certain emails.

shibumi commented 2 years ago

Like @shibumi mentioned, if you want to be really careful about whether a Fulcio root correctly verified the claim that the email owns the private signing key you could use your own root of trust, and that Fulcio root could also allowlist only certain emails.

One question about this: Is this related to the SPIRE federation and strictly coupled to the DNS FQDN? I know that @dlorenc mentioned this in one of his blog articles, but I am not sure if it was about the TUF root or about Issuer federation. Do I need an own Issuer if I want to use my own TUF root?

asraa commented 2 years ago

Do I need an own Issuer if I want to use my own TUF root?

Nope! You can use existing issuers, supplying the TUF root (e.g. Fulcio root) would mean you can make your instance configurable to which issuers it trusts and what else it uses for verification before handing you a signed certificate. OTOH if you want your own issuer, you need your own Fulcio/TUF root or to add it to the issuers in the public prod instance of Fulcio.

I am not entirely sure which blog post, but I would assume that would be talking about a specialized issuer.

shibumi commented 2 years ago

supplying the TUF root (e.g. Fulcio root) would mean you can make your instance configurable to which issuers it trusts

Ah okay. It's the other way around. Thank you for the explanation.

Looks like, I misunderstood this blog article: https://dlorenc.medium.com/the-sigstore-trust-model-4b146b2ecf2c

asraa commented 2 years ago

Ah gotcha! I see. Yeah in that article it mentions you can either add your own Issuer to Sigstore's root ("Send a four-line PR to the Fulcio repo, adding your domain!") OR create your own TUF repo that you can do anything you want in ("We’re also happy to accept other existing trust roots.")

Lots of configurability :)

Dentrax commented 2 years ago

Thank you all for such great explanations and clarifications. ❤️ Since I'm a newbie at this whole stuff, it was a good learning for me from your valuable comments.

shibumi commented 2 years ago

Thanks :)

So, to summarize this: @Dentrax I think providing your own TUF root is the option of choice in this issue. If you have more questions feel free to reach out to me via slack, mail, twitter or this PR. IMO, the TUF root should fix the "trust" issue...

shibumi commented 2 years ago

Thank you all for such great explanations and clarifications. heart Since I'm a newbie at this whole stuff, it was a good learning for me from your valuable comments.

No worries. It's a complex topic and I still don't understand everything, too :D

lukehinds commented 2 years ago

I agree with a lot of the comments on here, we should be encouraging folks to not use GPG. It's a flawed protocol with many issues around key management.

We should also not be encouraging people to store private keys on cloud services like github/gitlab etc.

GPG and it's clumsy tooling and lack of adoption is what originally inspired the sigstore project to start.

asraa commented 2 years ago

@Dentrax Yes! Feel free to reach out -- @rgerganov is also testing out supplying a BYO TUF root, so if there's any friction hopefully we'll see it soon!