use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use serde::{Deserialize, Serialize};
use crate::error::WSError;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DsseEnvelope {
pub payload: String,
pub payload_type: String,
pub signatures: Vec<DsseSignature>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DsseSignature {
#[serde(skip_serializing_if = "Option::is_none")]
pub keyid: Option<String>,
pub sig: String,
}
pub trait DsseSigner {
fn sign(&self, pae: &[u8]) -> Result<Vec<u8>, WSError>;
fn key_id(&self) -> Option<String> {
None
}
}
pub trait DsseVerifier {
fn verify(&self, pae: &[u8], signature: &[u8]) -> Result<(), WSError>;
}
impl DsseEnvelope {
pub fn sign(
payload: &[u8],
payload_type: &str,
signer: &dyn DsseSigner,
) -> Result<Self, WSError> {
let pae = compute_pae(payload_type, payload);
let sig_bytes = signer.sign(&pae)?;
Ok(Self {
payload: BASE64.encode(payload),
payload_type: payload_type.to_string(),
signatures: vec![DsseSignature {
keyid: signer.key_id(),
sig: BASE64.encode(sig_bytes),
}],
})
}
pub fn sign_multi(
payload: &[u8],
payload_type: &str,
signers: &[&dyn DsseSigner],
) -> Result<Self, WSError> {
if signers.is_empty() {
return Err(WSError::InvalidArgument);
}
let pae = compute_pae(payload_type, payload);
let mut signatures = Vec::with_capacity(signers.len());
for signer in signers {
let sig_bytes = signer.sign(&pae)?;
signatures.push(DsseSignature {
keyid: signer.key_id(),
sig: BASE64.encode(sig_bytes),
});
}
Ok(Self {
payload: BASE64.encode(payload),
payload_type: payload_type.to_string(),
signatures,
})
}
pub fn verify(&self, verifier: &dyn DsseVerifier) -> Result<Vec<u8>, WSError> {
if self.signatures.is_empty() {
return Err(WSError::VerificationFailed);
}
let payload = BASE64.decode(&self.payload).map_err(|e| {
WSError::InternalError(format!("Invalid base64 payload: {}", e))
})?;
let pae = compute_pae(&self.payload_type, &payload);
let mut verified = false;
for sig in &self.signatures {
let sig_bytes = BASE64.decode(&sig.sig).map_err(|e| {
WSError::InternalError(format!("Invalid base64 signature: {}", e))
})?;
if verifier.verify(&pae, &sig_bytes).is_ok() {
verified = true;
break;
}
}
if !verified {
return Err(WSError::VerificationFailed);
}
Ok(payload)
}
pub fn verify_all(&self, verifier: &dyn DsseVerifier) -> Result<Vec<u8>, WSError> {
if self.signatures.is_empty() {
return Err(WSError::VerificationFailed);
}
let payload = BASE64.decode(&self.payload).map_err(|e| {
WSError::InternalError(format!("Invalid base64 payload: {}", e))
})?;
let pae = compute_pae(&self.payload_type, &payload);
for sig in &self.signatures {
let sig_bytes = BASE64.decode(&sig.sig).map_err(|e| {
WSError::InternalError(format!("Invalid base64 signature: {}", e))
})?;
verifier.verify(&pae, &sig_bytes)?;
}
Ok(payload)
}
pub fn payload_bytes(&self) -> Result<Vec<u8>, WSError> {
BASE64.decode(&self.payload).map_err(|e| {
WSError::InternalError(format!("Invalid base64 payload: {}", e))
})
}
pub fn to_json(&self) -> Result<String, WSError> {
serde_json::to_string(self).map_err(|e| {
WSError::InternalError(format!("Failed to serialize DSSE envelope: {}", e))
})
}
pub fn to_json_pretty(&self) -> Result<String, WSError> {
serde_json::to_string_pretty(self).map_err(|e| {
WSError::InternalError(format!("Failed to serialize DSSE envelope: {}", e))
})
}
pub fn from_json(json: &str) -> Result<Self, WSError> {
serde_json::from_str(json).map_err(|e| {
WSError::InternalError(format!("Failed to parse DSSE envelope: {}", e))
})
}
pub fn unsigned(payload: &[u8], payload_type: &str) -> Self {
Self {
payload: BASE64.encode(payload),
payload_type: payload_type.to_string(),
signatures: vec![],
}
}
pub fn add_signature(&mut self, signer: &dyn DsseSigner) -> Result<(), WSError> {
let payload = self.payload_bytes()?;
let pae = compute_pae(&self.payload_type, &payload);
let sig_bytes = signer.sign(&pae)?;
self.signatures.push(DsseSignature {
keyid: signer.key_id(),
sig: BASE64.encode(sig_bytes),
});
Ok(())
}
}
fn compute_pae(payload_type: &str, payload: &[u8]) -> Vec<u8> {
let mut pae = Vec::new();
pae.extend_from_slice(b"DSSEv1 ");
pae.extend_from_slice(payload_type.len().to_string().as_bytes());
pae.push(b' ');
pae.extend_from_slice(payload_type.as_bytes());
pae.push(b' ');
pae.extend_from_slice(payload.len().to_string().as_bytes());
pae.push(b' ');
pae.extend_from_slice(payload);
pae
}
pub struct Ed25519DsseSigner {
secret_key: ed25519_compact::SecretKey,
key_id: Option<String>,
}
impl Ed25519DsseSigner {
pub fn new(secret_key: ed25519_compact::SecretKey, key_id: Option<String>) -> Self {
Self { secret_key, key_id }
}
pub fn from_bytes(bytes: &[u8], key_id: Option<String>) -> Result<Self, WSError> {
let secret_key = ed25519_compact::SecretKey::from_slice(bytes)
.map_err(|e| WSError::CryptoError(e))?;
Ok(Self { secret_key, key_id })
}
}
impl DsseSigner for Ed25519DsseSigner {
fn sign(&self, pae: &[u8]) -> Result<Vec<u8>, WSError> {
let signature = self.secret_key.sign(pae, None);
Ok(signature.to_vec())
}
fn key_id(&self) -> Option<String> {
self.key_id.clone()
}
}
pub struct Ed25519DsseVerifier {
public_key: ed25519_compact::PublicKey,
}
impl Ed25519DsseVerifier {
pub fn new(public_key: ed25519_compact::PublicKey) -> Self {
Self { public_key }
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, WSError> {
let public_key = ed25519_compact::PublicKey::from_slice(bytes)
.map_err(|e| WSError::CryptoError(e))?;
Ok(Self { public_key })
}
}
impl DsseVerifier for Ed25519DsseVerifier {
fn verify(&self, pae: &[u8], signature: &[u8]) -> Result<(), WSError> {
let sig = ed25519_compact::Signature::from_slice(signature)
.map_err(|e| WSError::CryptoError(e))?;
self.public_key
.verify(pae, &sig)
.map_err(|_| WSError::VerificationFailed)
}
}
pub mod payload_types {
pub const IN_TOTO: &str = "application/vnd.in-toto+json";
pub const SLSA_PROVENANCE: &str = "application/vnd.slsa.provenance+json";
pub const CYCLONEDX: &str = "application/vnd.cyclonedx+json";
pub const WSC_TRANSFORMATION: &str = "application/vnd.wsc.transformation+json";
}
#[cfg(test)]
mod tests {
use super::*;
fn generate_test_keypair() -> (ed25519_compact::SecretKey, ed25519_compact::PublicKey) {
let kp = ed25519_compact::KeyPair::generate();
(kp.sk, kp.pk)
}
#[test]
fn test_pae_computation() {
let pae = compute_pae("application/example", b"hello");
let expected = b"DSSEv1 19 application/example 5 hello";
assert_eq!(pae, expected);
}
#[test]
fn test_pae_empty_payload() {
let pae = compute_pae("text/plain", b"");
let expected = b"DSSEv1 10 text/plain 0 ";
assert_eq!(pae, expected);
}
#[test]
fn test_sign_and_verify() {
let (sk, pk) = generate_test_keypair();
let signer = Ed25519DsseSigner::new(sk, Some("test-key".to_string()));
let verifier = Ed25519DsseVerifier::new(pk);
let payload = b"test payload";
let envelope = DsseEnvelope::sign(
payload,
payload_types::IN_TOTO,
&signer,
).unwrap();
assert_eq!(envelope.payload_type, payload_types::IN_TOTO);
assert_eq!(envelope.signatures.len(), 1);
assert_eq!(envelope.signatures[0].keyid, Some("test-key".to_string()));
let verified = envelope.verify(&verifier).unwrap();
assert_eq!(verified, payload);
}
#[test]
fn test_json_roundtrip() {
let (sk, _pk) = generate_test_keypair();
let signer = Ed25519DsseSigner::new(sk, None);
let envelope = DsseEnvelope::sign(
b"test data",
"application/json",
&signer,
).unwrap();
let json = envelope.to_json().unwrap();
let parsed = DsseEnvelope::from_json(&json).unwrap();
assert_eq!(parsed.payload, envelope.payload);
assert_eq!(parsed.payload_type, envelope.payload_type);
assert_eq!(parsed.signatures.len(), envelope.signatures.len());
}
#[test]
fn test_multi_signature() {
let (sk1, pk1) = generate_test_keypair();
let (sk2, pk2) = generate_test_keypair();
let signer1 = Ed25519DsseSigner::new(sk1, Some("key1".to_string()));
let signer2 = Ed25519DsseSigner::new(sk2, Some("key2".to_string()));
let verifier1 = Ed25519DsseVerifier::new(pk1);
let verifier2 = Ed25519DsseVerifier::new(pk2);
let envelope = DsseEnvelope::sign_multi(
b"multi-signed payload",
"application/json",
&[&signer1, &signer2],
).unwrap();
assert_eq!(envelope.signatures.len(), 2);
assert!(envelope.verify(&verifier1).is_ok());
assert!(envelope.verify(&verifier2).is_ok());
}
#[test]
fn test_verify_fails_wrong_key() {
let (sk, _pk) = generate_test_keypair();
let (_, other_pk) = generate_test_keypair();
let signer = Ed25519DsseSigner::new(sk, None);
let wrong_verifier = Ed25519DsseVerifier::new(other_pk);
let envelope = DsseEnvelope::sign(
b"test",
"application/json",
&signer,
).unwrap();
assert!(envelope.verify(&wrong_verifier).is_err());
}
#[test]
fn test_unsigned_envelope() {
let envelope = DsseEnvelope::unsigned(b"unsigned data", "text/plain");
assert!(envelope.signatures.is_empty());
assert_eq!(envelope.payload_bytes().unwrap(), b"unsigned data");
}
#[test]
fn test_add_signature() {
let (sk, pk) = generate_test_keypair();
let signer = Ed25519DsseSigner::new(sk, Some("added".to_string()));
let verifier = Ed25519DsseVerifier::new(pk);
let mut envelope = DsseEnvelope::unsigned(b"deferred signing", "text/plain");
assert!(envelope.signatures.is_empty());
envelope.add_signature(&signer).unwrap();
assert_eq!(envelope.signatures.len(), 1);
let verified = envelope.verify(&verifier).unwrap();
assert_eq!(verified, b"deferred signing");
}
#[test]
fn test_payload_types() {
assert!(payload_types::IN_TOTO.contains("in-toto"));
assert!(payload_types::CYCLONEDX.contains("cyclonedx"));
assert!(payload_types::SLSA_PROVENANCE.contains("slsa"));
}
#[test]
fn test_key_id_propagation() {
let (sk, _pk) = generate_test_keypair();
let signer_with_id = Ed25519DsseSigner::new(sk.clone(), Some("my-key-id".to_string()));
assert_eq!(signer_with_id.key_id(), Some("my-key-id".to_string()));
let signer_no_id = Ed25519DsseSigner::new(sk, None);
assert_eq!(signer_no_id.key_id(), None);
}
#[test]
fn test_verify_all_returns_correct_payload() {
let (sk, pk) = generate_test_keypair();
let signer = Ed25519DsseSigner::new(sk, None);
let verifier = Ed25519DsseVerifier::new(pk);
let payload = b"verify_all test payload";
let envelope = DsseEnvelope::sign(payload, "text/plain", &signer).unwrap();
let result = envelope.verify_all(&verifier).unwrap();
assert_eq!(result, payload);
assert!(!result.is_empty());
}
#[test]
fn test_verify_all_rejects_empty_signatures() {
let (_sk, pk) = generate_test_keypair();
let verifier = Ed25519DsseVerifier::new(pk);
let envelope = DsseEnvelope::unsigned(b"no sigs", "text/plain");
assert!(envelope.verify_all(&verifier).is_err());
}
#[test]
fn test_to_json_pretty_valid_output() {
let (sk, _pk) = generate_test_keypair();
let signer = Ed25519DsseSigner::new(sk, Some("pretty-key".to_string()));
let envelope = DsseEnvelope::sign(b"pretty test", "text/plain", &signer).unwrap();
let json = envelope.to_json_pretty().unwrap();
assert!(json.contains("payload"));
assert!(json.contains("payloadType"));
assert!(json.contains("signatures"));
assert!(json.contains('\n')); assert!(!json.is_empty());
let parsed = DsseEnvelope::from_json(&json).unwrap();
assert_eq!(parsed.payload, envelope.payload);
}
}
#[cfg(kani)]
mod proofs {
use super::compute_pae;
#[kani::proof]
fn proof_pae_injective_different_types() {
let pae_a = compute_pae("a", &[]);
let pae_b = compute_pae("b", &[]);
assert_ne!(pae_a, pae_b, "PAE collision for different types");
}
#[kani::proof]
fn proof_pae_injective_different_payloads() {
let pae_a = compute_pae("", &[0]);
let pae_b = compute_pae("", &[1]);
assert_ne!(pae_a, pae_b, "PAE collision for different payloads");
}
#[kani::proof]
fn proof_pae_deterministic() {
let pae1 = compute_pae("", &[]);
let pae2 = compute_pae("", &[]);
assert_eq!(pae1, pae2);
}
#[kani::proof]
fn proof_pae_has_dsse_prefix() {
let pae = compute_pae("", &[]);
assert!(pae.starts_with(b"DSSEv1 "), "PAE missing DSSEv1 prefix");
}
#[kani::proof]
fn proof_pae_length_prefix_prevents_ambiguity() {
let pae_a = compute_pae("a", b"");
let pae_b = compute_pae("", b"a");
assert_ne!(pae_a, pae_b, "PAE ambiguity: different type/payload split produced same encoding");
}
}