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