Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ All versions prior to 1.0.0 are untracked.
- Fix and test the sharded file hasher
- Added tests for verifying signatures created with v0.3.1
- cli: `model_signing sign` now supports the `--oauth_force_oob` option (default: False)
- Added support for specifying `--client_id` and `--client_secret` for OIDC authentication.
- Added support for specifying `--client_id` and `--client_secret` for OIDC authentication
- cli: Added support for `--allow_symlinks` option
- Fix Bundle deserialization error caused by null keyid in DSSE signatures; keyid now serializes as an empty string
- Implemented public key identifier hash matching for bundle verification
- Add warning for older verification material formats (e.g., raw public key bytes) during verification, recommending re-signing

## [1.0.1] - 2024-04-18

Expand Down
52 changes: 31 additions & 21 deletions src/model_signing/_signing/sign_ec_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Signers and verifiers using elliptic curve keys."""

import hashlib
import pathlib
from typing import Optional

Expand Down Expand Up @@ -125,26 +126,16 @@ def sign(self, payload: signing.Payload) -> signing.Signature:
def _get_verification_material(self) -> bundle_pb.VerificationMaterial:
"""Returns the verification material to include in the bundle."""
public_key = self._private_key.public_key()
key_size = public_key.curve.key_size

# TODO: Once Python 3.9 support is deprecated revert to using `match`
if key_size == 256:
key_details = common_pb.PublicKeyDetails.PKIX_ECDSA_P256_SHA_256
elif key_size == 384:
key_details = common_pb.PublicKeyDetails.PKIX_ECDSA_P384_SHA_384
elif key_size == 521:
key_details = common_pb.PublicKeyDetails.PKIX_ECDSA_P521_SHA_512
else:
raise ValueError(f"Unexpected key size {key_size}")

raw_bytes = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

hash_bytes = hashlib.sha256(raw_bytes).digest().hex()

return bundle_pb.VerificationMaterial(
public_key=common_pb.PublicKey(
raw_bytes=public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
),
key_details=key_details,
)
public_key=common_pb.PublicKeyIdentifier(hint=hash_bytes)
)


Expand All @@ -165,9 +156,28 @@ def __init__(self, public_key_path: pathlib.Path):

@override
def _verify_bundle(self, bundle: bundle_pb.Bundle) -> tuple[str, bytes]:
# TODO: This method currently does not verify that the public key
# matches what is included in the signature's verification material.
# Instead, the verification material is completely ignored right now.
raw_bytes = self._public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

hash_bytes = hashlib.sha256(raw_bytes).digest().hex()

if bundle.verification_material.public_key.hint:
key_hint = bundle.verification_material.public_key.hint
if key_hint != hash_bytes:
raise ValueError(
"Key mismatch: The public key hash in the signature's "
"verification material does not match the provided "
"public key. "
)
else:
print(
"WARNING: This model's signature uses an older "
"verification material format. Please re-sign "
"with an updated signer to use a public key "
"identifier hash. "
)

envelope = bundle.dsse_envelope
try:
Expand Down
27 changes: 9 additions & 18 deletions src/model_signing/_signing/sign_pkcs11.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

from collections.abc import Iterable
import hashlib
import pathlib
from typing import Optional

Expand Down Expand Up @@ -191,26 +192,16 @@ def sign(self, payload: signing.Payload) -> signing.Signature:
def _get_verification_material(self) -> bundle_pb.VerificationMaterial:
"""Returns the verification material to include in the bundle."""
public_key = self._public_key
key_size = public_key.curve.key_size

# TODO: Once Python 3.9 support is deprecated revert to using `match`
if key_size == 256:
key_details = common_pb.PublicKeyDetails.PKIX_ECDSA_P256_SHA_256
elif key_size == 384:
key_details = common_pb.PublicKeyDetails.PKIX_ECDSA_P384_SHA_384
elif key_size == 521:
key_details = common_pb.PublicKeyDetails.PKIX_ECDSA_P521_SHA_512
else:
raise ValueError(f"Unexpected key size {key_size}")

raw_bytes = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

hash_bytes = hashlib.sha256(raw_bytes).digest().hex()

return bundle_pb.VerificationMaterial(
public_key=common_pb.PublicKey(
raw_bytes=public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
),
key_details=key_details,
)
public_key=common_pb.PublicKeyIdentifier(hint=hash_bytes)
)


Expand Down
4 changes: 3 additions & 1 deletion src/model_signing/_signing/sign_sigstore_pb.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"""

import abc
import json
import pathlib
import sys
from typing import cast
Expand Down Expand Up @@ -110,7 +111,8 @@ def write(self, path: pathlib.Path) -> None:
@override
def read(cls, path: pathlib.Path) -> Self:
content = path.read_text()
return cls(bundle_pb.Bundle().from_json(content))
parsed_dict = json.loads(content)
return cls(bundle_pb.Bundle().from_dict(parsed_dict))


class Signer(signing.Signer):
Expand Down
30 changes: 17 additions & 13 deletions tests/api_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def test_sign_and_verify(
use_staging=True,
).verify(sample_model_folder, signature_path)

assert [
assert get_signed_files(signature_path) == [
"d0/f00",
"d0/f01",
"d0/f02",
Expand All @@ -176,7 +176,7 @@ def test_sign_and_verify(
"f1",
"f2",
"f3",
] == get_signed_files(signature_path)
]
check_ignore_paths(signature_path, True, [])
assert get_model_name(signature_path) == os.path.basename(
sample_model_folder
Expand Down Expand Up @@ -214,9 +214,11 @@ def test_sign_and_verify(self, base_path, populate_tmpdir):
)
).verify(model_path, signature)

assert [".gitignore", "signme-1", "signme-2"] == get_signed_files(
signature
)
assert get_signed_files(signature) == [
".gitignore",
"signme-1",
"signme-2",
]
check_ignore_paths(signature, ignore_git_paths, ["model.sig"])
assert get_model_name(signature) == os.path.basename(model_path)

Expand All @@ -233,7 +235,7 @@ def test_sign_and_verify(self, base_path, populate_tmpdir):
)
).sign(model_path, signature)

assert ["signme-1", "signme-2"] == get_signed_files(signature)
assert get_signed_files(signature) == ["signme-1", "signme-2"]
check_ignore_paths(
signature, ignore_git_paths, ["model.sig", "ignored"]
)
Expand Down Expand Up @@ -280,9 +282,11 @@ def test_sign_and_verify(self, base_path, populate_tmpdir):
)
).verify(model_path, signature)

assert [".gitignore", "signme-1", "signme-2"] == get_signed_files(
signature
)
assert get_signed_files(signature) == [
".gitignore",
"signme-1",
"signme-2",
]
check_ignore_paths(signature, ignore_git_paths, ["model.sig"])
assert get_model_name(signature) == os.path.basename(model_path)

Expand All @@ -301,7 +305,7 @@ def test_sign_and_verify(self, base_path, populate_tmpdir):
)
).sign(model_path, signature)

assert ["signme-1", "signme-2"] == get_signed_files(signature)
assert get_signed_files(signature) == ["signme-1", "signme-2"]
check_ignore_paths(
signature, ignore_git_paths, ["model.sig", "ignored"]
)
Expand Down Expand Up @@ -349,11 +353,11 @@ def test_sign_and_verify_sharded(self, base_path, populate_tmpdir):
)
# .verify(model_path, signature)

assert [
assert get_signed_files(signature) == [
".gitignore:0:4",
"signme-1:0:8",
"signme-2:0:8",
] == get_signed_files(signature)
]
check_ignore_paths(signature, ignore_git_paths, ["model.sig"])
assert get_model_name(signature) == os.path.basename(model_path)

Expand All @@ -374,7 +378,7 @@ def test_sign_and_verify_sharded(self, base_path, populate_tmpdir):
.use_shard_serialization()
).sign(model_path, signature)

assert ["signme-1:0:8", "signme-2:0:8"] == get_signed_files(signature)
assert get_signed_files(signature) == ["signme-1:0:8", "signme-2:0:8"]
check_ignore_paths(
signature, ignore_git_paths, ["model.sig", "ignored"]
)
Expand Down
Loading