From bebf731307d4a93ef4dba8c80373b3de22be60ca Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 21 Nov 2023 14:03:25 +0100 Subject: [PATCH 01/27] AES tests (#340) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel GarcĂ­a --- crates/bitwarden/src/crypto/aes_ops.rs | 100 +++++++++++++++++++--- crates/bitwarden/src/crypto/enc_string.rs | 12 ++- crates/bitwarden/src/crypto/master_key.rs | 11 ++- crates/bitwarden/src/crypto/rsa.rs | 4 +- 4 files changed, 110 insertions(+), 17 deletions(-) diff --git a/crates/bitwarden/src/crypto/aes_ops.rs b/crates/bitwarden/src/crypto/aes_ops.rs index 132718349..3deedabd8 100644 --- a/crates/bitwarden/src/crypto/aes_ops.rs +++ b/crates/bitwarden/src/crypto/aes_ops.rs @@ -13,10 +13,9 @@ use aes::cipher::{ BlockEncryptMut, KeyIvInit, }; use hmac::Mac; -use rand::RngCore; use crate::{ - crypto::{EncString, PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE}, + crypto::{PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE}, error::{CryptoError, Result}, }; @@ -63,10 +62,11 @@ pub fn decrypt_aes256_hmac( /// /// A AesCbc256_B64 EncString #[allow(unused)] -pub fn encrypt_aes256(data_dec: &[u8], key: GenericArray) -> Result { - let (iv, data) = encrypt_aes256_internal(data_dec, key); +pub fn encrypt_aes256(data_dec: &[u8], key: GenericArray) -> ([u8; 16], Vec) { + let rng = rand::thread_rng(); + let (iv, data) = encrypt_aes256_internal(rng, data_dec, key); - Ok(EncString::AesCbc256_B64 { iv, data }) + (iv, data) } /// Encrypt using AES-256 in CBC mode with MAC. @@ -80,11 +80,12 @@ pub fn encrypt_aes256_hmac( data_dec: &[u8], mac_key: GenericArray, key: GenericArray, -) -> Result { - let (iv, data) = encrypt_aes256_internal(data_dec, key); +) -> Result<([u8; 16], [u8; 32], Vec)> { + let rng = rand::thread_rng(); + let (iv, data) = encrypt_aes256_internal(rng, data_dec, key); let mac = validate_mac(&mac_key, &iv, &data)?; - Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) + Ok((iv, mac, data)) } /// Encrypt using AES-256 in CBC mode. @@ -92,9 +93,13 @@ pub fn encrypt_aes256_hmac( /// Used internally by: /// - [encrypt_aes256] /// - [encrypt_aes256_hmac] -fn encrypt_aes256_internal(data_dec: &[u8], key: GenericArray) -> ([u8; 16], Vec) { +fn encrypt_aes256_internal( + mut rng: impl rand::RngCore, + data_dec: &[u8], + key: GenericArray, +) -> ([u8; 16], Vec) { let mut iv = [0u8; 16]; - rand::thread_rng().fill_bytes(&mut iv); + rng.fill_bytes(&mut iv); let data = cbc::Encryptor::::new(&key, &iv.into()) .encrypt_padded_vec_mut::(data_dec); @@ -112,3 +117,78 @@ fn validate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> Result<[u8; 32]> { Ok(mac) } + +#[cfg(test)] +mod tests { + use aes::cipher::generic_array::sequence::GenericSequence; + use base64::Engine; + use rand::SeedableRng; + + use crate::util::BASE64_ENGINE; + + use super::*; + + /// Helper function for generating a `GenericArray` of size 32 with each element being + /// a multiple of a given increment, starting from a given offset. + fn generate_generic_array(offset: u8, increment: u8) -> GenericArray { + GenericArray::generate(|i| offset + i as u8 * increment) + } + + /// Helper function for generating a vector of a given size with each element being + /// a multiple of a given increment, starting from a given offset. + fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec { + (0..length).map(|i| offset + i as u8 * increment).collect() + } + + #[test] + fn test_encrypt_aes256_internal() { + let key = generate_generic_array(0, 1); + + let rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + let result = encrypt_aes256_internal(rng, "EncryptMe!".as_bytes(), key); + assert_eq!( + result, + ( + [62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161], + vec![214, 76, 187, 97, 58, 146, 212, 140, 95, 164, 177, 204, 179, 133, 172, 148] + ) + ); + } + + #[test] + fn test_validate_mac() { + let mac_key = generate_vec(16, 0, 16); + + let iv = generate_vec(16, 0, 16); + let data = generate_vec(16, 0, 16); + + let result = validate_mac(&mac_key, &iv, &data); + + assert!(result.is_ok()); + let mac = result.unwrap(); + assert_eq!(mac.len(), 32); + } + + #[test] + fn test_decrypt_aes256() { + let iv = generate_vec(16, 0, 1); + let iv: &[u8; 16] = iv.as_slice().try_into().unwrap(); + let key = generate_generic_array(0, 1); + let data = BASE64_ENGINE.decode("ByUF8vhyX4ddU9gcooznwA==").unwrap(); + + let decrypted = decrypt_aes256(iv, data, key).unwrap(); + + assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!"); + } + + #[test] + fn test_encrypt_decrypt_aes256() { + let key = generate_generic_array(0, 1); + let data = "EncryptMe!"; + + let (iv, encrypted) = encrypt_aes256(data.as_bytes(), key); + let decrypted = decrypt_aes256(&iv, encrypted, key).unwrap(); + + assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!"); + } +} diff --git a/crates/bitwarden/src/crypto/enc_string.rs b/crates/bitwarden/src/crypto/enc_string.rs index ac7f3fc6b..09f877c50 100644 --- a/crates/bitwarden/src/crypto/enc_string.rs +++ b/crates/bitwarden/src/crypto/enc_string.rs @@ -1,5 +1,6 @@ use std::{fmt::Display, str::FromStr}; +use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::Engine; use serde::{de::Visitor, Deserialize}; @@ -331,6 +332,15 @@ impl serde::Serialize for EncString { } impl EncString { + pub(crate) fn encrypt_aes256_hmac( + data_dec: &[u8], + mac_key: GenericArray, + key: GenericArray, + ) -> Result { + let (iv, mac, data) = super::encrypt_aes256_hmac(data_dec, mac_key, key)?; + Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) + } + /// The numerical representation of the encryption type of the [EncString]. const fn enc_type(&self) -> u8 { match self { @@ -357,7 +367,7 @@ fn invalid_len_error(expected: usize) -> impl Fn(Vec) -> EncStringParseError impl LocateKey for EncString {} impl KeyEncryptable for &[u8] { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { - super::encrypt_aes256_hmac(self, key.mac_key.ok_or(CryptoError::InvalidMac)?, key.key) + EncString::encrypt_aes256_hmac(self, key.mac_key.ok_or(CryptoError::InvalidMac)?, key.key) } } diff --git a/crates/bitwarden/src/crypto/master_key.rs b/crates/bitwarden/src/crypto/master_key.rs index b2c8e6e5f..fdcf93dde 100644 --- a/crates/bitwarden/src/crypto/master_key.rs +++ b/crates/bitwarden/src/crypto/master_key.rs @@ -4,8 +4,8 @@ use rand::Rng; use sha2::Digest; use super::{ - encrypt_aes256_hmac, hkdf_expand, EncString, KeyDecryptable, PbkdfSha256Hmac, - SymmetricCryptoKey, UserKey, PBKDF_SHA256_HMAC_OUT_SIZE, + hkdf_expand, EncString, KeyDecryptable, PbkdfSha256Hmac, SymmetricCryptoKey, UserKey, + PBKDF_SHA256_HMAC_OUT_SIZE, }; use crate::{client::kdf::Kdf, error::Result, util::BASE64_ENGINE}; @@ -61,8 +61,11 @@ fn make_user_key( rng.fill(&mut user_key); let stretched_key = stretch_master_key(master_key)?; - let protected = - encrypt_aes256_hmac(&user_key, stretched_key.mac_key.unwrap(), stretched_key.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)) diff --git a/crates/bitwarden/src/crypto/rsa.rs b/crates/bitwarden/src/crypto/rsa.rs index 0d2d135b9..f105dbfe2 100644 --- a/crates/bitwarden/src/crypto/rsa.rs +++ b/crates/bitwarden/src/crypto/rsa.rs @@ -5,7 +5,7 @@ use rsa::{ }; use crate::{ - crypto::{encrypt_aes256_hmac, EncString, SymmetricCryptoKey}, + crypto::{EncString, SymmetricCryptoKey}, error::{Error, Result}, util::BASE64_ENGINE, }; @@ -33,7 +33,7 @@ pub(super) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { .to_pkcs8_der() .map_err(|_| Error::Internal("unable to create private key"))?; - let protected = encrypt_aes256_hmac(pkcs.as_bytes(), key.mac_key.unwrap(), key.key)?; + let protected = EncString::encrypt_aes256_hmac(pkcs.as_bytes(), key.mac_key.unwrap(), key.key)?; Ok(RsaKeyPair { public: b64, From 24f0dfdbb09dbc5ff0ca879b89948d7be7d7ae1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 23 Nov 2023 12:49:46 +0100 Subject: [PATCH 02/27] [PM-4695] Implement unlock with user key and update examples to use biometrics (#330) ``` - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Enable the SDK to export the decrypted user key and expose a way to initialize the SDK with it. The Android and iOS examples have been revamped to allow choosing how to unlock the client. Some small notes: - The iOS example is directly storing the user key in a biometric protected keychain, while that doesn't seem possible in Android. Instead, we generate a key in the secure keystore and use it to encrypt/decrypt the user key. - The iOS example biometrics don't seem to work on the simulator and require a real device for testing. This depends on the refactor done on #329 --- crates/bitwarden-uniffi/src/crypto.rs | 13 + crates/bitwarden/src/client/client.rs | 17 + .../src/client/encryption_settings.rs | 38 +- crates/bitwarden/src/mobile/client_crypto.rs | 8 +- crates/bitwarden/src/mobile/crypto.rs | 24 +- languages/kotlin/app/build.gradle | 1 + .../com/bitwarden/myapplication/Biometrics.kt | 132 ++++++ .../bitwarden/myapplication/MainActivity.kt | 369 +++++++++++----- languages/kotlin/doc.md | 32 ++ .../swift/iOS/App.xcodeproj/project.pbxproj | 12 +- languages/swift/iOS/App/Biometrics.swift | 92 ++++ languages/swift/iOS/App/ContentView.swift | 405 +++++++++++------- 12 files changed, 848 insertions(+), 295 deletions(-) create mode 100644 languages/kotlin/app/src/main/java/com/bitwarden/myapplication/Biometrics.kt create mode 100644 languages/swift/iOS/App/Biometrics.swift diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index 1792a5bbc..804202e2f 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -32,4 +32,17 @@ impl ClientCrypto { .initialize_org_crypto(req) .await?) } + + /// Get the uses's decrypted encryption key. Note: It's very important + /// to keep this key safe, as it can be used to decrypt all of the user's data + pub async fn get_user_encryption_key(&self) -> Result { + Ok(self + .0 + .0 + .write() + .await + .crypto() + .get_user_encryption_key() + .await?) + } } diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 213a12fb2..960ca9bda 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -222,6 +222,23 @@ impl Client { Ok(self.encryption_settings.as_ref().unwrap()) } + #[cfg(feature = "mobile")] + pub(crate) fn initialize_user_crypto_decrypted_key( + &mut self, + decrypted_user_key: &str, + private_key: EncString, + ) -> Result<&EncryptionSettings> { + let user_key = decrypted_user_key.parse::()?; + self.encryption_settings = Some(EncryptionSettings::new_decrypted_key( + user_key, + private_key, + )?); + Ok(self + .encryption_settings + .as_ref() + .expect("It was initialized on the previous line")) + } + pub(crate) fn initialize_crypto_single_key( &mut self, key: SymmetricCryptoKey, diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index 6533c7d2e..7c16725e7 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -27,6 +27,7 @@ impl std::fmt::Debug for EncryptionSettings { } impl EncryptionSettings { + /// Initialize the encryption settings with the user password and their encrypted keys #[cfg(feature = "internal")] pub(crate) fn new( login_method: &UserLoginMethod, @@ -45,24 +46,33 @@ impl EncryptionSettings { // Decrypt the user key let user_key = master_key.decrypt_user_key(user_key)?; - // Decrypt the private key with the user key - let private_key = { - let dec: Vec = private_key.decrypt_with_key(&user_key)?; - Some( - rsa::RsaPrivateKey::from_pkcs8_der(&dec) - .map_err(|_| CryptoError::InvalidKey)?, - ) - }; - - Ok(EncryptionSettings { - user_key, - private_key, - org_keys: HashMap::new(), - }) + Self::new_decrypted_key(user_key, private_key) } } } + /// Initialize the encryption settings with the decrypted user key and the encrypted user private key + /// This should only be used when unlocking the vault via biometrics or when the vault is set to lock: "never" + /// Otherwise handling the decrypted user key is dangerous and discouraged + #[cfg(feature = "internal")] + pub(crate) fn new_decrypted_key( + user_key: SymmetricCryptoKey, + private_key: EncString, + ) -> Result { + let private_key = { + let dec: Vec = private_key.decrypt_with_key(&user_key)?; + Some(rsa::RsaPrivateKey::from_pkcs8_der(&dec).map_err(|_| CryptoError::InvalidKey)?) + }; + + Ok(EncryptionSettings { + user_key, + private_key, + org_keys: HashMap::new(), + }) + } + + /// Initialize the encryption settings with only a single decrypted key. + /// This is used only for logging in Secrets Manager with an access token pub(crate) fn new_single_key(key: SymmetricCryptoKey) -> Self { EncryptionSettings { user_key: key, diff --git a/crates/bitwarden/src/mobile/client_crypto.rs b/crates/bitwarden/src/mobile/client_crypto.rs index c0edb1982..118e02726 100644 --- a/crates/bitwarden/src/mobile/client_crypto.rs +++ b/crates/bitwarden/src/mobile/client_crypto.rs @@ -3,7 +3,8 @@ use crate::Client; use crate::{ error::Result, mobile::crypto::{ - initialize_org_crypto, initialize_user_crypto, InitOrgCryptoRequest, InitUserCryptoRequest, + get_user_encryption_key, initialize_org_crypto, initialize_user_crypto, + InitOrgCryptoRequest, InitUserCryptoRequest, }, }; @@ -21,6 +22,11 @@ impl<'a> ClientCrypto<'a> { pub async fn initialize_org_crypto(&mut self, req: InitOrgCryptoRequest) -> Result<()> { initialize_org_crypto(self.client, req).await } + + #[cfg(feature = "internal")] + pub async fn get_user_encryption_key(&mut self) -> Result { + get_user_encryption_key(self.client).await + } } impl<'a> Client { diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index 2cf850279..9c2c8478f 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -3,7 +3,12 @@ use std::collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{client::kdf::Kdf, crypto::EncString, error::Result, Client}; +use crate::{ + client::kdf::Kdf, + crypto::EncString, + error::{Error, Result}, + Client, +}; #[cfg(feature = "internal")] #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -31,6 +36,10 @@ pub enum InitUserCryptoMethod { /// The user's encrypted symmetric crypto key user_key: String, }, + DecryptedKey { + /// The user's decrypted encryption key, obtained using `get_user_encryption_key` + decrypted_user_key: String, + }, } #[cfg(feature = "internal")] @@ -49,6 +58,9 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ let user_key: EncString = user_key.parse()?; 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)?; + } } Ok(()) @@ -69,3 +81,13 @@ pub async fn initialize_org_crypto(client: &mut Client, req: InitOrgCryptoReques client.initialize_org_crypto(organization_keys)?; Ok(()) } + +#[cfg(feature = "internal")] +pub async fn get_user_encryption_key(client: &mut Client) -> Result { + let user_key = client + .get_encryption_settings()? + .get_key(&None) + .ok_or(Error::VaultLocked)?; + + Ok(user_key.to_base64()) +} diff --git a/languages/kotlin/app/build.gradle b/languages/kotlin/app/build.gradle index 709e32889..ef42d43bb 100644 --- a/languages/kotlin/app/build.gradle +++ b/languages/kotlin/app/build.gradle @@ -60,6 +60,7 @@ dependencies { implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-tooling-preview' implementation 'androidx.compose.material3:material3' + implementation "androidx.biometric:biometric:1.1.0" implementation "io.ktor:ktor-client-core:2.3.3" implementation "io.ktor:ktor-client-cio:2.3.3" implementation "io.ktor:ktor-client-content-negotiation:2.3.3" diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/Biometrics.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/Biometrics.kt new file mode 100644 index 000000000..8f8115e8d --- /dev/null +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/Biometrics.kt @@ -0,0 +1,132 @@ +package com.bitwarden.myapplication + +import android.os.Build +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Log +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricManager.Authenticators +import androidx.biometric.BiometricPrompt +import androidx.biometric.BiometricPrompt.CryptoObject +import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import java.security.KeyStore +import java.util.Base64 +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey +import javax.crypto.spec.GCMParameterSpec + +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the biometric unlock functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ + +class Biometric(private var activity: FragmentActivity) { + private var promptInfo: BiometricPrompt.PromptInfo = + BiometricPrompt.PromptInfo.Builder().setTitle("Unlock") + .setSubtitle("Bitwarden biometric unlock") + .setDescription("Confirm biometric to continue").setConfirmationRequired(true) + .setNegativeButtonText("Use account password").build() + + suspend fun encryptString( + keyName: String, plaintext: String, callback: (String, String) -> Unit + ) { + if (canAuthenticate()) { + val cipher = getCipher() + cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(keyName)) + + val bio = createBiometricPrompt { + val ciphertext = it.cipher!!.doFinal(plaintext.toByteArray()) + callback( + String(Base64.getEncoder().encode(ciphertext)), + String(Base64.getEncoder().encode(cipher.iv)) + ) + } + CoroutineScope(Dispatchers.Main).async { + bio.authenticate(promptInfo, CryptoObject(cipher)) + }.await() + } + } + + suspend fun decryptString( + keyName: String, encrypted: String, initializationVector: String, callback: (String) -> Unit + ) { + if (canAuthenticate()) { + val enc = Base64.getDecoder().decode(encrypted) + val iv = Base64.getDecoder().decode(initializationVector) + + val cipher = getCipher() + cipher.init(Cipher.DECRYPT_MODE, getSecretKey(keyName), GCMParameterSpec(128, iv)) + + val bio = createBiometricPrompt { + callback(String(it.cipher!!.doFinal(enc))) + } + + CoroutineScope(Dispatchers.Main).async { + bio.authenticate(promptInfo, CryptoObject(cipher)) + }.await() + } + } + + private fun canAuthenticate() = BiometricManager.from(activity) + .canAuthenticate(Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS + + private fun createBiometricPrompt(processData: (CryptoObject) -> Unit): BiometricPrompt { + return BiometricPrompt(activity, + ContextCompat.getMainExecutor(activity), + object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + Log.e("Biometric", "$errorCode :: $errString") + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + Log.e("Biometric", "Authentication failed for an unknown reason") + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + processData(result.cryptoObject!!) + } + }) + } + + private fun getCipher(): Cipher { + val transform = + "${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_GCM}/${KeyProperties.ENCRYPTION_PADDING_NONE}" + return Cipher.getInstance(transform) + } + + private fun getSecretKey(keyName: String): SecretKey { + // If the SecretKey exists, return it + val keyStore = KeyStore.getInstance("AndroidKeyStore") + keyStore.load(null) + keyStore.getKey(keyName, null)?.let { return it as SecretKey } + + // Otherwise, we generate a new one + val keyGenParams = KeyGenParameterSpec.Builder( + keyName, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ).apply { + setBlockModes(KeyProperties.BLOCK_MODE_GCM) + setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + setKeySize(256) + setUserAuthenticationRequired(true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG) + } + }.build() + + val keyGenerator = + KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") + keyGenerator.init(keyGenParams) + return keyGenerator.generateKey() + } +} 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 13dec7d0e..3c03c6bca 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 @@ -1,15 +1,27 @@ package com.bitwarden.myapplication +import android.content.Context import android.os.Bundle -import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Checkbox +import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.fragment.app.FragmentActivity import com.bitwarden.core.DateTime import com.bitwarden.core.Folder import com.bitwarden.core.InitOrgCryptoRequest @@ -44,151 +56,272 @@ import java.security.cert.X509Certificate import java.util.Base64 import javax.net.ssl.X509TrustManager -class MainActivity : ComponentActivity() { +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the SDK functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ + +const val SERVER_URL = "https://10.0.2.2:8080/" +const val API_URL = SERVER_URL + "api/" +const val IDENTITY_URL = SERVER_URL + "identity/" + +const val EMAIL = "test@bitwarden.com" +const val PASSWORD = "asdfasdfasdf" + +// We should separate keys for each user by appending the user_id +const val BIOMETRIC_KEY = "biometric_key" + +class MainActivity : FragmentActivity() { + private lateinit var biometric: Biometric + private lateinit var client: Client + private lateinit var http: HttpClient + + private var accessToken = "" + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + biometric = Biometric(this) + client = Client(null) + http = httpClient() + + setContent { + MyApplicationTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { - val SERVER_URL = "https://10.0.2.2:8080/" - val API_URL = SERVER_URL + "api/" - val IDENTITY_URL = SERVER_URL + "identity/" + val setupBiometrics = remember { mutableStateOf(true) } + val outputText = remember { mutableStateOf("") } - val EMAIL = "test@bitwarden.com" - val PASSWORD = "asdfasdfasdf" + Row { + Checkbox(checked = setupBiometrics.value, + onCheckedChange = { isChecked -> + setupBiometrics.value = isChecked + }) + Text( + "Setup biometric unlock after login", + modifier = Modifier.align(CenterVertically) + ) + } - GlobalScope.launch { - var client = Client(null) - val http = httpClient() + Button({ + GlobalScope.launch { + clientExamplePassword( + client, http, outputText, setupBiometrics.value + ) + } + }) { + Text("Login with username + password") + } - ///////////////////////////// Get master password hash ///////////////////////////// - @Serializable - data class PreloginRequest(val email: String) + Divider( + color = Color.Black, + thickness = 1.dp, + modifier = Modifier.padding(30.dp) + ) - @Serializable - data class PreloginResponse( - val kdf: UInt, - val kdfIterations: UInt, - val kdfMemory: UInt?, - val kdfParallelism: UInt? - ) + Button({ + GlobalScope.launch { + clientExampleBiometrics(client, http, outputText) + } + }) { + Text("Unlock with biometrics") + } - val prelogin_body = http.post(IDENTITY_URL + "accounts/prelogin") { - contentType(ContentType.Application.Json) - setBody(PreloginRequest(EMAIL)) - }.body() - val kdf = if (prelogin_body.kdf == 0u) { - Kdf.Pbkdf2(prelogin_body.kdfIterations) - } else { - Kdf.Argon2id( - prelogin_body.kdfIterations, - prelogin_body.kdfMemory!!, - prelogin_body.kdfParallelism!! - ) + Button({ + GlobalScope.launch { + client.destroy() + client = Client(null) + outputText.value = "OK" + } + }) { + Text("Lock & reset client") + } + + Text( + "Output: " + outputText.value, + modifier = Modifier.padding(vertical = 10.dp) + ) + } + } } - val masterPasswordHash = client.auth().hashPassword(EMAIL, PASSWORD, kdf) + } + } + + private suspend fun clientExamplePassword( + client: Client, http: HttpClient, outputText: MutableState, setupBiometrics: Boolean + ) { + println("### Logging in with username and password ###") + ///////////////////////////// Get master password hash ///////////////////////////// + @Serializable + data class PreloginRequest(val email: String) - ///////////////////////////// Login ///////////////////////////// + @Serializable + data class PreloginResponse( + val kdf: UInt, val kdfIterations: UInt, val kdfMemory: UInt?, val kdfParallelism: UInt? + ) - @Serializable - data class LoginResponse( - val Key: String, - val PrivateKey: String, - val access_token: String, - val refresh_token: String, + val prelogin_body = http.post(IDENTITY_URL + "accounts/prelogin") { + contentType(ContentType.Application.Json) + setBody(PreloginRequest(EMAIL)) + }.body() + val kdf = if (prelogin_body.kdf == 0u) { + Kdf.Pbkdf2(prelogin_body.kdfIterations) + } else { + Kdf.Argon2id( + prelogin_body.kdfIterations, + prelogin_body.kdfMemory!!, + prelogin_body.kdfParallelism!! ) + } + val masterPasswordHash = client.auth().hashPassword(EMAIL, PASSWORD, kdf) - val loginBody = http.post(IDENTITY_URL + "connect/token") { - contentType(ContentType.Application.Json) - header("Auth-Email", Base64.getEncoder().encodeToString(EMAIL.toByteArray())) - setBody(FormDataContent(Parameters.build { - append("scope", "api offline_access") - append("client_id", "web") - append("deviceType", "12") - append("deviceIdentifier", "0745d426-8dab-484a-9816-4959721d77c7") - append("deviceName", "edge") - - append("grant_type", "password") - append("username", EMAIL) - append("password", masterPasswordHash) - })) - }.body() - - ///////////////////////////// Sync ///////////////////////////// - - val syncBody = http.get(API_URL + "sync?excludeDomains=true") { - bearerAuth(loginBody.access_token) - }.body() - - val folders = (syncBody["folders"] as JsonArray).map { - val o = it as JsonObject - Folder( - (o["id"] as JsonPrimitive).content, - (o["name"] as JsonPrimitive).content, - DateTime.parse( - (o["revisionDate"] as JsonPrimitive).content - ) - ) - } + ///////////////////////////// Login ///////////////////////////// - ///////////////////////////// Initialize crypto ///////////////////////////// - val orgs = ((syncBody["profile"] as JsonObject)["organizations"]) as JsonArray - val orgKeys = HashMap() + @Serializable + data class LoginResponse( + val Key: String, + val PrivateKey: String, + val access_token: String, + val refresh_token: String, + ) - for (org in orgs) { - val o = org as JsonObject - orgKeys[(o["id"] as JsonPrimitive).content] = (o["key"] as JsonPrimitive).content - } + val loginBody = http.post(IDENTITY_URL + "connect/token") { + contentType(ContentType.Application.Json) + header("Auth-Email", Base64.getEncoder().encodeToString(EMAIL.toByteArray())) + setBody(FormDataContent(Parameters.build { + append("scope", "api offline_access") + append("client_id", "web") + append("deviceType", "12") + append("deviceIdentifier", "0745d426-8dab-484a-9816-4959721d77c7") + append("deviceName", "edge") - client.crypto().initializeUserCrypto( - InitUserCryptoRequest( - kdfParams = kdf, - email = EMAIL, - privateKey = loginBody.PrivateKey, - method = InitUserCryptoMethod.Password( - password = PASSWORD, - userKey = loginBody.Key - ) - ) - ) + append("grant_type", "password") + append("username", EMAIL) + append("password", masterPasswordHash) + })) + }.body() - client.crypto().initializeOrgCrypto( - InitOrgCryptoRequest( - organizationKeys = orgKeys + client.crypto().initializeUserCrypto( + InitUserCryptoRequest( + kdfParams = kdf, + email = EMAIL, + privateKey = loginBody.PrivateKey, + method = InitUserCryptoMethod.Password( + password = PASSWORD, userKey = loginBody.Key ) ) + ) - ///////////////////////////// Decrypt some folders ///////////////////////////// + accessToken = loginBody.access_token - val decryptedFolders = client.vault().folders().decryptList(folders) + decryptVault(client, http, outputText) - println(decryptedFolders) + if (setupBiometrics) { + // Save values for future logins + 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()) - setContent { - MyApplicationTheme { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background - ) { - Greeting("Hey") + // TODO: This should be protected by Android's secure KeyStore + val decryptedKey = client.crypto().getUserEncryptionKey() + + biometric.encryptString(BIOMETRIC_KEY, decryptedKey) { key, iv -> + putString("encryptedUserKey", key) + putString("encryptedUserKeyIv", iv) + apply() } } } } -} -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", modifier = modifier - ) -} + private suspend fun clientExampleBiometrics( + client: Client, http: HttpClient, outputText: MutableState + ) { + println("### Unlocking with Biometrics ###") + + 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 encryptedUserKey = pref.getString("encryptedUserKey", "")!! + val encryptedUserKeyIv = pref.getString("encryptedUserKeyIv", "")!! + + biometric.decryptString( + BIOMETRIC_KEY, encryptedUserKey, encryptedUserKeyIv + ) { key -> + GlobalScope.launch { + client.crypto().initializeUserCrypto( + InitUserCryptoRequest( + kdfParams = kdf, + email = EMAIL, + privateKey = privateKey!!, + method = InitUserCryptoMethod.DecryptedKey(decryptedUserKey = key) + ) + ) + + decryptVault(client, http, outputText) + } + } + } + + suspend fun decryptVault(client: Client, http: HttpClient, outputText: MutableState) { + ///////////////////////////// Sync ///////////////////////////// + + val syncBody = http.get(API_URL + "sync?excludeDomains=true") { + bearerAuth(accessToken) + }.body() + + val folders = (syncBody["folders"] as JsonArray).map { + val o = it as JsonObject + Folder( + (o["id"] as JsonPrimitive).content, + (o["name"] as JsonPrimitive).content, + DateTime.parse( + (o["revisionDate"] as JsonPrimitive).content + ) + ) + } + + ///////////////////////////// Initialize org crypto ///////////////////////////// + val orgs = ((syncBody["profile"] as JsonObject)["organizations"]) as JsonArray + val orgKeys = HashMap() + + for (org in orgs) { + val o = org as JsonObject + orgKeys[(o["id"] as JsonPrimitive).content] = (o["key"] as JsonPrimitive).content + } + + client.crypto().initializeOrgCrypto(InitOrgCryptoRequest(organizationKeys = orgKeys)) + + ///////////////////////////// Decrypt some folders ///////////////////////////// -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - MyApplicationTheme { - Greeting("Sdk") + val decryptedFolders = client.vault().folders().decryptList(folders) + outputText.value = decryptedFolders.toString() + println(decryptedFolders) } } diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index 70446bd8f..fd65c7b71 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -205,6 +205,17 @@ Initialization method for the organization crypto. Needs to be called after **Output**: std::result::Result<,BitwardenError> +### `get_user_encryption_key` + +Get the uses's decrypted encryption key. Note: It's very important to keep this key safe, +as it can be used to decrypt all of the user's data + +**Arguments**: + +- self: + +**Output**: std::result::Result + ## ClientExporters ### `export_vault` @@ -906,6 +917,27 @@ implementations. + + decryptedKey + object + + + + + + + + + + + + + + + +
KeyTypeDescription
decrypted_user_keystringThe user's decrypted encryption key, obtained using `get_user_encryption_key`
+ + ## `InitUserCryptoRequest` diff --git a/languages/swift/iOS/App.xcodeproj/project.pbxproj b/languages/swift/iOS/App.xcodeproj/project.pbxproj index 003c09c0d..e8b3519fd 100644 --- a/languages/swift/iOS/App.xcodeproj/project.pbxproj +++ b/languages/swift/iOS/App.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 33F2B0502B0511C700E1E91C /* Biometrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33F2B04F2B0511C700E1E91C /* Biometrics.swift */; }; 55B6153E2A8678B300BE93F4 /* testApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B6153D2A8678B300BE93F4 /* testApp.swift */; }; 55B615402A8678B300BE93F4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B6153F2A8678B300BE93F4 /* ContentView.swift */; }; 55B615422A8678B400BE93F4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 55B615412A8678B400BE93F4 /* Assets.xcassets */; }; @@ -15,6 +16,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 33F2B04F2B0511C700E1E91C /* Biometrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Biometrics.swift; sourceTree = ""; }; 55B6153A2A8678B300BE93F4 /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55B6153D2A8678B300BE93F4 /* testApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testApp.swift; sourceTree = ""; }; 55B6153F2A8678B300BE93F4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -60,6 +62,7 @@ 55B6153F2A8678B300BE93F4 /* ContentView.swift */, 55B615412A8678B400BE93F4 /* Assets.xcassets */, 55B615432A8678B400BE93F4 /* Preview Content */, + 33F2B04F2B0511C700E1E91C /* Biometrics.swift */, ); path = App; sourceTree = ""; @@ -118,7 +121,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1420; + LastUpgradeCheck = 1500; TargetAttributes = { 55B615392A8678B300BE93F4 = { CreatedOnToolsVersion = 14.2; @@ -160,6 +163,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 33F2B0502B0511C700E1E91C /* Biometrics.swift in Sources */, 55B615402A8678B300BE93F4 /* ContentView.swift in Sources */, 55B6153E2A8678B300BE93F4 /* testApp.swift in Sources */, ); @@ -172,6 +176,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -204,6 +209,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -232,6 +238,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -264,6 +271,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -293,6 +301,7 @@ DEVELOPMENT_TEAM = LTZ2PFU5D6; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock vault with biometrics"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -323,6 +332,7 @@ DEVELOPMENT_TEAM = LTZ2PFU5D6; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock vault with biometrics"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/languages/swift/iOS/App/Biometrics.swift b/languages/swift/iOS/App/Biometrics.swift new file mode 100644 index 000000000..2fc5ef2a3 --- /dev/null +++ b/languages/swift/iOS/App/Biometrics.swift @@ -0,0 +1,92 @@ +// +// Biometrics.swift +// App +// +// Created by Dani on 15/11/23. +// + +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the biometric unlock functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ + +import Foundation +import LocalAuthentication + +let SERVICE: String = "com.example.app" + +// We should separate keys for each user by appending the user_id +let KEY: String = "biometric_key" + + +func biometricStoreValue(value: String) { + var error: Unmanaged? + let accessControl = SecAccessControlCreateWithFlags( + nil, + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + .biometryCurrentSet, + &error) + + guard accessControl != nil && error == nil else { + fatalError("SecAccessControlCreateWithFlags failed") + } + + let query = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: SERVICE, + kSecAttrAccount: KEY, + kSecValueData: value.data(using: .utf8)!, + kSecAttrAccessControl: accessControl as Any + ] as CFDictionary + + // Try to delete the previous secret, if it exists + // Otherwise we get `errSecDuplicateItem` + SecItemDelete(query) + + let status = SecItemAdd(query, nil) + guard status == errSecSuccess else { + fatalError("Unable to store the secret: " + errToString(status: status)) + } +} + +private func errToString(status: OSStatus) -> String { + if let err = SecCopyErrorMessageString(status, nil) as String? { + err + } else { + "Unknown error" + } +} + +func biometricRetrieveValue() -> String? { + let searchQuery = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: SERVICE, + kSecAttrAccount: KEY, + kSecMatchLimit: kSecMatchLimitOne, + kSecReturnData: true, + kSecReturnAttributes: true, + ] as CFDictionary + + var item: AnyObject? + let status = SecItemCopyMatching(searchQuery, &item) + + // If the item is not found, we just return nil + if status == errSecItemNotFound { + return nil + } + + // TODO: We probably want to handle these errors better + guard status == noErr else { + fatalError("Unable to retrieve the secret: " + errToString(status: status)) + } + + if let resultDictionary = item as? [String: Any], + let data = resultDictionary[kSecValueData as String] as? Data { + return String(decoding: data, as: UTF8.self) + } + + return nil +} diff --git a/languages/swift/iOS/App/ContentView.swift b/languages/swift/iOS/App/ContentView.swift index 484caf14f..5df7dc3fd 100644 --- a/languages/swift/iOS/App/ContentView.swift +++ b/languages/swift/iOS/App/ContentView.swift @@ -8,176 +8,261 @@ import BitwardenSdk import SwiftUI -struct ContentView: View { - - @State private var msg: String - - init() { - let client = Client(settings: nil) +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the SDK functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ - _msg = State(initialValue: client.echo(msg: "Sdk")) +let SERVER_URL = "https://localhost:8080/" +let API_URL = SERVER_URL + "api/" +let IDENTITY_URL = SERVER_URL + "identity/" - let SERVER_URL = "https://localhost:8080/" - let API_URL = SERVER_URL + "api/" - let IDENTITY_URL = SERVER_URL + "identity/" - - let EMAIL = "test@bitwarden.com" - let PASSWORD = "asdfasdfasdf" +let EMAIL = "test@bitwarden.com" +let PASSWORD = "asdfasdfasdf" +struct ContentView: View { + private var http: URLSession + + @State private var client: Client + + @State private var accessToken: String = "" + + init() { // Disable SSL Cert validation. Don't do this in production - let ignoreHttpsDelegate = IgnoreHttpsDelegate() - let http = URLSession( - configuration: URLSessionConfiguration.default, delegate: ignoreHttpsDelegate, + http = URLSession( + configuration: URLSessionConfiguration.default, delegate: IgnoreHttpsDelegate(), delegateQueue: nil) - - Task { - - ////////////////////////////// Get master password hash ////////////////////////////// - - struct PreloginRequest: Codable { let email: String } - struct PreloginResponse: Codable { - let kdf: UInt32 - let kdfIterations: UInt32 - let kdfMemory: UInt32? - let kdfParallelism: UInt32? - - } - - let (preloginDataJson, _) = try await http.data( - for: request( - method: "POST", url: IDENTITY_URL + "accounts/prelogin", - fn: { r in - r.setValue("application/json", forHTTPHeaderField: "Content-Type") - r.httpBody = try JSONEncoder().encode(PreloginRequest(email: EMAIL)) - })) - let preloginData = try JSONDecoder().decode( - PreloginResponse.self, from: preloginDataJson) - - let kdf: Kdf - if preloginData.kdf == 0 { - kdf = Kdf.pbkdf2(iterations: preloginData.kdfIterations) - } else { - kdf = Kdf.argon2id( - iterations: preloginData.kdfIterations, memory: preloginData.kdfMemory!, - parallelism: preloginData.kdfParallelism!) - } - - let passwordHash = try await client.auth().hashPassword( - email: EMAIL, password: PASSWORD, kdfParams: kdf) - - ///////////////////////////// Login ///////////////////////////// - - struct LoginResponse: Codable { - let Key: String - let PrivateKey: String - let access_token: String - let refresh_token: String - } - - let (loginDataJson, _) = try await http.data( - for: request( - method: "POST", url: IDENTITY_URL + "connect/token", - fn: { r in - r.setValue( - EMAIL.data(using: .utf8)?.base64EncodedString(), - forHTTPHeaderField: "Auth-Email") - r.setValue( - "application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") - - var comp = URLComponents() - comp.queryItems = [ - URLQueryItem(name: "scope", value: "api offline_access"), - URLQueryItem(name: "client_id", value: "web"), - URLQueryItem(name: "deviceType", value: "12"), - URLQueryItem( - name: "deviceIdentifier", - value: "0745d426-8dab-484a-9816-4959721d77c7"), - URLQueryItem(name: "deviceName", value: "edge"), - URLQueryItem(name: "grant_type", value: "password"), - URLQueryItem(name: "username", value: EMAIL), - URLQueryItem(name: "password", value: passwordHash), - ] - r.httpBody = comp.percentEncodedQuery! - .replacingOccurrences(of: "@", with: "%40") - .replacingOccurrences(of: "+", with: "%2B") - .data(using: .utf8) - })) - let loginData = try JSONDecoder().decode(LoginResponse.self, from: loginDataJson) - - ///////////////////////////// Sync ///////////////////////////// - - struct SyncOrganization: Codable { - let id: String - let key: String - } - struct SyncProfile: Codable { - let organizations: [SyncOrganization] - - } - struct SyncFolder: Codable { - let id: String - let name: String - let revisionDate: String - } - struct SyncResponse: Codable { - let profile: SyncProfile - let folders: [SyncFolder] - } - - let (syncDataJson, _) = try await http.data( - for: request( - method: "GET", url: API_URL + "sync?excludeDomains=true", - fn: { r in - r.setValue( - "Bearer " + loginData.access_token, forHTTPHeaderField: "Authorization") - })) - - let syncData = try JSONDecoder().decode(SyncResponse.self, from: syncDataJson) - - ///////////////////////////// Initialize crypto ///////////////////////////// - - try await client.crypto().initializeUserCrypto( - req: InitUserCryptoRequest( - kdfParams: kdf, - email: EMAIL, - privateKey: loginData.PrivateKey, - method: InitUserCryptoMethod.password( - password: PASSWORD, - userKey: loginData.Key - ) - )) - - try await client.crypto().initializeOrgCrypto( - req: InitOrgCryptoRequest( - organizationKeys: Dictionary.init( - uniqueKeysWithValues: syncData.profile.organizations.map { ($0.id, $0.key) } - ) - )) - - ///////////////////////////// Decrypt some folders ///////////////////////////// - - let dateFormatter = ISO8601DateFormatter() - dateFormatter.formatOptions = [.withFractionalSeconds] - - let decryptedFolders = try await client.vault().folders().decryptList( - folders: syncData.folders.map { - Folder( - id: $0.id, name: $0.name, - revisionDate: dateFormatter.date(from: $0.revisionDate)!) - }) - print(decryptedFolders) - } + + client = Client(settings: nil) } - + + @State var setupBiometrics: Bool = true + @State var outputText: String = "" + var body: some View { VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundColor(.accentColor) - Text("Hello " + msg) + Toggle("Setup biometric unlock after login", isOn: $setupBiometrics).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 decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) + } catch { + print("ERROR:", error) + } + } + }, label: { + Text("Login with username + password") + }) + + Divider().padding(30) + + Button(action: { + Task { + do { + try await clientExampleBiometrics(clientCrypto: client.crypto()) + try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) + } catch { + print("ERROR:", error) + } + } + }, label: { + Text("Unlock with biometrics") + }) + + Button(action: { + client = Client(settings: nil) + }, label: { + Text("Lock & reset client") + }).padding() + + Text("Output: " + outputText).padding(.top) } .padding() } + + func clientExamplePassword(clientAuth: ClientAuthProtocol, clientCrypto: ClientCryptoProtocol, setupBiometrics: Bool) async throws { + ////////////////////////////// Get master password hash ////////////////////////////// + + struct PreloginRequest: Codable { let email: String } + struct PreloginResponse: Codable { + let kdf: UInt32 + let kdfIterations: UInt32 + let kdfMemory: UInt32? + let kdfParallelism: UInt32? + + } + + let (preloginDataJson, _) = try await http.data( + for: request( + method: "POST", url: IDENTITY_URL + "accounts/prelogin", + fn: { r in + r.setValue("application/json", forHTTPHeaderField: "Content-Type") + r.httpBody = try JSONEncoder().encode(PreloginRequest(email: EMAIL)) + })) + let preloginData = try JSONDecoder().decode( + PreloginResponse.self, from: preloginDataJson) + + let kdf: Kdf + if preloginData.kdf == 0 { + kdf = Kdf.pbkdf2(iterations: preloginData.kdfIterations) + } else { + kdf = Kdf.argon2id( + iterations: preloginData.kdfIterations, memory: preloginData.kdfMemory!, + parallelism: preloginData.kdfParallelism!) + } + + let passwordHash = try await clientAuth.hashPassword( + email: EMAIL, password: PASSWORD, kdfParams: kdf) + + ///////////////////////////// Login ///////////////////////////// + + struct LoginResponse: Codable { + let Key: String + let PrivateKey: String + let access_token: String + let refresh_token: String + } + + let (loginDataJson, _) = try await http.data( + for: request( + method: "POST", url: IDENTITY_URL + "connect/token", + fn: { r in + r.setValue( + EMAIL.data(using: .utf8)?.base64EncodedString(), + forHTTPHeaderField: "Auth-Email") + r.setValue( + "application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + + var comp = URLComponents() + comp.queryItems = [ + URLQueryItem(name: "scope", value: "api offline_access"), + URLQueryItem(name: "client_id", value: "web"), + URLQueryItem(name: "deviceType", value: "12"), + URLQueryItem( + name: "deviceIdentifier", + value: "0745d426-8dab-484a-9816-4959721d77c7"), + URLQueryItem(name: "deviceName", value: "edge"), + URLQueryItem(name: "grant_type", value: "password"), + URLQueryItem(name: "username", value: EMAIL), + URLQueryItem(name: "password", value: passwordHash), + ] + r.httpBody = comp.percentEncodedQuery! + .replacingOccurrences(of: "@", with: "%40") + .replacingOccurrences(of: "+", with: "%2B") + .data(using: .utf8) + })) + let loginData = try JSONDecoder().decode(LoginResponse.self, from: loginDataJson) + + try await clientCrypto.initializeUserCrypto( + req: InitUserCryptoRequest( + kdfParams: kdf, + email: EMAIL, + privateKey: loginData.PrivateKey, + method: InitUserCryptoMethod.password( + password: PASSWORD, + userKey: loginData.Key + ) + )) + + accessToken = loginData.access_token + + if (setupBiometrics) { + 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.synchronize() + + let key = try await clientCrypto.getUserEncryptionKey() + biometricStoreValue(value: key) + } + } + + func clientExampleBiometrics(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 key = biometricRetrieveValue()! + + try await clientCrypto.initializeUserCrypto(req: InitUserCryptoRequest( + kdfParams: kdf, + email: EMAIL, + privateKey: privateKey, + method: InitUserCryptoMethod.decryptedKey( + decryptedUserKey: key + ) + )) + } + + func decryptVault(clientCrypto: ClientCryptoProtocol, clientVault: ClientVaultProtocol) async throws { + ///////////////////////////// Sync ///////////////////////////// + + struct SyncOrganization: Codable { + let id: String + let key: String + } + struct SyncProfile: Codable { + let organizations: [SyncOrganization] + + } + struct SyncFolder: Codable { + let id: String + let name: String + let revisionDate: String + } + struct SyncResponse: Codable { + let profile: SyncProfile + let folders: [SyncFolder] + } + + let (syncDataJson, _) = try await http.data( + for: request( + method: "GET", url: API_URL + "sync?excludeDomains=true", + fn: { r in + r.setValue( + "Bearer " + accessToken, forHTTPHeaderField: "Authorization") + })) + + let syncData = try JSONDecoder().decode(SyncResponse.self, from: syncDataJson) + + ///////////////////////////// Initialize org crypto ///////////////////////////// + + try await clientCrypto.initializeOrgCrypto( + req: InitOrgCryptoRequest( + organizationKeys: Dictionary.init( + uniqueKeysWithValues: syncData.profile.organizations.map { ($0.id, $0.key) } + ) + )) + + ///////////////////////////// Decrypt some folders ///////////////////////////// + + let dateFormatter = ISO8601DateFormatter() + dateFormatter.formatOptions = [.withFractionalSeconds] + + let decryptedFolders = try await clientVault.folders().decryptList( + folders: syncData.folders.map { + Folder( + id: $0.id, name: $0.name, + revisionDate: dateFormatter.date(from: $0.revisionDate)!) + }) + print(decryptedFolders) + } } struct ContentView_Previews: PreviewProvider { @@ -202,7 +287,7 @@ extension IgnoreHttpsDelegate: URLSessionDelegate { ) { //Trust the certificate even if not valid let urlCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!) - + completionHandler(.useCredential, urlCredential) } } From 47e1a5de5ee220eda5cff77fb0cad8009a3f3b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 23 Nov 2023 16:38:56 +0100 Subject: [PATCH 03/27] Improve MAC validation (#333) ## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Change the name of the function `validate_mac` to the more appropiate `generate_mac`, and change the mac comparison to be constant time with the use of the `subtle` crate. Note that `hmac.finalize()` returns a type `CtOutput` that supports constant time comparison by using the `subtle` crate internally, but `CtOutput` doesn't allow getting a reference to the internal array which would complicate the implementation of `EncString`, so I chose to use the `subtle` constant time comparison directly. --- Cargo.lock | 1 + crates/bitwarden/Cargo.toml | 1 + crates/bitwarden/src/crypto/aes_ops.rs | 15 ++++++++------- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c040eb279..fb1768bee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,6 +351,7 @@ dependencies = [ "serde_repr", "sha1", "sha2", + "subtle", "thiserror", "tokio", "uniffi", diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index fea9a2fd7..31e39c350 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -42,6 +42,7 @@ pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } argon2 = { version = ">=0.5.0, <0.6", features = [ "alloc", ], default-features = false } +subtle = ">=2.5.0, <3.0" rand = ">=0.8.5, <0.9" num-bigint = ">=0.4, <0.5" num-traits = ">=0.2.15, <0.3" diff --git a/crates/bitwarden/src/crypto/aes_ops.rs b/crates/bitwarden/src/crypto/aes_ops.rs index 3deedabd8..182986c3b 100644 --- a/crates/bitwarden/src/crypto/aes_ops.rs +++ b/crates/bitwarden/src/crypto/aes_ops.rs @@ -13,6 +13,7 @@ use aes::cipher::{ BlockEncryptMut, KeyIvInit, }; use hmac::Mac; +use subtle::ConstantTimeEq; use crate::{ crypto::{PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE}, @@ -47,8 +48,8 @@ pub fn decrypt_aes256_hmac( mac_key: GenericArray, key: GenericArray, ) -> Result> { - let res = validate_mac(&mac_key, iv, &data)?; - if res != *mac { + let res = generate_mac(&mac_key, iv, &data)?; + if res.ct_ne(mac).into() { return Err(CryptoError::InvalidMac.into()); } decrypt_aes256(iv, data, key) @@ -83,7 +84,7 @@ pub fn encrypt_aes256_hmac( ) -> Result<([u8; 16], [u8; 32], Vec)> { let rng = rand::thread_rng(); let (iv, data) = encrypt_aes256_internal(rng, data_dec, key); - let mac = validate_mac(&mac_key, &iv, &data)?; + let mac = generate_mac(&mac_key, &iv, &data)?; Ok((iv, mac, data)) } @@ -106,8 +107,8 @@ fn encrypt_aes256_internal( (iv, data) } -/// Validate a MAC using HMAC-SHA256. -fn validate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> Result<[u8; 32]> { +/// Generate a MAC using HMAC-SHA256. +fn generate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> Result<[u8; 32]> { let mut hmac = PbkdfSha256Hmac::new_from_slice(mac_key).expect("HMAC can take key of any size"); hmac.update(iv); hmac.update(data); @@ -156,13 +157,13 @@ mod tests { } #[test] - fn test_validate_mac() { + fn test_generate_mac() { let mac_key = generate_vec(16, 0, 16); let iv = generate_vec(16, 0, 16); let data = generate_vec(16, 0, 16); - let result = validate_mac(&mac_key, &iv, &data); + let result = generate_mac(&mac_key, &iv, &data); assert!(result.is_ok()); let mac = result.unwrap(); From a026cdaf44025900062a197251281e4a1ff88022 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 23 Nov 2023 17:46:11 +0100 Subject: [PATCH 04/27] Sync renovate config to be inline with other repos (#342) --- .github/renovate.json | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 71e131e43..16222e52c 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -2,44 +2,26 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base", - "schedule:weekends", ":combinePatchMinorReleases", ":dependencyDashboard", ":maintainLockFilesWeekly", ":prConcurrentLimit10", ":rebaseStalePrs", - ":separateMajorReleases" + ":separateMajorReleases", + "group:monorepos", + "schedule:weekends" ], "separateMajorMinor": true, "enabledManagers": ["cargo", "github-actions", "npm", "nuget"], + "commitMessagePrefix": "[deps]:", + "commitMessageTopic": "{{depName}}", "packageRules": [ - { - "groupName": "npm minor", - "matchManagers": ["npm"], - "matchUpdateTypes": ["minor", "patch"] - }, - { - "matchManagers": ["cargo"], - "matchUpdateTypes": ["patch"], - "enabled": false - }, - { - "matchManagers": ["cargo"], - "matchUpdateTypes": ["minor"], - "matchCurrentVersion": ">=1.0.0", - "enabled": false - }, { "matchManagers": ["cargo"], "matchPackagePatterns": ["pyo3*"], "matchUpdateTypes": ["minor", "patch"], "groupName": "pyo3 non-major" }, - { - "groupName": "nuget minor", - "matchManagers": ["nuget"], - "matchUpdateTypes": ["minor", "patch"] - }, { "groupName": "gh minor", "matchManagers": ["github-actions"], From 2f04f8c3223f4dd1ac9a59d76c2dd8a82b7ec3c8 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 23 Nov 2023 17:49:50 +0100 Subject: [PATCH 05/27] Use reqwest header constants (#343) --- crates/bitwarden/src/auth/api/request/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden/src/auth/api/request/mod.rs b/crates/bitwarden/src/auth/api/request/mod.rs index 1277fa969..85b36a983 100644 --- a/crates/bitwarden/src/auth/api/request/mod.rs +++ b/crates/bitwarden/src/auth/api/request/mod.rs @@ -35,10 +35,10 @@ async fn send_identity_connect_request( &configurations.identity.base_path )) .header( - "Content-Type", + reqwest::header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ) - .header("Accept", "application/json") + .header(reqwest::header::ACCEPT, "application/json") .header("Device-Type", configurations.device_type as usize); if let Some(ref user_agent) = configurations.identity.user_agent { From 94dae37ca20279f5c139a6e88f0f3462088c8ba8 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 23 Nov 2023 18:29:15 +0100 Subject: [PATCH 06/27] Sort cargo.toml files (#344) --- crates/bitwarden-c/Cargo.toml | 1 - crates/bitwarden-json/Cargo.toml | 2 +- crates/bitwarden-napi/Cargo.toml | 6 ++-- crates/bitwarden-py/Cargo.toml | 1 - crates/bitwarden-uniffi/Cargo.toml | 2 +- crates/bitwarden-wasm/Cargo.toml | 9 +++--- crates/bitwarden/Cargo.toml | 52 ++++++++++++++---------------- crates/bw/Cargo.toml | 8 ++--- crates/bws/Cargo.toml | 7 ++-- crates/sdk-schemas/Cargo.toml | 4 +-- 10 files changed, 43 insertions(+), 49 deletions(-) diff --git a/crates/bitwarden-c/Cargo.toml b/crates/bitwarden-c/Cargo.toml index 9181bedae..20f3f5229 100644 --- a/crates/bitwarden-c/Cargo.toml +++ b/crates/bitwarden-c/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" edition = "2021" rust-version = "1.57" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["lib", "staticlib", "cdylib"] bench = false diff --git a/crates/bitwarden-json/Cargo.toml b/crates/bitwarden-json/Cargo.toml index 579a8bdab..f7c0fd58d 100644 --- a/crates/bitwarden-json/Cargo.toml +++ b/crates/bitwarden-json/Cargo.toml @@ -18,9 +18,9 @@ internal = ["bitwarden/internal"] # Internal testing methods secrets = ["bitwarden/secrets"] # Secrets manager API [dependencies] +log = ">=0.4.18, <0.5" schemars = ">=0.8.12, <0.9" serde = { version = ">=1.0, <2.0", features = ["derive"] } serde_json = ">=1.0.96, <2.0" -log = ">=0.4.18, <0.5" bitwarden = { path = "../bitwarden" } diff --git a/crates/bitwarden-napi/Cargo.toml b/crates/bitwarden-napi/Cargo.toml index 505b68e57..2e86fefe2 100644 --- a/crates/bitwarden-napi/Cargo.toml +++ b/crates/bitwarden-napi/Cargo.toml @@ -16,10 +16,10 @@ rust-version = "1.57" crate-type = ["cdylib", "rlib"] [dependencies] -napi = {version="2", features=["async"]} -napi-derive = "2" +env_logger = "0.10.0" log = "0.4.18" -env_logger="0.10.0" +napi = { version = "2", features = ["async"] } +napi-derive = "2" bitwarden-json = { path = "../bitwarden-json", version = "0.3.0", features = ["secrets"] } diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml index 613203c84..5c1fa5cac 100644 --- a/crates/bitwarden-py/Cargo.toml +++ b/crates/bitwarden-py/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" edition = "2021" rust-version = "1.57" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] name = "bitwarden_py" crate-type = ["cdylib"] diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index 53da0eb8d..098c5b2bf 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -18,8 +18,8 @@ chrono = { version = ">=0.4.26, <0.5", features = [ "std", ], default-features = false } env_logger = "0.10.0" -uniffi = "=0.25.1" schemars = { version = ">=0.8, <0.9", optional = true } +uniffi = "=0.25.1" bitwarden = { path = "../bitwarden", features = ["mobile", "internal"] } diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index add03f0f3..fcb0c2b15 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -4,18 +4,17 @@ version = "0.1.0" edition = "2021" rust-version = "1.57" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["cdylib", "rlib"] [dependencies] -js-sys = "0.3.63" -serde = {version = "1.0.163", features = ["derive"] } -wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } -wasm-bindgen-futures = "0.4.36" console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } +js-sys = "0.3.63" log = "0.4.18" +serde = { version = "1.0.163", features = ["derive"] } +wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4.36" bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 31e39c350..b0135dfa3 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -20,44 +20,42 @@ internal = [] # Internal testing methods mobile = ["uniffi", "internal"] # Mobile-specific features [dependencies] +aes = ">=0.8.2, <0.9" +argon2 = { version = ">=0.5.0, <0.6", features = [ + "alloc", +], default-features = false } +assert_matches = ">=1.5.0, <2.0" base64 = ">=0.21.2, <0.22" +bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.2" } +bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.2" } +cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] } +chrono = { version = ">=0.4.26, <0.5", features = [ + "serde", + "std", +], default-features = false } +# We don't use this directly (it's used by rand), but we need it here to enable WASM support +getrandom = { version = ">=0.2.9", features = ["js"] } +hkdf = ">=0.12.3, <0.13" +hmac = ">=0.12.1, <0.13" lazy_static = ">=1.4.0, <2.0" +log = ">=0.4.18, <0.5" +num-bigint = ">=0.4, <0.5" +num-traits = ">=0.2.15, <0.3" +pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } +rand = ">=0.8.5, <0.9" reqwest = { version = ">=0.11, <0.12", features = ["json"] } +rsa = ">=0.9.2, <0.10" +schemars = { version = ">=0.8, <0.9", features = ["uuid1", "chrono"] } serde = { version = ">=1.0, <2.0", features = ["derive"] } serde_json = ">=1.0.96, <2.0" serde_qs = ">=0.12.0, <0.13" serde_repr = ">=0.1.12, <0.2" -schemars = { version = ">=0.8, <0.9", features = ["uuid1", "chrono"] } -log = ">=0.4.18, <0.5" -assert_matches = ">=1.5.0, <2.0" -thiserror = ">=1.0.40, <2.0" -aes = ">=0.8.2, <0.9" -cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] } -hkdf = ">=0.12.3, <0.13" -hmac = ">=0.12.1, <0.13" -rsa = ">=0.9.2, <0.10" sha1 = ">=0.10.5, <0.11" sha2 = ">=0.10.6, <0.11" -pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } -argon2 = { version = ">=0.5.0, <0.6", features = [ - "alloc", -], default-features = false } subtle = ">=2.5.0, <3.0" -rand = ">=0.8.5, <0.9" -num-bigint = ">=0.4, <0.5" -num-traits = ">=0.2.15, <0.3" -uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } -chrono = { version = ">=0.4.26, <0.5", features = [ - "serde", - "std", -], default-features = false } +thiserror = ">=1.0.40, <2.0" uniffi = { version = "=0.25.1", optional = true } - -# We don't use this directly (it's used by rand), but we need it here to enable WASM support -getrandom = { version = ">=0.2.9", features = ["js"] } - -bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.2" } -bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.2" } +uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } [dev-dependencies] rand_chacha = "0.3.1" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 0793470b3..349087818 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -12,15 +12,13 @@ Bitwarden Password Manager CLI """ keywords = ["bitwarden", "password-manager", "cli"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] clap = { version = "4.3.0", features = ["derive", "env"] } -tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } -log = "0.4.18" -env_logger = "0.10.0" color-eyre = "0.6" +env_logger = "0.10.0" inquire = "0.6.2" +log = "0.4.18" +tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = [ "internal", diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 9ccd5c546..91d75de69 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -12,8 +12,6 @@ Bitwarden Secrets Manager CLI """ keywords = ["bitwarden", "secrets-manager", "cli"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] bat = { version = "0.24.0", features = [ "regex-onig", @@ -29,6 +27,10 @@ comfy-table = "^7.0.1" directories = "5.0.1" env_logger = "0.10.0" log = "0.4.18" +regex = { version = "1.10.2", features = [ + "std", + "perf", +], default-features = false } serde = "^1.0.163" serde_json = "^1.0.96" serde_yaml = "0.9" @@ -37,7 +39,6 @@ thiserror = "1.0.40" tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } toml = "0.8.0" uuid = { version = "^1.3.3", features = ["serde"] } -regex = { version = "1.10.2", features=["std", "perf"], default-features=false } bitwarden = { path = "../bitwarden", version = "0.3.1", features = ["secrets"] } diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index 64ce34274..86a73cd19 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -12,10 +12,10 @@ internal = [ ] [dependencies] -schemars = { version = "0.8.12", features = ["preserve_order"] } -serde_json = "1.0.96" anyhow = "1.0.71" itertools = "0.12.0" +schemars = { version = "0.8.12", features = ["preserve_order"] } +serde_json = "1.0.96" bitwarden = { path = "../bitwarden" } bitwarden-json = { path = "../bitwarden-json" } From 62c7c1932532eeba68e278c11c9b6c0a2e4de062 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 11:47:53 +0100 Subject: [PATCH 07/27] [deps]: Update Rust crate thiserror to 1.0.50 (#345) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 91d75de69..fe94dfcb2 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -35,7 +35,7 @@ serde = "^1.0.163" serde_json = "^1.0.96" serde_yaml = "0.9" supports-color = "2.0.0" -thiserror = "1.0.40" +thiserror = "1.0.50" tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } toml = "0.8.0" uuid = { version = "^1.3.3", features = ["serde"] } From 03c5d6b57713c0c2a56168e12e1a72f315580794 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 11:48:06 +0100 Subject: [PATCH 08/27] [deps]: Update Rust crate toml to 0.8.8 (#346) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index fe94dfcb2..225c99bcd 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -37,7 +37,7 @@ serde_yaml = "0.9" supports-color = "2.0.0" thiserror = "1.0.50" tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } -toml = "0.8.0" +toml = "0.8.8" uuid = { version = "^1.3.3", features = ["serde"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = ["secrets"] } From 5407db721214d35b7498a46a99c71a05087e4929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Fri, 24 Nov 2023 18:27:01 +0100 Subject: [PATCH 09/27] Fix VSCode tasks (#347) ## Type of change ``` - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective Fix invalid path in VSCode tasks --- .vscode/tasks.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 55dc10eb2..fa97b8fc7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -6,7 +6,7 @@ "command": "build", "problemMatcher": ["$rustc"], "options": { - "cwd": "${workspaceFolder}/bitwarden-c/" + "cwd": "${workspaceFolder}/crates/bitwarden-c/" }, "group": { "kind": "build", @@ -19,7 +19,7 @@ "command": "build", "args": ["--release"], "options": { - "cwd": "${workspaceFolder}/bitwarden-c/" + "cwd": "${workspaceFolder}/crates/bitwarden-c/" }, "problemMatcher": ["$rustc"], "label": "rust: bitwarden-c release build" From 422a9e4cfb5aa7d3e7f022d6a1fc539f709019d4 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 27 Nov 2023 11:15:58 +0100 Subject: [PATCH 10/27] Change all dependencies to be ranges (#357) --- crates/bitwarden-api-api/Cargo.toml | 14 +++++++------- crates/bitwarden-api-identity/Cargo.toml | 14 +++++++------- crates/bitwarden/Cargo.toml | 6 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/bitwarden-api-api/Cargo.toml b/crates/bitwarden-api-api/Cargo.toml index 518cc33ed..2e05088e0 100644 --- a/crates/bitwarden-api-api/Cargo.toml +++ b/crates/bitwarden-api-api/Cargo.toml @@ -13,14 +13,14 @@ categories = ["api-bindings"] edition = "2018" [dependencies] -serde = "^1.0.163" -serde_derive = "^1.0.163" -serde_json = "^1.0.96" -serde_repr = "^0.1.12" -url = "^2.3.1" -uuid = { version = "^1.3.3", features = ["serde"] } +serde = ">=1.0.163, <2" +serde_derive = ">=1.0.163, <2" +serde_json = ">=1.0.96, <2" +serde_repr = ">=0.1.12, <0.2" +url = ">=2.3.1, <3" +uuid = { version = ">=1.3.3, <2", features = ["serde"] } [dependencies.reqwest] -version = "^0.11.18" +version = ">=0.11.18, <0.12" features = ["json", "multipart"] [dev-dependencies] diff --git a/crates/bitwarden-api-identity/Cargo.toml b/crates/bitwarden-api-identity/Cargo.toml index 70be0560b..7207c5f8e 100644 --- a/crates/bitwarden-api-identity/Cargo.toml +++ b/crates/bitwarden-api-identity/Cargo.toml @@ -13,14 +13,14 @@ categories = ["api-bindings"] edition = "2018" [dependencies] -serde = "^1.0.163" -serde_derive = "^1.0.163" -serde_json = "^1.0.96" -serde_repr = "^0.1.12" -url = "^2.3.1" -uuid = { version = "^1.3.3", features = ["serde"] } +serde = ">=1.0.163, <2" +serde_derive = ">=1.0.163, <2" +serde_json = ">=1.0.96, <2" +serde_repr = ">=0.1.12, <0.2" +url = ">=2.3.1, <3" +uuid = { version = ">=1.3.3, <2", features = ["serde"] } [dependencies.reqwest] -version = "^0.11.18" +version = ">=0.11.18, <0.12" features = ["json", "multipart"] [dev-dependencies] diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index b0135dfa3..e52c03723 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -34,7 +34,7 @@ chrono = { version = ">=0.4.26, <0.5", features = [ "std", ], default-features = false } # We don't use this directly (it's used by rand), but we need it here to enable WASM support -getrandom = { version = ">=0.2.9", features = ["js"] } +getrandom = { version = ">=0.2.9, <0.3", features = ["js"] } hkdf = ">=0.12.3, <0.13" hmac = ">=0.12.1, <0.13" lazy_static = ">=1.4.0, <2.0" @@ -59,5 +59,5 @@ uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } [dev-dependencies] rand_chacha = "0.3.1" -tokio = { version = "1.28.2", features = ["rt", "macros"] } -wiremock = "0.5.18" +tokio = { version = "1.34.0", features = ["rt", "macros"] } +wiremock = "0.5.21" From afe290dae16adee7ca543418059f3bcb1f1e85e2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:16:24 +0100 Subject: [PATCH 11/27] [deps]: Update Rust crate log to 0.4.20 (#352) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-napi/Cargo.toml | 2 +- crates/bitwarden-wasm/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-napi/Cargo.toml b/crates/bitwarden-napi/Cargo.toml index 2e86fefe2..5139d17ca 100644 --- a/crates/bitwarden-napi/Cargo.toml +++ b/crates/bitwarden-napi/Cargo.toml @@ -17,7 +17,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] env_logger = "0.10.0" -log = "0.4.18" +log = "0.4.20" napi = { version = "2", features = ["async"] } napi-derive = "2" diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index fcb0c2b15..0c8a60c56 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"] console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } js-sys = "0.3.63" -log = "0.4.18" +log = "0.4.20" serde = { version = "1.0.163", features = ["derive"] } wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4.36" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 349087818..85df34109 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -17,7 +17,7 @@ clap = { version = "4.3.0", features = ["derive", "env"] } color-eyre = "0.6" env_logger = "0.10.0" inquire = "0.6.2" -log = "0.4.18" +log = "0.4.20" tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = [ diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 225c99bcd..a35d7f642 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -26,7 +26,7 @@ color-eyre = "0.6" comfy-table = "^7.0.1" directories = "5.0.1" env_logger = "0.10.0" -log = "0.4.18" +log = "0.4.20" regex = { version = "1.10.2", features = [ "std", "perf", From 8815cfd1ddcd0f32c339c7bbd5ff6d9d96ceffbe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 10:17:17 +0000 Subject: [PATCH 12/27] [deps]: Update Rust crate anyhow to 1.0.75 (#348) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/sdk-schemas/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index 86a73cd19..017d74a10 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -12,7 +12,7 @@ internal = [ ] [dependencies] -anyhow = "1.0.71" +anyhow = "1.0.75" itertools = "0.12.0" schemars = { version = "0.8.12", features = ["preserve_order"] } serde_json = "1.0.96" From 736f2d03780cf7feb2aef36b4b928eef849dc888 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 10:17:33 +0000 Subject: [PATCH 13/27] [deps]: Update Rust crate chrono to 0.4.31 (#349) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index a35d7f642..ebf917ab1 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -16,7 +16,7 @@ keywords = ["bitwarden", "secrets-manager", "cli"] bat = { version = "0.24.0", features = [ "regex-onig", ], default-features = false } -chrono = { version = "0.4.26", features = [ +chrono = { version = "0.4.31", features = [ "clock", "std", ], default-features = false } From 8073952c0c5c5376832bb34624462a9df3abce40 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:27:22 +0100 Subject: [PATCH 14/27] [deps]: Update Rust crate env_logger to 0.10.1 (#350) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-napi/Cargo.toml | 2 +- crates/bitwarden-uniffi/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-napi/Cargo.toml b/crates/bitwarden-napi/Cargo.toml index 5139d17ca..8ab71c56d 100644 --- a/crates/bitwarden-napi/Cargo.toml +++ b/crates/bitwarden-napi/Cargo.toml @@ -16,7 +16,7 @@ rust-version = "1.57" crate-type = ["cdylib", "rlib"] [dependencies] -env_logger = "0.10.0" +env_logger = "0.10.1" log = "0.4.20" napi = { version = "2", features = ["async"] } napi-derive = "2" diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index 098c5b2bf..474f5f299 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -17,7 +17,7 @@ chrono = { version = ">=0.4.26, <0.5", features = [ "serde", "std", ], default-features = false } -env_logger = "0.10.0" +env_logger = "0.10.1" schemars = { version = ">=0.8, <0.9", optional = true } uniffi = "=0.25.1" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 85df34109..244ecf51e 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -15,7 +15,7 @@ keywords = ["bitwarden", "password-manager", "cli"] [dependencies] clap = { version = "4.3.0", features = ["derive", "env"] } color-eyre = "0.6" -env_logger = "0.10.0" +env_logger = "0.10.1" inquire = "0.6.2" log = "0.4.20" tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index ebf917ab1..bf91adda8 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -25,7 +25,7 @@ clap_complete = "4.3.2" color-eyre = "0.6" comfy-table = "^7.0.1" directories = "5.0.1" -env_logger = "0.10.0" +env_logger = "0.10.1" log = "0.4.20" regex = { version = "1.10.2", features = [ "std", From 9b752bafcd5a32c0ceb671f435d7355e71180e93 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:27:33 +0100 Subject: [PATCH 15/27] [deps]: Update Rust crate schemars to 0.8.16 (#354) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/sdk-schemas/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index 017d74a10..f3e70c04c 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -14,7 +14,7 @@ internal = [ [dependencies] anyhow = "1.0.75" itertools = "0.12.0" -schemars = { version = "0.8.12", features = ["preserve_order"] } +schemars = { version = "0.8.16", features = ["preserve_order"] } serde_json = "1.0.96" bitwarden = { path = "../bitwarden" } From b88ce1341cfbb683b006046a8bd25d434439a74a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:33:35 +0100 Subject: [PATCH 16/27] [deps]: Update Rust crate tokio to 1.34.0 (#358) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-py/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml index 5c1fa5cac..94e9e6a43 100644 --- a/crates/bitwarden-py/Cargo.toml +++ b/crates/bitwarden-py/Cargo.toml @@ -18,7 +18,7 @@ bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } pyo3-build-config = { version = "0.20.0" } [target.'cfg(not(target_arch="wasm32"))'.dependencies] -tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } pyo3-asyncio = { version = "0.20.0", features = [ "attributes", "tokio-runtime", diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 244ecf51e..4659caac8 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -18,7 +18,7 @@ color-eyre = "0.6" env_logger = "0.10.1" inquire = "0.6.2" log = "0.4.20" -tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = [ "internal", diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index bf91adda8..649015b10 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -36,7 +36,7 @@ serde_json = "^1.0.96" serde_yaml = "0.9" supports-color = "2.0.0" thiserror = "1.0.50" -tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } toml = "0.8.8" uuid = { version = "^1.3.3", features = ["serde"] } From 6f84712b7a611c3aeaf2ce7c1cc5e0f843391770 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:34:58 +0100 Subject: [PATCH 17/27] [deps]: Update Rust crate clap to 4.4.8 (#359) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-cli/Cargo.toml | 2 +- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-cli/Cargo.toml b/crates/bitwarden-cli/Cargo.toml index 606e24856..bedf57e58 100644 --- a/crates/bitwarden-cli/Cargo.toml +++ b/crates/bitwarden-cli/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" rust-version = "1.57" [dependencies] -clap = { version = "4.3.0", features = ["derive"] } +clap = { version = "4.4.8", features = ["derive"] } color-eyre = "0.6" inquire = "0.6.2" supports-color = "2.0.0" diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 4659caac8..4c2d6b012 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -13,7 +13,7 @@ Bitwarden Password Manager CLI keywords = ["bitwarden", "password-manager", "cli"] [dependencies] -clap = { version = "4.3.0", features = ["derive", "env"] } +clap = { version = "4.4.8", features = ["derive", "env"] } color-eyre = "0.6" env_logger = "0.10.1" inquire = "0.6.2" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 649015b10..770f5f6db 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -20,7 +20,7 @@ chrono = { version = "0.4.31", features = [ "clock", "std", ], default-features = false } -clap = { version = "4.3.0", features = ["derive", "env", "string"] } +clap = { version = "4.4.8", features = ["derive", "env", "string"] } clap_complete = "4.3.2" color-eyre = "0.6" comfy-table = "^7.0.1" From a2597217349c3fac94ed8606fdfdee2bcfa091fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:35:22 +0100 Subject: [PATCH 18/27] [deps]: Update Rust crate tempfile to 3.8.1 (#362) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bw/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 4c2d6b012..9ceb2b153 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -27,4 +27,4 @@ bitwarden = { path = "../bitwarden", version = "0.3.1", features = [ bitwarden-cli = { path = "../bitwarden-cli", version = "0.1.0" } [dev-dependencies] -tempfile = "3.5.0" +tempfile = "3.8.1" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 770f5f6db..c8afd73e4 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -43,7 +43,7 @@ uuid = { version = "^1.3.3", features = ["serde"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = ["secrets"] } [dev-dependencies] -tempfile = "3.5.0" +tempfile = "3.8.1" [target.'cfg(target_os = "linux")'.dependencies] openssl = { version = "0.10", features = ["vendored"] } From 78c5fa23174902dd33eaccf12cb25d076e560758 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 10:36:13 +0000 Subject: [PATCH 19/27] [deps]: Update Rust crate serde_json to v1.0.108 (#355) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- crates/sdk-schemas/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index c8afd73e4..2a1561b8e 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -32,7 +32,7 @@ regex = { version = "1.10.2", features = [ "perf", ], default-features = false } serde = "^1.0.163" -serde_json = "^1.0.96" +serde_json = "^1.0.108" serde_yaml = "0.9" supports-color = "2.0.0" thiserror = "1.0.50" diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index f3e70c04c..2e7706200 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -15,7 +15,7 @@ internal = [ anyhow = "1.0.75" itertools = "0.12.0" schemars = { version = "0.8.16", features = ["preserve_order"] } -serde_json = "1.0.96" +serde_json = "1.0.108" bitwarden = { path = "../bitwarden" } bitwarden-json = { path = "../bitwarden-json" } From 520608dbe0e4fa245cd9414d3b909295195e85d3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:36:36 +0100 Subject: [PATCH 20/27] [deps]: Update Rust crate supports-color to 2.1.0 (#361) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-cli/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-cli/Cargo.toml b/crates/bitwarden-cli/Cargo.toml index bedf57e58..0966f7885 100644 --- a/crates/bitwarden-cli/Cargo.toml +++ b/crates/bitwarden-cli/Cargo.toml @@ -8,4 +8,4 @@ rust-version = "1.57" clap = { version = "4.4.8", features = ["derive"] } color-eyre = "0.6" inquire = "0.6.2" -supports-color = "2.0.0" +supports-color = "2.1.0" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 2a1561b8e..32a189988 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -34,7 +34,7 @@ regex = { version = "1.10.2", features = [ serde = "^1.0.163" serde_json = "^1.0.108" serde_yaml = "0.9" -supports-color = "2.0.0" +supports-color = "2.1.0" thiserror = "1.0.50" tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } toml = "0.8.8" From 57352eb3bf13a445da6f0dfe297748be8d5f0393 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:40:15 +0100 Subject: [PATCH 21/27] [deps]: Update Rust crate clap_complete to 4.4.4 (#360) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 32a189988..39cf92a85 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -21,7 +21,7 @@ chrono = { version = "0.4.31", features = [ "std", ], default-features = false } clap = { version = "4.4.8", features = ["derive", "env", "string"] } -clap_complete = "4.3.2" +clap_complete = "4.4.4" color-eyre = "0.6" comfy-table = "^7.0.1" directories = "5.0.1" From 7a216991aefdec3e2e6b02f38436eb8c68dae91f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:42:15 +0100 Subject: [PATCH 22/27] [deps]: Update Rust crate uuid to ^1.6.1 (#365) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/bws/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb1768bee..232b823b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3618,9 +3618,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58fe91d841bc04822c9801002db4ea904b9e4b8e6bbad25127b46eff8dc516b" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ "serde", ] diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 39cf92a85..3580446b9 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -38,7 +38,7 @@ supports-color = "2.1.0" thiserror = "1.0.50" tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] } toml = "0.8.8" -uuid = { version = "^1.3.3", features = ["serde"] } +uuid = { version = "^1.6.1", features = ["serde"] } bitwarden = { path = "../bitwarden", version = "0.3.1", features = ["secrets"] } From cd93d42ae3bd82b353005d5b1af28f047da769a7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:42:47 +0100 Subject: [PATCH 23/27] [deps]: Update Rust crate comfy-table to ^7.1.0 (#363) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bws/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 3580446b9..c0dfc7bac 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -23,7 +23,7 @@ chrono = { version = "0.4.31", features = [ clap = { version = "4.4.8", features = ["derive", "env", "string"] } clap_complete = "4.4.4" color-eyre = "0.6" -comfy-table = "^7.0.1" +comfy-table = "^7.1.0" directories = "5.0.1" env_logger = "0.10.1" log = "0.4.20" From b9ca9e5928441beb98d3ad98ee2b2a66a0d0faf7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:38:14 +0100 Subject: [PATCH 24/27] [deps]: Update Rust crate napi-build to 2.1.0 (#364) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- crates/bitwarden-napi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-napi/Cargo.toml b/crates/bitwarden-napi/Cargo.toml index 8ab71c56d..63eae2b8b 100644 --- a/crates/bitwarden-napi/Cargo.toml +++ b/crates/bitwarden-napi/Cargo.toml @@ -24,7 +24,7 @@ napi-derive = "2" bitwarden-json = { path = "../bitwarden-json", version = "0.3.0", features = ["secrets"] } [build-dependencies] -napi-build = "2.0.1" +napi-build = "2.1.0" [profile.release] lto = true From 2c01cd3ff4d2fca565e14dfa479e44ee3d68cce7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:07:30 +0100 Subject: [PATCH 25/27] [deps]: Update Rust crate serde to v1.0.193 (#367) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- crates/bitwarden-wasm/Cargo.toml | 2 +- crates/bws/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 232b823b4..ff741ead5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2778,18 +2778,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index 0c8a60c56..7ad0d1c3e 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -12,7 +12,7 @@ console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } js-sys = "0.3.63" log = "0.4.20" -serde = { version = "1.0.163", features = ["derive"] } +serde = { version = "1.0.193", features = ["derive"] } wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4.36" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index c0dfc7bac..95dbef003 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -31,7 +31,7 @@ regex = { version = "1.10.2", features = [ "std", "perf", ], default-features = false } -serde = "^1.0.163" +serde = "^1.0.193" serde_json = "^1.0.108" serde_yaml = "0.9" supports-color = "2.1.0" From af31957425d007926f0ec72a9ae1b5c434244f5d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:07:56 +0100 Subject: [PATCH 26/27] [deps]: Update Rust crate async-lock to 3.1.2 (#368) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 16 ++++++++-------- crates/bitwarden-uniffi/Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff741ead5..2a4d3b6aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,11 +203,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.1.1" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655b9c7fe787d3b25cc0f804a1a8401790f0c5bc395beb5a64dc77d8de079105" +checksum = "dea8b3453dd7cc96711834b75400d671b73e3656975fa68d9f277163b7f7e316" dependencies = [ - "event-listener 3.1.0", + "event-listener 4.0.0", "event-listener-strategy", "pin-project-lite", ] @@ -1065,9 +1065,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "3.1.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae" dependencies = [ "concurrent-queue", "parking", @@ -1076,11 +1076,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" dependencies = [ - "event-listener 3.1.0", + "event-listener 4.0.0", "pin-project-lite", ] diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index 474f5f299..405642d71 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -12,7 +12,7 @@ crate-type = ["lib", "staticlib", "cdylib"] bench = false [dependencies] -async-lock = "3.0.0" +async-lock = "3.1.2" chrono = { version = ">=0.4.26, <0.5", features = [ "serde", "std", From 92a67b194e59ba445531abbdd76971c250d56f3e Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Mon, 27 Nov 2023 11:00:44 -0500 Subject: [PATCH 27/27] SM-874: Fix Python Integration (#229) ## Type of change ``` - [x] Bug fix - [ ] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective The Python integration is out of date and is not currently working. This PR fixes the Python integration by adding the ability to log in via access token, which is currently the supported way to interact with the Bitwarden Secrets Manager SDK. This PR also adds a `ProjectsClient` to `bitwarden_client.py` for easy use, and updates the error handling on Project or Secret deletes in the SDK. ## Code changes - **crates/sdk-schemas/src/main.rs:** We need the `AccessTokenLoginResponse` struct to be generated so access token auth is available to integrations - **languages/python/BitwardenClient/bitwarden_client.py:** - Import the following from `.schemas`: - `AccessTokenLoginRequest` - `AccessTokenLoginResponse` - `ResponseForAccessTokenLoginResponse` - Define a new function `access_token_login` to support authenticating with an access token - Add the `project_ids` parameter to the `create` and `update` functions for `SecretsClient` - Add a `ProjectsClient` class and `projects()` to the `BitwardenClient` - Removed the password login methods, as they are not supported - Removed the imports that are not needed, and add one that are (like `sys`, etc.) - **languages/python/README.md:** Update the readme instructions, give more examples - **languages/python/login.py:** Renamed `login.py` to `example.py` - **languages/python/example.py:** Update the example to showcase auth with an access token ## Before you submit - Please add **unit tests** where it makes sense to do so (encouraged but not required) --- crates/sdk-schemas/src/main.rs | 1 + .../BitwardenClient/bitwarden_client.py | 90 +++++++++++++------ languages/python/README.md | 6 +- languages/python/example.py | 44 +++++++++ languages/python/login.py | 24 ----- 5 files changed, 115 insertions(+), 50 deletions(-) create mode 100644 languages/python/example.py delete mode 100644 languages/python/login.py diff --git a/crates/sdk-schemas/src/main.rs b/crates/sdk-schemas/src/main.rs index 9d7e52a47..ef7a2db83 100644 --- a/crates/sdk-schemas/src/main.rs +++ b/crates/sdk-schemas/src/main.rs @@ -99,6 +99,7 @@ fn main() -> Result<()> { write_schema_for_response! { bitwarden::auth::login::ApiKeyLoginResponse, bitwarden::auth::login::PasswordLoginResponse, + bitwarden::auth::login::AccessTokenLoginResponse, bitwarden::secrets_manager::secrets::SecretIdentifiersResponse, bitwarden::secrets_manager::secrets::SecretResponse, bitwarden::secrets_manager::secrets::SecretsResponse, diff --git a/languages/python/BitwardenClient/bitwarden_client.py b/languages/python/BitwardenClient/bitwarden_client.py index 6a0a77ed8..cb669a40e 100644 --- a/languages/python/BitwardenClient/bitwarden_client.py +++ b/languages/python/BitwardenClient/bitwarden_client.py @@ -1,8 +1,8 @@ import json -from typing import Any, List +from typing import Any, List, Optional +from uuid import UUID import bitwarden_py -from .schemas import ClientSettings, Command, PasswordLoginRequest, PasswordLoginResponse, ResponseForPasswordLoginResponse, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, ResponseForSecretsDeleteResponse, ResponseForSyncResponse, ResponseForUserAPIKeyResponse, SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretVerificationRequest, SecretsCommand, SecretsDeleteRequest, SecretsDeleteResponse, SyncRequest, SyncResponse, UserAPIKeyResponse - +from .schemas import ClientSettings, Command, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, ResponseForSecretsDeleteResponse, SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretsCommand, SecretsDeleteRequest, SecretsDeleteResponse, AccessTokenLoginRequest, AccessTokenLoginResponse, ResponseForAccessTokenLoginResponse, ResponseForProjectResponse, ProjectsCommand, ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsListRequest, ResponseForProjectsResponse, ResponseForProjectsDeleteResponse, ProjectsDeleteRequest class BitwardenClient: def __init__(self, settings: ClientSettings = None): @@ -12,32 +12,25 @@ def __init__(self, settings: ClientSettings = None): settings_json = json.dumps(settings.to_dict()) self.inner = bitwarden_py.BitwardenClient(settings_json) - def password_login(self, email: str, password: str) -> ResponseForPasswordLoginResponse: - result = self._run_command( - Command(password_login=PasswordLoginRequest(email, password)) - ) - return ResponseForPasswordLoginResponse.from_dict(result) - - def get_user_api_key(self, secret: str, is_otp: bool = False) -> ResponseForUserAPIKeyResponse: - result = self._run_command( - Command(get_user_api_key=SecretVerificationRequest( - secret if not is_otp else None, secret if is_otp else None)) + def access_token_login(self, access_token: str): + self._run_command( + Command(access_token_login=AccessTokenLoginRequest(access_token)) ) - return ResponseForUserAPIKeyResponse.from_dict(result) - - def sync(self, exclude_subdomains: bool = False) -> ResponseForSyncResponse: - result = self._run_command( - Command(sync=SyncRequest(exclude_subdomains)) - ) - return ResponseForSyncResponse.from_dict(result) def secrets(self): return SecretsClient(self) + def projects(self): + return ProjectsClient(self) + def _run_command(self, command: Command) -> Any: response_json = self.inner.run_command(json.dumps(command.to_dict())) - return json.loads(response_json) + response = json.loads(response_json) + if response["success"] == False: + raise Exception(response["errorMessage"]) + + return response class SecretsClient: def __init__(self, client: BitwardenClient): @@ -52,10 +45,12 @@ def get(self, id: str) -> ResponseForSecretResponse: def create(self, key: str, note: str, organization_id: str, - value: str) -> ResponseForSecretResponse: + value: str, + project_ids: Optional[List[UUID]] = None + ) -> ResponseForSecretResponse: result = self.client._run_command( Command(secrets=SecretsCommand( - create=SecretCreateRequest(key, note, organization_id, value))) + create=SecretCreateRequest(key, note, organization_id, value, project_ids))) ) return ResponseForSecretResponse.from_dict(result) @@ -70,10 +65,12 @@ def update(self, id: str, key: str, note: str, organization_id: str, - value: str) -> ResponseForSecretResponse: + value: str, + project_ids: Optional[List[UUID]] = None + ) -> ResponseForSecretResponse: result = self.client._run_command( Command(secrets=SecretsCommand(update=SecretPutRequest( - id, key, note, organization_id, value))) + id, key, note, organization_id, value, project_ids))) ) return ResponseForSecretResponse.from_dict(result) @@ -82,3 +79,46 @@ def delete(self, ids: List[str]) -> ResponseForSecretsDeleteResponse: Command(secrets=SecretsCommand(delete=SecretsDeleteRequest(ids))) ) return ResponseForSecretsDeleteResponse.from_dict(result) + +class ProjectsClient: + def __init__(self, client: BitwardenClient): + self.client = client + + def get(self, id: str) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(get=ProjectGetRequest(id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def create(self, + name: str, + organization_id: str, + ) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand( + create=ProjectCreateRequest(name, organization_id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def list(self, organization_id: str) -> ResponseForProjectsResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand( + list=ProjectsListRequest(organization_id))) + ) + return ResponseForProjectsResponse.from_dict(result) + + def update(self, id: str, + name: str, + organization_id: str, + ) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(update=ProjectPutRequest( + id, name, organization_id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def delete(self, ids: List[str]) -> ResponseForProjectsDeleteResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(delete=ProjectsDeleteRequest(ids))) + ) + return ResponseForProjectsDeleteResponse.from_dict(result) diff --git a/languages/python/README.md b/languages/python/README.md index 03a4ce572..871be274f 100644 --- a/languages/python/README.md +++ b/languages/python/README.md @@ -9,6 +9,10 @@ ```bash pip install setuptools_rust ``` +- dateutil + ```bash + pip install python-dateutil + ``` # Installation @@ -18,7 +22,7 @@ From the `languages/python/` directory, python3 ./setup.py develop ``` -Move the the resulting `.so` file to `bitwarden_py.so`, if it isn't already there. +Rename the the resulting `.so` file to `bitwarden_py.so`, if it isn't already there. # Run diff --git a/languages/python/example.py b/languages/python/example.py new file mode 100644 index 000000000..2ee1cb280 --- /dev/null +++ b/languages/python/example.py @@ -0,0 +1,44 @@ +import json +import logging +import sys +from BitwardenClient.bitwarden_client import BitwardenClient +from BitwardenClient.schemas import client_settings_from_dict, DeviceType + +# Create the BitwardenClient, which is used to interact with the SDK +client = BitwardenClient(client_settings_from_dict({ + "apiUrl": "http://localhost:4000", + "deviceType": DeviceType.SDK, + "identityUrl": "http://localhost:33656", + "userAgent": "Python", +})) + +# Add some logging & set the org id +logging.basicConfig(level=logging.DEBUG) +organization_id = "org_id_here" + +# Attempt to authenticate with the Secrets Manager Access Token +client.access_token_login("access_token_here") + +# -- Example Project Commands -- + +project = client.projects().create("ProjectName", organization_id) +project2 = client.projects().create("Project - Don't Delete Me!", organization_id) +updated_project = client.projects().update(project.data.id, "Cool New Project Name", organization_id) +get_that_project = client.projects().get(project.data.id) + +input("Press Enter to delete the project...") +client.projects().delete([project.data.id]) + +print(client.projects().list(organization_id)) + +# -- Example Secret Commands -- + +secret = client.secrets().create("TEST_SECRET", "This is a test secret", organization_id, "Secret1234!", [project2.data.id]) +secret2 = client.secrets().create("Secret - Don't Delete Me!", "This is a test secret that will stay", organization_id, "Secret1234!", [project2.data.id]) +secret_updated = client.secrets().update(secret.data.id, "TEST_SECRET_UPDATED", "This as an updated test secret", organization_id, "Secret1234!_updated", [project2.data.id]) +secret_retrieved = client.secrets().get(secret.data.id) + +input("Press Enter to delete the secret...") +client.secrets().delete([secret.data.id]) + +print(client.secrets().list(organization_id)) diff --git a/languages/python/login.py b/languages/python/login.py deleted file mode 100644 index 9b756a005..000000000 --- a/languages/python/login.py +++ /dev/null @@ -1,24 +0,0 @@ -import json -import logging -from BitwardenClient.bitwarden_client import BitwardenClient -from BitwardenClient.schemas import client_settings_from_dict - -client = BitwardenClient(client_settings_from_dict({ - "api_url": "http://localhost:4000", - "identity_url": "http://localhost:33656", - "user_agent": "Python", -})) - -logging.basicConfig(level=logging.DEBUG) - -result = client.password_login("test@bitwarden.com", "asdfasdf") -print(result) -print(client.get_user_api_key("asdfasdf")) - -sync = client.sync() - -secret = client.secrets().create("TEST_SECRET", "This is a test secret", - sync.data.profile.organizations[0].id, "Secret1234!") -print(secret) - -client.secrets().delete([secret.data.id])