sigstore / sigstore-python

A Sigstore client written in Python
https://pypi.org/p/sigstore
Other
227 stars 49 forks source link

Bring your own PKI #916

Closed laurentsimon closed 5 months ago

laurentsimon commented 8 months ago

Context: Overall we would like to offer a unified CLI / API (as part of https://github.com/google/model-transparency) to sign and verify AI artifacts.

We've received interest to support custom PKIs. IIUC, these come in two flavors:

  1. Support for private Fulcio deployment. IIUC, This requires new options like --fulcio-root-pubkey (--fulcio-url is already supported) for signing, and --fulcio-root-pubkey for verification.
  2. Support for a non-Fulcio CA that would typically be an existing certificate (for enterprise that already have their own PKI setup). This requires a different flow altogether.

for (2): Given that sigstore-python already contains all the logic for verification, I think we could augment it with a new API that takes in an interface / object to let callers customize the certificate management / chain verification / etc. That would require defining the right interface to abstract the functionality we want. Fulcio / Sigstore would be the default certificate manager. Is this a reasonable approach? Wdut?

@haydentherapper @major-security

jku commented 8 months ago

I won't comment on the Non-fulcio CA idea since I'm not really sure what that would mean -- following is likely only relevant to private deployments.

I think our expectation has been that in future we could only support the trusted root format that root-signing publishes: this trusted root could come from a source other than root-signing TUF repo but preferably it would be the only supported way to configure any of the trust components.

CLI

I believe the long term plan for the CLI specifically is to remove "individual trust component" flags as much as possible: It's really a pain to make the hundreds of combinations bug free, safe and understandable... Instead we'd like to just get a single "complete trust root" as argument. This could mean:

So for any non-standard trust components you'd have to build your own trusted_root.json with the URLs and keys you like -- there could be tools to build a reasonable trusted_root.json of course but making that safe wouldn't be sigstore-python head ache anymore...

API

In the API there may be more space to support other approaches as well:

woodruffw commented 8 months ago

I believe the long term plan for the CLI specifically is to remove "individual trust component" flags as much as possible: It's really a pain to make the hundreds of combinations bug free, safe and understandable... Instead we'd like to just get a single "complete trust root" as argument. This could mean:

Yep, this. Long term, there should be a single --trusted-root flag that bootstraps everything in the BYO PKI setting. I'd like to get rid of some of the current individual states with the next release (v3), and then the remaining ones with v4 🙂

So yeah, in that case the BYO PKI scenario would only need to provide a trusted_root.json, and everything else would "just work."

Re: API: right now we have staging() and production() classmethods on Signer and Verifier to produce signing and verification object in a single shot against the public Sigstore instances. For the BYO PKI scenario, my preference would be to add a new from_trusted_root() or similar classmethod to each, which would behave similarly. That way all of the internal machinery remains the same.

haydentherapper commented 8 months ago

For supporting a private Sigstore deployment, I agree that long-term, providing a trusted root file seems like a good solution. Short term, can we make the TUF repository configurable with --tuf-url + --tuf-root like Jussi mentioned? From a quick glance, it seems like there is support for this in the internals. Coupled with fulcio-url and rekor-url, that would be full support for a private Sigstore deployment.

As for the non-Fulcio case, let's split out signing and verification. For cryptographic verification, it's mostly straightforward - You provide a Sigstore bundle (or maybe detached verification materials, but my preference would be requiring the bundle) and a key or certificate+chain, providing via the trusted_root file. Verification policy is a bit harder, since we don't know the contents of the certificate. For Cosign, we've thought about allowing Cue or Rego policies rather than trying to figure out what a policy should contain for BYO cases.

Signing is a bit trickier, because there's a lot of variation for what constitutes a signer. Should clients add support for raw keys? KMS? A key from a certificate that's verified during signing? All of these examples are supported by Cosign, although not with a clean API. I see a few options for the signing path:

The first would be nice, but might require changes to the python API to have a generic signer representation.

woodruffw commented 8 months ago

Short term, can we make the TUF repository configurable with --tuf-url + --tuf-root like Jussi mentioned? From a quick glance, it seems like there is support for this in the internals. Coupled with fulcio-url and rekor-url, that would be full support for a private Sigstore deployment.

That sounds good to me!

For cryptographic verification, it's mostly straightforward - You provide a Sigstore bundle (or maybe detached verification materials, but my preference would be requiring the bundle) and a key or certificate+chain, providing via the trusted_root file. Verification policy is a bit harder, since we don't know the contents of the certificate. For Cosign, we've thought about allowing Cue or Rego policies rather than trying to figure out what a policy should contain for BYO cases.

This make sense to me, up to and including cryptographic verification. Policy verification does seem hard, however 🙂 -- we currently have a pretty bare-bones boolean/FOL extension policy API in sigstore-python, but we'd probably be ultimately better off standardizing on Cue or Rego and punting this to whoever writes those policies (which I suppose then become another input?)

Add support for BYO key/cert in sigstore-python

I think this should be relatively easy to add to the pre-existing API -- we can do any sign/verify operations that pyca/cryptography supports, so we could add/amend an API to allow a PrivateKey input. The only risk I see there is in breaking abstractions around keyless signing/making it harder to expose a tidy interface for the "happy path," but maybe we can isolate these enough to have that not be an issue.

jku commented 8 months ago

For supporting a private Sigstore deployment, I agree that long-term, providing a trusted root file seems like a good solution. Short term, can we make the TUF repository configurable with --tuf-url + --tuf-root like Jussi mentioned?

This should be fairly easy. The only reasons I haven't done that yet are

laurentsimon commented 8 months ago

I think this should be relatively easy to add to the pre-existing API -- we can do any sign/verify operations that pyca/cryptography supports, so we could add/amend an API to allow a PrivateKey input.

That'd be nice and work for our use case. @major-security wdut?

Verification policy is a bit harder, since we don't know the contents of the certificate. For Cosign, we've thought about allowing Cue or Rego policies rather than trying to figure out what a policy should contain for BYO cases.

What's a "verification policy"?

As a first step, we can leave it up to the API caller to read the bundle and verify the chain+cert, before they themselves call the verification API. That does not preclude adding more advanced options later in sigstore-python, but reduces the commitment to take upfront for you. I think that would unblock us for model-transparency repo. @major-security please keep me honest

trusted_root file

I'm not familiar with the format and scope. Is this file intended to support only keyless verification?

Signing is a bit trickier, because there's a lot of variation for what constitutes a signer. Should clients add support for raw keys? KMS?

Agreed. Providing an API that enables folks to customize their signer is a first good step imo. If all they have to do is create an instance of a well-defined interface, it's already an enabler for adoption.

haydentherapper commented 8 months ago

Verification policy is what values are expected in a certificate. For a Fulcio certificate, we mandate checking the subject alternative name and a custom OID for the OIDC issuer. For BYO PKI, we don't know what extensions or OIDs are required, so it's easiest to leave policy checks up to the caller.

For Cosign, we took the stance that a certificate should require an identity, since we wanted to always require identity flags. We recommended for BYO PKI use cases to extract the public key with something like https://smallstep.com/docs/step-cli/reference/certificate/key/ and handle verification of the certificate chain out of band. This may be something we revise in the future if we namespace BYO PKI signing and verification (like cosign private sign or something).

trusted_root is usable for key-based verification. You can provide a key_hint which is a user-defined fingerprint of the key. We may need to make small changes though as we've primarily thought about it in the context of the public instance.

laurentsimon commented 8 months ago

Thanks everyone. Let me try to summarize for the case of non-Fulcio PKI - since it's the most complicated one. A solution would work as follows:

  1. sigstore-python provides an API for signing using a PrivateKey (+ type, eg ECDSA or other). This should be relatively easy. This API will optionally (default=true) upload to rekor
  2. sigstore-python provides an API for verification that takes in a PublicKey or a trusted_root option. Option A: This verification only performs a signature verification. Option B: This verification does signature verification + Rekor verification.
  3. The caller (model-transparency repo in this case) works as follow. a. For signing, we call the signing API with PrivateKey arg, that's all. b. For verification, we read the bundle file, extract the certs and verify them ourselves. We extract the corresponding PublicKeys. Then 2 options:

Does this look correct?

laurentsimon commented 7 months ago

friendly ping. Would love your feedback on my last comment.

haydentherapper commented 7 months ago

sigstore-python provides an API for verification that takes in a PublicKey or a trusted_root option. Option A: This verification only performs a signature verification. Option B: This verification does signature verification + Rekor verification.

It should still be possible (or even strongly suggested) to verify a signing event was recorded in Rekor with only a public key. This was one of the motivations when I filed https://github.com/sigstore/protobuf-specs/issues/236 to include public keys in the trust root file.

laurentsimon commented 7 months ago

@jku @woodruffw any comments or preference?

woodruffw commented 7 months ago
  • sigstore-python provides an API for signing using a PrivateKey (+ type, eg ECDSA or other). This should be relatively easy. This API will optionally (default=true) upload to rekor
  • sigstore-python provides an API for verification that takes in a PublicKey or a trusted_root option. Option A: This verification only performs a signature verification. Option B: This verification does signature verification + Rekor verification.

I'm personally 👎 on having sigstore-python take bare keys and especially having it do bare signature verification (with none of the other things that make "Sigstore Sigstore"). 👍 to @haydentherapper's observation about bare pubkeys being committable in Rekor, as well.

My strong preference here would be:

  1. The client's root of trust is always initialized either via TUF (possibly a self-hosted instance for BYO cases) or via a --trusted-root or --client-trust-config (pending https://github.com/sigstore/protobuf-specs/pull/277) JSON input. This keeps the number of trusted state initialization pathways to a bare minimum, and ensures that the error states between TUF vs. a JSON input are nearly identical (since they'll have the same underlying logic/models).
  2. For verification, the input should always be a Bundle, which in turn can contain either an X.509 cert or a public key. The latter isn't supported yet, but should be relatively straightforward and would be my preferred approach for the "BYO key" scenario.
  3. For signing, bare keys pose a significant challenge/impedance mismatch with the current Signer/SigningContext APIs. I think trying to shoehorn it into the existing APIs will cause a lot of user confusion (especially when the encouraged path is "keyless"). Instead of supporting signing with a bare PrivateKey, IMO we could expose a (better) Bundle building API that takes the result of the user doing their own signing operation. This saves a lot of complexity on sigstore-python's side without pushing too much onto the user, since PyCA Cryptography's signing API is ~3 lines of Python 🙂

Under this, we'd support both BYO PKI and BYO key without making context-specific compromises on what "verification" means in sigstore-python (which we've been trying to ratchet down to "always includes Rekor verification").

laurentsimon commented 7 months ago

My strong preference here would be:

  1. The client's root of trust is always initialized either via TUF (possibly a self-hosted instance for BYO cases) or via a --trusted-root or --client-trust-config (pending trustroot: initial client config messages protobuf-specs#277) JSON input. This keeps the number of trusted state initialization pathways to a bare minimum, and ensures that the error states between TUF vs. a JSON input are nearly identical (since they'll have the same underlying logic/models).
  2. For verification, the input should always be a Bundle, which in turn can contain either an X.509 cert or a public key. The latter isn't supported yet, but should be relatively straightforward and would be my preferred approach for the "BYO key" scenario.

LGTM.

The only requirements (from verifier.py) is that the cert is a code signing cert. So existing PKIs should work seamlessly.

For private deployments (a company's internal PKI):

  1. SCT verification may need to be disabled. Maybe via trusted root config?
  2. The policy verification seems to allow disabling Sigstore-specific verification (rekor), which would enable private deployments that don't make use of tlogs. This feature will be kept, correct? Btw, I'm curious why the signature verification is not performed in this case?
  1. For signing, bare keys pose a significant challenge/impedance mismatch with the current Signer/SigningContext APIs. I think trying to shoehorn it into the existing APIs will cause a lot of user confusion (especially when the encouraged path is "keyless"). Instead of supporting signing with a bare PrivateKey, IMO we could expose a (better) Bundle building API that takes the result of the user doing their own signing operation. This saves a lot of complexity on sigstore-python's side without pushing too much onto the user, since PyCA Cryptography's signing API is ~3 lines of Python 🙂

LGTM.

For private deployments, callers will have the ability to disable rekor / tlog, correct?

haydentherapper commented 7 months ago

SCT verification may need to be disabled. Maybe via trusted root config?

This seems like a reasonable feature. There will be private deployments that don't need transparency, eg the artifacts are signed in the same trust boundary as artifact consumption. This seems reasonable to disable for both Fulcio's CT log and Rekor. For a data point, in sigstore-go, we have a verification options interface like https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_verification.proto#L49 for users to specify expected thresholds.

woodruffw commented 5 months ago

Triaging: I think this will be covered under #1010: with that, sigstore will learn the --trust-config flag, which can be used to pass in the entire trust config/BYO PKI state.