1use aes_gcm::aead::{Aead, KeyInit, Payload};
6use aes_gcm::{Aes256Gcm, Nonce};
7use base64::Engine;
8use base64::engine::general_purpose::URL_SAFE;
9use ed25519_dalek::{Signer, Verifier};
10use hkdf::Hkdf;
11use rand::RngCore;
12use serde_json::{Map, Value};
13use sha2::{Digest, Sha256};
14use std::collections::HashMap;
15use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
16
17use crate::errors::{AcpError, AcpResult};
18use crate::json_support;
19use crate::messages::{Envelope, ProtectedPayload, WrappedContentKey};
20
21pub fn b64_encode(value: &[u8]) -> String {
22 URL_SAFE.encode(value)
23}
24
25pub fn b64_decode(value: &str) -> AcpResult<Vec<u8>> {
26 URL_SAFE
27 .decode(value.as_bytes())
28 .map_err(|e| AcpError::Crypto(format!("invalid base64 value: {e}")))
29}
30
31pub fn canonical_json(value: &Value) -> AcpResult<String> {
32 json_support::canonical_json_string(value)
33}
34
35pub fn sha256_hex(value: &[u8]) -> String {
36 let mut hasher = Sha256::new();
37 hasher.update(value);
38 hex::encode(hasher.finalize())
39}
40
41pub fn generate_ed25519_keypair() -> (String, String) {
42 let mut secret = [0u8; 32];
43 rand::thread_rng().fill_bytes(&mut secret);
44 let signing_key = ed25519_dalek::SigningKey::from_bytes(&secret);
45 let verify_key = signing_key.verifying_key();
46 (
47 b64_encode(&signing_key.to_bytes()),
48 b64_encode(&verify_key.to_bytes()),
49 )
50}
51
52pub fn generate_x25519_keypair() -> (String, String) {
53 let mut secret = [0u8; 32];
54 rand::thread_rng().fill_bytes(&mut secret);
55 let private_key = StaticSecret::from(secret);
56 let public_key = X25519PublicKey::from(&private_key);
57 (
58 b64_encode(&private_key.to_bytes()),
59 b64_encode(public_key.as_bytes()),
60 )
61}
62
63pub fn sign_bytes(data: &[u8], signing_private_key_b64: &str) -> AcpResult<String> {
64 let key_bytes = b64_decode(signing_private_key_b64)?;
65 let key_bytes: [u8; 32] = key_bytes
66 .try_into()
67 .map_err(|_| AcpError::Crypto("invalid Ed25519 private key length".to_string()))?;
68 let signing_key = ed25519_dalek::SigningKey::from_bytes(&key_bytes);
69 Ok(b64_encode(&signing_key.sign(data).to_bytes()))
70}
71
72pub fn verify_signature(data: &[u8], signature_b64: &str, signing_public_key_b64: &str) -> bool {
73 let Ok(signature_bytes) = b64_decode(signature_b64) else {
74 return false;
75 };
76 let Ok(signature_bytes) = <[u8; 64]>::try_from(signature_bytes) else {
77 return false;
78 };
79 let Ok(public_key_bytes) = b64_decode(signing_public_key_b64) else {
80 return false;
81 };
82 let Ok(public_key_bytes) = <[u8; 32]>::try_from(public_key_bytes) else {
83 return false;
84 };
85 let Ok(signature) = ed25519_dalek::Signature::try_from(signature_bytes.as_slice()) else {
86 return false;
87 };
88 let Ok(verifying_key) = ed25519_dalek::VerifyingKey::from_bytes(&public_key_bytes) else {
89 return false;
90 };
91 verifying_key.verify(data, &signature).is_ok()
92}
93
94pub fn envelope_aad(envelope: &Envelope) -> AcpResult<Vec<u8>> {
95 let value = serde_json::json!({
96 "acp_version": envelope.acp_version,
97 "message_id": envelope.message_id,
98 "operation_id": envelope.operation_id,
99 "sender": envelope.sender,
100 "recipients": envelope.recipients,
101 });
102 json_support::canonical_json_bytes(&value)
103}
104
105pub fn encrypt_for_recipients(
106 payload: &Map<String, Value>,
107 envelope: &Envelope,
108 recipient_encryption_public_keys: &HashMap<String, String>,
109) -> AcpResult<ProtectedPayload> {
110 let plaintext = canonical_json(&Value::Object(payload.clone()))?.into_bytes();
111 let mut content_key = [0u8; 32];
112 rand::thread_rng().fill_bytes(&mut content_key);
113 let mut nonce = [0u8; 12];
114 rand::thread_rng().fill_bytes(&mut nonce);
115
116 let payload_aad = envelope_aad(envelope)?;
117 let payload_cipher = Aes256Gcm::new_from_slice(&content_key)
118 .map_err(|e| AcpError::Crypto(format!("unable to initialize payload cipher: {e}")))?;
119 let ciphertext = payload_cipher
120 .encrypt(
121 Nonce::from_slice(&nonce),
122 Payload {
123 msg: &plaintext,
124 aad: &payload_aad,
125 },
126 )
127 .map_err(|e| AcpError::Crypto(format!("payload encryption failed: {e}")))?;
128
129 let mut ephemeral_secret_bytes = [0u8; 32];
130 rand::thread_rng().fill_bytes(&mut ephemeral_secret_bytes);
131 let ephemeral_private = StaticSecret::from(ephemeral_secret_bytes);
132 let ephemeral_public = X25519PublicKey::from(&ephemeral_private);
133
134 let mut wrapped_content_keys = Vec::new();
135 for (recipient, recipient_public_key_b64) in recipient_encryption_public_keys {
136 let recipient_public = decode_x25519_public(recipient_public_key_b64)?;
137 let shared_secret = ephemeral_private.diffie_hellman(&recipient_public);
138 let wrap_key = derive_wrap_key(shared_secret.as_bytes(), recipient)?;
139 let mut wrap_nonce = [0u8; 12];
140 rand::thread_rng().fill_bytes(&mut wrap_nonce);
141 let wrap_cipher = Aes256Gcm::new_from_slice(&wrap_key)
142 .map_err(|e| AcpError::Crypto(format!("unable to initialize wrap cipher: {e}")))?;
143 let wrapped_cek = wrap_cipher
144 .encrypt(
145 Nonce::from_slice(&wrap_nonce),
146 Payload {
147 msg: &content_key,
148 aad: envelope.message_id.as_bytes(),
149 },
150 )
151 .map_err(|e| AcpError::Crypto(format!("content key wrap failed: {e}")))?;
152 wrapped_content_keys.push(WrappedContentKey {
153 recipient: recipient.to_string(),
154 ephemeral_public_key: b64_encode(ephemeral_public.as_bytes()),
155 nonce: b64_encode(&wrap_nonce),
156 ciphertext: b64_encode(&wrapped_cek),
157 });
158 }
159
160 Ok(ProtectedPayload {
161 nonce: b64_encode(&nonce),
162 ciphertext: b64_encode(&ciphertext),
163 wrapped_content_keys,
164 payload_hash: sha256_hex(&ciphertext),
165 signature_kid: String::new(),
166 signature: String::new(),
167 })
168}
169
170pub fn sign_protected_payload(
171 envelope: &Envelope,
172 protected_payload: &mut ProtectedPayload,
173 signing_private_key_b64: &str,
174 signature_kid: &str,
175) -> AcpResult<()> {
176 protected_payload.signature_kid = signature_kid.to_string();
177 let input = message_signature_input(envelope, protected_payload)?;
178 protected_payload.signature = sign_bytes(&input, signing_private_key_b64)?;
179 Ok(())
180}
181
182pub fn verify_protected_payload_signature(
183 envelope: &Envelope,
184 protected_payload: &ProtectedPayload,
185 sender_signing_public_key_b64: &str,
186) -> bool {
187 if protected_payload.signature.trim().is_empty() {
188 return false;
189 }
190 let Ok(input) = message_signature_input(envelope, protected_payload) else {
191 return false;
192 };
193 verify_signature(
194 &input,
195 &protected_payload.signature,
196 sender_signing_public_key_b64,
197 )
198}
199
200pub fn decrypt_for_recipient(
201 envelope: &Envelope,
202 protected_payload: &ProtectedPayload,
203 recipient_id: &str,
204 recipient_encryption_private_key_b64: &str,
205) -> AcpResult<Map<String, Value>> {
206 let matching = protected_payload
207 .wrapped_content_keys
208 .iter()
209 .find(|item| item.recipient == recipient_id)
210 .ok_or_else(|| {
211 AcpError::Crypto(format!(
212 "No wrapped content key available for recipient {recipient_id}"
213 ))
214 })?;
215
216 let recipient_private = decode_x25519_private(recipient_encryption_private_key_b64)?;
217 let ephemeral_public = decode_x25519_public(&matching.ephemeral_public_key)?;
218 let shared_secret = recipient_private.diffie_hellman(&ephemeral_public);
219 let wrap_key = derive_wrap_key(shared_secret.as_bytes(), recipient_id)?;
220
221 let wrap_cipher = Aes256Gcm::new_from_slice(&wrap_key)
222 .map_err(|e| AcpError::Crypto(format!("unable to initialize wrap cipher: {e}")))?;
223 let wrapped_nonce = b64_decode(&matching.nonce)?;
224 let wrapped_nonce: [u8; 12] = wrapped_nonce
225 .try_into()
226 .map_err(|_| AcpError::Crypto("invalid wrapped nonce length".to_string()))?;
227 let content_key = wrap_cipher
228 .decrypt(
229 Nonce::from_slice(&wrapped_nonce),
230 Payload {
231 msg: &b64_decode(&matching.ciphertext)?,
232 aad: envelope.message_id.as_bytes(),
233 },
234 )
235 .map_err(|e| AcpError::Crypto(format!("failed to unwrap content key: {e}")))?;
236
237 let payload_cipher = Aes256Gcm::new_from_slice(&content_key)
238 .map_err(|e| AcpError::Crypto(format!("unable to initialize payload cipher: {e}")))?;
239 let payload_nonce = b64_decode(&protected_payload.nonce)?;
240 let payload_nonce: [u8; 12] = payload_nonce
241 .try_into()
242 .map_err(|_| AcpError::Crypto("invalid payload nonce length".to_string()))?;
243 let plaintext = payload_cipher
244 .decrypt(
245 Nonce::from_slice(&payload_nonce),
246 Payload {
247 msg: &b64_decode(&protected_payload.ciphertext)?,
248 aad: &envelope_aad(envelope)?,
249 },
250 )
251 .map_err(|e| AcpError::Crypto(format!("failed to decrypt message payload: {e}")))?;
252 let payload_value: Value = serde_json::from_slice(&plaintext)?;
253 match payload_value {
254 Value::Object(map) => Ok(map),
255 _ => Err(AcpError::Crypto(
256 "decrypted payload is not a JSON object".to_string(),
257 )),
258 }
259}
260
261fn message_signature_input(
262 envelope: &Envelope,
263 protected: &ProtectedPayload,
264) -> AcpResult<Vec<u8>> {
265 let value = serde_json::json!({
266 "envelope": envelope,
267 "protected": protected.to_signable_value(),
268 });
269 json_support::canonical_json_bytes(&value)
270}
271
272fn decode_x25519_public(value: &str) -> AcpResult<X25519PublicKey> {
273 let bytes = b64_decode(value)?;
274 let bytes: [u8; 32] = bytes
275 .try_into()
276 .map_err(|_| AcpError::Crypto("invalid X25519 public key length".to_string()))?;
277 Ok(X25519PublicKey::from(bytes))
278}
279
280fn decode_x25519_private(value: &str) -> AcpResult<StaticSecret> {
281 let bytes = b64_decode(value)?;
282 let bytes: [u8; 32] = bytes
283 .try_into()
284 .map_err(|_| AcpError::Crypto("invalid X25519 private key length".to_string()))?;
285 Ok(StaticSecret::from(bytes))
286}
287
288fn derive_wrap_key(shared_secret: &[u8], recipient: &str) -> AcpResult<[u8; 32]> {
289 let hkdf = Hkdf::<Sha256>::new(None, shared_secret);
290 let mut out = [0u8; 32];
291 hkdf.expand(format!("acp-v1-wrap:{recipient}").as_bytes(), &mut out)
292 .map_err(|e| AcpError::Crypto(format!("hkdf expand failed: {e}")))?;
293 Ok(out)
294}