matrix_sdk_crypto/olm/
utility.rs

1// Copyright 2020 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
54/// A trait for objects that are able to verify signatures.
55pub trait VerifyJson {
56    /// Verify a signature over the SignedJsonObject using this public Ed25519
57    /// key.
58    ///
59    /// # Arguments
60    ///
61    /// * `user_id` - The user that claims to have signed this object.
62    ///
63    /// * `key_id` - The ID of the key that was used to sign this object.
64    ///
65    ///   **Note**: The key ID must match the ID of the public key that is
66    ///   verifying the signature. This is only used to find the correct
67    ///   signature.
68    ///
69    /// * `signed_object` - The signed object that we should check for a valid
70    ///   signature.
71    ///
72    /// Returns Ok if the signature was successfully verified, otherwise an
73    /// SignatureError.
74    fn verify_json(
75        &self,
76        user_id: &UserId,
77        key_id: &DeviceKeyId,
78        signed_object: &impl SignedJsonObject,
79    ) -> Result<(), SignatureError>;
80
81    /// Verify a signature over the canonicalized signed JSON object using
82    /// this public Ed25519 key.
83    ///
84    /// # Arguments
85    ///
86    /// * `user_id` - The user that claims to have signed this object.
87    ///
88    /// * `key_id` - The ID of the key that was used to sign this object.
89    ///
90    ///   **Note**: The key ID must match the ID of the public key that is
91    ///   verifying the signature. This is only used to find the correct
92    ///   signature.
93    ///
94    /// * `canonicalized_json` - The canonicalized version of a signed JSON
95    ///   object.
96    ///
97    /// This method should only be used if an object's signature needs to be
98    /// checked multiple times, and you'd like to avoid performing the
99    /// canonicalization step each time. Otherwise, prefer the
100    /// [`VerifyJson::verify_json`] method
101    ///
102    /// Returns Ok if the signature was successfully verified, otherwise an
103    /// SignatureError.
104    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
162/// A trait for Matrix objects that we can canonicalize, sign and verify
163/// signatures for, as described by the [spec].
164///
165/// [spec]: https://spec.matrix.org/unstable/appendices/#signing-json
166pub trait SignedJsonObject: Serialize {
167    /// Get the collection of signatures present on this signed JSON object.
168    fn signatures(&self) -> &Signatures;
169
170    /// Convert this signed JSON object to a canonicalized signed JSON string.
171    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}