Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
74 changes: 74 additions & 0 deletions sigstore/_internal/key_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# 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 == 2048:
raise ValueError("Unsupported RSA key size: 2048")
elif 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)