Skip to main content

webex_message_handler/
message_decryptor.rs

1//! JWE message decryption for Webex activities.
2
3use crate::errors::WebexError;
4use crate::jwe;
5use crate::kms_client::KmsClient;
6use crate::types::MercuryActivity;
7use tracing::warn;
8
9/// Decrypts encrypted Webex message activities using KMS keys.
10pub struct MessageDecryptor<'a> {
11    kms_client: &'a mut KmsClient,
12}
13
14impl<'a> MessageDecryptor<'a> {
15    pub fn new(kms_client: &'a mut KmsClient) -> Self {
16        Self { kms_client }
17    }
18
19    /// Decrypt an encrypted Mercury activity.
20    ///
21    /// Returns a clone with decrypted `display_name` and `content` fields.
22    /// If the activity is not encrypted (no `encryption_key_url`), returns a clone as-is.
23    pub async fn decrypt_activity(
24        &mut self,
25        activity: &MercuryActivity,
26    ) -> Result<MercuryActivity, WebexError> {
27        // Locate encryption key URL from one of three locations
28        let encryption_key_url = activity
29            .encryption_key_url
30            .as_deref()
31            .filter(|s| !s.is_empty())
32            .or_else(|| {
33                activity
34                    .object
35                    .encryption_key_url
36                    .as_deref()
37                    .filter(|s| !s.is_empty())
38            })
39            .or_else(|| {
40                activity
41                    .target
42                    .encryption_key_url
43                    .as_deref()
44                    .filter(|s| !s.is_empty())
45            });
46
47        // Not encrypted
48        let encryption_key_url = match encryption_key_url {
49            Some(url) => url.to_string(),
50            None => return Ok(activity.clone()),
51        };
52
53        // Fetch the key from KMS
54        let key = self
55            .kms_client
56            .get_key(&encryption_key_url)
57            .await
58            .map_err(|e| {
59                WebexError::decryption(format!(
60                    "Failed to fetch encryption key from {encryption_key_url}: {e}"
61                ))
62            })?;
63
64        // Clone and decrypt fields
65        let mut decrypted = activity.clone();
66
67        // Decrypt displayName
68        if let Some(ref display_name) = decrypted.object.display_name {
69            if !display_name.is_empty() {
70                match jwe::decrypt_message_jwe(display_name, &key) {
71                    Ok(plaintext) => {
72                        decrypted.object.display_name = Some(
73                            String::from_utf8(plaintext).unwrap_or_else(|_| {
74                                display_name.clone()
75                            }),
76                        );
77                    }
78                    Err(e) => {
79                        warn!(
80                            "Failed to decrypt displayName in activity {}: {e}",
81                            activity.id
82                        );
83                    }
84                }
85            }
86        }
87
88        // Decrypt content
89        if let Some(ref content) = decrypted.object.content {
90            if !content.is_empty() {
91                match jwe::decrypt_message_jwe(content, &key) {
92                    Ok(plaintext) => {
93                        decrypted.object.content = Some(
94                            String::from_utf8(plaintext).unwrap_or_else(|_| {
95                                content.clone()
96                            }),
97                        );
98                    }
99                    Err(e) => {
100                        warn!(
101                            "Failed to decrypt content in activity {}: {e}",
102                            activity.id
103                        );
104                    }
105                }
106            }
107        }
108
109        Ok(decrypted)
110    }
111}