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.
1415use ruma::{CanonicalJsonValue, DeviceKeyAlgorithm, DeviceKeyId, UserId};
16use serde::Serialize;
17use serde_json::Value;
18use vodozemac::{olm::Account, Ed25519PublicKey, Ed25519SecretKey, Ed25519Signature};
1920use crate::{
21 error::SignatureError,
22 types::{CrossSigningKey, DeviceKeys, Signature, Signatures, SignedKey},
23};
2425fn to_signable_json(mut value: Value) -> Result<String, SignatureError> {
26let json_object = value.as_object_mut().ok_or(SignatureError::NotAnObject)?;
27let _ = json_object.remove("signatures");
28let _ = json_object.remove("unsigned");
2930let canonical_json: CanonicalJsonValue = value.try_into()?;
31Ok(canonical_json.to_string())
32}
3334pub trait SignJson {
35fn sign_json(&self, value: Value) -> Result<Ed25519Signature, SignatureError>;
36}
3738impl SignJson for Account {
39fn sign_json(&self, value: Value) -> Result<Ed25519Signature, SignatureError> {
40let serialized = to_signable_json(value)?;
4142Ok(self.sign(serialized))
43 }
44}
4546impl SignJson for Ed25519SecretKey {
47fn sign_json(&self, value: Value) -> Result<Ed25519Signature, SignatureError> {
48let serialized = to_signable_json(value)?;
4950Ok(self.sign(serialized.as_ref()))
51 }
52}
5354/// 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.
74fn verify_json(
75&self,
76 user_id: &UserId,
77 key_id: &DeviceKeyId,
78 signed_object: &impl SignedJsonObject,
79 ) -> Result<(), SignatureError>;
8081/// 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.
104fn verify_canonicalized_json(
105&self,
106 user_id: &UserId,
107 key_id: &DeviceKeyId,
108 signatures: &Signatures,
109 canonical_json: &str,
110 ) -> Result<(), SignatureError>;
111}
112113impl VerifyJson for Ed25519PublicKey {
114fn verify_json(
115&self,
116 user_id: &UserId,
117 key_id: &DeviceKeyId,
118 signed_object: &impl SignedJsonObject,
119 ) -> Result<(), SignatureError> {
120if key_id.algorithm() != DeviceKeyAlgorithm::Ed25519 {
121return Err(SignatureError::UnsupportedAlgorithm);
122 }
123124let canonicalized = signed_object.to_canonical_json()?;
125 verify_signature(*self, user_id, key_id, signed_object.signatures(), &canonicalized)
126 }
127128fn verify_canonicalized_json(
129&self,
130 user_id: &UserId,
131 key_id: &DeviceKeyId,
132 signatures: &Signatures,
133 canonical_json: &str,
134 ) -> Result<(), SignatureError> {
135if key_id.algorithm() != DeviceKeyAlgorithm::Ed25519 {
136return Err(SignatureError::UnsupportedAlgorithm);
137 }
138139 verify_signature(*self, user_id, key_id, signatures, canonical_json)
140 }
141}
142143fn verify_signature(
144 public_key: Ed25519PublicKey,
145 user_id: &UserId,
146 key_id: &DeviceKeyId,
147 signatures: &Signatures,
148 canonical_json: &str,
149) -> Result<(), SignatureError> {
150let s = signatures
151 .get(user_id)
152 .and_then(|m| m.get(key_id))
153 .ok_or(SignatureError::NoSignatureFound)?;
154155match s {
156Ok(Signature::Ed25519(s)) => Ok(public_key.verify(canonical_json.as_bytes(), s)?),
157Ok(Signature::Other(_)) => Err(SignatureError::UnsupportedAlgorithm),
158Err(_) => Err(SignatureError::InvalidSignature),
159 }
160}
161162/// 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.
168fn signatures(&self) -> &Signatures;
169170/// Convert this signed JSON object to a canonicalized signed JSON string.
171fn to_canonical_json(&self) -> Result<String, SignatureError> {
172let value = serde_json::to_value(self)?;
173 to_signable_json(value)
174 }
175}
176177impl SignedJsonObject for DeviceKeys {
178fn signatures(&self) -> &Signatures {
179&self.signatures
180 }
181}
182183impl SignedJsonObject for SignedKey {
184fn signatures(&self) -> &Signatures {
185self.signatures()
186 }
187}
188189impl SignedJsonObject for CrossSigningKey {
190fn signatures(&self) -> &Signatures {
191&self.signatures
192 }
193}
194195impl SignedJsonObject for crate::types::MegolmV1AuthData {
196fn signatures(&self) -> &Signatures {
197&self.signatures
198 }
199}
200201#[cfg(test)]
202mod tests {
203use ruma::{device_id, user_id, DeviceKeyAlgorithm, DeviceKeyId};
204use serde_json::json;
205use vodozemac::Ed25519PublicKey;
206207use super::VerifyJson;
208use crate::types::DeviceKeys;
209210#[test]
211fn signature_test() {
212let 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});
232233let signing_key = "n469gw7zm+KW+JsFIJKnFVvCKU14HwQyocggcCIQgZY";
234235let signing_key = Ed25519PublicKey::from_base64(signing_key)
236 .expect("The signing key wasn't proper base64");
237238let device_keys: DeviceKeys = serde_json::from_value(device_keys).unwrap();
239240 signing_key
241 .verify_json(
242user_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}