1use crate::{
7 DIGEST_LEN, DdError, Digest, ErrorCode, HashAlgorithm, NONCE_LEN, PeerId, PublicIdentity,
8 RecipientWrap, Result,
9};
10use chacha20poly1305::ChaCha20Poly1305;
11use chacha20poly1305::aead::{Aead, KeyInit, Payload};
12use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
13use hkdf::Hkdf;
14use hmac::{Hmac, Mac};
15use rand::RngCore;
16use rand::rngs::OsRng;
17use sha2::{Digest as _, Sha256};
18use x25519_dalek::{PublicKey as XPublic, StaticSecret};
19use zeroize::{Zeroize, ZeroizeOnDrop};
20
21type HmacSha256 = Hmac<Sha256>;
22
23pub const ENC_SCHEME: &str = "x25519-hkdf-chacha20poly1305-v2";
24pub const SESSION_SCHEME: &str = "x25519-hkdf-chacha20poly1305-session-v2";
25
26pub trait CryptoProvider: Send + Sync {
27 fn hash(&self, alg: HashAlgorithm, data: &[u8]) -> Digest;
28 fn sign(&self, sk: &SigningKey, msg: &[u8]) -> [u8; 64];
29 fn verify(&self, pk: &[u8; 32], msg: &[u8], sig: &[u8; 64]) -> Result<()>;
30 fn aead_encrypt(
31 &self,
32 key: &[u8; 32],
33 nonce: &[u8; NONCE_LEN],
34 aad: &[u8],
35 pt: &[u8],
36 ) -> Result<Vec<u8>>;
37 fn aead_decrypt(
38 &self,
39 key: &[u8; 32],
40 nonce: &[u8; NONCE_LEN],
41 aad: &[u8],
42 ct: &[u8],
43 ) -> Result<Vec<u8>>;
44}
45
46#[derive(Debug, Default, Clone, Copy)]
47pub struct DefaultProvider;
48
49impl CryptoProvider for DefaultProvider {
50 fn hash(&self, alg: HashAlgorithm, data: &[u8]) -> Digest {
51 match alg {
52 HashAlgorithm::Blake3 => Digest(*blake3::hash(data).as_bytes()),
53 HashAlgorithm::Sha256 => Digest(Sha256::digest(data).into()),
54 }
55 }
56
57 fn sign(&self, sk: &SigningKey, msg: &[u8]) -> [u8; 64] {
58 sk.sign(msg).to_bytes()
59 }
60
61 fn verify(&self, pk: &[u8; 32], msg: &[u8], sig: &[u8; 64]) -> Result<()> {
62 let vk =
63 VerifyingKey::from_bytes(pk).map_err(|_| DdError::crypto("bad ed25519 public key"))?;
64 vk.verify(msg, &Signature::from_bytes(sig))
65 .map_err(|_| DdError::crypto("signature invalid"))
66 }
67
68 fn aead_encrypt(
69 &self,
70 key: &[u8; 32],
71 nonce: &[u8; NONCE_LEN],
72 aad: &[u8],
73 pt: &[u8],
74 ) -> Result<Vec<u8>> {
75 let aead =
76 ChaCha20Poly1305::new_from_slice(key).map_err(|_| DdError::crypto("aead key"))?;
77 aead.encrypt(nonce.into(), Payload { msg: pt, aad })
78 .map_err(|_| DdError::crypto("encrypt failed"))
79 }
80
81 fn aead_decrypt(
82 &self,
83 key: &[u8; 32],
84 nonce: &[u8; NONCE_LEN],
85 aad: &[u8],
86 ct: &[u8],
87 ) -> Result<Vec<u8>> {
88 let aead =
89 ChaCha20Poly1305::new_from_slice(key).map_err(|_| DdError::crypto("aead key"))?;
90 aead.decrypt(nonce.into(), Payload { msg: ct, aad })
91 .map_err(|_| DdError::crypto("decrypt failed"))
92 }
93}
94
95impl DefaultProvider {
96 pub fn peer_id(&self, ed: &[u8; 32], x: &[u8; 32]) -> PeerId {
97 let mut buf = Vec::from(&b"ddp-nid-v2"[..]);
98 buf.extend_from_slice(ed);
99 buf.extend_from_slice(x);
100 PeerId::from_digest(self.hash(HashAlgorithm::Blake3, &buf).0)
101 }
102}
103
104#[derive(Zeroize, ZeroizeOnDrop)]
105pub struct PrivateIdentity {
106 #[zeroize(skip)]
107 pub public: PublicIdentity,
108 #[zeroize(skip)]
109 pub peer_id: PeerId,
110 ed25519: [u8; 32],
111 x25519: [u8; 32],
112}
113
114impl PrivateIdentity {
115 pub fn generate() -> Self {
116 let mut ed = [0u8; 32];
117 let mut x = [0u8; 32];
118 OsRng.fill_bytes(&mut ed);
119 OsRng.fill_bytes(&mut x);
120 Self::from_secrets(ed, x)
121 }
122
123 pub fn from_secrets(ed25519: [u8; 32], x25519: [u8; 32]) -> Self {
124 let p = DefaultProvider;
125 let signing = SigningKey::from_bytes(&ed25519);
126 let ed_pk = signing.verifying_key().to_bytes();
127 let x_sk = StaticSecret::from(x25519);
128 let x_pk = XPublic::from(&x_sk).to_bytes();
129 let mut pre = Vec::from(&b"ddp-id-v2"[..]);
130 pre.extend_from_slice(&ed_pk);
131 pre.extend_from_slice(&x_pk);
132 let signature = p.sign(&signing, &pre);
133 let public = PublicIdentity {
134 version: 2,
135 ed25519_pk: ed_pk,
136 x25519_pk: x_pk,
137 signature,
138 };
139 let peer_id = p.peer_id(&ed_pk, &x_pk);
140 Self {
141 public,
142 peer_id,
143 ed25519,
144 x25519,
145 }
146 }
147
148 pub fn signing_key(&self) -> SigningKey {
149 SigningKey::from_bytes(&self.ed25519)
150 }
151
152 pub fn x25519_secret(&self) -> StaticSecret {
153 StaticSecret::from(self.x25519)
154 }
155
156 pub fn ed25519_bytes(&self) -> [u8; 32] {
157 self.ed25519
158 }
159
160 pub fn x25519_bytes(&self) -> [u8; 32] {
161 self.x25519
162 }
163}
164
165pub fn verify_identity(id: &PublicIdentity) -> Result<PeerId> {
166 if id.version != 2 {
167 return Err(DdError::protocol(
168 ErrorCode::Ddp1002UnsupportedVersion,
169 "identity version",
170 ));
171 }
172 let p = DefaultProvider;
173 let mut pre = Vec::from(&b"ddp-id-v2"[..]);
174 pre.extend_from_slice(&id.ed25519_pk);
175 pre.extend_from_slice(&id.x25519_pk);
176 p.verify(&id.ed25519_pk, &pre, &id.signature)?;
177 Ok(p.peer_id(&id.ed25519_pk, &id.x25519_pk))
178}
179
180pub fn random_bytes<const N: usize>() -> [u8; N] {
181 let mut b = [0u8; N];
182 OsRng.fill_bytes(&mut b);
183 b
184}
185
186pub fn generate_cek() -> [u8; 32] {
187 random_bytes()
188}
189
190fn wrap_derive(shared: &[u8; 32], eph_pk: &[u8; 32]) -> Result<[u8; 32]> {
191 let hk = Hkdf::<Sha256>::new(Some(eph_pk), shared);
192 let mut key = [0u8; 32];
193 hk.expand(b"ddp-wrap-v2", &mut key)
194 .map_err(|_| DdError::crypto("hkdf wrap"))?;
195 Ok(key)
196}
197
198pub fn wrap_cek(
199 recipient_x25519: &[u8; 32],
200 recipient: PeerId,
201 cek: &[u8; 32],
202) -> Result<RecipientWrap> {
203 let eph_sk = StaticSecret::random_from_rng(OsRng);
204 let eph_pk = XPublic::from(&eph_sk);
205 let shared = eph_sk.diffie_hellman(&XPublic::from(*recipient_x25519));
206 let key = wrap_derive(shared.as_bytes(), eph_pk.as_bytes())?;
207 let nonce = random_bytes::<NONCE_LEN>();
208 let p = DefaultProvider;
209 let mut aad = Vec::from(&b"ddp-wrap-aad-v2"[..]);
210 aad.extend_from_slice(recipient.as_bytes());
211 let wrapped = p.aead_encrypt(&key, &nonce, &aad, cek)?;
212 Ok(RecipientWrap {
213 peer: recipient,
214 eph_pk: eph_pk.to_bytes(),
215 nonce,
216 wrapped_key: wrapped,
217 })
218}
219
220pub fn unwrap_cek(identity: &PrivateIdentity, wrap: &RecipientWrap) -> Result<[u8; 32]> {
221 if wrap.peer != identity.peer_id {
222 return Err(DdError::protocol(
223 ErrorCode::Ddp1008NotRecipient,
224 "wrap is for another peer",
225 ));
226 }
227 let eph = XPublic::from(wrap.eph_pk);
228 let shared = identity.x25519_secret().diffie_hellman(&eph);
229 let key = wrap_derive(shared.as_bytes(), eph.as_bytes())?;
230 let p = DefaultProvider;
231 let mut aad = Vec::from(&b"ddp-wrap-aad-v2"[..]);
232 aad.extend_from_slice(identity.peer_id.as_bytes());
233 let cek = p.aead_decrypt(&key, &wrap.nonce, &aad, &wrap.wrapped_key)?;
234 if cek.len() != 32 {
235 return Err(DdError::crypto("wrapped key length"));
236 }
237 let mut out = [0u8; 32];
238 out.copy_from_slice(&cek);
239 Ok(out)
240}
241
242pub fn encrypt_payload(
243 cek: &[u8; 32],
244 nonce: &[u8; NONCE_LEN],
245 aad: &[u8],
246 pt: &[u8],
247) -> Result<Vec<u8>> {
248 DefaultProvider.aead_encrypt(cek, nonce, aad, pt)
249}
250
251pub fn decrypt_payload(
252 cek: &[u8; 32],
253 nonce: &[u8; NONCE_LEN],
254 aad: &[u8],
255 ct: &[u8],
256) -> Result<Vec<u8>> {
257 DefaultProvider.aead_decrypt(cek, nonce, aad, ct)
258}
259
260pub struct SessionKeys {
262 pub send: [u8; 32],
263 pub recv: [u8; 32],
264 pub transcript: [u8; DIGEST_LEN],
265}
266
267pub fn session_keys(
268 initiator: bool,
269 local_eph_sk: &StaticSecret,
270 remote_eph_pk: &XPublic,
271 transcript: &[u8],
272) -> Result<SessionKeys> {
273 let shared = local_eph_sk.diffie_hellman(remote_eph_pk);
274 let hk = Hkdf::<Sha256>::new(Some(transcript), shared.as_bytes());
275 let mut okm = [0u8; 64];
276 hk.expand(b"ddp-session-v2", &mut okm)
277 .map_err(|_| DdError::crypto("hkdf session"))?;
278 let mut a = [0u8; 32];
279 let mut b = [0u8; 32];
280 a.copy_from_slice(&okm[..32]);
281 b.copy_from_slice(&okm[32..]);
282 let transcript_d = DefaultProvider.hash(HashAlgorithm::Blake3, transcript).0;
283 Ok(if initiator {
284 SessionKeys {
285 send: a,
286 recv: b,
287 transcript: transcript_d,
288 }
289 } else {
290 SessionKeys {
291 send: b,
292 recv: a,
293 transcript: transcript_d,
294 }
295 })
296}
297
298pub fn hmac_tag(key: &[u8], data: &[u8]) -> [u8; 16] {
299 let mut mac = <HmacSha256 as Mac>::new_from_slice(key).expect("hmac");
300 mac.update(data);
301 let out = mac.finalize().into_bytes();
302 let mut tag = [0u8; 16];
303 tag.copy_from_slice(&out[..16]);
304 tag
305}
306
307pub fn u64_be(n: u64) -> [u8; 8] {
308 n.to_be_bytes()
309}
310
311pub fn push_lp_str(buf: &mut Vec<u8>, s: &str) {
312 let bytes = s.as_bytes();
313 buf.extend_from_slice(&(bytes.len() as u16).to_be_bytes());
314 buf.extend_from_slice(bytes);
315}
316
317pub fn x25519_ephemeral() -> (StaticSecret, XPublic) {
318 let sk = StaticSecret::random_from_rng(OsRng);
319 let pk = XPublic::from(&sk);
320 (sk, pk)
321}
322
323pub use x25519_dalek::{PublicKey as X25519Public, StaticSecret as X25519Secret};
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn identity_and_wrap() {
331 let a = PrivateIdentity::generate();
332 let b = PrivateIdentity::generate();
333 assert_eq!(verify_identity(&a.public).unwrap(), a.peer_id);
334 let cek = generate_cek();
335 let wrap = wrap_cek(&b.public.x25519_pk, b.peer_id, &cek).unwrap();
336 assert_eq!(unwrap_cek(&b, &wrap).unwrap(), cek);
337 assert!(unwrap_cek(&a, &wrap).is_err());
338 }
339
340 #[test]
341 fn decode_encode_hash() {
342 let p = DefaultProvider;
343 let d = p.hash(HashAlgorithm::Blake3, b"abc");
344 assert_eq!(d.0.len(), 32);
345 }
346}