1use hmac::{Hmac, Mac};
30use sha2::Sha256;
31use subtle::ConstantTimeEq;
32
33use crate::refinement::TrustProof;
34
35type HmacSha256 = Hmac<Sha256>;
36
37#[derive(Debug)]
40pub enum TrustError {
41 MalformedSignature(&'static str),
43 SignatureMismatch,
45 InvalidKey(String),
47 ExchangeFailed(String),
50 UnsupportedProof(String),
54}
55
56impl std::fmt::Display for TrustError {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 Self::MalformedSignature(ctx) => {
60 write!(f, "malformed signature: {ctx}")
61 }
62 Self::SignatureMismatch => {
63 write!(f, "signature mismatch (constant-time compare)")
64 }
65 Self::InvalidKey(m) => write!(f, "invalid key: {m}"),
66 Self::ExchangeFailed(m) => write!(f, "exchange failed: {m}"),
67 Self::UnsupportedProof(m) => write!(f, "unsupported proof: {m}"),
68 }
69 }
70}
71
72impl std::error::Error for TrustError {}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct VerifiedPayload {
80 pub proof: TrustProof,
81 pub key_id: String,
84}
85
86pub fn verify_hmac_sha256(
96 payload: &[u8],
97 tag: &[u8],
98 key: &[u8],
99 key_id: &str,
100) -> Result<VerifiedPayload, TrustError> {
101 if tag.len() != 32 {
102 return Err(TrustError::MalformedSignature(
103 "HMAC-SHA256 tag must be exactly 32 bytes",
104 ));
105 }
106 let mut mac = HmacSha256::new_from_slice(key).map_err(|_| {
107 TrustError::InvalidKey(
108 "HMAC-SHA256 accepts any key length; this error is unreachable"
109 .into(),
110 )
111 })?;
112 mac.update(payload);
113 mac.verify_slice(tag).map_err(|_| TrustError::SignatureMismatch)?;
115 Ok(VerifiedPayload {
116 proof: TrustProof::Hmac,
117 key_id: key_id.to_string(),
118 })
119}
120
121pub fn verify_hmac_sha256_hex(
126 payload: &[u8],
127 tag_hex: &str,
128 key: &[u8],
129 key_id: &str,
130) -> Result<VerifiedPayload, TrustError> {
131 let tag = hex_decode(tag_hex.strip_prefix("sha256=").unwrap_or(tag_hex))
132 .ok_or(TrustError::MalformedSignature(
133 "HMAC-SHA256 hex tag did not decode",
134 ))?;
135 verify_hmac_sha256(payload, &tag, key, key_id)
136}
137
138fn hex_decode(s: &str) -> Option<Vec<u8>> {
139 if s.len() % 2 != 0 {
140 return None;
141 }
142 let mut out = Vec::with_capacity(s.len() / 2);
143 for chunk in s.as_bytes().chunks(2) {
144 let hi = hex_nibble(chunk[0])?;
145 let lo = hex_nibble(chunk[1])?;
146 out.push((hi << 4) | lo);
147 }
148 Some(out)
149}
150
151fn hex_nibble(c: u8) -> Option<u8> {
152 match c {
153 b'0'..=b'9' => Some(c - b'0'),
154 b'a'..=b'f' => Some(c - b'a' + 10),
155 b'A'..=b'F' => Some(c - b'A' + 10),
156 _ => None,
157 }
158}
159
160pub fn verify_ed25519(
167 payload: &[u8],
168 signature: &[u8],
169 public_key: &[u8],
170 key_id: &str,
171) -> Result<VerifiedPayload, TrustError> {
172 if public_key.len() != 32 {
173 return Err(TrustError::InvalidKey(
174 "Ed25519 public key must be 32 bytes".into(),
175 ));
176 }
177 if signature.len() != 64 {
178 return Err(TrustError::MalformedSignature(
179 "Ed25519 signature must be 64 bytes",
180 ));
181 }
182 let pk_array: [u8; 32] = public_key.try_into().map_err(|_| {
183 TrustError::InvalidKey("public key length invariant violated".into())
184 })?;
185 let pk = ed25519_dalek::VerifyingKey::from_bytes(&pk_array)
186 .map_err(|e| TrustError::InvalidKey(e.to_string()))?;
187 let sig_array: [u8; 64] = signature.try_into().map_err(|_| {
188 TrustError::MalformedSignature("signature length invariant violated")
189 })?;
190 let sig = ed25519_dalek::Signature::from_bytes(&sig_array);
191 pk.verify_strict(payload, &sig)
192 .map_err(|_| TrustError::SignatureMismatch)?;
193 Ok(VerifiedPayload {
194 proof: TrustProof::Ed25519,
195 key_id: key_id.to_string(),
196 })
197}
198
199pub async fn verify_jwt_signature(
211 token: &str,
212 verifier: &crate::jwt_verifier::JwtVerifier,
213) -> Result<VerifiedPayload, TrustError> {
214 let verified = verifier.verify(token).await.map_err(|e| match e {
215 crate::jwt_verifier::JwtVerifyError::UnsupportedAlg(a) => {
216 TrustError::UnsupportedProof(format!("alg={a}"))
217 }
218 other => TrustError::ExchangeFailed(other.to_string()),
219 })?;
220 let key_id = verified
224 .jti
225 .clone()
226 .or_else(|| verified.sub.clone())
227 .unwrap_or_else(|| "<anonymous>".to_string());
228 Ok(VerifiedPayload {
229 proof: TrustProof::JwtSig,
230 key_id,
231 })
232}
233
234pub struct OAuthCodeExchangeRequest<'a> {
242 pub token_endpoint: &'a str,
243 pub client_id: &'a str,
244 pub client_secret: Option<&'a str>,
245 pub redirect_uri: &'a str,
246 pub code: &'a str,
247 pub code_verifier: &'a str,
248}
249
250#[derive(Debug, Clone, serde::Deserialize)]
253pub struct OAuthTokenResponse {
254 pub access_token: String,
255 #[serde(default)]
256 pub token_type: String,
257 #[serde(default)]
258 pub expires_in: Option<u64>,
259 #[serde(default)]
260 pub refresh_token: Option<String>,
261 #[serde(default)]
262 pub scope: Option<String>,
263 #[serde(default)]
264 pub id_token: Option<String>,
265}
266
267pub async fn verify_oauth_code_exchange(
271 req: OAuthCodeExchangeRequest<'_>,
272) -> Result<(VerifiedPayload, OAuthTokenResponse), TrustError> {
273 let mut form = vec![
274 ("grant_type", "authorization_code"),
275 ("code", req.code),
276 ("redirect_uri", req.redirect_uri),
277 ("client_id", req.client_id),
278 ("code_verifier", req.code_verifier),
279 ];
280 if let Some(secret) = req.client_secret {
281 form.push(("client_secret", secret));
282 }
283
284 let client = reqwest::Client::new();
285 let resp = client
286 .post(req.token_endpoint)
287 .form(&form)
288 .send()
289 .await
290 .map_err(|e| TrustError::ExchangeFailed(e.to_string()))?;
291
292 if !resp.status().is_success() {
293 let status = resp.status();
294 let body = resp.text().await.unwrap_or_default();
295 return Err(TrustError::ExchangeFailed(format!(
296 "HTTP {status}: {body}"
297 )));
298 }
299
300 let token: OAuthTokenResponse = resp
301 .json()
302 .await
303 .map_err(|e| TrustError::ExchangeFailed(format!("body parse: {e}")))?;
304
305 Ok((
306 VerifiedPayload {
307 proof: TrustProof::OAuthCodeExchange,
308 key_id: req.client_id.to_string(),
309 },
310 token,
311 ))
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 #[test]
319 fn hmac_roundtrip() {
320 let key = b"super-secret-key";
321 let payload = b"order#42|amount=100.00";
322
323 let mut mac = HmacSha256::new_from_slice(key).unwrap();
324 mac.update(payload);
325 let tag = mac.finalize().into_bytes();
326
327 let vp =
328 verify_hmac_sha256(payload, &tag, key, "key-v1").unwrap();
329 assert_eq!(vp.proof, TrustProof::Hmac);
330 assert_eq!(vp.key_id, "key-v1");
331 }
332
333 #[test]
334 fn hmac_rejects_tampered_payload() {
335 let key = b"super-secret-key";
336 let mut mac = HmacSha256::new_from_slice(key).unwrap();
337 mac.update(b"original");
338 let tag = mac.finalize().into_bytes();
339
340 let err = verify_hmac_sha256(b"tampered", &tag, key, "k").unwrap_err();
341 matches!(err, TrustError::SignatureMismatch);
342 }
343
344 #[test]
345 fn hmac_rejects_wrong_length_tag() {
346 let err = verify_hmac_sha256(b"x", &[0u8; 16], b"k", "k").unwrap_err();
347 matches!(err, TrustError::MalformedSignature(_));
348 }
349
350 #[test]
351 fn hmac_hex_decode_roundtrip() {
352 let key = b"k";
353 let mut mac = HmacSha256::new_from_slice(key).unwrap();
354 mac.update(b"hello");
355 let tag = mac.finalize().into_bytes();
356 let hex_tag = tag.iter().map(|b| format!("{b:02x}")).collect::<String>();
357
358 let vp = verify_hmac_sha256_hex(b"hello", &hex_tag, key, "k").unwrap();
359 assert_eq!(vp.proof, TrustProof::Hmac);
360
361 let prefixed = format!("sha256={hex_tag}");
363 let vp2 =
364 verify_hmac_sha256_hex(b"hello", &prefixed, key, "k").unwrap();
365 assert_eq!(vp2.proof, TrustProof::Hmac);
366 }
367
368 #[test]
369 fn ed25519_roundtrip() {
370 use ed25519_dalek::{Signer, SigningKey};
371 let seed: [u8; 32] = rand::random();
378 let sk = SigningKey::from_bytes(&seed);
379 let pk = sk.verifying_key();
380 let payload = b"sigstore-attestation";
381 let sig = sk.sign(payload);
382
383 let vp = verify_ed25519(
384 payload,
385 &sig.to_bytes(),
386 pk.as_bytes(),
387 "sigstore-key-1",
388 )
389 .unwrap();
390 assert_eq!(vp.proof, TrustProof::Ed25519);
391 assert_eq!(vp.key_id, "sigstore-key-1");
392 }
393
394 #[test]
395 fn ed25519_rejects_tampered_payload() {
396 use ed25519_dalek::{Signer, SigningKey};
397 let seed: [u8; 32] = rand::random();
400 let sk = SigningKey::from_bytes(&seed);
401 let pk = sk.verifying_key();
402 let sig = sk.sign(b"original");
403
404 let err = verify_ed25519(
405 b"tampered",
406 &sig.to_bytes(),
407 pk.as_bytes(),
408 "k",
409 )
410 .unwrap_err();
411 matches!(err, TrustError::SignatureMismatch);
412 }
413
414 #[test]
415 fn ed25519_rejects_wrong_key_length() {
416 let err = verify_ed25519(b"x", &[0u8; 64], b"too_short", "k").unwrap_err();
417 matches!(err, TrustError::InvalidKey(_));
418 }
419
420 #[test]
421 fn subtle_compare_still_available_for_adopters() {
422 let a = [1u8, 2, 3];
425 let b = [1u8, 2, 3];
426 assert!(bool::from(a.ct_eq(&b)));
427 }
428}