Skip to content

Commit f67b019

Browse files
committed
Change API
Signed-off-by: laurentsimon <laurentsimon@google.com>
1 parent adcb448 commit f67b019

File tree

5 files changed

+82
-58
lines changed

5 files changed

+82
-58
lines changed

sigstore/_utils.py

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@
2323
import sys
2424
from typing import IO, NewType, Union
2525

26-
from cryptography.hazmat.primitives import hashes, serialization
26+
from cryptography.hazmat.primitives import serialization
2727
from cryptography.hazmat.primitives.asymmetric import ec, rsa
28-
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
2928
from cryptography.x509 import Certificate, ExtensionNotFound, Version
3029
from cryptography.x509.oid import ExtendedKeyUsageOID, ExtensionOID
3130
from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm
3231

32+
from sigstore import hashes as sigstore_hashes
3333
from sigstore.errors import Error
3434

3535
if sys.version_info < (3, 11):
@@ -141,26 +141,13 @@ def key_id(key: PublicKey) -> KeyID:
141141

142142
return KeyID(hashlib.sha256(public_bytes).digest())
143143

144-
def hazmat_digest_to_bundle(algo: str) -> HashAlgorithm:
145-
lookup = {hashes.SHA256().name: HashAlgorithm.SHA2_256}
146-
if algo in lookup:
147-
return HashAlgorithm(lookup[algo])
148-
return ValueError(f"unknown digest algorithm {algo}")
149-
150-
def get_digest(
151-
input_: IO[bytes],
152-
algorithm_: Prehashed = None,
153-
) -> (bytes, Prehashed):
154-
if algorithm_ is None:
155-
return sha256_streaming(input_), Prehashed(hashes.SHA256())
156-
157-
if isinstance(algorithm_, Prehashed):
158-
# Check we have a 256-bit digest size for compatibility with secp256r1.
159-
if algorithm_.digest_size != 32:
160-
return ValueError(f"invalid digest size ({algorithm_.digest_size}), expected 32")
161-
return input_.getvalue(), algorithm_
162-
163-
raise ValueError("invalid arguments")
144+
def get_digest(input_: IO[bytes] | sigstore_hashes.Hashed) -> sigstore_hashes.Hashed:
145+
if isinstance(input_, sigstore_hashes.Hashed):
146+
return input_
147+
148+
# NOTE: Not able to check for the type `TypeError: Subscripted generics cannot be used with class and instance checks`
149+
# when calling isinstance(_input, IO[bytes])
150+
return sigstore_hashes.Hashed(digest=sha256_streaming(input_), algorithm=HashAlgorithm.SHA2_256)
164151

165152
def sha256_streaming(io: IO[bytes]) -> bytes:
166153
"""

sigstore/hashes.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
# Copyright 2023 The Sigstore Authors
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
from cryptography.hazmat.primitives import hashes
17+
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
18+
from pydantic import BaseModel
19+
from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm
20+
21+
22+
class DIRSHA256_P1(hashes.HashAlgorithm):
23+
name = "dirsha256-p1"
24+
digest_size = 32
25+
block_size = -1
26+
27+
class Hashed(BaseModel):
28+
"""
29+
Represents hashed value.
30+
"""
31+
32+
algorithm: HashAlgorithm
33+
"""
34+
The digest algorithm uses to compute the digest.
35+
"""
36+
37+
digest: bytes
38+
"""
39+
The digest representing the hash value.
40+
"""
41+
42+
def as_prehashed(self) -> Prehashed:
43+
return Prehashed(self.hazmat_algorithm())
44+
45+
def hazmat_algorithm(self) -> hashes.HashAlgorithm:
46+
if self.algorithm == HashAlgorithm.SHA2_256:
47+
return hashes.SHA256()
48+
if self.algorithm == HashAlgorithm.HASH_ALGORITHM_UNSPECIFIED:
49+
return DIRSHA256_P1()
50+
raise ValueError(f"unknown hash algorithm: {self.algorithm}")

sigstore/sign.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@
4949
import rekor_types
5050
from cryptography.hazmat.primitives import hashes, serialization
5151
from cryptography.hazmat.primitives.asymmetric import ec
52-
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
5352
from cryptography.x509.oid import NameOID
5453
from pydantic import BaseModel
5554
from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import (
5655
Bundle,
5756
VerificationMaterial,
5857
)
5958
from sigstore_protobuf_specs.dev.sigstore.common.v1 import (
59+
HashAlgorithm,
6060
HashOutput,
6161
LogId,
6262
MessageSignature,
@@ -71,6 +71,7 @@
7171
TransparencyLogEntry,
7272
)
7373

74+
from sigstore import hashes as sigstore_hashes
7475
from sigstore._internal.fulcio import (
7576
ExpiredCertificate,
7677
FulcioCertificateSigningResponse,
@@ -79,7 +80,7 @@
7980
from sigstore._internal.rekor.client import RekorClient
8081
from sigstore._internal.sct import verify_sct
8182
from sigstore._internal.trustroot import TrustedRoot
82-
from sigstore._utils import B64Str, HexStr, PEMCert, get_digest, hazmat_digest_to_bundle
83+
from sigstore._utils import B64Str, HexStr, PEMCert, get_digest
8384
from sigstore.oidc import ExpiredIdentity, IdentityToken
8485
from sigstore.transparency import LogEntry
8586

@@ -173,11 +174,10 @@ def _signing_cert(
173174

174175
def sign(
175176
self,
176-
input_: IO[bytes],
177-
algorithm_: Prehashed = None,
177+
input_: IO[bytes] | sigstore_hashes.Hashed,
178178
) -> SigningResult:
179179
"""Public API for signing blobs"""
180-
input_digest, algorithm = get_digest(input_, algorithm_)
180+
hashed_input = get_digest(input_)
181181
private_key = self._private_key
182182

183183
if not self._identity_token.in_validity_period():
@@ -201,7 +201,7 @@ def sign(
201201

202202
# Sign artifact
203203
artifact_signature = private_key.sign(
204-
input_digest, ec.ECDSA(algorithm)
204+
hashed_input.digest, ec.ECDSA(hashed_input.as_prehashed())
205205
)
206206
b64_artifact_signature = B64Str(base64.b64encode(artifact_signature).decode())
207207

@@ -223,8 +223,8 @@ def sign(
223223
),
224224
data=rekor_types.hashedrekord.Data(
225225
hash=rekor_types.hashedrekord.Hash(
226-
algorithm=algorithm._algorithm.name,
227-
value=input_digest.hex(),
226+
algorithm=hashed_input.hazmat_algorithm().name,
227+
value=hashed_input.digest.hex(),
228228
)
229229
),
230230
),
@@ -234,8 +234,8 @@ def sign(
234234
logger.debug(f"Transparency log entry created with index: {entry.log_index}")
235235

236236
return SigningResult(
237-
digest_algorithm=hazmat_digest_to_bundle(algorithm._algorithm.name),
238-
input_digest=HexStr(input_digest.hex()),
237+
digest_algorithm=hashed_input.algorithm,
238+
input_digest=HexStr(hashed_input.digest.hex()),
239239
cert_pem=PEMCert(
240240
cert.public_bytes(encoding=serialization.Encoding.PEM).decode()
241241
),
@@ -314,16 +314,11 @@ class SigningResult(BaseModel):
314314
Represents the artifacts of a signing operation.
315315
"""
316316

317-
digest_algorithm: str
317+
digest_algorithm: HashAlgorithm
318318
"""
319319
The digest algorithm to use for the hash.
320320
"""
321321

322-
input_digest: HexStr
323-
"""
324-
The hex-encoded SHA256 digest of the input that was signed for.
325-
"""
326-
327322
cert_pem: PEMCert
328323
"""
329324
The PEM-encoded public half of the certificate used for signing.

sigstore/verify/models.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
from typing import IO
2727

2828
import rekor_types
29-
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
3029
from cryptography.hazmat.primitives.serialization import Encoding
3130
from cryptography.x509 import (
3231
Certificate,
@@ -54,6 +53,7 @@
5453
TransparencyLogEntry,
5554
)
5655

56+
from sigstore import hashes as sigstore_hashes
5757
from sigstore._internal.rekor import RekorClient
5858
from sigstore._utils import (
5959
B64Str,
@@ -62,7 +62,6 @@
6262
cert_is_leaf,
6363
cert_is_root_ca,
6464
get_digest,
65-
hazmat_digest_to_bundle,
6665
)
6766
from sigstore.errors import Error
6867
from sigstore.transparency import LogEntry, LogInclusionProof
@@ -180,17 +179,11 @@ class VerificationMaterials:
180179
Represents the materials needed to perform a Sigstore verification.
181180
"""
182181

183-
digest_algorithm: Prehashed
182+
hashed_input: sigstore_hashes.Hashed
184183
"""
185-
The digest algorithm to use for the hash.
184+
The hash of the verification input.
186185
"""
187186

188-
input_digest: bytes
189-
"""
190-
The 'digest_algorithm' hash of the verification input, as raw bytes.
191-
"""
192-
193-
194187
certificate: Certificate
195188
"""
196189
The certificate that attests to and contains the public signing key.
@@ -234,12 +227,11 @@ class VerificationMaterials:
234227
def __init__(
235228
self,
236229
*,
237-
input_: IO[bytes],
230+
input_: IO[bytes] | sigstore_hashes.Hashed,
238231
cert_pem: PEMCert,
239232
signature: bytes,
240233
offline: bool = False,
241234
rekor_entry: LogEntry | None,
242-
algorithm_: Prehashed = None
243235
):
244236
"""
245237
Create a new `VerificationMaterials` from the given materials.
@@ -254,7 +246,7 @@ def __init__(
254246
Effect: `input_` is consumed as part of construction.
255247
"""
256248

257-
self.input_digest, self.digest_algorithm = get_digest(input_, algorithm_)
249+
self.hashed_input = get_digest(input_)
258250
self.certificate = load_pem_x509_certificate(cert_pem.encode())
259251
self.signature = signature
260252

@@ -426,8 +418,8 @@ def rekor_entry(self, client: RekorClient) -> LogEntry:
426418
),
427419
data=rekor_types.hashedrekord.Data(
428420
hash=rekor_types.hashedrekord.Hash(
429-
algorithm=self.digest_algorithm._algorithm.name,
430-
value=self.input_digest.hex(),
421+
algorithm=self.hashed_input.hazmat_algorithm().name,
422+
value=self.hashed_input.digest.hex(),
431423
),
432424
),
433425
),
@@ -520,8 +512,8 @@ def to_bundle(self) -> Bundle:
520512
),
521513
message_signature=MessageSignature(
522514
message_digest=HashOutput(
523-
algorithm=hazmat_digest_to_bundle(self.digest_algorithm._algorithm.name),
524-
digest=self.input_digest,
515+
algorithm=self.hashed_input.algorithm,
516+
digest=self.hashed_input.digest,
525517
),
526518
signature=self.signature,
527519
),

sigstore/verify/verifier.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,8 @@ def verify(
223223
signing_key = cast(ec.EllipticCurvePublicKey, signing_key)
224224
signing_key.verify(
225225
materials.signature,
226-
materials.input_digest,
227-
ec.ECDSA(materials.digest_algorithm),
226+
materials.hashed_input.digest,
227+
ec.ECDSA(materials.hashed_input.as_prehashed()),
228228
)
229229
except InvalidSignature:
230230
return VerificationFailure(reason="Signature is invalid for input")
@@ -239,7 +239,7 @@ def verify(
239239
except RekorEntryMissingError:
240240
return LogEntryMissing(
241241
signature=B64Str(base64.b64encode(materials.signature).decode()),
242-
artifact_hash=HexStr(materials.input_digest.hex()),
242+
artifact_hash=HexStr(materials.hashed_input.digest.hex()),
243243
)
244244
except InvalidRekorEntryError:
245245
return VerificationFailure(

0 commit comments

Comments
 (0)