Skip to main content

matrix_sdk_crypto/types/signatures/
mod.rs

1/*
2Copyright 2022-2026 The Matrix.org Foundation C.I.C.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17mod signature;
18#[cfg(feature = "experimental-x509-identity-verification")]
19mod x509_signature;
20
21use std::collections::{BTreeMap, btree_map::IntoIter};
22
23use ruma::{DeviceKeyId, OwnedDeviceKeyId, OwnedUserId, UserId};
24use serde::{Deserialize, Deserializer, Serialize, Serializer};
25use vodozemac::Ed25519Signature;
26
27pub use self::signature::Signature;
28#[cfg(feature = "experimental-x509-identity-verification")]
29pub use self::x509_signature::{X509_SIGNATURE_ALGORITHM, X509Signature};
30
31/// Represents a signature that could not be decoded.
32///
33/// This will currently only hold invalid Ed25519 signatures.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct InvalidSignature {
36    /// The base64 encoded string that is claimed to contain a signature but
37    /// could not be decoded.
38    pub source: String,
39}
40
41/// Signatures for a signed object.
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct Signatures(
44    BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>>,
45);
46
47impl Signatures {
48    /// Create a new, empty, signatures collection.
49    pub fn new() -> Self {
50        Signatures(Default::default())
51    }
52
53    /// Add the given signature from the given signer and the given key_id to
54    /// the collection.
55    pub fn add_signature(
56        &mut self,
57        signer: OwnedUserId,
58        key_id: OwnedDeviceKeyId,
59        signature: impl Into<Signature>,
60    ) -> Option<Result<Signature, InvalidSignature>> {
61        self.0.entry(signer).or_default().insert(key_id, Ok(signature.into()))
62    }
63
64    /// Try to find an Ed25519 signature from the given signer with the given
65    /// key id.
66    pub fn get_signature(&self, signer: &UserId, key_id: &DeviceKeyId) -> Option<Ed25519Signature> {
67        self.get(signer)?.get(key_id)?.as_ref().ok()?.ed25519()
68    }
69
70    /// Get the map of signatures that belong to the given user.
71    pub fn get(
72        &self,
73        signer: &UserId,
74    ) -> Option<&BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>> {
75        self.0.get(signer)
76    }
77
78    /// Remove all the signatures we currently hold.
79    pub fn clear(&mut self) {
80        self.0.clear()
81    }
82
83    /// Do we hold any signatures or is our collection completely empty.
84    pub fn is_empty(&self) -> bool {
85        self.0.is_empty()
86    }
87
88    /// How many signatures do we currently hold.
89    pub fn signature_count(&self) -> usize {
90        self.0.values().map(|u| u.len()).sum()
91    }
92}
93
94impl Default for Signatures {
95    fn default() -> Self {
96        Self::new()
97    }
98}
99
100impl IntoIterator for Signatures {
101    type Item = (OwnedUserId, BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>);
102
103    type IntoIter =
104        IntoIter<OwnedUserId, BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>>;
105
106    fn into_iter(self) -> Self::IntoIter {
107        self.0.into_iter()
108    }
109}
110
111impl<'de> Deserialize<'de> for Signatures {
112    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113    where
114        D: Deserializer<'de>,
115    {
116        let map: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, String>> =
117            Deserialize::deserialize(deserializer)?;
118
119        let map = map
120            .into_iter()
121            .map(|(user, signatures)| {
122                let signatures = signatures
123                    .into_iter()
124                    .map(|(key_id, s)| {
125                        let algorithm = key_id.algorithm();
126                        let signature = Signature::from_base64(algorithm, s);
127                        Ok((key_id, signature))
128                    })
129                    .collect::<Result<BTreeMap<_, _>, _>>()?;
130
131                Ok((user, signatures))
132            })
133            .collect::<Result<_, _>>()?;
134
135        Ok(Signatures(map))
136    }
137}
138
139impl Serialize for Signatures {
140    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
141    where
142        S: Serializer,
143    {
144        let signatures: BTreeMap<&OwnedUserId, BTreeMap<&OwnedDeviceKeyId, String>> = self
145            .0
146            .iter()
147            .map(|(u, m)| {
148                (
149                    u,
150                    m.iter()
151                        .map(|(d, s)| {
152                            (
153                                d,
154                                match s {
155                                    Ok(s) => s.to_base64(),
156                                    Err(i) => i.source.to_owned(),
157                                },
158                            )
159                        })
160                        .collect(),
161                )
162            })
163            .collect();
164
165        Serialize::serialize(&signatures, serializer)
166    }
167}
168
169#[cfg(test)]
170mod test {
171    use insta::{assert_json_snapshot, with_settings};
172    use ruma::{DeviceKeyAlgorithm, device_id, owned_user_id};
173
174    use super::*;
175
176    #[test]
177    fn snapshot_signatures() {
178        let signatures = Signatures(BTreeMap::from([
179            (
180                owned_user_id!("@alice:localhost"),
181                BTreeMap::from([
182                    (
183                        DeviceKeyId::from_parts(
184                            DeviceKeyAlgorithm::Ed25519,
185                            device_id!("ABCDEFGH"),
186                        ),
187                        Ok(Signature::from(Ed25519Signature::from_slice(&[0u8; 64]).unwrap())),
188                    ),
189                    (
190                        DeviceKeyId::from_parts(
191                            DeviceKeyAlgorithm::Curve25519,
192                            device_id!("IJKLMNOP"),
193                        ),
194                        Ok(Signature::from(Ed25519Signature::from_slice(&[1u8; 64]).unwrap())),
195                    ),
196                ]),
197            ),
198            (
199                owned_user_id!("@bob:localhost"),
200                BTreeMap::from([(
201                    DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, device_id!("ABCDEFGH")),
202                    Err(InvalidSignature { source: "SOME+B64+SOME+B64+SOME+B64+==".to_owned() }),
203                )]),
204            ),
205        ]));
206
207        with_settings!({sort_maps => true, prepend_module_to_snapshot => false}, {
208            assert_json_snapshot!(signatures);
209        });
210    }
211}