use crate::encoding::{KeyId, PayloadBytes, SignatureBytes};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DsseEnvelope {
pub payload_type: String,
pub payload: PayloadBytes,
pub signatures: Vec<DsseSignature>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DsseSignature {
pub sig: SignatureBytes,
#[serde(default, skip_serializing_if = "KeyId::is_empty")]
pub keyid: KeyId,
}
impl DsseEnvelope {
pub fn new(
payload_type: String,
payload: PayloadBytes,
signatures: Vec<DsseSignature>,
) -> Self {
Self {
payload_type,
payload,
signatures,
}
}
pub fn pae(&self) -> Vec<u8> {
pae(&self.payload_type, self.payload.as_bytes())
}
pub fn decode_payload(&self) -> Vec<u8> {
self.payload.as_bytes().to_vec()
}
}
pub fn pae(payload_type: &str, payload: &[u8]) -> Vec<u8> {
let mut result = Vec::new();
result.extend_from_slice(b"DSSEv1 ");
result.extend_from_slice(format!("{} ", payload_type.len()).as_bytes());
result.extend_from_slice(payload_type.as_bytes());
result.push(b' ');
result.extend_from_slice(format!("{} ", payload.len()).as_bytes());
result.extend_from_slice(payload);
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pae() {
let pae_result = pae("application/example", b"hello world");
let expected = b"DSSEv1 19 application/example 11 hello world";
assert_eq!(pae_result, expected);
}
#[test]
fn test_dsse_envelope_serde() {
let envelope = DsseEnvelope {
payload_type: "application/vnd.in-toto+json".to_string(),
payload: PayloadBytes::from_bytes(b"{\"_type\":\"https://in-toto.io/Statement/v1\"}"),
signatures: vec![DsseSignature {
sig: SignatureBytes::from_bytes(b"\x30\x44\x02\x20"),
keyid: KeyId::default(),
}],
};
let json = serde_json::to_string(&envelope).unwrap();
let parsed: DsseEnvelope = serde_json::from_str(&json).unwrap();
assert_eq!(envelope, parsed);
}
#[test]
fn test_dsse_envelope_keyid_handling() {
let json_with_empty_keyid = r#"{"payloadType":"application/vnd.in-toto+json","payload":"dGVzdA==","signatures":[{"sig":"c2ln","keyid":""}]}"#;
let envelope: DsseEnvelope = serde_json::from_str(json_with_empty_keyid).unwrap();
assert_eq!(envelope.signatures[0].keyid, KeyId::default());
let reserialized = serde_json::to_string(&envelope).unwrap();
assert!(
!reserialized.contains("keyid"),
"Empty keyid should be omitted from output"
);
let json_without_keyid = r#"{"payloadType":"application/vnd.in-toto+json","payload":"dGVzdA==","signatures":[{"sig":"c2ln"}]}"#;
let envelope_no_keyid: DsseEnvelope = serde_json::from_str(json_without_keyid).unwrap();
assert_eq!(envelope_no_keyid.signatures[0].keyid, KeyId::default());
let json_with_keyid = r#"{"payloadType":"application/vnd.in-toto+json","payload":"dGVzdA==","signatures":[{"sig":"c2ln","keyid":"test-key"}]}"#;
let envelope_with_keyid: DsseEnvelope = serde_json::from_str(json_with_keyid).unwrap();
let json_out = serde_json::to_string(&envelope_with_keyid).unwrap();
assert!(
json_out.contains(r#""keyid":"test-key""#),
"Non-empty keyid should be included in output"
);
}
}