internal static List <Account> OpenSharedFolder(R.SharedFolderInfo info, Session session, RSAParameters privateKey, IRestTransport transport) { if (!info.Accepted) { return(new List <Account>(0)); } var encryptedKey = info.EncryptedKey ?? ""; if (encryptedKey.IsNullOrEmpty()) { return(new List <Account>(0)); } var password = Crypto.DecryptRsaPkcs1(encryptedKey.Decode64(), privateKey).ToUtf8(); // Each shared folder is like an independent vault with its own password return(OpenFolder(session, password, info.Name ?? "-", new RestClient(transport, ApiBaseUrl(info.Id))).Accounts); }
public static Account[] OpenVaultCliApi(string clientId, string clientSecret, string password, string deviceId, string baseUrl, IRestTransport transport) { // Reset to default. Let the user simply pass a null or "" and not bother with an overload. if (baseUrl.IsNullOrEmpty()) { baseUrl = DefaultBaseUrl; } var rest = new RestClient(transport, baseUrl, defaultHeaders: DefaultRestHeaders); // 1. Login and get the client info var authInfo = LoginCliApi(clientId, clientSecret, deviceId, rest); // 2. Fetch the vault var encryptedVault = DownloadVault(rest, $"{authInfo.TokenType} {authInfo.AccessToken}"); // 3. Derive the master encryption key or KEK (key encryption key) var key = Util.DeriveKey(encryptedVault.Profile.Email, password, authInfo.KdfIterations); // 4. Decrypt and parse the vault. Done! return(DecryptVault(encryptedVault, key)); }
public static Account[] OpenVaultBrowser(string username, string password, string deviceId, string baseUrl, IUi ui, ISecureStorage storage, IRestTransport transport) { // Reset to default. Let the user simply pass a null or "" and not bother with an overload. if (baseUrl.IsNullOrEmpty()) { baseUrl = DefaultBaseUrl; } var rest = new RestClient(transport, baseUrl, defaultHeaders: DefaultRestHeaders); // 1. Request the number of KDF iterations needed to derive the key var iterations = RequestKdfIterationCount(username, rest); // 2. Derive the master encryption key or KEK (key encryption key) var key = Util.DeriveKey(username, password, iterations); // 3. Hash the password that is going to be sent to the server var hash = Util.HashPassword(password, key); // 4. Authenticate with the server and get the token var token = Login(username, hash, deviceId, ui, storage, rest); // 5. Fetch the vault var encryptedVault = DownloadVault(rest, token); // 6. Decrypt and parse the vault. Done! return(DecryptVault(encryptedVault, key)); }
public static Account[] OpenVault(ClientInfo clientInfo, Ui ui, IRestTransport transport) { var rest = new RestClient(transport, ApiBaseUrl(clientInfo.Username)); var session = Login(clientInfo, ui, rest); try { // Open the main user's vault var(accounts, privateKey) = OpenFolder(session, clientInfo.Password, "", rest); // Open all the folders shared with the user if (privateKey != null) { foreach (var info in GetSharedFolderList(session, rest)) { accounts.AddRange(OpenSharedFolder(info, session, privateKey.Value, transport)); } } return(accounts.ToArray()); } finally { Logout(session, rest); } }
// TODO: It's impossible to test this function because of the S3.* static calls. public static byte[] OpenVaultDb(string username, string password, string deviceId, string deviceName, IUi ui, IRestTransport transport) { var rest = new RestClient(transport, "https://spcb.stickypassword.com/SPCClient/"); // Request the token that is encrypted with the master password and get the one-time PIN // when a new device is registered for the first time. var(encryptedToken, passcode) = GetEncryptedTokenAndPasscode(username, deviceId, ui, rest); // Decrypt the token. This token is now used to authenticate with the server. var token = Util.DecryptToken(username, password, encryptedToken); // The device must be registered first. AuthorizeDevice(username, token, deviceId, deviceName, passcode, DateTime.Now, rest); // Get the S3 credentials to access the database on AWS. var s3Token = GetS3Token(username, token, deviceId, DateTime.Now, rest); // Download the database. return(DownloadLatestDb(s3Token, transport)); }
public static Account[] OpenVault(string username, string password, ClientInfo clientInfo, IUi ui, IRestTransport transport) { var rest = new RestClient(transport, "https://lastpass.com"); var session = Login(username, password, clientInfo, ui, rest); try { var blob = DownloadVault(session, rest); var key = Util.DeriveKey(username, password, session.KeyIterationCount); var privateKey = new RSAParameters(); if (!session.EncryptedPrivateKey.IsNullOrEmpty()) { privateKey = Parser.ParseEncryptedPrivateKey(session.EncryptedPrivateKey, key); } return(ParseVault(blob, key, privateKey)); } finally { Logout(session, rest); } }
// TODO: Write a test that runs the whole sequence and checks the result. internal static Vault Open(string username, string password, Ui ui, ISecureStorage storage, IRestTransport transport) { return(new Vault(Client.OpenVault(username, password, ui, storage, transport))); }
// // Internal // internal static Vault Open(string username, string accountPassword, string vaultPassword, IRestTransport restTransport, IBoshTransport boshTransport) { return(new Vault(Client.OpenVault(username, accountPassword, vaultPassword, restTransport, boshTransport))); }
public static Account[] OpenVault(string oauthToken, string[] recoveryWords, IRestTransport transport) { // We do this first to fail early in case the recovery words are incorrect. var masterKey = Util.DeriveMasterKeyFromRecoveryWords(recoveryWords); var rest = new RestClient(transport, "https://api.dropboxapi.com/2", defaultHeaders: new Dictionary <string, string> { ["Authorization"] = $"Bearer {oauthToken}" }); // 1. Get account info var accountInfo = Post <R.AccountInfo>("users/get_current_account", RestClient.JsonNull, // Important to send null! RestClient.NoHeaders, rest); if (accountInfo.Disabled) { throw new InternalErrorException($"The account is disabled"); } // 2. Get features var features = Post <R.Features>("passwords/get_features_v2", RestClient.JsonNull, // Important to send null! RestClient.NoHeaders, rest); if (features.Eligibility.Tag != "enabled") { throw new InternalErrorException("Dropbox Passwords is not enabled on this account"); } // 3. List the root folder // TODO: Very long folders are not supported. See "has_more" and "cursor". var rootFolder = Post <R.RootFolder>("files/list_folder", new Dictionary <string, object> { ["path"] = "" }, MakeRootPathHeaders(features.Eligibility.RootPath), rest); // 4. Get all entries var contentRest = new RestClient(rest.Transport, "https://content.dropboxapi.com/2", defaultHeaders: rest.DefaultHeaders); var entries = DownloadAllEntries(rootFolder, features.Eligibility.RootPath, contentRest); // Try to find all keysets that decrypt (normally there's only one). var keysets = FindAndDecryptAllKeysets(entries, masterKey); // Try to decrypt all account entries and see what decrypts. var accounts = FindAndDecryptAllAccounts(entries, keysets); // Done, phew! return(accounts); }
internal Session(ClientInfo clientInfo, Keychain keychain, AesKey key, RestClient rest, IRestTransport transport) { ClientInfo = clientInfo; Keychain = keychain; Key = key; Rest = rest; Transport = transport; }
public RestClient(IRestTransport transport, string baseUrl = "", IRequestSigner signer = null, ReadOnlyHttpHeaders defaultHeaders = null, ReadOnlyHttpCookies defaultCookies = null) { Transport = transport; BaseUrl = baseUrl; Signer = signer ?? new UnitRequestSigner(); DefaultHeaders = defaultHeaders ?? new ReadOnlyDictionary <string, string>(NoHeaders); DefaultCookies = defaultCookies ?? new ReadOnlyDictionary <string, string>(NoCookies); }
// Returns the second factor token from Duo or null when canceled by the user. public static Result Authenticate(string host, string signature, IDuoUi ui, IRestTransport transport) { var rest = new RestClient(transport, $"https://{host}"); var(tx, app) = ParseSignature(signature); var html = DownloadFrame(tx, rest); var(sid, devices) = ParseFrame(html); while (true) { // Ask the user to choose what to do var choice = ui.ChooseDuoFactor(devices); if (choice == null) { return(null); // Canceled by user } // SMS is a special case: it doesn't submit any codes, it rather tells the server to send // a new batch of passcodes to the phone via SMS. if (choice.Factor == DuoFactor.SendPasscodesBySms) { SubmitFactor(sid, choice, "", rest); choice = new DuoChoice(choice.Device, DuoFactor.Passcode, choice.RememberMe); } // Ask for the passcode var passcode = ""; if (choice.Factor == DuoFactor.Passcode) { passcode = ui.ProvideDuoPasscode(choice.Device); if (passcode.IsNullOrEmpty()) { return(null); // Canceled by user } } var token = SubmitFactorAndWaitForToken(sid, choice, passcode, ui, rest); // Flow error like an incorrect passcode. The UI has been updated with the error. Keep going. if (token.IsNullOrEmpty()) { continue; } // All good return(new Result($"{token}:{app}", choice.RememberMe)); } }
// TODO: Don't return JObject public static R.Vault OpenVault(string username, string deviceId, Ui ui, IRestTransport transport) { var rest = new RestClient(transport, BaseApiUrl); var loginType = RequestLoginType(username, rest); if (loginType == LoginType.DoesntExist) { throw new BadCredentialsException("Invalid username"); } var registered = IsDeviceRegistered(username, deviceId, rest); // We have a registered device, no 2FA code is needed unless always-OTP mode is on. In // always-OTP mode registering a device doesn't stop the server from asking OTP the next // time around. So remember-me function is this mode is more or less useless. The // registered device shows up in the admin area though. if (registered && loginType != LoginType.GoogleAuth_Always) { return(Fetch(username, deviceId, rest)); } // Try to fetch a few times and then register the device var attempt = 0; while (true) { try { var passcode = GetPasscodeFromUser(username, loginType, attempt++, ui, rest); var blob = Fetch(username, loginType, passcode.Code, rest); if (passcode.RememberMe && !registered) { var token = blob.Token ?? passcode.Code; RegisterDeviceWithToken(username, deviceId, DeviceName, token, rest); } return(blob); } catch (BadMultiFactorException) when(attempt < 3) { // Ignore } } }
public static void OpenVault(string username, Ui ui, IRestTransport transport) { var newRest = new RestClient(transport, "https://api.dashlane.com/v1/authentication/", new Dl1RequestSigner(), defaultHeaders: new Dictionary <string, string> { ["dashlane-client-agent"] = "{\"platform\":\"server_leeloo\",\"version\":\"57.220.0.1495220\"}", ["User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36", }); RequestEmailToken(username, newRest); var info = RegisterNewDevice(username, ui, newRest); // TODO: Fetch the vault with this UKI var uki = $"{info.AccessKey}-{info.SecretKey}"; }
public static Account[] OpenVault(string username, string password, string passphrase, IUi ui, ISecureStorage storage, IRestTransport transport) { var rest = new RestClient(transport); // This token is needed to access other pages of the login flow. It's sent via headers, // cookies and in the request data. var token = RequestToken(rest); // TLD is determined by the region/data center. Each user is associated with a specific region. var userInfo = RequestUserInfo(username, token, DataCenterToTld(DefaultDataCenter), rest); // Perform the login dance that possibly involves the MFA steps. The cookies are later // used by the subsequent requests. // // TODO: It would be ideal to figure out which cookies are needed for general // cleanliness. It was too many of them and so they are now passed altogether a bundle // between the requests. var cookies = LogIn(userInfo, password, token, ui, storage, rest); try { var vaultKey = Authenticate(passphrase, cookies, userInfo.Tld, rest); var vaultResponse = DownloadVault(cookies, userInfo.Tld, rest); var sharingKey = DecryptSharingKey(vaultResponse, vaultKey); return(ParseAccounts(vaultResponse, vaultKey, sharingKey)); } finally { LogOut(cookies, userInfo.Tld, rest); } }
// // Internal // internal static Vault Open(ClientInfo clientInfo, Ui ui, IRestTransport transport) { return new Vault(Client.OpenVault(clientInfo, ui, transport)); }
// Downloads an object from S3 in us-east-1 region. public static byte[] GetObject(string bucket, string path, Credentials credentials, IRestTransport transport) { return(GetObject(bucket, path, credentials, new RestClient(transport))); }
public static Account[] OpenVault(string username, string password, Ui ui, ISecureStorage storage, IRestTransport transport) { var rest = new RestClient(transport); // Step 1: Register a new deice or use the existing one from the previous run. var deviceInfo = LoadDeviceInfo(storage) ?? RegisterNewDevice("truekey-sharp", rest); // Step 2: Parse the token to decode OTP information. var otpInfo = Util.ParseClientToken(deviceInfo.Token); // Step 3: Validate the OTP info to make sure it's got only the // things we support at the moment. Util.ValidateOtpInfo(otpInfo); // Store the token and ID for the next time. StoreDeviceInfo(deviceInfo, storage); // Bundle up everything in one place var clientInfo = new ClientInfo(username, "truekey-sharp", deviceInfo, otpInfo); // Step 4: Auth step 1 gives us a transaction id to pass along to the next step. var transactionId = AuthStep1(clientInfo, rest); // Step 5: Auth step 2 gives us the instructions on what to do next. For a new client that // would be some form of second factor auth. For a known client that would be a // pair of OAuth tokens. var whatsNext = AuthStep2(clientInfo, password, transactionId, rest); // The device is trusted if it's already authenticated at this point and // no second factor is needed. var isTrusted = whatsNext.IsAuthenticated; // Step 6: Auth FSM -- walk through all the auth steps until we're done. var oauthToken = TwoFactorAuth.Start(clientInfo, whatsNext, ui, rest); // Step 7: Save this device as trusted not to repeat the two factor dance next times. if (!isTrusted) { SaveDeviceAsTrusted(clientInfo, transactionId, oauthToken, rest); } // Step 8: Get the vault from the server. var encryptedVault = GetVault(oauthToken, rest); // Step 9: Compute the master key. var masterKey = Util.DecryptMasterKey(password, encryptedVault.MasterKeySalt, encryptedVault.EncryptedMasterKey); // Step 10: Decrypt the accounts. var accounts = encryptedVault .EncryptedAccounts .Select(i => new Account(id: i.Id, name: i.Name, username: i.Username, password: Util.Decrypt(masterKey, i.EncryptedPassword).ToUtf8(), url: i.Url, note: Util.Decrypt(masterKey, i.EncryptedNote).ToUtf8())) .ToArray(); return(accounts); }
internal static Vault Open(string oauthToken, string[] recoveryWords, IRestTransport transport) { return(new Vault(Client.OpenVault(oauthToken, recoveryWords, transport))); }
public static Account[] OpenVault(string username, string accountPassword, string vaultPassword, IRestTransport restTransport, IBoshTransport boshTransport) { var rest = new RestClient(restTransport); // 1. Login var(sessionCookie, authCookies) = Login(username, accountPassword, rest); try { // 2. Get XMPP info var xmpp = GetXmppInfo(authCookies, rest); // 3. The server returns a bunch of alternative URLs with the XMPP BOSH Js library which // are located on different domains. We need to pick one and all the following request // are done using this domain and its sub and sibling domains. var jsLibraryHost = ChooseJsLibraryHost(xmpp); // 4. Generate JID var jid = GenerateJid(xmpp.UserId, jsLibraryHost); // 5. Get notify server BOSH url var httpsBoshUrl = GetBoshUrl(jid, jsLibraryHost, rest); // 6. "find_bosh_bind" call returns a https:// link which doesn't work with the web sockets. // It need to be converted to the wss:// before it could be used. var wssBoshUrl = ConvertHttpsBoshUrlToWss(httpsBoshUrl); // 6. Connect to the notify XMPP BOSH server var bosh = new Bosh(wssBoshUrl, jid, xmpp.XmppCredentials.Password, boshTransport); bosh.Connect(); // 7. Get DB info which mainly contains the encryption settings (key derivation info) var dbInfoBlob = bosh.GetChanges(GetDatabaseInfoCommand, GetDatabaseInfoCommandId) .Where(x => x.Type == "Database") .Select(x => x.Data) .FirstOrDefault()? .Decode64(); if (dbInfoBlob == null) { throw MakeError("Database info is not found in the response"); } var dbInfo = DatabaseInfo.Parse(dbInfoBlob); var version = dbInfo.Version; if (!SupportedDbVersions.Contains(version)) { throw new UnsupportedFeatureException($"Database version {version} is not supported"); } var encryptionKey = Util.DeriveEncryptionKey(vaultPassword, dbInfo); var authKey = Util.DeriveMasterPasswordAuthKey(jid.UserId, encryptionKey, dbInfo); // 8. Get DB that contains all of the accounts // TODO: Test on a huge vault to see if the accounts come in batches and // we need to make multiple requests var db = bosh.GetChanges(GetDatabaseCommand, GetDatabaseCommandId, authKey.ToBase64()); return(Parser.ParseVault(db, encryptionKey).ToArray()); } finally { // 9. Logout Logout(sessionCookie, authCookies, rest); } }
// // Internal // internal static Vault Open(string username, string password, string deviceId, Ui ui, IRestTransport transport) { return(new Vault(Remote.OpenVault(username, deviceId, ui, transport), password)); }
public RestBoshTransport(IRestTransport transport) { _rest = new RestClient(transport); }
// This functions finds out what the latest version of the database is and downloads // it from S3. internal static byte[] DownloadLatestDb(S3Token token, IRestTransport transport) { return(DownloadLatestDb(token, new RestClient(transport))); }