Skip to content

Use TUF to download key/cert material #351

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

Merged
merged 41 commits into from
Dec 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a7598df
tuf: Add initial TUF trust root updater
jku Dec 20, 2022
e6d392e
Rekor: Refactor CTKeyring, Use TUF in prod/staging
jku Dec 20, 2022
22a6055
cli: Use TUF for rekor/ctfe keys if not in args
jku Dec 20, 2022
b6325fd
Fix linter issues in TUF related code
jku Dec 20, 2022
c689fdb
tuf: Fetch Fulcio certificates with TUF
jku Dec 20, 2022
d7b75c1
cli: Use production rekor key by default
jku Dec 20, 2022
6386af7
tuf: Enable staging support
jku Dec 20, 2022
29885ff
_store: Add missing staging root.json
jku Dec 20, 2022
e1712a7
verifier: blacken
woodruffw Dec 20, 2022
b62a410
pyproject, sigstore/tuf: use appdirs for local state
woodruffw Dec 20, 2022
a8d1e4e
verifier: unused import
woodruffw Dec 20, 2022
f75c866
_internal/tuf: disambiguate caches correctly
woodruffw Dec 20, 2022
3a8f026
sign, verify, internal: refactor rekor client handling
woodruffw Dec 20, 2022
7d80e93
test/verify: fix TestVerificationMaterials test
woodruffw Dec 20, 2022
1dd9c1f
Refactor RekorClient construction once more
jku Dec 21, 2022
8072f1d
internal: Improve tuf docstrings
jku Dec 21, 2022
e85d6f4
internal: Refactor tuf
jku Dec 21, 2022
238f191
tests: Remove test for _store
jku Dec 21, 2022
8632250
_store: Remove all certificates and keys
jku Dec 21, 2022
d342697
tests: Add mock TUF fetcher for staging
jku Dec 21, 2022
5e5b280
tests: Don't require network in parametrized setup
jku Dec 21, 2022
6a41e3a
cli: Silence python-tuf logging a little
jku Dec 21, 2022
b54ed9f
tests: Add TrustUpdater test
jku Dec 21, 2022
68425ed
tests: Add basic test for TrustUpdater
jku Dec 21, 2022
170096e
Merge branch 'main' into tuf-refactor
woodruffw Dec 21, 2022
4ad04ce
_utils: lintage
woodruffw Dec 21, 2022
ae9df01
test/unit: put TUF assets under assets dir
woodruffw Dec 21, 2022
a210a6f
tests/unit: re-parametrize
woodruffw Dec 21, 2022
bbc6a99
_store, _utils: remove obsolete comment, re-add helper
woodruffw Dec 21, 2022
69f249e
test/unit: re-add store tests
woodruffw Dec 21, 2022
03bdaf7
tuf: re-use our read_embedded helper
woodruffw Dec 21, 2022
476b8f4
README: update `--help` texts
woodruffw Dec 21, 2022
3c88b26
gitignore, test: allow staging-tuf assets
woodruffw Dec 21, 2022
d9aa72c
tuf: Switch to using f-strings for logging
tetsuo-cpp Dec 22, 2022
deadd3c
Merge remote-tracking branch 'origin/main' into tuf-refactor
tetsuo-cpp Dec 22, 2022
b1fdc9f
test: document TUF staging mock better
jku Dec 22, 2022
cf4e46f
_internal/rekor: Mention updater arg in docsstrings
jku Dec 22, 2022
be7a6d7
_internal/tuf: Reword a TODO into a NOTE
jku Dec 22, 2022
b7c0bdb
_internal/tuf: Add nosec for mypy-related assert
jku Dec 22, 2022
e94d78c
Merge branch 'main' into tuf-refactor
woodruffw Dec 22, 2022
4e7f680
_internal/tuf: replace nosec with type ignore
woodruffw Dec 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,5 @@ build
!sigstore/_store/*.crt
!sigstore/_store/*.pem
!sigstore/_store/*.pub
!test/unit/assets/*.txt
!test/unit/assets/*.crt
!test/unit/assets/*.sig
!test/unit/assets/*.rekor
!test/unit/assets/*
!test/unit/assets/staging-tuf/*
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,11 @@ Sigstore instance options:
(default: https://rekor.sigstore.dev)
--rekor-root-pubkey FILE
A PEM-encoded root public key for Rekor itself
(conflicts with --staging) (default: rekor.pub
(embedded))
(conflicts with --staging) (default: None)
--fulcio-url URL The Fulcio instance to use (conflicts with --staging)
(default: https://fulcio.sigstore.dev)
--ctfe FILE A PEM-encoded public key for the CT log (conflicts
with --staging) (default: ctfe.pub (embedded))
with --staging) (default: None)
```
<!-- @end-sigstore-sign-help@ -->

Expand Down Expand Up @@ -198,8 +197,7 @@ Sigstore instance options:
(default: https://rekor.sigstore.dev)
--rekor-root-pubkey FILE
A PEM-encoded root public key for Rekor itself
(conflicts with --staging) (default: rekor.pub
(embedded))
(conflicts with --staging) (default: None)
```
<!-- @end-sigstore-verify-help@ -->

Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ classifiers = [
"Topic :: Security :: Cryptography",
]
dependencies = [
"appdirs ~= 1.4",
"cryptography >= 38",
"importlib_resources ~= 5.7; python_version < '3.11'",
"pydantic",
"pyjwt >= 2.1",
"pyOpenSSL >= 22.0.0",
"requests",
"securesystemslib",
"tuf >= 2.0.0",
]
requires-python = ">=3.7"

Expand Down
58 changes: 29 additions & 29 deletions sigstore/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,9 @@
RekorClient,
RekorEntry,
)
from sigstore._internal.tuf import TrustUpdater
from sigstore._sign import Signer
from sigstore._utils import (
SplitCertificateChainError,
load_pem_public_key,
read_embedded,
split_certificate_chain,
)
from sigstore._utils import SplitCertificateChainError, split_certificate_chain
from sigstore._verify import (
CertificateVerificationFailure,
RekorEntryMissing,
Expand All @@ -57,23 +53,12 @@
)

logger = logging.getLogger(__name__)
logging.basicConfig(level=os.environ.get("SIGSTORE_LOGLEVEL", "INFO").upper())


class _Embedded:
"""
A repr-wrapper for reading embedded resources, needed to help `argparse`
render defaults correctly.
"""

def __init__(self, name: str) -> None:
self._name = name

def read(self) -> bytes:
return read_embedded(self._name)
level = os.environ.get("SIGSTORE_LOGLEVEL", "INFO").upper()
logging.basicConfig(level=level)

def __repr__(self) -> str:
return f"{self._name} (embedded)"
# workaround to make tuf less verbose https://github.com/theupdateframework/python-tuf/pull/2243
if level == "INFO":
logging.getLogger("tuf").setLevel("WARNING")


def _boolify_env(envvar: str) -> bool:
Expand Down Expand Up @@ -116,7 +101,7 @@ def _add_shared_instance_options(group: argparse._ArgumentGroup) -> None:
metavar="FILE",
type=argparse.FileType("rb"),
help="A PEM-encoded root public key for Rekor itself (conflicts with --staging)",
default=os.getenv("SIGSTORE_REKOR_ROOT_PUBKEY", _Embedded("rekor.pub")),
default=os.getenv("SIGSTORE_REKOR_ROOT_PUBKEY"),
)


Expand Down Expand Up @@ -238,7 +223,7 @@ def _parser() -> argparse.ArgumentParser:
metavar="FILE",
type=argparse.FileType("rb"),
help="A PEM-encoded public key for the CT log (conflicts with --staging)",
default=os.getenv("SIGSTORE_CTFE", _Embedded("ctfe.pub")),
default=os.getenv("SIGSTORE_CTFE"),
)

sign.add_argument(
Expand Down Expand Up @@ -427,12 +412,21 @@ def _sign(args: argparse.Namespace) -> None:
elif args.fulcio_url == DEFAULT_FULCIO_URL and args.rekor_url == DEFAULT_REKOR_URL:
signer = Signer.production()
else:
ct_keyring = CTKeyring([load_pem_public_key(args.ctfe_pem.read())])
# Assume "production" keys if none are given as arguments
updater = TrustUpdater.production()
if args.ctfe_pem is not None:
ctfe_keys = [args.ctfe_pem.read()]
else:
ctfe_keys = updater.get_ctfe_keys()
if args.rekor_root_pubkey is not None:
rekor_key = args.rekor_root_pubkey.read()
else:
rekor_key = updater.get_rekor_key()

ct_keyring = CTKeyring(ctfe_keys)
signer = Signer(
fulcio=FulcioClient(args.fulcio_url),
rekor=RekorClient(
args.rekor_url, args.rekor_root_pubkey.read(), ct_keyring
),
rekor=RekorClient(args.rekor_url, rekor_key, ct_keyring),
)

# The order of precedence is as follows:
Expand Down Expand Up @@ -560,10 +554,16 @@ def _verify(args: argparse.Namespace) -> None:
except SplitCertificateChainError as error:
args._parser.error(f"Failed to parse certificate chain: {error}")

if args.rekor_root_pubkey is not None:
rekor_key = args.rekor_root_pubkey.read()
else:
updater = TrustUpdater.production()
rekor_key = updater.get_rekor_key()

verifier = Verifier(
rekor=RekorClient(
url=args.rekor_url,
pubkey=args.rekor_root_pubkey.read(),
pubkey=rekor_key,
# We don't use the CT keyring in verification so we can supply an empty keyring
ct_keyring=CTKeyring(),
),
Expand Down
45 changes: 4 additions & 41 deletions sigstore/_internal/ctfe.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec, rsa

from sigstore._utils import (
PublicKey,
key_id,
load_pem_public_key,
read_embedded,
)
from sigstore._utils import key_id, load_pem_public_key


class CTKeyringError(Exception):
Expand Down Expand Up @@ -58,48 +53,16 @@ class CTKeyring:
This structure exists to facilitate key rotation in a CT log.
"""

def __init__(self, keys: List[PublicKey] = []):
def __init__(self, keys: List[bytes] = []):
"""
Create a new `CTKeyring`, with `keys` as the initial set of signing
keys.
"""
self._keyring = {}
for key in keys:
for key_bytes in keys:
key = load_pem_public_key(key_bytes)
self._keyring[key_id(key)] = key

@classmethod
def staging(cls) -> CTKeyring:
"""
Returns a `CTKeyring` instance capable of verifying SCTs from
Sigstore's staging deployment.
"""
keyring = cls()
keyring._add_resource("ctfe.staging.pub")
keyring._add_resource("ctfe_2022.staging.pub")
keyring._add_resource("ctfe_2022.2.staging.pub")

return keyring

@classmethod
def production(cls) -> CTKeyring:
"""
Returns a `CTKeyring` instance capable of verifying SCTs from
Sigstore's production deployment.
"""
keyring = cls()
keyring._add_resource("ctfe.pub")
keyring._add_resource("ctfe_2022.pub")

return keyring

def _add_resource(self, name: str) -> None:
"""
Adds a key to the current keyring, as identified by its
resource name under `sigstore._store`.
"""
key_pem = read_embedded(name)
self.add(key_pem)

def add(self, key_pem: bytes) -> None:
"""
Adds a PEM-encoded key to the current keyring.
Expand Down
29 changes: 16 additions & 13 deletions sigstore/_internal/rekor/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,14 @@
from securesystemslib.formats import encode_canonical

from sigstore._internal.ctfe import CTKeyring
from sigstore._utils import base64_encode_pem_cert, read_embedded
from sigstore._internal.tuf import TrustUpdater
from sigstore._utils import base64_encode_pem_cert

logger = logging.getLogger(__name__)

DEFAULT_REKOR_URL = "https://rekor.sigstore.dev"
STAGING_REKOR_URL = "https://rekor.sigstage.dev"

_DEFAULT_REKOR_ROOT_PUBKEY = read_embedded("rekor.pub")
_STAGING_REKOR_ROOT_PUBKEY = read_embedded("rekor.staging.pub")

_DEFAULT_REKOR_CTFE_PUBKEY = read_embedded("ctfe.pub")
_STAGING_REKOR_CTFE_PUBKEY = read_embedded("ctfe.staging.pub")


class RekorBundle(BaseModel):
"""
Expand Down Expand Up @@ -466,20 +461,28 @@ def __del__(self) -> None:
self.session.close()

@classmethod
def production(cls) -> RekorClient:
def production(cls, updater: TrustUpdater) -> RekorClient:
"""
Returns a `RekorClient` populated with the default Rekor production instance.

updater must be a `TrustUpdater` for the production TUF repository.
"""
return cls(
DEFAULT_REKOR_URL, _DEFAULT_REKOR_ROOT_PUBKEY, CTKeyring.production()
)
rekor_key = updater.get_rekor_key()
ctfe_keys = updater.get_ctfe_keys()

return cls(DEFAULT_REKOR_URL, rekor_key, CTKeyring(ctfe_keys))

@classmethod
def staging(cls) -> RekorClient:
def staging(cls, updater: TrustUpdater) -> RekorClient:
"""
Returns a `RekorClient` populated with the default Rekor staging instance.

updater must be a `TrustUpdater` for the staging TUF repository.
"""
return cls(STAGING_REKOR_URL, _STAGING_REKOR_ROOT_PUBKEY, CTKeyring.staging())
rekor_key = updater.get_rekor_key()
ctfe_keys = updater.get_ctfe_keys()

return cls(STAGING_REKOR_URL, rekor_key, CTKeyring(ctfe_keys))

@property
def log(self) -> RekorLog:
Expand Down
Loading