sos_login/
device.rs

1//! Types for device support.
2use crate::{Error, Result};
3use secrecy::ExposeSecret;
4use sos_backend::{
5    database::entity::{AccountEntity, FolderEntity, FolderRow},
6    AccessPoint, BackendTarget,
7};
8use sos_core::{
9    crypto::AccessKey,
10    device::{DeviceMetaData, DevicePublicKey, TrustedDevice},
11    encode, AccountId, SecretId, VaultFlags,
12};
13use sos_filesystem::write_exclusive;
14use sos_signer::ed25519::{self, BoxedEd25519Signer, SingleParty};
15use sos_vault::{
16    secret::{Secret, SecretSigner},
17    BuilderCredentials, SecretAccess, Vault, VaultBuilder,
18};
19use urn::Urn;
20
21/// Signing key for a device.
22#[derive(Clone)]
23pub struct DeviceSigner(pub(crate) BoxedEd25519Signer);
24
25impl DeviceSigner {
26    /// Create a new random device signing key.
27    pub fn random() -> Self {
28        let key = SingleParty::new_random();
29        Self(Box::new(key))
30    }
31
32    /// Device signing key.
33    pub fn signing_key(&self) -> &BoxedEd25519Signer {
34        &self.0
35    }
36
37    /// Public verifying key as bytes.
38    pub fn public_key(&self) -> DevicePublicKey {
39        self.0.verifying_key().as_bytes().into()
40    }
41
42    /// Bytes for this signing key.
43    pub fn to_bytes(&self) -> [u8; 32] {
44        self.0.to_bytes().try_into().unwrap()
45    }
46}
47
48impl TryFrom<[u8; 32]> for DeviceSigner {
49    type Error = Error;
50
51    fn try_from(value: [u8; 32]) -> Result<Self> {
52        let signer: SingleParty = value.try_into()?;
53        Ok(Self(Box::new(signer)))
54    }
55}
56
57impl From<DeviceSigner> for BoxedEd25519Signer {
58    fn from(value: DeviceSigner) -> Self {
59        value.0
60    }
61}
62
63impl From<SingleParty> for DeviceSigner {
64    fn from(value: SingleParty) -> Self {
65        Self(Box::new(value))
66    }
67}
68
69/// Manages the access point that protects the device signing key.
70///
71/// Call [DeviceManager::sign_out] to lock the device vault.
72pub struct DeviceManager {
73    /// Signing key for this device.
74    signer: DeviceSigner,
75    /// Access to the vault that stores the device
76    /// signing key.
77    access_point: AccessPoint,
78}
79
80impl DeviceManager {
81    /// Initialize a new device manager.
82    ///
83    /// The access point should be unlocked before assigning to a
84    /// device manager.
85    fn init(signer: DeviceSigner, access_point: AccessPoint) -> Self {
86        Self {
87            signer,
88            access_point,
89        }
90    }
91
92    /// Create a new device manager from a signer.
93    pub async fn new(
94        target: &BackendTarget,
95        account_id: &AccountId,
96        signer: DeviceSigner,
97        password: &AccessKey,
98    ) -> Result<Self> {
99        let device_vault = Self::create_device_vault(password).await?;
100        let access_point = match target {
101            BackendTarget::FileSystem(paths) => {
102                let buffer = encode(&device_vault).await?;
103                write_exclusive(paths.device_file(), &buffer).await?;
104                AccessPoint::from_path(paths.device_file(), device_vault)
105            }
106            BackendTarget::Database(_, client) => {
107                let account_id = *account_id;
108                let folder_row = FolderRow::new_insert(&device_vault).await?;
109                client
110                    .conn(move |conn| {
111                        let account = AccountEntity::new(&conn);
112                        let folder = FolderEntity::new(&conn);
113                        let account_row = account.find_one(&account_id)?;
114                        let folder_id = folder
115                            .insert_folder(account_row.row_id, &folder_row)?;
116                        account.insert_device_folder(
117                            account_row.row_id,
118                            folder_id,
119                        )
120                    })
121                    .await
122                    .map_err(sos_backend::database::Error::from)?;
123                AccessPoint::new(target.clone(), device_vault).await
124            }
125        };
126        Ok(Self::init(signer, access_point))
127    }
128
129    /// Load a device manager from an existing vault.
130    pub async fn open_vault(
131        target: BackendTarget,
132        vault: Vault,
133        access_key: &AccessKey,
134    ) -> Result<(Self, SecretId)> {
135        let device_key_urn = Self::device_urn()?;
136        tracing::debug!(
137            urn = %device_key_urn,
138            backend_target = %target,
139            "device::open_vault");
140
141        let mut device_keeper = AccessPoint::new(target, vault).await;
142        device_keeper.unlock(access_key).await?;
143
144        // Try to find the device signing key
145        let mut device_signer_secret: Option<(SecretId, Secret)> = None;
146        for id in device_keeper.vault().keys() {
147            if let Some((meta, secret, _)) =
148                device_keeper.read_secret(id).await?
149            {
150                if let Some(urn) = meta.urn() {
151                    if urn == &device_key_urn {
152                        device_signer_secret = Some((*id, secret));
153                        break;
154                    }
155                }
156            }
157        }
158
159        if let Some((
160            id,
161            Secret::Signer {
162                private_key: SecretSigner::SinglePartyEd25519(data),
163                ..
164            },
165        )) = device_signer_secret
166        {
167            let key: ed25519::SingleParty =
168                data.expose_secret().as_slice().try_into()?;
169            Ok((DeviceManager::init(key.into(), device_keeper), id))
170        } else {
171            Err(Error::VaultEntryKind(device_key_urn.to_string()))
172        }
173    }
174
175    pub(crate) fn device_urn() -> Result<Urn> {
176        use sos_core::constants::DEVICE_KEY_URN;
177        Ok(DEVICE_KEY_URN.parse()?)
178    }
179
180    async fn create_device_vault(password: &AccessKey) -> Result<Vault> {
181        // Prepare the device vault
182        let vault = VaultBuilder::new()
183            .public_name("Device".to_string())
184            .flags(
185                VaultFlags::SYSTEM | VaultFlags::DEVICE | VaultFlags::NO_SYNC,
186            )
187            .build(BuilderCredentials::Password(
188                password.clone().into(),
189                None,
190            ))
191            .await?;
192
193        Ok(vault)
194    }
195
196    /// Access point for the device vault.
197    pub(crate) fn access_point(&self) -> &AccessPoint {
198        &self.access_point
199    }
200
201    /// Mutable access point for the device vault.
202    pub(crate) fn access_point_mut(&mut self) -> &mut AccessPoint {
203        &mut self.access_point
204    }
205
206    /// Signing key for this device.
207    pub(crate) fn signer(&self) -> &DeviceSigner {
208        &self.signer
209    }
210
211    /// Consume this device manager into a buffer of the vault.
212    pub async fn into_vault_buffer(self) -> Result<Vec<u8>> {
213        let vault: Vault = self.access_point.into();
214        let buffer = encode(&vault).await?;
215        Ok(buffer)
216    }
217
218    /// Current device information.
219    pub fn current_device(
220        &self,
221        extra_info: Option<DeviceMetaData>,
222    ) -> TrustedDevice {
223        TrustedDevice::new(self.signer.public_key(), extra_info, None)
224    }
225
226    /// Sign out locking the device vault.
227    pub fn sign_out(&mut self) {
228        self.access_point.lock();
229    }
230}