From cd894c27e0b0d5746b95b9c2933da3ba6e9a3f5b Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sun, 5 Apr 2020 02:17:25 -0400 Subject: basic implementation of the cryptographic stuff --- src/api.rs | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 src/api.rs (limited to 'src/api.rs') diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..ae2e550 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,161 @@ +use crate::prelude::*; + +#[derive(serde::Serialize, Debug)] +struct PreloginReq { + email: String, +} + +#[derive(serde::Deserialize, Debug)] +struct PreloginRes { + #[serde(rename = "Kdf")] + kdf: u32, + #[serde(rename = "KdfIterations")] + kdf_iterations: u32, +} + +#[derive(serde::Serialize, Debug)] +struct ConnectReq { + grant_type: String, + username: String, + password: String, + scope: String, + client_id: String, + #[serde(rename = "deviceType")] + device_type: u32, + #[serde(rename = "deviceIdentifier")] + device_identifier: String, + #[serde(rename = "deviceName")] + device_name: String, + #[serde(rename = "devicePushToken")] + device_push_token: String, +} + +#[derive(serde::Deserialize, Debug)] +struct ConnectRes { + access_token: String, + expires_in: u32, + token_type: String, + refresh_token: String, + #[serde(rename = "Key")] + key: String, +} + +#[derive(serde::Deserialize, Debug)] +struct SyncRes { + #[serde(rename = "Ciphers")] + ciphers: Vec, + #[serde(rename = "Profile")] + profile: Profile, +} + +#[derive(serde::Deserialize, Debug)] +struct Profile { + #[serde(rename = "Key")] + key: String, +} + +#[derive(serde::Deserialize, Debug)] +pub struct Cipher { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "Login")] + pub login: Login, +} + +#[derive(serde::Deserialize, Debug)] +pub struct Login { + #[serde(rename = "Username")] + pub username: String, + #[serde(rename = "Password")] + pub password: String, +} + +#[derive(Debug)] +pub struct Client { + api_url_base: String, + identity_url_base: String, +} + +impl Client { + #[allow(dead_code)] + pub fn new() -> Self { + Self { + api_url_base: "https://api.bitwarden.com".to_string(), + identity_url_base: "https://identity.bitwarden.com".to_string(), + } + } + + pub fn new_self_hosted(base_url: &str) -> Self { + Self { + api_url_base: format!("{}/api", base_url), + identity_url_base: format!("{}/identity", base_url), + } + } + + pub fn prelogin(&self, email: &str) -> Result { + let prelogin = PreloginReq { + email: email.to_string(), + }; + let client = reqwest::blocking::Client::new(); + let res = client + .post(&self.api_url("/accounts/prelogin")) + .json(&prelogin) + .send() + .context(crate::error::Reqwest)?; + let prelogin_res: PreloginRes = + res.json().context(crate::error::Reqwest)?; + Ok(prelogin_res.kdf_iterations) + } + + pub fn login( + &self, + email: &str, + master_password_hash: &[u8], + ) -> Result<(String, String, String)> { + let connect_req = ConnectReq { + grant_type: "password".to_string(), + username: email.to_string(), + password: base64::encode(&master_password_hash), + scope: "api offline_access".to_string(), + client_id: "desktop".to_string(), + device_type: 8, + device_identifier: uuid::Uuid::new_v4() + .to_hyphenated() + .to_string(), + device_name: "test cli".to_string(), + device_push_token: "".to_string(), + }; + let client = reqwest::blocking::Client::new(); + let res = client + .post(&self.identity_url("/connect/token")) + .form(&connect_req) + .send() + .context(crate::error::Reqwest)?; + let connect_res: ConnectRes = + res.json().context(crate::error::Reqwest)?; + Ok(( + connect_res.access_token, + connect_res.refresh_token, + connect_res.key, + )) + } + + pub fn sync(&self, access_token: &str) -> Result> { + let client = reqwest::blocking::Client::new(); + let res = client + .get(&self.api_url("/sync")) + .header("Authorization", format!("Bearer {}", access_token)) + .send() + .context(crate::error::Reqwest)?; + let sync_res: SyncRes = res.json().context(crate::error::Reqwest)?; + Ok(sync_res.ciphers) + } + + fn api_url(&self, path: &str) -> String { + format!("{}{}", self.api_url_base, path) + } + + fn identity_url(&self, path: &str) -> String { + format!("{}{}", self.identity_url_base, path) + } +} -- cgit v1.2.3-54-g00ecf