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

Adapt to new sigstore api #630

Merged
merged 7 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ awskms = ["boto3", "botocore", "cryptography>=40.0.0"]
hsm = ["asn1crypto", "cryptography>=40.0.0", "PyKCS11"]
pynacl = ["pynacl>1.2.0"]
PySPX = ["PySPX>=0.5.0"]
sigstore = ["sigstore==1.1.2"]
sigstore = ["sigstore~=2.0"]

[tool.hatch.version]
path = "securesystemslib/__init__.py"
Expand Down
2 changes: 1 addition & 1 deletion requirements-sigstore.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sigstore==1.1.2
sigstore==2.0.0
65 changes: 50 additions & 15 deletions securesystemslib/signer/_sigstore_signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,23 +130,22 @@ class SigstoreSigner(Signer):

SCHEME = "sigstore"

def __init__(self, token: str, public_key: Key):
# TODO: Vet public key
# - signer eligible for keytype/scheme?
# - token matches identity/issuer?
def __init__(self, token: Any, public_key: Key):
self.public_key = public_key
# token is of type sigstore.oidc.IdentityToken but the module should be usable
# without sigstore so it's not annotated
jku marked this conversation as resolved.
Show resolved Hide resolved
self._token = token

@classmethod
def from_priv_key_uri(
cls,
priv_key_uri: str,
public_key: Key,
secrets_handler: Optional[SecretsHandler] = None,
_secrets_handler: Optional[SecretsHandler] = None,
jku marked this conversation as resolved.
Show resolved Hide resolved
) -> "SigstoreSigner":
# pylint: disable=import-outside-toplevel
try:
from sigstore.oidc import Issuer, detect_credential
from sigstore.oidc import IdentityToken, Issuer, detect_credential
except ImportError as e:
raise UnsupportedLibraryError(IMPORT_ERROR) from e

Expand All @@ -159,16 +158,32 @@ def from_priv_key_uri(
raise ValueError(f"SigstoreSigner does not support {priv_key_uri}")

params = dict(parse.parse_qsl(uri.query))
ambient = params.get("ambient", "true") == "true"

if params.get("ambient") == "false":
if not ambient:
jku marked this conversation as resolved.
Show resolved Hide resolved
# TODO: Restrict oauth flow to use identity/issuer from public_key
# TODO: Use secrets_handler for identity_token() secret arg
issuer = Issuer.production()
token = issuer.identity_token()
else:
# Note: this method signature only works with sigstore-python 1.1.2:
# dependencies must be updated when changing this
token = detect_credential("sigstore")
credential = detect_credential()
if not credential:
raise RuntimeError("Failed to detect Sigstore credentials")
token = IdentityToken(credential)

key_identity = public_key.keyval["identity"]
key_issuer = public_key.keyval["issuer"]
if key_issuer != token.expected_certificate_subject:
raise ValueError(
f"Signer identity issuer {token.expected_certificate_subject} "
f"did not match key: {key_issuer}"
)
# TODO: should check ambient identity too: unfortunately IdentityToken does
# not provide access to the expected identity value (cert SAN) in ambient case
if not ambient and key_identity != token.identity:
raise ValueError(
f"Signer identity {token.identity} did not match key: {key_identity}"
)

return cls(token, public_key)

Expand Down Expand Up @@ -200,6 +215,25 @@ def import_(

return uri, key

@classmethod
def import_via_auth(cls) -> Tuple[str, SigstoreKey]:
"""Create public key and signer URI by interactive authentication

Returns a private key URI (for Signer.from_priv_key_uri()) and a public
key. This method always uses the interactive authentication.
"""
# pylint: disable=import-outside-toplevel
try:
from sigstore.oidc import Issuer
except ImportError as e:
raise UnsupportedLibraryError(IMPORT_ERROR) from e

# authenticate to get the identity and issuer
token = Issuer.production().identity_token()
return cls.import_(
token.identity, token.expected_certificate_subject, False
)

def sign(self, payload: bytes) -> Signature:
"""Signs payload using the OIDC token on the signer instance.

Expand All @@ -217,14 +251,15 @@ def sign(self, payload: bytes) -> Signature:
"""
# pylint: disable=import-outside-toplevel
try:
from sigstore.sign import Signer as _Signer
from sigstore.sign import SigningContext
except ImportError as e:
raise UnsupportedLibraryError(IMPORT_ERROR) from e

signer = _Signer.production()
result = signer.sign(io.BytesIO(payload), self._token)
# TODO: Ask upstream if they can make this public
bundle = result._to_bundle() # pylint: disable=protected-access
context = SigningContext.production()
with context.signer(self._token) as sigstore_signer:
result = sigstore_signer.sign(io.BytesIO(payload))

bundle = result.to_bundle()

return Signature(
self.public_key.keyid,
Expand Down
Loading