├── .config └── dotnet-tools.json ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── UPGRADE.md ├── ci ├── make-nuget-package.sh └── push-package-to-nuget.sh ├── example ├── Bitwarden │ ├── Bitwarden.csproj │ ├── Program.cs │ └── config.yaml.example ├── Common │ ├── AsyncPlainStorage.cs │ ├── BaseUi.cs │ ├── Common.csproj │ ├── DuoUi.cs │ ├── PlainStorage.cs │ └── Util.cs ├── Dashlane │ ├── Dashlane.csproj │ ├── Program.cs │ └── config.yaml.example ├── DropboxPasswords │ ├── DropboxPasswords.csproj │ ├── Program.cs │ └── config.yaml.example ├── Kaspersky │ ├── Kaspersky.csproj │ ├── Program.cs │ └── config.yaml.example ├── Kdbx │ ├── Kdbx.csproj │ ├── Program.cs │ └── config.yaml.example ├── LastPass │ ├── LastPass.csproj │ ├── Program.cs │ └── config.yaml.example ├── LastPassAvalonia │ ├── App.axaml │ ├── App.axaml.cs │ ├── LastPassAvalonia.csproj │ ├── MainWindow.axaml │ ├── MainWindow.axaml.cs │ ├── MainWindowViewModel.cs │ ├── Program.cs │ ├── ViewModelBase.cs │ └── app.manifest ├── OnePassword │ ├── OnePassword.csproj │ ├── Program.cs │ └── config.yaml.example ├── OpVault │ ├── OpVault.csproj │ ├── Program.cs │ └── config.yaml.example ├── ProtonPass │ ├── Program.cs │ ├── ProtonPass.csproj │ └── config.yaml.example ├── RoboForm │ ├── Program.cs │ ├── RoboForm.csproj │ └── config.yaml.example ├── StickyPassword │ ├── Program.cs │ ├── StickyPassword.csproj │ └── config.yaml.example ├── TrueKey │ ├── Program.cs │ ├── TrueKey.csproj │ └── config.yaml.example └── ZohoVault │ ├── Program.cs │ ├── ZohoVault.csproj │ └── config.yaml.example ├── password-manager-access.sln ├── password-manager-access.sln.DotSettings ├── src ├── Bitwarden │ ├── Account.cs │ ├── CipherString.cs │ ├── Client.cs │ ├── ClientInfo.cs │ ├── Collection.cs │ ├── NoItem.cs │ ├── Organization.cs │ ├── ParseError.cs │ ├── Response.cs │ ├── Session.cs │ ├── SshKey.cs │ ├── Ui.cs │ ├── Util.cs │ ├── Vault.cs │ └── VaultItem.cs ├── Common │ ├── Asn1.cs │ ├── ChaCha20.cs │ ├── ChaCha20CryptoTransform.cs │ ├── Crypto.cs │ ├── Debug.cs │ ├── Exceptions.cs │ ├── ExposeInternals.cs │ ├── Extensions.cs │ ├── Hkdf.cs │ ├── IAsyncSecureStorage.cs │ ├── ISecureLogger.cs │ ├── ISecureStorage.cs │ ├── ISqliteProvider.cs │ ├── Indisposable.cs │ ├── Json.cs │ ├── MfaMethod.cs │ ├── NullStorage.cs │ ├── Os.cs │ ├── OutputSpanStream.cs │ ├── Pbkdf2.cs │ ├── Pem.cs │ ├── Poly1305.cs │ ├── Rental.cs │ ├── RestAsync.cs │ ├── RestClient.Async.cs │ ├── RestClient.cs │ ├── S3.cs │ ├── SpanStream.cs │ ├── Try.cs │ ├── Url.cs │ ├── WarningFree.cs │ └── XChaCha20.cs ├── Dashlane │ ├── Account.cs │ ├── Client.cs │ ├── Dl1RequestSigner.cs │ ├── Parse.cs │ ├── Response.cs │ ├── Ui.cs │ └── Vault.cs ├── DropboxPasswords │ ├── Account.cs │ ├── Client.cs │ ├── ClientInfo.cs │ ├── IUi.cs │ ├── Response.cs │ ├── Util.cs │ └── Vault.cs ├── Duo │ ├── DuoResult.cs │ ├── DuoV1.cs │ ├── DuoV4.cs │ ├── IDuoAsyncUi.cs │ ├── IDuoUi.cs │ ├── Response.cs │ ├── ResponseV1.cs │ ├── ResponseV4.cs │ └── Util.cs ├── Kaspersky │ ├── Account.cs │ ├── Bosh.cs │ ├── Client.cs │ ├── DatabaseInfo.cs │ ├── IBoshTransport.cs │ ├── Jid.cs │ ├── Parser.cs │ ├── Response.cs │ ├── Util.cs │ ├── Vault.cs │ └── WebSocketBoshTransport.cs ├── Kdbx │ ├── Account.cs │ ├── BlockStream.cs │ ├── Parser.cs │ ├── Twofish.cs │ ├── Util.cs │ └── Vault.cs ├── LastPass │ ├── Account.cs │ ├── Client.Sso.cs │ ├── Client.cs │ ├── ClientInfo.cs │ ├── Model.cs │ ├── Parser.cs │ ├── ParserOptions.cs │ ├── Platform.cs │ ├── Session.cs │ ├── SharedFolder.cs │ ├── Ui.cs │ ├── Util.cs │ └── Vault.cs ├── OnePassword │ ├── Account.cs │ ├── AccountKey.cs │ ├── AesGcm.cs │ ├── AesKey.cs │ ├── AppInfo.cs │ ├── Client.cs │ ├── Credentials.cs │ ├── Encrypted.cs │ ├── IDecryptor.cs │ ├── Keychain.cs │ ├── MacRequestSigner.cs │ ├── NoItem.cs │ ├── Region.cs │ ├── Response.cs │ ├── RsaKey.cs │ ├── ServiceAccount.cs │ ├── Session.cs │ ├── Srp.cs │ ├── SrpInfo.cs │ ├── SshKey.cs │ ├── UInt128.cs │ ├── Ui.cs │ ├── Util.cs │ ├── Vault.cs │ ├── VaultInfo.cs │ └── VaultItem.cs ├── OpVault │ ├── Account.cs │ ├── Folder.cs │ ├── KeyMac.cs │ ├── Model.cs │ ├── Opdata01.cs │ ├── Util.cs │ └── Vault.cs ├── PasswordManagerAccess.csproj ├── ProtonPass │ ├── Account.cs │ ├── Client.cs │ ├── IAsyncUi.cs │ ├── Model.cs │ ├── Protobuf │ │ ├── ItemV1.cs │ │ └── VaultV1.cs │ ├── Srp.cs │ └── Vault.cs ├── RoboForm │ ├── Account.cs │ ├── AuthInfo.cs │ ├── Client.cs │ ├── ClientInfo.cs │ ├── OneFile.cs │ ├── Response.cs │ ├── Session.cs │ ├── Ui.cs │ ├── Util.cs │ ├── Vault.cs │ └── VaultParser.cs ├── StickyPassword │ ├── Account.cs │ ├── Client.cs │ ├── Parser.cs │ ├── S3Token.cs │ ├── Ui.cs │ ├── Util.cs │ └── Vault.cs ├── TrueKey │ ├── Account.cs │ ├── AesCcm.cs │ ├── Client.cs │ ├── EncryptedAccount.cs │ ├── EncryptedVault.cs │ ├── Response.cs │ ├── TwoFactorAuth.cs │ ├── Ui.cs │ ├── Util.cs │ └── Vault.cs └── ZohoVault │ ├── Account.cs │ ├── Client.cs │ ├── Credentials.cs │ ├── Response.cs │ ├── Settings.cs │ ├── Ui.cs │ ├── Util.cs │ └── Vault.cs └── test ├── Bitwarden ├── AccountTest.cs ├── CipherStringTest.cs ├── ClientTest.cs ├── Fixtures │ ├── collections.json │ ├── duo.html │ ├── folders.json │ ├── item-not-found.json │ ├── login-mfa-unsupported-only.json │ ├── login-mfa.json │ ├── profile.json │ ├── single-item-card.json │ ├── single-item-login.json │ ├── single-item-ssh-key.json │ ├── vault-with-collections.json │ ├── vault-with-errors.json │ ├── vault-with-fields.json │ ├── vault-with-ssh-keys.json │ └── vault.json ├── SshKeyTest.cs └── UtilTest.cs ├── Common ├── Asn1Test.cs ├── ChaCha20Test.cs ├── CryptoTest.cs ├── CryptoTestVectors.cs ├── ExtensionsTest.cs ├── Fixtures │ ├── openssl-private-key.pem │ └── openssl-rsa-private-key.pem ├── HkdfTest.cs ├── JsonTest.cs ├── OsTest.cs ├── OutputSpanStreamTest.cs ├── Pbkdf2Test.cs ├── PemTest.cs ├── Poly1305Test.cs ├── RestClientTest.cs ├── S3Test.cs ├── SpanStreamTest.cs ├── TryTest.cs ├── UrlTest.cs └── XChaCha20Test.cs ├── Dashlane ├── AccountTest.cs ├── ClientTest.cs ├── Dl1RequestSignerTest.cs ├── Fixtures │ ├── device-registered.json │ ├── email-token-sent.json │ ├── email-token-triggered.json │ ├── email-token-verified.json │ ├── empty-fullfile-one-add-transaction.json │ ├── empty-fullfile-two-add-one-remove-transactions.json │ ├── empty-fullfile-two-add-transactions.json │ ├── empty-vault.json │ ├── invalid-email-token.json │ ├── invalid-email.json │ ├── invalid-uki.json │ ├── non-empty-vault.json │ ├── otp-requested.json │ ├── two-accounts-in-fullfile-one-remove-transaction.json │ ├── two-accounts-in-fullfile-two-remove-one-add-transactions.json │ ├── two-accounts-in-fullfile-two-remove-transactions.json │ └── two-accounts-in-fullfile.json ├── ParseTest.cs └── VaultTest.cs ├── DropboxPasswords ├── AccountTest.cs ├── ClientTest.cs ├── Fixtures │ ├── account-info.json │ ├── code-for-oauth-exchange.json │ ├── entries.json │ ├── entry-keyset.json │ ├── entry-vault.json │ ├── expired-oauth.json │ ├── features.json │ ├── keyset.json │ ├── root-folder.json │ └── transmitted-encryption-key.json └── UtilTest.cs ├── Duo ├── DuoV1Test.cs └── UtilTest.cs ├── Exceptions.cs ├── Kaspersky ├── AccountTest.cs ├── BoshTest.cs ├── ClientTest.cs ├── Fixtures │ ├── large-vault-response-1.xml │ ├── large-vault-response-2.xml │ ├── vault-response.xml │ ├── vault-version8-response.xml │ └── vault-version9-response.xml ├── ParserTest.cs └── UtilTest.cs ├── Kdbx ├── BlockStreamTest.cs ├── Fixtures │ ├── kdbx4-aes-aes.kdbx │ ├── kdbx4-aes-chacha20.kdbx │ ├── kdbx4-aes-twofish.kdbx │ ├── kdbx4-argon2-aes-1k-block.kdbx │ ├── kdbx4-argon2-aes.kdbx │ ├── kdbx4-with-fields.kdbx │ ├── kdbx4-with-keyfile.kdbx │ ├── kdbx4-with-nested-folders.kdbx │ ├── keyfile-generic.bin │ ├── keyfile-legacy-binary.bin │ ├── keyfile-legacy-hex.txt │ └── keyfile-xml.xml ├── ParserTest.cs └── UtilTest.cs ├── LastPass ├── AccountTest.cs ├── ClientTest.cs ├── Fixtures │ └── blob-base64.txt ├── ParserTest.cs ├── SharedFolderTest.cs ├── TestData.cs ├── UtilTest.cs └── VaultTest.cs ├── MemoryStorage.cs ├── OnePassword ├── AccountKeyTest.cs ├── AccountTest.cs ├── AesGcmTest.cs ├── ClientTest.cs ├── CredentialsTest.cs ├── Fixtures │ ├── empty-object-response.json │ ├── encrypted-aes-key.json │ ├── encrypted-rsa-key.json │ ├── exchange-a-for-b-response.json │ ├── get-account-info-response.json │ ├── get-keysets-response.json │ ├── get-vault-accounts-4tz6-response.json │ ├── get-vault-accounts-ixsi-response.json │ ├── get-vault-accounts-ru74-batch-1-response.json │ ├── get-vault-accounts-ru74-batch-2-response.json │ ├── get-vault-accounts-ru74-batch-3-response.json │ ├── get-vault-accounts-ru74-response.json │ ├── get-vault-accounts-saiw-response.json │ ├── get-vault-with-no-items-response.json │ ├── get-vault-with-server-secrets-response.json │ ├── mfa-response.json │ ├── no-auth-response.json │ ├── rsa-key-oaep-256.json │ ├── rsa-key.json │ ├── start-new-session-response.json │ ├── vault-item-with-lots-of-fields.json │ ├── verify-key-response-mfa.json │ └── verify-key-response.json ├── MacRequestSignerTest.cs ├── ResponseTest.cs ├── RsaKeyTest.cs ├── SrpInfoTest.cs ├── SrpTest.cs ├── SshKeyTest.cs ├── TestData.cs ├── UInt128Test.cs └── UtilTest.cs ├── OpVault ├── AccountTest.cs ├── Fixtures │ ├── corrupted.opvault │ │ └── default │ │ │ ├── band_3.js │ │ │ ├── band_6.js │ │ │ ├── band_D.js │ │ │ ├── band_E.js │ │ │ ├── folders.js │ │ │ └── profile.js │ ├── onepassword_data │ │ └── default │ │ │ ├── 1C7D72EFA19A4EE98DB7A9661D2F5732_3B94A1F475014E27BFB00C99A42214DF.attachment │ │ │ ├── 2A632FDD32F5445E91EB5636C7580447_8FA293F2B001459D8F8F78C21E6BF9F6.attachment │ │ │ ├── E0D293D29B10483F8DFDAC72ED0BE5C0_898CD4CD00164930A2E15B159CE65E8F.attachment │ │ │ ├── F2DB5DA3FCA64372A751E0E85C67A538_23F6167DC1FB457A8DE7033ACDCD06DB.attachment │ │ │ ├── F2DB5DA3FCA64372A751E0E85C67A538_AFBDA49A5F684179A78161E40CA2AAD3.attachment │ │ │ ├── FF445AB1497241A28812363154E1A738_16684B74F26145169EC03B950DC68E95.attachment │ │ │ ├── band_0.js │ │ │ ├── band_1.js │ │ │ ├── band_2.js │ │ │ ├── band_3.js │ │ │ ├── band_4.js │ │ │ ├── band_5.js │ │ │ ├── band_6.js │ │ │ ├── band_7.js │ │ │ ├── band_8.js │ │ │ ├── band_A.js │ │ │ ├── band_D.js │ │ │ ├── band_E.js │ │ │ ├── band_F.js │ │ │ ├── folders.js │ │ │ └── profile.js │ └── test.opvault │ │ └── default │ │ ├── band_3.js │ │ ├── band_6.js │ │ ├── band_D.js │ │ ├── band_E.js │ │ ├── folders.js │ │ └── profile.js ├── FolderTest.cs ├── KeyMacTest.cs ├── Opdata01Test.cs ├── UtilTest.cs └── VaultTest.cs ├── PasswordManagerAccess.Test.csproj ├── ProtonPass ├── ClientTest.cs ├── Fixtures │ ├── auth-info.json │ ├── extra-auth-info.json │ └── sessions.json ├── SrpTest.cs └── VaultTest.cs ├── RestFlow.cs ├── RoboForm ├── AuthInfoTest.cs ├── ClientTest.cs ├── Fixtures │ ├── blob-with-extra-root-siblings.json │ ├── blob.bin │ ├── blob.json │ ├── main-folder-blob.bin │ ├── more-shared-stuff-folder-blob.bin │ ├── shared-stuff-folder-blob.bin │ ├── two-and-one-unaccepted-shared-folders.json │ └── two-shared-folders.json ├── OneFileTest.cs ├── SessionTest.cs ├── TestData.cs ├── UtilTest.cs └── VaultParserTest.cs ├── StickyPassword ├── AccountTest.cs ├── ClientTest.cs ├── Fixtures │ ├── corrupted.sqlite │ └── db.sqlite ├── ParserTest.cs ├── S3TokenTest.cs ├── UtilTest.cs └── VaultTest.cs ├── TestBase.cs ├── TestUtil.cs ├── TrueKey ├── AccountTest.cs ├── AesCcmTest.cs ├── ClientTest.cs ├── EncryptedAccountTest.cs ├── EncryptedVaultTest.cs ├── Fixtures │ ├── auth-check-pending-response.json │ ├── auth-check-success-response.json │ ├── auth-step1-response.json │ ├── auth-step2-response.json │ ├── auth-step2-success-response.json │ ├── get-vault-response.json │ ├── post-response-with-error.json │ ├── register-new-device-response.json │ └── save-device-response.json └── UtilTest.cs ├── ZohoVault ├── AccountTest.cs ├── ClientTest.cs ├── Fixtures │ ├── auth-info-response.json │ ├── auth-info-with-shared-items-response.json │ ├── auth-info-with-unknown-kdf-response.json │ ├── login-incorrect-password-response.json │ ├── login-mfa-required-response.json │ ├── login-success-response.json │ ├── login-unknown-error-response.json │ ├── lookup-another-region-response.json │ ├── lookup-no-user-response.json │ ├── lookup-success-response.json │ ├── lookup-unknown-error-response.json │ ├── mfa-incorrect-code-response.json │ ├── mfa-success-response.json │ ├── trust-success-response.json │ ├── vault-response.json │ └── vault-with-shared-items-response.json ├── TestData.cs └── UtilTest.cs └── app.config /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "csharpier": { 6 | "version": "0.29.2", 7 | "commands": [ 8 | "dotnet-csharpier" 9 | ], 10 | "rollForward": false 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 150 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /.vs/ 3 | /.vscode/ 4 | /example/*/bin/ 5 | /example/*/obj/ 6 | /example/**/config.yaml 7 | /example/*/storage.yaml 8 | /scripts/ 9 | /src/bin/ 10 | /src/obj/ 11 | /test/bin/ 12 | /test/obj/ 13 | /password-manager-access.sln.DotSettings.user 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2019 Dmitry Yakimenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /ci/make-nuget-package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # This script is needed to strip out the leading `v` from the git tag. Otherwise it just calls 5 | # `dotnet pack`. 6 | 7 | GIT_TAG=$1 8 | 9 | if [[ -z $GIT_TAG ]]; then 10 | echo "Usage: $0 git-tag" 11 | echo "Example: $0 v1.2.3" 12 | exit 1 13 | fi 14 | 15 | if [[ $GIT_TAG != v* ]]; then 16 | echo "The git tag must start with 'v'" 17 | exit 1 18 | fi 19 | 20 | # Remove the leading 'v' from the git tag 21 | VERSION=$(echo $GIT_TAG | cut -c 2-) 22 | 23 | # Build the NuGet package 24 | cd src 25 | dotnet pack --configuration Release --include-source --include-symbols -property:Version=$VERSION 26 | -------------------------------------------------------------------------------- /ci/push-package-to-nuget.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | API_KEY=$NUGET_API_KEY 5 | 6 | if [[ -z $API_KEY ]]; then 7 | echo "Environemtn variable NUGET_API_KEY must be set" 8 | exit 1 9 | fi 10 | 11 | env 12 | dotnet nuget push --source nuget.org --api-key $API_KEY --skip-duplicate src/bin/Release/*.nupkg 13 | -------------------------------------------------------------------------------- /example/Bitwarden/Bitwarden.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Exe 11 | net8.0 12 | 12 13 | false 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/Bitwarden/config.yaml.example: -------------------------------------------------------------------------------- 1 | # For the browser mode 2 | username: someone@gmail.com 3 | password: passw0rd! 4 | device-id: 40e485a2-9728-5896-9939-14e977482d18 5 | base-url: http://localhost 6 | 7 | # For the CLI/API mode 8 | client-id: user.a6047118-6587-4888-a785-ad980084f273 9 | client-secret: yIV5T3GHYZbqmArtNa7FUbFRTS4xpF 10 | password: passw0rd! 11 | device-id: 40e485a2-9728-5896-9939-14e977482d18 12 | base-url: http://localhost 13 | -------------------------------------------------------------------------------- /example/Common/AsyncPlainStorage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System.Threading.Tasks; 5 | using PasswordManagerAccess.Common; 6 | 7 | namespace PasswordManagerAccess.Example.Common 8 | { 9 | public class AsyncPlainStorage : IAsyncSecureStorage 10 | { 11 | public AsyncPlainStorage() 12 | : this(new PlainStorage()) { } 13 | 14 | public AsyncPlainStorage(string filename) 15 | : this(new PlainStorage(filename)) { } 16 | 17 | public Task LoadString(string name) 18 | { 19 | return Task.FromResult(_plainStorage.LoadString(name)); 20 | } 21 | 22 | public Task StoreString(string name, string value) 23 | { 24 | _plainStorage.StoreString(name, value); 25 | return Task.CompletedTask; 26 | } 27 | 28 | // 29 | // Private 30 | // 31 | 32 | private AsyncPlainStorage(PlainStorage plainStorage) 33 | { 34 | _plainStorage = plainStorage; 35 | } 36 | 37 | private readonly PlainStorage _plainStorage; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/Common/Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 12 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/Dashlane/Dashlane.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Exe 11 | net8.0 12 | 12 13 | false 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/Dashlane/config.yaml.example: -------------------------------------------------------------------------------- 1 | username: someone@gmail.com 2 | password: passw0rd! 3 | -------------------------------------------------------------------------------- /example/DropboxPasswords/DropboxPasswords.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Exe 12 | net8.0 13 | 12 14 | false 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/DropboxPasswords/config.yaml.example: -------------------------------------------------------------------------------- 1 | oauth-token: gec1P_XkEXgAAAAAAAAAAa5xRmTf15WTzbynOqvrSM2WN4XfignuWxPikWtuuPNg 2 | recovery-words: foot comic noise behave plastic blur differ feed game upgrade hand rotate 3 | -------------------------------------------------------------------------------- /example/Kaspersky/Kaspersky.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Exe 11 | net8.0 12 | 12 13 | false 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/Kaspersky/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System; 5 | using PasswordManagerAccess.Common; 6 | using PasswordManagerAccess.Example.Common; 7 | using PasswordManagerAccess.Kaspersky; 8 | 9 | namespace PasswordManagerAccess.Example.Kaspersky 10 | { 11 | public static class Program 12 | { 13 | public static void Main(string[] args) 14 | { 15 | var config = Util.ReadConfig(); 16 | 17 | try 18 | { 19 | var vault = Vault.Open(config["username"], config["account-password"], config["vault-password"]); 20 | for (var i = 0; i < vault.Accounts.Length; ++i) 21 | { 22 | var a = vault.Accounts[i]; 23 | Console.WriteLine("{0}: {1} {2} {3} {4} {5}", i + 1, a.Id, a.Name, a.Url, a.Notes, a.Folder); 24 | 25 | for (var j = 0; j < a.Credentials.Length; ++j) 26 | { 27 | var c = a.Credentials[j]; 28 | Console.WriteLine(" - {0}: {1} {2} {3}:{4} ({5})", j + 1, c.Id, c.Name, c.Username, c.Password, c.Notes); 29 | } 30 | } 31 | } 32 | catch (BaseException e) 33 | { 34 | Util.PrintException(e); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/Kaspersky/config.yaml.example: -------------------------------------------------------------------------------- 1 | username: account@example.com 2 | account-password: password123 3 | vault-password: password123 4 | -------------------------------------------------------------------------------- /example/Kdbx/Kdbx.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Exe 11 | net8.0 12 | 12 13 | false 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/Kdbx/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System; 5 | using System.Linq; 6 | using PasswordManagerAccess.Common; 7 | using PasswordManagerAccess.Example.Common; 8 | using PasswordManagerAccess.Kdbx; 9 | 10 | namespace PasswordManagerAccess.Example.Kdbx 11 | { 12 | public static class Program 13 | { 14 | static void Main(string[] args) 15 | { 16 | var config = Util.ReadConfig(); 17 | 18 | try 19 | { 20 | var keyfile = config.ContainsKey("keyfile") ? config["keyfile"] : null; 21 | var accounts = Vault.Open(config["path"], config["password"], keyfile).Accounts; 22 | for (var i = 0; i < accounts.Length; ++i) 23 | { 24 | var account = accounts[i]; 25 | Console.WriteLine( 26 | " - {0}: {1} {2} {3} {4} {5} {6} {7} {{{8}}}", 27 | i + 1, 28 | account.Id, 29 | account.Name, 30 | account.Username, 31 | account.Password, 32 | account.Url, 33 | account.Note, 34 | account.Path, 35 | string.Join(", ", account.Fields.Select(x => $"{x.Key}={x.Value}")) 36 | ); 37 | } 38 | } 39 | catch (BaseException e) 40 | { 41 | Util.PrintException(e); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /example/Kdbx/config.yaml.example: -------------------------------------------------------------------------------- 1 | path: path/to/kdbx 2 | password: passw0rd! 3 | keyfile: path/to/optional/keyfile 4 | -------------------------------------------------------------------------------- /example/LastPass/LastPass.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Exe 12 | net8.0 13 | 12 14 | false 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/LastPass/config.yaml.example: -------------------------------------------------------------------------------- 1 | username: account@example.com 2 | password: password123 3 | client-id: 385e2742aefd399bd182c1ea4c1aac4b 4 | client-description: Example for lastpass-sharp 5 | -------------------------------------------------------------------------------- /example/LastPassAvalonia/App.axaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/LastPassAvalonia/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Markup.Xaml; 4 | 5 | namespace LastPassAvalonia; 6 | 7 | public partial class App : Application 8 | { 9 | public override void Initialize() 10 | { 11 | AvaloniaXamlLoader.Load(this); 12 | } 13 | 14 | public override void OnFrameworkInitializationCompleted() 15 | { 16 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 17 | { 18 | desktop.MainWindow = new MainWindow { DataContext = new MainWindowViewModel() }; 19 | } 20 | 21 | base.OnFrameworkInitializationCompleted(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/LastPassAvalonia/LastPassAvalonia.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net8.0 5 | enable 6 | true 7 | app.manifest 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | None 20 | All 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /example/LastPassAvalonia/MainWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Avalonia.Controls; 3 | 4 | namespace LastPassAvalonia; 5 | 6 | public partial class MainWindow : Window 7 | { 8 | public MainWindow() 9 | { 10 | InitializeComponent(); 11 | 12 | // For some reason the widnow often opens in the background. Bring it to the front! 13 | Opened += async (_, _) => 14 | { 15 | await Task.Delay(1000); 16 | Activate(); 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/LastPassAvalonia/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.ReactiveUI; 4 | 5 | namespace LastPassAvalonia; 6 | 7 | class Program 8 | { 9 | // Initialization code. Don't use any Avalonia, third-party APIs or any 10 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 11 | // yet and stuff might break. 12 | [STAThread] 13 | public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); 14 | 15 | // Avalonia configuration, don't remove; also used by visual designer. 16 | public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure().UsePlatformDetect().WithInterFont().LogToTrace().UseReactiveUI(); 17 | } 18 | -------------------------------------------------------------------------------- /example/LastPassAvalonia/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using ReactiveUI; 5 | 6 | namespace LastPassAvalonia 7 | { 8 | public class ViewModelBase : ReactiveObject { } 9 | } 10 | -------------------------------------------------------------------------------- /example/LastPassAvalonia/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/OnePassword/OnePassword.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Exe 11 | net8.0 12 | 12 13 | false 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/OnePassword/config.yaml.example: -------------------------------------------------------------------------------- 1 | username: someone@gmail.com 2 | password: passw0rd! 3 | account-key: A3-RTN9SA-DY9445Y5FF96X6E7B5GPFA95R9 4 | device-id: gvcwm2onr66yune47arn4qhq4z 5 | domain: my.1password.eu 6 | -------------------------------------------------------------------------------- /example/OpVault/OpVault.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Exe 11 | net8.0 12 | 12 13 | false 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/OpVault/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System; 5 | using PasswordManagerAccess.Example.Common; 6 | using PasswordManagerAccess.OpVault; 7 | 8 | namespace PasswordManagerAccess.Example.OpVault 9 | { 10 | public static class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | var config = Util.ReadConfig(); 15 | 16 | // Use one from the tests by default 17 | if (!config.ContainsKey("path") || !config.ContainsKey("password")) 18 | { 19 | config["path"] = "../../../../../test/OpVault/Fixtures/test.opvault"; 20 | config["password"] = "password"; 21 | } 22 | 23 | var accounts = Vault.Open(config["path"], config["password"]); 24 | for (var i = 0; i < accounts.Length; ++i) 25 | { 26 | var account = accounts[i]; 27 | Console.WriteLine( 28 | " - {0}: {1} {2} {3} {4} {5} {6} {7}", 29 | i + 1, 30 | account.Id, 31 | account.Name, 32 | account.Username, 33 | account.Password, 34 | account.Url, 35 | account.Note, 36 | account.Folder.Name 37 | ); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/OpVault/config.yaml.example: -------------------------------------------------------------------------------- 1 | path: path/to/opvault/ 2 | password: passw0rd! 3 | -------------------------------------------------------------------------------- /example/ProtonPass/ProtonPass.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Exe 12 | net8.0 13 | 12 14 | false 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/ProtonPass/config.yaml.example: -------------------------------------------------------------------------------- 1 | username: account@example.com 2 | password: password123 3 | 4 | # Optional. When it's not set, the UI will ask for the extra password if needed 5 | extra-password: extra-password123 6 | -------------------------------------------------------------------------------- /example/RoboForm/RoboForm.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Exe 11 | net8.0 12 | 12 13 | false 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/RoboForm/config.yaml.example: -------------------------------------------------------------------------------- 1 | username: someone@gmail.com 2 | password: passw0rd! 3 | device-id: B00112233445566778899aabbccddeeff 4 | -------------------------------------------------------------------------------- /example/StickyPassword/StickyPassword.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Exe 11 | net8.0 12 | 12 13 | false 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/StickyPassword/config.yaml.example: -------------------------------------------------------------------------------- 1 | username: someone@gmail.com 2 | password: passw0rd! 3 | -------------------------------------------------------------------------------- /example/TrueKey/TrueKey.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Exe 11 | net8.0 12 | 12 13 | false 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/TrueKey/config.yaml.example: -------------------------------------------------------------------------------- 1 | username: someone@gmail.com 2 | password: passw0rd! 3 | -------------------------------------------------------------------------------- /example/ZohoVault/ZohoVault.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Exe 11 | net8.0 12 | 12 13 | false 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/ZohoVault/config.yaml.example: -------------------------------------------------------------------------------- 1 | username: someone@gmail.com 2 | password: passw0rd! 3 | passphrase: pass-fraze! 4 | 5 | # Optional 6 | google-auth-totp-secret: ALNBLRLVX2FDMPSD 7 | -------------------------------------------------------------------------------- /password-manager-access.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 3 | Licensed under the terms of the MIT license. See LICENCE for details. 4 | True 5 | True 6 | True 7 | 8 | -------------------------------------------------------------------------------- /src/Bitwarden/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Bitwarden 5 | { 6 | public class Account : VaultItem 7 | { 8 | // TODO: Add 'required' modifier to all properties 9 | public string Username { get; init; } 10 | public string Password { get; init; } 11 | public string Url { get; init; } 12 | public string Totp { get; init; } 13 | 14 | // 15 | // Internal 16 | // 17 | 18 | internal Account(VaultItem item) 19 | { 20 | Id = item.Id; 21 | Name = item.Name; 22 | Note = item.Note; 23 | DeletedDate = item.DeletedDate; 24 | Folder = item.Folder; 25 | CollectionIds = item.CollectionIds; 26 | HidePassword = item.HidePassword; 27 | CustomFields = item.CustomFields; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Bitwarden/ClientInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Bitwarden 5 | { 6 | public class ClientInfoBrowser 7 | { 8 | public readonly string Username; 9 | public readonly string Password; 10 | public readonly string DeviceId; 11 | 12 | public ClientInfoBrowser(string username, string password, string deviceId) 13 | { 14 | Username = username; 15 | Password = password; 16 | DeviceId = deviceId; 17 | } 18 | } 19 | 20 | public class ClientInfoCliApi 21 | { 22 | public readonly string ClientId; 23 | public readonly string ClientSecret; 24 | public readonly string Password; 25 | public readonly string DeviceId; 26 | 27 | public ClientInfoCliApi(string clientId, string clientSecret, string password, string deviceId) 28 | { 29 | ClientId = clientId; 30 | ClientSecret = clientSecret; 31 | Password = password; 32 | DeviceId = deviceId; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Bitwarden/Collection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Bitwarden 5 | { 6 | public class Collection 7 | { 8 | public readonly string Id; 9 | public readonly string Name; 10 | public readonly string OrganizationId; 11 | public readonly bool HidePasswords; 12 | 13 | public Collection(string id, string name, string organizationId, bool hidePasswords) 14 | { 15 | Id = id; 16 | Name = name; 17 | OrganizationId = organizationId; 18 | HidePasswords = hidePasswords; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Bitwarden/NoItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Bitwarden; 5 | 6 | public enum NoItem 7 | { 8 | NotFound, 9 | UnsupportedType, 10 | } 11 | -------------------------------------------------------------------------------- /src/Bitwarden/Organization.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Bitwarden 5 | { 6 | public class Organization 7 | { 8 | public readonly string Id; 9 | public readonly string Name; 10 | 11 | public Organization(string id, string name) 12 | { 13 | Id = id; 14 | Name = name; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Bitwarden/ParseError.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Bitwarden 5 | { 6 | public class ParseError 7 | { 8 | public readonly string Description; 9 | public readonly string Message; 10 | public readonly string CallStack; 11 | 12 | public ParseError(string description, string message, string callStack) 13 | { 14 | Description = description; 15 | Message = message; 16 | CallStack = callStack; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Bitwarden/Session.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | #nullable enable 5 | 6 | using System.Collections.Generic; 7 | using PasswordManagerAccess.Common; 8 | 9 | namespace PasswordManagerAccess.Bitwarden 10 | { 11 | // The session object is opaque to the user. It holds all the state needed by Client to perform 12 | // various operations like downloading vaults or logging out. 13 | public class Session 14 | { 15 | internal string Token { get; } 16 | internal byte[] Key { get; } 17 | internal Profile Profile { get; } 18 | internal RestClient Rest { get; } 19 | internal IRestTransport Transport { get; } 20 | 21 | // Set lazily if/when needed 22 | internal Dictionary? Folders { get; set; } 23 | internal Dictionary? Collections { get; set; } 24 | 25 | internal Session(string token, byte[] key, Profile profile, RestClient rest, IRestTransport transport) 26 | { 27 | Token = token; 28 | Key = key; 29 | Profile = profile; 30 | Rest = rest; 31 | Transport = transport; 32 | } 33 | } 34 | 35 | // TODO: Move to Model maybe? 36 | internal record Profile(byte[] VaultKey, byte[] PrivateKey, Dictionary Organizations, Dictionary OrgKeys); 37 | } 38 | -------------------------------------------------------------------------------- /src/Bitwarden/SshKey.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Bitwarden 5 | { 6 | public class SshKey : VaultItem 7 | { 8 | // TODO: Add 'required' modifier to all properties 9 | public string PublicKey { get; init; } 10 | public string PrivateKey { get; init; } 11 | public string Fingerprint { get; init; } 12 | 13 | // 14 | // Internal 15 | // 16 | 17 | internal SshKey(VaultItem item) 18 | { 19 | Id = item.Id; 20 | Name = item.Name; 21 | Note = item.Note; 22 | DeletedDate = item.DeletedDate; 23 | Folder = item.Folder; 24 | CollectionIds = item.CollectionIds; 25 | HidePassword = item.HidePassword; 26 | CustomFields = item.CustomFields; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Bitwarden/Ui.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Duo; 5 | 6 | namespace PasswordManagerAccess.Bitwarden.Ui 7 | { 8 | public interface IUi : IDuoUi 9 | { 10 | // The UI will no longer be used and could be closed 11 | public abstract void Close(); 12 | 13 | // To cancel return Method.Cancel (always available) 14 | MfaMethod ChooseMfaMethod(MfaMethod[] availableMethods); 15 | 16 | // To cancel any of these return null 17 | Passcode ProvideGoogleAuthPasscode(); 18 | Passcode ProvideEmailPasscode(string emailHint); 19 | Passcode ProvideYubiKeyPasscode(); 20 | } 21 | 22 | public enum MfaMethod 23 | { 24 | // Always available 25 | Cancel, 26 | 27 | GoogleAuth, 28 | Email, 29 | Duo, 30 | YubiKey, 31 | U2f, 32 | DuoOrg, 33 | } 34 | 35 | public class Passcode 36 | { 37 | public readonly string Code; 38 | public readonly bool RememberMe; 39 | 40 | public Passcode(string code, bool rememberMe) 41 | { 42 | Code = code; 43 | RememberMe = rememberMe; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Bitwarden/Vault.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | #nullable enable 5 | 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace PasswordManagerAccess.Bitwarden 10 | { 11 | public record Vault(Account[] Accounts, SshKey[] SshKeys, Collection[] Collections, Organization[] Organizations, ParseError[] ParseErrors) 12 | { 13 | public IReadOnlyDictionary CollectionsById => _collectionsById ??= Collections.ToDictionary(c => c.Id); 14 | public IReadOnlyDictionary OrganizationsById => _organizationsById ??= Organizations.ToDictionary(o => o.Id); 15 | 16 | // 17 | // Private 18 | // 19 | 20 | private IReadOnlyDictionary? _collectionsById; 21 | private IReadOnlyDictionary? _organizationsById; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Bitwarden/VaultItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Bitwarden 5 | { 6 | public class VaultItem 7 | { 8 | public string Id { get; init; } 9 | public string Name { get; init; } 10 | public string Note { get; init; } 11 | public string DeletedDate { get; init; } 12 | public string Folder { get; init; } 13 | public string[] CollectionIds { get; init; } 14 | public bool HidePassword { get; init; } 15 | public CustomField[] CustomFields { get; init; } 16 | } 17 | 18 | public record CustomField(string Name, string Value) { } 19 | } 20 | -------------------------------------------------------------------------------- /src/Common/Debug.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | //#define PROFILE_ENABLED 5 | 6 | using System; 7 | using System.Diagnostics; 8 | using System.Runtime.CompilerServices; 9 | 10 | namespace PasswordManagerAccess.Common 11 | { 12 | internal static class Debug 13 | { 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | public static void Measure(string name, Action f) 16 | { 17 | #if PROFILE_ENABLED 18 | Measure( 19 | name, 20 | () => 21 | { 22 | f(); 23 | return 0; 24 | } 25 | ); 26 | #else 27 | f(); 28 | #endif 29 | } 30 | 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | public static T Measure(string name, Func f) 33 | { 34 | #if PROFILE_ENABLED 35 | var sw = new Stopwatch(); 36 | sw.Start(); 37 | try 38 | { 39 | return f(); 40 | } 41 | finally 42 | { 43 | sw.Stop(); 44 | Console.WriteLine($"{name}: {sw.Elapsed}"); 45 | } 46 | #else 47 | return f(); 48 | #endif 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Common/ExposeInternals.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System.Runtime.CompilerServices; 5 | 6 | [assembly: InternalsVisibleTo("PasswordManagerAccess.Test")] 7 | -------------------------------------------------------------------------------- /src/Common/Hkdf.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System.Security.Cryptography; 5 | 6 | namespace PasswordManagerAccess.Common 7 | { 8 | internal static class Hkdf 9 | { 10 | public static byte[] Sha1(byte[] ikm, byte[] salt, byte[] info, int byteCount) => 11 | DeriveKey(ikm, salt, info, byteCount, HashAlgorithmName.SHA1); 12 | 13 | public static byte[] Sha256(byte[] ikm, byte[] salt, byte[] info, int byteCount) => 14 | DeriveKey(ikm, salt, info, byteCount, HashAlgorithmName.SHA256); 15 | 16 | public static byte[] Sha384(byte[] ikm, byte[] salt, byte[] info, int byteCount) => 17 | DeriveKey(ikm, salt, info, byteCount, HashAlgorithmName.SHA384); 18 | 19 | public static byte[] Sha512(byte[] ikm, byte[] salt, byte[] info, int byteCount) => 20 | DeriveKey(ikm, salt, info, byteCount, HashAlgorithmName.SHA512); 21 | 22 | // 23 | // Internal 24 | // 25 | 26 | internal static byte[] DeriveKey(byte[] ikm, byte[] salt, byte[] info, int byteCount, HashAlgorithmName hash) 27 | { 28 | if (byteCount < 0) 29 | throw new InternalErrorException("Byte count should be nonnegative"); 30 | 31 | return HKDF.DeriveKey(hash, ikm, byteCount, salt, info); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Common/IAsyncSecureStorage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | #nullable enable 5 | 6 | using System.Threading.Tasks; 7 | 8 | namespace PasswordManagerAccess.Common 9 | { 10 | public interface IAsyncSecureStorage 11 | { 12 | // Returns null if no value exists 13 | Task LoadString(string name); 14 | 15 | // Pass null to delete the value 16 | Task StoreString(string name, string? value); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Common/ISecureStorage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Common 5 | { 6 | // This is the interface to be implemented by the user of the library. 7 | // It's used to securely store things like the second factor token between 8 | // sessions when "remember me" option is used. 9 | public interface ISecureStorage 10 | { 11 | // Returns null if no value exists 12 | string LoadString(string name); 13 | 14 | // Pass null to delete the value 15 | void StoreString(string name, string value); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Common/Indisposable.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System; 5 | 6 | namespace PasswordManagerAccess.Common 7 | { 8 | // This is an IDisposable wrapper for any regular non-disposable class so it could be 9 | // assigned to "using var" variable. It could be used to mix disposable and regular objects. 10 | // For example: 11 | // using IDisposable obj = isRegular ? new Indisposable(new Regular()) : new Disposable(); 12 | internal readonly struct Indisposable : IDisposable 13 | { 14 | public T Value { get; } 15 | 16 | public Indisposable(T t) 17 | { 18 | Value = t; 19 | } 20 | 21 | public static implicit operator T(Indisposable i) => i.Value; 22 | 23 | public void Dispose() 24 | { 25 | // Nothing to dispose of 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Common/Json.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | 7 | namespace PasswordManagerAccess.Common 8 | { 9 | internal static class Json 10 | { 11 | public static bool TryParse(string json, out JObject o) 12 | { 13 | try 14 | { 15 | o = JObject.Parse(json); 16 | return true; 17 | } 18 | catch (JsonException) 19 | { 20 | o = null; 21 | return false; 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Common/MfaMethod.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Common; 5 | 6 | public enum MfaMethod 7 | { 8 | None, 9 | 10 | // OTP 11 | GoogleAuthenticator, 12 | MicrosoftAuthenticator, 13 | YubikeyOtp, 14 | 15 | // OTP-like 16 | U2F, 17 | Fido2, 18 | WebAuthn, 19 | 20 | // OOB 21 | Duo, 22 | LastPassAuthenticator, 23 | } 24 | 25 | public static class MfaMethodExtensions 26 | { 27 | public static string GetName(this MfaMethod method) => 28 | method switch 29 | { 30 | MfaMethod.None => "None", 31 | MfaMethod.GoogleAuthenticator => "Google Authenticator", 32 | MfaMethod.MicrosoftAuthenticator => "Microsoft Authenticator", 33 | MfaMethod.YubikeyOtp => "Yubikey OTP", 34 | MfaMethod.U2F => "U2F", 35 | MfaMethod.WebAuthn => "WebAuthn", 36 | MfaMethod.LastPassAuthenticator => "LastPass Authenticator", 37 | MfaMethod.Duo => "Duo", 38 | _ => "Unknown", 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/Common/NullStorage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Common 5 | { 6 | // Secure storage that is safe to use to disable the "remember me" feature. 7 | // Simply pass `new NullStorage()` to `Vault.Open` of any of the password managers 8 | // when the "remember me" feature is not needed. 9 | public class NullStorage : ISecureStorage 10 | { 11 | public string LoadString(string name) 12 | { 13 | return null; 14 | } 15 | 16 | public void StoreString(string name, string value) { } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Common/Os.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System; 5 | 6 | namespace PasswordManagerAccess.Common 7 | { 8 | internal static class Os 9 | { 10 | public static uint UnixSeconds() 11 | { 12 | return (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); 13 | } 14 | 15 | public static long UnixMilliseconds() 16 | { 17 | return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Common/Rental.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System; 5 | using System.Buffers; 6 | 7 | namespace PasswordManagerAccess.Common 8 | { 9 | internal readonly struct Rental : IDisposable 10 | { 11 | public byte[] Buffer { get; } 12 | 13 | public static Rental Rent(int size) => new Rental(ArrayPool.Shared.Rent(size)); 14 | 15 | public static implicit operator byte[](Rental r) => r.Buffer; 16 | 17 | public static implicit operator ReadOnlySpan(Rental r) => r.Buffer.AsRoSpan(); 18 | 19 | public static T With(int size, Func f) 20 | { 21 | using var rental = Rent(size); 22 | return f(rental); 23 | } 24 | 25 | public static void With(int size, Action f) 26 | { 27 | using var rental = Rent(size); 28 | f(rental); 29 | } 30 | 31 | public void Dispose() 32 | { 33 | ArrayPool.Shared.Return(Buffer); 34 | } 35 | 36 | private Rental(byte[] buffer) 37 | { 38 | Buffer = buffer; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Common/Url.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System; 5 | 6 | namespace PasswordManagerAccess.Common 7 | { 8 | internal static class Url 9 | { 10 | // Returns null when not found 11 | internal static string ExtractQueryParameter(string url, string name) 12 | { 13 | var nameEquals = name + '='; 14 | var start = url.IndexOf(nameEquals, StringComparison.Ordinal); 15 | if (start < 0) 16 | return null; 17 | 18 | start += nameEquals.Length; 19 | var end = url.IndexOf('&', start); 20 | 21 | return end < 0 22 | ? url.Substring(start) // The last parameter 23 | : url.Substring(start, end - start); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Common/WarningFree.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Common; 5 | 6 | internal static class WarningFree 7 | { 8 | public static readonly bool AlwaysTrue = true; 9 | public static readonly bool AlwaysFalse = false; 10 | } 11 | -------------------------------------------------------------------------------- /src/Dashlane/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Dashlane 5 | { 6 | public class Account 7 | { 8 | public Account(string id, string name, string username, string password, string url, string note) 9 | { 10 | Id = id; 11 | Name = name; 12 | Username = username; 13 | Password = password; 14 | Url = url; 15 | Note = note; 16 | } 17 | 18 | public string Id { get; private set; } 19 | public string Name { get; private set; } 20 | public string Username { get; private set; } 21 | public string Password { get; private set; } 22 | public string Url { get; private set; } 23 | public string Note { get; private set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Dashlane/Ui.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Dashlane 5 | { 6 | public abstract class Ui 7 | { 8 | // 9 | // MFA 10 | // 11 | 12 | // Passcode result 13 | public class Passcode 14 | { 15 | // Return this to signal the cancellation of the operation 16 | public static readonly Passcode Cancel = new Passcode("cancel", false); 17 | 18 | // Return this to resend the email token (not valid for Google Auth) 19 | public static readonly Passcode Resend = new Passcode("resend", false); 20 | 21 | public readonly string Code; 22 | public readonly bool RememberMe; 23 | 24 | public Passcode(string code, bool rememberMe) 25 | { 26 | Code = code; 27 | RememberMe = rememberMe; 28 | } 29 | } 30 | 31 | // To cancel return Passcode.Cancel 32 | // DO NOT return Passcode.Resend, it's only valid for email passcode 33 | public abstract Passcode ProvideGoogleAuthPasscode(int attempt); 34 | 35 | // To cancel return Passcode.Cancel 36 | // To resend the token return Passcode.Resend 37 | public abstract Passcode ProvideEmailPasscode(int attempt); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/DropboxPasswords/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | #nullable enable 5 | 6 | namespace PasswordManagerAccess.DropboxPasswords 7 | { 8 | public class Account 9 | { 10 | public readonly string Id; 11 | public readonly string Name; 12 | public readonly string Username; 13 | public readonly string Password; 14 | public readonly string Url; 15 | public readonly string Note; 16 | public readonly string Folder; 17 | 18 | public Account(string id, string name, string username, string password, string url, string note, string folder) 19 | { 20 | Id = id; 21 | Name = name; 22 | Username = username; 23 | Password = password; 24 | Url = url; 25 | Note = note; 26 | Folder = folder; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/DropboxPasswords/ClientInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.DropboxPasswords 5 | { 6 | public class ClientInfo 7 | { 8 | public readonly string DeviceId; 9 | public readonly string DeviceName; 10 | 11 | public ClientInfo(string deviceId, string deviceName) 12 | { 13 | DeviceId = deviceId; 14 | DeviceName = deviceName; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/DropboxPasswords/IUi.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | #nullable enable 5 | 6 | namespace PasswordManagerAccess.DropboxPasswords 7 | { 8 | public interface IUi 9 | { 10 | // Returns the redirect URL with the code. null if canceled or errored. 11 | string PerformOAuthLogin(string url, string redirectUrl); 12 | 13 | // Tell the user that the enrollment request is about to be sent to other enrolled devices. 14 | // Could be used to tell the user to prepare their devices, like opening the browser extension to make sure 15 | // it is ready to receive the request. The list of devices is unfortunately not available at this point. 16 | void WillSendEnrollRequest(); 17 | 18 | // Tell the user that the enrollment request has been sent to other enrolled devices. 19 | void EnrollRequestSent(string[] deviceNames); 20 | 21 | enum Action 22 | { 23 | KeepWaiting, 24 | ResendRequest, 25 | Cancel, 26 | } 27 | 28 | // We waited for the user to confirm the enrollment on another device and it timed out. 29 | // Chances are the user didn't see the request. Ask what to do next. 30 | // Return: 31 | // - Action.KeepWaiting to do another long poll and keep waiting 32 | // - Action.ResendRequest to resend the request and keep waiting 33 | // - Action.Cancel to cancel the enrollment and abort with an error 34 | Action AskForNextAction(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/DropboxPasswords/Vault.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System; 5 | using PasswordManagerAccess.Common; 6 | 7 | #nullable enable 8 | 9 | namespace PasswordManagerAccess.DropboxPasswords 10 | { 11 | public class Vault 12 | { 13 | public readonly Account[] Accounts; 14 | 15 | public static Vault Open(ClientInfo clientInfo, IUi ui, ISecureStorage storage) 16 | { 17 | return Open(clientInfo, Array.Empty(), ui, storage); 18 | } 19 | 20 | public static Vault Open(ClientInfo clientInfo, string[] recoveryWords, IUi ui, ISecureStorage storage) 21 | { 22 | using var transport = new RestTransport(); 23 | return new Vault(Client.OpenVault(clientInfo, recoveryWords, ui, storage, transport)); 24 | } 25 | 26 | public static string GenerateRandomDeviceId() 27 | { 28 | return Guid.NewGuid().ToString().ToUpper(); 29 | } 30 | 31 | // 32 | // Internal 33 | // 34 | 35 | internal Vault(Account[] accounts) 36 | { 37 | Accounts = accounts; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Duo/DuoResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Duo 5 | { 6 | internal record DuoResult(string Code, string State, bool RememberMe) 7 | { 8 | // This is returned what the V4 URL returns a redirect to the V1 api. This could happen when the traditional 9 | // prompt is enabled in the Duo admin panel. 10 | public static readonly DuoResult RedirectToV1 = new("redirect-to-v1", "redirect-to-v1", false); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Duo/Response.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using Newtonsoft.Json; 5 | 6 | namespace PasswordManagerAccess.Duo.Response 7 | { 8 | public struct Envelope 9 | { 10 | [JsonProperty("stat", Required = Required.Always)] 11 | public string Status; 12 | 13 | [JsonProperty("message")] 14 | public string Message; 15 | 16 | [JsonProperty("response")] 17 | public T Payload; 18 | } 19 | 20 | public class SubmitFactor 21 | { 22 | [JsonProperty("txid")] 23 | public string TransactionId; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Duo/ResponseV1.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using Newtonsoft.Json; 5 | 6 | namespace PasswordManagerAccess.Duo.ResponseV1 7 | { 8 | public class Status 9 | { 10 | [JsonProperty("result")] 11 | public string Result; 12 | 13 | [JsonProperty("status")] 14 | public string Message; 15 | } 16 | 17 | public class FetchToken : Status 18 | { 19 | [JsonProperty("cookie")] 20 | public string Cookie; 21 | } 22 | 23 | public class Poll : Status 24 | { 25 | [JsonProperty("result_url")] 26 | public string Url; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Duo/ResponseV4.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using Newtonsoft.Json; 5 | 6 | namespace PasswordManagerAccess.Duo.ResponseV4 7 | { 8 | public class Data 9 | { 10 | [JsonProperty("phones")] 11 | public Phone[] Phones; 12 | 13 | [JsonProperty("auth_method_order")] 14 | public Method[] Methods; 15 | } 16 | 17 | public class Phone 18 | { 19 | [JsonProperty("index")] 20 | public string Id; 21 | 22 | [JsonProperty("name")] 23 | public string Name; 24 | 25 | [JsonProperty("key")] 26 | public string Key; 27 | 28 | [JsonProperty("next_passcode")] 29 | public string NextPasscode; 30 | } 31 | 32 | public class Method 33 | { 34 | [JsonProperty("deviceKey")] 35 | public string DeviceKey; 36 | 37 | [JsonProperty("factor")] 38 | public string Factor; 39 | } 40 | 41 | public class Status 42 | { 43 | [JsonProperty("status_code")] 44 | public string Code; 45 | 46 | [JsonProperty("result")] 47 | public string Result; 48 | 49 | [JsonProperty("reason")] 50 | public string Reason; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Kaspersky/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Kaspersky 5 | { 6 | public class Account 7 | { 8 | public readonly string Id; 9 | public readonly string Name; 10 | public readonly string Url; 11 | public readonly string Notes; 12 | public readonly string Folder; 13 | public Credentials[] Credentials { get; internal set; } 14 | 15 | public Account(string id, string name, string url, string notes, string folder, Credentials[] credentials) 16 | { 17 | Id = id; 18 | Name = name; 19 | Url = url; 20 | Notes = notes; 21 | Folder = folder; 22 | Credentials = credentials; 23 | } 24 | } 25 | 26 | public class Credentials 27 | { 28 | public readonly string Id; 29 | public readonly string Name; 30 | public readonly string Username; 31 | public readonly string Password; 32 | public readonly string Notes; 33 | 34 | public Credentials(string id, string name, string username, string password, string notes) 35 | { 36 | Id = id; 37 | Name = name; 38 | Username = username; 39 | Password = password; 40 | Notes = notes; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Kaspersky/DatabaseInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | 6 | namespace PasswordManagerAccess.Kaspersky 7 | { 8 | internal readonly struct DatabaseInfo 9 | { 10 | public readonly int Version; 11 | public readonly int Iterations; 12 | public readonly byte[] Salt; 13 | 14 | public static DatabaseInfo Parse(byte[] blob) 15 | { 16 | if (blob.Length < 24) 17 | throw new InternalErrorException($"Blob is too short ({blob.Length})"); 18 | 19 | return blob.Open(r => 20 | { 21 | var version = r.ReadInt32(); 22 | var iterations = r.ReadInt32(); 23 | var salt = r.ReadBytes(16); 24 | 25 | return new DatabaseInfo(version, iterations, salt); 26 | }); 27 | } 28 | 29 | internal DatabaseInfo(int version, int iterations, byte[] salt) 30 | { 31 | Version = version; 32 | Iterations = iterations; 33 | Salt = salt; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Kaspersky/IBoshTransport.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | #nullable enable 5 | 6 | using System; 7 | using PasswordManagerAccess.Common; 8 | 9 | namespace PasswordManagerAccess.Kaspersky 10 | { 11 | internal interface IBoshTransport : IDisposable 12 | { 13 | Exception? Connect(string url); 14 | Try Request(string body, int expectedMessageCount); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Kaspersky/Jid.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Kaspersky 5 | { 6 | internal class Jid 7 | { 8 | public readonly string UserId; 9 | public readonly string Host; 10 | public readonly string Resource; 11 | 12 | public readonly string Node; 13 | public readonly string Bare; 14 | public readonly string Full; 15 | 16 | public Jid(string userId, string host, string resource) 17 | { 18 | UserId = userId; 19 | Host = host; 20 | Resource = resource; 21 | 22 | Node = $"{UserId}#{Client.DeviceKind}#{Client.ServiceId}"; 23 | Bare = $"{Node}@{Host}"; 24 | Full = $"{Bare}/{Resource}"; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Kaspersky/Response.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using Newtonsoft.Json; 5 | 6 | namespace PasswordManagerAccess.Kaspersky.Response 7 | { 8 | internal class Result 9 | { 10 | [JsonProperty("Status", Required = Required.Always)] 11 | public readonly string Status; 12 | } 13 | 14 | internal class Start : Result 15 | { 16 | [JsonProperty("LogonContext", Required = Required.Always)] 17 | public readonly string Context; 18 | } 19 | 20 | internal class UserToken 21 | { 22 | [JsonProperty("UserToken", Required = Required.Always)] 23 | public readonly string Token; 24 | 25 | [JsonProperty("TokenType", Required = Required.Always)] 26 | public readonly string Type; 27 | } 28 | 29 | internal class XmppSettings 30 | { 31 | [JsonProperty("userId", Required = Required.Always)] 32 | public readonly string UserId; 33 | 34 | [JsonProperty("xmppLibraryUrls", Required = Required.Always)] 35 | public readonly string[] XmppLibraryUrls; 36 | 37 | [JsonProperty("xmppCredentials", Required = Required.Always)] 38 | public readonly XmppCredentials XmppCredentials; 39 | } 40 | 41 | internal readonly struct XmppCredentials 42 | { 43 | [JsonProperty("userId", Required = Required.Always)] 44 | public readonly string UserId; 45 | 46 | [JsonProperty("password", Required = Required.Always)] 47 | public readonly string Password; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Kaspersky/Util.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System.Text; 5 | using PasswordManagerAccess.Common; 6 | 7 | namespace PasswordManagerAccess.Kaspersky 8 | { 9 | internal class Util 10 | { 11 | internal static byte[] DeriveMasterPasswordAuthKey(string userId, byte[] encryptionKey, DatabaseInfo databaseInfo) 12 | { 13 | return Pbkdf2.GenerateSha256(password: encryptionKey, salt: Encoding.Unicode.GetBytes(userId), iterationCount: 1500, byteCount: 64); 14 | } 15 | 16 | internal static byte[] DeriveEncryptionKey(string password, DatabaseInfo databaseInfo) 17 | { 18 | return Crypto.Pbkdf2Sha256(password: password, salt: databaseInfo.Salt, iterations: databaseInfo.Iterations, byteCount: 32); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Kaspersky/Vault.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | 6 | namespace PasswordManagerAccess.Kaspersky 7 | { 8 | public class Vault 9 | { 10 | public readonly Account[] Accounts; 11 | 12 | public static Vault Open(string username, string accountPassword, string vaultPassword) 13 | { 14 | using var restTransport = new RestTransport(); 15 | using var boshTransport = new WebSocketBoshTransport(); 16 | return Open(username, accountPassword, vaultPassword, restTransport, boshTransport); 17 | } 18 | 19 | // 20 | // Internal 21 | // 22 | 23 | internal static Vault Open( 24 | string username, 25 | string accountPassword, 26 | string vaultPassword, 27 | IRestTransport restTransport, 28 | IBoshTransport boshTransport 29 | ) 30 | { 31 | return new Vault(Client.OpenVault(username, accountPassword, vaultPassword, restTransport, boshTransport)); 32 | } 33 | 34 | // 35 | // Private 36 | // 37 | 38 | private Vault(Account[] accounts) 39 | { 40 | Accounts = accounts; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Kdbx/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | 7 | namespace PasswordManagerAccess.Kdbx 8 | { 9 | public class Account 10 | { 11 | public readonly string Id; 12 | public readonly string Name; 13 | public readonly string Username; 14 | public readonly string Password; 15 | public readonly string Url; 16 | public readonly string Note; 17 | public readonly string Path; 18 | public readonly IReadOnlyDictionary Fields; 19 | 20 | public Account( 21 | string id, 22 | string name, 23 | string username, 24 | string password, 25 | string url, 26 | string note, 27 | string path, 28 | IReadOnlyDictionary fields 29 | ) 30 | { 31 | Id = id; 32 | Name = name; 33 | Username = username; 34 | Password = password; 35 | Url = url; 36 | Note = note; 37 | Path = path; 38 | Fields = fields; 39 | } 40 | 41 | internal static readonly IReadOnlyDictionary NoFields = new ReadOnlyDictionary( 42 | new Dictionary(0) 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Kdbx/Vault.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System.IO; 5 | 6 | namespace PasswordManagerAccess.Kdbx 7 | { 8 | public class Vault 9 | { 10 | public readonly Account[] Accounts; 11 | 12 | public static Vault Open(string filename, string password, string keyfile = null) 13 | { 14 | return new Vault(Parser.Parse(filename, password, keyfile)); 15 | } 16 | 17 | public static Vault Open(byte[] input, string password, byte[] keyfile = null) 18 | { 19 | return new Vault(Parser.Parse(input, password, keyfile)); 20 | } 21 | 22 | public static Vault Open(Stream input, string password, Stream keyfile = null) 23 | { 24 | return new Vault(Parser.Parse(input, password, keyfile)); 25 | } 26 | 27 | // 28 | // Internal 29 | // 30 | 31 | internal Vault(Account[] accounts) 32 | { 33 | Accounts = accounts; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/LastPass/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.LastPass 5 | { 6 | public class Account 7 | { 8 | public readonly string Id; 9 | public readonly string Name; 10 | public readonly string Username; 11 | public readonly string Password; 12 | public readonly string Url; 13 | public readonly string Path; 14 | public readonly string Notes; 15 | public readonly string Totp; 16 | public readonly bool IsFavorite; 17 | public readonly bool IsShared; 18 | 19 | public Account( 20 | string id, 21 | string name, 22 | string username, 23 | string password, 24 | string url, 25 | string path, 26 | string notes, 27 | string totp, 28 | bool isFavorite, 29 | bool isShared 30 | ) 31 | { 32 | Id = id; 33 | Name = name; 34 | Username = username; 35 | Password = password; 36 | Url = url; 37 | Path = path; 38 | Notes = notes; 39 | Totp = totp; 40 | IsFavorite = isFavorite; 41 | IsShared = isShared; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/LastPass/ClientInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.LastPass 5 | { 6 | public class ClientInfo 7 | { 8 | public readonly Platform Platform; 9 | public readonly string Id; 10 | public readonly string Description; 11 | 12 | public ClientInfo(Platform platform, string id, string description) 13 | { 14 | Platform = platform; 15 | Id = id; 16 | Description = description; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/LastPass/ParserOptions.cs: -------------------------------------------------------------------------------- 1 | namespace PasswordManagerAccess.LastPass 2 | { 3 | // TODO: Rename to Options 4 | public class ParserOptions 5 | { 6 | public static ParserOptions Default { get; } = new ParserOptions(); 7 | 8 | // When is is enabled the library attempts to convert the secure notes that look like accounts into accounts. 9 | // Other types of notes are discarded. If you want to have all the secure notes return in a form of accounts, 10 | // set this to false. 11 | public bool ParseSecureNotesToAccount { get; set; } = true; 12 | 13 | // When enabled the library will log the requests and responses to the ISecureLogger interface. 14 | // In addition to that when an exception is thrown, the log entries will be attached to the exception. 15 | public bool LoggingEnabled { get; set; } = false; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/LastPass/Platform.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.LastPass 5 | { 6 | public enum Platform 7 | { 8 | Desktop, 9 | Mobile, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/LastPass/Session.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.LastPass 5 | { 6 | internal record Session(string Id, string Token, string EncryptedPrivateKey); 7 | } 8 | -------------------------------------------------------------------------------- /src/LastPass/SharedFolder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.LastPass 5 | { 6 | internal class SharedFolder 7 | { 8 | public readonly string Id; 9 | public readonly string Name; 10 | public readonly byte[] EncryptionKey; 11 | 12 | public SharedFolder(string id, string name, byte[] encryptionKey) 13 | { 14 | Id = id; 15 | Name = name; 16 | EncryptionKey = encryptionKey; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OnePassword/AppInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.OnePassword 5 | { 6 | public class AppInfo 7 | { 8 | public string Name { get; set; } 9 | public string Version { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/OnePassword/Credentials.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.OnePassword 5 | { 6 | public class Credentials 7 | { 8 | public string Username { get; set; } 9 | public string Password { get; set; } 10 | 11 | public string AccountKey 12 | { 13 | get => _accountKey; 14 | set 15 | { 16 | _accountKey = value; 17 | _parsedAccountKey = null; 18 | } 19 | } 20 | 21 | public string Domain { get; set; } 22 | public string DeviceUuid { get; set; } 23 | 24 | // 25 | // Internal 26 | // 27 | 28 | internal string SrpX { get; set; } 29 | internal AesKey Key { get; set; } 30 | 31 | internal AccountKey ParsedAccountKey 32 | { 33 | get 34 | { 35 | if (_parsedAccountKey == null) 36 | _parsedAccountKey = OnePassword.AccountKey.Parse(AccountKey); 37 | 38 | return _parsedAccountKey; 39 | } 40 | } 41 | 42 | // 43 | // Private 44 | // 45 | 46 | private string _accountKey; 47 | private AccountKey _parsedAccountKey; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/OnePassword/IDecryptor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.OnePassword 5 | { 6 | internal interface IDecryptor 7 | { 8 | byte[] Decrypt(Encrypted e); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/OnePassword/NoItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.OnePassword; 5 | 6 | public enum NoItem 7 | { 8 | NotFound, 9 | Deleted, 10 | UnsupportedType, 11 | } 12 | -------------------------------------------------------------------------------- /src/OnePassword/Region.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | 6 | namespace PasswordManagerAccess.OnePassword 7 | { 8 | public enum Region 9 | { 10 | Global, 11 | Europe, 12 | Canada, 13 | } 14 | 15 | public static class Extensions 16 | { 17 | public static string ToDomain(this Region region) 18 | { 19 | return region switch 20 | { 21 | Region.Global => "my.1password.com", 22 | Region.Europe => "my.1password.eu", 23 | Region.Canada => "my.1password.ca", 24 | _ => throw new InternalErrorException("The region is not valid"), 25 | }; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/OnePassword/ServiceAccount.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.OnePassword 5 | { 6 | public class ServiceAccount 7 | { 8 | public string Token { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/OnePassword/Session.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | 6 | namespace PasswordManagerAccess.OnePassword 7 | { 8 | /// The session object is opaque to the user. It holds all the state needed by Client to perform 9 | /// various operations like opening vaults or logging out. 10 | public class Session 11 | { 12 | internal readonly Credentials Credentials; 13 | internal readonly Keychain Keychain; 14 | internal readonly AesKey Key; 15 | internal readonly RestClient Rest; 16 | internal readonly IRestTransport Transport; 17 | 18 | internal Session(Credentials credentials, Keychain keychain, AesKey key, RestClient rest, IRestTransport transport) 19 | { 20 | Credentials = credentials; 21 | Keychain = keychain; 22 | Key = key; 23 | Rest = rest; 24 | Transport = transport; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/OnePassword/SrpInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.OnePassword 5 | { 6 | internal class SrpInfo 7 | { 8 | public readonly string SrpMethod; 9 | public readonly string KeyMethod; 10 | public readonly int Iterations; 11 | public readonly byte[] Salt; 12 | 13 | public SrpInfo(string srpMethod, string keyMethod, int iterations, byte[] salt) 14 | { 15 | SrpMethod = srpMethod; 16 | KeyMethod = keyMethod; 17 | Iterations = iterations; 18 | Salt = salt; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/OnePassword/Ui.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Duo; 5 | 6 | namespace PasswordManagerAccess.OnePassword.Ui 7 | { 8 | public interface IUi : IDuoUi 9 | { 10 | // Return Passcode.Cancel to cancel 11 | Passcode ProvideGoogleAuthPasscode(); 12 | 13 | // Only the `Passcode.RememberMe` is used. The `Passcode.Code` is ignored as it's requested later 14 | // with the system UI and there's no way around it. 15 | // Return Passcode.Cancel to cancel 16 | Passcode ProvideWebAuthnRememberMe(); 17 | } 18 | 19 | public class Passcode 20 | { 21 | public static readonly Passcode Cancel = new Passcode("cancel", false); 22 | 23 | public readonly string Code; 24 | public readonly bool RememberMe; 25 | 26 | public Passcode(string code, bool rememberMe) 27 | { 28 | Code = code; 29 | RememberMe = rememberMe; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/OnePassword/Vault.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | #nullable enable 5 | 6 | namespace PasswordManagerAccess.OnePassword 7 | { 8 | public record Vault(VaultInfo Info, Account[] Accounts, SshKey[] SshKeys); 9 | } 10 | -------------------------------------------------------------------------------- /src/OpVault/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.OpVault 5 | { 6 | public class Account 7 | { 8 | public string Id { get; } 9 | public string Name { get; } 10 | public string Username { get; } 11 | public string Password { get; } 12 | public string Url { get; } 13 | public string Note { get; } 14 | public Folder Folder { get; } 15 | 16 | // 17 | // Non-public 18 | // 19 | 20 | internal Account(string id, string name, string username, string password, string url, string note, Folder folder) 21 | { 22 | Id = id; 23 | Name = name; 24 | Username = username; 25 | Password = password; 26 | Url = url; 27 | Note = note; 28 | Folder = folder; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/OpVault/Folder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.OpVault 5 | { 6 | public class Folder 7 | { 8 | // `None` is used to mark "no folder" situation not to use null and avoid crashes. 9 | // It guaranteed to be exactly this instance, so it's ok to use reference compare to check for no parent: 10 | // if (folder.Parent == Folder.None) { ... } 11 | public static readonly Folder None = new Folder("", ""); 12 | 13 | public string Id { get; } 14 | public string Name { get; } 15 | 16 | public Folder Parent 17 | { 18 | get => _parent ?? None; 19 | internal set => _parent = value; 20 | } 21 | 22 | // 23 | // Non-public 24 | // 25 | 26 | internal Folder(string id, string name) 27 | { 28 | Id = id; 29 | Name = name; 30 | } 31 | 32 | // `_parent` is nullable. The public `Parent` property hides the null value by returning `None` instead. 33 | private Folder _parent; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/OpVault/KeyMac.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | 6 | namespace PasswordManagerAccess.OpVault 7 | { 8 | // Represents a key and [H]MAC key pair. They always go together since all the encryption is 9 | // authenticated: every time something is encrypted a MAC/tag is added. 10 | internal class KeyMac 11 | { 12 | public readonly byte[] Key; 13 | public readonly byte[] MacKey; 14 | 15 | public KeyMac(byte[] buffer) 16 | { 17 | if (buffer.Length != 64) 18 | throw new InternalErrorException("Buffer must be exactly 64 bytes long"); 19 | 20 | Key = buffer.Sub(0, 32); 21 | MacKey = buffer.Sub(32, 32); 22 | } 23 | 24 | public KeyMac(string base64) 25 | : this(base64.Decode64()) { } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/OpVault/Util.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | 6 | namespace PasswordManagerAccess.OpVault 7 | { 8 | internal static class Util 9 | { 10 | public static KeyMac DeriveKek(byte[] password, byte[] salt, int iterations) 11 | { 12 | return new KeyMac(Pbkdf2.GenerateSha512(password, salt, iterations, 64)); 13 | } 14 | 15 | public static byte[] DecryptAes(byte[] ciphertext, byte[] iv, KeyMac key) 16 | { 17 | return Crypto.DecryptAes256CbcNoPadding(ciphertext, iv, key.Key); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ProtonPass/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | #nullable enable 5 | 6 | using System; 7 | 8 | namespace PasswordManagerAccess.ProtonPass 9 | { 10 | public class Account 11 | { 12 | public string Id { get; internal set; } = ""; 13 | public string Name { get; internal set; } = ""; 14 | public string Email { get; internal set; } = ""; 15 | public string Username { get; internal set; } = ""; 16 | public string Password { get; internal set; } = ""; 17 | public string[] Urls { get; internal set; } = Array.Empty(); 18 | public string Totp { get; internal set; } = ""; 19 | public string Note { get; internal set; } = ""; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ProtonPass/IAsyncUi.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | #nullable enable 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace PasswordManagerAccess.ProtonPass 9 | { 10 | public interface IAsyncUi 11 | { 12 | public record CaptchaResult(bool Solved, string Token) 13 | { 14 | // Return this to signal the cancellation of the operation 15 | public static readonly CaptchaResult Cancel = new CaptchaResult(false, ""); 16 | } 17 | 18 | // We don't have remember-me for ProtonPass like in other modules 19 | // TODO: Figure out how it's implemented if at all 20 | public record PasscodeResult(string Passcode) 21 | { 22 | // Return this to signal the cancellation of the operation 23 | public static readonly PasscodeResult Cancel = new PasscodeResult("cancel"); 24 | } 25 | 26 | // Return CaptchaResult.Cancel to cancel 27 | Task SolveCaptcha(string url, string humanVerificationToken, CancellationToken cancellationToken); 28 | 29 | // Return OtpResult.Cancel to cancel 30 | Task ProvideExtraPassword(int attempt, CancellationToken cancellationToken); 31 | 32 | // Return OtpResult.Cancel to cancel 33 | Task ProvideGoogleAuthPasscode(int attempt, CancellationToken cancellationToken); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ProtonPass/Vault.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | #nullable enable 5 | 6 | using System; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using PasswordManagerAccess.Common; 10 | 11 | namespace PasswordManagerAccess.ProtonPass 12 | { 13 | public class Vault 14 | { 15 | public string Id { get; internal set; } = ""; 16 | public string Name { get; internal set; } = ""; 17 | public string Description { get; internal set; } = ""; 18 | public Account[] Accounts { get; internal set; } = Array.Empty(); 19 | 20 | // TODO: Consider removing the = default on the cancellation token 21 | public static async Task OpenAll( 22 | string username, 23 | string password, 24 | IAsyncUi ui, 25 | IAsyncSecureStorage storage, 26 | CancellationToken cancellationToken = default 27 | ) 28 | { 29 | return await OpenAll(username, password, ui, storage, new RestAsync.Config(), cancellationToken).ConfigureAwait(false); 30 | } 31 | 32 | // 33 | // Internal 34 | // 35 | 36 | internal static async Task OpenAll( 37 | string username, 38 | string password, 39 | IAsyncUi ui, 40 | IAsyncSecureStorage storage, 41 | RestAsync.Config config, 42 | CancellationToken cancellationToken 43 | ) 44 | { 45 | return await Client.OpenAll(username, password, ui, storage, config, cancellationToken).ConfigureAwait(false); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/RoboForm/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.RoboForm 5 | { 6 | public class Account 7 | { 8 | public enum FieldKind 9 | { 10 | Text, 11 | Password, 12 | } 13 | 14 | public struct Field 15 | { 16 | public readonly string Name; 17 | public readonly string Value; 18 | public readonly FieldKind Kind; 19 | 20 | public Field(string name, string value, FieldKind kind) 21 | { 22 | Name = name; 23 | Value = value; 24 | Kind = kind; 25 | } 26 | } 27 | 28 | public readonly string Name; 29 | public readonly string Path; 30 | public readonly string Url; 31 | public readonly Field[] Fields; 32 | 33 | // These are guessed based on some heuristic. When the guess cannot be 34 | // made with confidence the guessed value is null. 35 | public readonly string GuessedUsername; 36 | public readonly string GuessedPassword; 37 | 38 | public Account(string name, string path, string url, Field[] fields, string guessedUsername, string guessedPassword) 39 | { 40 | Name = name; 41 | Path = path; 42 | Url = url; 43 | Fields = fields; 44 | GuessedUsername = guessedUsername; 45 | GuessedPassword = guessedPassword; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/RoboForm/ClientInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.RoboForm 5 | { 6 | // TODO: This type is kinda useless. Used only in one function. 7 | // Maybe store this in Client.Credentials instead of copying the fields. 8 | internal class ClientInfo 9 | { 10 | public readonly string Username; 11 | public readonly string Password; 12 | public readonly string DeviceId; 13 | 14 | public ClientInfo(string username, string password, string deviceId) 15 | { 16 | Username = username; 17 | Password = password; 18 | DeviceId = deviceId; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/RoboForm/Response.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System.ComponentModel; 5 | using Newtonsoft.Json; 6 | 7 | namespace PasswordManagerAccess.RoboForm.Response 8 | { 9 | internal class ReceivedItems 10 | { 11 | [JsonProperty("accounts", Required = Required.Always)] 12 | public readonly SharedFolderInfo[] SharedFolders; 13 | } 14 | 15 | internal readonly struct SharedFolderInfo 16 | { 17 | [JsonProperty("accountId", Required = Required.Always)] 18 | public readonly string Id; 19 | 20 | [JsonProperty("name")] 21 | public readonly string Name; 22 | 23 | [JsonProperty("mp")] 24 | public readonly string EncryptedKey; 25 | 26 | [JsonProperty("accepted", DefaultValueHandling = DefaultValueHandling.Populate)] 27 | [DefaultValue(false)] 28 | public readonly bool Accepted; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/RoboForm/Session.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace PasswordManagerAccess.RoboForm 7 | { 8 | internal class Session 9 | { 10 | public readonly string Token; 11 | public readonly string DeviceId; 12 | public readonly Dictionary Cookies; 13 | 14 | public Session(string token, string deviceId) 15 | { 16 | Token = token; 17 | DeviceId = deviceId; 18 | 19 | Cookies = new Dictionary(2) { ["sib-auth"] = token, ["sib-deviceid"] = deviceId }; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/RoboForm/Ui.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.RoboForm 5 | { 6 | public abstract class Ui 7 | { 8 | public class SecondFactorPassword 9 | { 10 | public readonly string Password; 11 | public readonly bool RememberDevice; 12 | 13 | public SecondFactorPassword(string password, bool rememberDevice) 14 | { 15 | Password = password; 16 | RememberDevice = rememberDevice; 17 | } 18 | } 19 | 20 | // TODO: Support null to cancel 21 | public abstract SecondFactorPassword ProvideSecondFactorPassword(string kind); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/RoboForm/Util.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | 6 | namespace PasswordManagerAccess.RoboForm 7 | { 8 | internal static class Util 9 | { 10 | public static string RandomDeviceId() 11 | { 12 | // All the device ids returned by the server seem to be in this format. 13 | // Example: B57192ee77db5e5989c5ef7e091b119ea 14 | return "B" + Crypto.RandomBytes(16).ToHex(); 15 | } 16 | 17 | public static byte[] ComputeClientKey(string password, AuthInfo authInfo) 18 | { 19 | return Crypto.HmacSha256(HashPassword(password, authInfo), "Client Key".ToBytes()); 20 | } 21 | 22 | // 23 | // Internal 24 | // 25 | 26 | internal static byte[] HashPassword(string password, AuthInfo authInfo) 27 | { 28 | var passwordBytes = password.ToBytes(); 29 | if (authInfo.IsMd5) 30 | passwordBytes = Crypto.Md5(passwordBytes); 31 | 32 | return Pbkdf2.GenerateSha256(passwordBytes, authInfo.Salt, authInfo.IterationCount, 32); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/StickyPassword/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.StickyPassword 5 | { 6 | public class Account 7 | { 8 | public Account(long id, string name, string url, string notes, Credentials[] credentials) 9 | { 10 | Id = id; 11 | Name = name; 12 | Url = url; 13 | Notes = notes; 14 | Credentials = credentials; 15 | } 16 | 17 | public long Id { get; private set; } 18 | public string Name { get; private set; } 19 | public string Url { get; private set; } 20 | public string Notes { get; private set; } 21 | public Credentials[] Credentials { get; private set; } 22 | } 23 | 24 | public class Credentials 25 | { 26 | public Credentials(string username, string password, string description) 27 | { 28 | Username = username; 29 | Password = password; 30 | Description = description; 31 | } 32 | 33 | public string Username { get; private set; } 34 | public string Password { get; private set; } 35 | public string Description { get; private set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/StickyPassword/S3Token.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | 6 | namespace PasswordManagerAccess.StickyPassword 7 | { 8 | internal class S3Token 9 | { 10 | public readonly S3.Credentials Credentials; 11 | public readonly string BucketName; 12 | public readonly string ObjectPrefix; 13 | 14 | public S3Token(string accessKeyId, string secretAccessKey, string securityToken, string bucketName, string objectPrefix) 15 | : this( 16 | new S3.Credentials(accessKeyId: accessKeyId, secretAccessKey: secretAccessKey, securityToken: securityToken), 17 | bucketName, 18 | objectPrefix 19 | ) { } 20 | 21 | public S3Token(S3.Credentials credentials, string bucketName, string objectPrefix) 22 | { 23 | Credentials = credentials; 24 | BucketName = bucketName; 25 | ObjectPrefix = objectPrefix; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/StickyPassword/Ui.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.StickyPassword.Ui 5 | { 6 | public interface IUi 7 | { 8 | Passcode ProvideEmailPasscode(); 9 | } 10 | 11 | public class Passcode 12 | { 13 | public static readonly Passcode Cancel = new Passcode("cancel"); 14 | public static readonly Passcode Resend = new Passcode("resend"); 15 | 16 | public readonly string Code; 17 | 18 | public Passcode(string code) 19 | { 20 | Code = code; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/StickyPassword/Util.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | 6 | namespace PasswordManagerAccess.StickyPassword 7 | { 8 | internal static class Util 9 | { 10 | public static byte[] DecryptToken(string username, string password, byte[] encryptedToken) 11 | { 12 | var key = DeriveTokenKey(username, password); 13 | return Crypto.DecryptAes256CbcNoPadding(encryptedToken, AesIv, key); 14 | } 15 | 16 | public static byte[] DeriveTokenKey(string username, string password) 17 | { 18 | var salt = Crypto.Md5(username.ToLowerInvariant()); 19 | return Crypto.Pbkdf2Sha1(password, salt, 5000, 32); 20 | } 21 | 22 | public static byte[] DeriveDbKey(string password, byte[] salt) 23 | { 24 | return Crypto.Pbkdf2Sha1(password, salt, 10000, 32); 25 | } 26 | 27 | public static byte[] Decrypt(byte[] ciphertext, byte[] key) 28 | { 29 | return Crypto.DecryptAes256Cbc(ciphertext, AesIv, key); 30 | } 31 | 32 | public static byte[] Encrypt(byte[] plaintext, byte[] key) 33 | { 34 | return Crypto.EncryptAes256Cbc(plaintext, AesIv, key); 35 | } 36 | 37 | // 38 | // Data 39 | // 40 | 41 | // Secutity fuckup: StickyPassword uses static zero IV in their encryption everywhere! 42 | private static readonly byte[] AesIv = new byte[16]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/TrueKey/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.TrueKey 5 | { 6 | public class Account 7 | { 8 | public readonly int Id; 9 | public readonly string Name; 10 | public readonly string Username; 11 | public readonly string Password; 12 | public readonly string Url; 13 | public readonly string Note; 14 | 15 | public Account(int id, string name, string username, string password, string url, string note) 16 | { 17 | Id = id; 18 | Name = name; 19 | Username = username; 20 | Password = password; 21 | Url = url; 22 | Note = note; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/TrueKey/EncryptedAccount.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.TrueKey 5 | { 6 | public class EncryptedAccount 7 | { 8 | public readonly int Id; 9 | public readonly string Name; 10 | public readonly string Username; 11 | public readonly byte[] EncryptedPassword; 12 | public readonly string Url; 13 | public readonly byte[] EncryptedNote; 14 | 15 | public EncryptedAccount(int id, string name, string username, byte[] encryptedPassword, string url, byte[] encryptedNote) 16 | { 17 | Id = id; 18 | Name = name; 19 | Username = username; 20 | EncryptedPassword = encryptedPassword; 21 | Url = url; 22 | EncryptedNote = encryptedNote; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/TrueKey/EncryptedVault.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.TrueKey 5 | { 6 | public class EncryptedVault 7 | { 8 | public readonly byte[] MasterKeySalt; 9 | public readonly byte[] EncryptedMasterKey; 10 | public readonly EncryptedAccount[] EncryptedAccounts; 11 | 12 | public EncryptedVault(byte[] masterKeySalt, byte[] encryptedMasterKey, EncryptedAccount[] encryptedAccounts) 13 | { 14 | MasterKeySalt = masterKeySalt; 15 | EncryptedMasterKey = encryptedMasterKey; 16 | EncryptedAccounts = encryptedAccounts; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/TrueKey/Ui.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.TrueKey 5 | { 6 | // This is the interface to be implemented by the user of the library. 7 | // It's used to provide answers for the interactive two factor login 8 | // process. The process is meant to interact with the user but it doesn't 9 | // mean it has to. The implementation could simply return the first answer 10 | // or something else like that. Have to be careful with the "wait" states 11 | // though, as the answer should only come after the user is confirmed the 12 | // second factor action, like clicked a button in the email or confirmed 13 | // on the phone. 14 | // The only reason this is a class and not an interface is because there's 15 | // no way to have an enum inside an interface. 16 | public abstract class Ui 17 | { 18 | public enum Answer 19 | { 20 | Check, 21 | Resend, 22 | Email, 23 | Device0, 24 | } 25 | 26 | public abstract Answer AskToWaitForEmail(string email, Answer[] validAnswers); 27 | public abstract Answer AskToWaitForOob(string name, string email, Answer[] validAnswers); 28 | public abstract Answer AskToChooseOob(string[] names, string email, Answer[] validAnswers); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/TrueKey/Vault.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | 6 | namespace PasswordManagerAccess.TrueKey 7 | { 8 | public class Vault 9 | { 10 | public readonly Account[] Accounts; 11 | 12 | public static Vault Open(string username, string password, Ui ui, ISecureStorage storage) 13 | { 14 | using var transport = new RestTransport(); 15 | return Open(username, password, ui, storage, transport); 16 | } 17 | 18 | // TODO: Write a test that runs the whole sequence and checks the result. 19 | internal static Vault Open(string username, string password, Ui ui, ISecureStorage storage, IRestTransport transport) 20 | { 21 | return new Vault(Client.OpenVault(username, password, ui, storage, transport)); 22 | } 23 | 24 | // 25 | // Private 26 | // 27 | 28 | private Vault(Account[] accounts) 29 | { 30 | Accounts = accounts; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ZohoVault/Account.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.ZohoVault 5 | { 6 | public class Account 7 | { 8 | public readonly string Id; 9 | public readonly string Name; 10 | public readonly string Username; 11 | public readonly string Password; 12 | public readonly string Url; 13 | public readonly string Note; 14 | 15 | public Account(string id, string name, string username, string password, string url, string note) 16 | { 17 | Id = id; 18 | Name = name; 19 | Username = username; 20 | Password = password; 21 | Url = url; 22 | Note = note; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ZohoVault/Credentials.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.ZohoVault 5 | { 6 | public class Credentials 7 | { 8 | public string Username { get; } 9 | public string Password { get; } 10 | public string Passphrase { get; } 11 | 12 | public Credentials(string username, string password, string passphrase) 13 | { 14 | Username = username; 15 | Password = password; 16 | Passphrase = passphrase; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ZohoVault/Settings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.ZohoVault 5 | { 6 | public class Settings 7 | { 8 | // When set to true the session cookies will be saved to the secure storage and logout will not be performed at the end. 9 | // This allows a subsequent request to reuse the previous session without doing a full login. Zoho limits the number 10 | // of full logins to 20 per day. 11 | public bool KeepSession { get; set; } = true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ZohoVault/Ui.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.ZohoVault.Ui 5 | { 6 | public interface IUi 7 | { 8 | // To cancel return Passcode.Cancel 9 | public abstract Passcode ProvideGoogleAuthPasscode(); 10 | } 11 | 12 | // Passcode result 13 | public class Passcode 14 | { 15 | // Return this to signal the cancellation of the operation 16 | public static readonly Passcode Cancel = new Passcode("cancel", false); 17 | 18 | public readonly string Code; 19 | public readonly bool RememberMe; 20 | 21 | public Passcode(string code, bool rememberMe) 22 | { 23 | Code = code; 24 | RememberMe = rememberMe; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ZohoVault/Vault.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.ZohoVault.Ui; 6 | 7 | namespace PasswordManagerAccess.ZohoVault 8 | { 9 | public class Vault 10 | { 11 | public readonly Account[] Accounts; 12 | 13 | public static Vault Open(Credentials credentials, Settings settings, IUi ui, ISecureStorage storage) 14 | { 15 | using var transport = new RestTransport(); 16 | return Open(credentials, settings, ui, storage, transport); 17 | } 18 | 19 | // 20 | // Internal 21 | // 22 | 23 | internal static Vault Open(Credentials credentials, Settings settings, IUi ui, ISecureStorage storage, IRestTransport transport) 24 | { 25 | return new Vault(Client.OpenVault(credentials, settings, ui, storage, transport)); 26 | } 27 | 28 | internal Vault(Account[] accounts) 29 | { 30 | Accounts = accounts; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/Bitwarden/Fixtures/collections.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "readOnly": false, 5 | "hidePasswords": false, 6 | "manage": true, 7 | "id": "406cc470-08c9-469c-b4a7-b2ea00bf352b", 8 | "organizationId": "d9e85d3d-72bb-4269-a50d-b2ea00bf3505", 9 | "name": "2.oYiIYTccAerK16ii2sj9tg==|kH8QDVBNQoT0Fir6AnW2aQ76AuM2hEfeBrus6ODZ3XA=|xBBUigUa0+q7eONwmQEvNJcw9yyYptc0gjj0gf7XYY4=", 10 | "externalId": null, 11 | "object": "collectionDetails" 12 | }, 13 | { 14 | "readOnly": false, 15 | "hidePasswords": false, 16 | "manage": true, 17 | "id": "c323c924-420d-45b6-b58e-b2ea00bfb96a", 18 | "organizationId": "d9e85d3d-72bb-4269-a50d-b2ea00bf3505", 19 | "name": "2.FUgZmWUkdTG19V5dvLpkEA==|ejaq/LmVX84OTVC4lclNSA==|pUjUKHjuBhisNKiTNGvbS/zyLLJ+qVIrl8NT26qWdi4=", 20 | "externalId": "", 21 | "object": "collectionDetails" 22 | } 23 | ], 24 | "continuationToken": null, 25 | "object": "list" 26 | } -------------------------------------------------------------------------------- /test/Bitwarden/Fixtures/folders.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "d0e9210c-610b-4427-a344-a99600d462d3", 5 | "name": "2.v3pXjkl5gADfG3ybbtqCfA==|4Ylk3LkaN6VVagD/YUZr2Q==|5ATetjLE4GhYlU6WfpyoNGjQWj1oAUfoDO++WefzQK0=", 6 | "revisionDate": "2025-05-29T10:02:21.6633333Z", 7 | "object": "folder" 8 | }, 9 | { 10 | "id": "94542f0a-d858-46ce-87a5-a99600d47732", 11 | "name": "2.f348ky6Q7rYjbPGqElp8Mw==|tbgLvStJ4CmQxiO+Sv8ZmA==|SuOq7A08BYtKNfr3gdVtC2c7Pca2SlfEAj59kgr84Zg=", 12 | "revisionDate": "2018-11-12T12:53:33.8166667Z", 13 | "object": "folder" 14 | } 15 | ], 16 | "continuationToken": null, 17 | "object": "list" 18 | } -------------------------------------------------------------------------------- /test/Bitwarden/Fixtures/item-not-found.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Resource not found.", 3 | "validationErrors": null, 4 | "exceptionMessage": null, 5 | "exceptionStackTrace": null, 6 | "innerExceptionMessage": null, 7 | "object": "error" 8 | } -------------------------------------------------------------------------------- /test/Bitwarden/Fixtures/login-mfa-unsupported-only.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "invalid_grant", 3 | "error_description": "Two factor required.", 4 | "TwoFactorProviders": [1337], 5 | "TwoFactorProviders2": 6 | { 7 | "1337": 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/Bitwarden/Fixtures/login-mfa.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "invalid_grant", 3 | "error_description": "Two factor required.", 4 | "TwoFactorProviders": [0, 1, 2, 3, 4], 5 | "TwoFactorProviders2": 6 | { 7 | "0": null, 8 | "1": 9 | { 10 | "Email": "la***********@gmail.com" 11 | }, 12 | "2": 13 | { 14 | "Host": "api-005dde75.duosecurity.com", 15 | "Signature": "TX|bGFzdHBhc3MucnVieUBnbWFpbC5jb218RElLU0FYUEtZWDJST1lXSUdJRFp8MTU3NDA4MDgyNA==|45db357b849438b4978733ea10691d5e03db31bb:APP|bGFzdHBhc3MucnVieUBnbWFpbC5jb218RElLU0FYUEtZWDJST1lXSUdJRFp8MTU3NDA4NDEyNA==|697b856757cde45b3d9031332c167fbfe341652b" 16 | }, 17 | "3": 18 | { 19 | "Nfc": false 20 | }, 21 | "4": 22 | { 23 | "Challenge": "{\"appId\":\"https://vault.bitwarden.com/app-id.json\",\"challenge\":\"L-vPG2bu7wj4iwWWmYo7R6PwP7TEezOkGeSPj0sjqLg\",\"keys\":[{\"keyHandle\":\"PSQxqnD3Mf0A3DIzyQsMrbSSI4dc36HLoFXOj5vAJObKmRGXiYutnqCpBOEWYYuaCX0JI3QTKhI1VEDehpMStQ\",\"version\":\"U2F_V2\"}]}", 24 | "Challenges": "[{\"appId\":\"https://vault.bitwarden.com/app-id.json\",\"challenge\":\"L-vPG2bu7wj4iwWWmYo7R6PwP7TEezOkGeSPj0sjqLg\",\"keyHandle\":\"PSQxqnD3Mf0A3DIzyQsMrbSSI4dc36HLoFXOj5vAJObKmRGXiYutnqCpBOEWYYuaCX0JI3QTKhI1VEDehpMStQ\",\"version\":\"U2F_V2\"}]" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Common/JsonTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using Newtonsoft.Json.Linq; 5 | using PasswordManagerAccess.Common; 6 | using Xunit; 7 | 8 | namespace PasswordManagerAccess.Test.Common 9 | { 10 | public class JsonTest 11 | { 12 | [Fact] 13 | public void TryParse_returns_true_on_success_and_returns_parser_result() 14 | { 15 | var success = Json.TryParse("{}", out var jo); 16 | 17 | Assert.True(success); 18 | Assert.NotNull(jo); 19 | } 20 | 21 | [Fact] 22 | public void TryParse_returns_false_on_failure_and_sets_result_to_null() 23 | { 24 | var notNull = new JObject(); 25 | var success = Json.TryParse("~not-a-json~", out notNull); 26 | Assert.False(success); 27 | Assert.Null(notNull); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/Common/OsTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.Common 8 | { 9 | public class OsTest 10 | { 11 | [Fact] 12 | public void UnixSeconds_returns_timestamp_older_than_2020_01_01_00_00_00() 13 | { 14 | Assert.True(Os.UnixSeconds() > 1577836800); 15 | } 16 | 17 | [Fact] 18 | public void UnixMilliseconds_returns_timestamp_older_than_2020_01_01_00_00_00_000() 19 | { 20 | Assert.True(Os.UnixMilliseconds() > 1577836800000); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/Common/Poly1305Test.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System.Collections.Generic; 5 | using PasswordManagerAccess.Common; 6 | using Xunit; 7 | 8 | namespace PasswordManagerAccess.Test.Common 9 | { 10 | public class Poly1305Test 11 | { 12 | [Theory] 13 | [MemberData(nameof(Poly1305TestCases))] 14 | public void Poly1305_computes_tag(CryptoTestVectors.Poly1305TestVector v) 15 | { 16 | // TODO: We do not support arbitrary input length 17 | if (v.Input.Length % Poly1305.BlockSize != 0) 18 | return; 19 | 20 | var poly1305 = new Poly1305(v.Key); 21 | poly1305.Update(v.Input); 22 | 23 | // Span mac = stackalloc byte[16]; 24 | // poly1305.Finish(mac); 25 | } 26 | 27 | public static IEnumerable Poly1305TestCases = TestBase.ToMemberData(CryptoTestVectors.Poly1305TestVectors); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/Common/UrlTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.Common 8 | { 9 | public class UrlTest 10 | { 11 | [Theory] 12 | [InlineData("https://blah.com?param=single", "single")] 13 | [InlineData("https://blah.com?param=first&more=next", "first")] 14 | [InlineData("https://blah.com?first=blah¶m=middle&more=next", "middle")] 15 | [InlineData("https://blah.com?first=blah&more=next¶m=last", "last")] 16 | [InlineData("https://blah.com?param=", "")] 17 | [InlineData("https://blah.com?param=&more=next", "")] 18 | [InlineData("https://blah.com", null)] 19 | [InlineData("https://blah.com?", null)] 20 | [InlineData("https://blah.com?blah=none", null)] 21 | public void ExtractQueryParameter_returns_parameter_value(string url, string expected) 22 | { 23 | var v = Url.ExtractQueryParameter(url, "param"); 24 | Assert.Equal(expected, v); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Dashlane/AccountTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Dashlane; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.Dashlane 8 | { 9 | public class AccountTest 10 | { 11 | [Fact] 12 | public void Account_properties_are_set() 13 | { 14 | var id = "id"; 15 | var name = "name"; 16 | var username = "username"; 17 | var password = "password"; 18 | var url = "url"; 19 | var note = "note"; 20 | var account = new Account(id, name, username, password, url, note); 21 | 22 | Assert.Equal(id, account.Id); 23 | Assert.Equal(name, account.Name); 24 | Assert.Equal(username, account.Username); 25 | Assert.Equal(password, account.Password); 26 | Assert.Equal(url, account.Url); 27 | Assert.Equal(note, account.Note); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/Dashlane/Fixtures/email-token-sent.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "ffaa5424-ab8a-40b7-b64d-260d30979bd9", 3 | "data": { 4 | "accountType": "masterPassword", 5 | "verifications": [ 6 | { 7 | "type": "email_token" 8 | } 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/Dashlane/Fixtures/email-token-triggered.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "c3d847f8-1d0d-4e3d-a9e9-73ea09f7b686", 3 | "data": {} 4 | } 5 | -------------------------------------------------------------------------------- /test/Dashlane/Fixtures/email-token-verified.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "77ea91bf-45ce-4a93-ab72-d033af4e539a", 3 | "data": { 4 | "authTicket": "0b2026a49954a2b22a347c27b045efa053e75c40ce778e05dbd7be91c2d49f2c" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/Dashlane/Fixtures/invalid-email-token.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "6aeda661-1d74-4ad8-aaee-db3b622ed816", 3 | "errors": [ 4 | { 5 | "type": "business_error", 6 | "code": "verification_failed", 7 | "message": "The server was not able to verify the token. This could be due for example to the user inputing a wrong token" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/Dashlane/Fixtures/invalid-email.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "2df1c900-1616-4778-9ea6-690909e49301", 3 | "errors": [ 4 | { 5 | "type": "business_error", 6 | "code": "user_not_found", 7 | "message": "The given login does not exist." 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/Dashlane/Fixtures/invalid-uki.json: -------------------------------------------------------------------------------- 1 | { 2 | "objectType": "message", 3 | "content": "Incorrect authentification" 4 | } 5 | -------------------------------------------------------------------------------- /test/Dashlane/Fixtures/otp-requested.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "f8aec971-39a8-4ae6-a474-5e2ba1010d75", 3 | "data": { 4 | "accountType": "masterPassword", 5 | "verifications": [ 6 | { 7 | "type": "totp" 8 | } 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/DropboxPasswords/AccountTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.DropboxPasswords; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.DropboxPasswords 8 | { 9 | public class AccountTest 10 | { 11 | [Fact] 12 | public void Account_properties_are_set() 13 | { 14 | var id = "id"; 15 | var name = "name"; 16 | var username = "username"; 17 | var password = "password"; 18 | var url = "url"; 19 | var note = "note"; 20 | var folder = "folder"; 21 | var account = new Account(id, name, username, password, url, note, folder); 22 | 23 | Assert.Equal(id, account.Id); 24 | Assert.Equal(name, account.Name); 25 | Assert.Equal(username, account.Username); 26 | Assert.Equal(password, account.Password); 27 | Assert.Equal(url, account.Url); 28 | Assert.Equal(note, account.Note); 29 | Assert.Equal(folder, account.Folder); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/DropboxPasswords/Fixtures/code-for-oauth-exchange.json: -------------------------------------------------------------------------------- 1 | { 2 | "token_type": "bearer", 3 | "access_token": "SpEDMQTeZlUAAAAAAAAAAVWWS95lD7vRptHu5Prl9NA02kM5PsdBhY--QBpOexA8", 4 | "scope": "account_info.read account_info.write contacts.read contacts.write file_requests.read file_requests.write files.content.read files.content.write files.metadata.read files.metadata.write private:account_info.read private:account_info.write private:files.content.read private:files.content.write private:files.metadata.read private:settings.read private:sharing.read private:sharing.write sharing.read sharing.write", 5 | "uid": "3472601408", 6 | "account_id": "dbid:AAD7aMTP1kLKKxwZlTCEM9JGnZXtH956UB4" 7 | } 8 | -------------------------------------------------------------------------------- /test/DropboxPasswords/Fixtures/entry-keyset.json: -------------------------------------------------------------------------------- 1 | { 2 | "encryptedBundle": { 3 | "base64EncryptedData": "liYxnpBRkyYZE3vhH9LEtb4NXiXGXoS5GeCJ2LU++j+PJCTnZlFVTwGVU+71hxG6W9+eebBcH1oXGCdqZxxvFZ+J4bXODfX176tbliFqwc7gYB+vl2+Xm9jC8vnN4Lcx6fF6dbzTDjFULtPU3BHH7rLGQBxB1oZKl+HdMLbWu5OhL8A7khDflYkEHBqzkfYhkoMfJRQrSzOXz1rbs2jAHpx0Xb5Nii76F1lQPzvtM3qFJKoVyZ6KrqOmhXXWV7m8ogxDPlr5eyA9GGuSBZSJDu2HWNlu5ZHJAGrbvwVFGON6Pyv3HUssBamGMynTGE8x+2HgRB6Z28J7usk2V8zhLHyBAsCAxwp1RPb7ojTTfg==", 4 | "base64Nonce": "YfMdI4TBiAWYa/rBGMwWZRS5myNzPQ3r" 5 | }, 6 | "type": "keyset", 7 | "version": 1, 8 | "identifier": "4F6989CF-DE29-4E4E-BA9D-1497C2DEDEA1" 9 | } 10 | -------------------------------------------------------------------------------- /test/DropboxPasswords/Fixtures/entry-vault.json: -------------------------------------------------------------------------------- 1 | { 2 | "encryptedBundle": { 3 | "base64EncryptedData": "NFk15gab4V7hGL7AKYxmx1+G46tD6gLjd4lIgHOu6ZNbegxzpYph3fF2lT8duAVDdpcNo7OgrMVxNXo3a+Fpm3okDVqwa0IYFixWl5V3XaNKY/en7K6QX/+Cn0ibHqrPc9TH32rZvI6S/6W/y2XjbaJXqvB1gls432YvGgUPz3xhWvRcHeSuS1puHJnNPXOFOFsawLUgRHFY+OeOC3OMiieJ2BzlXh5OlZRb+0WUwKhz3yMaLBorpzSA9yCGAC3ZxBvQqYr6OK4ViKeXt+EZWhrmDgTAOzzFf/HEZjIuHy5wV/ScA9apjBqPesNPdrl2hEWZWgXdaCrKNRQ7Qx/WjSV+nCaspjWjKBOIDpr2peXKJRXAHMq8LVCrN8opmWf3w0Igjsy6W3OrrUFTeYhUy96IpUMEjhNEVY7z71lAC27vz12nPoJqXKqpxMnIquDJ/Z3DngrLjXTC8meVC3Mb3u7VIHhGAY14aK2K75SeqgDACQ8lkl/VEiBMKsPC6vhcnusadN4hugKskBPYfPoVgIqCEfCOyB639y94GSn+U1LlTfSVdbLm+DvR+BcM9dL9LLiVwUtAgu8+ZskLjHvqlCZbk8/lpvOe69Qlg4lGqKx+KCi23kLfko7zRzgHILRc4UnaJ1VDG0JGL81CsCzFPcvCdhk3QLqvR+XNGIEt/kY9scb8YcaXW7hjWlel/TSHN9VfBimn7UAMFo2Ea+nmBn834LuDLFxrbSpJrcETmrWLH/SrsuFXhRyJj/jxoMc3bEtscr7RrHX4ygdqMq9mZ96nEyPGQ8zmpuIixqry5bGlrCDt8mAoGut4X1bGox4Us6QurdUnxhS27c8RerFCi0g1RnN+plsDm7o5wU0iAz4EBHbbs7Zk/SBBPU8Hv+PiTCVNbcBt8fdJ2haB8uxqCCG8QRzzhAjttR0FwxTBknBfeqZoemHFHfF1XlsKSnFPYPwNUTisXK/yK9iW9ftbcGrF40CMDbIXes9LJLuKIuJKGd22gcDRLRs117QzyUNvqCVA/so43U+rs37Ph3axHzpfK6XxdY04zgP60xy2tk4GaNtJW/wKdyBctzvunECDOlXTTeuOf2MNI8gGtsvmdfbbWs2gmx7OWPzqciwoUjpRuXIL+BYHUCcNVbIv3mXaIE/j/qH8B98H2rhg2PRiqv2npM4fIZXkI97dE22JKeKvLZC51O/0", 4 | "base64Nonce": "eZ5GPo+kv6D3UIejPbyp5Iim4XZ2aDxr" 5 | }, 6 | "type": "password", 7 | "version": 1, 8 | "identifier": "9D22A71D-C525-42DC-B9F2-5CC147A10303" 9 | } 10 | -------------------------------------------------------------------------------- /test/DropboxPasswords/Fixtures/expired-oauth.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | ".tag": "invalid_access_token" 4 | }, 5 | "error_summary": "invalid_access_token/" 6 | } 7 | -------------------------------------------------------------------------------- /test/DropboxPasswords/Fixtures/features.json: -------------------------------------------------------------------------------- 1 | { 2 | "analytics_id": "083af38d-afeb-44be-9cea-bfd531b3220e", 3 | "eligibility": { 4 | ".tag": "enabled", 5 | "passwords_path_root": "8233664752" 6 | }, 7 | "quota_info": { 8 | "device_limit": { 9 | ".tag": "unlimited" 10 | }, 11 | "password_limit": { 12 | ".tag": "unlimited" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/DropboxPasswords/Fixtures/keyset.json: -------------------------------------------------------------------------------- 1 | { 2 | "mainFolderIdentifier": "9D22A71D-C525-42DC-B9F2-5CC147A10303", 3 | "version": 1, 4 | "keyMap": { 5 | "9D22A71D-C525-42DC-B9F2-5CC147A10303": { 6 | "base64SecretKey": "HRYFsq5+nGEg+EQ3c+DrX3t2+W4o0T9JOauduV2dvtA=", 7 | "timestampSeconds": 1598722601, 8 | "color": "0061FF", 9 | "deleted": false 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/DropboxPasswords/Fixtures/transmitted-encryption-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "channel_payloads": [{ 3 | "channel_state": { 4 | "channel_id": { 5 | "app_id": "passwords_bolt", 6 | "unique_id": "3473601408_6A1F1A04-0E8D-4F8F-BBAE-EBBD8BFB445E" 7 | }, 8 | "revision": "1695223306656755909", 9 | "token": "DNa4mHh8jxFCieyf/COw8BsOo7jOgGw7l7k5AuokcnjOnnyEeOPCPNS4/KIThylVODuyHwi3MyH9ShQXa9J5wYbkIL09klIzwNBV7nHgv7OZtWeJsmo9LTalZ+pmI6FMO+aytAytp+y+fgQZaiTQHr1ObxL43mq81KvgWehFscmEBcR1cNNfy5lPNangJWyrimg=" 10 | }, 11 | "payloads": [{ 12 | "revision": "1695223306656755909", 13 | "payload": { 14 | "source_device_id": "1aabc3a9-c780-46a8-af05-d9388ba32982", 15 | "target_device_id": "6A1F1A04-0E8D-4F8F-BBAE-EBBD8BFB445E", 16 | "message_type": 1, 17 | "encrypted_user_key_bundle": { 18 | "encrypted_data": "kDZmVHrS3ZRNZUnUcaKQ6z5KqR5XYY6ymmJLAZhNVJk=", 19 | "nonce": "nSgGUq0+wgk6FuTonn/gLX3tMRYyDEsP" 20 | }, 21 | "encrypted_account_key_bundle": { 22 | "encrypted_data": "", 23 | "nonce": "" 24 | }, 25 | "source_device_public_key": "1YPKexhpTXpqx9WQC2rfQ19qg1SD27jKkv8Iu2CqZU4=", 26 | "enroll_action": "enroll_device", 27 | "notification_id": "41cfef97-2758-46d3-9d01-7662648fdd15" 28 | } 29 | }] 30 | }] 31 | } 32 | -------------------------------------------------------------------------------- /test/Duo/UtilTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Duo; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.Duo 8 | { 9 | public class UtilTest 10 | { 11 | [Fact] 12 | public void Parse_returns_parsed_document() 13 | { 14 | var doc = Util.Parse(""); 15 | 16 | Assert.NotNull(doc); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/Kaspersky/UtilTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.Kaspersky; 6 | using Xunit; 7 | 8 | namespace PasswordManagerAccess.Test.Kaspersky 9 | { 10 | public class UtilTest 11 | { 12 | [Fact] 13 | public void DeriveMasterPasswordAuthKey_returns_derived_key() 14 | { 15 | var key = Util.DeriveMasterPasswordAuthKey( 16 | "206a9e27-f96a-44d5-ac0d-84efe4f1835a", 17 | "d8f2bfe4980d90e3d402844e5332859ecbda531ab24962d2fdad4d39ad98d2f9".DecodeHex(), 18 | new DatabaseInfo(2, 1500, "39b56347c16c94c36553fd74a7cd2cb1".DecodeHex()) 19 | ); 20 | 21 | Assert.Equal( 22 | ( 23 | "d6602e5364ffa30389d9bab817919919ed417aabce1723ebc554099a34375253" 24 | + "fee450daa7b0d9959672398c79cde5736d020c73db661f12da6c2ede7c747e2c" 25 | ).DecodeHex(), 26 | key 27 | ); 28 | } 29 | 30 | [Fact] 31 | public void DeriveEncryptionKey_returns_derived_key() 32 | { 33 | var key = Util.DeriveEncryptionKey("Password123!", new DatabaseInfo(2, 1500, "39b56347c16c94c36553fd74a7cd2cb1".DecodeHex())); 34 | 35 | Assert.Equal("d8f2bfe4980d90e3d402844e5332859ecbda531ab24962d2fdad4d39ad98d2f9".DecodeHex(), key); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/Kdbx/BlockStreamTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using System.IO; 5 | using PasswordManagerAccess.Common; 6 | using PasswordManagerAccess.Kdbx; 7 | using Xunit; 8 | 9 | namespace PasswordManagerAccess.Test.Kdbx 10 | { 11 | public class BlockStreamTest : TestBase 12 | { 13 | // TODO: We need test data with multiple block. At the moment it's quite difficult since 14 | // the KeepPass clients generate files with 1MB blocks. We need to hack the program 15 | // to generate much smaller blocks. 16 | [Fact] 17 | public void All_bytes_are_read_with_ReadAll() 18 | { 19 | using var ms = new MemoryStream(GetBinaryFixture("kdbx4-aes-aes", "kdbx"), writable: false); 20 | ms.Seek(271, SeekOrigin.Begin); // Skip header 21 | 22 | using var s = new BlockStream(ms, AesAesHmacKey.DecodeHex()); 23 | var bytes = s.ReadAll(); 24 | 25 | Assert.Equal(2112, bytes.Length); 26 | } 27 | 28 | // 29 | // Data 30 | // 31 | 32 | internal const string AesAesHmacKey = 33 | "b7091bd40ed02eb88585811747435c4ab2d68c3d355773da5f923bbe094bfa5c" + "fa29fef53e43c0df65fb99b061ffb63619745f0ca472a39bd47fc017ce352d4d"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/kdbx4-aes-aes.kdbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/Kdbx/Fixtures/kdbx4-aes-aes.kdbx -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/kdbx4-aes-chacha20.kdbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/Kdbx/Fixtures/kdbx4-aes-chacha20.kdbx -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/kdbx4-aes-twofish.kdbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/Kdbx/Fixtures/kdbx4-aes-twofish.kdbx -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/kdbx4-argon2-aes-1k-block.kdbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/Kdbx/Fixtures/kdbx4-argon2-aes-1k-block.kdbx -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/kdbx4-argon2-aes.kdbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/Kdbx/Fixtures/kdbx4-argon2-aes.kdbx -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/kdbx4-with-fields.kdbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/Kdbx/Fixtures/kdbx4-with-fields.kdbx -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/kdbx4-with-keyfile.kdbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/Kdbx/Fixtures/kdbx4-with-keyfile.kdbx -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/kdbx4-with-nested-folders.kdbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/Kdbx/Fixtures/kdbx4-with-nested-folders.kdbx -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/keyfile-generic.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/Kdbx/Fixtures/keyfile-generic.bin -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/keyfile-legacy-binary.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/Kdbx/Fixtures/keyfile-legacy-binary.bin -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/keyfile-legacy-hex.txt: -------------------------------------------------------------------------------- 1 | 21df19998df2380329206c5f5298d20a7c58c7721c5aca77021ef7afd0c5163e -------------------------------------------------------------------------------- /test/Kdbx/Fixtures/keyfile-xml.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.00 5 | 6 | 7 | Id8ZmY3yOAMpIGxfUpjSCnxYx3IcWsp3Ah73r9DFFj4= 8 | 9 | -------------------------------------------------------------------------------- /test/Kdbx/UtilTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.Kdbx; 6 | using Xunit; 7 | 8 | namespace PasswordManagerAccess.Test.Kdbx 9 | { 10 | public class UtilTest 11 | { 12 | [Theory] 13 | [InlineData("", "", "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=")] 14 | [InlineData("password", "", "c2Qcmfdxn1fY9L6xGjA6/NGQJDpRzth4LKbT2+AU0UY=")] 15 | [InlineData("", "qYcgDd8vqIY7JlMPfNxdksWGVhpooSGOnh9bFFEbq5Q=", "AURDnmuQFyEbjTZ2HfigcKvMorpeyWSXAbqfKs16v/U=")] 16 | [InlineData("password", "qYcgDd8vqIY7JlMPfNxdksWGVhpooSGOnh9bFFEbq5Q=", "+0dAIBzDDLIer72hVh++t+wdi5kE5TFCEfu76vjzdxM=")] 17 | public void ComposeMasterKey_returns_composite_key(string password, string keyfile, string expected) 18 | { 19 | var key = Util.ComposeMasterKey(password, keyfile.Decode64()); 20 | Assert.Equal(expected.Decode64(), key); 21 | } 22 | 23 | [Fact] 24 | public void ComposeMasterKey_throws_on_invalid_keyfile_length() 25 | { 26 | Exceptions.AssertThrowsInternalError(() => Util.ComposeMasterKey("password", "invalid".ToBytes()), "Key file must be 32 bytes long"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/LastPass/SharedFolderTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.LastPass; 6 | using Xunit; 7 | 8 | namespace PasswordManagerAccess.Test.LastPass 9 | { 10 | public class SharedFolderTest 11 | { 12 | [Fact] 13 | public void SharedFolder_properties_are_set() 14 | { 15 | var id = "1234567890"; 16 | var name = "name"; 17 | var key = "blah".ToBytes(); 18 | 19 | var folder = new SharedFolder(id, name, key); 20 | 21 | Assert.Equal(id, folder.Id); 22 | Assert.Equal(name, folder.Name); 23 | Assert.Equal(key, folder.EncryptionKey); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/LastPass/VaultTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.LastPass; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.LastPass 8 | { 9 | public class VaultTest 10 | { 11 | [Fact] 12 | public void GenerateRandomClientId_returns_32_characters() 13 | { 14 | var id = Vault.GenerateRandomClientId(); 15 | Assert.Equal(32, id.Length); 16 | } 17 | 18 | [Fact] 19 | public void GenerateRandomClientId_returns_different_ids() 20 | { 21 | var id1 = Vault.GenerateRandomClientId(); 22 | var id2 = Vault.GenerateRandomClientId(); 23 | 24 | Assert.NotEqual(id2, id1); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/OnePassword/AccountTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using Xunit; 5 | 6 | namespace PasswordManagerAccess.Test.OnePassword 7 | { 8 | public class AccountTest 9 | { 10 | [Fact] 11 | public void Account_needs_some_tests() 12 | { 13 | // TODO: It's quite difficult to make a test for these things as they rely on some encrypted 14 | // blobs wrapped in json multiple times and are completely opaque. Figure out a better 15 | // way to test it. This stuff is tested inside Client higher level tests. 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/OnePassword/CredentialsTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.OnePassword; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.OnePassword 8 | { 9 | public class CredentialsTest 10 | { 11 | [Fact] 12 | public void ParsedAccountKey_updated_when_account_key_changes() 13 | { 14 | var credentials = new Credentials { AccountKey = "A3-ABCDEF-GGGGGG-HHHHH-IIIII-JJJJJ-KKKKK" }; 15 | 16 | Assert.Equal("A3", credentials.ParsedAccountKey.Format); 17 | Assert.Equal("ABCDEF", credentials.ParsedAccountKey.Uuid); 18 | Assert.Equal("GGGGGGHHHHHIIIIIJJJJJKKKKK", credentials.ParsedAccountKey.Key); 19 | 20 | credentials.AccountKey = "A3-PQRSTU-VVVVVV-WWWWW-XXXXX-YYYYY-ZZZZZ"; 21 | 22 | Assert.Equal("A3", credentials.ParsedAccountKey.Format); 23 | Assert.Equal("PQRSTU", credentials.ParsedAccountKey.Uuid); 24 | Assert.Equal("VVVVVVWWWWWXXXXXYYYYYZZZZZ", credentials.ParsedAccountKey.Key); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/empty-object-response.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/encrypted-aes-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "cty": "b5+jwk+json", 3 | "data": "6diyWpc9lhcSpL1lB1-FYFNgkrHQ_wsfoDA7gb2-oFzZTItL9QdxopxS80UhTOTyFhW2ubtZeD3YiyvflaG4_XfJ810MBCCFlFtBqE0clPp6YzQsOPuyWa7yPqIt8wIJdPfCMG9Tqy4KuCLCeZsSfqU1O9ccWRtAO3C9mPLJA_hpjbedOSbQ0D4kth9rfyKW8RlHSf-f6Fhhmy7_ObH_5NI9JZKu3hFXxA-FbOA", 4 | "enc": "A256GCM", 5 | "iv": "zd_oSihcQftkI3bu", 6 | "kid": "mp" 7 | } 8 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/exchange-a-for-b-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "sessionID": "TOZVTFIFBZGFDFNE5KSZFY7EZY", 3 | "userB": "37324a654b7493ea3c71d7a8b6b7e7e5e9040a3b13dfa72154ccad13276e4cf402869f13fde2f7c4c9d8b829ba15c1699b14728dfff45358c67932d5bdb8d0a311f54822c743e5549130f843ee9be65ec3455ed50b1779928fd112074078ceee141c98148e6eb657abeb4eb77796197279e05a5fa7776ae4dc8066e4c5852847126fc030cd4af6cf808ccab07dac5e477735132bad29290c165253bc9502108f2b424696835670a24b6cfd5b2ead23569e8b1149056800fcc0f20819d1571a4b95fba0f3e516d3256636f14f8e748d8630279651352b02a6544d431a863a086f37089414fd69da3857435e4205dcda863761a08620a5d24d15bdb3b22c495e452eebad7f6f0f76a073dfcd6adac85a7f1506efba7d704af812e4412be0b3e9937d65efaecf9d2b178908ff2eb9eeccf297441fc4e701d4f2b49b21a72b37e0bfd4e56c27d8b4792eeb50ff453da49229699a061dccb7622f7c6c0bf6d1b01bc79b7ddc8848550c87d6ebf1e3a0317bd33c502ffbf96d5a2f56a10bcaf842b6625b7d8cc0e489887305a107a6a51e8565967f66c78c47f25d4249f4b46d93556c437664d0464ec0ba4697a78da1e8393396cc190c1547d469254a0a492901d922e8138d0f64f23b2cc5afe833ead9241a3f1bd0a76acf32f07adfab36a4784781da7e87fa6ebdf2c008df3c55f9e0024d275c4d5c55a866d888e7ad4de67d1e77", 4 | "encConfig": "nil" 5 | } 6 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/get-vault-accounts-4tz6-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "contentVersion": 2, 3 | "items": [ 4 | { 5 | "uuid": "xcv22phyhit7kpgh7ydqx4hiqa", 6 | "templateUuid": "001", 7 | "trashed": "N", 8 | "createdAt": "2016-08-04T15:58:13Z", 9 | "updatedAt": "2016-08-04T15:58:39Z", 10 | "changerUuid": "DFM4DHQMQ5AS5M6UHSD6PNV774", 11 | "packageUuid": "", 12 | "itemVersion": 1, 13 | "encryptedBy": "byq5gi5adlasqyy2l2o7iddzvq", 14 | "encOverview": 15 | { 16 | "cty": "b5+jwk+json", 17 | "data": "lopmM7wgLbwyGdMrJuOUzoQhsZmWitLXeVeLJMDnq9DQdLNZrDqcKerPQqnbovL_hsaSkHo0JatR1q2HrHMj0GYRGFDXSpcIFTIDXCNr6OovYbmzCpTni-50UHCLLDEqoNHD8Vzl_MNXdDXc1X1tBmzrzx7sEDdzldqz_3hLopIxNLTEbhvAeJmm2THdmxkvYO8", 18 | "enc": "A256GCM", 19 | "iv": "YmCf7GqRUeDqYDKQ", 20 | "kid": "byq5gi5adlasqyy2l2o7iddzvq" 21 | }, 22 | "encDetails": 23 | { 24 | "cty": "b5+jwk+json", 25 | "data": "LYh49RU6osbJikiG9Wj1d0Q0eCrt1vXIZqryLMWplj2AFpsGTcsvf2E0WYHASEP6kFKNQEyL9MaP4DN4PBCcCfU9hvCc_784xRTs43WDyis8a8fVdobjszlkbsWChWJSF3YarnzGKzAp-0TD09fl26T7MltW5B8sjoSATSX1faTO006z4NSdSpfoe0Y18AVaBgeM59UGUZcPIryPyrNoBrviBG77CXcCZav0d956Oj5n4kann_QH2-sVO3oF56zSJcmH99kufS7b0w", 26 | "enc": "A256GCM", 27 | "iv": "VTRgDWZ9FI-jmj3B", 28 | "kid": "byq5gi5adlasqyy2l2o7iddzvq" 29 | } 30 | }], 31 | "batchComplete": true 32 | } 33 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/get-vault-accounts-ru74-batch-1-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "contentVersion": 3, 3 | "items": [ 4 | { 5 | "uuid": "wm3uxq4xsmb4mghxw6o3s7zrem", 6 | "templateUuid": "001", 7 | "trashed": "N", 8 | "createdAt": "2016-08-04T13:15:10Z", 9 | "updatedAt": "2016-08-04T13:16:07Z", 10 | "changerUuid": "DFM4DHQMQ5AS5M6UHSD6PNV774", 11 | "packageUuid": "", 12 | "itemVersion": 2, 13 | "encryptedBy": "x4ouqoqyhcnqojrgubso4hsdga", 14 | "encOverview": 15 | { 16 | "cty": "b5+jwk+json", 17 | "data": "9eH5PkEAnOA5QDds7BrUuwL3vo2abF1SSzwwVXwjnA_4zacK-qBCoK7flbzG-Pi_PIa9uxk3XkiJ4QavGD3FBJpdW9xxcvS5jadnyhmOfUNt7AKPLcg96hh0SLX9GdMnOGNXW_SW_UdBV4N-ux9wHv3dEf6gUw5emAYmucDcyGt4bvxNtXeFTFnZhuAB-lGA9l5xzjc", 18 | "enc": "A256GCM", 19 | "iv": "tgpqo7Cz06z2ka-W", 20 | "kid": "x4ouqoqyhcnqojrgubso4hsdga" 21 | }, 22 | "encDetails": 23 | { 24 | "cty": "b5+jwk+json", 25 | "data": "d-EtBOtn5hJWRX-IRVZc03axWv_EMDZtpCgFJEzP0qAHoCv8JTZJ00oWWDCINR2zFoHg5pks8ZvEMV1nquPwmN2hQxrB9mS8nK5wfWylgKms56VsdwD3P8SIZZcUOywX79oVN7VEu5C3fqBdeAiVvG_76ziHPFryApSbk9I34dIow6uZ0ldxGd5RPVaYRPkkZ1tf3_Nmgnm2n11JxEf0fSgj9Bc554hIMWLn7x3xYJbBgot_fYHqGP0UG7oBG0mXym1Lm_6lZFopQBlgIB0", 26 | "enc": "A256GCM", 27 | "iv": "RY7AOcB6_bTUwEwN", 28 | "kid": "x4ouqoqyhcnqojrgubso4hsdga" 29 | } 30 | }], 31 | "batchComplete": false 32 | } 33 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/get-vault-accounts-ru74-batch-2-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "contentVersion": 4, 3 | "items": [ 4 | { 5 | "uuid": "wm3uxq4xsmb4mghxw6o3s7zren", 6 | "templateUuid": "001", 7 | "trashed": "N", 8 | "createdAt": "2016-08-04T13:15:10Z", 9 | "updatedAt": "2016-08-04T13:16:07Z", 10 | "changerUuid": "DFM4DHQMQ5AS5M6UHSD6PNV774", 11 | "packageUuid": "", 12 | "itemVersion": 2, 13 | "encryptedBy": "x4ouqoqyhcnqojrgubso4hsdga", 14 | "encOverview": 15 | { 16 | "cty": "b5+jwk+json", 17 | "data": "9eH5PkEAnOA5QDds7BrUuwL3vo2abF1SSzwwVXwjnA_4zacK-qBCoK7flbzG-Pi_PIa9uxk3XkiJ4QavGD3FBJpdW9xxcvS5jadnyhmOfUNt7AKPLcg96hh0SLX9GdMnOGNXW_SW_UdBV4N-ux9wHv3dEf6gUw5emAYmucDcyGt4bvxNtXeFTFnZhuAB-lGA9l5xzjc", 18 | "enc": "A256GCM", 19 | "iv": "tgpqo7Cz06z2ka-W", 20 | "kid": "x4ouqoqyhcnqojrgubso4hsdga" 21 | }, 22 | "encDetails": 23 | { 24 | "cty": "b5+jwk+json", 25 | "data": "d-EtBOtn5hJWRX-IRVZc03axWv_EMDZtpCgFJEzP0qAHoCv8JTZJ00oWWDCINR2zFoHg5pks8ZvEMV1nquPwmN2hQxrB9mS8nK5wfWylgKms56VsdwD3P8SIZZcUOywX79oVN7VEu5C3fqBdeAiVvG_76ziHPFryApSbk9I34dIow6uZ0ldxGd5RPVaYRPkkZ1tf3_Nmgnm2n11JxEf0fSgj9Bc554hIMWLn7x3xYJbBgot_fYHqGP0UG7oBG0mXym1Lm_6lZFopQBlgIB0", 26 | "enc": "A256GCM", 27 | "iv": "RY7AOcB6_bTUwEwN", 28 | "kid": "x4ouqoqyhcnqojrgubso4hsdga" 29 | } 30 | }], 31 | "batchComplete": false 32 | } 33 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/get-vault-accounts-ru74-batch-3-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "contentVersion": 5, 3 | "items": [ 4 | { 5 | "uuid": "wm3uxq4xsmb4mghxw6o3s7zreo", 6 | "templateUuid": "001", 7 | "trashed": "N", 8 | "createdAt": "2016-08-04T13:15:10Z", 9 | "updatedAt": "2016-08-04T13:16:07Z", 10 | "changerUuid": "DFM4DHQMQ5AS5M6UHSD6PNV774", 11 | "packageUuid": "", 12 | "itemVersion": 2, 13 | "encryptedBy": "x4ouqoqyhcnqojrgubso4hsdga", 14 | "encOverview": 15 | { 16 | "cty": "b5+jwk+json", 17 | "data": "9eH5PkEAnOA5QDds7BrUuwL3vo2abF1SSzwwVXwjnA_4zacK-qBCoK7flbzG-Pi_PIa9uxk3XkiJ4QavGD3FBJpdW9xxcvS5jadnyhmOfUNt7AKPLcg96hh0SLX9GdMnOGNXW_SW_UdBV4N-ux9wHv3dEf6gUw5emAYmucDcyGt4bvxNtXeFTFnZhuAB-lGA9l5xzjc", 18 | "enc": "A256GCM", 19 | "iv": "tgpqo7Cz06z2ka-W", 20 | "kid": "x4ouqoqyhcnqojrgubso4hsdga" 21 | }, 22 | "encDetails": 23 | { 24 | "cty": "b5+jwk+json", 25 | "data": "d-EtBOtn5hJWRX-IRVZc03axWv_EMDZtpCgFJEzP0qAHoCv8JTZJ00oWWDCINR2zFoHg5pks8ZvEMV1nquPwmN2hQxrB9mS8nK5wfWylgKms56VsdwD3P8SIZZcUOywX79oVN7VEu5C3fqBdeAiVvG_76ziHPFryApSbk9I34dIow6uZ0ldxGd5RPVaYRPkkZ1tf3_Nmgnm2n11JxEf0fSgj9Bc554hIMWLn7x3xYJbBgot_fYHqGP0UG7oBG0mXym1Lm_6lZFopQBlgIB0", 26 | "enc": "A256GCM", 27 | "iv": "RY7AOcB6_bTUwEwN", 28 | "kid": "x4ouqoqyhcnqojrgubso4hsdga" 29 | } 30 | }], 31 | "batchComplete": true 32 | } 33 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/get-vault-accounts-ru74-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "contentVersion": 3, 3 | "items": [ 4 | { 5 | "uuid": "wm3uxq4xsmb4mghxw6o3s7zrem", 6 | "templateUuid": "001", 7 | "trashed": "N", 8 | "createdAt": "2016-08-04T13:15:10Z", 9 | "updatedAt": "2016-08-04T13:16:07Z", 10 | "changerUuid": "DFM4DHQMQ5AS5M6UHSD6PNV774", 11 | "packageUuid": "", 12 | "itemVersion": 2, 13 | "encryptedBy": "x4ouqoqyhcnqojrgubso4hsdga", 14 | "encOverview": 15 | { 16 | "cty": "b5+jwk+json", 17 | "data": "9eH5PkEAnOA5QDds7BrUuwL3vo2abF1SSzwwVXwjnA_4zacK-qBCoK7flbzG-Pi_PIa9uxk3XkiJ4QavGD3FBJpdW9xxcvS5jadnyhmOfUNt7AKPLcg96hh0SLX9GdMnOGNXW_SW_UdBV4N-ux9wHv3dEf6gUw5emAYmucDcyGt4bvxNtXeFTFnZhuAB-lGA9l5xzjc", 18 | "enc": "A256GCM", 19 | "iv": "tgpqo7Cz06z2ka-W", 20 | "kid": "x4ouqoqyhcnqojrgubso4hsdga" 21 | }, 22 | "encDetails": 23 | { 24 | "cty": "b5+jwk+json", 25 | "data": "d-EtBOtn5hJWRX-IRVZc03axWv_EMDZtpCgFJEzP0qAHoCv8JTZJ00oWWDCINR2zFoHg5pks8ZvEMV1nquPwmN2hQxrB9mS8nK5wfWylgKms56VsdwD3P8SIZZcUOywX79oVN7VEu5C3fqBdeAiVvG_76ziHPFryApSbk9I34dIow6uZ0ldxGd5RPVaYRPkkZ1tf3_Nmgnm2n11JxEf0fSgj9Bc554hIMWLn7x3xYJbBgot_fYHqGP0UG7oBG0mXym1Lm_6lZFopQBlgIB0", 26 | "enc": "A256GCM", 27 | "iv": "RY7AOcB6_bTUwEwN", 28 | "kid": "x4ouqoqyhcnqojrgubso4hsdga" 29 | } 30 | }], 31 | "batchComplete": true 32 | } 33 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/get-vault-with-no-items-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "contentVersion": 3, 3 | "batchComplete": true 4 | } 5 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/mfa-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "dsecret": "gUhBItRHUI7vAc04TJNUkA" 3 | } 4 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/no-auth-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 102, 3 | "errorMessage": "Authentication required.", 4 | "requestId": 5425802 5 | } 6 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/start-new-session-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok", 3 | "sessionID": "TOZVTFIFBZGFDFNE5KSZFY7EZY", 4 | "accountKeyFormat": "A3", 5 | "accountKeyUuid": "RTN9SA", 6 | "userAuth": { 7 | "method": "SRPg-4096", 8 | "alg": "PBES2g-HS256", 9 | "iterations": 100000, 10 | "salt": "-JLqTVQLjQg08LWZ0gyuUA" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/OnePassword/Fixtures/verify-key-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "notifier": "wss://b5n.1password.com/FPF3CVJPDVAT5NVS26SPR4MPAI/DFM4DHQMQ5AS5M6UHSD6PNV774/rz64r4uhyvgew672nm4ncaqonq", 3 | "accountUuid": "FPF3CVJPDVAT5NVS26SPR4MPAI", 4 | "userUuid": "DFM4DHQMQ5AS5M6UHSD6PNV774", 5 | "mfa": null, 6 | "serverVerifyHash": "Ke81HUwT13BAW5OrymPX7i7Aii_3cnCKSDJOlt55HMc" 7 | } 8 | -------------------------------------------------------------------------------- /test/OnePassword/ResponseTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using Newtonsoft.Json; 5 | using Xunit; 6 | using R = PasswordManagerAccess.OnePassword.Response; 7 | 8 | namespace PasswordManagerAccess.Test.OnePassword 9 | { 10 | public class ResponseTest : TestBase 11 | { 12 | [Fact] 13 | public void VaultItemDetails_parses_with_all_types_of_fields() 14 | { 15 | var json = GetFixture("vault-item-with-lots-of-fields"); 16 | var details = JsonConvert.DeserializeObject(json); 17 | Assert.Equal(2, details.Sections.Length); 18 | Assert.Equal(6, details.Sections[0].Fields.Length); 19 | Assert.Equal(6, details.Sections[1].Fields.Length); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/OnePassword/SrpInfoTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.OnePassword; 6 | using Xunit; 7 | 8 | namespace PasswordManagerAccess.Test.OnePassword 9 | { 10 | public class SrpInfoTest 11 | { 12 | [Fact] 13 | public void SrpInfo_properties_are_set() 14 | { 15 | var srpMethod = "srp-method"; 16 | var keyMethod = "key-method"; 17 | var iterations = 1337; 18 | var salt = "salt".ToBytes(); 19 | 20 | var session = new SrpInfo(srpMethod, keyMethod, iterations, salt); 21 | 22 | Assert.Equal(srpMethod, session.SrpMethod); 23 | Assert.Equal(keyMethod, session.KeyMethod); 24 | Assert.Equal(iterations, session.Iterations); 25 | Assert.Equal(salt, session.Salt); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/OpVault/AccountTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.OpVault; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.OpVault 8 | { 9 | public class AccountTest 10 | { 11 | [Fact] 12 | public void Account_properties_are_set() 13 | { 14 | var id = "id"; 15 | var name = "name"; 16 | var username = "username"; 17 | var password = "password"; 18 | var url = "url"; 19 | var note = "note"; 20 | var folder = new Folder("id", "name"); 21 | 22 | var account = new Account(id: id, name: name, username: username, password: password, url: url, note: note, folder: folder); 23 | 24 | Assert.Equal(id, account.Id); 25 | Assert.Equal(name, account.Name); 26 | Assert.Equal(username, account.Username); 27 | Assert.Equal(password, account.Password); 28 | Assert.Equal(url, account.Url); 29 | Assert.Equal(note, account.Note); 30 | Assert.Same(folder, account.Folder); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/OpVault/Fixtures/corrupted.opvault/default/band_3.js: -------------------------------------------------------------------------------- 1 | ld({}); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/corrupted.opvault/default/band_6.js: -------------------------------------------------------------------------------- 1 | ld({"6E7770574277434888367C1DCDF499D5":{"category":"001","created":1532620972,"d":"b3BkYXRhMDHtAAAAAAAAAKLIceCdZquxJW+0Fq+GOHyIv1zfksz91oqqvkpSHz+qES0rPwztM5fRjuVN3yYrxvaFvmUPQkT2X/HTN3ZjJXxxzm4peRPI/SQQHX3afbZ6RvHAHUXCtsvKi1reMT4abqtF6puZcdEdxnHsBXUaE0liOwEw62Nl8CQQInz2SthgkLKMIWqooBd+D5o+HLbv1ltZzrPhPyLh2UECxb4unPU0X7tLMFIDdDF0+ODfL6g5EkLyZ/pGTLECq42h42y3SnvKwaj2kWxH/odENAtpFoWaYfj1AkslCLAfnWpbA/M17zh1x+Wz8dKDGE0ufStTtGdyybXh/q9Xzs3nq7bWfeJGVjau37uwCJHXBTfkQzuy7H460JL3RzRn6uUWp0+dDQ==","folder":"2E65D45711E64489BC8AA00418844E6C","hmac":"Q3yExWPkB+a1/x9zEPBXHqhvWpwEMHzQOeSTmMWoff8=","k":"jRRLFCz8L5SkTO17ndOctgA8sIxaubAAK9q84LfC/sUvS7bHMzpFkhTT3wiXOnCF0DqhQyo9bxkk7pr2IUCwH8n6gZ+wwdIEtnpNyCc4NlZu5aXywa0E3B0Ytv3RAIFAtg7yqyqxnSsNLnoYHot79A==","o":"b3BkYXRhMDFvAAAAAAAAAABUfMSKAQo2xA4jIxRdDsuUSk9uQmJouYHJ5CT6CIgY3DZd7qrc2VejvzfkMLVTaZI9DRHdgS75LG16kL8xaUmVtGk2ZqnVWJ2UA8y4S6QPdjoWzJLJbiYvGhYicNYgK5A2WzFTrCXPT2vQfHzZeh2gJohM4ZI5wmpHPN5XchepRpyIptYTjkyg0ssLjSISul9j/4vuP4FDwH9W6Vr31JQ=","tx":1534954307,"updated":1534954307,"uuid":"6E7770574277434888367C1DCDF499D5"}}); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/corrupted.opvault/default/band_D.js: -------------------------------------------------------------------------------- 1 | ld({"DC3E009F004D4CB69741B88FBE3922DB":{"category":"001","created":1532622524,"d":"b3BkYXRhMDHXAAAAAAAAAJN2lz6c8iacNoj+sIagiGfmfXHOctAku4EYRHsNGk7gxlkYnA42/GZmjZ4Scv24o9RKYUXegKVwzA4vyb0ZCSkmNwPefKctJnD/7z9SdzOBeWjl5Q51g0K5LUp+r9w9orXNl3Yw8QLVMec5e931TO1noVwv8PUm3M4364iJxJXUfS0qq9zkARFLCfQ3tdmFsmNw4GWTQUGWmpOSoOiG3Zb+L2JFk/0PFZ4WcudjbGrzaQbncUowfarNLINfGRQnTloQJMbmPeifG/CcdavGPUXIWuAaijxr+HccoQrfaMRzACQgUzHXj8k8IQJ3WdWtEoGfkMhx+x1kOwsgd3Fvr8q3Oqpz5hF8oFMiOohGWR82","hmac":"Ynt8AFjd0RsjLfNacMCZg/Ks40imi96wf01cKe0spfo=","k":"jRRLFCz8L5SkTO17ndOctsTDEKB8mUQU4SWlpMUjtFlEn1YVGQ7tfrkHFnCtVsRWMlYPm2/XSxQDktOjjdXOjISP4nDXfEWweFWfMdOBFs15lymC4AzK14N74ZJLZN0i6rE7kXG5Svd+/dTl1tm0XA==","o":"b3BkYXRhMDFqAAAAAAAAACardnVcvLkcxg7X0ozkFfmJJcjR6i9w0q+KPVMBvuA454G+jBzfgDCtfYWb6+a5E8L4Fe8FDU8h8rYsdpc8NOfwjzyIBDr9WufyYXcRtDryKEfmfLGRob3H/rJAhMSu3cdmx6x1ZJd+CFnUi8zSBa3PbYx4iqzCMeDGjTSLXaCY3ktZF2KQPeQwpmivbr7pR3WqjTisKPzlL0HGdudBVcM=","tx":1532622559,"updated":1532622559,"uuid":"DC3E009F004D4CB69741B88FBE3922DB"}}); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/corrupted.opvault/default/band_E.js: -------------------------------------------------------------------------------- 1 | ld({"E8DAF664A83444A9A1F7335E246B82F3":{"category":"001","created":1532622588,"d":"b3BkYXRhMDHpAAAAAAAAALyZH17F95AWiKbFhGvdCJlAchn4muxkHYfoZ1wuJqGMy8oQedkJaLTyD0nrs+89VdYjnI4OmXjeN9qgQFYVSeC0t2w8erx9gwoS8buDlW5T2f2zzzwUxHyzWY9dzYyl7V8FhuXFrfbFJlMVeGBZdSSadCow8RiIqw6BQTuegLLWUIWVt2jmbRYq87KsOUFruUTgZICGo6lAe5n0lz8N/fLrblL8u1OaIA7+vrhok7aSO5Z+JsI8L7CrW+qjhSm1A1+2rY0Sd2LUnEHnbwpwI1IYcJCMC06eqvAMTCieBv6tYtKH8Ip0GB2gdNl+3waAAXzV41XXrcpZq5lrhzKHBnPP3s8FWdAk0qF6TtKuFMuq9a624LGsnbONkARwbak4XQ==","folder":"1D3B2B341F7A43F6A316179F4216E731","hmac":"cAYkV5Uos3h8L/a1I30uvq1DBL/+XlOJHyB/iXcQxM4=","k":"jRRLFCz8L5SkTO17ndOctv47sauqCqfk5/Qtn3W9YOaiG121xdMq5+h1R3OuHuCMBJTzemieJbvmAJYpyqHyA7w6HzWlzmmMfB2RxqUIOjEVHDa6rwv2u6R3uy77zDepQZhDjmf4896soKDB6+jGfA==","o":"b3BkYXRhMDFqAAAAAAAAAO0+GuArnlj8ZcZgS9Z36Am+lZ3w1rk+gbgtkCjEDmAwheXJVMBKQdkZzDUp7N/e1gJDenYLLB/by+bpC4GCY5LWy+Jn8r2s6c93raPX8u5IwpKFFOmbGg2jY29Dtn0uXGANI1s9e+3NhRmZFjm9Fv/c8WPSFtrWkqVexSNket+QIWFl4OwHyY8wM6+YvBIxZMRbzMFSwqs34bazlpSGiGQ=","tx":1532622633,"updated":1532622633,"uuid":"E8DAF664A83444A9A1F7335E246B82F3"}}); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/corrupted.opvault/default/folders.js: -------------------------------------------------------------------------------- 1 | loadFolders({"1D3B2B341F7A43F6A316179F4216E731":{"created":1532622563,"overview":"b3BkYXRhMDEWAAAAAAAAAHKFasJ8a1gACyMOlWc0tui8ZeouMgSW1ih5ZlCDiqpIa+vE5AByUmWD5JJfrUF5tNKE0j0JEGTe9ngBC83Vaign15GUDmb8bmCvnJpl2Ctd","tx":1532622585,"updated":1532622585,"uuid":"1D3B2B341F7A43F6A316179F4216E731"},"2E65D45711E64489BC8AA00418844E6C":{"created":1534937526,"overview":"b3BkYXRhMDEdAAAAAAAAAFuiusLOl55sZ+IcQRliBX5LXx1YkpfkqPCEwRXVJC8/24hfikcZceKjFv6NFagDUNMX50HWUGyiTzeNix9RNw3voJBoM62DhFUvhlvmpHe/","parent":"1D3B2B341F7A43F6A316179F4216E731","tx":1534937542,"updated":1534937542,"uuid":"2E65D45711E64489BC8AA00418844E6C"},"484EA99B6CC5431FB7B032BD1F9984D2":{"created":1532620960,"overview":"b3BkYXRhMDEMAAAAAAAAAFBT5Avd57Dt+X6a+kzhmWIIc9GKUvENb9rd561mX/DI9VYM3z4ld+oHCKo/AvcRAGkI4uBTf2ni2ZD9SXKaz9E=","trashed":true,"tx":1532620962,"updated":1532620962,"uuid":"484EA99B6CC5431FB7B032BD1F9984D2"}}); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/corrupted.opvault/default/profile.js: -------------------------------------------------------------------------------- 1 | var profile={"lastUpdatedBy":"","profileName":"default","passwordHint":"","uuid":"714A14D7017048CC9577AD050FC9C6CA","salt":"pzJ5y/CiCeU8Sbo8+k4/zg==","iterations":40000,"createdAt":1532620907,"updatedAt":1532620907,"masterKey":"b3BkYXRhMDEAAQAAAAAAALWgCJgau7XXb4yfb1yXFwpI30CyMvo1TSq+S8Yq0uEmmNRv7jmZ6+k4FS/74ZlaPEJiVATgYtUmf7qaWgGNK23j0/MCGUYdArvW6WXSYy6Bpf+tWCEN2f+qVZunyzzrSCLRwRRqeOdYjIAOEcsPUDzHUj8GZT6+vz//oFjHE4S5Z6782vgnr+pDwb2Upmy1/E7GrI32hCMR7nQnjtPEJMx10gsfgvHeiH6/3YoRksoxanf/C7l++27yPAV8PXmMhQx90TEG0P/z55I1hbCpcgBWZzKzDVUzWSvghUIe7gVW9ObNUoorpyTaBxE8A8yVyee2f6aYCp6yLMbe088NbDzGiI3audNJRjVNl/NOTA6ZGr4c6FtUugKnuqApgD3K3e65VBKdsZ0Z3ur37oVy78BmPTMlDOPPXIQZhhP/TnC4","overviewKey":"b3BkYXRhMDFAAAAAAAAAAA0U+jBMI3olRCXWK4nftC3t0mVG7aSt7VzXEXVCRGsQVy9hQAvfmGFMlFhuIiLGkRzI+zYglFqcEq1aETODJhJ7wOgOMS+lFKlBzWoFUnc4GG65nqctqCq2cBdo7hbdhA5WPm2FOmAVw2Wd8e5sK7SYCz6QgKV403EN+rXzAHI8"}; -------------------------------------------------------------------------------- /test/OpVault/Fixtures/onepassword_data/default/1C7D72EFA19A4EE98DB7A9661D2F5732_3B94A1F475014E27BFB00C99A42214DF.attachment: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/OpVault/Fixtures/onepassword_data/default/1C7D72EFA19A4EE98DB7A9661D2F5732_3B94A1F475014E27BFB00C99A42214DF.attachment -------------------------------------------------------------------------------- /test/OpVault/Fixtures/onepassword_data/default/2A632FDD32F5445E91EB5636C7580447_8FA293F2B001459D8F8F78C21E6BF9F6.attachment: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/OpVault/Fixtures/onepassword_data/default/2A632FDD32F5445E91EB5636C7580447_8FA293F2B001459D8F8F78C21E6BF9F6.attachment -------------------------------------------------------------------------------- /test/OpVault/Fixtures/onepassword_data/default/E0D293D29B10483F8DFDAC72ED0BE5C0_898CD4CD00164930A2E15B159CE65E8F.attachment: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/OpVault/Fixtures/onepassword_data/default/E0D293D29B10483F8DFDAC72ED0BE5C0_898CD4CD00164930A2E15B159CE65E8F.attachment -------------------------------------------------------------------------------- /test/OpVault/Fixtures/onepassword_data/default/F2DB5DA3FCA64372A751E0E85C67A538_23F6167DC1FB457A8DE7033ACDCD06DB.attachment: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/OpVault/Fixtures/onepassword_data/default/F2DB5DA3FCA64372A751E0E85C67A538_23F6167DC1FB457A8DE7033ACDCD06DB.attachment -------------------------------------------------------------------------------- /test/OpVault/Fixtures/onepassword_data/default/F2DB5DA3FCA64372A751E0E85C67A538_AFBDA49A5F684179A78161E40CA2AAD3.attachment: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/OpVault/Fixtures/onepassword_data/default/F2DB5DA3FCA64372A751E0E85C67A538_AFBDA49A5F684179A78161E40CA2AAD3.attachment -------------------------------------------------------------------------------- /test/OpVault/Fixtures/onepassword_data/default/FF445AB1497241A28812363154E1A738_16684B74F26145169EC03B950DC68E95.attachment: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/OpVault/Fixtures/onepassword_data/default/FF445AB1497241A28812363154E1A738_16684B74F26145169EC03B950DC68E95.attachment -------------------------------------------------------------------------------- /test/OpVault/Fixtures/onepassword_data/default/band_5.js: -------------------------------------------------------------------------------- 1 | ld({ 2 | "5ADFF73C09004C448D45565BC4750DE2": { 3 | "category": "001", 4 | "created": 1325483951, 5 | "d": "b3BkYXRhMDFTAQAAAAAAAA9fJBca6be+oz0ye3FsSVyJCY0AnQXWfJgYQgYThr4c7eiW5POIskdjdle535X+cfFdriq6OUxNv9VIbUn0QUI1jP+V75VDoa7pDIL+zpR22VfXsR6RY5S5JINpEbZ+smIrMFphM9+ToD/Xli8zxmcqfywSekbLMkITwpFfyv31ZlB0I2WZCABQ5H/P7+UIRfK+jnwPc5VkUpQW+Bf81OfTNKa8N2OH0XIUzQEKcAG6ZpBohM+V8RdVIR+7Zg77uskQU28n4gIdJi0jsoFyarM1NeCoysX9cpGJATcNNI8XKKU0LxC18yBK0ST6INXXQ9hSrG9wEv6cpsdix/GNkma+XaQp74Dar55+DnauAZpMEfJcGe6PKSLA8QfUYtiqqD9Voh2F60dyyIVqXcgdpeBfVf7jdygOXuG07dKp5qW1w1eUH8I7zLX8Y+msuFGRyHXQ1WvB4qU+iiHyy9nxP+HH4fJbl27fUJ5q3L01o96Wdi/2MVhAYcoY6RD2DlRVUJgXDW/abRFmYnyXuL6MnnI=", 6 | "folder": "379A3A7E5D5A47A6AA3A69C4D1E57D1B", 7 | "hmac": "U1LfhLPcGrQT4s1vq83f5ikspRc6JZyUFmzpwyX0Jo4=", 8 | "k": "CcSDvXgNE+Ro5U+MXx6VoYgA29o2mbTP45K5GORJaTgb3lGvFLZs0Gs7eecAaCQw5w/fJI9Frl5pl9/ntH+jJy/SOyg5KBxsGtnkjG3LXOcEJck8BBqWI/T2dfwfwSIcIji9dzZvACWifGNgnMdzBA==", 9 | "o": "b3BkYXRhMDGbAAAAAAAAAKl92atkS8UPkld5AfENoWUe4WN4E8iVpJ/bj95sFeIthtaAHWk3OFHRB5XuBunuM43sUpiAIbCVuoosOMMpM1dJX7gctwSgpHen6ObUx85NGpgGvk2rhII1CzPhER+ACkIlopVBJd9tZsXf9sR24pO62soASghk397BuyaEkobgEnS7x8pBdQ3rKnXBSO5HMdLmb9Iw0YFZYHDORrJoQN19TFqIH2LQHe2/yvdMGD7r/gCbHZR/cqPCnIyYOqvT9GSVcsnLwxC9y5PKLV6Mng4=", 10 | "tx": 1373753420, 11 | "updated": 1325483951, 12 | "uuid": "5ADFF73C09004C448D45565BC4750DE2" 13 | } 14 | }); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/onepassword_data/default/band_6.js: -------------------------------------------------------------------------------- 1 | ld({ 2 | "67979020CCA54120BAFA2742C3F23F2B": { 3 | "category": "108", 4 | "created": 1370129714, 5 | "d": "b3BkYXRhMDHAAAAAAAAAALKcrmbSK3N10mz8SnKVCpdQS2cYLptNG47UL3OT3kJ3HFTlnEZUlC+RgPGWt1ZTSiC+vGBFMIltHU3o1sJ/LxO7k8nSuX3Iky4BadclqAur8ux/kH2TyfBdWTu+sRSskE5tMb3SB0z3Yfv+w5nj3c7amD2eClrxwFyjW/Jv1reHAI4p3HD9bbDxVlVxHFuqsVlwsb8fiAdIXmhtf1ZQv8XM+Vd1KBSHaKC/nVcwyG/ZS0r4CyGdiQUq2bEvdERssRR1nzjT+g/sFseD8q4jrXVXhezXQdstl81GM3WSvVSm5lT/z6qMbCUrcPW7AZsFIcAMqtRHexBvKwfjpn3Tj5M=", 6 | "hmac": "AVY2ZVXViuYtgfnSKShK/ZbbVn6T9SMfugz7F89Kd2Q=", 7 | "k": "NwsqfULiH/XRz0LPCNJ5u1Kv4Onmqmeu1Ye4UKmipo6YspWDQ9zswlSWqgtjhKVzsv+eq9G6qQftYwG4cHbid18RdZksQWqDCrnE7arx9zwR9mYdxB9Eymb/nSU4o03D9pkAk/niM23vS7qkbbap8A==", 8 | "o": "b3BkYXRhMDE8AAAAAAAAAPnQNt3DIzXvm/rjmdk/NHmfWLgOs+/hvM6nFutXkkSPcWK2Xl9NAzyoMV86XJviJF2wYd74eJFXZgFDgflquGnrK6xQifFqMj6zxVF4r6EACcNtzHgsrv054MFtKKiZm073KEQStDhnI2dwtRWQQjM=", 9 | "tx": 1373753420, 10 | "updated": 1370129765, 11 | "uuid": "67979020CCA54120BAFA2742C3F23F2B" 12 | } 13 | }); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/onepassword_data/default/band_7.js: -------------------------------------------------------------------------------- 1 | ld({ 2 | "72366D161D9E43D98E58EB801DAD1EF8": { 3 | "category": "001", 4 | "created": 1325483951, 5 | "d": "b3BkYXRhMDEbAQAAAAAAAHuKL7sJ20Yz8sgns/j9LqJDQy9lms3XaDZBwYt8bmEKK3t2nQxNvUQVqxISzoRj/nX/axFvqcEOSOzZhxN7CvszP9eBPmTS2zTZvz4iu4NQ/LqXlUJ6wpf4HTjnhaqqcunas49y9ahK9xSICfo1mbmVyUI2raUoxMIQCzthfS/Wqr6J2uk6I0RraEO/eJBvOxO+buXfDQ5Bt9WoZREy+o0qcGEUs6kIMcTG5PmbOWV0DH3/Y29ggzzrUiaAbmvcu88e6warGI5Ii9gnW9iLt3AIFtIvuZQNhwyDDH7e8LPYOdusu7MfQGannWoc4QGTLnUkZrIozo3WTsoCEFv1Q9sYjyucSaR2Q2BEHVwiKzPsu4YaUADQSl65IgyfMRhjpU3qNsIxtu1gLjjRWwx8YV7BTOOSgz5MMwesZrMX5WcsulOgH6/TRH6mQtbi9d/kTw==", 6 | "hmac": "A3fS6NKkoS6T1vTDvd+mVUWweXeNnWdPYb+T81WfgwY=", 7 | "k": "du1CJ4AKSNBWoORyfTICsCJ9ltR/Jdy95IwZBXPsxD2fs+LmzTrFPB6sXeoB8Or7aaISaH6fzf5PJfhwIYs0WwtiJNMsHQOJ0aijvDMmpFvE1EHle+E/V9aPK0f3nws5opwfcUAxQVKAoZCg6VFXng==", 8 | "o": "b3BkYXRhMDGTAAAAAAAAAExAg52C/fG2dWHCUgSx+8mg6eRc4M2Z0Qb9+ievEU6lNLuHqQQAEnJhe8zJTNfUm7bKMA9aqNrR9EpObuMR1j+uN2pIFJmD1pDtHsemM1vnSr5tZ5jUYPjOC7pWJyvC16ap4zBPfDMcrUjCVjgnZlppyZ3cJuxwVJNFRHUqShpX7oetObnVfOeixiUsvdSFjEGC9dbzvnQHrcv4G+nwmHtSLI9vN78SCWkX8I8DKZd4QZt/94Am9OWArX3r+s3Yvq4HTvNto/kC1q+a3k55AJw=", 9 | "tx": 1373753420, 10 | "updated": 1325483951, 11 | "uuid": "72366D161D9E43D98E58EB801DAD1EF8" 12 | } 13 | }); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/onepassword_data/default/profile.js: -------------------------------------------------------------------------------- 1 | var profile={"lastUpdatedBy":"Dropbox","updatedAt":1370323483,"profileName":"default","salt":"P0pOMMN6Ow5wIKOOSsaSQg==","masterKey":"b3BkYXRhMDEAAQAAAAAAACN8JuE76yN6hbjqzEvd0RGnu3vufPcfAZ35JoyzdR1WPRvr8DMefe9MJu65DmHSwjObPC0jznXpafJQob6CNzKCNoeVC+GXIvLckvAuYUNSwILQQ1jEIcHdyQ0H2MbJ+0YlWEbvlQ8UVH5bcrMqDmTPPSRkbUG3/dV1NKHdgI0V6N/kKZ737oo+kj3ChJZQTKywvmR6RgB5et5stBaUwutNQbZ0znYtZumIlf3pjdqGK4RyCHSwmwgLUO+VFLTqDjoZ9dUcy4hQzSZiPlba3vK8vGJRlN0Qf2Y6dUj5kYAwdYdOzE/Ji3hbTNVsPOm8sjzPcPGQj8haW5UgzSDZ0mo7+ymsKJwSYjAsgvawh31WY2m5j7VR+50ERDTEyxxQ3LW7WgetAxX9l0LX0O3Jue1oW/p2l44ij9qiN9rkFScx","iterations":50000,"uuid":"2B894A18997C4638BACC55F2D56A4890","overviewKey":"b3BkYXRhMDFAAAAAAAAAAIy1hZwIGeiLn4mLE1R8lEwIOye95GEyfZcPKlyXkkb0IBTfCXM+aDxjD7hOliuTM/YMIqxK+firVvW3c5cp2QMgvQHpDW2AsAQpBqcgBgRUCSP+THMVg15ZeR9lI77mHBpTQ70D+bchvkSmw3hoEGot7YcnQCATbouhMXIMO52D","createdAt":1373753414}; -------------------------------------------------------------------------------- /test/OpVault/Fixtures/test.opvault/default/band_3.js: -------------------------------------------------------------------------------- 1 | ld({}); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/test.opvault/default/band_6.js: -------------------------------------------------------------------------------- 1 | ld({"6E7770574277434888367C1DCDF499D5":{"category":"001","created":1532620972,"d":"b3BkYXRhMDHtAAAAAAAAAKLIceCdZquxJW+0Fq+GOHyIv1zfksz91oqqvkpSHz+qES0rPwztM5fRjuVN3yYrxvaFvmUPQkT2X/HTN3ZjJXxxzm4peRPI/SQQHX3afbZ6RvHAHUXCtsvKi1reMT4abqtF6puZcdEdxnHsBXUaE0liOwEw62Nl8CQQInz2SthgkLKMIWqooBd+D5o+HLbv1ltZzrPhPyLh2UECxb4unPU0X7tLMFIDdDF0+ODfL6g5EkLyZ/pGTLECq42h42y3SnvKwaj2kWxH/odENAtpFoWaYfj1AkslCLAfnWpbA/M17zh1x+Wz8dKDGE0ufStTtGdyybXh/q9Xzs3nq7bWfeJGVjau37uwCJHXBTfkQzuy7H460JL3RzRn6uUWp0+dDQ==","folder":"2E65D45711E64489BC8AA00418844E6C","hmac":"Q3yExWPkB+a1/x9zEPBXHqhvWpwEMHzQOeSTmMWoff8=","k":"jRRLFCz8L5SkTO17ndOctgA8sIxaubAAK9q84LfC/sUvS7bHMzpFkhTT3wIXOnCF0DqhQyo9bxkk7pr2IUCwH8n6gZ+wwdIEtnpNyCc4NlZu5aXywa0E3B0Ytv3RAIFAtg7yqyqxnSsNLnoYHot79A==","o":"b3BkYXRhMDFvAAAAAAAAAABUfMSKAQo2xA4jIxRdDsuUSk9uQmJouYHJ5CT6CIgY3DZd7qrc2VejvzfkMLVTaZI9DRHdgS75LG16kL8xaUmVtGk2ZqnVWJ2UA8y4S6QPdjoWzJLJbiYvGhYicNYgK5A2WzFTrCXPT2vQfHzZeh2gJohM4ZI5wmpHPN5XchepRpyIptYTjkyg0ssLjSISul9j/4vuP4FDwH9W6Vr31JQ=","tx":1534954307,"updated":1534954307,"uuid":"6E7770574277434888367C1DCDF499D5"}}); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/test.opvault/default/band_D.js: -------------------------------------------------------------------------------- 1 | ld({"DC3E009F004D4CB69741B88FBE3922DB":{"category":"001","created":1532622524,"d":"b3BkYXRhMDHXAAAAAAAAAJN2lz6c8iacNoj+sIagiGfmfXHOctAku4EYRHsNGk7gxlkYnA42/GZmjZ4Scv24o9RKYUXegKVwzA4vyb0ZCSkmNwPefKctJnD/7z9SdzOBeWjl5Q51g0K5LUp+r9w9orXNl3Yw8QLVMec5e931TO1noVwv8PUm3M4364iJxJXUfS0qq9zkARFLCfQ3tdmFsmNw4GWTQUGWmpOSoOiG3Zb+L2JFk/0PFZ4WcudjbGrzaQbncUowfarNLINfGRQnTloQJMbmPeifG/CcdavGPUXIWuAaijxr+HccoQrfaMRzACQgUzHXj8k8IQJ3WdWtEoGfkMhx+x1kOwsgd3Fvr8q3Oqpz5hF8oFMiOohGWR82","hmac":"Ynt8AFjd0RsjLfNacMCZg/Ks40imi96wf01cKe0spfo=","k":"jRRLFCz8L5SkTO17ndOctsTDEKB8mUQU4SWlpMUjtFlEn1YVGQ7tfrkHFnCtVsRWMlYPm2/XSxQDktOjjdXOjISP4nDXfEWweFWfMdOBFs15lymC4AzK14N74ZJLZN0i6rE7kXG5Svd+/dTl1tm0XA==","o":"b3BkYXRhMDFqAAAAAAAAACardnVcvLkcxg7X0ozkFfmJJcjR6i9w0q+KPVMBvuA454G+jBzfgDCtfYWb6+a5E8L4Fe8FDU8h8rYsdpc8NOfwjzyIBDr9WufyYXcRtDryKEfmfLGRob3H/rJAhMSu3cdmx6x1ZJd+CFnUi8zSBa3PbYx4iqzCMeDGjTSLXaCY3ktZF2KQPeQwpmivbr7pR3WqjTisKPzlL0HGdudBVcM=","tx":1532622559,"updated":1532622559,"uuid":"DC3E009F004D4CB69741B88FBE3922DB"}}); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/test.opvault/default/band_E.js: -------------------------------------------------------------------------------- 1 | ld({"E8DAF664A83444A9A1F7335E246B82F3":{"category":"001","created":1532622588,"d":"b3BkYXRhMDHpAAAAAAAAALyZH17F95AWiKbFhGvdCJlAchn4muxkHYfoZ1wuJqGMy8oQedkJaLTyD0nrs+89VdYjnI4OmXjeN9qgQFYVSeC0t2w8erx9gwoS8buDlW5T2f2zzzwUxHyzWY9dzYyl7V8FhuXFrfbFJlMVeGBZdSSadCow8RiIqw6BQTuegLLWUIWVt2jmbRYq87KsOUFruUTgZICGo6lAe5n0lz8N/fLrblL8u1OaIA7+vrhok7aSO5Z+JsI8L7CrW+qjhSm1A1+2rY0Sd2LUnEHnbwpwI1IYcJCMC06eqvAMTCieBv6tYtKH8Ip0GB2gdNl+3waAAXzV41XXrcpZq5lrhzKHBnPP3s8FWdAk0qF6TtKuFMuq9a624LGsnbONkARwbak4XQ==","folder":"1D3B2B341F7A43F6A316179F4216E731","hmac":"cAYkV5Uos3h8L/a1I30uvq1DBL/+XlOJHyB/iXcQxM4=","k":"jRRLFCz8L5SkTO17ndOctv47sauqCqfk5/Qtn3W9YOaiG121xdMq5+h1R3OuHuCMBJTzemieJbvmAJYpyqHyA7w6HzWlzmmMfB2RxqUIOjEVHDa6rwv2u6R3uy77zDepQZhDjmf4896soKDB6+jGfA==","o":"b3BkYXRhMDFqAAAAAAAAAO0+GuArnlj8ZcZgS9Z36Am+lZ3w1rk+gbgtkCjEDmAwheXJVMBKQdkZzDUp7N/e1gJDenYLLB/by+bpC4GCY5LWy+Jn8r2s6c93raPX8u5IwpKFFOmbGg2jY29Dtn0uXGANI1s9e+3NhRmZFjm9Fv/c8WPSFtrWkqVexSNket+QIWFl4OwHyY8wM6+YvBIxZMRbzMFSwqs34bazlpSGiGQ=","tx":1532622633,"updated":1532622633,"uuid":"E8DAF664A83444A9A1F7335E246B82F3"}}); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/test.opvault/default/folders.js: -------------------------------------------------------------------------------- 1 | loadFolders({"1D3B2B341F7A43F6A316179F4216E731":{"created":1532622563,"overview":"b3BkYXRhMDEWAAAAAAAAAHKFasJ8a1gACyMOlWc0tui8ZeouMgSW1ih5ZlCDiqpIa+vE5AByUmWD5JJfrUF5tNKE0j0JEGTe9ngBC83Vaign15GUDmb8bmCvnJpl2Ctd","tx":1532622585,"updated":1532622585,"uuid":"1D3B2B341F7A43F6A316179F4216E731"},"2E65D45711E64489BC8AA00418844E6C":{"created":1534937526,"overview":"b3BkYXRhMDEdAAAAAAAAAFuiusLOl55sZ+IcQRliBX5LXx1YkpfkqPCEwRXVJC8/24hfikcZceKjFv6NFagDUNMX50HWUGyiTzeNix9RNw3voJBoM62DhFUvhlvmpHe/","parent":"1D3B2B341F7A43F6A316179F4216E731","tx":1534937542,"updated":1534937542,"uuid":"2E65D45711E64489BC8AA00418844E6C"},"484EA99B6CC5431FB7B032BD1F9984D2":{"created":1532620960,"overview":"b3BkYXRhMDEMAAAAAAAAAFBT5Avd57Dt+X6a+kzhmWIIc9GKUvENb9rd561mX/DI9VYM3z4ld+oHCKo/AvcRAGkI4uBTf2ni2ZD9SXKaz9E=","trashed":true,"tx":1532620962,"updated":1532620962,"uuid":"484EA99B6CC5431FB7B032BD1F9984D2"}}); -------------------------------------------------------------------------------- /test/OpVault/Fixtures/test.opvault/default/profile.js: -------------------------------------------------------------------------------- 1 | var profile={"lastUpdatedBy":"","profileName":"default","passwordHint":"","uuid":"714A14D7017048CC9577AD050FC9C6CA","salt":"pzJ5y/CiCeU8Sbo8+k4/zg==","iterations":40000,"createdAt":1532620907,"updatedAt":1532620907,"masterKey":"b3BkYXRhMDEAAQAAAAAAALWgCJgau7XXb4yfb1yXFwpI30CyMvo1TSq+S8Yq0uEmmNRv7jmZ6+k4FS/74ZlaPEJiVATgYtUmf7qaWgGNK23j0/MCGUYdArvW6WXSYy6Bpf+tWCEN2f+qVZunyzzrSCLRwRRqeOdYjIAOEcsPUDzHUj8GZT6+vz//oFjHE4S5Z6782vgnr+pDwb2Upmy1/E7GrI32hCMR7nQnjtPEJMx10gsfgvHeiH6/3YoRksoxanf/C7l++27yPAV8PXmMhQx90TEG0P/z55I1hbCpcgBWZzKzDVUzWSvghUIe7gVW9ObNUoorpyTaBxE8A8yVyee2f6aYCp6yLMbe088NbDzGiI3audNJRjVNl/NOTA6ZGr4c6FtUugKnuqApgD3K3e65VBKdsZ0Z3ur37oVy78BmPTMlDOPPXIQZhhP/TnC4","overviewKey":"b3BkYXRhMDFAAAAAAAAAAA0U+jBMI3olRCXWK4nftC3t0mVG7aSt7VzXEXVCRGsQVy9hQAvfmGFMlFhuIiLGkRzI+zYglFqcEq1aETODJhJ7wOgOMS+lFKlBzWoFUnc4GG65nqctqCq2cBdo7hbdhA5WPm2FOmAVw2Wd8e5sK7SYCz6QgKV403EN+rXzAHI8"}; -------------------------------------------------------------------------------- /test/OpVault/FolderTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.OpVault; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.OpVault 8 | { 9 | public class FolderTest 10 | { 11 | [Fact] 12 | public void Folder_properties_are_set() 13 | { 14 | var id = "id"; 15 | var name = "name"; 16 | 17 | var folder = new Folder(id, name); 18 | 19 | Assert.Equal(id, folder.Id); 20 | Assert.Equal(name, folder.Name); 21 | Assert.Same(Folder.None, folder.Parent); 22 | } 23 | 24 | [Fact] 25 | public void Null_or_unset_parent_is_reference_equal_to_None() 26 | { 27 | var folder = new Folder("", ""); 28 | Assert.Same(Folder.None, folder.Parent); 29 | 30 | folder.Parent = null; 31 | Assert.Same(Folder.None, folder.Parent); 32 | } 33 | 34 | [Fact] 35 | public void Parent_of_None_is_None() 36 | { 37 | Assert.Same(Folder.None, Folder.None.Parent); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/OpVault/KeyMacTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.OpVault; 6 | using Xunit; 7 | 8 | namespace PasswordManagerAccess.Test.OpVault 9 | { 10 | public class KeyMacTest 11 | { 12 | [Fact] 13 | public void KeyMac_created_from_bytes() 14 | { 15 | var key = new KeyMac(Buffer.Decode64()); 16 | 17 | Assert.Equal(Key.Decode64(), key.Key); 18 | Assert.Equal(MacKey.Decode64(), key.MacKey); 19 | } 20 | 21 | [Fact] 22 | public void KeyMac_created_from_base64() 23 | { 24 | var key = new KeyMac(Buffer); 25 | 26 | Assert.Equal(Key.Decode64(), key.Key); 27 | Assert.Equal(MacKey.Decode64(), key.MacKey); 28 | } 29 | 30 | [Theory] 31 | [InlineData(0)] 32 | [InlineData(63)] 33 | [InlineData(65)] 34 | public void KeyMac_ctor_throws_on_invalid_length(int length) 35 | { 36 | Exceptions.AssertThrowsInternalError(() => new KeyMac("0".Repeat(length).ToBytes()), "Buffer must be exactly 64 bytes long"); 37 | } 38 | 39 | // 40 | // Data 41 | // 42 | 43 | private const string Buffer = "a7HZUoTh0E9I7LCTF3AHDRQXGEbcnQuUMv6Vcvv7e13IOFMfmCJORzuf" + "hnDVeB4cDrxnTsPFYMTvpHboE8MPGg=="; 44 | private const string Key = "a7HZUoTh0E9I7LCTF3AHDRQXGEbcnQuUMv6Vcvv7e10="; 45 | private const string MacKey = "yDhTH5giTkc7n4Zw1XgeHA68Z07DxWDE76R26BPDDxo="; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/OpVault/UtilTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.OpVault; 6 | using Xunit; 7 | 8 | namespace PasswordManagerAccess.Test.OpVault 9 | { 10 | public class UtilTest 11 | { 12 | [Fact] 13 | public void DeriveKek_returns_key() 14 | { 15 | var expected = new KeyMac("a7HZUoTh0E9I7LCTF3AHDRQXGEbcnQuUMv6Vcvv7e13IOFMfmCJORzuf" + "hnDVeB4cDrxnTsPFYMTvpHboE8MPGg=="); 16 | var kek = Util.DeriveKek("password".ToBytes(), "pzJ5y/CiCeU8Sbo8+k4/zg==".Decode64(), 40000); 17 | 18 | Assert.Equal(expected.Key, kek.Key); 19 | Assert.Equal(expected.MacKey, kek.MacKey); 20 | } 21 | 22 | [Fact] 23 | public void DecryptAes_returns_plaintext_without_padding() 24 | { 25 | // Generated with Ruby/openssl 26 | var plaintext = Util.DecryptAes("yNVOKI5bgIJ0lPdVszvlEQ==".Decode64(), "iviviviviviviviv".ToBytes(), TestKey); 27 | 28 | Assert.Equal("decrypted data!!".ToBytes(), plaintext); 29 | } 30 | 31 | // 32 | // Data 33 | // 34 | 35 | private static readonly KeyMac TestKey = new KeyMac("key!key!key!key!key!key!key!key!saltsaltsaltsaltsaltsaltsaltsalt".ToBytes()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/ProtonPass/Fixtures/auth-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "Code": 1000, 3 | "Modulus": "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\nA5AwfkcWr2Sq7Wy8hpOHAnAFo8hZdKsVmInqvOckcHaeV36YPTK4H7yfE5cUtkHaL/MaPl1J5expZ9x/mXkAjsTicSXEi3iAAaBa4CrWYydjm29ESeejdwwsR9M/FprqvYkJ1Nb6VzhKr2ZvjPKiw2UK1N0PcuYlf+0fxOXOH0vW0aXBk0nKu1vlloqTVoYUkdevAs1eL2bCjS0dgECy2QxqNdNj9/uhDVshxEJxl1wyAPvLG0Eq7XWmRGyA3pgEDp3IwhTVQJ+BfawJf+vN/Q4tEOtTAhCFCCpL59bFw5fTehKrMJ6cfAfdQIrqiVygbW5FRevLqQVX/YlIYghn0g==\n-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwl4EARYIABAFAlwB1jwJEDUFhcTpUY8mAABhWgEAypod4Gzxqy1RoZhVMG5a\nTnbwx8xwdYwmvqq7cPHKBrYA/0+eOtSjHOA95MjC8aq1v5XOsHhbxnnSvPGJ\nZ/+kS6gI\n=ARO0\n-----END PGP SIGNATURE-----\n", 4 | "ServerEphemeral": "VEzZpI2zi3UrtmaAaSVmvzjPklX/yf5y+e9VPFWOy1SyOW1GbjHjEdNYnUejyRuY8n9WZ21zFlnk2HKVkuUIGMIwiv7Hb7N7zlz6KTqJTX7ot87jQuH/UdAk7KnboKh6aOmqMnNeaIaImKyEMhrzBfKupFjLIzlcPRXIH1ickkO4K02Ewnd5STazY/EKMxZNyi3FGvWa/97yfYGTT5qu0F9/ogmAaX2uRnZYH02SjDkeyfrWk9e+APEi6cZ+A6PRS2wx3XFhzSl9C9PDtgUT9z4IGr7Pugv+znp2dh9usyCR8tDGH9sHXHZkvKixaMDr9E/dA4RlFvMMFCa/oHt4xw==", 5 | "Version": 4, 6 | "Salt": "sNvZT3Qzr/0y5w==", 7 | "SRPSession": "b9383fa145662386c91b7c440c2a4720" 8 | } 9 | -------------------------------------------------------------------------------- /test/ProtonPass/Fixtures/extra-auth-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "Code": 1000, 3 | "SRPData": { 4 | "Modulus": "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\n+5BPJAhzD2SxyIsOs2MF5TB+9ZSoaVoxY3s6fFBvxikVkBtT/WH3knz8R73kzqhudy6OsgKO2QqJM9qIrxHWM66B/2AWSSgIR1NQw6Jr3HiJafkXrCUTmhb450gWpeBPArbEcT//SXWx86CI4m8WTuKve/x5U6E1BSAPa2LNrljPK/5Mp3FVPPwYqoMirG41CZ8kyKiPTKtgcDIzcYbmFtFAj+uBTX2YjIjC7B5VA2MjYW6Pgr5rXPnWB1Egy6s2NYbYPPQJuuZ+HLoHqCc0jfpeXjdtGbZajIgiPNYVGFg3hBrIgJUS7vZYsRr4g3UBet7N1vT/YKjmEGIjFxVRsw==\n-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwl4EARYIABAFAlwB1jwJEDUFhcTpUY8mAACUJQEA6jHX4wxL1ldnP66eU5Dq\n8bFEdhy1xQQu8INbyv42wxYBAJMBTRNitq5vstCtIZC06HKwM0KvukTjsniM\nP+czfooI\n=KAKr\n-----END PGP SIGNATURE-----\n", 5 | "ServerEphemeral": "cpOjTyrS/ZaDT1iuJmaevIlV3XcsNsk+gomM1PeAkKh7lPNdXi9E9O5yNRahoQqoLki29/2kkCZ9biiZe/zHwsy11j76kDlDl/YEUq9fHc8FTsAUjyXNZdQRhX5KXtNeLb/6qZEtqbSTMIM+lWMaYAx3bvLzfnDISc8q1k0l5DNpKxgMa0MD16zSnrGz9gRLtuXK33fgP8Xj2ARQvLHQoz6mJs32vzTh3v6jEqTWgYxhOtPZ6sHh+Y/x3sNVAaDIuDFBDyIaHRZzdsxMOJBAfgiCKnYA2y3xOczlgKtGFy+z+nLUvkZhaG2mnzA4S/bQQrKX0TneQPV5nTzBYJ+tUA==", 6 | "SrpSessionID": "0621fe3601bdf366283bf99837e891d2", 7 | "SrpSalt": "Rbr+rLgHibg/aA==", 8 | "Version": 4 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/ProtonPass/Fixtures/sessions.json: -------------------------------------------------------------------------------- 1 | { 2 | "Code": 1000, 3 | "AccessToken": "gx6unefgftd3dem4uaf2ajimi4l4cgjq", 4 | "RefreshToken": "aguiibwtz4mhnwwlquvaagi2ysppjhtx", 5 | "TokenType": "Bearer", 6 | "Scopes": [], 7 | "UID": "mbv6z4cpi4mseqh2wbljnrynlbr7lcqm", 8 | "LocalID": 8 9 | } 10 | -------------------------------------------------------------------------------- /test/ProtonPass/VaultTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | namespace PasswordManagerAccess.Test.ProtonPass 5 | { 6 | public class VaultTest : TestBase { } 7 | } 8 | -------------------------------------------------------------------------------- /test/RoboForm/Fixtures/blob.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/RoboForm/Fixtures/blob.bin -------------------------------------------------------------------------------- /test/RoboForm/Fixtures/main-folder-blob.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/RoboForm/Fixtures/main-folder-blob.bin -------------------------------------------------------------------------------- /test/RoboForm/Fixtures/more-shared-stuff-folder-blob.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/RoboForm/Fixtures/more-shared-stuff-folder-blob.bin -------------------------------------------------------------------------------- /test/RoboForm/Fixtures/shared-stuff-folder-blob.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/RoboForm/Fixtures/shared-stuff-folder-blob.bin -------------------------------------------------------------------------------- /test/RoboForm/SessionTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.RoboForm; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.RoboForm 8 | { 9 | public class SessionTest 10 | { 11 | [Fact] 12 | public void Token_is_set() 13 | { 14 | Assert.Equal("token", new Session("token", "").Token); 15 | } 16 | 17 | [Fact] 18 | public void DeviceId_is_set() 19 | { 20 | Assert.Equal("device-id", new Session("", "device-id").DeviceId); 21 | } 22 | 23 | [Fact] 24 | public void Cookies_are_set() 25 | { 26 | var session = new Session("token", "device-id"); 27 | 28 | Assert.Equal(2, session.Cookies.Count); 29 | Assert.Equal("token", session.Cookies["sib-auth"]); 30 | Assert.Equal("device-id", session.Cookies["sib-deviceid"]); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/RoboForm/TestData.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.RoboForm; 6 | 7 | namespace PasswordManagerAccess.Test.RoboForm 8 | { 9 | internal static class TestData 10 | { 11 | public const string Username = "lastpass.ruby@gmail.com"; 12 | public const string Password = "h74@aB$SCt9dTBQ3%rmAVN3oOmtGLt58Nix7!3z%vUO4Ni07rfjutHRbhJ9!SkOk"; 13 | public const string DeviceId = "B57192ee77db5e5989c5ef7e091b119ea"; 14 | public const string Nonce = "-DeHRrZjC8DZ_0e8RGsisg"; 15 | 16 | public static readonly Client.Credentials Credentials = new Client.Credentials(Username, Password, DeviceId, Nonce); 17 | 18 | public const string EncodedAuthInfoHeader = 19 | "SibAuth sid=\"6Ag93Y02vihucO9IQl1fbg\",data=\"cj0tRGVIUnJaakM4RFpfMGU4UkdzaXNn" 20 | + "TTItdGpnZi02MG0tLUZCaExRMjZ0ZyxzPUErRnQ4VU02NzRPWk9PalVqWENkYnc9PSxpPTQwOTY=\""; 21 | 22 | public static readonly AuthInfo AuthInfo = new AuthInfo( 23 | sid: "6Ag93Y02vihucO9IQl1fbg", 24 | data: "r=-DeHRrZjC8DZ_0e8RGsisgM2-tjgf-60m--FBhLQ26tg,s=A+Ft8UM674OZOOjUjXCdbw==,i=4096", 25 | nonce: "-DeHRrZjC8DZ_0e8RGsisgM2-tjgf-60m--FBhLQ26tg", 26 | salt: "A+Ft8UM674OZOOjUjXCdbw==".Decode64(), 27 | iterationCount: 4096, 28 | isMd5: false 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/RoboForm/UtilTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.RoboForm; 6 | using Xunit; 7 | 8 | namespace PasswordManagerAccess.Test.RoboForm 9 | { 10 | public class UtilTest 11 | { 12 | [Fact] 13 | public void RandomDeviceId_starts_with_B() 14 | { 15 | for (var i = 0; i < 10; ++i) 16 | Assert.StartsWith("B", Util.RandomDeviceId()); 17 | } 18 | 19 | [Fact] 20 | public void RandomDeviceId_has_correct_length() 21 | { 22 | for (var i = 0; i < 10; ++i) 23 | Assert.Equal(33, Util.RandomDeviceId().Length); 24 | } 25 | 26 | [Fact] 27 | public void ComputeClientKey_returns_key() 28 | { 29 | // Generated with the original JavaScript code 30 | Assert.Equal("8sbDhSTLwbl0FhiHAxFxGUQvQwcr4JIbpExO64+Jj8o=".Decode64(), Util.ComputeClientKey(TestData.Password, TestData.AuthInfo)); 31 | } 32 | 33 | [Fact] 34 | public void HashPassword_returns_hashed_password() 35 | { 36 | // TODO: Generate a test case with MD5 37 | 38 | // Generated with the original JavaScript code 39 | Assert.Equal("b+rd7TUt65+hdE7+lHCBPPWHjxbq6qs0y7zufYfqHto=".Decode64(), Util.HashPassword(TestData.Password, TestData.AuthInfo)); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/StickyPassword/Fixtures/corrupted.sqlite: -------------------------------------------------------------------------------- 1 | not an sqlite database 2 | -------------------------------------------------------------------------------- /test/StickyPassword/Fixtures/db.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/detunized/password-manager-access/95927aae89fb764aaf0fc1ca667fa5a7c68e0329/test/StickyPassword/Fixtures/db.sqlite -------------------------------------------------------------------------------- /test/StickyPassword/S3TokenTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.StickyPassword; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.StickyPassword 8 | { 9 | public class S3TokenTest 10 | { 11 | [Fact] 12 | public void S3Token_properties_are_set() 13 | { 14 | var accessKeyId = "accessKeyId"; 15 | var secretAccessKey = "secretAccessKey"; 16 | var securityToken = "securityToken"; 17 | var bucketName = "bucketName"; 18 | var objectPrefix = "objectPrefix"; 19 | 20 | var token = new S3Token(accessKeyId, secretAccessKey, securityToken, bucketName, objectPrefix); 21 | 22 | Assert.Equal(accessKeyId, token.Credentials.AccessKeyId); 23 | Assert.Equal(secretAccessKey, token.Credentials.SecretAccessKey); 24 | Assert.Equal(securityToken, token.Credentials.SecurityToken); 25 | Assert.Equal(bucketName, token.BucketName); 26 | Assert.Equal(objectPrefix, token.ObjectPrefix); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/StickyPassword/VaultTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.StickyPassword; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.StickyPassword 8 | { 9 | public class VaultTest 10 | { 11 | [Fact] 12 | public void Vault_GenerateRandomDeviceId_returns_id() 13 | { 14 | var id = Vault.GenerateRandomDeviceId(); 15 | 16 | Assert.Matches("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", id); 17 | } 18 | 19 | [Fact] 20 | public void Vault_GenerateRandomDeviceId_returns_different_ids() 21 | { 22 | var id1 = Vault.GenerateRandomDeviceId(); 23 | var id2 = Vault.GenerateRandomDeviceId(); 24 | 25 | Assert.NotEqual(id1, id2); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/TrueKey/AccountTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.TrueKey; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.TrueKey 8 | { 9 | public class AccountTest 10 | { 11 | [Fact] 12 | public void Account_properties_are_set() 13 | { 14 | var id = 1337; 15 | var name = "name"; 16 | var username = "username"; 17 | var password = "password"; 18 | var url = "url"; 19 | var note = "note"; 20 | var account = new Account(id, name, username, password, url, note); 21 | 22 | Assert.Equal(id, account.Id); 23 | Assert.Equal(name, account.Name); 24 | Assert.Equal(username, account.Username); 25 | Assert.Equal(password, account.Password); 26 | Assert.Equal(url, account.Url); 27 | Assert.Equal(note, account.Note); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/TrueKey/EncryptedAccountTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.TrueKey; 6 | using Xunit; 7 | 8 | namespace PasswordManagerAccess.Test.TrueKey 9 | { 10 | public class EncryptedAccountTest 11 | { 12 | [Fact] 13 | public void EncryptedAccount_properties_are_set() 14 | { 15 | var id = 1337; 16 | var name = "name"; 17 | var username = "username"; 18 | var password = "password".ToBytes(); 19 | var url = "url"; 20 | var note = "note".ToBytes(); 21 | var account = new EncryptedAccount(id, name, username, password, url, note); 22 | 23 | Assert.Equal(id, account.Id); 24 | Assert.Equal(name, account.Name); 25 | Assert.Equal(username, account.Username); 26 | Assert.Equal(password, account.EncryptedPassword); 27 | Assert.Equal(url, account.Url); 28 | Assert.Equal(note, account.EncryptedNote); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/TrueKey/EncryptedVaultTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.TrueKey; 6 | using Xunit; 7 | 8 | namespace PasswordManagerAccess.Test.TrueKey 9 | { 10 | public class EncryptedVaultTest 11 | { 12 | [Fact] 13 | public void EncryptedVault_properties_are_set() 14 | { 15 | var salt = "salt".ToBytes(); 16 | var key = "key".ToBytes(); 17 | var accounts = new EncryptedAccount[0]; 18 | var vault = new EncryptedVault(salt, key, accounts); 19 | 20 | Assert.Equal(salt, vault.MasterKeySalt); 21 | Assert.Equal(key, vault.EncryptedMasterKey); 22 | Assert.Same(accounts, vault.EncryptedAccounts); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/TrueKey/Fixtures/auth-check-pending-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "refreshToken": null, 3 | "refreshTokenExpiry": 0.0, 4 | "ResponseResult": { 5 | "IsSuccess": false, 6 | "ErrorCode": "E3013", 7 | "ErrorDescription": "Authentication Pending", 8 | "TransactionId": null 9 | }, 10 | "nextStep": 0, 11 | "NextStepData": null, 12 | "authCode": null, 13 | "redirectUri": null, 14 | "idToken": null, 15 | "state": null, 16 | "cloudKey": null, 17 | "isTrustedDevice": false, 18 | "uasTokenInfo": null 19 | } 20 | -------------------------------------------------------------------------------- /test/TrueKey/Fixtures/auth-step1-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "oAuthTransId": "6cdfcd43-065c-43a1-aa7a-017de98eefd0", 3 | "responseResult": { 4 | "isSuccess": true, 5 | "errorCode": null, 6 | "errorDescription": null, 7 | "transactionId": null 8 | }, 9 | "riskAnalysisInfo": null 10 | } 11 | -------------------------------------------------------------------------------- /test/TrueKey/Fixtures/auth-step2-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "refreshTokenExpiry": 0.0, 3 | "responseResult": { 4 | "isSuccess": true, 5 | "errorCode": null, 6 | "errorDescription": null, 7 | "transactionId": "296264da-50a5-4d32-b9ab-c086f360093b" 8 | }, 9 | "riskAnalysisInfo": { 10 | "nextStep": 12, 11 | "flowId": null, 12 | "nextStepData": { 13 | "oobDevices": [ 14 | { 15 | "deviceId": "MTU5NjAwMjI3MQP04dNsmSNQ2LOPWrIepKI6ra8lkjoubkr1B9TMpWSSytkNsFK2n/utQGl+8giXPuzWxS+p9GSPvBQPE2444eZgyDogtwq3vWKX2ayAvmEj1G198GiDmjfjXpkMq41hQYU=", 16 | "deviceName": "LGE Nexus 5", 17 | "oobPreferred": false 18 | } 19 | ], 20 | "verificationEmail": "username@example.com", 21 | "bcaResyncToken": null 22 | }, 23 | "altNextStep": 14, 24 | "bcaNextStep": 0, 25 | "bcaNextStepData": null 26 | }, 27 | "authCode": null, 28 | "redirectUrl": null, 29 | "state": null, 30 | "cloudKey": null, 31 | "idToken": null, 32 | "uasTokenInfo": null, 33 | "oAuthTransId": "ae830c59-634b-437c-95b6-58158e85ffae", 34 | "activeSession": false, 35 | "templateCount": 0, 36 | "refreshToken": null 37 | } 38 | -------------------------------------------------------------------------------- /test/TrueKey/Fixtures/post-response-with-error.json: -------------------------------------------------------------------------------- 1 | { 2 | "ResponseResult": { 3 | "IsSuccess": false, 4 | "ErrorCode": "E1234", 5 | "ErrorDescription": "Serious error, no kidding" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/TrueKey/Fixtures/register-new-device-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "responseResult": { 3 | "isSuccess": true, 4 | "errorCode": "", 5 | "errorDescription": null, 6 | "transactionId": "e36c90af-9f73-4354-af78-902dfa80bd87" 7 | }, 8 | "clientToken": "AQCmAwEAAh4AAAAAWMajHQAAGU9DUkEtMTpIT1RQLVNIQTI1Ni0wOlFBMDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIOiRfItpCTOkvq0ZfV2+GgvP83aF9SrTBfOuabZfcQr9AAAAAAgAIBwWTZpUTIn493Us/JwczrK6O0+LH8FRidFaZkJ2AlTu", 9 | "tkDeviceId": "d871347bd0a3e7af61f60f511bc7de5e944c5c778705649d4aa8dc77bcd21489412894", 10 | "tkDeviceNonce": "1489412894", 11 | "tkReEnrollToken": "bd23b315af167d81ea216a1c26a1a836657583e84fecea2fb3281920a33aec84" 12 | } 13 | -------------------------------------------------------------------------------- /test/TrueKey/Fixtures/save-device-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "responseResult": { 3 | "isSuccess": true, 4 | "errorCode": null, 5 | "errorDescription": null, 6 | "transactionId": null 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/ZohoVault/AccountTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.ZohoVault; 5 | using Xunit; 6 | 7 | namespace PasswordManagerAccess.Test.ZohoVault 8 | { 9 | public class AccountTest 10 | { 11 | [Fact] 12 | public void Account_properties_are_set() 13 | { 14 | var id = "id"; 15 | var name = "name"; 16 | var username = "username"; 17 | var password = "password"; 18 | var url = "url"; 19 | var note = "note"; 20 | var account = new Account(id, name, username, password, url, note); 21 | 22 | Assert.Equal(id, account.Id); 23 | Assert.Equal(name, account.Name); 24 | Assert.Equal(username, account.Username); 25 | Assert.Equal(password, account.Password); 26 | Assert.Equal(url, account.Url); 27 | Assert.Equal(note, account.Note); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/auth-info-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "operation": { 3 | "details": { 4 | "ITERATION": 1000, 5 | "LASTMODFIEDTIME": "", 6 | "LOGIN": "PBKDF2_AES", 7 | "PASSPHRASE": "awNZM8agxVecKpRoC821Oq6NlvVwm6KpPGW+cLdzRoc2Mg5vqPQzoONwww==", 8 | "SALT": "f78e6ffce8e57501a02c9be303db2c68", 9 | "USER": { 10 | "EMAIL": "lastpass.ruby@gmail.com", 11 | "USERID": "30024000000008004", 12 | "USERNAME": "lastpass.ruby" 13 | } 14 | }, 15 | "name": "GET_LOGIN", 16 | "result": { 17 | "message": "", 18 | "status": "success" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/auth-info-with-shared-items-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "operation": 3 | { 4 | "name": "GET_LOGIN", 5 | "result": 6 | { 7 | "status": "success", 8 | "message": "" 9 | }, 10 | "details": 11 | { 12 | "LASTMODFIEDTIME": "", 13 | "CREATIONTIME": "1560635027050", 14 | "LOGIN": "PBKDF2_AES", 15 | "USER": 16 | { 17 | "USERNAME": "LastPass Ruby", 18 | "NEW_UI": false, 19 | "USERID": "113381000000009004", 20 | "EMAIL": "guess@gmail.com", 21 | "ZUID": "691361765", 22 | "ROLES": "SuperAdmin,Admin,User" 23 | }, 24 | "ITERATION": 1000, 25 | "SALT": "d4a220dd898c1be90b9079bc5080250b", 26 | "PASSPHRASE": "/QHU4JJmBV2suF3AQxAXWKKE3hbp44y5KF4mInVTjtcXv92zvxrW0wy1lA==" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/auth-info-with-unknown-kdf-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "operation": { 3 | "details": { 4 | "ITERATION": 1000, 5 | "LASTMODFIEDTIME": "", 6 | "LOGIN": "UNKNOWN_KDF", 7 | "PASSPHRASE": "awNZM8agxVecKpRoC821Oq6NlvVwm6KpPGW+cLdzRoc2Mg5vqPQzoONwww==", 8 | "SALT": "f78e6ffce8e57501a02c9be303db2c68", 9 | "USER": { 10 | "EMAIL": "lastpass.ruby@gmail.com", 11 | "USERID": "30024000000008004", 12 | "USERNAME": "lastpass.ruby" 13 | } 14 | }, 15 | "name": "GET_LOGIN", 16 | "result": { 17 | "message": "", 18 | "status": "success" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/login-incorrect-password-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 500, 3 | "resource_name": "passwordauth", 4 | "message": "Invalid password", 5 | "errors": [ 6 | { 7 | "code": "IN102", 8 | "message": "Invalid password" 9 | } 10 | ], 11 | "localized_message": "Incorrect password. Please try again." 12 | } 13 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/login-mfa-required-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "passwordauth": { 3 | "modes": { 4 | "totp": {}, 5 | "yubikey": { 6 | "yub-name": "yk" 7 | }, 8 | "allowed_modes": [ 9 | "totp", 10 | "yubikey", 11 | "recoverycode" 12 | ] 13 | }, 14 | "trust_mfa_days": 180, 15 | "href": "https://accounts.zoho.com/signin/v2/primary/691361765/password", 16 | "restrict_trust_mfa": false, 17 | "token": "368385fa1577b9edf2a4307383c6f012a91d731f0e3359f57cf7be0ee66b78804aa0ac3c3ae66fe9c3f4b45e340c34da41bfa446bbcf9e1eca7bb77d9ccafd5b814256b5395cf8a4691306f5862c3d37ad37cf100e8c887c91c4b27a498317434522667c16fcd1953ca975da1f5827613a60143d794876ba3f207a12051eb22fb728edee417dabcae5cdc4efde54c93ba7ab15181e9a46d998a7a21ec3670024" 18 | }, 19 | "status_code": 201, 20 | "code": "MFA302", 21 | "resource_name": "passwordauth", 22 | "message": "SignIn success with MFA verification redirection" 23 | } 24 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/login-success-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "passwordauth": { 3 | "code": "SIGIN_SUCCESS", 4 | "redirect_uri": "https://vault.zoho.com/online/main", 5 | "href": "https://accounts.zoho.com/signin/v2/primary/633590133/password" 6 | }, 7 | "status_code": 201, 8 | "code": "SI200", 9 | "resource_name": "passwordauth", 10 | "message": "Sign in success" 11 | } 12 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/login-unknown-error-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 600, 3 | "resource_name": "passwordauth", 4 | "message": "Unknown error", 5 | "errors": [ 6 | { 7 | "code": "E600", 8 | "message": "Unknown error" 9 | } 10 | ], 11 | "localized_message": "Unknown error. Please don't try again." 12 | } 13 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/lookup-another-region-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 500, 3 | "data": { 4 | "loginid": "dude@lebowski.com", 5 | "redirect_uri": "https://accounts.zoho.com" 6 | }, 7 | "resource_name": "lookup", 8 | "message": "User exists in another DC", 9 | "errors": [ 10 | { 11 | "code": "U400", 12 | "message": "User exists in another DC" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/lookup-no-user-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 400, 3 | "resource_name": "lookup", 4 | "message": "User does not exists", 5 | "errors": [ 6 | { 7 | "code": "U401", 8 | "message": "User does not exists" 9 | } 10 | ], 11 | "localized_message": "This account cannot be found. Please use a different account or sign up<\/a> for a new account." 12 | } 13 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/lookup-success-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "lookup": { 3 | "identifier": "633590133", 4 | "loginid": "dude@lebowski.com", 5 | "modes": { 6 | "password": {}, 7 | "allowed_modes": [ 8 | "password", 9 | "otp" 10 | ], 11 | "otp": { 12 | "data": [ 13 | { 14 | "is_primary": true, 15 | "e_mobile": "eb06d8b482ffba5c6659f24bdc35f46d90e3ba55ef398898f70a79b7f1f86308", 16 | "r_mobile": "49-12******34" 17 | } 18 | ], 19 | "count": 1 20 | } 21 | }, 22 | "digest": "5edfa5597c0acd2b6d9ce6358884ee9596eaafeb9aee278912b3083fa03c2e18d4ccbe09dd06c00939157cb00b3259af", 23 | "href": "https://accounts.zoho.com/signin/v2/lookup/lastpass.ruby@gmail.com" 24 | }, 25 | "status_code": 201, 26 | "code": "U200", 27 | "resource_name": "lookup", 28 | "message": "User exists" 29 | } 30 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/lookup-unknown-error-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 600, 3 | "resource_name": "lookup", 4 | "message": "Unknown error", 5 | "errors": [ 6 | { 7 | "code": "U403", 8 | "message": "Unknown error" 9 | } 10 | ], 11 | "localized_message": "Unknown error" 12 | } 13 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/mfa-incorrect-code-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 500, 3 | "resource_name": "totpsecauth", 4 | "message": "Invalid Verification Code", 5 | "errors": [ 6 | { 7 | "code": "IN105", 8 | "message": "Invalid Verification Code" 9 | } 10 | ], 11 | "localized_message": "Incorrect OTP. Please try again." 12 | } 13 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/mfa-success-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 201, 3 | "totpsecauth": { 4 | "href": "https://accounts.zoho.com/signin/v2/secondary/691361765/totp", 5 | "status": "success" 6 | }, 7 | "resource_name": "totpsecauth", 8 | "message": "Success" 9 | } 10 | -------------------------------------------------------------------------------- /test/ZohoVault/Fixtures/trust-success-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "trustmfa": { 3 | "code": "POST_ANNOUCEMENT_REDIRECTION", 4 | "redirect_uri": "https://accounts.zoho.eu/accounts/announcement/oneauth-banner?servicename=ZohoVault&serviceurl=https%3A%2F%2Fvault.zoho.eu%2Fonline%2Fmain", 5 | "href": "https://accounts.zoho.eu/signin/v2/secondary/20069106347/trust" 6 | }, 7 | "status_code": 201, 8 | "code": "SI302", 9 | "resource_name": "trustmfa", 10 | "message": "SignIn success with post announcement redirection" 11 | } 12 | -------------------------------------------------------------------------------- /test/ZohoVault/TestData.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Dmitry Yakimenko (detunized@gmail.com). 2 | // Licensed under the terms of the MIT license. See LICENCE for details. 3 | 4 | using PasswordManagerAccess.Common; 5 | using PasswordManagerAccess.ZohoVault; 6 | 7 | namespace PasswordManagerAccess.Test.ZohoVault 8 | { 9 | internal static class TestData 10 | { 11 | // Calculated with the original Js code 12 | public static readonly byte[] Key = "d7643007973dba7243d724f66fd806bf".ToBytes(); 13 | public static readonly byte[] Key2 = "c16c3e48073b8932c77c1aaa2170fbf3".ToBytes(); 14 | public const string Passphrase = "passphrase123"; 15 | 16 | // Based on "auth-info-response.json" 17 | public static readonly Client.AuthInfo AuthInfo = 18 | new( 19 | iterationCount: 1000, 20 | salt: "f78e6ffce8e57501a02c9be303db2c68".ToBytes(), 21 | encryptionCheck: "awNZM8agxVecKpRoC821Oq6NlvVwm6KpPGW+cLdzRoc2Mg5vqPQzoONwww==".Decode64() 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | --------------------------------------------------------------------------------