Skip to content

Commit 970d0ee

Browse files
committed
make all interfaces more type safe
1 parent 75f0a03 commit 970d0ee

File tree

14 files changed

+290
-181
lines changed

14 files changed

+290
-181
lines changed

crates/sigstore-crypto/src/checkpoint.rs

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
//! through an extension trait on `sigstore_types::Checkpoint`.
55
66
use crate::{Error, Result};
7+
use sigstore_types::{DerPublicKey, KeyHint, SignatureBytes};
78

89
// Re-export checkpoint types from sigstore-types
910
pub use sigstore_types::{Checkpoint, CheckpointSignature};
1011

1112
/// Compute the key hint (4-byte key ID) from a public key.
1213
///
1314
/// The key hint is the first 4 bytes of SHA-256(public key).
14-
pub fn compute_key_hint(public_key_der: &[u8]) -> [u8; 4] {
15-
let hash = crate::hash::sha256(public_key_der);
15+
pub fn compute_key_hint(public_key: &DerPublicKey) -> KeyHint {
16+
let hash = crate::hash::sha256(public_key.as_bytes());
1617
let bytes = hash.as_bytes();
17-
[bytes[0], bytes[1], bytes[2], bytes[3]]
18+
KeyHint::new([bytes[0], bytes[1], bytes[2], bytes[3]])
1819
}
1920

2021
// OID constants for key type identification
@@ -38,10 +39,10 @@ pub enum KeyType {
3839
/// Detect the key type from SPKI-encoded public key bytes.
3940
///
4041
/// This parses the SubjectPublicKeyInfo structure to determine the algorithm.
41-
pub fn detect_key_type(public_key_der: &[u8]) -> KeyType {
42+
pub fn detect_key_type(public_key: &DerPublicKey) -> KeyType {
4243
use spki::SubjectPublicKeyInfoRef;
4344

44-
match SubjectPublicKeyInfoRef::try_from(public_key_der) {
45+
match SubjectPublicKeyInfoRef::try_from(public_key.as_bytes()) {
4546
Ok(spki) => {
4647
if spki.algorithm.oid == ID_ED25519 {
4748
KeyType::Ed25519
@@ -55,7 +56,7 @@ pub fn detect_key_type(public_key_der: &[u8]) -> KeyType {
5556
Err(_) => {
5657
// If we can't parse as SPKI, might be raw key bytes
5758
// Check if it looks like a raw Ed25519 key (32 bytes)
58-
if public_key_der.len() == 32 {
59+
if public_key.as_bytes().len() == 32 {
5960
KeyType::Ed25519
6061
} else {
6162
KeyType::Unknown
@@ -68,48 +69,53 @@ pub fn detect_key_type(public_key_der: &[u8]) -> KeyType {
6869
///
6970
/// For Ed25519, this extracts the 32-byte raw key from the SPKI wrapper.
7071
/// For ECDSA, the full SPKI is typically used by aws-lc-rs.
71-
pub fn extract_raw_key(public_key_der: &[u8]) -> Result<Vec<u8>> {
72+
pub fn extract_raw_key(public_key: &DerPublicKey) -> Result<Vec<u8>> {
7273
use spki::SubjectPublicKeyInfoRef;
7374

74-
match SubjectPublicKeyInfoRef::try_from(public_key_der) {
75+
match SubjectPublicKeyInfoRef::try_from(public_key.as_bytes()) {
7576
Ok(spki) => {
7677
let raw_bytes = spki.subject_public_key.raw_bytes();
7778
Ok(raw_bytes.to_vec())
7879
}
7980
Err(_) => {
8081
// Already raw bytes
81-
Ok(public_key_der.to_vec())
82+
Ok(public_key.as_bytes().to_vec())
8283
}
8384
}
8485
}
8586

8687
/// Verify an Ed25519 signature.
8788
///
8889
/// Accepts either SPKI-encoded or raw 32-byte public keys.
89-
pub fn verify_ed25519(public_key_der: &[u8], signature: &[u8], message: &[u8]) -> Result<()> {
90-
use aws_lc_rs::signature;
90+
pub fn verify_ed25519(
91+
public_key: &DerPublicKey,
92+
signature: &SignatureBytes,
93+
message: &[u8],
94+
) -> Result<()> {
95+
use aws_lc_rs::signature as sig;
9196

9297
// Extract raw key bytes from SPKI if needed
93-
let raw_key = extract_raw_key(public_key_der)?;
98+
let raw_key = extract_raw_key(public_key)?;
9499

95-
let public_key = signature::UnparsedPublicKey::new(&signature::ED25519, &raw_key);
96-
public_key
97-
.verify(message, signature)
100+
let pk = sig::UnparsedPublicKey::new(&sig::ED25519, &raw_key);
101+
pk.verify(message, signature.as_bytes())
98102
.map_err(|_| Error::Verification("Ed25519 verification failed".to_string()))
99103
}
100104

101105
/// Verify an ECDSA P-256 signature.
102106
///
103107
/// Expects SPKI-encoded public key (as produced by x509 certificates).
104-
pub fn verify_ecdsa_p256(public_key_der: &[u8], signature: &[u8], message: &[u8]) -> Result<()> {
105-
use aws_lc_rs::signature;
108+
pub fn verify_ecdsa_p256(
109+
public_key: &DerPublicKey,
110+
signature: &SignatureBytes,
111+
message: &[u8],
112+
) -> Result<()> {
113+
use aws_lc_rs::signature as sig;
106114

107115
// aws-lc-rs expects the full SPKI for ECDSA, or raw uncompressed point
108-
let public_key =
109-
signature::UnparsedPublicKey::new(&signature::ECDSA_P256_SHA256_ASN1, public_key_der);
116+
let pk = sig::UnparsedPublicKey::new(&sig::ECDSA_P256_SHA256_ASN1, public_key.as_bytes());
110117

111-
public_key
112-
.verify(message, signature)
118+
pk.verify(message, signature.as_bytes())
113119
.map_err(|_| Error::Verification("ECDSA P-256 verification failed".to_string()))
114120
}
115121

@@ -118,20 +124,20 @@ pub fn verify_ecdsa_p256(public_key_der: &[u8], signature: &[u8], message: &[u8]
118124
/// This function detects the key type from the SPKI structure and calls
119125
/// the appropriate verification function.
120126
pub fn verify_signature_auto(
121-
public_key_der: &[u8],
122-
signature: &[u8],
127+
public_key: &DerPublicKey,
128+
signature: &SignatureBytes,
123129
message: &[u8],
124130
) -> Result<()> {
125-
match detect_key_type(public_key_der) {
126-
KeyType::Ed25519 => verify_ed25519(public_key_der, signature, message),
127-
KeyType::EcdsaP256 => verify_ecdsa_p256(public_key_der, signature, message),
131+
match detect_key_type(public_key) {
132+
KeyType::Ed25519 => verify_ed25519(public_key, signature, message),
133+
KeyType::EcdsaP256 => verify_ecdsa_p256(public_key, signature, message),
128134
KeyType::Unknown => {
129135
// Fallback: try both (maintains backwards compatibility)
130136
tracing::debug!("Unknown key type, trying Ed25519 then ECDSA P-256");
131-
if verify_ed25519(public_key_der, signature, message).is_ok() {
137+
if verify_ed25519(public_key, signature, message).is_ok() {
132138
return Ok(());
133139
}
134-
verify_ecdsa_p256(public_key_der, signature, message)
140+
verify_ecdsa_p256(public_key, signature, message)
135141
}
136142
}
137143
}
@@ -148,13 +154,13 @@ pub trait CheckpointVerifyExt {
148154
/// The key type is automatically detected from the SPKI structure.
149155
///
150156
/// Returns Ok(()) if verification succeeds, or an error if it fails.
151-
fn verify_signature(&self, public_key_der: &[u8]) -> Result<()>;
157+
fn verify_signature(&self, public_key: &DerPublicKey) -> Result<()>;
152158
}
153159

154160
impl CheckpointVerifyExt for Checkpoint {
155-
fn verify_signature(&self, public_key_der: &[u8]) -> Result<()> {
161+
fn verify_signature(&self, public_key: &DerPublicKey) -> Result<()> {
156162
// Compute key hint from public key
157-
let key_hint = compute_key_hint(public_key_der);
163+
let key_hint = compute_key_hint(public_key);
158164

159165
// Find signature with matching key hint
160166
let signature = self
@@ -165,7 +171,7 @@ impl CheckpointVerifyExt for Checkpoint {
165171
let signed_data = self.signed_data();
166172

167173
// Use automatic key type detection
168-
verify_signature_auto(public_key_der, &signature.signature, signed_data)
174+
verify_signature_auto(public_key, &signature.signature, signed_data)
169175
.map_err(|e| Error::Checkpoint(format!("Signature verification failed: {}", e)))
170176
}
171177
}
@@ -199,6 +205,6 @@ mod tests {
199205
assert_eq!(checkpoint.signatures.len(), 1);
200206
assert_eq!(checkpoint.signatures[0].name, "rekor.sigstore.dev");
201207
// Key hint is first 4 bytes of base64-decoded signature
202-
assert_eq!(checkpoint.signatures[0].key_id.len(), 4);
208+
assert_eq!(checkpoint.signatures[0].key_id.as_bytes().len(), 4);
203209
}
204210
}

crates/sigstore-crypto/src/verification.rs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,6 @@ impl VerificationKey {
3636
})
3737
}
3838

39-
/// Create a verification key from raw public key bytes (internal use)
40-
fn from_raw_bytes(bytes: impl Into<Vec<u8>>, scheme: SigningScheme) -> Self {
41-
Self {
42-
bytes: bytes.into(),
43-
scheme,
44-
}
45-
}
46-
4739
/// Get the raw public key bytes
4840
pub fn as_bytes(&self) -> &[u8] {
4941
&self.bytes
@@ -155,30 +147,38 @@ impl VerificationKey {
155147
///
156148
/// This is a convenience function that creates a temporary `VerificationKey`.
157149
/// For repeated verifications with the same key, prefer using `VerificationKey` directly.
150+
///
151+
/// # Arguments
152+
/// * `public_key` - DER-encoded SPKI public key
153+
/// * `data` - Data that was signed
154+
/// * `signature` - The signature to verify
155+
/// * `scheme` - The signing scheme used
158156
pub fn verify_signature(
159-
public_key: &[u8],
157+
public_key: &DerPublicKey,
160158
data: &[u8],
161-
signature: &[u8],
159+
signature: &SignatureBytes,
162160
scheme: SigningScheme,
163161
) -> Result<()> {
164-
VerificationKey::from_raw_bytes(public_key, scheme)
165-
.verify(data, &SignatureBytes::from_bytes(signature))
162+
VerificationKey::from_spki(public_key, scheme)?.verify(data, signature)
166163
}
167164

168165
/// Verify a signature over prehashed data using the specified scheme
169166
///
170167
/// This is used for hashedrekord verification where the signature is over
171168
/// the SHA-256 hash of the artifact, not the artifact itself.
169+
///
170+
/// # Arguments
171+
/// * `public_key` - DER-encoded SPKI public key
172+
/// * `digest` - SHA-256 hash of the artifact
173+
/// * `signature` - The signature to verify
174+
/// * `scheme` - The signing scheme used
172175
pub fn verify_signature_prehashed(
173-
public_key: &[u8],
174-
digest_bytes: &[u8],
175-
signature: &[u8],
176+
public_key: &DerPublicKey,
177+
digest: &Sha256Hash,
178+
signature: &SignatureBytes,
176179
scheme: SigningScheme,
177180
) -> Result<()> {
178-
let digest = Sha256Hash::try_from_slice(digest_bytes)
179-
.map_err(|e| Error::Verification(format!("Invalid digest: {e}")))?;
180-
VerificationKey::from_raw_bytes(public_key, scheme)
181-
.verify_prehashed(&digest, &SignatureBytes::from_bytes(signature))
181+
VerificationKey::from_spki(public_key, scheme)?.verify_prehashed(digest, signature)
182182
}
183183

184184
#[cfg(test)]

crates/sigstore-crypto/src/x509.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,3 @@ pub fn extract_fulcio_issuer(cert: &Certificate) -> Result<Option<String>> {
190190

191191
Ok(None)
192192
}
193-

crates/sigstore-rekor/src/body.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,19 @@ pub struct PublicKeyContent {
6666
pub content: PemContent,
6767
}
6868

69+
impl PublicKeyContent {
70+
/// Parse the PEM content and return a DER certificate
71+
pub fn to_certificate(&self) -> Result<DerCertificate, crate::error::Error> {
72+
let pem_bytes = self.content.as_bytes();
73+
let pem_str = String::from_utf8(pem_bytes.to_vec()).map_err(|e| {
74+
crate::error::Error::InvalidResponse(format!("PEM not valid UTF-8: {}", e))
75+
})?;
76+
DerCertificate::from_pem(&pem_str).map_err(|e| {
77+
crate::error::Error::InvalidResponse(format!("failed to parse certificate PEM: {}", e))
78+
})
79+
}
80+
}
81+
6982
// ============================================================================
7083
// HashedRekord v0.0.2
7184
// ============================================================================
@@ -163,6 +176,19 @@ pub struct DsseV001Signature {
163176
pub verifier: PemContent,
164177
}
165178

179+
impl DsseV001Signature {
180+
/// Parse the PEM verifier and return a DER certificate
181+
pub fn to_certificate(&self) -> Result<DerCertificate, crate::error::Error> {
182+
let pem_bytes = self.verifier.as_bytes();
183+
let pem_str = String::from_utf8(pem_bytes.to_vec()).map_err(|e| {
184+
crate::error::Error::InvalidResponse(format!("PEM not valid UTF-8: {}", e))
185+
})?;
186+
DerCertificate::from_pem(&pem_str).map_err(|e| {
187+
crate::error::Error::InvalidResponse(format!("failed to parse certificate PEM: {}", e))
188+
})
189+
}
190+
}
191+
166192
// ============================================================================
167193
// DSSE v0.0.2
168194
// ============================================================================

crates/sigstore-trust-root/src/trusted_root.rs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{Error, Result};
44
use chrono::{DateTime, Utc};
55
use rustls_pki_types::CertificateDer;
66
use serde::{Deserialize, Serialize};
7-
use sigstore_types::{DerCertificate, DerPublicKey, HashAlgorithm, LogId, LogKeyId};
7+
use sigstore_types::{DerCertificate, DerPublicKey, HashAlgorithm, KeyHint, LogId, LogKeyId};
88
use std::collections::HashMap;
99

1010
/// TSA certificate with optional validity period (start, end)
@@ -209,46 +209,44 @@ impl TrustedRoot {
209209

210210
/// Get all Rekor public keys with their key hints (4-byte identifiers)
211211
///
212-
/// Returns a vector of (key_hint, public_key_der) tuples where key_hint is
212+
/// Returns a vector of (key_hint, public_key) tuples where key_hint is
213213
/// the first 4 bytes of the keyId from the log_id field.
214-
pub fn rekor_keys_with_hints(&self) -> Result<Vec<([u8; 4], Vec<u8>)>> {
214+
pub fn rekor_keys_with_hints(&self) -> Result<Vec<(KeyHint, DerPublicKey)>> {
215215
let mut keys = Vec::new();
216216
for tlog in &self.tlogs {
217-
let key_bytes = tlog.public_key.raw_bytes.as_bytes().to_vec();
218-
219217
// Decode the key_id to get the key hint (first 4 bytes)
220218
let key_id_bytes = tlog.log_id.key_id.decode()?;
221219

222220
if key_id_bytes.len() >= 4 {
223-
let key_hint: [u8; 4] = [
221+
let key_hint = KeyHint::new([
224222
key_id_bytes[0],
225223
key_id_bytes[1],
226224
key_id_bytes[2],
227225
key_id_bytes[3],
228-
];
229-
keys.push((key_hint, key_bytes));
226+
]);
227+
keys.push((key_hint, tlog.public_key.raw_bytes.clone()));
230228
}
231229
}
232230
Ok(keys)
233231
}
234232

235233
/// Get a specific Rekor public key by log ID
236-
pub fn rekor_key_for_log(&self, log_id: &LogKeyId) -> Result<Vec<u8>> {
234+
pub fn rekor_key_for_log(&self, log_id: &LogKeyId) -> Result<DerPublicKey> {
237235
for tlog in &self.tlogs {
238236
if &tlog.log_id.key_id == log_id {
239-
return Ok(tlog.public_key.raw_bytes.as_bytes().to_vec());
237+
return Ok(tlog.public_key.raw_bytes.clone());
240238
}
241239
}
242240
Err(Error::KeyNotFound(log_id.to_string()))
243241
}
244242

245243
/// Get all Certificate Transparency log public keys mapped by key ID
246-
pub fn ctfe_keys(&self) -> Result<HashMap<LogKeyId, Vec<u8>>> {
244+
pub fn ctfe_keys(&self) -> Result<HashMap<LogKeyId, DerPublicKey>> {
247245
let mut keys = HashMap::new();
248246
for ctlog in &self.ctlogs {
249247
keys.insert(
250248
ctlog.log_id.key_id.clone(),
251-
ctlog.public_key.raw_bytes.as_bytes().to_vec(),
249+
ctlog.public_key.raw_bytes.clone(),
252250
);
253251
}
254252
Ok(keys)
@@ -257,13 +255,13 @@ impl TrustedRoot {
257255
/// Get all Certificate Transparency log public keys with their SHA-256 log IDs
258256
/// Returns a list of (log_id, public_key) pairs where log_id is the SHA-256 hash
259257
/// of the public key (used for matching against SCTs)
260-
pub fn ctfe_keys_with_ids(&self) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
258+
pub fn ctfe_keys_with_ids(&self) -> Result<Vec<(Vec<u8>, DerPublicKey)>> {
261259
let mut result = Vec::new();
262260
for ctlog in &self.ctlogs {
263261
let key_bytes = ctlog.public_key.raw_bytes.as_bytes();
264262
// Compute SHA-256 hash of the public key to get the log ID
265263
let log_id = sigstore_crypto::sha256(key_bytes).as_bytes().to_vec();
266-
result.push((log_id, key_bytes.to_vec()));
264+
result.push((log_id, ctlog.public_key.raw_bytes.clone()));
267265
}
268266
Ok(result)
269267
}

0 commit comments

Comments
 (0)