From 8f314413da63492e5d2ab2095ae606fb21f7478a Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Wed, 24 Jan 2024 18:33:43 +0100 Subject: [PATCH] Login with device (#529) Login with device and approve device. --- .../src/enc_string/asymmetric.rs | 9 +- .../src/keys/asymmetric_crypto_key.rs | 104 ++++++++++++- crates/bitwarden-crypto/src/keys/mod.rs | 5 +- crates/bitwarden-crypto/src/rsa.rs | 8 +- crates/bitwarden-uniffi/src/auth/mod.rs | 22 ++- crates/bitwarden-uniffi/src/uniffi_support.rs | 3 +- crates/bitwarden/src/auth/auth_request.rs | 137 ++++++++++++++++++ crates/bitwarden/src/auth/client_auth.rs | 13 +- crates/bitwarden/src/auth/mod.rs | 7 +- crates/bitwarden/src/mobile/crypto.rs | 15 ++ languages/kotlin/doc.md | 48 ++++++ 11 files changed, 349 insertions(+), 22 deletions(-) create mode 100644 crates/bitwarden/src/auth/auth_request.rs diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 953b3d28f..ea969f4f9 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -8,7 +8,7 @@ use super::{from_b64_vec, split_enc_string}; use crate::{ error::{CryptoError, EncStringParseError, Result}, rsa::encrypt_rsa2048_oaep_sha1, - AsymmetricCryptoKey, KeyDecryptable, + AsymmetricCryptoKey, AsymmetricEncryptable, KeyDecryptable, }; /// # Encrypted string primitive @@ -138,11 +138,12 @@ impl serde::Serialize for AsymmetricEncString { } impl AsymmetricEncString { - pub(crate) fn encrypt_rsa2048_oaep_sha1( + /// Encrypt and produce a [AsymmetricEncString::Rsa2048_OaepSha1_B64] variant. + pub fn encrypt_rsa2048_oaep_sha1( data_dec: &[u8], - key: &AsymmetricCryptoKey, + key: &dyn AsymmetricEncryptable, ) -> Result { - let enc = encrypt_rsa2048_oaep_sha1(&key.key, data_dec)?; + let enc = encrypt_rsa2048_oaep_sha1(key.to_public_key(), data_dec)?; Ok(AsymmetricEncString::Rsa2048_OaepSha1_B64 { data: enc }) } diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs index 142013d4e..560e7e4cc 100644 --- a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -1,15 +1,45 @@ -use rsa::RsaPrivateKey; +use rsa::{pkcs8::DecodePublicKey, RsaPrivateKey, RsaPublicKey}; use super::key_encryptable::CryptoKey; use crate::error::{CryptoError, Result}; -/// An asymmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::EncString) +/// Trait to allow both [`AsymmetricCryptoKey`] and [`AsymmetricPublicCryptoKey`] to be used to +/// encrypt [AsymmetricEncString](crate::AsymmetricEncString). +pub trait AsymmetricEncryptable { + fn to_public_key(&self) -> &RsaPublicKey; +} + +/// An asymmetric public encryption key. Can only encrypt +/// [AsymmetricEncString](crate::AsymmetricEncString), usually accompanied by a +/// [AsymmetricCryptoKey] +pub struct AsymmetricPublicCryptoKey { + pub(crate) key: RsaPublicKey, +} + +impl AsymmetricPublicCryptoKey { + /// Build a public key from the SubjectPublicKeyInfo DER. + pub fn from_der(der: &[u8]) -> Result { + Ok(Self { + key: rsa::RsaPublicKey::from_public_key_der(der) + .map_err(|_| CryptoError::InvalidKey)?, + }) + } +} + +impl AsymmetricEncryptable for AsymmetricPublicCryptoKey { + fn to_public_key(&self) -> &RsaPublicKey { + &self.key + } +} + +/// An asymmetric encryption key. Contains both the public and private key. Can be used to both +/// encrypt and decrypt [`AsymmetricEncString`](crate::AsymmetricEncString). pub struct AsymmetricCryptoKey { pub(crate) key: RsaPrivateKey, } impl AsymmetricCryptoKey { - /// Generate a random AsymmetricCryptoKey (RSA-2048) + /// Generate a random AsymmetricCryptoKey (RSA-2048). pub fn generate(rng: &mut R) -> Self { let bits = 2048; @@ -45,7 +75,6 @@ impl AsymmetricCryptoKey { pub fn to_public_der(&self) -> Result> { use rsa::pkcs8::EncodePublicKey; Ok(self - .key .to_public_key() .to_public_key_der() .map_err(|_| CryptoError::InvalidKey)? @@ -54,6 +83,12 @@ impl AsymmetricCryptoKey { } } +impl AsymmetricEncryptable for AsymmetricCryptoKey { + fn to_public_key(&self) -> &RsaPublicKey { + self.key.as_ref() + } +} + impl CryptoKey for AsymmetricCryptoKey {} // We manually implement these to make sure we don't print any sensitive data @@ -67,7 +102,9 @@ impl std::fmt::Debug for AsymmetricCryptoKey { mod tests { use base64::{engine::general_purpose::STANDARD, Engine}; - use super::AsymmetricCryptoKey; + use crate::{ + AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, KeyDecryptable, + }; #[test] fn test_asymmetric_crypto_key() { @@ -111,4 +148,61 @@ DnqOsltgPomWZ7xVfMkm9niL2OA= assert_eq!(der_key.to_der().unwrap(), der_key_vec); assert_eq!(pem_key.to_der().unwrap(), der_key_vec); } + + #[test] + fn test_encrypt_public_decrypt_private() { + let private_key = STANDARD + .decode(concat!( + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu9xd+vmkIPoqH", + "NejsFZzkd1xuCn1TqGTT7ANhAEnbI/yaVt3caI30kwUC2WIToFpNgu7Ej0x2TteY", + "OgrLrdcC4jy1SifmKYv/v3ZZxrd/eqttmH2k588panseRwHK3LVk7xA+URhQ/bjL", + "gPM59V0uR1l+z1fmooeJPFz5WSXNObc9Jqnh45FND+U/UYHXTLSomTn7jgZFxJBK", + "veS7q6Lat7wAnYZCF2dnPmhZoJv+SKPltA8HAGsgQGWBF1p5qxV1HrAUk8kBBnG2", + "paj0w8p5UM6RpDdCuvKH7j1LiuWffn3b9Z4dgzmE7jsMmvzoQtypzIKaSxhqzvFO", + "od9V8dJdAgMBAAECggEAGGIYjOIB1rOKkDHP4ljXutI0mCRPl3FMDemiBeppoIfZ", + "G/Q3qpAKmndDt0Quwh/yfcNdvZhf1kwCCTWri/uPz5fSUIyDV3TaTRu0ZWoHaBVj", + "Hxylg+4HRZUQj+Vi50/PWr/jQmAAVMcrMfcoTl82q2ynmP/R1vM3EsXOCjTliv5B", + "XlMPRjj/9PDBH0dnnVcAPDOpflzOTL2f4HTFEMlmg9/tZBnd96J/cmfhjAv9XpFL", + "FBAFZzs5pz0rwCNSR8QZNonnK7pngVUlGDLORK58y84tGmxZhGdne3CtCWey/sJ4", + "7QF0Pe8YqWBU56926IY6DcSVBuQGZ6vMCNlU7J8D2QKBgQDXyh3t2TicM/n1QBLk", + "zLoGmVUmxUGziHgl2dnJiGDtyOAU3+yCorPgFaCie29s5qm4b0YEGxUxPIrRrEro", + "h0FfKn9xmr8CdmTPTcjJW1+M7bxxq7oBoU/QzKXgIHlpeCjjnvPJt0PcNkNTjCXv", + "shsrINh2rENoe/x79eEfM/N5eQKBgQDPkYSmYyALoNq8zq0A4BdR+F5lb5Fj5jBH", + "Jk68l6Uti+0hRbJ2d1tQTLkU+eCPQLGBl6fuc1i4K5FV7v14jWtRPdD7wxrkRi3j", + "ilqQwLBOU6Bj3FK4DvlLF+iYTuBWj2/KcxflXECmsjitKHLK6H7kFEiuJql+NAHU", + "U9EFXepLBQKBgQDQ+HCnZ1bFHiiP8m7Zl9EGlvK5SwlnPV9s+F1KJ4IGhCNM09UM", + "ZVfgR9F5yCONyIrPiyK40ylgtwqQJlOcf281I8irUXpsfg7+Gou5Q31y0r9NLUpC", + "Td8niyePtqMdGjouxD2+OHXFCd+FRxFt4IMi7vnxYr0csAVAXkqWlw7PsQKBgH/G", + "/PnQm7GM3BrOwAGB8dksJDAddkshMScblezTDYP0V43b8firkTLliCo5iNum357/", + "VQmdSEhXyag07yR/Kklg3H2fpbZQ3X7tdMMXW3FcWagfwWw9C4oGtdDM/Z1Lv23J", + "XDR9je8QV4OBGul+Jl8RfYx3kG94ZIfo8Qt0vP5hAoGARjAzdCGYz42NwaUk8n94", + "W2RuKHtTV9vtjaAbfPFbZoGkT7sXNJVlrA0C+9f+H9rOTM3mX59KrjmLVzde4Vhs", + "avWMShuK4vpAiDQLU7GyABvi5CR6Ld+AT+LSzxHhVe0ASOQPNCA2SOz3RQvgPi7R", + "GDgRMUB6cL3IRVzcR0dC6cY=", + )) + .unwrap(); + + let public_key = STANDARD + .decode(concat!( + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc", + "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X", + "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd", + "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui", + "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK", + "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS", + "XQIDAQAB", + )) + .unwrap(); + + let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap(); + let public_key = AsymmetricPublicCryptoKey::from_der(&public_key).unwrap(); + + let plaintext = "Hello, world!"; + let encrypted = + AsymmetricEncString::encrypt_rsa2048_oaep_sha1(plaintext.as_bytes(), &public_key) + .unwrap(); + let decrypted: String = encrypted.decrypt_with_key(&private_key).unwrap(); + + assert_eq!(plaintext, decrypted); + } } diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index 561fd8436..285e58b55 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -9,7 +9,10 @@ mod symmetric_crypto_key; pub use symmetric_crypto_key::derive_symmetric_key; pub use symmetric_crypto_key::SymmetricCryptoKey; mod asymmetric_crypto_key; -pub use asymmetric_crypto_key::AsymmetricCryptoKey; +pub use asymmetric_crypto_key::{ + AsymmetricCryptoKey, AsymmetricEncryptable, AsymmetricPublicCryptoKey, +}; + mod user_key; pub use user_key::UserKey; mod device_key; diff --git a/crates/bitwarden-crypto/src/rsa.rs b/crates/bitwarden-crypto/src/rsa.rs index 52dd572aa..bee88a655 100644 --- a/crates/bitwarden-crypto/src/rsa.rs +++ b/crates/bitwarden-crypto/src/rsa.rs @@ -44,15 +44,11 @@ pub(crate) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { }) } -pub(super) fn encrypt_rsa2048_oaep_sha1( - private_key: &RsaPrivateKey, - data: &[u8], -) -> Result> { +pub(super) fn encrypt_rsa2048_oaep_sha1(public_key: &RsaPublicKey, data: &[u8]) -> Result> { let mut rng = rand::thread_rng(); let padding = Oaep::new::(); - private_key - .to_public_key() + public_key .encrypt(&mut rng, padding, data) .map_err(|e| CryptoError::RsaError(e.into())) } diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index f67aa3a02..62c791967 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -1,7 +1,9 @@ use std::sync::Arc; -use bitwarden::auth::{password::MasterPasswordPolicyOptions, RegisterKeyResponse}; -use bitwarden_crypto::{HashPurpose, Kdf}; +use bitwarden::auth::{ + password::MasterPasswordPolicyOptions, AuthRequestResponse, RegisterKeyResponse, +}; +use bitwarden_crypto::{AsymmetricEncString, HashPurpose, Kdf}; use crate::{error::Result, Client}; @@ -91,4 +93,20 @@ impl ClientAuth { .validate_password(password, password_hash.to_string()) .await?) } + + /// Initialize a new auth request + pub async fn new_auth_request(&self, email: String) -> Result { + Ok(self.0 .0.write().await.auth().new_auth_request(&email)?) + } + + /// Approve an auth request + pub async fn approve_auth_request(&self, public_key: String) -> Result { + Ok(self + .0 + .0 + .write() + .await + .auth() + .approve_auth_request(public_key)?) + } } diff --git a/crates/bitwarden-uniffi/src/uniffi_support.rs b/crates/bitwarden-uniffi/src/uniffi_support.rs index a91e3de5f..663d5c41e 100644 --- a/crates/bitwarden-uniffi/src/uniffi_support.rs +++ b/crates/bitwarden-uniffi/src/uniffi_support.rs @@ -1,6 +1,7 @@ -use bitwarden_crypto::EncString; +use bitwarden_crypto::{AsymmetricEncString, EncString}; // Forward the type definitions to the main bitwarden crate type DateTime = chrono::DateTime; uniffi::ffi_converter_forward!(DateTime, bitwarden::UniFfiTag, crate::UniFfiTag); uniffi::ffi_converter_forward!(EncString, bitwarden::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!(AsymmetricEncString, bitwarden::UniFfiTag, crate::UniFfiTag); diff --git a/crates/bitwarden/src/auth/auth_request.rs b/crates/bitwarden/src/auth/auth_request.rs new file mode 100644 index 000000000..facdc8f82 --- /dev/null +++ b/crates/bitwarden/src/auth/auth_request.rs @@ -0,0 +1,137 @@ +use base64::{engine::general_purpose::STANDARD, Engine}; +use bitwarden_crypto::{ + fingerprint, AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, +}; +#[cfg(feature = "mobile")] +use bitwarden_crypto::{KeyDecryptable, SymmetricCryptoKey}; +use bitwarden_generators::{password, PasswordGeneratorRequest}; + +use crate::{error::Error, Client}; + +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct AuthRequestResponse { + /// Base64 encoded private key + /// This key is temporarily passed back and will most likely not be available in the future + pub private_key: String, + /// Base64 encoded public key + pub public_key: String, + /// Fingerprint of the public key + pub fingerprint: String, + /// Access code + pub access_code: String, +} + +/// Initiate a new auth request. +/// +/// Generates a private key and access code. The pulic key is uploaded to the server and transmitted +/// to another device. Where the user confirms the validity by confirming the fingerprint. The user +/// key is then encrypted using the public key and returned to the initiating device. +pub(crate) fn new_auth_request(email: &str) -> Result { + let mut rng = rand::thread_rng(); + + let key = AsymmetricCryptoKey::generate(&mut rng); + + let spki = key.to_public_der()?; + + let fingerprint = fingerprint(email, &spki)?; + let b64 = STANDARD.encode(&spki); + + Ok(AuthRequestResponse { + private_key: STANDARD.encode(key.to_der()?), + public_key: b64, + fingerprint, + access_code: password(PasswordGeneratorRequest { + length: 25, + lowercase: true, + uppercase: true, + numbers: true, + special: false, + ..Default::default() + })?, + }) +} + +/// Decrypt the user key using the private key generated previously. +#[cfg(feature = "mobile")] +pub(crate) fn auth_request_decrypt_user_key( + private_key: String, + user_key: AsymmetricEncString, +) -> Result { + let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; + let key: String = user_key.decrypt_with_key(&key)?; + + Ok(key.parse()?) +} + +/// Approve an auth request. +/// +/// Encrypts the user key with a public key. +pub(crate) fn approve_auth_request( + client: &mut Client, + public_key: String, +) -> Result { + let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(public_key)?)?; + + let enc = client.get_encryption_settings()?; + let key = enc.get_key(&None).ok_or(Error::VaultLocked)?; + + Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1( + &key.to_vec(), + &public_key, + )?) +} + +#[test] +fn test_auth_request() { + let request = new_auth_request("test@bitwarden.com").unwrap(); + + let secret = + "w2LO+nwV4oxwswVYCxlOfRUseXfvU03VzvKQHrqeklPgiMZrspUe6sOBToCnDn9Ay0tuCBn8ykVVRb7PWhub2Q=="; + + let private_key = + AsymmetricCryptoKey::from_der(&STANDARD.decode(&request.private_key).unwrap()).unwrap(); + + let encrypted = + AsymmetricEncString::encrypt_rsa2048_oaep_sha1(secret.as_bytes(), &private_key).unwrap(); + + let decrypted = auth_request_decrypt_user_key(request.private_key, encrypted).unwrap(); + + assert_eq!(decrypted.to_base64(), secret); +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; + + use bitwarden_crypto::Kdf; + + use super::*; + use crate::client::{LoginMethod, UserLoginMethod}; + + #[test] + fn test_approve() { + let mut client = Client::new(None); + client.set_login_method(LoginMethod::User(UserLoginMethod::Username { + client_id: "123".to_owned(), + email: "test@bitwarden.com".to_owned(), + kdf: Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }, + })); + + let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); + let private_key ="2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap(); + client + .initialize_user_crypto("asdfasdfasdf", user_key, private_key) + .unwrap(); + + let public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnRtpYLp9QLaEUkdPkWZX6TrMUKFoSaFamBKDL0NlS6xwtETTqYIxRVsvnHii3Dhz+fh3aHQVyBa1rBXogeH3MLERzNADwZhpWtBT9wKCXY5o0fIWYdZV/Nf0Y+0ZoKdImrGPLPmyHGfCqrvrK7g09q8+3kXUlkdAImlQqc5TiYwiHBfUQVTBq/Ae7a0FEpajx1NUM4h3edpCYxbvnpSTuzMgbmbUUS4gdCaheA2ibYxy/zkLzsaLygoibMyGNl9Y8J5n7dDrVXpUKZTihVfXwHfEZwtKNunWsmmt8rEJWVpguUDEDVSUogoxQcNaCi7KHn9ioSip76hg1jLpypO3WwIDAQAB"; + + // Verify fingerprint + let pbkey = STANDARD.decode(public_key).unwrap(); + let fingerprint = fingerprint("test@bitwarden.com", &pbkey).unwrap(); + assert_eq!(fingerprint, "spill-applaud-sweep-habitable-shrunk"); + + approve_auth_request(&mut client, public_key.to_owned()).unwrap(); + } +} diff --git a/crates/bitwarden/src/auth/client_auth.rs b/crates/bitwarden/src/auth/client_auth.rs index c0a5e7aa3..aaa387741 100644 --- a/crates/bitwarden/src/auth/client_auth.rs +++ b/crates/bitwarden/src/auth/client_auth.rs @@ -1,5 +1,5 @@ #[cfg(feature = "internal")] -use bitwarden_crypto::{DeviceKey, TrustDeviceResponse}; +use bitwarden_crypto::{AsymmetricEncString, DeviceKey, TrustDeviceResponse}; #[cfg(feature = "secrets")] use crate::auth::login::{login_access_token, AccessTokenLoginRequest, AccessTokenLoginResponse}; @@ -7,6 +7,7 @@ use crate::{auth::renew::renew_token, error::Result, Client}; #[cfg(feature = "internal")] use crate::{ auth::{ + auth_request::{approve_auth_request, new_auth_request}, login::{ login_api_key, login_password, send_two_factor_email, ApiKeyLoginRequest, ApiKeyLoginResponse, PasswordLoginRequest, PasswordLoginResponse, @@ -16,7 +17,7 @@ use crate::{ password_strength, satisfies_policy, validate_password, MasterPasswordPolicyOptions, }, register::{make_register_keys, register}, - RegisterKeyResponse, RegisterRequest, + AuthRequestResponse, RegisterKeyResponse, RegisterRequest, }, client::Kdf, error::Error, @@ -102,6 +103,14 @@ impl<'a> ClientAuth<'a> { validate_password(self.client, password, password_hash).await } + pub fn new_auth_request(&self, email: &str) -> Result { + new_auth_request(email) + } + + pub fn approve_auth_request(&mut self, public_key: String) -> Result { + approve_auth_request(self.client, public_key) + } + pub async fn trust_device(&self) -> Result { trust_device(self.client) } diff --git a/crates/bitwarden/src/auth/mod.rs b/crates/bitwarden/src/auth/mod.rs index e80ab28cf..23b64eaf9 100644 --- a/crates/bitwarden/src/auth/mod.rs +++ b/crates/bitwarden/src/auth/mod.rs @@ -6,13 +6,18 @@ pub mod login; pub mod password; pub mod renew; pub use jwt_token::JWTToken; - #[cfg(feature = "internal")] mod register; #[cfg(feature = "internal")] use bitwarden_crypto::{HashPurpose, MasterKey}; #[cfg(feature = "internal")] pub use register::{RegisterKeyResponse, RegisterRequest}; +#[cfg(feature = "internal")] +mod auth_request; +#[cfg(feature = "mobile")] +pub(crate) use auth_request::auth_request_decrypt_user_key; +#[cfg(feature = "internal")] +pub use auth_request::AuthRequestResponse; #[cfg(feature = "internal")] use crate::{client::Kdf, error::Result}; diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index f88b3d3e6..bd71af2be 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -51,10 +51,18 @@ pub enum InitUserCryptoMethod { /// this. pin_protected_user_key: EncString, }, + AuthRequest { + /// Private Key generated by the `crate::auth::new_auth_request`. + request_private_key: String, + /// User Key protected by the private key provided in `AuthRequestResponse`. + protected_user_key: AsymmetricEncString, + }, } #[cfg(feature = "internal")] pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequest) -> Result<()> { + use crate::auth::auth_request_decrypt_user_key; + let login_method = crate::client::LoginMethod::User(crate::client::UserLoginMethod::Username { client_id: "".to_string(), email: req.email, @@ -79,6 +87,13 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ } => { client.initialize_user_crypto_pin(&pin, pin_protected_user_key, private_key)?; } + InitUserCryptoMethod::AuthRequest { + request_private_key, + protected_user_key, + } => { + let user_key = auth_request_decrypt_user_key(request_private_key, protected_user_key)?; + client.initialize_user_crypto_decrypted_key(user_key, private_key)?; + } } Ok(()) diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index ccf193301..fd98e463e 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -138,6 +138,28 @@ password, use the email OTP. **Output**: std::result::Result<,BitwardenError> +### `new_auth_request` + +Initialize a new auth request + +**Arguments**: + +- self: +- email: String + +**Output**: std::result::Result + +### `approve_auth_request` + +Approve an auth request + +**Arguments**: + +- self: +- public_key: String + +**Output**: std::result::Result + ## ClientAttachments ### `encrypt_buffer` @@ -1227,6 +1249,32 @@ implementations. + + authRequest + object + + + + + + + + + + + + + + + + + + + + +
KeyTypeDescription
request_private_keystringPrivate Key generated by the `crate::auth::new_auth_request`.
protected_user_keyUser Key protected by the private key provided in `AuthRequestResponse`.
+ + ## `InitUserCryptoRequest`