From 826d272516b4963db77d1448f4512606c7158c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Tue, 12 Dec 2023 14:46:48 +0100 Subject: [PATCH] [PM-4694] Implement PIN unlock (#421) ## Type of change ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Implement support for deriving the PIN encrypted user key and initializing crypto with it. Updated the mobile app demos to use this new functionality. --- crates/bitwarden-uniffi/src/crypto.rs | 10 +- crates/bitwarden/src/client/client.rs | 24 +++- crates/bitwarden/src/crypto/master_key.rs | 41 ++++-- .../src/crypto/symmetric_crypto_key.rs | 10 ++ crates/bitwarden/src/mobile/client_crypto.rs | 9 +- crates/bitwarden/src/mobile/crypto.rs | 118 +++++++++++++++++- .../bitwarden/myapplication/MainActivity.kt | 89 ++++++++++++- languages/kotlin/doc.md | 39 ++++++ languages/swift/iOS/App/ContentView.swift | 61 ++++++++- 9 files changed, 381 insertions(+), 20 deletions(-) diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 995750b7f..25d577ba5 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use bitwarden::mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}; +use bitwarden::mobile::crypto::{ + DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, +}; use crate::{error::Result, Client}; @@ -45,4 +47,10 @@ impl ClientCrypto { .get_user_encryption_key() .await?) } + + /// Generates a PIN protected user key from the provided PIN. The result can be stored and later used + /// to initialize another client instance by using the PIN and the PIN key with `initialize_user_crypto`. + pub async fn derive_pin_key(&self, pin: String) -> Result { + Ok(self.0 .0.write().await.crypto().derive_pin_key(pin).await?) + } } diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 2b6b76a2f..2165b8dc7 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -224,10 +224,9 @@ impl Client { #[cfg(feature = "mobile")] pub(crate) fn initialize_user_crypto_decrypted_key( &mut self, - decrypted_user_key: &str, + user_key: SymmetricCryptoKey, private_key: EncString, ) -> Result<&EncryptionSettings> { - let user_key = decrypted_user_key.parse::()?; self.encryption_settings = Some(EncryptionSettings::new_decrypted_key( user_key, private_key, @@ -238,6 +237,27 @@ impl Client { .expect("It was initialized on the previous line")) } + #[cfg(feature = "mobile")] + pub(crate) fn initialize_user_crypto_pin( + &mut self, + pin: &str, + pin_protected_user_key: EncString, + private_key: EncString, + ) -> Result<&EncryptionSettings> { + use crate::crypto::MasterKey; + + let pin_key = match &self.login_method { + Some(LoginMethod::User( + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. }, + )) => MasterKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?, + _ => return Err(Error::NotAuthenticated), + }; + + let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?; + self.initialize_user_crypto_decrypted_key(decrypted_user_key, private_key) + } + pub(crate) fn initialize_crypto_single_key( &mut self, key: SymmetricCryptoKey, diff --git a/crates/bitwarden/src/crypto/master_key.rs b/crates/bitwarden/src/crypto/master_key.rs index 88e5b9327..9786da3c2 100644 --- a/crates/bitwarden/src/crypto/master_key.rs +++ b/crates/bitwarden/src/crypto/master_key.rs @@ -52,6 +52,16 @@ impl MasterKey { let dec: Vec = user_key.decrypt_with_key(&stretched_key)?; SymmetricCryptoKey::try_from(dec.as_slice()) } + + pub(crate) fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { + let stretched_key = stretch_master_key(self)?; + + EncString::encrypt_aes256_hmac( + user_key.to_vec().as_slice(), + stretched_key.mac_key.unwrap(), + stretched_key.key, + ) + } } /// Generate a new random user key and encrypt it with the master key. @@ -62,15 +72,9 @@ fn make_user_key( let mut user_key = [0u8; 64]; rng.fill(&mut user_key); - let stretched_key = stretch_master_key(master_key)?; - let protected = EncString::encrypt_aes256_hmac( - &user_key, - stretched_key.mac_key.unwrap(), - stretched_key.key, - )?; - - let u: &[u8] = &user_key; - Ok((UserKey::new(SymmetricCryptoKey::try_from(u)?), protected)) + let user_key = SymmetricCryptoKey::try_from(user_key.as_slice())?; + let protected = master_key.encrypt_user_key(&user_key)?; + Ok((UserKey::new(user_key), protected)) } /// Derive a generic key from a secret and salt using the provided KDF. @@ -285,4 +289,23 @@ mod tests { "Decrypted key doesn't match user key" ); } + + #[test] + fn test_make_user_key2() { + let master_key = MasterKey(SymmetricCryptoKey::generate("test1")); + + let user_key = SymmetricCryptoKey::generate("test2"); + + let encrypted = master_key.encrypt_user_key(&user_key).unwrap(); + let decrypted = master_key.decrypt_user_key(encrypted).unwrap(); + + assert_eq!( + decrypted.key, user_key.key, + "Decrypted key doesn't match user key" + ); + assert_eq!( + decrypted.mac_key, user_key.mac_key, + "Decrypted key doesn't match user key" + ); + } } diff --git a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs index 7b2086f75..5bce9e65d 100644 --- a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs +++ b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs @@ -35,6 +35,16 @@ impl SymmetricCryptoKey { BASE64_ENGINE.encode(&buf) } + + #[cfg(feature = "internal")] + pub(super) fn to_vec(&self) -> Vec { + let mut buf = Vec::new(); + buf.extend_from_slice(&self.key); + if let Some(mac) = self.mac_key { + buf.extend_from_slice(&mac); + } + buf + } } impl FromStr for SymmetricCryptoKey { diff --git a/crates/bitwarden/src/mobile/client_crypto.rs b/crates/bitwarden/src/mobile/client_crypto.rs index 118e02726..cddb7c140 100644 --- a/crates/bitwarden/src/mobile/client_crypto.rs +++ b/crates/bitwarden/src/mobile/client_crypto.rs @@ -3,8 +3,8 @@ use crate::Client; use crate::{ error::Result, mobile::crypto::{ - get_user_encryption_key, initialize_org_crypto, initialize_user_crypto, - InitOrgCryptoRequest, InitUserCryptoRequest, + derive_pin_key, get_user_encryption_key, initialize_org_crypto, initialize_user_crypto, + DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, }, }; @@ -27,6 +27,11 @@ impl<'a> ClientCrypto<'a> { pub async fn get_user_encryption_key(&mut self) -> Result { get_user_encryption_key(self.client).await } + + #[cfg(feature = "internal")] + pub async fn derive_pin_key(&mut self, pin: String) -> Result { + derive_pin_key(self.client, pin) + } } impl<'a> Client { diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index 9c2c8478f..edb44fadc 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -40,10 +40,18 @@ pub enum InitUserCryptoMethod { /// The user's decrypted encryption key, obtained using `get_user_encryption_key` decrypted_user_key: String, }, + Pin { + /// The user's PIN + pin: String, + /// The user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain this. + pin_protected_user_key: EncString, + }, } #[cfg(feature = "internal")] pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequest) -> Result<()> { + use crate::crypto::SymmetricCryptoKey; + let login_method = crate::client::LoginMethod::User(crate::client::UserLoginMethod::Username { client_id: "".to_string(), email: req.email, @@ -59,7 +67,14 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ client.initialize_user_crypto(&password, user_key, private_key)?; } InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => { - client.initialize_user_crypto_decrypted_key(&decrypted_user_key, private_key)?; + let user_key = decrypted_user_key.parse::()?; + client.initialize_user_crypto_decrypted_key(user_key, private_key)?; + } + InitUserCryptoMethod::Pin { + pin, + pin_protected_user_key, + } => { + client.initialize_user_crypto_pin(&pin, pin_protected_user_key, private_key)?; } } @@ -91,3 +106,104 @@ pub async fn get_user_encryption_key(client: &mut Client) -> Result { Ok(user_key.to_base64()) } + +#[cfg(feature = "internal")] +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct DerivePinKeyResponse { + pin_protected_user_key: EncString, + encrypted_pin: EncString, +} + +#[cfg(feature = "internal")] +pub fn derive_pin_key(client: &mut Client, pin: String) -> Result { + use crate::{ + client::{LoginMethod, UserLoginMethod}, + crypto::{KeyEncryptable, MasterKey}, + }; + + let derived_key = match &client.login_method { + Some(LoginMethod::User( + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. }, + )) => MasterKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?, + _ => return Err(Error::NotAuthenticated), + }; + + let user_key = client + .get_encryption_settings()? + .get_key(&None) + .ok_or(Error::VaultLocked)?; + + Ok(DerivePinKeyResponse { + pin_protected_user_key: derived_key.encrypt_user_key(user_key)?, + encrypted_pin: pin.encrypt_with_key(user_key)?, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{client::kdf::Kdf, Client}; + + #[tokio::test] + async fn test_initialize_user_crypto_pin() { + let mut client = Client::new(None); + + let priv_key = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw="; + + initialize_user_crypto( + &mut client, + InitUserCryptoRequest { + kdf_params: Kdf::PBKDF2 { + iterations: 100_000.try_into().unwrap(), + }, + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + method: InitUserCryptoMethod::Password { + password: "asdfasdfasdf".into(), + user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".into(), + }, + }, + ) + .await + .unwrap(); + + let pin_key = derive_pin_key(&mut client, "1234".into()).unwrap(); + + let mut client2 = Client::new(None); + + initialize_user_crypto( + &mut client2, + InitUserCryptoRequest { + kdf_params: Kdf::PBKDF2 { + iterations: 100_000.try_into().unwrap(), + }, + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + method: InitUserCryptoMethod::Pin { + pin: "1234".into(), + pin_protected_user_key: pin_key.pin_protected_user_key, + }, + }, + ) + .await + .unwrap(); + + assert_eq!( + client + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64(), + client2 + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64() + ); + } +} diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt index b41b12b8c..c38c14a07 100644 --- a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt @@ -72,6 +72,8 @@ const val IDENTITY_URL = SERVER_URL + "identity/" const val EMAIL = "test@bitwarden.com" const val PASSWORD = "asdfasdfasdf" +const val PIN = "1234" + // We should separate keys for each user by appending the user_id const val BIOMETRIC_KEY = "biometric_key" @@ -99,10 +101,12 @@ class MainActivity : FragmentActivity() { ) { val setupBiometrics = remember { mutableStateOf(true) } + val setupPin = remember { mutableStateOf(true) } val outputText = remember { mutableStateOf("") } Row { - Checkbox(checked = setupBiometrics.value, + Checkbox( + checked = setupBiometrics.value, onCheckedChange = { isChecked -> setupBiometrics.value = isChecked }) @@ -112,10 +116,20 @@ class MainActivity : FragmentActivity() { ) } + Row { + Checkbox(checked = setupPin.value, onCheckedChange = { isChecked -> + setupPin.value = isChecked + }) + Text( + "Setup pin unlock after login", + modifier = Modifier.align(CenterVertically) + ) + } + Button({ GlobalScope.launch { clientExamplePassword( - client, http, outputText, setupBiometrics.value + client, http, outputText, setupBiometrics.value, setupPin.value ) } }) { @@ -136,6 +150,14 @@ class MainActivity : FragmentActivity() { Text("Unlock with biometrics") } + Button({ + GlobalScope.launch { + clientExamplePin(client, http, outputText) + } + }) { + Text("Unlock with PIN") + } + Button({ GlobalScope.launch { client.destroy() @@ -157,7 +179,11 @@ class MainActivity : FragmentActivity() { } private suspend fun clientExamplePassword( - client: Client, http: HttpClient, outputText: MutableState, setupBiometrics: Boolean + client: Client, + http: HttpClient, + outputText: MutableState, + setupBiometrics: Boolean, + setupPin: Boolean ) { println("### Logging in with username and password ###") ///////////////////////////// Get master password hash ///////////////////////////// @@ -248,6 +274,25 @@ class MainActivity : FragmentActivity() { } } } + + if (setupPin) { + val pinOptions = client.crypto().derivePinKey(PIN); + + val sharedPref = getPreferences(Context.MODE_PRIVATE) + with(sharedPref.edit()) { + putString("accessToken", accessToken) + putString("privateKey", loginBody.PrivateKey) + + putInt("kdfType", prelogin_body.kdf.toInt()) + putInt("kdfIterations", prelogin_body.kdfIterations.toInt()) + putInt("kdfMemory", (prelogin_body.kdfMemory ?: 0u).toInt()) + putInt("kdfParallelism", (prelogin_body.kdfParallelism ?: 0u).toInt()) + + putString("encryptedPin", pinOptions.encryptedPin) + putString("pinProtectedUserKey", pinOptions.pinProtectedUserKey) + apply() + } + } } private suspend fun clientExampleBiometrics( @@ -290,6 +335,44 @@ class MainActivity : FragmentActivity() { } } + private suspend fun clientExamplePin( + client: Client, http: HttpClient, outputText: MutableState + ) { + println("### Unlocking with PIN ###") + + val pref = getPreferences(Context.MODE_PRIVATE) + accessToken = pref.getString("accessToken", "").orEmpty() + val privateKey = pref.getString("privateKey", "") + + val kdf = if (pref.getInt("kdfType", 0) == 0) { + Kdf.Pbkdf2(pref.getInt("kdfIterations", 0).toUInt()) + } else { + Kdf.Argon2id( + pref.getInt("kdfIterations", 0).toUInt(), + pref.getInt("kdfMemory", 0).toUInt(), + pref.getInt("kdfParallelism", 0).toUInt() + ) + } + + val encryptedPin = pref.getString("encryptedPin", "")!! + val pinProtectedUserKey = pref.getString("pinProtectedUserKey", "")!! + + GlobalScope.launch { + client.crypto().initializeUserCrypto( + InitUserCryptoRequest( + kdfParams = kdf, + email = EMAIL, + privateKey = privateKey!!, + method = InitUserCryptoMethod.Pin( + pinProtectedUserKey = pinProtectedUserKey, pin = PIN + ) + ) + ) + + decryptVault(client, http, outputText) + } + } + suspend fun decryptVault(client: Client, http: HttpClient, outputText: MutableState) { ///////////////////////////// Sync ///////////////////////////// diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index f9f47db64..9249751f0 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -233,6 +233,19 @@ as it can be used to decrypt all of the user's data **Output**: std::result::Result +### `derive_pin_key` + +Generates a PIN protected user key from the provided PIN. The result can be stored and later used to +initialize another client instance by using the PIN and the PIN key with +`initialize_user_crypto`. + +**Arguments**: + +- self: +- pin: String + +**Output**: std::result::Result + ## ClientExporters ### `export_vault` @@ -998,6 +1011,32 @@ implementations. + + pin + object + + + + + + + + + + + + + + + + + + + + +
KeyTypeDescription
pinstringThe user's PIN
pin_protected_user_keyThe user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain this.
+ + ## `InitUserCryptoRequest` diff --git a/languages/swift/iOS/App/ContentView.swift b/languages/swift/iOS/App/ContentView.swift index c2b488b8f..b5fc0d4f0 100644 --- a/languages/swift/iOS/App/ContentView.swift +++ b/languages/swift/iOS/App/ContentView.swift @@ -23,6 +23,8 @@ let IDENTITY_URL = SERVER_URL + "identity/" let EMAIL = "test@bitwarden.com" let PASSWORD = "asdfasdfasdf" +let PIN = "1234" + struct ContentView: View { private var http: URLSession @@ -40,16 +42,18 @@ struct ContentView: View { } @State var setupBiometrics: Bool = true + @State var setupPin: Bool = true @State var outputText: String = "" var body: some View { VStack { Toggle("Setup biometric unlock after login", isOn: $setupBiometrics).padding(.init(top: 0, leading: 20, bottom: 0, trailing: 20)) + Toggle("Setup PIN unlock after login", isOn: $setupPin).padding(.init(top: 0, leading: 20, bottom: 0, trailing: 20)) Button(action: { Task { do { - try await clientExamplePassword(clientAuth: client.auth(), clientCrypto: client.crypto(), setupBiometrics: setupBiometrics) + try await clientExamplePassword(clientAuth: client.auth(), clientCrypto: client.crypto(), setupBiometrics: setupBiometrics, setupPin: setupPin) try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) } catch { print("ERROR:", error) @@ -74,6 +78,19 @@ struct ContentView: View { Text("Unlock with biometrics") }) + Button(action: { + Task { + do { + try await clientExamplePin(clientCrypto: client.crypto()) + try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) + } catch { + print("ERROR:", error) + } + } + }, label: { + Text("Unlock with PIN") + }) + Button(action: { client = Client(settings: nil) }, label: { @@ -85,7 +102,7 @@ struct ContentView: View { .padding() } - func clientExamplePassword(clientAuth: ClientAuthProtocol, clientCrypto: ClientCryptoProtocol, setupBiometrics: Bool) async throws { + func clientExamplePassword(clientAuth: ClientAuthProtocol, clientCrypto: ClientCryptoProtocol, setupBiometrics: Bool, setupPin: Bool) async throws { ////////////////////////////// Get master password hash ////////////////////////////// struct PreloginRequest: Codable { let email: String } @@ -183,6 +200,22 @@ struct ContentView: View { let key = try await clientCrypto.getUserEncryptionKey() biometricStoreValue(value: key) } + + if (setupPin) { + let pinOptions = try await clientCrypto.derivePinKey(pin: PIN) + + let defaults = UserDefaults.standard + defaults.set(loginData.PrivateKey, forKey: "privateKey") + defaults.set(preloginData.kdf, forKey: "kdfType") + defaults.set(preloginData.kdfIterations, forKey: "kdfIterations") + defaults.set(preloginData.kdfMemory, forKey: "kdfMemory") + defaults.set(preloginData.kdfParallelism, forKey: "kdfParallelism") + + defaults.set(pinOptions.encryptedPin, forKey: "encryptedPin") + defaults.set(pinOptions.pinProtectedUserKey, forKey: "pinProtectedUserKey") + + defaults.synchronize() + } } func clientExampleBiometrics(clientCrypto: ClientCryptoProtocol) async throws { @@ -210,6 +243,30 @@ struct ContentView: View { )) } + func clientExamplePin(clientCrypto: ClientCryptoProtocol) async throws { + let defaults = UserDefaults.standard + let privateKey = defaults.string(forKey: "privateKey")! + let kdf = if defaults.integer(forKey: "kdfType") == 0 { + Kdf.pbkdf2(iterations: UInt32(defaults.integer(forKey: "kdfIterations"))) + } else { + Kdf.argon2id( + iterations: UInt32(defaults.integer(forKey: "kdfIterations")), + memory: UInt32(defaults.integer(forKey: "kdfMemory")), + parallelism: UInt32(defaults.integer(forKey: "kdfParallelism")) + ) + } + + let encryptedPin = defaults.string(forKey: "encryptedPin")! + let pinProtectedUserKey = defaults.string(forKey: "pinProtectedUserKey")! + + try await clientCrypto.initializeUserCrypto(req: InitUserCryptoRequest( + kdfParams: kdf, + email: EMAIL, + privateKey: privateKey, + method: InitUserCryptoMethod.pin(pin: PIN, pinProtectedUserKey: pinProtectedUserKey) + )) + } + func decryptVault(clientCrypto: ClientCryptoProtocol, clientVault: ClientVaultProtocol) async throws { ///////////////////////////// Sync /////////////////////////////