Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring your own PKI #916

Closed
laurentsimon opened this issue Feb 28, 2024 · 15 comments · Fixed by #1010
Closed

Bring your own PKI #916

laurentsimon opened this issue Feb 28, 2024 · 15 comments · Fixed by #1010
Labels
enhancement New feature or request

Comments

@laurentsimon
Copy link
Contributor

laurentsimon commented Feb 28, 2024

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

@laurentsimon laurentsimon added the enhancement New feature or request label Feb 28, 2024
@jku
Copy link
Member

jku commented Feb 28, 2024

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:

  • by default use trusted_root.json from production TUF
  • --staging means use trusted_root.json from staging TUF
  • --tuf-url <URL> [--tuf-root <FILENAME>] use trusted_root.json from a non-standard TUF (this option does not exist yet)
  • --trusted--root <FILENAME> allows provisioning the trust root manually (this option does not exist yet)

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:

  • I imagine sign and verify APIs will mainly support just a TrustedRoot instance as a trust root argument (mirroring the CLI)
  • TrustedRoot on the other hand could possibly be improved to handle something else than just "read trusted_root.json from disk or TUF repo"

@woodruffw
Copy link
Member

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
Copy link
Contributor

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:

  • Add support for BYO key/cert in sigstore-python
  • (assuming a key) Recommend using openssl or a KMS client to sign, then use sigstore-python to upload to Rekor (I don't think sigstore-python supports uploading a signature generated out of band?)
  • Use openssl/etc and have a Python version of rekor-cli

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

@woodruffw
Copy link
Member

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
Copy link
Member

jku commented Feb 29, 2024

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

  • avoiding more UI clutter before all the legacy options get removed... but if there's someone actually interested in this, we can certainly add it right now.
  • There's some hard coding of service URLs in the code base: we should be exposing the URLs from the trustroot (via Keyrings maybe?). Without this making TUF configurable is lip service

@laurentsimon
Copy link
Contributor Author

laurentsimon commented Mar 4, 2024

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
Copy link
Contributor

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
Copy link
Contributor Author

laurentsimon commented Mar 7, 2024

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:
  • Option A: We provide the PublicKeys as args to the verification API (could be in the form of a trusted_root config). Then we call a Rekor API to verify the entry is in the log (online) or at least to verify the inclusion promise (offline).
  • Option B: We construct an updated bundle by removing the cert chain and inserting a PublicKey hint. Then we call the "standard" verification API (does both signature verification and rekor verification) and provide PublicKeys as args (could be in the form of a trusted_root config). This option seems simpler because sigstore-python need not expose a rekor API and the caller only needs to make a single call (?)

Does this look correct?

@laurentsimon
Copy link
Contributor Author

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

@haydentherapper
Copy link
Contributor

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 sigstore/protobuf-specs#236 to include public keys in the trust root file.

@laurentsimon
Copy link
Contributor Author

@jku @woodruffw any comments or preference?

@woodruffw
Copy link
Member

  • 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 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.
  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
Copy link
Contributor Author

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
Copy link
Contributor

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
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants