matrix_sdk_crypto/backups/keys/
backup.rs

1// Copyright 2021 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 std::sync::Arc;
16
17use matrix_sdk_common::locks::Mutex;
18use ruma::{
19    api::client::backup::{EncryptedSessionDataInit, KeyBackupData, KeyBackupDataInit},
20    serde::Base64,
21};
22use vodozemac::{pk_encryption::PkEncryption, Curve25519PublicKey};
23use zeroize::Zeroizing;
24
25use super::decryption::DecodeError;
26use crate::{olm::InboundGroupSession, types::Signatures};
27
28#[derive(Debug)]
29struct InnerBackupKey {
30    key: Curve25519PublicKey,
31    signatures: Signatures,
32    version: Mutex<Option<String>>,
33}
34
35/// The public part of a backup key.
36#[derive(Clone)]
37pub struct MegolmV1BackupKey {
38    inner: Arc<InnerBackupKey>,
39}
40
41#[cfg(not(tarpaulin_include))]
42impl std::fmt::Debug for MegolmV1BackupKey {
43    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        formatter
45            .debug_struct("MegolmV1BackupKey")
46            .field("key", &self.to_base64())
47            .field("version", &self.backup_version())
48            .finish()
49    }
50}
51
52impl MegolmV1BackupKey {
53    pub(super) fn new(key: Curve25519PublicKey, version: Option<String>) -> Self {
54        Self {
55            inner: InnerBackupKey {
56                key,
57                signatures: Default::default(),
58                version: Mutex::new(version),
59            }
60            .into(),
61        }
62    }
63
64    /// Get the full name of the backup algorithm this backup key supports.
65    pub fn backup_algorithm(&self) -> &str {
66        "m.megolm_backup.v1.curve25519-aes-sha2"
67    }
68
69    /// Get all the signatures of this `MegolmV1BackupKey`.
70    pub fn signatures(&self) -> Signatures {
71        self.inner.signatures.to_owned()
72    }
73
74    /// Try to create a new `MegolmV1BackupKey` from a base 64 encoded string.
75    pub fn from_base64(public_key: &str) -> Result<Self, DecodeError> {
76        let key = Curve25519PublicKey::from_base64(public_key)?;
77
78        let inner =
79            InnerBackupKey { key, signatures: Default::default(), version: Mutex::new(None) };
80
81        Ok(MegolmV1BackupKey { inner: inner.into() })
82    }
83
84    /// Convert the [`MegolmV1BackupKey`] to a base 64 encoded string.
85    pub fn to_base64(&self) -> String {
86        self.inner.key.to_base64()
87    }
88
89    /// Get the backup version that this key is used with, if any.
90    pub fn backup_version(&self) -> Option<String> {
91        self.inner.version.lock().clone()
92    }
93
94    /// Set the backup version that this `MegolmV1BackupKey` will be used with.
95    ///
96    /// The key won't be able to encrypt room keys unless a version has been
97    /// set.
98    pub fn set_version(&self, version: String) {
99        *self.inner.version.lock() = Some(version);
100    }
101
102    /// Export the given inbound group session, and encrypt the data, ready for
103    /// writing to the backup.
104    pub async fn encrypt(&self, session: InboundGroupSession) -> KeyBackupData {
105        let pk = PkEncryption::from_key(self.inner.key);
106
107        // The forwarding chains don't mean much, we only care whether we received the
108        // session directly from the creator of the session or not.
109        let forwarded_count = (session.has_been_imported() as u8).into();
110        let first_message_index = session.first_known_index().into();
111
112        // Convert our key to the backup representation.
113        let key = session.to_backup().await;
114
115        // The key gets zeroized in `BackedUpRoomKey` but we're creating a copy
116        // here that won't, so let's wrap it up in a `Zeroizing` struct.
117        let key =
118            Zeroizing::new(serde_json::to_vec(&key).expect("Can't serialize exported room key"));
119
120        let message = pk.encrypt(&key);
121
122        let session_data = EncryptedSessionDataInit {
123            ephemeral: Base64::new(message.ephemeral_key.to_vec()),
124            ciphertext: Base64::new(message.ciphertext),
125            mac: Base64::new(message.mac),
126        }
127        .into();
128
129        KeyBackupDataInit {
130            first_message_index,
131            forwarded_count,
132            // TODO: is this actually used anywhere? seems to be completely
133            // useless and requires us to get the Device out of the store?
134            // Also should this be checked at the time of the backup or at the
135            // time of the room key receival?
136            is_verified: false,
137            session_data,
138        }
139        .into()
140    }
141}