Skip to content

Commit 71da8a7

Browse files
Add support for ed25519 keys (#1377)
* add support for ed25519 keys Signed-off-by: Ramon Petgrave <[email protected]> * add changelog self link Signed-off-by: Ramon Petgrave <[email protected]> * lint Signed-off-by: Ramon Petgrave <[email protected]> * use latest tsa release, not latest tag Signed-off-by: Ramon Petgrave <[email protected]> * add changelog for tsa test fix Signed-off-by: Ramon Petgrave <[email protected]> * add tests Signed-off-by: Ramon Petgrave <[email protected]> * dosctring for test Signed-off-by: Ramon Petgrave <[email protected]> * format Signed-off-by: Ramon Petgrave <[email protected]> * fix filename Signed-off-by: Ramon Petgrave <[email protected]> --------- Signed-off-by: Ramon Petgrave <[email protected]>
1 parent baa95ac commit 71da8a7

File tree

6 files changed

+179
-14
lines changed

6 files changed

+179
-14
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ jobs:
6868
if: ${{ matrix.conf.os == 'ubuntu-latest' }}
6969
run: |
7070
# Fetch the latest sigstore/timestamp-authority build
71-
SIGSTORE_TIMESTAMP_VERSION=$(gh api /repos/sigstore/timestamp-authority/tags --jq '.[0].name')
71+
SIGSTORE_TIMESTAMP_VERSION=$(gh api /repos/sigstore/timestamp-authority/releases --jq '.[0].tag_name')
7272
wget https://github.com/sigstore/timestamp-authority/releases/download/${SIGSTORE_TIMESTAMP_VERSION}/timestamp-server-linux-amd64 -O /tmp/timestamp-server
7373
chmod +x /tmp/timestamp-server
7474

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ All versions prior to 0.9.0 are untracked.
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
* Added support for ed25519 keys.
14+
[#1377](https://github.com/sigstore/sigstore-python/pull/1377)
15+
1116
### Fixed
1217

1318
* TSA: Changed the Timestamp Authority requests to explicitly use sha256 for message digests.
@@ -20,6 +25,10 @@ All versions prior to 0.9.0 are untracked.
2025
* API: Make Rekor APIs compatible with Rekor v2 by removing trailing slashes
2126
from endpoints ([#1366](https://github.com/sigstore/sigstore-python/pull/1366))
2227

28+
* CI: Timestamp Authority tests use latest release, not latest tag, of
29+
[sigstore/timestamp-authority](https://github.com/sigstore/timestamp-authority)
30+
[#1377](https://github.com/sigstore/sigstore-python/pull/1377)
31+
2332
### Changed
2433

2534
* `--trust-config` now requires a file with SigningConfig v0.2, and is able to fully

sigstore/_internal/trust.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323
from datetime import datetime, timezone
2424
from enum import Enum
2525
from pathlib import Path
26-
from typing import ClassVar, NewType
26+
from typing import ClassVar, NewType, Optional
2727

2828
import cryptography.hazmat.primitives.asymmetric.padding as padding
2929
from cryptography.exceptions import InvalidSignature
3030
from cryptography.hazmat.primitives import hashes
31-
from cryptography.hazmat.primitives.asymmetric import ec, rsa
31+
from cryptography.hazmat.primitives.asymmetric import ec, ed25519, rsa
3232
from cryptography.x509 import (
3333
Certificate,
3434
load_der_x509_certificate,
@@ -94,7 +94,7 @@ class Key:
9494
Represents a key in a `Keyring`.
9595
"""
9696

97-
hash_algorithm: hashes.HashAlgorithm
97+
hash_algorithm: Optional[hashes.HashAlgorithm]
9898
key: PublicKey
9999
key_id: KeyID
100100

@@ -121,7 +121,7 @@ def __init__(self, public_key: _PublicKey) -> None:
121121
if not public_key.raw_bytes:
122122
raise VerificationError("public key is empty")
123123

124-
hash_algorithm: hashes.HashAlgorithm
124+
hash_algorithm: Optional[hashes.HashAlgorithm]
125125
if public_key.key_details in self._RSA_SHA_256_DETAILS:
126126
hash_algorithm = hashes.SHA256()
127127
key = load_der_public_key(public_key.raw_bytes, types=(rsa.RSAPublicKey,))
@@ -130,6 +130,11 @@ def __init__(self, public_key: _PublicKey) -> None:
130130
key = load_der_public_key(
131131
public_key.raw_bytes, types=(ec.EllipticCurvePublicKey,)
132132
)
133+
elif public_key.key_details == _PublicKeyDetails.PKIX_ED25519:
134+
hash_algorithm = None
135+
key = load_der_public_key(
136+
public_key.raw_bytes, types=(ed25519.Ed25519PublicKey,)
137+
)
133138
else:
134139
raise VerificationError(f"unsupported key type: {public_key.key_details}")
135140

@@ -141,20 +146,31 @@ def verify(self, signature: bytes, data: bytes) -> None:
141146
"""
142147
Verifies the given `data` against `signature` using the current key.
143148
"""
144-
if isinstance(self.key, rsa.RSAPublicKey):
149+
if isinstance(self.key, rsa.RSAPublicKey) and self.hash_algorithm is not None:
145150
self.key.verify(
146151
signature=signature,
147152
data=data,
148153
# TODO: Parametrize this as well, for PSS.
149154
padding=padding.PKCS1v15(),
150155
algorithm=self.hash_algorithm,
151156
)
152-
elif isinstance(self.key, ec.EllipticCurvePublicKey):
157+
elif (
158+
isinstance(self.key, ec.EllipticCurvePublicKey)
159+
and self.hash_algorithm is not None
160+
):
153161
self.key.verify(
154162
signature=signature,
155163
data=data,
156164
signature_algorithm=ec.ECDSA(self.hash_algorithm),
157165
)
166+
elif (
167+
isinstance(self.key, ed25519.Ed25519PublicKey)
168+
and self.hash_algorithm is None
169+
):
170+
self.key.verify(
171+
signature=signature,
172+
data=data,
173+
)
158174
else:
159175
# Unreachable without API misuse.
160176
raise VerificationError(f"keyring: unsupported key: {self.key}")

sigstore/_utils.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from typing import IO, NewType, Union
2525

2626
from cryptography.hazmat.primitives import serialization
27-
from cryptography.hazmat.primitives.asymmetric import ec, rsa
27+
from cryptography.hazmat.primitives.asymmetric import ec, ed25519, rsa
2828
from cryptography.x509 import (
2929
Certificate,
3030
ExtensionNotFound,
@@ -43,9 +43,13 @@
4343
from importlib import resources
4444

4545

46-
PublicKey = Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey]
46+
PublicKey = Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey]
4747

48-
PublicKeyTypes = Union[type[rsa.RSAPublicKey], type[ec.EllipticCurvePublicKey]]
48+
PublicKeyTypes = Union[
49+
type[rsa.RSAPublicKey],
50+
type[ec.EllipticCurvePublicKey],
51+
type[ed25519.Ed25519PublicKey],
52+
]
4953

5054
HexStr = NewType("HexStr", str)
5155
"""
@@ -64,7 +68,11 @@
6468
def load_pem_public_key(
6569
key_pem: bytes,
6670
*,
67-
types: tuple[PublicKeyTypes, ...] = (rsa.RSAPublicKey, ec.EllipticCurvePublicKey),
71+
types: tuple[PublicKeyTypes, ...] = (
72+
rsa.RSAPublicKey,
73+
ec.EllipticCurvePublicKey,
74+
ed25519.Ed25519PublicKey,
75+
),
6876
) -> PublicKey:
6977
"""
7078
A specialization of `cryptography`'s `serialization.load_pem_public_key`
@@ -86,7 +94,11 @@ def load_pem_public_key(
8694
def load_der_public_key(
8795
key_der: bytes,
8896
*,
89-
types: tuple[PublicKeyTypes, ...] = (rsa.RSAPublicKey, ec.EllipticCurvePublicKey),
97+
types: tuple[PublicKeyTypes, ...] = (
98+
rsa.RSAPublicKey,
99+
ec.EllipticCurvePublicKey,
100+
ed25519.Ed25519PublicKey,
101+
),
90102
) -> PublicKey:
91103
"""
92104
The `load_pem_public_key` specialization, but DER.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
{
2+
"mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1",
3+
"tlogs": [
4+
{
5+
"baseUrl": "http://localhost:3003",
6+
"hashAlgorithm": "SHA2_256",
7+
"publicKey": {
8+
"rawBytes": "MCowBQYDK2VwAyEAREvJyNZGjX6B3DAIuD3BTg9rIwV00GY8Xg5FU+IFDUQ=",
9+
"keyDetails": "PKIX_ED25519",
10+
"validFor": {
11+
"start": "1970-01-01T00:00:00Z"
12+
}
13+
},
14+
"logId": {
15+
"keyId": "tAlACZWkUrif9Z9sOIrpk1ak1I8loRNufk79N6l1SNg="
16+
}
17+
}
18+
],
19+
"certificateAuthorities": [
20+
{
21+
"subject": {
22+
"organization": "sigstore.dev",
23+
"commonName": "sigstore"
24+
},
25+
"uri": "https://fulcio.sigstore.dev",
26+
"certChain": {
27+
"certificates": [
28+
{
29+
"rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ=="
30+
}
31+
]
32+
},
33+
"validFor": {
34+
"start": "2021-03-07T03:20:29.000Z",
35+
"end": "2022-12-31T23:59:59.999Z"
36+
}
37+
},
38+
{
39+
"subject": {
40+
"organization": "sigstore.dev",
41+
"commonName": "sigstore"
42+
},
43+
"uri": "https://fulcio.sigstore.dev",
44+
"certChain": {
45+
"certificates": [
46+
{
47+
"rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow="
48+
},
49+
{
50+
"rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ"
51+
}
52+
]
53+
},
54+
"validFor": {
55+
"start": "2022-04-13T20:06:15.000Z"
56+
}
57+
}
58+
],
59+
"ctlogs": [
60+
{
61+
"baseUrl": "https://ctfe.sigstore.dev/test",
62+
"hashAlgorithm": "SHA2_256",
63+
"publicKey": {
64+
"rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==",
65+
"keyDetails": "PKIX_ECDSA_P256_SHA_256",
66+
"validFor": {
67+
"start": "2021-03-14T00:00:00.000Z",
68+
"end": "2022-10-31T23:59:59.999Z"
69+
}
70+
},
71+
"logId": {
72+
"keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I="
73+
}
74+
},
75+
{
76+
"baseUrl": "https://ctfe.sigstore.dev/2022",
77+
"hashAlgorithm": "SHA2_256",
78+
"publicKey": {
79+
"rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==",
80+
"keyDetails": "PKIX_ECDSA_P256_SHA_256",
81+
"validFor": {
82+
"start": "2022-10-20T00:00:00.000Z"
83+
}
84+
},
85+
"logId": {
86+
"keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4="
87+
}
88+
}
89+
],
90+
"timestampAuthorities": [
91+
{
92+
"subject": {
93+
"organization": "GitHub, Inc.",
94+
"commonName": "Internal Services Root"
95+
},
96+
"certChain": {
97+
"certificates": [
98+
{
99+
"rawBytes": "MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe"
100+
},
101+
{
102+
"rawBytes": "MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA=="
103+
},
104+
{
105+
"rawBytes": "MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMTvzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwqemfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDDU0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD"
106+
}
107+
]
108+
},
109+
"validFor": {
110+
"start": "2023-04-14T00:00:00.000Z"
111+
}
112+
}
113+
]
114+
}

test/unit/internal/test_trust.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,18 @@ def test_good(self, asset):
6565

6666

6767
class TestTrustedRoot:
68-
def test_good(self, asset):
69-
path = asset("trusted_root/trustedroot.v1.json")
68+
@pytest.mark.parametrize(
69+
"file",
70+
[
71+
"trusted_root/trustedroot.v1.json",
72+
"trusted_root/trustedroot.v1.local_tlog_ed25519_rekor-tiles.json",
73+
],
74+
)
75+
def test_good(self, asset, file):
76+
"""
77+
Ensures that the trusted_roots are well-formed and that the embedded keys are supported.
78+
"""
79+
path = asset(file)
7080
root = TrustedRoot.from_file(path)
7181

7282
assert (
@@ -76,6 +86,10 @@ def test_good(self, asset):
7686
assert len(root._inner.certificate_authorities) == 2
7787
assert len(root._inner.ctlogs) == 2
7888
assert len(root._inner.timestamp_authorities) == 1
89+
assert root.rekor_keyring(KeyringPurpose.VERIFY) is not None
90+
assert root.ct_keyring(KeyringPurpose.VERIFY) is not None
91+
assert root.get_fulcio_certs() is not None
92+
assert root.get_timestamp_authorities() is not None
7993

8094
def test_bad_media_type(self, asset):
8195
path = asset("trusted_root/trustedroot.badtype.json")

0 commit comments

Comments
 (0)