Skip to content

Commit ce6d31d

Browse files
authored
Merge branch 'main' into parallel-signing
2 parents 761a76d + a18d758 commit ce6d31d

File tree

5 files changed

+211
-24
lines changed

5 files changed

+211
-24
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ All versions prior to 0.9.0 are untracked.
1919
* API: `IdentityToken` now supports `client_id` for audience claim validation.
2020
[#1402](https://github.com/sigstore/sigstore-python/pull/1402)
2121

22-
2322
* Added a `RekorV2Client` for posting new entries to a Rekor V2 instance.
2423
[#1400](https://github.com/sigstore/sigstore-python/pull/1422)
2524

25+
* Added a function for determining the `key_details` of a certificate`.
26+
[#1456](https://github.com/sigstore/sigstore-python/pull/1456)
27+
2628
### Fixed
2729

2830
* Avoid instantiation issues with `TransparencyLogEntry` when `InclusionPromise` is not present.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ dependencies = [
3838
"rfc8785 ~= 0.1.2",
3939
"rfc3161-client >= 1.0.3,< 1.1.0",
4040
# NOTE(ww): Both under active development, so strictly pinned.
41-
"sigstore-protobuf-specs == 0.4.3",
41+
"sigstore-protobuf-specs == 0.5.0",
4242
"sigstore-rekor-types == 0.0.18",
4343
"tuf ~= 6.0",
4444
"platformdirs ~= 4.2",
@@ -62,7 +62,7 @@ lint = [
6262
"mypy ~= 1.1",
6363
# NOTE(ww): ruff is under active development, so we pin conservatively here
6464
# and let Dependabot periodically perform this update.
65-
"ruff < 0.12.3",
65+
"ruff < 0.12.4",
6666
"types-requests",
6767
"types-pyOpenSSL",
6868
]

sigstore/_internal/key_details.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Copyright 2025 The Sigstore Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
Utilities for getting the sigstore_protobuf_specs.dev.sigstore.common.v1.PublicKeyDetails.
17+
"""
18+
19+
from typing import cast
20+
21+
from cryptography.hazmat.primitives.asymmetric import ec, ed25519, padding, rsa
22+
from cryptography.x509 import Certificate
23+
from sigstore_protobuf_specs.dev.sigstore.common import v1
24+
25+
26+
def _get_key_details(certificate: Certificate) -> v1.PublicKeyDetails:
27+
"""
28+
Determine PublicKeyDetails from the Certificate.
29+
We disclude the unrecommended types.
30+
See
31+
- https://github.com/sigstore/architecture-docs/blob/6a8d78108ef4bb403046817fbcead211a9dca71d/algorithm-registry.md.
32+
- https://github.com/sigstore/protobuf-specs/blob/3aaae418f76fb4b34df4def4cd093c464f20fed3/protos/sigstore_common.proto
33+
"""
34+
public_key = certificate.public_key()
35+
params = certificate.signature_algorithm_parameters
36+
if isinstance(public_key, ec.EllipticCurvePublicKey):
37+
if isinstance(public_key.curve, ec.SECP256R1):
38+
key_details = v1.PublicKeyDetails.PKIX_ECDSA_P256_SHA_256
39+
elif isinstance(public_key.curve, ec.SECP384R1):
40+
key_details = v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_384
41+
elif isinstance(public_key.curve, ec.SECP521R1):
42+
key_details = v1.PublicKeyDetails.PKIX_ECDSA_P521_SHA_512
43+
else:
44+
raise ValueError(f"Unsupported EC curve: {public_key.curve.name}")
45+
elif isinstance(public_key, rsa.RSAPublicKey):
46+
if public_key.key_size == 3072:
47+
if isinstance(params, padding.PKCS1v15):
48+
key_details = v1.PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256
49+
elif isinstance(params, padding.PSS):
50+
key_details = v1.PublicKeyDetails.PKIX_RSA_PSS_3072_SHA256
51+
else:
52+
raise ValueError(
53+
f"Unsupported public key type, size, and padding: {type(public_key)}, {public_key.key_size}, {params}"
54+
)
55+
elif public_key.key_size == 4096:
56+
if isinstance(params, padding.PKCS1v15):
57+
key_details = v1.PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256
58+
elif isinstance(params, padding.PSS):
59+
key_details = v1.PublicKeyDetails.PKIX_RSA_PSS_3072_SHA256
60+
else:
61+
raise ValueError(
62+
f"Unsupported public key type, size, and padding: {type(public_key)}, {public_key.key_size}, {params}"
63+
)
64+
else:
65+
raise ValueError(f"Unsupported RSA key size: {public_key.key_size}")
66+
elif isinstance(public_key, ed25519.Ed25519PublicKey):
67+
key_details = v1.PublicKeyDetails.PKIX_ED25519
68+
# There is likely no need to explicitly detect PKIX_ED25519_PH, especially since the cryptography
69+
# library does not yet support Ed25519ph.
70+
else:
71+
raise ValueError(f"Unsupported public key type: {type(public_key)}")
72+
return cast(v1.PublicKeyDetails, key_details)

sigstore/_internal/rekor/client_v2.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,16 @@
2020

2121
import json
2222
import logging
23-
from typing import cast
2423

2524
import requests
2625
from cryptography.hazmat.primitives import serialization
27-
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
2826
from cryptography.x509 import Certificate
2927
from sigstore_protobuf_specs.dev.sigstore.common import v1 as common_v1
3028
from sigstore_protobuf_specs.dev.sigstore.rekor import v2
3129
from sigstore_protobuf_specs.io import intoto
3230

3331
from sigstore._internal import USER_AGENT
32+
from sigstore._internal.key_details import _get_key_details
3433
from sigstore._internal.rekor import (
3534
EntryRequestBody,
3635
RekorClientError,
@@ -91,23 +90,6 @@ def create_entry(self, payload: EntryRequestBody) -> LogEntry:
9190
_logger.debug(f"integrated: {integrated_entry}")
9291
return LogEntry._from_dict_rekor(integrated_entry)
9392

94-
@staticmethod
95-
def _get_key_details(certificate: Certificate) -> common_v1.PublicKeyDetails:
96-
"""
97-
Determine PublicKeyDetails from a certificate
98-
99-
We know that sign.Signer only uses secp256r1, so do not support anything else.
100-
"""
101-
public_key = certificate.public_key()
102-
if isinstance(public_key, EllipticCurvePublicKey):
103-
if public_key.curve.name == "secp256r1":
104-
return cast(
105-
common_v1.PublicKeyDetails,
106-
common_v1.PublicKeyDetails.PKIX_ECDSA_P256_SHA_256,
107-
)
108-
raise ValueError(f"Unsupported EC curve: {public_key.curve.name}")
109-
raise ValueError(f"Unsupported public key type: {type(public_key)}")
110-
11193
@classmethod
11294
def _build_hashed_rekord_request(
11395
cls,
@@ -129,7 +111,7 @@ def _build_hashed_rekord_request(
129111
encoding=serialization.Encoding.DER
130112
)
131113
),
132-
key_details=cls._get_key_details(certificate),
114+
key_details=_get_key_details(certificate),
133115
),
134116
),
135117
)
@@ -163,7 +145,7 @@ def _build_dsse_request(
163145
encoding=serialization.Encoding.DER
164146
)
165147
),
166-
key_details=cls._get_key_details(certificate),
148+
key_details=_get_key_details(certificate),
167149
)
168150
],
169151
)
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Copyright 2025 The Sigstore Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from unittest.mock import Mock
16+
17+
import pytest
18+
from cryptography.hazmat.primitives.asymmetric import dsa, ec, ed25519, padding, rsa
19+
from sigstore_protobuf_specs.dev.sigstore.common import v1
20+
21+
from sigstore._internal.key_details import _get_key_details
22+
23+
24+
@pytest.mark.parametrize(
25+
"mock_certificate",
26+
[
27+
# ec
28+
pytest.param(
29+
Mock(
30+
public_key=Mock(
31+
return_value=ec.generate_private_key(ec.SECP192R1()).public_key()
32+
)
33+
),
34+
marks=[pytest.mark.xfail(strict=True)],
35+
),
36+
Mock(
37+
public_key=Mock(
38+
return_value=ec.generate_private_key(ec.SECP256R1()).public_key()
39+
)
40+
),
41+
Mock(
42+
public_key=Mock(
43+
return_value=ec.generate_private_key(ec.SECP384R1()).public_key()
44+
)
45+
),
46+
Mock(
47+
public_key=Mock(
48+
return_value=ec.generate_private_key(ec.SECP521R1()).public_key()
49+
)
50+
),
51+
# rsa pkcs1
52+
pytest.param(
53+
Mock(
54+
public_key=Mock(
55+
return_value=rsa.generate_private_key(
56+
public_exponent=65537, key_size=2048
57+
).public_key()
58+
),
59+
signature_algorithm_parameters=padding.PKCS1v15(),
60+
),
61+
marks=[pytest.mark.xfail(strict=True)],
62+
),
63+
Mock(
64+
public_key=Mock(
65+
return_value=rsa.generate_private_key(
66+
public_exponent=65537, key_size=3072
67+
).public_key()
68+
),
69+
signature_algorithm_parameters=padding.PKCS1v15(),
70+
),
71+
Mock(
72+
public_key=Mock(
73+
return_value=rsa.generate_private_key(
74+
public_exponent=65537, key_size=4096
75+
).public_key()
76+
),
77+
signature_algorithm_parameters=padding.PKCS1v15(),
78+
),
79+
# rsa pss
80+
pytest.param(
81+
Mock(
82+
public_key=Mock(
83+
return_value=rsa.generate_private_key(
84+
public_exponent=65537, key_size=2048
85+
).public_key()
86+
),
87+
signature_algorithm_parameters=padding.PSS(None, 0),
88+
),
89+
marks=[pytest.mark.xfail(strict=True)],
90+
),
91+
Mock(
92+
public_key=Mock(
93+
return_value=rsa.generate_private_key(
94+
public_exponent=65537, key_size=3072
95+
).public_key()
96+
),
97+
signature_algorithm_parameters=padding.PSS(None, 0),
98+
),
99+
Mock(
100+
public_key=Mock(
101+
return_value=rsa.generate_private_key(
102+
public_exponent=65537, key_size=4096
103+
).public_key()
104+
),
105+
signature_algorithm_parameters=padding.PSS(None, 0),
106+
),
107+
# ed25519
108+
Mock(
109+
public_key=Mock(
110+
return_value=ed25519.Ed25519PrivateKey.generate().public_key(),
111+
signature_algorithm_parameters=None,
112+
)
113+
),
114+
# unsupported
115+
pytest.param(
116+
Mock(
117+
public_key=Mock(
118+
return_value=dsa.generate_private_key(key_size=1024).public_key()
119+
),
120+
signature_algorithm_parameters=None,
121+
),
122+
marks=[pytest.mark.xfail(strict=True)],
123+
),
124+
],
125+
)
126+
def test_get_key_details(mock_certificate):
127+
"""
128+
Ensures that we return a PublicKeyDetails for supported key types and schemes.
129+
"""
130+
key_details = _get_key_details(mock_certificate)
131+
assert isinstance(key_details, v1.PublicKeyDetails)

0 commit comments

Comments
 (0)