1use crate::encoding::STANDARD;
5use chacha20poly1305::aead::{Aead, KeyInit};
6use chacha20poly1305::{ChaCha20Poly1305, Nonce};
7use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
8use hkdf::Hkdf;
9use rand::RngCore;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use sha2::{Digest, Sha256};
13use x25519_dalek::{PublicKey as X25519Public, StaticSecret as X25519Secret};
14
15use crate::canonicalize;
16
17const HKDF_INFO: &[u8] = b"tfbundle/wrap";
18
19#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
20pub struct EncryptedProofBundle {
21 pub bundle_version: String,
22 pub level: String,
23 pub ciphertext: String,
24 pub nonce: String,
25 pub wrapped_keys: Vec<WrappedKey>,
26 #[serde(skip_serializing_if = "Option::is_none", default)]
27 pub transparency_anchor: Option<Value>,
28 pub signature: SignatureEnvelope,
29}
30
31#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
32pub struct WrappedKey {
33 pub recipient: String,
34 #[serde(skip_serializing_if = "Option::is_none", default)]
35 pub recipient_key_id: Option<String>,
36 pub ephemeral_public: String,
37 pub wrapped: String,
38 pub wrap_nonce: String,
39}
40
41#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
42pub struct SignatureEnvelope {
43 pub algorithm: String,
44 pub signer: String,
45 pub signature: String,
46}
47
48#[derive(Clone, Debug)]
49pub struct BundleRecipient {
50 pub actor: String,
51 pub kem_public: [u8; 32],
52 pub key_id: Option<String>,
53}
54
55pub fn seal_bundle(
56 bundle: &Value,
57 recipients: &[BundleRecipient],
58 level: &str,
59 signer_priv: &[u8; 32],
60 signer: &str,
61) -> EncryptedProofBundle {
62 let mut rng = rand::thread_rng();
63 let mut data_key = [0u8; 32];
64 rng.fill_bytes(&mut data_key);
65 let mut nonce_bytes = [0u8; 12];
66 rng.fill_bytes(&mut nonce_bytes);
67 let cipher = ChaCha20Poly1305::new(&data_key.into());
68 let plaintext = canonicalize(bundle).unwrap_or_default();
69 let ciphertext = cipher
70 .encrypt(Nonce::from_slice(&nonce_bytes), plaintext.as_bytes())
71 .expect("seal");
72
73 let mut wrapped_keys = Vec::with_capacity(recipients.len());
74 for r in recipients {
75 let mut eph_seed = [0u8; 32];
76 rng.fill_bytes(&mut eph_seed);
77 let eph = X25519Secret::from(eph_seed);
78 let eph_pub = X25519Public::from(&eph);
79 let recipient_pub = X25519Public::from(r.kem_public);
80 let shared = eph.diffie_hellman(&recipient_pub);
81 let hk = Hkdf::<Sha256>::new(None, shared.as_bytes());
82 let mut wrap_key = [0u8; 32];
83 hk.expand(HKDF_INFO, &mut wrap_key).expect("hkdf");
84 let mut wrap_nonce = [0u8; 12];
85 rng.fill_bytes(&mut wrap_nonce);
86 let wrap_cipher = ChaCha20Poly1305::new(&wrap_key.into());
87 let wrapped = wrap_cipher
88 .encrypt(Nonce::from_slice(&wrap_nonce), data_key.as_ref())
89 .expect("wrap");
90 wrapped_keys.push(WrappedKey {
91 recipient: r.actor.clone(),
92 recipient_key_id: r.key_id.clone(),
93 ephemeral_public: STANDARD.encode(eph_pub.as_bytes()),
94 wrapped: STANDARD.encode(&wrapped),
95 wrap_nonce: STANDARD.encode(wrap_nonce),
96 });
97 }
98
99 let mut stub = EncryptedProofBundle {
100 bundle_version: "1".into(),
101 level: level.into(),
102 ciphertext: STANDARD.encode(&ciphertext),
103 nonce: STANDARD.encode(nonce_bytes),
104 wrapped_keys,
105 transparency_anchor: None,
106 signature: SignatureEnvelope {
107 algorithm: "ed25519".into(),
108 signer: signer.into(),
109 signature: String::new(),
110 },
111 };
112 let digest = encrypted_signing_bytes(&stub);
113 let signing = SigningKey::from_bytes(signer_priv);
114 let sig: Signature = signing.sign(&digest);
115 stub.signature.signature = STANDARD.encode(sig.to_bytes());
116 stub
117}
118
119pub fn open_bundle(
120 enc: &EncryptedProofBundle,
121 recipient_priv: &[u8; 32],
122 recipient_actor: &str,
123 signer_pub: Option<&[u8; 32]>,
124) -> Result<Value, String> {
125 let wrap = enc
126 .wrapped_keys
127 .iter()
128 .find(|w| w.recipient == recipient_actor)
129 .ok_or_else(|| format!("no wrapped key for recipient {}", recipient_actor))?;
130 let eph_pub_bytes = STANDARD
131 .decode(&wrap.ephemeral_public)
132 .map_err(|e| format!("ephemeral_public base64: {}", e))?;
133 let mut eph_pub_arr = [0u8; 32];
134 if eph_pub_bytes.len() != 32 {
135 return Err("ephemeral_public not 32 bytes".into());
136 }
137 eph_pub_arr.copy_from_slice(&eph_pub_bytes);
138 let recipient_secret = X25519Secret::from(*recipient_priv);
139 let shared = recipient_secret.diffie_hellman(&X25519Public::from(eph_pub_arr));
140 let hk = Hkdf::<Sha256>::new(None, shared.as_bytes());
141 let mut wrap_key = [0u8; 32];
142 hk.expand(HKDF_INFO, &mut wrap_key)
143 .map_err(|e| e.to_string())?;
144 let wrapped = STANDARD
145 .decode(&wrap.wrapped)
146 .map_err(|e| format!("wrapped base64: {}", e))?;
147 let wrap_nonce = STANDARD
148 .decode(&wrap.wrap_nonce)
149 .map_err(|e| format!("wrap_nonce base64: {}", e))?;
150 let data_key_bytes = ChaCha20Poly1305::new(&wrap_key.into())
151 .decrypt(Nonce::from_slice(&wrap_nonce), wrapped.as_ref())
152 .map_err(|e| format!("unwrap: {}", e))?;
153 let ciphertext = STANDARD
154 .decode(&enc.ciphertext)
155 .map_err(|e| format!("ciphertext base64: {}", e))?;
156 let nonce = STANDARD
157 .decode(&enc.nonce)
158 .map_err(|e| format!("nonce base64: {}", e))?;
159 let mut data_key_arr = [0u8; 32];
160 if data_key_bytes.len() != 32 {
161 return Err("data_key not 32 bytes".into());
162 }
163 data_key_arr.copy_from_slice(&data_key_bytes);
164 let plaintext = ChaCha20Poly1305::new(&data_key_arr.into())
165 .decrypt(Nonce::from_slice(&nonce), ciphertext.as_ref())
166 .map_err(|e| format!("decrypt: {}", e))?;
167
168 if let Some(pk) = signer_pub {
169 let digest = encrypted_signing_bytes(enc);
170 let sig_bytes = STANDARD
171 .decode(&enc.signature.signature)
172 .map_err(|e| format!("signature base64: {}", e))?;
173 let sig = Signature::from_slice(&sig_bytes).map_err(|e| format!("sig parse: {}", e))?;
174 let vk = VerifyingKey::from_bytes(pk).map_err(|e| format!("verifying key: {}", e))?;
175 if vk.verify(&digest, &sig).is_err() {
176 return Err("encrypted bundle signature did not verify".into());
177 }
178 }
179
180 let json: Value = serde_json::from_slice(&plaintext).map_err(|e| e.to_string())?;
181 Ok(json)
182}
183
184pub fn encrypted_signing_bytes(enc: &EncryptedProofBundle) -> [u8; 32] {
185 let mut value = serde_json::to_value(enc).unwrap_or(Value::Null);
186 if let Value::Object(map) = &mut value {
187 map.remove("signature");
188 }
189 let canonical = canonicalize(&value).unwrap_or_default();
190 Sha256::digest(canonical.as_bytes()).into()
191}
192
193pub fn build_rfc3161_request(data: &[u8]) -> Vec<u8> {
195 let digest: [u8; 32] = Sha256::digest(data).into();
196 let oid_sha256: [u8; 11] = [
197 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
198 ];
199 let alg_id = der_sequence(&[oid_sha256.to_vec(), vec![0x05, 0x00]]);
200 let hashed_message = der_octet_string(&digest);
201 let message_imprint = der_sequence(&[alg_id, hashed_message]);
202 let version = der_integer(&[0x01]);
203 let cert_req = vec![0x01, 0x01, 0xff];
204 der_sequence(&[version, message_imprint, cert_req])
205}
206
207fn der_sequence(parts: &[Vec<u8>]) -> Vec<u8> {
208 let body: Vec<u8> = parts.iter().flat_map(|p| p.clone()).collect();
209 let mut out = Vec::with_capacity(2 + body.len());
210 out.push(0x30);
211 out.extend_from_slice(&der_len(body.len()));
212 out.extend_from_slice(&body);
213 out
214}
215
216fn der_octet_string(bytes: &[u8]) -> Vec<u8> {
217 let mut out = Vec::with_capacity(2 + bytes.len());
218 out.push(0x04);
219 out.extend_from_slice(&der_len(bytes.len()));
220 out.extend_from_slice(bytes);
221 out
222}
223
224fn der_integer(bytes: &[u8]) -> Vec<u8> {
225 let mut start = 0usize;
226 while start < bytes.len() - 1 && bytes[start] == 0 {
227 start += 1;
228 }
229 let payload = &bytes[start..];
230 let needs_pad = payload[0] & 0x80 != 0;
231 let len = payload.len() + if needs_pad { 1 } else { 0 };
232 let mut out = Vec::with_capacity(2 + len);
233 out.push(0x02);
234 out.extend_from_slice(&der_len(len));
235 if needs_pad {
236 out.push(0x00);
237 }
238 out.extend_from_slice(payload);
239 out
240}
241
242fn der_len(n: usize) -> Vec<u8> {
243 if n < 0x80 {
244 return vec![n as u8];
245 }
246 let mut bytes = Vec::new();
247 let mut v = n;
248 while v > 0 {
249 bytes.insert(0, (v & 0xff) as u8);
250 v >>= 8;
251 }
252 let mut out = Vec::with_capacity(1 + bytes.len());
253 out.push(0x80 | bytes.len() as u8);
254 out.extend_from_slice(&bytes);
255 out
256}
257
258pub trait AnchorBackend: Send + Sync {
262 fn kind(&self) -> &'static str;
263 fn submit(&self, bundle_bytes: &[u8]) -> Result<Value, String>;
264 fn verify(&self, bundle_bytes: &[u8], inclusion_proof: &Value) -> bool;
265}
266
267#[cfg(feature = "http-anchors")]
274pub struct Rfc6962Anchor {
275 pub log_url: String,
276}
277
278#[cfg(feature = "http-anchors")]
279impl Rfc6962Anchor {
280 pub fn new(log_url: impl Into<String>) -> Self {
281 Self {
282 log_url: log_url.into(),
283 }
284 }
285}
286
287#[cfg(feature = "http-anchors")]
288impl AnchorBackend for Rfc6962Anchor {
289 fn kind(&self) -> &'static str {
290 "rfc6962"
291 }
292 fn submit(&self, bundle_bytes: &[u8]) -> Result<Value, String> {
293 let digest = Sha256::digest(bundle_bytes);
299 let cert_b64 = crate::crypto::b64encode(&digest);
300 let body = serde_json::json!({ "chain": [cert_b64] });
301 let url = format!("{}/ct/v1/add-chain", self.log_url.trim_end_matches('/'));
302 let resp = ureq::post(&url)
303 .set("content-type", "application/json")
304 .send_json(body)
305 .map_err(|e| format!("rfc6962 submit: {e}"))?;
306 let json: Value = resp
307 .into_json()
308 .map_err(|e| format!("rfc6962 response parse: {e}"))?;
309 Ok(serde_json::json!({
310 "kind": "rfc6962",
311 "log_url": self.log_url,
312 "sct": json,
313 "digest_hex": digest.iter().map(|b| format!("{:02x}", b)).collect::<String>(),
314 }))
315 }
316 fn verify(&self, bundle_bytes: &[u8], inclusion_proof: &Value) -> bool {
317 let digest = Sha256::digest(bundle_bytes);
318 let want: String = digest.iter().map(|b| format!("{:02x}", b)).collect();
319 inclusion_proof
320 .get("digest_hex")
321 .and_then(|v| v.as_str())
322 .map(|s| s == want)
323 .unwrap_or(false)
324 }
325}
326
327#[cfg(feature = "http-anchors")]
331pub struct SigstoreAnchor {
332 pub rekor_url: String,
333}
334
335#[cfg(feature = "http-anchors")]
336impl SigstoreAnchor {
337 pub fn new(rekor_url: impl Into<String>) -> Self {
338 Self {
339 rekor_url: rekor_url.into(),
340 }
341 }
342}
343
344#[cfg(feature = "http-anchors")]
345impl AnchorBackend for SigstoreAnchor {
346 fn kind(&self) -> &'static str {
347 "sigstore"
348 }
349 fn submit(&self, bundle_bytes: &[u8]) -> Result<Value, String> {
350 let digest = Sha256::digest(bundle_bytes);
351 let hex: String = digest.iter().map(|b| format!("{:02x}", b)).collect();
352 let body = serde_json::json!({
354 "apiVersion": "0.0.1",
355 "kind": "hashedrekord",
356 "spec": {
357 "data": { "hash": { "algorithm": "sha256", "value": hex } },
358 "signature": { "format": "x509" },
359 },
360 });
361 let url = format!(
362 "{}/api/v1/log/entries",
363 self.rekor_url.trim_end_matches('/')
364 );
365 let resp = ureq::post(&url)
366 .set("content-type", "application/json")
367 .send_json(body)
368 .map_err(|e| format!("sigstore submit: {e}"))?;
369 let json: Value = resp
370 .into_json()
371 .map_err(|e| format!("sigstore response parse: {e}"))?;
372 Ok(serde_json::json!({
373 "kind": "sigstore",
374 "rekor_url": self.rekor_url,
375 "entry": json,
376 "digest_hex": digest.iter().map(|b| format!("{:02x}", b)).collect::<String>(),
377 }))
378 }
379 fn verify(&self, bundle_bytes: &[u8], inclusion_proof: &Value) -> bool {
380 let digest = Sha256::digest(bundle_bytes);
381 let want: String = digest.iter().map(|b| format!("{:02x}", b)).collect();
382 inclusion_proof
383 .get("digest_hex")
384 .and_then(|v| v.as_str())
385 .map(|s| s == want)
386 .unwrap_or(false)
387 }
388}
389
390#[derive(Default)]
392pub struct MemoryAnchor {
393 entries: std::sync::Mutex<std::collections::HashMap<String, usize>>,
394}
395
396impl MemoryAnchor {
397 pub fn new() -> Self {
398 Self::default()
399 }
400
401 pub fn submit(&self, bundle_bytes: &[u8]) -> Value {
402 let digest = Sha256::digest(bundle_bytes);
403 let hex: String = digest.iter().map(|b| format!("{:02x}", b)).collect();
404 let mut entries = self.entries.lock().unwrap();
405 let seq = entries.len();
406 entries.insert(hex.clone(), seq);
407 serde_json::json!({ "kind": "memory", "digest": hex, "sequence_number": seq })
408 }
409
410 pub fn verify_inclusion(&self, bundle_bytes: &[u8], inclusion_proof: &Value) -> bool {
411 let digest = Sha256::digest(bundle_bytes);
412 let hex: String = digest.iter().map(|b| format!("{:02x}", b)).collect();
413 if inclusion_proof.get("digest").and_then(|v| v.as_str()) != Some(&hex) {
414 return false;
415 }
416 let seq = inclusion_proof
417 .get("sequence_number")
418 .and_then(|v| v.as_u64())
419 .map(|n| n as usize);
420 let entries = self.entries.lock().unwrap();
421 seq == entries.get(&hex).copied()
422 }
423}
424
425impl AnchorBackend for MemoryAnchor {
426 fn kind(&self) -> &'static str {
427 "memory"
428 }
429 fn submit(&self, bundle_bytes: &[u8]) -> Result<Value, String> {
430 Ok(MemoryAnchor::submit(self, bundle_bytes))
431 }
432 fn verify(&self, bundle_bytes: &[u8], inclusion_proof: &Value) -> bool {
433 MemoryAnchor::verify_inclusion(self, bundle_bytes, inclusion_proof)
434 }
435}