├── .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