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 mut aad = Map::new();
96 aad.insert(
97 "acp_version".to_string(),
98 Value::String(envelope.acp_version.clone()),
99 );
100 aad.insert(
101 "message_id".to_string(),
102 Value::String(envelope.message_id.clone()),
103 );
104 aad.insert(
105 "operation_id".to_string(),
106 Value::String(envelope.operation_id.clone()),
107 );
108 aad.insert("sender".to_string(), Value::String(envelope.sender.clone()));
109 aad.insert(
110 "recipients".to_string(),
111 Value::Array(
112 envelope
113 .recipients
114 .iter()
115 .map(|recipient| Value::String(recipient.clone()))
116 .collect(),
117 ),
118 );
119 if let Some(tenant) = envelope.tenant.as_ref() {
120 aad.insert("tenant".to_string(), Value::String(tenant.clone()));
121 }
122 json_support::canonical_json_bytes(&Value::Object(aad))
123}
124
125pub fn encrypt_for_recipients(
126 payload: &Map<String, Value>,
127 envelope: &Envelope,
128 recipient_encryption_public_keys: &HashMap<String, String>,
129) -> AcpResult<ProtectedPayload> {
130 let plaintext = canonical_json(&Value::Object(payload.clone()))?.into_bytes();
131 let mut content_key = [0u8; 32];
132 rand::thread_rng().fill_bytes(&mut content_key);
133 let mut nonce = [0u8; 12];
134 rand::thread_rng().fill_bytes(&mut nonce);
135
136 let payload_aad = envelope_aad(envelope)?;
137 let payload_cipher = Aes256Gcm::new_from_slice(&content_key)
138 .map_err(|e| AcpError::Crypto(format!("unable to initialize payload cipher: {e}")))?;
139 let ciphertext = payload_cipher
140 .encrypt(
141 Nonce::from_slice(&nonce),
142 Payload {
143 msg: &plaintext,
144 aad: &payload_aad,
145 },
146 )
147 .map_err(|e| AcpError::Crypto(format!("payload encryption failed: {e}")))?;
148
149 let mut ephemeral_secret_bytes = [0u8; 32];
150 rand::thread_rng().fill_bytes(&mut ephemeral_secret_bytes);
151 let ephemeral_private = StaticSecret::from(ephemeral_secret_bytes);
152 let ephemeral_public = X25519PublicKey::from(&ephemeral_private);
153
154 let mut wrapped_content_keys = Vec::new();
155 for (recipient, recipient_public_key_b64) in recipient_encryption_public_keys {
156 let recipient_public = decode_x25519_public(recipient_public_key_b64)?;
157 let shared_secret = ephemeral_private.diffie_hellman(&recipient_public);
158 let wrap_key = derive_wrap_key(shared_secret.as_bytes(), recipient)?;
159 let mut wrap_nonce = [0u8; 12];
160 rand::thread_rng().fill_bytes(&mut wrap_nonce);
161 let wrap_cipher = Aes256Gcm::new_from_slice(&wrap_key)
162 .map_err(|e| AcpError::Crypto(format!("unable to initialize wrap cipher: {e}")))?;
163 let wrapped_cek = wrap_cipher
164 .encrypt(
165 Nonce::from_slice(&wrap_nonce),
166 Payload {
167 msg: &content_key,
168 aad: envelope.message_id.as_bytes(),
169 },
170 )
171 .map_err(|e| AcpError::Crypto(format!("content key wrap failed: {e}")))?;
172 wrapped_content_keys.push(WrappedContentKey {
173 recipient: recipient.to_string(),
174 ephemeral_public_key: b64_encode(ephemeral_public.as_bytes()),
175 nonce: b64_encode(&wrap_nonce),
176 ciphertext: b64_encode(&wrapped_cek),
177 });
178 }
179
180 Ok(ProtectedPayload {
181 nonce: b64_encode(&nonce),
182 ciphertext: b64_encode(&ciphertext),
183 wrapped_content_keys,
184 payload_hash: sha256_hex(&ciphertext),
185 signature_kid: String::new(),
186 signature: String::new(),
187 })
188}
189
190pub fn sign_protected_payload(
191 envelope: &Envelope,
192 protected_payload: &mut ProtectedPayload,
193 signing_private_key_b64: &str,
194 signature_kid: &str,
195) -> AcpResult<()> {
196 protected_payload.signature_kid = signature_kid.to_string();
197 let input = message_signature_input(envelope, protected_payload)?;
198 protected_payload.signature = sign_bytes(&input, signing_private_key_b64)?;
199 Ok(())
200}
201
202pub fn verify_protected_payload_signature(
203 envelope: &Envelope,
204 protected_payload: &ProtectedPayload,
205 sender_signing_public_key_b64: &str,
206) -> bool {
207 if protected_payload.signature.trim().is_empty() {
208 return false;
209 }
210 let Ok(input) = message_signature_input(envelope, protected_payload) else {
211 return false;
212 };
213 verify_signature(
214 &input,
215 &protected_payload.signature,
216 sender_signing_public_key_b64,
217 )
218}
219
220pub fn decrypt_for_recipient(
221 envelope: &Envelope,
222 protected_payload: &ProtectedPayload,
223 recipient_id: &str,
224 recipient_encryption_private_key_b64: &str,
225) -> AcpResult<Map<String, Value>> {
226 let matching = protected_payload
227 .wrapped_content_keys
228 .iter()
229 .find(|item| item.recipient == recipient_id)
230 .ok_or_else(|| {
231 AcpError::Crypto(format!(
232 "No wrapped content key available for recipient {recipient_id}"
233 ))
234 })?;
235
236 let recipient_private = decode_x25519_private(recipient_encryption_private_key_b64)?;
237 let ephemeral_public = decode_x25519_public(&matching.ephemeral_public_key)?;
238 let shared_secret = recipient_private.diffie_hellman(&ephemeral_public);
239 let wrap_key = derive_wrap_key(shared_secret.as_bytes(), recipient_id)?;
240
241 let wrap_cipher = Aes256Gcm::new_from_slice(&wrap_key)
242 .map_err(|e| AcpError::Crypto(format!("unable to initialize wrap cipher: {e}")))?;
243 let wrapped_nonce = b64_decode(&matching.nonce)?;
244 let wrapped_nonce: [u8; 12] = wrapped_nonce
245 .try_into()
246 .map_err(|_| AcpError::Crypto("invalid wrapped nonce length".to_string()))?;
247 let content_key = wrap_cipher
248 .decrypt(
249 Nonce::from_slice(&wrapped_nonce),
250 Payload {
251 msg: &b64_decode(&matching.ciphertext)?,
252 aad: envelope.message_id.as_bytes(),
253 },
254 )
255 .map_err(|e| AcpError::Crypto(format!("failed to unwrap content key: {e}")))?;
256
257 let payload_cipher = Aes256Gcm::new_from_slice(&content_key)
258 .map_err(|e| AcpError::Crypto(format!("unable to initialize payload cipher: {e}")))?;
259 let payload_nonce = b64_decode(&protected_payload.nonce)?;
260 let payload_nonce: [u8; 12] = payload_nonce
261 .try_into()
262 .map_err(|_| AcpError::Crypto("invalid payload nonce length".to_string()))?;
263 let plaintext = payload_cipher
264 .decrypt(
265 Nonce::from_slice(&payload_nonce),
266 Payload {
267 msg: &b64_decode(&protected_payload.ciphertext)?,
268 aad: &envelope_aad(envelope)?,
269 },
270 )
271 .map_err(|e| AcpError::Crypto(format!("failed to decrypt message payload: {e}")))?;
272 let payload_value: Value = serde_json::from_slice(&plaintext)?;
273 match payload_value {
274 Value::Object(map) => Ok(map),
275 _ => Err(AcpError::Crypto(
276 "decrypted payload is not a JSON object".to_string(),
277 )),
278 }
279}
280
281fn message_signature_input(
282 envelope: &Envelope,
283 protected: &ProtectedPayload,
284) -> AcpResult<Vec<u8>> {
285 let value = serde_json::json!({
286 "envelope": envelope,
287 "protected": protected.to_signable_value(),
288 });
289 json_support::canonical_json_bytes(&value)
290}
291
292fn decode_x25519_public(value: &str) -> AcpResult<X25519PublicKey> {
293 let bytes = b64_decode(value)?;
294 let bytes: [u8; 32] = bytes
295 .try_into()
296 .map_err(|_| AcpError::Crypto("invalid X25519 public key length".to_string()))?;
297 Ok(X25519PublicKey::from(bytes))
298}
299
300fn decode_x25519_private(value: &str) -> AcpResult<StaticSecret> {
301 let bytes = b64_decode(value)?;
302 let bytes: [u8; 32] = bytes
303 .try_into()
304 .map_err(|_| AcpError::Crypto("invalid X25519 private key length".to_string()))?;
305 Ok(StaticSecret::from(bytes))
306}
307
308fn derive_wrap_key(shared_secret: &[u8], recipient: &str) -> AcpResult<[u8; 32]> {
309 let hkdf = Hkdf::<Sha256>::new(None, shared_secret);
310 let mut out = [0u8; 32];
311 hkdf.expand(format!("acp-v1-wrap:{recipient}").as_bytes(), &mut out)
312 .map_err(|e| AcpError::Crypto(format!("hkdf expand failed: {e}")))?;
313 Ok(out)
314}