fxa_client/internal/commands/
keys.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
5// All commands share the same structs for their crypto-keys.
6
7use serde::{de::DeserializeOwned, Deserialize, Serialize};
8
9use super::super::device::Device;
10use super::super::scopes;
11use crate::{Error, Result, ScopedKey};
12use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
13use rc_crypto::ece::{self, EcKeyComponents};
14use sync15::{EncryptedPayload, KeyBundle};
15
16#[derive(Serialize, Deserialize, Clone)]
17pub(crate) enum VersionedPrivateCommandKeys {
18    V1(PrivateCommandKeysV1),
19}
20
21#[derive(Serialize, Deserialize, Clone)]
22pub(crate) struct PrivateCommandKeysV1 {
23    p256key: EcKeyComponents,
24    auth_secret: Vec<u8>,
25}
26pub(crate) type PrivateCommandKeys = PrivateCommandKeysV1;
27
28impl PrivateCommandKeys {
29    // We define this method so if someone attempts to serialize `PrivateCommandKeys` directly
30    // they actually get a serialization of `VersionedPrivateCommandKeys`, which is what we want,
31    // because the latter "tags" the version.
32    // We should work out how to clean this up to avoid these hacks.
33    pub(crate) fn serialize(&self) -> Result<String> {
34        Ok(serde_json::to_string(&VersionedPrivateCommandKeys::V1(
35            self.clone(),
36        ))?)
37    }
38
39    pub(crate) fn deserialize(s: &str) -> Result<Self> {
40        let versionned: VersionedPrivateCommandKeys = serde_json::from_str(s)?;
41        match versionned {
42            VersionedPrivateCommandKeys::V1(prv_key) => Ok(prv_key),
43        }
44    }
45}
46
47impl PrivateCommandKeys {
48    pub fn from_random() -> Result<Self> {
49        rc_crypto::ensure_initialized();
50        let (key_pair, auth_secret) = ece::generate_keypair_and_auth_secret()?;
51        Ok(Self {
52            p256key: key_pair.raw_components()?,
53            auth_secret: auth_secret.to_vec(),
54        })
55    }
56
57    pub fn p256key(&self) -> &EcKeyComponents {
58        &self.p256key
59    }
60
61    pub fn auth_secret(&self) -> &[u8] {
62        &self.auth_secret
63    }
64}
65
66#[derive(Serialize, Deserialize)]
67struct CommandKeysPayload {
68    /// Hex encoded kid.
69    kid: String,
70    /// Base 64 encoded IV.
71    #[serde(rename = "IV")]
72    iv: String,
73    /// Hex encoded hmac.
74    hmac: String,
75    /// Base 64 encoded ciphertext.
76    ciphertext: String,
77}
78
79impl CommandKeysPayload {
80    fn decrypt(self, scoped_key: &ScopedKey) -> Result<PublicCommandKeys> {
81        let (ksync, kxcs) = extract_oldsync_key_components(scoped_key)?;
82        if hex::decode(self.kid)? != kxcs {
83            return Err(Error::MismatchedKeys);
84        }
85        let key = KeyBundle::from_ksync_bytes(&ksync)?;
86        let encrypted_payload = EncryptedPayload {
87            iv: self.iv,
88            hmac: self.hmac,
89            ciphertext: self.ciphertext,
90        };
91        Ok(encrypted_payload.decrypt_into(&key)?)
92    }
93}
94
95#[derive(Serialize, Deserialize)]
96pub struct PublicCommandKeys {
97    /// URL Safe Base 64 encoded push public key.
98    #[serde(rename = "publicKey")]
99    public_key: String,
100    /// URL Safe Base 64 encoded auth secret.
101    #[serde(rename = "authSecret")]
102    auth_secret: String,
103}
104
105impl PublicCommandKeys {
106    fn encrypt(&self, scoped_key: &ScopedKey) -> Result<CommandKeysPayload> {
107        let (ksync, kxcs) = extract_oldsync_key_components(scoped_key)?;
108        let key = KeyBundle::from_ksync_bytes(&ksync)?;
109        let encrypted_payload = EncryptedPayload::from_cleartext_payload(&key, &self)?;
110        Ok(CommandKeysPayload {
111            kid: hex::encode(kxcs),
112            iv: encrypted_payload.iv,
113            hmac: encrypted_payload.hmac,
114            ciphertext: encrypted_payload.ciphertext,
115        })
116    }
117    pub fn as_command_data(&self, scoped_key: &ScopedKey) -> Result<String> {
118        let encrypted_public_keys = self.encrypt(scoped_key)?;
119        Ok(serde_json::to_string(&encrypted_public_keys)?)
120    }
121    pub(crate) fn public_key(&self) -> &str {
122        &self.public_key
123    }
124    pub(crate) fn auth_secret(&self) -> &str {
125        &self.auth_secret
126    }
127}
128
129impl From<PrivateCommandKeys> for PublicCommandKeys {
130    fn from(internal: PrivateCommandKeys) -> Self {
131        Self {
132            public_key: URL_SAFE_NO_PAD.encode(internal.p256key.public_key()),
133            auth_secret: URL_SAFE_NO_PAD.encode(&internal.auth_secret),
134        }
135    }
136}
137
138fn extract_oldsync_key_components(oldsync_key: &ScopedKey) -> Result<(Vec<u8>, Vec<u8>)> {
139    if oldsync_key.scope != scopes::OLD_SYNC {
140        return Err(Error::IllegalState(
141            "Only oldsync scoped keys are supported at the moment.",
142        ));
143    }
144    let kxcs: &str = oldsync_key.kid.splitn(2, '-').collect::<Vec<_>>()[1];
145    let kxcs = URL_SAFE_NO_PAD.decode(kxcs)?;
146    let ksync = oldsync_key.key_bytes()?;
147    Ok((ksync, kxcs))
148}
149
150#[derive(Debug, Serialize, Deserialize)]
151struct EncryptedCommandPayload {
152    /// URL Safe Base 64 encrypted send-tab payload.
153    encrypted: String,
154}
155
156impl EncryptedCommandPayload {
157    pub(crate) fn decrypt<T: DeserializeOwned>(self, keys: &PrivateCommandKeys) -> Result<T> {
158        rc_crypto::ensure_initialized();
159        let encrypted = URL_SAFE_NO_PAD.decode(self.encrypted)?;
160        let decrypted = ece::decrypt(keys.p256key(), keys.auth_secret(), &encrypted)?;
161        Ok(serde_json::from_slice(&decrypted)?)
162    }
163}
164
165fn encrypt_payload<T: Serialize>(
166    payload: &T,
167    keys: PublicCommandKeys,
168) -> Result<EncryptedCommandPayload> {
169    rc_crypto::ensure_initialized();
170    let bytes = serde_json::to_vec(payload)?;
171    let public_key = URL_SAFE_NO_PAD.decode(keys.public_key())?;
172    let auth_secret = URL_SAFE_NO_PAD.decode(keys.auth_secret())?;
173    let encrypted = ece::encrypt(&public_key, &auth_secret, &bytes)?;
174    let encrypted = URL_SAFE_NO_PAD.encode(encrypted);
175    Ok(EncryptedCommandPayload { encrypted })
176}
177
178/// encrypt a command suitable for sending via a push message to another device.
179pub(crate) fn encrypt_command<T: Serialize>(
180    scoped_key: &ScopedKey,
181    target: &Device,
182    command: &'static str,
183    payload: &T,
184) -> Result<serde_json::Value> {
185    let public_keys = get_public_keys(scoped_key, target, command)?;
186    let encrypted_payload = encrypt_payload(payload, public_keys)?;
187    Ok(serde_json::to_value(encrypted_payload)?)
188}
189
190/// Get the public keys for a command for a device. These are encrypted in a device record.
191pub(crate) fn get_public_keys(
192    scoped_key: &ScopedKey,
193    target: &Device,
194    command: &'static str,
195) -> Result<PublicCommandKeys> {
196    let command = target
197        .available_commands
198        .get(command)
199        .ok_or(Error::UnsupportedCommand(command))?;
200    let bundle: CommandKeysPayload = serde_json::from_str(command)?;
201    bundle.decrypt(scoped_key)
202}
203
204/// decrypt a command sent from another device.
205pub(crate) fn decrypt_command<T: DeserializeOwned>(
206    v: serde_json::Value,
207    keys: &PrivateCommandKeys,
208) -> Result<T> {
209    let encrypted_payload: EncryptedCommandPayload = serde_json::from_value(v)?;
210    encrypted_payload.decrypt(keys)
211}