1use coset::{iana, CborSerializable, Label};
7use generic_array::GenericArray;
8use typenum::U32;
9
10use crate::{
11 error::EncStringParseError, xchacha20, CryptoError, SymmetricCryptoKey, XChaCha20Poly1305Key,
12};
13
14pub(crate) const XCHACHA20_POLY1305: i64 = -70000;
18
19pub(crate) fn encrypt_xchacha20_poly1305(
21 plaintext: &[u8],
22 key: &crate::XChaCha20Poly1305Key,
23) -> Result<Vec<u8>, CryptoError> {
24 let mut protected_header = coset::HeaderBuilder::new().build();
25 protected_header.alg = Some(coset::Algorithm::PrivateUse(XCHACHA20_POLY1305));
29
30 let mut nonce = [0u8; xchacha20::NONCE_SIZE];
31 let cose_encrypt0 = coset::CoseEncrypt0Builder::new()
32 .protected(protected_header)
33 .create_ciphertext(plaintext, &[], |data, aad| {
34 let ciphertext =
35 crate::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad);
36 nonce = ciphertext.nonce();
37 ciphertext.encrypted_bytes().to_vec()
38 })
39 .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build())
40 .build();
41
42 cose_encrypt0
43 .to_vec()
44 .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))
45}
46
47pub(crate) fn decrypt_xchacha20_poly1305(
49 cose_encrypt0_message: &[u8],
50 key: &crate::XChaCha20Poly1305Key,
51) -> Result<Vec<u8>, CryptoError> {
52 let msg = coset::CoseEncrypt0::from_slice(cose_encrypt0_message)
53 .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?;
54 let Some(ref alg) = msg.protected.header.alg else {
55 return Err(CryptoError::EncString(
56 EncStringParseError::CoseMissingAlgorithm,
57 ));
58 };
59 if *alg != coset::Algorithm::PrivateUse(XCHACHA20_POLY1305) {
60 return Err(CryptoError::WrongKeyType);
61 }
62
63 let decrypted_message = msg.decrypt(&[], |data, aad| {
64 let nonce = msg.unprotected.iv.as_slice();
65 crate::xchacha20::decrypt_xchacha20_poly1305(
66 nonce
67 .try_into()
68 .map_err(|_| CryptoError::InvalidNonceLength)?,
69 &(*key.enc_key).into(),
70 data,
71 aad,
72 )
73 })?;
74 Ok(decrypted_message)
75}
76
77const SYMMETRIC_KEY: Label = Label::Int(iana::SymmetricKeyParameter::K as i64);
78
79impl TryFrom<&coset::CoseKey> for SymmetricCryptoKey {
80 type Error = CryptoError;
81
82 fn try_from(cose_key: &coset::CoseKey) -> Result<Self, Self::Error> {
83 let key_bytes = cose_key
84 .params
85 .iter()
86 .find_map(|(label, value)| match (label, value) {
87 (&SYMMETRIC_KEY, ciborium::Value::Bytes(bytes)) => Some(bytes),
88 _ => None,
89 })
90 .ok_or(CryptoError::InvalidKey)?;
91 let alg = cose_key.alg.as_ref().ok_or(CryptoError::InvalidKey)?;
92
93 match alg {
94 coset::Algorithm::PrivateUse(XCHACHA20_POLY1305) => {
95 if key_bytes.len() != xchacha20::KEY_SIZE {
98 return Err(CryptoError::InvalidKey);
99 }
100 let enc_key = Box::pin(GenericArray::<u8, U32>::clone_from_slice(key_bytes));
101 let key_id = cose_key
102 .key_id
103 .as_slice()
104 .try_into()
105 .map_err(|_| CryptoError::InvalidKey)?;
106 Ok(SymmetricCryptoKey::XChaCha20Poly1305Key(
107 XChaCha20Poly1305Key { enc_key, key_id },
108 ))
109 }
110 _ => Err(CryptoError::InvalidKey),
111 }
112 }
113}
114
115#[cfg(test)]
116mod test {
117 use super::*;
118
119 #[test]
120 fn test_encrypt_decrypt_roundtrip() {
121 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) =
122 SymmetricCryptoKey::make_xchacha20_poly1305_key()
123 else {
124 panic!("Failed to create XChaCha20Poly1305Key");
125 };
126
127 let plaintext = b"Hello, world!";
128 let encrypted = encrypt_xchacha20_poly1305(plaintext, key).unwrap();
129 let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap();
130 assert_eq!(decrypted, plaintext);
131 }
132}