use anyhow::Error; use lastpass::{endpoints, DecryptionKey, LoginKey}; use reqwest::Client; use structopt::StructOpt; #[tokio::main] async fn main() -> Result<(), Error> { env_logger::init(); let args = Args::from_args(); log::debug!("Starting application with {:#?}", args); // Create a HTTP client, making sure it remembers cookies so we don't need // to supply the PHP session every time let client = Client::builder() .user_agent(lastpass::DEFAULT_USER_AGENT) .cookie_store(true) .build()?; // How many times should we iterate when generating keys? let iterations = endpoints::iterations(&client, &args.host, &args.username).await?; // create a key which can be used to log in let login_key = LoginKey::calculate(&args.username, &args.password, iterations); // send a login request and initialise our user session let session = endpoints::login( &client, &args.host, &args.username, &login_key, iterations, args.trusted_id(), ) .await?; log::info!( "Logged in as {} with session id: {}", args.username, session.session_id ); // The vault has a version number which gets incremented every time a change // is made. A real application avoid downloading a new snapshot of the vault // (a potentially expensive request) by using this number to see whether a // cached version is still valid. let vault_version = endpoints::get_vault_version(&client, &args.host).await?; log::info!("Current vault version: {}", vault_version); // We need our master decryption key to decrypt the vault (note: this is // separate to the login key) let decryption_key = DecryptionKey::calculate(&args.username, &args.password, iterations); // grab a snapshot of the vault let vault = endpoints::get_vault( &client, &args.host, &decryption_key, &session.private_key, ) .await?; // and dump it to stdout... this will be really really verbose log::debug!("{:#?}", vault); // now lets print out the contents of every attachment for account in &vault.accounts { if account.attachments.is_empty() { continue; } for attachment in &account.attachments { // each "account" (password, secure note, address, etc.) uses its // own key for encrypting attachments. An encrypted version is // attached to the account, and can only be accessed if you have // the master decryption key let attachment_key = account.attachment_key(&decryption_key)?; // print out the attachment's name and some info about it let filename = attachment.filename(&attachment_key)?; log::info!( "*** {} - {} ({} bytes) ***", account.name, filename, attachment.size ); // actually fetch the attachment let payload = endpoints::load_attachment( &client, &args.host, &session.token, &attachment.storage_key, &attachment_key, ) .await?; // try to print it out if it's text, otherwise print it as hex match std::str::from_utf8(&payload) { Ok(payload) => log::info!("{}", payload), Err(_) => log::info!("{:?}", hex::encode(payload)), } } } log::info!("Logging out"); endpoints::logout(&client, &args.host, &session.token).await?; Ok(()) } #[derive(Debug, StructOpt)] struct Args { #[structopt( long = "host", default_value = "lastpass.com", help = "The LastPass server's hostname" )] host: String, #[structopt(short = "u", long = "username", help = "Your username")] username: String, #[structopt( short = "t", long = "trusted-id", help = "The token from your 2FA provider" )] trusted_id: Option, #[structopt(short = "p", long = "password", help = "Your master password")] password: String, } impl Args { pub fn trusted_id(&self) -> Option<&str> { match self.trusted_id { Some(ref id) => Some(&id), None => None, } } }