matrix_sdk_crypto/olm/
utility.rs1use ruma::{CanonicalJsonValue, DeviceKeyAlgorithm, DeviceKeyId, UserId};
16use serde::Serialize;
17use serde_json::Value;
18use vodozemac::{olm::Account, Ed25519PublicKey, Ed25519SecretKey, Ed25519Signature};
19
20use crate::{
21 error::SignatureError,
22 types::{CrossSigningKey, DeviceKeys, Signature, Signatures, SignedKey},
23};
24
25fn to_signable_json(mut value: Value) -> Result<String, SignatureError> {
26 let json_object = value.as_object_mut().ok_or(SignatureError::NotAnObject)?;
27 let _ = json_object.remove("signatures");
28 let _ = json_object.remove("unsigned");
29
30 let canonical_json: CanonicalJsonValue = value.try_into()?;
31 Ok(canonical_json.to_string())
32}
33
34pub trait SignJson {
35 fn sign_json(&self, value: Value) -> Result<Ed25519Signature, SignatureError>;
36}
37
38impl SignJson for Account {
39 fn sign_json(&self, value: Value) -> Result<Ed25519Signature, SignatureError> {
40 let serialized = to_signable_json(value)?;
41
42 Ok(self.sign(serialized))
43 }
44}
45
46impl SignJson for Ed25519SecretKey {
47 fn sign_json(&self, value: Value) -> Result<Ed25519Signature, SignatureError> {
48 let serialized = to_signable_json(value)?;
49
50 Ok(self.sign(serialized.as_ref()))
51 }
52}
53
54pub trait VerifyJson {
56 fn verify_json(
75 &self,
76 user_id: &UserId,
77 key_id: &DeviceKeyId,
78 signed_object: &impl SignedJsonObject,
79 ) -> Result<(), SignatureError>;
80
81 fn verify_canonicalized_json(
105 &self,
106 user_id: &UserId,
107 key_id: &DeviceKeyId,
108 signatures: &Signatures,
109 canonical_json: &str,
110 ) -> Result<(), SignatureError>;
111}
112
113impl VerifyJson for Ed25519PublicKey {
114 fn verify_json(
115 &self,
116 user_id: &UserId,
117 key_id: &DeviceKeyId,
118 signed_object: &impl SignedJsonObject,
119 ) -> Result<(), SignatureError> {
120 if key_id.algorithm() != DeviceKeyAlgorithm::Ed25519 {
121 return Err(SignatureError::UnsupportedAlgorithm);
122 }
123
124 let canonicalized = signed_object.to_canonical_json()?;
125 verify_signature(*self, user_id, key_id, signed_object.signatures(), &canonicalized)
126 }
127
128 fn verify_canonicalized_json(
129 &self,
130 user_id: &UserId,
131 key_id: &DeviceKeyId,
132 signatures: &Signatures,
133 canonical_json: &str,
134 ) -> Result<(), SignatureError> {
135 if key_id.algorithm() != DeviceKeyAlgorithm::Ed25519 {
136 return Err(SignatureError::UnsupportedAlgorithm);
137 }
138
139 verify_signature(*self, user_id, key_id, signatures, canonical_json)
140 }
141}
142
143fn verify_signature(
144 public_key: Ed25519PublicKey,
145 user_id: &UserId,
146 key_id: &DeviceKeyId,
147 signatures: &Signatures,
148 canonical_json: &str,
149) -> Result<(), SignatureError> {
150 let s = signatures
151 .get(user_id)
152 .and_then(|m| m.get(key_id))
153 .ok_or(SignatureError::NoSignatureFound)?;
154
155 match s {
156 Ok(Signature::Ed25519(s)) => Ok(public_key.verify(canonical_json.as_bytes(), s)?),
157 Ok(Signature::Other(_)) => Err(SignatureError::UnsupportedAlgorithm),
158 Err(_) => Err(SignatureError::InvalidSignature),
159 }
160}
161
162pub trait SignedJsonObject: Serialize {
167 fn signatures(&self) -> &Signatures;
169
170 fn to_canonical_json(&self) -> Result<String, SignatureError> {
172 let value = serde_json::to_value(self)?;
173 to_signable_json(value)
174 }
175}
176
177impl SignedJsonObject for DeviceKeys {
178 fn signatures(&self) -> &Signatures {
179 &self.signatures
180 }
181}
182
183impl SignedJsonObject for SignedKey {
184 fn signatures(&self) -> &Signatures {
185 self.signatures()
186 }
187}
188
189impl SignedJsonObject for CrossSigningKey {
190 fn signatures(&self) -> &Signatures {
191 &self.signatures
192 }
193}
194
195impl SignedJsonObject for crate::types::MegolmV1AuthData {
196 fn signatures(&self) -> &Signatures {
197 &self.signatures
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use ruma::{device_id, user_id, DeviceKeyAlgorithm, DeviceKeyId};
204 use serde_json::json;
205 use vodozemac::Ed25519PublicKey;
206
207 use super::VerifyJson;
208 use crate::types::DeviceKeys;
209
210 #[test]
211 fn signature_test() {
212 let device_keys = json!({
213 "device_id": "GBEWHQOYGS",
214 "algorithms": [
215 "m.olm.v1.curve25519-aes-sha2",
216 "m.megolm.v1.aes-sha2"
217 ],
218 "keys": {
219 "curve25519:GBEWHQOYGS": "F8QhZ0Z1rjtWrQOblMDgZtEX5x1UrG7sZ2Kk3xliNAU",
220 "ed25519:GBEWHQOYGS": "n469gw7zm+KW+JsFIJKnFVvCKU14HwQyocggcCIQgZY"
221 },
222 "signatures": {
223 "@example:localhost": {
224 "ed25519:GBEWHQOYGS": "OlF2REsqjYdAfr04ONx8VS/5cB7KjrWYRlLF4eUm2foAiQL/RAfsjsa2JXZeoOHh6vEualZHbWlod49OewVqBg"
225 }
226 },
227 "unsigned": {
228 "device_display_name": "Weechat-Matrix-rs"
229 },
230 "user_id": "@example:localhost"
231 });
232
233 let signing_key = "n469gw7zm+KW+JsFIJKnFVvCKU14HwQyocggcCIQgZY";
234
235 let signing_key = Ed25519PublicKey::from_base64(signing_key)
236 .expect("The signing key wasn't proper base64");
237
238 let device_keys: DeviceKeys = serde_json::from_value(device_keys).unwrap();
239
240 signing_key
241 .verify_json(
242 user_id!("@example:localhost"),
243 &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, device_id!("GBEWHQOYGS")),
244 &device_keys,
245 )
246 .expect("Can't verify device keys");
247 }
248}