fxa_client/internal/
send_tab.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use super::{
6    commands::{
7        decrypt_command, encrypt_command, get_public_keys,
8        send_tab::{self, SendTabPayload},
9        IncomingDeviceCommand, PrivateCommandKeys as PrivateSendTabKeys,
10        PublicCommandKeys as PublicSendTabKeys,
11    },
12    http_client::GetDeviceResponse,
13    scopes, telemetry, FirefoxAccount,
14};
15use crate::{Error, Result};
16
17impl FirefoxAccount {
18    pub(crate) fn load_or_generate_send_tab_keys(&mut self) -> Result<PrivateSendTabKeys> {
19        if let Some(s) = self.send_tab_key() {
20            match PrivateSendTabKeys::deserialize(s) {
21                Ok(keys) => return Ok(keys),
22                Err(_) => {
23                    error_support::report_error!(
24                        "fxaclient-send-tab-key-deserialize",
25                        "Could not deserialize Send Tab keys. Re-creating them."
26                    );
27                }
28            }
29        }
30        let keys = PrivateSendTabKeys::from_random()?;
31        self.set_send_tab_key(keys.serialize()?);
32        Ok(keys)
33    }
34
35    /// Send a single tab to another device designated by its device ID.
36    /// XXX - We need a new send_tabs_to_devices() so we can correctly record
37    /// telemetry for these cases.
38    /// This probably requires a new "Tab" struct with the title and url.
39    /// android-components has SendToAllUseCase(), so this isn't just theoretical.
40    /// See <https://github.com/mozilla/application-services/issues/3402>
41    pub fn send_single_tab(
42        &mut self,
43        target_device_id: &str,
44        title: &str,
45        url: &str,
46    ) -> Result<()> {
47        let devices = self.get_devices(false)?;
48        let target = devices
49            .iter()
50            .find(|d| d.id == target_device_id)
51            .ok_or_else(|| Error::UnknownTargetDevice(target_device_id.to_owned()))?;
52        let (payload, sent_telemetry) = SendTabPayload::single_tab(title, url);
53        let oldsync_key = self.get_scoped_key(scopes::OLD_SYNC)?;
54        let command_payload =
55            encrypt_command(oldsync_key, target, send_tab::COMMAND_NAME, &payload)?;
56        self.invoke_command(send_tab::COMMAND_NAME, target, &command_payload, None)?;
57        self.telemetry.record_command_sent(sent_telemetry);
58        Ok(())
59    }
60
61    pub(crate) fn handle_send_tab_command(
62        &mut self,
63        sender: Option<GetDeviceResponse>,
64        payload: serde_json::Value,
65        reason: telemetry::ReceivedReason,
66    ) -> Result<IncomingDeviceCommand> {
67        let send_tab_key: PrivateSendTabKeys = match self.send_tab_key() {
68            Some(s) => PrivateSendTabKeys::deserialize(s)?,
69            None => {
70                return Err(Error::IllegalState(
71                    "Cannot find send-tab keys. Has initialize_device been called before?",
72                ));
73            }
74        };
75        match decrypt_command(payload, &send_tab_key) {
76            Ok(payload) => {
77                // It's an incoming tab, which we record telemetry for.
78                let recd_telemetry = telemetry::ReceivedCommand::for_send_tab(&payload, reason);
79                self.telemetry.record_command_received(recd_telemetry);
80                // The telemetry IDs escape to the consumer, but that's OK...
81                Ok(IncomingDeviceCommand::TabReceived { sender, payload })
82            }
83            Err(e) => {
84                // XXX - this seems ripe for telemetry collection!?
85                // It also seems like it might be possible to recover - ie, one
86                // of the reasons is that there are key mismatches. Doesn't that
87                // mean the "other" key might work?
88                crate::warn!("Could not decrypt Send Tab payload. Diagnosing then resetting the Send Tab keys.");
89                match self.diagnose_remote_keys(send_tab_key) {
90                    Ok(_) => {
91                        error_support::report_error!(
92                            "fxaclient-send-tab-decrypt",
93                            "Could not find the cause of the Send Tab keys issue."
94                        );
95                    }
96                    Err(e) => {
97                        error_support::report_error!("fxaclient-send-tab-decrypt", "{}", e);
98                    }
99                };
100                // Reset the Send Tab keys.
101                self.clear_send_tab_key();
102                self.reregister_current_capabilities()?;
103                Err(e)
104            }
105        }
106    }
107
108    fn send_tab_key(&self) -> Option<&str> {
109        self.state.get_commands_data(send_tab::COMMAND_NAME)
110    }
111
112    fn set_send_tab_key(&mut self, key: String) {
113        self.state.set_commands_data(send_tab::COMMAND_NAME, key)
114    }
115
116    fn clear_send_tab_key(&mut self) {
117        self.state.clear_commands_data(send_tab::COMMAND_NAME);
118    }
119
120    fn diagnose_remote_keys(&mut self, local_send_tab_key: PrivateSendTabKeys) -> Result<()> {
121        let own_device = &mut self
122            .get_current_device()?
123            .ok_or(Error::SendTabDiagnosisError("No remote device."))?;
124        let oldsync_key = self.get_scoped_key(scopes::OLD_SYNC)?;
125
126        let public_keys_remote = get_public_keys(oldsync_key, own_device, send_tab::COMMAND_NAME)
127            .map_err(|_| {
128            Error::SendTabDiagnosisError("Unable to decrypt public key bundle.")
129        })?;
130
131        let public_keys_local: PublicSendTabKeys = local_send_tab_key.into();
132
133        if public_keys_local.public_key() != public_keys_remote.public_key() {
134            return Err(Error::SendTabDiagnosisError("Mismatch in public key."));
135        }
136
137        if public_keys_local.auth_secret() != public_keys_remote.auth_secret() {
138            return Err(Error::SendTabDiagnosisError("Mismatch in auth secret."));
139        }
140        Ok(())
141    }
142}