Skip to content

Add function for determining key_details #1456

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

Merged
merged 9 commits into from
Jul 16, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ All versions prior to 0.9.0 are untracked.
* API: `IdentityToken` now supports `client_id` for audience claim validation.
[#1402](https://github.com/sigstore/sigstore-python/pull/1402)


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

* Added a function for determining the `key_details` of a certificate`.
[#1456](https://github.com/sigstore/sigstore-python/pull/1456)

### Fixed

* Avoid instantiation issues with `TransparencyLogEntry` when `InclusionPromise` is not present.
Expand Down
72 changes: 72 additions & 0 deletions sigstore/_internal/key_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright 2025 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.

"""
Utilities for getting the sigstore_protobuf_specs.dev.sigstore.common.v1.PublicKeyDetails.
"""

from typing import cast

from cryptography.hazmat.primitives.asymmetric import ec, ed25519, padding, rsa
from cryptography.x509 import Certificate
from sigstore_protobuf_specs.dev.sigstore.common import v1


def _get_key_details(certificate: Certificate) -> v1.PublicKeyDetails:
"""
Determine PublicKeyDetails from the Certificate.
We disclude the unrecommended types.
See
- https://github.com/sigstore/architecture-docs/blob/6a8d78108ef4bb403046817fbcead211a9dca71d/algorithm-registry.md.
- https://github.com/sigstore/protobuf-specs/blob/3aaae418f76fb4b34df4def4cd093c464f20fed3/protos/sigstore_common.proto
"""
public_key = certificate.public_key()
params = certificate.signature_algorithm_parameters
if isinstance(public_key, ec.EllipticCurvePublicKey):
if isinstance(public_key.curve, ec.SECP256R1):
key_details = v1.PublicKeyDetails.PKIX_ECDSA_P256_SHA_256
elif isinstance(public_key.curve, ec.SECP384R1):
key_details = v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_384
elif isinstance(public_key.curve, ec.SECP521R1):
key_details = v1.PublicKeyDetails.PKIX_ECDSA_P521_SHA_512
else:
raise ValueError(f"Unsupported EC curve: {public_key.curve.name}")
elif isinstance(public_key, rsa.RSAPublicKey):
if public_key.key_size == 3072:
if isinstance(params, padding.PKCS1v15):
key_details = v1.PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256
elif isinstance(params, padding.PSS):
key_details = v1.PublicKeyDetails.PKIX_RSA_PSS_3072_SHA256
else:
raise ValueError(
f"Unsupported public key type, size, and padding: {type(public_key)}, {public_key.key_size}, {params}"
)
elif public_key.key_size == 4096:
if isinstance(params, padding.PKCS1v15):
key_details = v1.PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256
elif isinstance(params, padding.PSS):
key_details = v1.PublicKeyDetails.PKIX_RSA_PSS_3072_SHA256
else:
raise ValueError(
f"Unsupported public key type, size, and padding: {type(public_key)}, {public_key.key_size}, {params}"
)
else:
raise ValueError(f"Unsupported RSA key size: {public_key.key_size}")
elif isinstance(public_key, ed25519.Ed25519PublicKey):
key_details = v1.PublicKeyDetails.PKIX_ED25519
# There is likely no need to explicitly detect PKIX_ED25519_PH, especially since the cryptography
# library does not yet support Ed25519ph.
else:
raise ValueError(f"Unsupported public key type: {type(public_key)}")
return cast(v1.PublicKeyDetails, key_details)
24 changes: 3 additions & 21 deletions sigstore/_internal/rekor/client_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,16 @@

import json
import logging
from typing import cast

import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
from cryptography.x509 import Certificate
from sigstore_protobuf_specs.dev.sigstore.common import v1 as common_v1
from sigstore_protobuf_specs.dev.sigstore.rekor import v2
from sigstore_protobuf_specs.io import intoto

from sigstore._internal import USER_AGENT
from sigstore._internal.key_details import _get_key_details
from sigstore._internal.rekor import (
EntryRequestBody,
RekorClientError,
Expand Down Expand Up @@ -93,23 +92,6 @@ def create_entry(self, payload: EntryRequestBody) -> LogEntry:
_logger.debug(f"integrated: {integrated_entry}")
return LogEntry._from_dict_rekor(integrated_entry)

@staticmethod
def _get_key_details(certificate: Certificate) -> common_v1.PublicKeyDetails:
"""
Determine PublicKeyDetails from a certificate

We know that sign.Signer only uses secp256r1, so do not support anything else.
"""
public_key = certificate.public_key()
if isinstance(public_key, EllipticCurvePublicKey):
if public_key.curve.name == "secp256r1":
return cast(
common_v1.PublicKeyDetails,
common_v1.PublicKeyDetails.PKIX_ECDSA_P256_SHA_256,
)
raise ValueError(f"Unsupported EC curve: {public_key.curve.name}")
raise ValueError(f"Unsupported public key type: {type(public_key)}")

@classmethod
def _build_hashed_rekord_request(
cls,
Expand All @@ -131,7 +113,7 @@ def _build_hashed_rekord_request(
encoding=serialization.Encoding.DER
)
),
key_details=cls._get_key_details(certificate),
key_details=_get_key_details(certificate),
),
),
)
Expand Down Expand Up @@ -165,7 +147,7 @@ def _build_dsse_request(
encoding=serialization.Encoding.DER
)
),
key_details=cls._get_key_details(certificate),
key_details=_get_key_details(certificate),
)
],
)
Expand Down
131 changes: 131 additions & 0 deletions test/unit/internal/test_key_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Copyright 2025 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 unittest.mock import Mock

import pytest
from cryptography.hazmat.primitives.asymmetric import dsa, ec, ed25519, padding, rsa
from sigstore_protobuf_specs.dev.sigstore.common import v1

from sigstore._internal.key_details import _get_key_details


@pytest.mark.parametrize(
"mock_certificate",
[
# ec
pytest.param(
Mock(
public_key=Mock(
return_value=ec.generate_private_key(ec.SECP192R1()).public_key()
)
),
marks=[pytest.mark.xfail(strict=True)],
),
Mock(
public_key=Mock(
return_value=ec.generate_private_key(ec.SECP256R1()).public_key()
)
),
Mock(
public_key=Mock(
return_value=ec.generate_private_key(ec.SECP384R1()).public_key()
)
),
Mock(
public_key=Mock(
return_value=ec.generate_private_key(ec.SECP521R1()).public_key()
)
),
# rsa pkcs1
pytest.param(
Mock(
public_key=Mock(
return_value=rsa.generate_private_key(
public_exponent=65537, key_size=2048
).public_key()
),
signature_algorithm_parameters=padding.PKCS1v15(),
),
marks=[pytest.mark.xfail(strict=True)],
),
Mock(
public_key=Mock(
return_value=rsa.generate_private_key(
public_exponent=65537, key_size=3072
).public_key()
),
signature_algorithm_parameters=padding.PKCS1v15(),
),
Mock(
public_key=Mock(
return_value=rsa.generate_private_key(
public_exponent=65537, key_size=4096
).public_key()
),
signature_algorithm_parameters=padding.PKCS1v15(),
),
# rsa pss
pytest.param(
Mock(
public_key=Mock(
return_value=rsa.generate_private_key(
public_exponent=65537, key_size=2048
).public_key()
),
signature_algorithm_parameters=padding.PSS(None, 0),
),
marks=[pytest.mark.xfail(strict=True)],
),
Mock(
public_key=Mock(
return_value=rsa.generate_private_key(
public_exponent=65537, key_size=3072
).public_key()
),
signature_algorithm_parameters=padding.PSS(None, 0),
),
Mock(
public_key=Mock(
return_value=rsa.generate_private_key(
public_exponent=65537, key_size=4096
).public_key()
),
signature_algorithm_parameters=padding.PSS(None, 0),
),
# ed25519
Mock(
public_key=Mock(
return_value=ed25519.Ed25519PrivateKey.generate().public_key(),
signature_algorithm_parameters=None,
)
),
# unsupported
pytest.param(
Mock(
public_key=Mock(
return_value=dsa.generate_private_key(key_size=1024).public_key()
),
signature_algorithm_parameters=None,
),
marks=[pytest.mark.xfail(strict=True)],
),
],
)
def test_get_key_details(mock_certificate):
"""
Ensures that we return a PublicKeyDetails for supported key types and schemes.
"""
key_details = _get_key_details(mock_certificate)
assert isinstance(key_details, v1.PublicKeyDetails)
Loading