Skip to main content

co_didcomm/messages/
message_raw_crypto.rs

1use std::convert::TryInto;
2
3use base64_url::{decode, encode};
4use serde_json::Value;
5
6use super::Message;
7use crate::{
8    crypto::{SignatureAlgorithm, Signer, SigningMethod, SymmetricCypherMethod},
9    Error,
10    Jwe,
11    JwmHeader,
12    Jws,
13    MessageType,
14    Signature,
15};
16
17// struct docu is placed in `message.rs`
18#[cfg(feature = "raw-crypto")]
19impl Message {
20    /// Encrypts current message by consuming it.
21    /// Uses provided cryptography function to perform
22    ///     the encryption. Agnostic of actual algorithm used.
23    /// Consuming is to make sure no changes are
24    ///     possible post packaging / sending.
25    /// Returns `(JwmHeader, Vec<u8>)` to be sent to recipient.
26    ///
27    /// # Arguments
28    ///
29    /// * `crypter` - encryptor that should be used
30    ///
31    /// * `cek` - content encryption key to encrypt message with
32    pub fn encrypt(self, crypter: SymmetricCypherMethod, cek: &[u8]) -> Result<String, Error> {
33        let mut jwe_header = self.jwm_header.clone();
34        if jwe_header.typ != MessageType::DidCommForward {
35            jwe_header.typ = MessageType::DidCommJwe;
36        }
37        let d_header = self.get_didcomm_header();
38        let iv = Jwe::generate_iv();
39        let multi = self.recipients.is_some();
40        jwe_header.skid = Some(d_header.from.clone().unwrap_or_default());
41        if !multi {
42            jwe_header.kid = Some(d_header.to[0].clone());
43        }
44        jwe_header.skid = d_header.from.clone();
45        let aad_string = encode(&serde_json::to_string(&jwe_header)?.as_bytes());
46        let aad = aad_string.as_bytes();
47        let ciphertext_and_tag = crypter(
48            &decode(&iv)?,
49            cek,
50            serde_json::to_string(&self)?.as_bytes(),
51            aad,
52        )?;
53        let (ciphertext, tag) = ciphertext_and_tag.split_at(ciphertext_and_tag.len() - 16);
54        let jwe = if self.serialize_flat_jwe {
55            let recipients = self.recipients.ok_or_else(|| {
56                Error::Generic("flat JWE JSON serialization needs a recipient".to_string())
57            })?;
58            if recipients.len() != 1 {
59                return Err(Error::Generic(
60                    "flat JWE JSON serialization needs exactly one recipient".to_string(),
61                ));
62            }
63
64            Jwe::new_flat(
65                None,
66                recipients[0].clone(),
67                ciphertext,
68                Some(jwe_header),
69                Some(tag),
70                Some(iv),
71            )
72        } else {
73            Jwe::new(
74                None,
75                self.recipients.clone(),
76                ciphertext,
77                Some(jwe_header),
78                Some(tag),
79                Some(iv),
80            )
81        };
82        Ok(serde_json::to_string(&jwe)?)
83    }
84
85    /// Decrypts received cypher into instance of `Message`.
86    /// Received message should be encrypted with our pub key.
87    /// Returns `Ok(Message)` if decryption / deserialization
88    /// succeeded. `Error` otherwise.
89    ///
90    /// # Arguments
91    ///
92    /// * `received_message` - received message as byte array
93    ///
94    /// * `decrypter` - decrypter that should be used
95    ///
96    /// * `cek` - content encryption key to decrypt message with
97    pub fn decrypt(
98        received_message: &[u8],
99        decrypter: SymmetricCypherMethod,
100        cek: &[u8],
101    ) -> Result<Self, Error> {
102        let jwe: Jwe = serde_json::from_slice(received_message)?;
103        let protected = jwe
104            .protected
105            .as_ref()
106            .ok_or_else(|| Error::Generic("jwe is missing protected header".to_string()))?;
107        let aad_string = encode(&serde_json::to_string(&protected)?.as_bytes());
108        let aad = aad_string.as_bytes();
109        let tag = jwe
110            .tag
111            .as_ref()
112            .ok_or("JWE is missing tag")
113            .map_err(|e| Error::Generic(e.to_string()))?;
114        let mut ciphertext_and_tag: Vec<u8> = vec![];
115        ciphertext_and_tag.extend(&jwe.get_payload());
116        ciphertext_and_tag.extend(&decode(&tag)?);
117
118        return match decrypter(jwe.get_iv().as_ref(), cek, &ciphertext_and_tag, aad) {
119            Ok(raw_message_bytes) => Ok(serde_json::from_slice(&raw_message_bytes)?),
120            Err(e) => {
121                error!("decryption failed; {}", &e);
122                Err(Error::PlugCryptoFailure)
123            }
124        };
125    }
126
127    /// Signs message and turns it into `Jws` envelope.
128    /// `Err` is returned if message is not properly prepared or data is malformed.
129    /// Jws enveloped payload is base64_url encoded
130    pub fn sign(
131        mut self,
132        signer: SigningMethod,
133        signing_sender_private_key: &[u8],
134    ) -> Result<String, Error> {
135        let mut jws_header = self.jwm_header.clone();
136        jws_header.typ = MessageType::DidCommJws;
137        if jws_header.alg.is_none() {
138            return Err(Error::JwsParseError);
139        }
140
141        // drop non jwm plain message header info
142        self.jwm_header = JwmHeader::default();
143
144        let jws_header_string_base64 = base64_url::encode(&serde_json::to_string(&jws_header)?);
145        let payload_json_string = serde_json::to_string(&self)?;
146        let payload_string_base64 = base64_url::encode(&payload_json_string);
147        let payload_to_sign = format!("{}.{}", &jws_header_string_base64, &payload_string_base64);
148        let signature = signer(signing_sender_private_key, payload_to_sign.as_bytes())?;
149        let signature_value = Signature::new(Some(jws_header), None, signature);
150
151        let jws: Jws = if self.serialize_flat_jws {
152            Jws::new_flat(payload_string_base64, signature_value)
153        } else {
154            let signature_values = self
155                .didcomm_header
156                .to
157                .iter()
158                .map(|_| signature_value.clone())
159                .collect();
160            Jws::new(payload_string_base64, signature_values)
161        };
162
163        Ok(serde_json::to_string(&jws)?)
164    }
165
166    /// Verifies signature and returns payload message on verification success.
167    /// `Err` return if signature invalid or data is malformed.
168    /// Expects Jws's payload to be a valid serialized `Message` and base64_url encoded.
169    pub fn verify(jws: &[u8], signing_sender_public_key: &[u8]) -> Result<Message, Error> {
170        let jws: Jws = serde_json::from_slice(jws)?;
171
172        let signatures_values_to_verify: Vec<Signature>;
173        if let Some(signatures) = &jws.signatures {
174            signatures_values_to_verify = signatures.clone();
175        } else if let Some(signature_value) = &jws.signature {
176            signatures_values_to_verify = vec![signature_value.clone()];
177        } else {
178            return Err(Error::JwsParseError);
179        }
180        let payload = &jws.payload;
181
182        let mut verified = false;
183        for signature_value in signatures_values_to_verify {
184            let alg = &signature_value.get_alg().ok_or(Error::JweParseError)?;
185            let signature = &signature_value.signature[..];
186            let verifier: SignatureAlgorithm = alg.try_into()?;
187            let protected_header = signature_value
188                .protected
189                .as_ref()
190                .ok_or(Error::JwsParseError)?;
191            let encoded_header = base64_url::encode(&serde_json::to_string(&protected_header)?);
192            let payload_to_verify = format!("{}.{}", &encoded_header, &payload);
193            if verifier.validator()(
194                signing_sender_public_key,
195                payload_to_verify.as_bytes(),
196                signature,
197            )? {
198                verified = true;
199                break;
200            }
201        }
202
203        if verified {
204            // body in JWS envelope should be a valid JWM message, so parse it into message
205            let message: Message = serde_json::from_slice(&base64_url::decode(&jws.payload)?)?;
206            Ok(message)
207        } else {
208            Err(Error::JwsParseError)
209        }
210    }
211
212    /// Verifies signature and returns payload message on verification success.
213    /// `Err` return if signature invalid or data is malformed.
214    /// Expects Jws's payload to be a valid serialized `Message` and base64_url encoded.
215    ///
216    /// # Arguments
217    ///
218    /// * `jws` - to be verified jws message as json `Value` object
219    ///
220    /// * `signing_sender_public_key` - optional public key used for verification, if `None` it will try to resolve the did in the `kid` field
221    pub fn verify_value(jws: &Value, signing_sender_public_key: &[u8]) -> Result<Message, Error> {
222        let jws_string = serde_json::to_string(jws)?;
223        Message::verify(&jws_string.into_bytes(), signing_sender_public_key)
224    }
225}
226
227#[cfg(test)]
228mod raw_tests {
229    use chacha20poly1305::{
230        aead::{Aead, KeyInit},
231        Key,
232        XChaCha20Poly1305,
233        XNonce,
234    };
235    use sodiumoxide::crypto::secretbox;
236    use x25519_dalek::{EphemeralSecret, PublicKey};
237
238    use super::{Error, Message};
239    use crate::crypto::CryptoAlgorithm;
240
241    #[test]
242    #[allow(non_snake_case)]
243    #[cfg(feature = "raw-crypto")]
244    fn plugin_crypto_xChaCha20Paly1305_dummy_key() {
245        // Arrange
246        let key = Key::from_slice(b"an example very very secret key.");
247        // Pluggable encryptor function to encrypt data
248        let my_crypter = Box::new(
249            |n: &[u8], k: &[u8], m: &[u8], _a: &[u8]| -> Result<Vec<u8>, Error> {
250                let aead = XChaCha20Poly1305::new(k.into());
251                let nonce = XNonce::from_slice(n);
252                aead.encrypt(nonce, m)
253                    .map_err(|e| Error::Generic(e.to_string()))
254            },
255        );
256        // Pluggable decrypter function to decrypt data
257        let my_decrypter = Box::new(
258            |n: &[u8], k: &[u8], m: &[u8], _a: &[u8]| -> Result<Vec<u8>, Error> {
259                let aead = XChaCha20Poly1305::new(k.into());
260                let nonce = XNonce::from_slice(n);
261                Ok(aead.decrypt(nonce, m).unwrap())
262            },
263        );
264        let m = Message::new().as_jwe(&CryptoAlgorithm::A256GCM, None);
265        let id = m.get_didcomm_header().id.to_owned();
266
267        // Act and Assert
268        let encrypted = m.encrypt(my_crypter, key);
269        assert!(&encrypted.is_ok()); // Encryption check
270        let raw_m = Message::decrypt(encrypted.unwrap().as_bytes(), my_decrypter, key);
271        assert!(&raw_m.is_ok()); // Decryption check
272        assert_eq!(id, raw_m.unwrap().get_didcomm_header().id); // Data consistency check
273    }
274
275    #[test]
276    #[cfg(feature = "raw-crypto")]
277    fn plugin_crypto_libsodium_box() {
278        // Arrange
279        // Pluggable encryptor function to encrypt data
280        let my_crypter = Box::new(
281            |n: &[u8], k: &[u8], m: &[u8], _a: &[u8]| -> Result<Vec<u8>, Error> {
282                let nonce = secretbox::Nonce::from_slice(n).unwrap();
283                Ok(secretbox::seal(
284                    m,
285                    &nonce,
286                    &secretbox::Key::from_slice(k).unwrap(),
287                ))
288            },
289        );
290        // Pluggable decrypter function to decrypt data
291        let my_decrypter = Box::new(
292            |n: &[u8], k: &[u8], m: &[u8], _a: &[u8]| -> Result<Vec<u8>, Error> {
293                let nonce = secretbox::Nonce::from_slice(n).unwrap();
294                Ok(secretbox::open(m, &nonce, &secretbox::Key::from_slice(k).unwrap()).unwrap())
295            },
296        );
297        let m = Message::new().as_jwe(&CryptoAlgorithm::A256GCM, None);
298        let id = m.get_didcomm_header().id.to_owned();
299        let key = secretbox::gen_key();
300
301        // Act and Assert
302        let encrypted = m.encrypt(my_crypter, key.as_ref());
303        assert!(&encrypted.is_ok()); // Encryption check
304        let encrypted = encrypted.unwrap();
305        println!("{}", &encrypted);
306        let raw_m = Message::decrypt(encrypted.as_bytes(), my_decrypter, key.as_ref());
307        assert!(&raw_m.is_ok()); // Decryption check
308        assert_eq!(id, raw_m.unwrap().get_didcomm_header().id); // Data consistency check
309    }
310
311    #[test]
312    #[allow(non_snake_case)]
313    #[cfg(feature = "raw-crypto")]
314    fn plugin_crypto_xChaCha20Paly1305_x25519_dalek_shared_secret() {
315        // Arrange
316        let sender_sk = EphemeralSecret::random_from_rng(rand_core::OsRng);
317        let sender_pk = PublicKey::from(&sender_sk);
318        let recipient_sk = EphemeralSecret::random_from_rng(rand_core::OsRng);
319        let recipient_pk = PublicKey::from(&recipient_sk);
320        let sender_shared = sender_sk.diffie_hellman(&recipient_pk);
321        let recipient_shared = recipient_sk.diffie_hellman(&sender_pk);
322        let m = Message::new().as_jwe(&CryptoAlgorithm::XC20P, None);
323        let id = m.get_didcomm_header().id.to_owned();
324        // Pluggable encryptor function to encrypt data
325        let my_crypter = Box::new(
326            |n: &[u8], k: &[u8], m: &[u8], _a: &[u8]| -> Result<Vec<u8>, Error> {
327                let aead = XChaCha20Poly1305::new(k.into());
328                let nonce = XNonce::from_slice(n);
329                aead.encrypt(nonce, m)
330                    .map_err(|e| Error::Generic(e.to_string()))
331            },
332        );
333        // Pluggable decrypter function to decrypt data
334        let my_decrypter = Box::new(
335            |n: &[u8], k: &[u8], m: &[u8], _a: &[u8]| -> Result<Vec<u8>, Error> {
336                let aead = XChaCha20Poly1305::new(k.into());
337                let nonce = XNonce::from_slice(n);
338                aead.decrypt(nonce, m)
339                    .map_err(|e| Error::Generic(e.to_string()))
340            },
341        );
342
343        // Act and Assert
344        let encrypted = m.encrypt(my_crypter, sender_shared.as_bytes());
345        assert!(&encrypted.is_ok()); // Encryption check
346        let raw_m = Message::decrypt(
347            encrypted.unwrap().as_bytes(),
348            my_decrypter,
349            recipient_shared.as_bytes(),
350        );
351        assert!(&raw_m.is_ok()); // Decryption check
352        assert_eq!(id, raw_m.unwrap().get_didcomm_header().id); // Data consistency check
353    }
354}