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

Use TUF to download key/cert material #351

Merged
merged 41 commits into from
Dec 22, 2022
Merged

Conversation

jku
Copy link
Member

@jku jku commented Dec 20, 2022

Should fix #25 by using TUF to download all certs and keys. It is based on code by @joshuagl, @wxjdsr (co-authors on first commit in this branch) but it is a significant rewrite compared to their branches.

FYI also @woodruffw, @di, @SantiagoTorres.

I'm allowing edits by maintainers, go wild if you feel like it.

Status (updated dec 22):

  • Works for signing and verifying
  • The embedded keys/certs are no longer used or included in git (except staging keys as test assets)
  • if the default (production) or --staging flows are used, TUF is used to fetch key material
  • TUF is also used in some other cases (code does the same thing as the previous implementation, and uses production key material if none is provided)
  • the implementation peeks into python-tuf internals currently (to get access to top-level target metadata). This should be okay for now but a long-term solution might be RFE: expose delegated metadata to client application theupdateframework/python-tuf#1995
  • When the caches are hot (certs, keys and metadata have been downloaded already), every "sigstore verify" and "sigstore sign" (with the production or staging flows) now results in a minimum of two network requests (404 and a small download). This may be avoidable in future but currently always happens
  • with empty caches we download ~10 files, in the order of 20kB altogether. These files are then cached for later use

Some TODO items (there are certainly more)

  • testing: many existing tests now require network
    • we could mock the requests for staging in the tests
    • longer term could make it possible to not do network requests when metadata is not expired
  • testing: the TUF module has no tests (it does get tested by all tests using staging though)
  • TUF metadata is stored in ~/.local/share/sigstore-python/tuf/<url>/ and targets are cached in ~/.cache/sigstore-python/tuf/<url>/ -- this is not great for windows
  • logging in python-tuf is pretty verbose at INFO: we'll have to change that at some level
  • error handling is almost non-existent, but let's handle in later PR _internal.tuf: exception handling #357 . The situations I've identified are:
    • python-tuf RepositoryError (metadata from repo is not valid in some way)
    • python-tuf DownloadError (trouble downloading things)
    • python-tuf OSError (trouble storing things on disk)
    • discovery errors in our _tuf module: the repository content was not what we expected
  • should remove the embedded key/cert material from the repo

jku and others added 8 commits December 20, 2022 14:09
TrustUpdater can be used to fetch specific trust roots:
* Currently supports fetching
  * ctfe keys and
  * the rekor key
* Caches target files in ~/.cache/sigstore-python/
* Stores metadata in ~/.local/share/sigstore-python/
* Expects to either
  * find the metadata _for the given URL_ in metadata store
  * (for prod and stage only) find the boostrap root.json in
    sigstore/_store

The "API" that TrustUpdater provides is not meant to be final: it is the
minimal one that should fulfill current needs. Nothing uses the TrustUpdater
yet, but it's testable:

>>> from sigstore._tuf import TrustUpdater, DEFAULT_TUF_URL
>>> updater = TrustUpdater(DEFAULT_TUF_URL)
>>> rekor_key_bytes = updater.get_rekor_key()

Co-authored-by: Joshua Lock <jlock@vmware.com>
Co-authored-by: wxjdsr <wxjdsr@126.com>
Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
CTKeyring:
 * Take bytes as constructore input: this makes it easier to
   feed things from either CLI arguments or the TUF trust updater.
 * Remove tests that no longer make sense. The prod/staging contant
   should still be tested but TUF is now used in the same flows:
   Unsure how to best test this.

Use TUF to find the CTFE and rekor key when using "production" or
"staging".

Note that "staging" is currently untested: I am not sure even the URL
makes sense.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
Use TUF to get CTFE/Rekor keys in the non-staging, non-production
flow. As before, the assumption is that user wants production
keys in this case.

Refactor TrustUpdater so that it does not do network traffic if
nothing is requested.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
* Assume that the active fulcio certs in the repository form
  a certificate chain that cryptography can ingest
* Refactor RekorClient construction so that we avoid
  constructing multiple TrustTupdaters

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
If this is not production or staging but rekor key is not given,
use production: this is what original (non-tuf) code was doing as well.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
https://tuf-root-staging.storage.googleapis.com/ does work as staging
repository.

There is still a bug somewhere as staging verify currently fails.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
This is needed to bootstrap the TUF metadata with
--staging.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
@jku
Copy link
Member Author

jku commented Dec 20, 2022

Oh, a note on fulcio: I now feed all certs with usage "Fulcio" and status "Active" to Verifier with the assumption that

  • these all form a chain
  • cryptography copes with the (non-controlled) order of these certs

hopefully there are no problems with these assumptions

@jku
Copy link
Member Author

jku commented Dec 20, 2022

@asraa if you want to have a look at the TrustUpdater.get_* methods (https://github.com/jku/sigstore-python/blob/tuf-refactor/sigstore/_internal/tuf.py#L90) and comment whether my uses of "usage" and "status" seem sensible (from perspective of root-signing repository maintenance), that would be great.

@woodruffw
Copy link
Member

Oh, a note on fulcio: I now feed all certs with usage "Fulcio" and status "Active" to Verifier with the assumption that

  • these all form a chain
  • cryptography copes with the (non-controlled) order of these certs

hopefully there are no problems with these assumptions

These assumptions should be fine! We throw everything into an OpenSSL X.509 store, and I don't believe it imposes any order of insertion.

Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
@woodruffw woodruffw added component:signing Core signing functionality component:verification Core verification functionality labels Dec 20, 2022
@woodruffw
Copy link
Member

Online tests now pass, but the forced offline tests are still failing (as expected).

@woodruffw
Copy link
Member

woodruffw commented Dec 20, 2022

  • logging in python-tuf is pretty verbose at INFO: we'll have to change that at some level

IMO this is worth an upstream PR -- looks like most of these should really be DEBUG level instead. I can look into that.

Edit: Opened: theupdateframework/python-tuf#2243

* Bring back RekorClient.production() and RekorClient.staging(): these
  are simple but make the calling code slightly clearer maybe
* Add a TrustUpdater argument to those methods: if you use
  production/staging, you need a TrustUpdater
* The TrustUpdater can not be constructed inside RekorClient
  as other components may need it as well.

It's not perfectly elegant for the caller but it's not horrible either:
  updater = TrustUpdater.staging()
  client = RekorClient.staging(updater)
This design means TrustUpdater does not know anything about the sigstore
mechanisms: it just discovers and downloads files.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
No functional change, just refactor the target discovery into a single
method.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
The test doesn't make a lot of sense now that the keys are not being
read from the _store.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Annoying.

Signed-off-by: William Woodruff <william@trailofbits.com>
@woodruffw
Copy link
Member

This is looking pretty good to me! I think we can move it out of a draft for now; there are only a few small cleanup tasks that I think should get done before we merge.

@woodruffw woodruffw marked this pull request as ready for review December 21, 2022 18:45
sigstore/_cli.py Show resolved Hide resolved
sigstore/_internal/tuf.py Outdated Show resolved Hide resolved
Signed-off-by: Alex Cameron <asc@tetsuo.sh>
@tetsuo-cpp
Copy link
Collaborator

I'm seeing some weird lint errors on this branch that I don't see in main, CI doesn't seem to have this issue as it's getting all the way to bandit:

sigstore/_utils.py:161: error: Returning Any from function declared to return "bytes"  [no-any-return]
Found 1 error in 1 file (checked 24 source files)

Looks like that file hasn't even been touched.

@jku
Copy link
Member Author

jku commented Dec 22, 2022

I'm seeing some weird lint errors on this branch that I don't see in main, CI doesn't seem to have this issue as it's getting all the way to bandit:

sigstore/_utils.py:161: error: Returning Any from function declared to return "bytes"  [no-any-return]
Found 1 error in 1 file (checked 24 source files)

Looks like that file hasn't even been touched.

This is unrelated, it's not showing up in main because William added an ignore in 0647d75.

@tetsuo-cpp
Copy link
Collaborator

tetsuo-cpp commented Dec 22, 2022

This is unrelated, it's not showing up in main because William added an ignore in 0647d75.

I assumed that was necessary somehow because we switched to Ruff, but your branch doesn't have that commit. Anyway, if you don't mind, I'll merge in main and get this branch up to date so hopefully this goes away.

tetsuo-cpp and others added 4 commits December 22, 2022 20:24
Signed-off-by: Alex Cameron <asc@tetsuo.sh>
Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
This is a potential improvement, not a necessary one.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
@jku
Copy link
Member Author

jku commented Dec 22, 2022

I pushed some tiny documentation tweaks but I would agree: this looks good to me apart from the final linting issue #351 (comment)

tetsuo-cpp
tetsuo-cpp previously approved these changes Dec 22, 2022
Copy link
Collaborator

@tetsuo-cpp tetsuo-cpp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM provided that the linting is sorted out. Nice work.

Also tweak one annotation (remove unneeded quotes)

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
@jku
Copy link
Member Author

jku commented Dec 22, 2022

I added the "#nosec" to get tests passing: feel free to solve the issue in some other way if you want.

@di
Copy link
Member

di commented Dec 22, 2022

/gcbrun

@woodruffw
Copy link
Member

/gcbrun

Copy link
Member

@woodruffw woodruffw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! :shipit:

@woodruffw woodruffw merged commit 5a68d1e into sigstore:main Dec 22, 2022
sigstore/_internal/tuf.py Show resolved Hide resolved

# NOTE: _updater has been fully initialized at this point, but mypy can't see that.
targets = self._updater._trusted_set.targets.signed.targets # type: ignore[union-attr]
for target_info in targets.values():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually would caution against using the custom metadata and use the path filters "rekor*", "fulcio*", "ctfe*", instead as the filtering mechanism. While we'll keep some of the metadata, I think we are aiming to change them (for example, status will be deprecated in favor of explicit NotBefore/NotAfter ranges)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That being said "usage" should match here and be stable

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I knew the custom metadata was not a permanent solution but I'm not sure filtering by path is going to be either -- maybe makes sense to update once the target discovery and delegation plans are properly decided

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure filtering by path is going to be either -- maybe makes sense to update once the target discovery and delegation plans are properly decided

That's fair! Yeah, in the end distributing as a single bundle would probably fix that and that likely will be the case next year. The migration to that will be slow anyway (as in, the existing targets will continue to be distributed like this).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component:signing Core signing functionality component:verification Core verification functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Retrieve CTFE signing key via TUF
5 participants