sos_net/pairing/
share_url.rs

1use super::{Error, Result};
2use hex;
3use rand::Rng;
4use sos_core::{csprng, AccountId};
5use std::str::FromStr;
6use url::Url;
7
8/// Account identifier.
9const AID: &str = "aid";
10/// Server URL.
11const URL: &str = "url";
12/// Noise public key.
13const KEY: &str = "key";
14/// Symmetric pre-shared key.
15const PSK: &str = "psk";
16
17/// URL shared to offer device pairing via an untrusted relay server.
18#[derive(Debug, Clone)]
19pub struct ServerPairUrl {
20    /// Account identifier.
21    account_id: AccountId,
22    /// Server used to transfer the account data.
23    server: Url,
24    /// Public key of the noise protocol.
25    public_key: Vec<u8>,
26    /// Symmetric pre-shared key.
27    pre_shared_key: [u8; 32],
28}
29
30impl ServerPairUrl {
31    /// Create a URL for pairing two devices.
32    ///
33    /// The public key is the noise protocol public key
34    /// of the device.
35    pub fn new(
36        account_id: AccountId,
37        server: Url,
38        public_key: Vec<u8>,
39    ) -> Self {
40        let pre_shared_key: [u8; 32] = csprng().gen();
41        Self {
42            account_id,
43            server,
44            public_key,
45            pre_shared_key,
46        }
47    }
48
49    /// Account identifier.
50    pub fn account_id(&self) -> &AccountId {
51        &self.account_id
52    }
53
54    /// Server URL.
55    pub fn server(&self) -> &Url {
56        &self.server
57    }
58
59    /// Noise protocol public key.
60    pub fn public_key(&self) -> &[u8] {
61        &self.public_key
62    }
63
64    /// Synmmetric pre-shared key.
65    pub fn pre_shared_key(&self) -> [u8; 32] {
66        self.pre_shared_key
67    }
68}
69
70impl From<ServerPairUrl> for Url {
71    fn from(value: ServerPairUrl) -> Self {
72        let mut url = Url::parse("data:text/plain,sos-pair").unwrap();
73        let key = hex::encode(&value.public_key);
74        let psk = hex::encode(&value.pre_shared_key);
75        url.query_pairs_mut()
76            .append_pair(AID, &value.account_id.to_string())
77            .append_pair(URL, &value.server.to_string())
78            .append_pair(KEY, &key)
79            .append_pair(PSK, &psk);
80        url
81    }
82}
83
84impl FromStr for ServerPairUrl {
85    type Err = Error;
86
87    fn from_str(s: &str) -> Result<Self> {
88        let url = Url::parse(s)?;
89
90        if url.scheme() != "data" {
91            return Err(Error::InvalidShareUrl);
92        }
93
94        if url.path() != "text/plain,sos-pair" {
95            return Err(Error::InvalidShareUrl);
96        }
97
98        let mut pairs = url.query_pairs();
99
100        let account_id = pairs.find_map(|q| {
101            if q.0.as_ref() == AID {
102                Some(q.1)
103            } else {
104                None
105            }
106        });
107        let account_id = account_id.ok_or(Error::InvalidShareUrl)?;
108        let account_id: AccountId = account_id.as_ref().parse()?;
109
110        let server = pairs.find_map(|q| {
111            if q.0.as_ref() == URL {
112                Some(q.1)
113            } else {
114                None
115            }
116        });
117        let server = server.ok_or(Error::InvalidShareUrl)?;
118        let server: Url = server.as_ref().parse()?;
119
120        let key = pairs.find_map(|q| {
121            if q.0.as_ref() == KEY {
122                Some(q.1)
123            } else {
124                None
125            }
126        });
127        let key = key.ok_or(Error::InvalidShareUrl)?;
128        let key = hex::decode(key.as_ref())?;
129
130        let psk = pairs.find_map(|q| {
131            if q.0.as_ref() == PSK {
132                Some(q.1)
133            } else {
134                None
135            }
136        });
137        let psk = psk.ok_or(Error::InvalidShareUrl)?;
138        let psk = hex::decode(psk.as_ref())?;
139        let psk: [u8; 32] = psk.as_slice().try_into()?;
140
141        Ok(Self {
142            account_id,
143            server,
144            public_key: key,
145            pre_shared_key: psk,
146        })
147    }
148}