From e5875d11a3928652d85cc247be5afedd1a23f1b0 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Tue, 9 Jan 2024 02:55:31 +0000 Subject: [PATCH 01/27] backup Signed-off-by: laurentsimon --- sigstore/_cli.py | 16 +++++++++++++++- sigstore/sign.py | 3 +++ sigstore/verify/models.py | 17 +++++++++++++---- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index da9714cba..46e7cb33d 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -688,7 +688,21 @@ def _sign(args: argparse.Namespace) -> None: logger.debug(f"signing for {file.name}") with file.open(mode="rb", buffering=0) as io: try: - result = signer.sign(input_=io) + # NOTE: This is where signin is performed. + import hashlib, io + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric.utils import Prehashed + class DIRHASH_SHARD1G_SHA256(hashes.HashAlgorithm): + name = "dirhash_shard1G_sha256" + digest_size = 32 + block_size = -1 + + hash = hashlib.sha256() + hash.update(iof.read()) + content = hash.digest() + contentio = io.BytesIO(content) + result = signer.sign(contentio, Prehashed(DIRHASH_SHARD1G_SHA256())) + #result = signer.sign(input_=iof) except ExpiredIdentity as exp_identity: print("Signature failed: identity token has expired") raise exp_identity diff --git a/sigstore/sign.py b/sigstore/sign.py index c12c12e6c..0ad934105 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -174,6 +174,9 @@ def _signing_cert( return certificate_response + # https://github.com/pyca/cryptography/blob/00f8304a3dfe7a2aab6f3150a3c620e87d848044/src/cryptography/hazmat/primitives/hashes.py + # https://github.com/pyca/cryptography/blob/00f8304a3dfe7a2aab6f3150a3c620e87d848044/src/cryptography/hazmat/primitives/asymmetric/utils.py#L14 + # https://github.com/pyca/cryptography/blob/main/src/cryptography/hazmat/primitives/asymmetric/rsa.py#L42 def sign( self, input_: IO[bytes] | Statement, diff --git a/sigstore/verify/models.py b/sigstore/verify/models.py index 3bb7d1eae..7e2581e4c 100644 --- a/sigstore/verify/models.py +++ b/sigstore/verify/models.py @@ -27,6 +27,7 @@ import rekor_types from cryptography.hazmat.primitives.serialization import Encoding +from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.x509 import ( Certificate, load_der_x509_certificate, @@ -61,7 +62,7 @@ base64_encode_pem_cert, cert_is_leaf, cert_is_root_ca, - sha256_streaming, + get_digest, ) from sigstore.errors import Error from sigstore.transparency import LogEntry, LogInclusionProof @@ -179,11 +180,17 @@ class VerificationMaterials: Represents the materials needed to perform a Sigstore verification. """ + digest_algorithm: bytes + """ + The digest algorithm to use for the hash. + """ + input_digest: bytes """ - The SHA256 hash of the verification input, as raw bytes. + The 'digest_algorithm' hash of the verification input, as raw bytes. """ + certificate: Certificate """ The certificate that attests to and contains the public signing key. @@ -232,6 +239,7 @@ def __init__( signature: bytes, offline: bool = False, rekor_entry: LogEntry | None, + algorithm_: Prehashed = None ): """ Create a new `VerificationMaterials` from the given materials. @@ -246,7 +254,7 @@ def __init__( Effect: `input_` is consumed as part of construction. """ - self.input_digest = sha256_streaming(input_) + self.input_digest, self.digest_algorithm = get_digest(input_, algorithm_) self.certificate = load_pem_x509_certificate(cert_pem.encode()) self.signature = signature @@ -416,7 +424,8 @@ def rekor_entry(self, client: RekorClient) -> LogEntry: ), data=rekor_types.hashedrekord.Data( hash=rekor_types.hashedrekord.Hash( - algorithm=rekor_types.hashedrekord.Algorithm.SHA256, + #algorithm=sigstore_rekor_types.Algorithm.SHA256, + algorithm=self.digest_algorithm, value=self.input_digest.hex(), ), ), From fd9af21609e386c43ccf902b34a89334f4940ab5 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Tue, 9 Jan 2024 16:05:05 +0000 Subject: [PATCH 02/27] backup Signed-off-by: laurentsimon --- sigstore/_utils.py | 18 +++++++++++++++++- sigstore/sign.py | 4 ++++ sigstore/verify/models.py | 4 ++-- sigstore/verify/verifier.py | 3 ++- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/sigstore/_utils.py b/sigstore/_utils.py index 0e4463566..84524439a 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -23,8 +23,10 @@ import sys from typing import IO, NewType, Union -from cryptography.hazmat.primitives import serialization +import rekor_types +from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.x509 import ( Certificate, ExtensionNotFound, @@ -157,6 +159,20 @@ def key_id(key: PublicKey) -> KeyID: return KeyID(hashlib.sha256(public_bytes).digest()) +def get_digest( + input_: IO[bytes], + algorithm_: Prehashed = None, + ) -> (bytes, Prehashed): + if algorithm_ is None: + return sha256_streaming(input_), Prehashed(hashes.SHA256()) + + if isinstance(algorithm_, Prehashed): + # Check we have a 256-bit digest size for compatibility with secp256r1. + if algorithm_.digest_size() != 32: + return ValueError(f"invalid digest size ({algorithm_.digest_size()}), expected 32") + return input_.getvalue(), algorithm_ + + raise ValueError("invalid arguments") def sha256_streaming(io: IO[bytes]) -> bytes: """ diff --git a/sigstore/sign.py b/sigstore/sign.py index 0ad934105..d5bedb553 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -82,7 +82,11 @@ from sigstore._internal.rekor.client import RekorClient from sigstore._internal.sct import verify_sct from sigstore._internal.trustroot import TrustedRoot +<<<<<<< HEAD from sigstore._utils import PEMCert, sha256_streaming +======= +from sigstore._utils import B64Str, HexStr, PEMCert, get_digest +>>>>>>> 442469b (backup) from sigstore.oidc import ExpiredIdentity, IdentityToken from sigstore.transparency import LogEntry diff --git a/sigstore/verify/models.py b/sigstore/verify/models.py index 7e2581e4c..3a025d1d6 100644 --- a/sigstore/verify/models.py +++ b/sigstore/verify/models.py @@ -180,7 +180,7 @@ class VerificationMaterials: Represents the materials needed to perform a Sigstore verification. """ - digest_algorithm: bytes + digest_algorithm: Prehashed """ The digest algorithm to use for the hash. """ @@ -425,7 +425,7 @@ def rekor_entry(self, client: RekorClient) -> LogEntry: data=rekor_types.hashedrekord.Data( hash=rekor_types.hashedrekord.Hash( #algorithm=sigstore_rekor_types.Algorithm.SHA256, - algorithm=self.digest_algorithm, + algorithm=self.digest_algorithm._algorithm.name, value=self.input_digest.hex(), ), ), diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 999e40b27..2c7641af1 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -226,7 +226,8 @@ def verify( signing_key.verify( materials.signature, materials.input_digest, - ec.ECDSA(Prehashed(hashes.SHA256())), + ec.ECDSA(materials.digest_algorithm), + #ec.ECDSA(Prehashed(hashes.SHA256())), ) except InvalidSignature: return VerificationFailure(reason="Signature is invalid for input") From daea2f02f3c42dc7b1df73d7cd6cd398eac9c655 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Tue, 9 Jan 2024 18:37:59 +0000 Subject: [PATCH 03/27] backup Signed-off-by: laurentsimon --- sigstore/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/_utils.py b/sigstore/_utils.py index 84524439a..6684fdcc6 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -168,7 +168,7 @@ def get_digest( if isinstance(algorithm_, Prehashed): # Check we have a 256-bit digest size for compatibility with secp256r1. - if algorithm_.digest_size() != 32: + if algorithm_.digest_size != 32: return ValueError(f"invalid digest size ({algorithm_.digest_size()}), expected 32") return input_.getvalue(), algorithm_ From 97bb8f0210e5a47ed78c62b663eabc0d0395e8d4 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Tue, 9 Jan 2024 21:15:57 +0000 Subject: [PATCH 04/27] backup Signed-off-by: laurentsimon --- sigstore/sign.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sigstore/sign.py b/sigstore/sign.py index d5bedb553..cc628c1a8 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -178,6 +178,7 @@ def _signing_cert( return certificate_response + # https://github.com/sigstore/rekor/issues/1299 # https://github.com/pyca/cryptography/blob/00f8304a3dfe7a2aab6f3150a3c620e87d848044/src/cryptography/hazmat/primitives/hashes.py # https://github.com/pyca/cryptography/blob/00f8304a3dfe7a2aab6f3150a3c620e87d848044/src/cryptography/hazmat/primitives/asymmetric/utils.py#L14 # https://github.com/pyca/cryptography/blob/main/src/cryptography/hazmat/primitives/asymmetric/rsa.py#L42 From 479bbad95709d6eb0581501154fb319e55ec9741 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Tue, 9 Jan 2024 21:20:35 +0000 Subject: [PATCH 05/27] remove test code in cli Signed-off-by: laurentsimon --- sigstore/_cli.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index 46e7cb33d..304583f5c 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -688,21 +688,7 @@ def _sign(args: argparse.Namespace) -> None: logger.debug(f"signing for {file.name}") with file.open(mode="rb", buffering=0) as io: try: - # NOTE: This is where signin is performed. - import hashlib, io - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.asymmetric.utils import Prehashed - class DIRHASH_SHARD1G_SHA256(hashes.HashAlgorithm): - name = "dirhash_shard1G_sha256" - digest_size = 32 - block_size = -1 - - hash = hashlib.sha256() - hash.update(iof.read()) - content = hash.digest() - contentio = io.BytesIO(content) - result = signer.sign(contentio, Prehashed(DIRHASH_SHARD1G_SHA256())) - #result = signer.sign(input_=iof) + result = signer.sign(input_=iof) except ExpiredIdentity as exp_identity: print("Signature failed: identity token has expired") raise exp_identity From 027087e29ba2fabcfbaffddfd6069958635819d1 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Tue, 9 Jan 2024 21:20:55 +0000 Subject: [PATCH 06/27] Fix typo Signed-off-by: laurentsimon --- sigstore/_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index 304583f5c..da9714cba 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -688,7 +688,7 @@ def _sign(args: argparse.Namespace) -> None: logger.debug(f"signing for {file.name}") with file.open(mode="rb", buffering=0) as io: try: - result = signer.sign(input_=iof) + result = signer.sign(input_=io) except ExpiredIdentity as exp_identity: print("Signature failed: identity token has expired") raise exp_identity From 54d0fe07f7d9ccc35c37ac114d5761fc4182116c Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Wed, 10 Jan 2024 14:05:26 +0000 Subject: [PATCH 07/27] cleanup Signed-off-by: laurentsimon --- sigstore/_cli.py | 2 +- sigstore/_utils.py | 7 +++---- sigstore/sign.py | 11 +---------- sigstore/verify/models.py | 5 ++--- sigstore/verify/verifier.py | 3 --- 5 files changed, 7 insertions(+), 21 deletions(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index da9714cba..869a255c7 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -686,7 +686,7 @@ def _sign(args: argparse.Namespace) -> None: with signing_ctx.signer(identity) as signer: for file, outputs in output_map.items(): logger.debug(f"signing for {file.name}") - with file.open(mode="rb", buffering=0) as io: + with file.open(mode="rb", buffering=0) as fio: try: result = signer.sign(input_=io) except ExpiredIdentity as exp_identity: diff --git a/sigstore/_utils.py b/sigstore/_utils.py index 6684fdcc6..e00dfec50 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -23,8 +23,7 @@ import sys from typing import IO, NewType, Union -import rekor_types -from cryptography.hazmat.primitives import serialization, hashes +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, rsa from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.x509 import ( @@ -162,7 +161,7 @@ def key_id(key: PublicKey) -> KeyID: def get_digest( input_: IO[bytes], algorithm_: Prehashed = None, - ) -> (bytes, Prehashed): + ) -> (bytes, Prehashed): if algorithm_ is None: return sha256_streaming(input_), Prehashed(hashes.SHA256()) @@ -171,7 +170,7 @@ def get_digest( if algorithm_.digest_size != 32: return ValueError(f"invalid digest size ({algorithm_.digest_size()}), expected 32") return input_.getvalue(), algorithm_ - + raise ValueError("invalid arguments") def sha256_streaming(io: IO[bytes]) -> bytes: diff --git a/sigstore/sign.py b/sigstore/sign.py index cc628c1a8..7563286b7 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -57,7 +57,6 @@ VerificationMaterial, ) from sigstore_protobuf_specs.dev.sigstore.common.v1 import ( - HashAlgorithm, HashOutput, LogId, MessageSignature, @@ -82,11 +81,7 @@ from sigstore._internal.rekor.client import RekorClient from sigstore._internal.sct import verify_sct from sigstore._internal.trustroot import TrustedRoot -<<<<<<< HEAD -from sigstore._utils import PEMCert, sha256_streaming -======= -from sigstore._utils import B64Str, HexStr, PEMCert, get_digest ->>>>>>> 442469b (backup) +from sigstore._utils import PEMCert, get_digest, sha256_streaming from sigstore.oidc import ExpiredIdentity, IdentityToken from sigstore.transparency import LogEntry @@ -178,10 +173,6 @@ def _signing_cert( return certificate_response - # https://github.com/sigstore/rekor/issues/1299 - # https://github.com/pyca/cryptography/blob/00f8304a3dfe7a2aab6f3150a3c620e87d848044/src/cryptography/hazmat/primitives/hashes.py - # https://github.com/pyca/cryptography/blob/00f8304a3dfe7a2aab6f3150a3c620e87d848044/src/cryptography/hazmat/primitives/asymmetric/utils.py#L14 - # https://github.com/pyca/cryptography/blob/main/src/cryptography/hazmat/primitives/asymmetric/rsa.py#L42 def sign( self, input_: IO[bytes] | Statement, diff --git a/sigstore/verify/models.py b/sigstore/verify/models.py index 3a025d1d6..8946a79e6 100644 --- a/sigstore/verify/models.py +++ b/sigstore/verify/models.py @@ -26,8 +26,8 @@ from typing import IO import rekor_types -from cryptography.hazmat.primitives.serialization import Encoding from cryptography.hazmat.primitives.asymmetric.utils import Prehashed +from cryptography.hazmat.primitives.serialization import Encoding from cryptography.x509 import ( Certificate, load_der_x509_certificate, @@ -184,7 +184,7 @@ class VerificationMaterials: """ The digest algorithm to use for the hash. """ - + input_digest: bytes """ The 'digest_algorithm' hash of the verification input, as raw bytes. @@ -424,7 +424,6 @@ def rekor_entry(self, client: RekorClient) -> LogEntry: ), data=rekor_types.hashedrekord.Data( hash=rekor_types.hashedrekord.Hash( - #algorithm=sigstore_rekor_types.Algorithm.SHA256, algorithm=self.digest_algorithm._algorithm.name, value=self.input_digest.hex(), ), diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 2c7641af1..c917b0f11 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -24,9 +24,7 @@ from typing import List, cast from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.x509 import Certificate, ExtendedKeyUsage, KeyUsage from cryptography.x509.oid import ExtendedKeyUsageOID from OpenSSL.crypto import ( @@ -227,7 +225,6 @@ def verify( materials.signature, materials.input_digest, ec.ECDSA(materials.digest_algorithm), - #ec.ECDSA(Prehashed(hashes.SHA256())), ) except InvalidSignature: return VerificationFailure(reason="Signature is invalid for input") From 31c1cceca032ea1e257944c482c7bdda1230cf1c Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Wed, 10 Jan 2024 14:08:45 +0000 Subject: [PATCH 08/27] Fix typo Signed-off-by: laurentsimon --- sigstore/_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index 869a255c7..da9714cba 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -686,7 +686,7 @@ def _sign(args: argparse.Namespace) -> None: with signing_ctx.signer(identity) as signer: for file, outputs in output_map.items(): logger.debug(f"signing for {file.name}") - with file.open(mode="rb", buffering=0) as fio: + with file.open(mode="rb", buffering=0) as io: try: result = signer.sign(input_=io) except ExpiredIdentity as exp_identity: From 9f20e2876e16c9b92756518bb95989ae32a5e642 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Wed, 10 Jan 2024 15:31:45 +0000 Subject: [PATCH 09/27] Add hash name conversion Signed-off-by: laurentsimon --- sigstore/_utils.py | 7 +++++++ sigstore/verify/models.py | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/sigstore/_utils.py b/sigstore/_utils.py index e00dfec50..7c92608ba 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -35,6 +35,7 @@ from cryptography.x509.oid import ExtendedKeyUsageOID, ExtensionOID from sigstore.errors import Error +from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm if sys.version_info < (3, 11): import importlib_resources as resources @@ -158,6 +159,12 @@ def key_id(key: PublicKey) -> KeyID: return KeyID(hashlib.sha256(public_bytes).digest()) +def hazmat_digest_to_bundle(algo: str): + lookup = {"sha256": HashAlgorithm.SHA2_256} + if algo in lookup: + return lookup[algo] + return algo + def get_digest( input_: IO[bytes], algorithm_: Prehashed = None, diff --git a/sigstore/verify/models.py b/sigstore/verify/models.py index 8946a79e6..b26c77d6c 100644 --- a/sigstore/verify/models.py +++ b/sigstore/verify/models.py @@ -63,6 +63,7 @@ cert_is_leaf, cert_is_root_ca, get_digest, + hazmat_digest_to_bundle, ) from sigstore.errors import Error from sigstore.transparency import LogEntry, LogInclusionProof @@ -518,7 +519,7 @@ def to_bundle(self) -> Bundle: ), message_signature=MessageSignature( message_digest=HashOutput( - algorithm=HashAlgorithm.SHA2_256, + algorithm=hazmat_digest_to_bundle(self.digest_algorithm._algorithm.name), digest=self.input_digest, ), signature=self.signature, From fe29ba8c3e95333e0284a28145546621c1a08c76 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Wed, 10 Jan 2024 15:45:23 +0000 Subject: [PATCH 10/27] cleanup Signed-off-by: laurentsimon --- sigstore/_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sigstore/_utils.py b/sigstore/_utils.py index 7c92608ba..cb378c934 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -159,11 +159,11 @@ def key_id(key: PublicKey) -> KeyID: return KeyID(hashlib.sha256(public_bytes).digest()) -def hazmat_digest_to_bundle(algo: str): - lookup = {"sha256": HashAlgorithm.SHA2_256} +def hazmat_digest_to_bundle(algo: str) -> HashAlgorithm: + lookup = {hashes.SHA256().name: HashAlgorithm.SHA2_256} if algo in lookup: - return lookup[algo] - return algo + return HashAlgorithm(lookup[algo]) + return ValueError(f"unknown digest algorithm {algo}") def get_digest( input_: IO[bytes], From 857ed2a872277906e57c8977d9dfd507035c384f Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Wed, 10 Jan 2024 15:50:20 +0000 Subject: [PATCH 11/27] linter Signed-off-by: laurentsimon --- sigstore/_utils.py | 2 +- sigstore/verify/models.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/sigstore/_utils.py b/sigstore/_utils.py index cb378c934..2d144c449 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -33,9 +33,9 @@ load_der_x509_certificate, ) from cryptography.x509.oid import ExtendedKeyUsageOID, ExtensionOID +from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm from sigstore.errors import Error -from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm if sys.version_info < (3, 11): import importlib_resources as resources diff --git a/sigstore/verify/models.py b/sigstore/verify/models.py index b26c77d6c..8e5129475 100644 --- a/sigstore/verify/models.py +++ b/sigstore/verify/models.py @@ -39,7 +39,6 @@ VerificationMaterial, ) from sigstore_protobuf_specs.dev.sigstore.common.v1 import ( - HashAlgorithm, HashOutput, LogId, MessageSignature, From 64822e68a7f5541fe74a6fde26d1c80f350dcdef Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Wed, 10 Jan 2024 15:59:11 +0000 Subject: [PATCH 12/27] Fix Signed-off-by: laurentsimon --- sigstore/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/_utils.py b/sigstore/_utils.py index 2d144c449..e7ec32143 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -175,7 +175,7 @@ def get_digest( if isinstance(algorithm_, Prehashed): # Check we have a 256-bit digest size for compatibility with secp256r1. if algorithm_.digest_size != 32: - return ValueError(f"invalid digest size ({algorithm_.digest_size()}), expected 32") + return ValueError(f"invalid digest size ({algorithm_.digest_size}), expected 32") return input_.getvalue(), algorithm_ raise ValueError("invalid arguments") From a0c269799f4294eaa349657e4bce7cb47a442617 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Fri, 12 Jan 2024 01:55:17 +0000 Subject: [PATCH 13/27] Change API Signed-off-by: laurentsimon --- sigstore/_utils.py | 34 ++++++++++--------------- sigstore/hashes.py | 50 +++++++++++++++++++++++++++++++++++++ sigstore/sign.py | 3 ++- sigstore/verify/models.py | 26 +++++++------------ sigstore/verify/verifier.py | 6 ++--- 5 files changed, 77 insertions(+), 42 deletions(-) create mode 100644 sigstore/hashes.py diff --git a/sigstore/_utils.py b/sigstore/_utils.py index e7ec32143..6a103e088 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -23,8 +23,9 @@ import sys from typing import IO, NewType, Union -from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec, rsa +<<<<<<< HEAD from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.x509 import ( Certificate, @@ -32,9 +33,13 @@ Version, load_der_x509_certificate, ) +======= +from cryptography.x509 import Certificate, ExtensionNotFound, Version +>>>>>>> f67b019 (Change API) from cryptography.x509.oid import ExtendedKeyUsageOID, ExtensionOID from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm +from sigstore import hashes as sigstore_hashes from sigstore.errors import Error if sys.version_info < (3, 11): @@ -159,26 +164,13 @@ def key_id(key: PublicKey) -> KeyID: return KeyID(hashlib.sha256(public_bytes).digest()) -def hazmat_digest_to_bundle(algo: str) -> HashAlgorithm: - lookup = {hashes.SHA256().name: HashAlgorithm.SHA2_256} - if algo in lookup: - return HashAlgorithm(lookup[algo]) - return ValueError(f"unknown digest algorithm {algo}") - -def get_digest( - input_: IO[bytes], - algorithm_: Prehashed = None, - ) -> (bytes, Prehashed): - if algorithm_ is None: - return sha256_streaming(input_), Prehashed(hashes.SHA256()) - - if isinstance(algorithm_, Prehashed): - # Check we have a 256-bit digest size for compatibility with secp256r1. - if algorithm_.digest_size != 32: - return ValueError(f"invalid digest size ({algorithm_.digest_size}), expected 32") - return input_.getvalue(), algorithm_ - - raise ValueError("invalid arguments") +def get_digest(input_: IO[bytes] | sigstore_hashes.Hashed) -> sigstore_hashes.Hashed: + if isinstance(input_, sigstore_hashes.Hashed): + return input_ + + # NOTE: Not able to check for the type `TypeError: Subscripted generics cannot be used with class and instance checks` + # when calling isinstance(_input, IO[bytes]) + return sigstore_hashes.Hashed(digest=sha256_streaming(input_), algorithm=HashAlgorithm.SHA2_256) def sha256_streaming(io: IO[bytes]) -> bytes: """ diff --git a/sigstore/hashes.py b/sigstore/hashes.py new file mode 100644 index 000000000..e4afa5d17 --- /dev/null +++ b/sigstore/hashes.py @@ -0,0 +1,50 @@ + +# Copyright 2023 The Sigstore Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric.utils import Prehashed +from pydantic import BaseModel +from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm + + +class DIRSHA256_P1(hashes.HashAlgorithm): + name = "dirsha256-p1" + digest_size = 32 + block_size = -1 + +class Hashed(BaseModel): + """ + Represents hashed value. + """ + + algorithm: HashAlgorithm + """ + The digest algorithm uses to compute the digest. + """ + + digest: bytes + """ + The digest representing the hash value. + """ + + def as_prehashed(self) -> Prehashed: + return Prehashed(self.hazmat_algorithm()) + + def hazmat_algorithm(self) -> hashes.HashAlgorithm: + if self.algorithm == HashAlgorithm.SHA2_256: + return hashes.SHA256() + if self.algorithm == HashAlgorithm.HASH_ALGORITHM_UNSPECIFIED: + return DIRSHA256_P1() + raise ValueError(f"unknown hash algorithm: {self.algorithm}") diff --git a/sigstore/sign.py b/sigstore/sign.py index 7563286b7..91f1993fe 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -49,7 +49,6 @@ import rekor_types from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.x509.oid import NameOID from in_toto_attestation.v1.statement import Statement from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import ( @@ -57,6 +56,7 @@ VerificationMaterial, ) from sigstore_protobuf_specs.dev.sigstore.common.v1 import ( + HashAlgorithm, HashOutput, LogId, MessageSignature, @@ -73,6 +73,7 @@ from sigstore_protobuf_specs.io.intoto import Envelope from sigstore._internal import dsse +from sigstore import hashes as sigstore_hashes from sigstore._internal.fulcio import ( ExpiredCertificate, FulcioCertificateSigningResponse, diff --git a/sigstore/verify/models.py b/sigstore/verify/models.py index 8e5129475..3024ee30e 100644 --- a/sigstore/verify/models.py +++ b/sigstore/verify/models.py @@ -26,7 +26,6 @@ from typing import IO import rekor_types -from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.hazmat.primitives.serialization import Encoding from cryptography.x509 import ( Certificate, @@ -54,6 +53,7 @@ TransparencyLogEntry, ) +from sigstore import hashes as sigstore_hashes from sigstore._internal.rekor import RekorClient from sigstore._utils import ( B64Str, @@ -62,7 +62,6 @@ cert_is_leaf, cert_is_root_ca, get_digest, - hazmat_digest_to_bundle, ) from sigstore.errors import Error from sigstore.transparency import LogEntry, LogInclusionProof @@ -180,17 +179,11 @@ class VerificationMaterials: Represents the materials needed to perform a Sigstore verification. """ - digest_algorithm: Prehashed + hashed_input: sigstore_hashes.Hashed """ - The digest algorithm to use for the hash. + The hash of the verification input. """ - input_digest: bytes - """ - The 'digest_algorithm' hash of the verification input, as raw bytes. - """ - - certificate: Certificate """ The certificate that attests to and contains the public signing key. @@ -234,12 +227,11 @@ class VerificationMaterials: def __init__( self, *, - input_: IO[bytes], + input_: IO[bytes] | sigstore_hashes.Hashed, cert_pem: PEMCert, signature: bytes, offline: bool = False, rekor_entry: LogEntry | None, - algorithm_: Prehashed = None ): """ Create a new `VerificationMaterials` from the given materials. @@ -254,7 +246,7 @@ def __init__( Effect: `input_` is consumed as part of construction. """ - self.input_digest, self.digest_algorithm = get_digest(input_, algorithm_) + self.hashed_input = get_digest(input_) self.certificate = load_pem_x509_certificate(cert_pem.encode()) self.signature = signature @@ -424,8 +416,8 @@ def rekor_entry(self, client: RekorClient) -> LogEntry: ), data=rekor_types.hashedrekord.Data( hash=rekor_types.hashedrekord.Hash( - algorithm=self.digest_algorithm._algorithm.name, - value=self.input_digest.hex(), + algorithm=self.hashed_input.hazmat_algorithm().name, + value=self.hashed_input.digest.hex(), ), ), ), @@ -518,8 +510,8 @@ def to_bundle(self) -> Bundle: ), message_signature=MessageSignature( message_digest=HashOutput( - algorithm=hazmat_digest_to_bundle(self.digest_algorithm._algorithm.name), - digest=self.input_digest, + algorithm=self.hashed_input.algorithm, + digest=self.hashed_input.digest, ), signature=self.signature, ), diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index c917b0f11..1c4b343fb 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -223,8 +223,8 @@ def verify( signing_key = cast(ec.EllipticCurvePublicKey, signing_key) signing_key.verify( materials.signature, - materials.input_digest, - ec.ECDSA(materials.digest_algorithm), + materials.hashed_input.digest, + ec.ECDSA(materials.hashed_input.as_prehashed()), ) except InvalidSignature: return VerificationFailure(reason="Signature is invalid for input") @@ -239,7 +239,7 @@ def verify( except RekorEntryMissingError: return LogEntryMissing( signature=B64Str(base64.b64encode(materials.signature).decode()), - artifact_hash=HexStr(materials.input_digest.hex()), + artifact_hash=HexStr(materials.hashed_input.digest.hex()), ) except InvalidRekorEntryError: return VerificationFailure( From 0271ef194d3c7a520dd835ae3c010ca61acdc6fe Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Fri, 12 Jan 2024 02:05:26 +0000 Subject: [PATCH 14/27] Remove dirhash example code Signed-off-by: laurentsimon --- sigstore/hashes.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/sigstore/hashes.py b/sigstore/hashes.py index e4afa5d17..580d91ba5 100644 --- a/sigstore/hashes.py +++ b/sigstore/hashes.py @@ -19,11 +19,6 @@ from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm -class DIRSHA256_P1(hashes.HashAlgorithm): - name = "dirsha256-p1" - digest_size = 32 - block_size = -1 - class Hashed(BaseModel): """ Represents hashed value. @@ -45,6 +40,5 @@ def as_prehashed(self) -> Prehashed: def hazmat_algorithm(self) -> hashes.HashAlgorithm: if self.algorithm == HashAlgorithm.SHA2_256: return hashes.SHA256() - if self.algorithm == HashAlgorithm.HASH_ALGORITHM_UNSPECIFIED: - return DIRSHA256_P1() + # Add more hashes here. raise ValueError(f"unknown hash algorithm: {self.algorithm}") From 3831c3d3600e489f23bc0707cc095bdf3849df32 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Fri, 12 Jan 2024 02:09:07 +0000 Subject: [PATCH 15/27] typo Signed-off-by: laurentsimon --- sigstore/hashes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/hashes.py b/sigstore/hashes.py index 580d91ba5..419d4a862 100644 --- a/sigstore/hashes.py +++ b/sigstore/hashes.py @@ -21,7 +21,7 @@ class Hashed(BaseModel): """ - Represents hashed value. + Represents a hashed value. """ algorithm: HashAlgorithm From eb13ae9eaa8641274b9e37f6026902e3530494fa Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Fri, 12 Jan 2024 15:56:47 +0000 Subject: [PATCH 16/27] comments Signed-off-by: laurentsimon --- sigstore/_utils.py | 8 +++++--- sigstore/hashes.py | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/sigstore/_utils.py b/sigstore/_utils.py index 6a103e088..03ed438fc 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -164,13 +164,15 @@ def key_id(key: PublicKey) -> KeyID: return KeyID(hashlib.sha256(public_bytes).digest()) + def get_digest(input_: IO[bytes] | sigstore_hashes.Hashed) -> sigstore_hashes.Hashed: if isinstance(input_, sigstore_hashes.Hashed): return input_ - # NOTE: Not able to check for the type `TypeError: Subscripted generics cannot be used with class and instance checks` - # when calling isinstance(_input, IO[bytes]) - return sigstore_hashes.Hashed(digest=sha256_streaming(input_), algorithm=HashAlgorithm.SHA2_256) + return sigstore_hashes.Hashed( + digest=sha256_streaming(input_), algorithm=HashAlgorithm.SHA2_256 + ) + def sha256_streaming(io: IO[bytes]) -> bytes: """ diff --git a/sigstore/hashes.py b/sigstore/hashes.py index 419d4a862..1f991ac9a 100644 --- a/sigstore/hashes.py +++ b/sigstore/hashes.py @@ -1,4 +1,3 @@ - # Copyright 2023 The Sigstore Authors # # Licensed under the Apache License, Version 2.0 (the "License"); From 9510d202b4524fd86252b3c911149c03fe106f20 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Wed, 17 Jan 2024 22:43:24 +0000 Subject: [PATCH 17/27] rebase Signed-off-by: laurentsimon --- sigstore/_utils.py | 5 ----- sigstore/hashes.py | 13 ++++++++----- sigstore/sign.py | 19 +++++++++---------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/sigstore/_utils.py b/sigstore/_utils.py index 03ed438fc..c7bf43720 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -25,17 +25,12 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec, rsa -<<<<<<< HEAD -from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.x509 import ( Certificate, ExtensionNotFound, Version, load_der_x509_certificate, ) -======= -from cryptography.x509 import Certificate, ExtensionNotFound, Version ->>>>>>> f67b019 (Change API) from cryptography.x509.oid import ExtendedKeyUsageOID, ExtensionOID from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm diff --git a/sigstore/hashes.py b/sigstore/hashes.py index 1f991ac9a..b1af6b2c3 100644 --- a/sigstore/hashes.py +++ b/sigstore/hashes.py @@ -15,6 +15,7 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from pydantic import BaseModel +import rekor_types from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm @@ -33,11 +34,13 @@ class Hashed(BaseModel): The digest representing the hash value. """ - def as_prehashed(self) -> Prehashed: - return Prehashed(self.hazmat_algorithm()) + def as_hashedrekord_algorithm(self) -> rekor_types.hashedrekord.Algorithm: + if self.algorithm == HashAlgorithm.SHA2_256: + return rekor_types.hashedrekord.Algorithm.SHA256 + raise ValueError(f"unknown hash algorithm: {self.algorithm}") - def hazmat_algorithm(self) -> hashes.HashAlgorithm: + def as_prehashed(self) -> Prehashed: if self.algorithm == HashAlgorithm.SHA2_256: - return hashes.SHA256() - # Add more hashes here. + return Prehashed(hashes.SHA256()) raise ValueError(f"unknown hash algorithm: {self.algorithm}") + diff --git a/sigstore/sign.py b/sigstore/sign.py index 91f1993fe..45bce7296 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -56,7 +56,6 @@ VerificationMaterial, ) from sigstore_protobuf_specs.dev.sigstore.common.v1 import ( - HashAlgorithm, HashOutput, LogId, MessageSignature, @@ -72,8 +71,8 @@ ) from sigstore_protobuf_specs.io.intoto import Envelope -from sigstore._internal import dsse from sigstore import hashes as sigstore_hashes +from sigstore._internal import dsse from sigstore._internal.fulcio import ( ExpiredCertificate, FulcioCertificateSigningResponse, @@ -82,7 +81,7 @@ from sigstore._internal.rekor.client import RekorClient from sigstore._internal.sct import verify_sct from sigstore._internal.trustroot import TrustedRoot -from sigstore._utils import PEMCert, get_digest, sha256_streaming +from sigstore._utils import PEMCert, get_digest from sigstore.oidc import ExpiredIdentity, IdentityToken from sigstore.transparency import LogEntry @@ -176,7 +175,7 @@ def _signing_cert( def sign( self, - input_: IO[bytes] | Statement, + input_: IO[bytes] | Statement | sigstore_hashes.Hashed, ) -> Bundle: """Public API for signing blobs""" private_key = self._private_key @@ -219,16 +218,16 @@ def sign( ), ) else: - input_digest = sha256_streaming(input_) + hashed_input = get_digest(input_) artifact_signature = private_key.sign( - input_digest, ec.ECDSA(Prehashed(hashes.SHA256())) + hashed_input.digest, ec.ECDSA(hashed_input.as_prehashed()) ) content = MessageSignature( message_digest=HashOutput( - algorithm=HashAlgorithm.SHA2_256, - digest=input_digest, + algorithm=hashed_input.algorithm, + digest=hashed_input.digest.hex(), ), signature=artifact_signature, ) @@ -244,8 +243,8 @@ def sign( ), data=rekor_types.hashedrekord.Data( hash=rekor_types.hashedrekord.Hash( - algorithm=rekor_types.hashedrekord.Algorithm.SHA256, - value=input_digest.hex(), + algorithm=hashed_input.as_hashedrekord_algorithm(), + value=hashed_input.digest.hex(), ) ), ), From a99e1e6d7d6c1f351c5ab9cfd6ec9de7a774cc8b Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Wed, 17 Jan 2024 22:51:08 +0000 Subject: [PATCH 18/27] Fix Signed-off-by: laurentsimon --- sigstore/verify/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sigstore/verify/models.py b/sigstore/verify/models.py index 3024ee30e..22aac1587 100644 --- a/sigstore/verify/models.py +++ b/sigstore/verify/models.py @@ -416,7 +416,7 @@ def rekor_entry(self, client: RekorClient) -> LogEntry: ), data=rekor_types.hashedrekord.Data( hash=rekor_types.hashedrekord.Hash( - algorithm=self.hashed_input.hazmat_algorithm().name, + algorithm=self.hashed_input.as_hashedrekord_algorithm(), value=self.hashed_input.digest.hex(), ), ), @@ -511,7 +511,7 @@ def to_bundle(self) -> Bundle: message_signature=MessageSignature( message_digest=HashOutput( algorithm=self.hashed_input.algorithm, - digest=self.hashed_input.digest, + digest=self.hashed_input.digest.hex(), ), signature=self.signature, ), From ef820de8ea3ca2d87710bb8040730e59c9218b5c Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Thu, 18 Jan 2024 14:18:58 +0000 Subject: [PATCH 19/27] Fix hash Signed-off-by: laurentsimon --- sigstore/sign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/sign.py b/sigstore/sign.py index 45bce7296..5e14e0e47 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -227,7 +227,7 @@ def sign( content = MessageSignature( message_digest=HashOutput( algorithm=hashed_input.algorithm, - digest=hashed_input.digest.hex(), + digest=hashed_input.digest, ), signature=artifact_signature, ) From a3b6d9a4c100ebf5ea88fbc0857d4f53c16383bc Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Thu, 18 Jan 2024 14:26:39 +0000 Subject: [PATCH 20/27] Update CHANGELOG Signed-off-by: laurentsimon --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22a0d49a6..9befd729b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,14 @@ All versions prior to 0.9.0 are untracked. ### Added +* API: `Signer.sign()` can now take a `Hashed` as an input, + performing a signature on a pre-computed hash value + ([#860](https://github.com/sigstore/sigstore-python/pull/860)) + * API: `Signer.sign()` can now take an in-toto `Statement` as an input, producing a DSSE-formatted signature rather than a "bare" signature ([#804](https://github.com/sigstore/sigstore-python/pull/804)) - * API: `SigningResult.content` has been added, representing either the `hashedrekord` entry's message signature or the `dsse` entry's envelope ([#804](https://github.com/sigstore/sigstore-python/pull/804)) From 09fa64840666766b5387cae4005b8d0815c5b989 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Thu, 18 Jan 2024 14:29:28 +0000 Subject: [PATCH 21/27] Fix Signed-off-by: laurentsimon --- sigstore/verify/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/verify/models.py b/sigstore/verify/models.py index 22aac1587..a80dd479c 100644 --- a/sigstore/verify/models.py +++ b/sigstore/verify/models.py @@ -511,7 +511,7 @@ def to_bundle(self) -> Bundle: message_signature=MessageSignature( message_digest=HashOutput( algorithm=self.hashed_input.algorithm, - digest=self.hashed_input.digest.hex(), + digest=self.hashed_input.digest, ), signature=self.signature, ), From e583a59025910fa2339b3af4b4adb9d26d0501f5 Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Thu, 18 Jan 2024 17:26:34 +0000 Subject: [PATCH 22/27] Make functions private Signed-off-by: laurentsimon --- sigstore/_cli.py | 16 ++++++++++++++-- sigstore/hashes.py | 4 ++-- sigstore/sign.py | 4 ++-- sigstore/verify/models.py | 2 +- sigstore/verify/verifier.py | 2 +- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index da9714cba..3646e34be 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -686,9 +686,21 @@ def _sign(args: argparse.Namespace) -> None: with signing_ctx.signer(identity) as signer: for file, outputs in output_map.items(): logger.debug(f"signing for {file.name}") - with file.open(mode="rb", buffering=0) as io: + with file.open(mode="rb", buffering=0) as iof: try: - result = signer.sign(input_=io) + import hashlib + import io + from sigstore import hashes as sigstore_hashes + from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm + + hash = hashlib.sha256() + hash.update(iof.read()) + digest = hash.digest() + print(digest.hex()) + hashed = sigstore_hashes.Hashed(digest=digest, algorithm=HashAlgorithm.SHA2_256) + result = signer.sign(hashed) + + #result = signer.sign(input_=io) except ExpiredIdentity as exp_identity: print("Signature failed: identity token has expired") raise exp_identity diff --git a/sigstore/hashes.py b/sigstore/hashes.py index b1af6b2c3..7b5a5900b 100644 --- a/sigstore/hashes.py +++ b/sigstore/hashes.py @@ -34,12 +34,12 @@ class Hashed(BaseModel): The digest representing the hash value. """ - def as_hashedrekord_algorithm(self) -> rekor_types.hashedrekord.Algorithm: + def _as_hashedrekord_algorithm(self) -> rekor_types.hashedrekord.Algorithm: if self.algorithm == HashAlgorithm.SHA2_256: return rekor_types.hashedrekord.Algorithm.SHA256 raise ValueError(f"unknown hash algorithm: {self.algorithm}") - def as_prehashed(self) -> Prehashed: + def _as_prehashed(self) -> Prehashed: if self.algorithm == HashAlgorithm.SHA2_256: return Prehashed(hashes.SHA256()) raise ValueError(f"unknown hash algorithm: {self.algorithm}") diff --git a/sigstore/sign.py b/sigstore/sign.py index 5e14e0e47..3cf8bc961 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -221,7 +221,7 @@ def sign( hashed_input = get_digest(input_) artifact_signature = private_key.sign( - hashed_input.digest, ec.ECDSA(hashed_input.as_prehashed()) + hashed_input.digest, ec.ECDSA(hashed_input._as_prehashed()) ) content = MessageSignature( @@ -243,7 +243,7 @@ def sign( ), data=rekor_types.hashedrekord.Data( hash=rekor_types.hashedrekord.Hash( - algorithm=hashed_input.as_hashedrekord_algorithm(), + algorithm=hashed_input._as_hashedrekord_algorithm(), value=hashed_input.digest.hex(), ) ), diff --git a/sigstore/verify/models.py b/sigstore/verify/models.py index a80dd479c..f004333dd 100644 --- a/sigstore/verify/models.py +++ b/sigstore/verify/models.py @@ -416,7 +416,7 @@ def rekor_entry(self, client: RekorClient) -> LogEntry: ), data=rekor_types.hashedrekord.Data( hash=rekor_types.hashedrekord.Hash( - algorithm=self.hashed_input.as_hashedrekord_algorithm(), + algorithm=self.hashed_input._as_hashedrekord_algorithm(), value=self.hashed_input.digest.hex(), ), ), diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 1c4b343fb..6b76777b6 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -224,7 +224,7 @@ def verify( signing_key.verify( materials.signature, materials.hashed_input.digest, - ec.ECDSA(materials.hashed_input.as_prehashed()), + ec.ECDSA(materials.hashed_input._as_prehashed()), ) except InvalidSignature: return VerificationFailure(reason="Signature is invalid for input") From 16058cbf8e743dd568c216abe2d2ac4a96c8b8ae Mon Sep 17 00:00:00 2001 From: laurentsimon Date: Thu, 18 Jan 2024 18:01:41 +0000 Subject: [PATCH 23/27] Remove tests code Signed-off-by: laurentsimon --- sigstore/_cli.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index 3646e34be..da9714cba 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -686,21 +686,9 @@ def _sign(args: argparse.Namespace) -> None: with signing_ctx.signer(identity) as signer: for file, outputs in output_map.items(): logger.debug(f"signing for {file.name}") - with file.open(mode="rb", buffering=0) as iof: + with file.open(mode="rb", buffering=0) as io: try: - import hashlib - import io - from sigstore import hashes as sigstore_hashes - from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm - - hash = hashlib.sha256() - hash.update(iof.read()) - digest = hash.digest() - print(digest.hex()) - hashed = sigstore_hashes.Hashed(digest=digest, algorithm=HashAlgorithm.SHA2_256) - result = signer.sign(hashed) - - #result = signer.sign(input_=io) + result = signer.sign(input_=io) except ExpiredIdentity as exp_identity: print("Signature failed: identity token has expired") raise exp_identity From 7d8413977b996dc7762823629e6580bb206d7912 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Thu, 18 Jan 2024 14:06:02 -0500 Subject: [PATCH 24/27] hashes: format Signed-off-by: William Woodruff --- sigstore/hashes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sigstore/hashes.py b/sigstore/hashes.py index 7b5a5900b..4dcb7540a 100644 --- a/sigstore/hashes.py +++ b/sigstore/hashes.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +import rekor_types from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from pydantic import BaseModel -import rekor_types from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm @@ -43,4 +43,3 @@ def _as_prehashed(self) -> Prehashed: if self.algorithm == HashAlgorithm.SHA2_256: return Prehashed(hashes.SHA256()) raise ValueError(f"unknown hash algorithm: {self.algorithm}") - From 43dddc5dc8eb070977265382d1855354815d24d4 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Thu, 18 Jan 2024 14:09:10 -0500 Subject: [PATCH 25/27] sigstore: docstrings Signed-off-by: William Woodruff --- sigstore/_utils.py | 4 ++++ sigstore/hashes.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/sigstore/_utils.py b/sigstore/_utils.py index c7bf43720..3c56ecde5 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -161,6 +161,10 @@ def key_id(key: PublicKey) -> KeyID: def get_digest(input_: IO[bytes] | sigstore_hashes.Hashed) -> sigstore_hashes.Hashed: + """ + Compute the SHA256 digest of an input stream or, if given a `Hashed`, + return it directly. + """ if isinstance(input_, sigstore_hashes.Hashed): return input_ diff --git a/sigstore/hashes.py b/sigstore/hashes.py index 4dcb7540a..c1cbe9c44 100644 --- a/sigstore/hashes.py +++ b/sigstore/hashes.py @@ -35,11 +35,17 @@ class Hashed(BaseModel): """ def _as_hashedrekord_algorithm(self) -> rekor_types.hashedrekord.Algorithm: + """ + Returns an appropriate `hashedrekord.Algorithm` for this `Hashed`. + """ if self.algorithm == HashAlgorithm.SHA2_256: return rekor_types.hashedrekord.Algorithm.SHA256 raise ValueError(f"unknown hash algorithm: {self.algorithm}") def _as_prehashed(self) -> Prehashed: + """ + Returns an appropriate Cryptography `Prehashed` for this `Hashed`. + """ if self.algorithm == HashAlgorithm.SHA2_256: return Prehashed(hashes.SHA256()) raise ValueError(f"unknown hash algorithm: {self.algorithm}") From 69dad22739ad147830f8947e260bd3538f644c9b Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Thu, 18 Jan 2024 14:11:00 -0500 Subject: [PATCH 26/27] hashes: docstring Signed-off-by: William Woodruff --- sigstore/hashes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sigstore/hashes.py b/sigstore/hashes.py index c1cbe9c44..dc6011762 100644 --- a/sigstore/hashes.py +++ b/sigstore/hashes.py @@ -12,6 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Hashing APIs. +""" + import rekor_types from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.utils import Prehashed From 513803e50e72b5417930fe0b9e3e8115fc311595 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Thu, 18 Jan 2024 14:48:18 -0500 Subject: [PATCH 27/27] test: add a prehashed signing test Signed-off-by: William Woodruff --- test/unit/conftest.py | 21 +++++++++++++++++-- test/unit/test_sign.py | 46 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/test/unit/conftest.py b/test/unit/conftest.py index 304167337..ea14f1210 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -40,6 +40,7 @@ from sigstore.sign import SigningContext from sigstore.verify import VerificationMaterials from sigstore.verify.policy import VerificationSuccess +from sigstore.verify.verifier import Verifier _ASSETS = (Path(__file__).parent / "assets").resolve() assert _ASSETS.is_dir() @@ -50,7 +51,9 @@ def _has_oidc_id(): # If there are tokens manually defined for us in the environment, use them. - if os.getenv("SIGSTORE_IDENTITY_TOKEN_production") is not None: + if os.getenv("SIGSTORE_IDENTITY_TOKEN_production") or os.getenv( + "SIGSTORE_IDENTITY_TOKEN_staging" + ): return True try: @@ -240,7 +243,7 @@ def tuf_dirs(monkeypatch, tmp_path): ], ids=["production", "staging"], ) -def id_config(request) -> tuple[SigningContext, IdentityToken]: +def signer_and_ident(request) -> tuple[type[SigningContext], type[IdentityToken]]: env, signer = request.param # Detect env variable for local interactive tests. token = os.getenv(f"SIGSTORE_IDENTITY_TOKEN_{env}") @@ -251,6 +254,20 @@ def id_config(request) -> tuple[SigningContext, IdentityToken]: return signer, IdentityToken(token) +@pytest.fixture +def staging() -> tuple[type[SigningContext], type[Verifier], IdentityToken]: + signer = SigningContext.staging + verifier = Verifier.staging + + # Detect env variable for local interactive tests. + token = os.getenv("SIGSTORE_IDENTITY_TOKEN_staging") + if not token: + # If the variable is not defined, try getting an ambient token. + token = detect_credential(_DEFAULT_AUDIENCE) + + return signer, verifier, IdentityToken(token) + + @pytest.fixture def dummy_jwt(): def _dummy_jwt(claims: dict): diff --git a/test/unit/test_sign.py b/test/unit/test_sign.py index ac6370ec7..9c5017d57 100644 --- a/test/unit/test_sign.py +++ b/test/unit/test_sign.py @@ -18,11 +18,17 @@ import pretend import pytest +from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm import sigstore.oidc from sigstore._internal.keyring import KeyringError, KeyringLookupError from sigstore._internal.sct import InvalidSCTError, InvalidSCTKeyError +from sigstore._utils import sha256_streaming +from sigstore.hashes import Hashed from sigstore.sign import SigningContext +from sigstore.verify.models import VerificationMaterials +from sigstore.verify.policy import UnsafeNoOp +from sigstore.verify.verifier import Verifier class TestSigningContext: @@ -36,8 +42,8 @@ def test_staging(self, mock_staging_tuf): @pytest.mark.online @pytest.mark.ambient_oidc -def test_sign_rekor_entry_consistent(id_config): - ctx, identity = id_config +def test_sign_rekor_entry_consistent(signer_and_ident): + ctx, identity = signer_and_ident # NOTE: The actual signer instance is produced lazily, so that parameter # expansion doesn't fail in offline tests. @@ -58,8 +64,8 @@ def test_sign_rekor_entry_consistent(id_config): @pytest.mark.online @pytest.mark.ambient_oidc -def test_sct_verify_keyring_lookup_error(id_config, monkeypatch): - ctx, identity = id_config +def test_sct_verify_keyring_lookup_error(signer_and_ident, monkeypatch): + ctx, identity = signer_and_ident # a signer whose keyring always fails to lookup a given key. ctx: SigningContext = ctx() @@ -80,8 +86,8 @@ def test_sct_verify_keyring_lookup_error(id_config, monkeypatch): @pytest.mark.online @pytest.mark.ambient_oidc -def test_sct_verify_keyring_error(id_config, monkeypatch): - ctx, identity = id_config +def test_sct_verify_keyring_error(signer_and_ident, monkeypatch): + ctx, identity = signer_and_ident # a signer whose keyring throws an internal error. ctx: SigningContext = ctx() @@ -97,8 +103,8 @@ def test_sct_verify_keyring_error(id_config, monkeypatch): @pytest.mark.online @pytest.mark.ambient_oidc -def test_identity_proof_claim_lookup(id_config, monkeypatch): - ctx, identity = id_config +def test_identity_proof_claim_lookup(signer_and_ident, monkeypatch): + ctx, identity = signer_and_ident ctx: SigningContext = ctx() assert identity is not None @@ -116,3 +122,27 @@ def test_identity_proof_claim_lookup(id_config, monkeypatch): assert expected_entry.integrated_time == actual_entry.integrated_time assert expected_entry.log_id.key_id == bytes.fromhex(actual_entry.log_id) assert expected_entry.log_index == actual_entry.log_index + + +@pytest.mark.online +@pytest.mark.ambient_oidc +def test_sign_prehashed(staging): + sign_ctx, verifier, identity = staging + + sign_ctx: SigningContext = sign_ctx() + verifier: Verifier = verifier() + + input_ = io.BytesIO(secrets.token_bytes(32)) + hashed = Hashed(digest=sha256_streaming(input_), algorithm=HashAlgorithm.SHA2_256) + + with sign_ctx.signer(identity) as signer: + bundle = signer.sign(hashed) + + assert bundle.message_signature.message_digest.algorithm == hashed.algorithm + assert bundle.message_signature.message_digest.digest == hashed.digest + + input_.seek(0) + materials = VerificationMaterials.from_bundle( + input_=input_, bundle=bundle, offline=False + ) + verifier.verify(materials=materials, policy=UnsafeNoOp())