1use coset::{iana, CborSerializable, Label};
7use generic_array::GenericArray;
8use typenum::U32;
9
10use crate::{
11 error::{EncStringParseError, EncodingError},
12 xchacha20, CryptoError, SymmetricCryptoKey, XChaCha20Poly1305Key,
13};
14
15pub(crate) const XCHACHA20_POLY1305: i64 = -70000;
19
20pub(crate) const SIGNING_NAMESPACE: i64 = -80000;
24
25pub(crate) fn encrypt_xchacha20_poly1305(
27 plaintext: &[u8],
28 key: &crate::XChaCha20Poly1305Key,
29) -> Result<Vec<u8>, CryptoError> {
30 let mut protected_header = coset::HeaderBuilder::new()
31 .key_id(key.key_id.to_vec())
32 .build();
33 protected_header.alg = Some(coset::Algorithm::PrivateUse(XCHACHA20_POLY1305));
37
38 let mut nonce = [0u8; xchacha20::NONCE_SIZE];
39 let cose_encrypt0 = coset::CoseEncrypt0Builder::new()
40 .protected(protected_header)
41 .create_ciphertext(plaintext, &[], |data, aad| {
42 let ciphertext =
43 crate::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad);
44 nonce = ciphertext.nonce();
45 ciphertext.encrypted_bytes().to_vec()
46 })
47 .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build())
48 .build();
49
50 cose_encrypt0
51 .to_vec()
52 .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))
53}
54
55pub(crate) fn decrypt_xchacha20_poly1305(
57 cose_encrypt0_message: &[u8],
58 key: &crate::XChaCha20Poly1305Key,
59) -> Result<Vec<u8>, CryptoError> {
60 let msg = coset::CoseEncrypt0::from_slice(cose_encrypt0_message)
61 .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?;
62 let Some(ref alg) = msg.protected.header.alg else {
63 return Err(CryptoError::EncString(
64 EncStringParseError::CoseMissingAlgorithm,
65 ));
66 };
67 if *alg != coset::Algorithm::PrivateUse(XCHACHA20_POLY1305) {
68 return Err(CryptoError::WrongKeyType);
69 }
70 if key.key_id != *msg.protected.header.key_id {
71 return Err(CryptoError::WrongCoseKeyId);
72 }
73
74 let decrypted_message = msg.decrypt(&[], |data, aad| {
75 let nonce = msg.unprotected.iv.as_slice();
76 crate::xchacha20::decrypt_xchacha20_poly1305(
77 nonce
78 .try_into()
79 .map_err(|_| CryptoError::InvalidNonceLength)?,
80 &(*key.enc_key).into(),
81 data,
82 aad,
83 )
84 })?;
85 Ok(decrypted_message)
86}
87
88const SYMMETRIC_KEY: Label = Label::Int(iana::SymmetricKeyParameter::K as i64);
89
90impl TryFrom<&coset::CoseKey> for SymmetricCryptoKey {
91 type Error = CryptoError;
92
93 fn try_from(cose_key: &coset::CoseKey) -> Result<Self, Self::Error> {
94 let key_bytes = cose_key
95 .params
96 .iter()
97 .find_map(|(label, value)| match (label, value) {
98 (&SYMMETRIC_KEY, ciborium::Value::Bytes(bytes)) => Some(bytes),
99 _ => None,
100 })
101 .ok_or(CryptoError::InvalidKey)?;
102 let alg = cose_key.alg.as_ref().ok_or(CryptoError::InvalidKey)?;
103
104 match alg {
105 coset::Algorithm::PrivateUse(XCHACHA20_POLY1305) => {
106 if key_bytes.len() != xchacha20::KEY_SIZE {
109 return Err(CryptoError::InvalidKey);
110 }
111 let enc_key = Box::pin(GenericArray::<u8, U32>::clone_from_slice(key_bytes));
112 let key_id = cose_key
113 .key_id
114 .as_slice()
115 .try_into()
116 .map_err(|_| CryptoError::InvalidKey)?;
117 Ok(SymmetricCryptoKey::XChaCha20Poly1305Key(
118 XChaCha20Poly1305Key { enc_key, key_id },
119 ))
120 }
121 _ => Err(CryptoError::InvalidKey),
122 }
123 }
124}
125
126pub trait CoseSerializable {
128 fn to_cose(&self) -> Vec<u8>;
130 fn from_cose(bytes: &[u8]) -> Result<Self, EncodingError>
132 where
133 Self: Sized;
134}
135#[cfg(test)]
136mod test {
137 use super::*;
138
139 const KEY_ID: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
140 const KEY_DATA: [u8; 32] = [
141 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
142 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
143 0x1e, 0x1f,
144 ];
145 const TEST_VECTOR_PLAINTEXT: &[u8] = b"Message test vector";
146 const TEST_VECTOR_COSE_ENCRYPT0: &[u8] = &[
147 131, 88, 25, 162, 1, 58, 0, 1, 17, 111, 4, 80, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
148 13, 14, 15, 161, 5, 88, 24, 39, 48, 159, 48, 215, 77, 21, 100, 241, 209, 216, 65, 99, 221,
149 83, 63, 118, 204, 200, 175, 126, 202, 53, 33, 88, 35, 218, 136, 132, 223, 131, 246, 169,
150 120, 134, 49, 56, 173, 169, 133, 232, 109, 248, 101, 59, 226, 90, 97, 210, 181, 76, 68,
151 158, 159, 94, 65, 67, 23, 112, 253, 83,
152 ];
153
154 #[test]
155 fn test_encrypt_decrypt_roundtrip() {
156 let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) =
157 SymmetricCryptoKey::make_xchacha20_poly1305_key()
158 else {
159 panic!("Failed to create XChaCha20Poly1305Key");
160 };
161
162 let plaintext = b"Hello, world!";
163 let encrypted = encrypt_xchacha20_poly1305(plaintext, key).unwrap();
164 let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap();
165 assert_eq!(decrypted, plaintext);
166 }
167
168 #[test]
169 fn test_decrypt_test_vector() {
170 let key = XChaCha20Poly1305Key {
171 key_id: KEY_ID,
172 enc_key: Box::pin(*GenericArray::from_slice(&KEY_DATA)),
173 };
174 let decrypted = decrypt_xchacha20_poly1305(TEST_VECTOR_COSE_ENCRYPT0, &key).unwrap();
175 assert_eq!(decrypted, TEST_VECTOR_PLAINTEXT);
176 }
177
178 #[test]
179 fn test_fail_wrong_key_id() {
180 let key = XChaCha20Poly1305Key {
181 key_id: [1; 16], enc_key: Box::pin(*GenericArray::from_slice(&KEY_DATA)),
183 };
184 assert!(matches!(
185 decrypt_xchacha20_poly1305(TEST_VECTOR_COSE_ENCRYPT0, &key),
186 Err(CryptoError::WrongCoseKeyId)
187 ));
188 }
189
190 #[test]
191 fn test_fail_wrong_algorithm() {
192 let protected_header = coset::HeaderBuilder::new()
193 .algorithm(iana::Algorithm::A256GCM)
194 .key_id(KEY_ID.to_vec())
195 .build();
196 let nonce = [0u8; 16];
197 let cose_encrypt0 = coset::CoseEncrypt0Builder::new()
198 .protected(protected_header)
199 .create_ciphertext(&[], &[], |_, _| Vec::new())
200 .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build())
201 .build();
202 let serialized_message = cose_encrypt0.to_vec().unwrap();
203
204 let key = XChaCha20Poly1305Key {
205 key_id: KEY_ID,
206 enc_key: Box::pin(*GenericArray::from_slice(&KEY_DATA)),
207 };
208 assert!(matches!(
209 decrypt_xchacha20_poly1305(&serialized_message, &key),
210 Err(CryptoError::WrongKeyType)
211 ));
212 }
213}