Compare commits

..

204 Commits

Author SHA1 Message Date
Philipp Crocoll
9be215c295 Merge remote-tracking branch 'origin/master' into feature/scrollable-credential-dialogs 2025-07-29 12:06:22 +02:00
PhilippC
bb97a023de Merge pull request #2964 from PhilippC/v1.13
V1.13 fixes:
* fix QuickUnlock layout
* add an option to disable QuickUnlock blocking for cases where this is explicitly needed
2025-07-29 12:02:58 +02:00
Philipp Crocoll
edb4907bf5 add option to disable QuickUnlock blocking behavior, closes #2960 2025-07-29 10:53:02 +02:00
Philipp Crocoll
a718c7ed7e fix quickunlock layout also when showing the "blocked" message 2025-07-29 10:50:43 +02:00
Philipp Crocoll
4f11789f26 remove no longer used file 2025-07-29 10:22:37 +02:00
Philipp Crocoll
eb15861b13 fix QuickUnlock layout to close #2956 2025-07-29 10:22:18 +02:00
Philipp Crocoll
8c2c8049c8 make credential dialogs scrollable. Closes #2959 2025-07-29 09:49:10 +02:00
PhilippC
2f3761b0a7 Merge pull request #2949 from PhilippC/update/target-sdk-35
Update to .net 9 and Target SDK version 35
2025-07-22 16:01:07 +02:00
Philipp Crocoll
260bc8adb2 adjust layouts and colors for Edge-to-Edge with transparent status bar as is default in Android 35+ 2025-07-22 15:16:28 +02:00
Philipp Crocoll
87e979635b Merge remote-tracking branch 'origin/master' into update/target-sdk-35 2025-07-22 09:51:25 +02:00
PhilippC
0c9c163755 Merge pull request #2954 from PhilippC/ui-improvement/non-cancelable-dialogs
Make dialogs for SFTP, FTP, Mega, WebDav and Nextcloud not cancelable
2025-07-22 09:05:36 +02:00
Philipp Crocoll
74ceea562b Make dialogs for SFTP, FTP, Mega, WebDav and Nextcloud not "cancelable", i.e. they don't close when tapping outside. 2025-07-22 08:41:01 +02:00
Philipp Crocoll
c6a5362ecb update to target sdk version 35 2025-07-15 17:16:14 +02:00
Philipp Crocoll
f655a89be0 don't run the release workflow when creating a tag 2025-07-15 14:26:12 +02:00
Philipp Crocoll
0d6f837578 manifest and changelog for 1.14-pre0 2025-07-15 14:24:10 +02:00
PhilippC
8c61b028b7 Merge pull request #2789 from PhilippC/feature/82-smb-support
Samba support
2025-07-15 14:17:54 +02:00
Philipp Crocoll
a3d5273285 Merge branch 'master' into feature/82-smb-support 2025-07-15 13:45:01 +02:00
Philipp Crocoll
93cf4f790c change TargetFramework to .net9 2025-07-15 13:41:58 +02:00
PhilippC
cd323c0a22 Merge pull request #2947 from PhilippC/feature/webdav-improvements
Webdav improvements:
 * Add support for chunked uploads (enabled by default)
 * Fix determination of resource type (file vs folder)
 * add support for transactional write
2025-07-15 13:38:08 +02:00
PhilippC
99ca8bf953 Merge pull request #2829 from PhilippC/1617-use-gnu-tls-stream
Use GnuTLS stream for FTPS
2025-07-15 13:23:05 +02:00
Philipp Crocoll
d40b3dc15c fix build issue with NoNet 2025-07-15 13:16:44 +02:00
Philipp Crocoll
057a7e2f7a another nonet fix 2025-07-15 13:12:18 +02:00
PhilippC
1b73c536d5 Merge pull request #2939 from PhilippC/l10n_master3
New Crowdin updates
2025-07-15 12:32:08 +02:00
Philipp Crocoll
2593a8548f Merge branch 'master' into 1617-use-gnu-tls-stream 2025-07-15 12:25:01 +02:00
Philipp Crocoll
13306a9076 fix another build issue in NoNet 2025-07-15 12:20:25 +02:00
Philipp Crocoll
cfb5098b38 add support for transactional upload 2025-07-15 12:18:45 +02:00
Philipp Crocoll
d04d455fbd add missing changelog for 1.13 2025-07-15 12:00:05 +02:00
Philipp Crocoll
b83c4b3772 improve Samba dialog. fix NoNet build 2025-07-15 11:51:15 +02:00
Philipp Crocoll
f03c11381e Merge branch 'master' into feature/82-smb-support 2025-07-15 11:28:49 +02:00
Philipp Crocoll
8a03ddb7f3 always upload files to Github release 2025-07-15 11:08:26 +02:00
Philipp Crocoll
913222d7cb allow chunked uploads, closes https://github.com/PhilippC/keepass2android/issues/2777 2025-07-15 11:07:40 +02:00
Philipp Crocoll
3e6d86c206 correctly check if an item is a folder or file. closes https://github.com/PhilippC/keepass2android/issues/2589 2025-07-15 09:10:41 +02:00
Philipp Crocoll
d6ce2a32e9 allow manually triggering a release workflow run 2025-07-15 08:30:17 +02:00
Philipp Crocoll
21f1c8404c update workflows to not create signed apks during build but only in release. Create releases as drafts. 2025-07-15 08:14:52 +02:00
PhilippC
16ff81cf81 New translations strings.xml (Czech) 2025-07-14 10:57:07 +02:00
PhilippC
0636f687ac New translations strings.xml (Greek) 2025-07-09 13:58:02 +02:00
PhilippC
60d8900473 New translations strings.xml (Greek) 2025-07-09 11:40:00 +02:00
PhilippC
4b2d2ef768 New translations strings.xml (Portuguese, Brazilian) 2025-07-09 02:47:52 +02:00
PhilippC
48899ba9a0 New translations strings.xml (Portuguese, Brazilian) 2025-07-09 01:34:39 +02:00
Philipp Crocoll
092b8689b8 fix build-artifact paths 2025-07-08 21:35:51 +02:00
Philipp Crocoll
3d3ba45cb1 extract keystore for subsequent build step 2025-07-08 18:23:02 +02:00
Philipp Crocoll
b380100307 Manifest and changelog for v1.13 2025-07-08 17:57:52 +02:00
Philipp Crocoll
4c5ddd59d8 build signed apk on every build of master branch 2025-07-08 17:57:37 +02:00
PhilippC
5ed183f318 Merge pull request #2929 from PhilippC/l10n_master3
New Crowdin updates
2025-07-08 17:56:50 +02:00
PhilippC
9c27fd3e78 Merge pull request #2938 from PhilippC/security/audit_suggestions
Security suggestions from Audit
2025-07-08 17:52:16 +02:00
Philipp Crocoll
62c361feb0 revert unintentional change 2025-07-08 17:03:32 +02:00
Philipp Crocoll
0ee2495528 disable password-based QuickUnlock when device is not protected by screen lock 2025-07-08 16:54:45 +02:00
Philipp Crocoll
7dc635a625 Update KeePass2 code for password quality estimation; add and use list of most popular passwords to account for NIST recommendation of using "blocklists" 2025-07-08 12:09:59 +02:00
Philipp Crocoll
e15112c3b4 disable cleartextTrafficPermitted. default to https for links. 2025-07-08 10:35:50 +02:00
PhilippC
1d85fffb18 New translations strings.xml (Chinese Simplified) 2025-07-07 18:44:26 +02:00
PhilippC
5e2f29e737 New translations strings.xml (Chinese Simplified) 2025-07-07 17:20:28 +02:00
Philipp Crocoll
89a09ea142 set version to 1.12-r9d 2025-07-05 14:17:07 +02:00
Philipp Crocoll
628c0d2c19 enable creation of a release 2025-07-05 14:16:17 +02:00
Philipp Crocoll
584feabe44 build process: add previously missing change; fix error in build.yml, more verbose output in release.yml 2025-07-05 13:34:01 +02:00
Philipp Crocoll
ae2cfde897 change makefile to no longer use msbuild but always dotnet 2025-07-05 13:10:21 +02:00
Philipp Crocoll
42c66670b8 release process: make find calls to run in bash 2025-07-05 12:12:49 +02:00
Philipp Crocoll
288539b902 make output more verbose 2025-07-05 11:09:55 +02:00
Philipp Crocoll
61fd32f121 avoid building the "full" apk when calling apk_split 2025-07-05 09:39:54 +02:00
Philipp Crocoll
43108ec4a6 remove ls step in release 2025-07-05 08:35:58 +02:00
Philipp Crocoll
51089c6b98 change build artifact release paths. make archive name depend on matrix variables. 2025-07-05 08:16:30 +02:00
Philipp Crocoll
cf18fcf91c remove distclean from build 2025-07-05 08:15:58 +02:00
Philipp Crocoll
da0513c768 don't create release but only list files to test the release workflow without polluting github releases 2025-07-05 07:49:39 +02:00
Philipp Crocoll
37f520cdbe manifest for r9c 2025-07-05 07:40:21 +02:00
Philipp Crocoll
c98572bee0 fix wildcard for selecting files to upload to release 2025-07-05 07:39:44 +02:00
Philipp Crocoll
b1774ffc4b increase version number ot r9b 2025-07-05 07:19:26 +02:00
Philipp Crocoll
57aaa0c4cd start release action by pushing version tags instead of when release is created on Github 2025-07-05 07:18:21 +02:00
Philipp Crocoll
b961ae1b33 make sure AndroidManifest.xml exists before running nuget 2025-07-05 07:12:17 +02:00
Philipp Crocoll
5e418e2b1b run workload update before any dotnet/nuget calls in github actions 2025-07-05 06:57:00 +02:00
Philipp Crocoll
6d22a213f3 add build target apk_split; build all combinations in a Github action when creating the release 2025-07-05 06:44:28 +02:00
Philipp Crocoll
a76addc43f manifest for 1.12-r9 2025-07-05 06:43:39 +02:00
Philipp Crocoll
1d96217713 remove GooglePlayServices Auth and Base from apk in NoNet build 2025-07-05 06:42:42 +02:00
Philipp Crocoll
d2b8fdcfff allow using Dropbox secrets from environment (better suited for Github action builds) 2025-07-05 06:34:23 +02:00
PhilippC
426fbc2510 New translations strings.xml (Polish) 2025-07-04 23:10:33 +02:00
PhilippC
e89a961c02 New translations strings.xml (Polish) 2025-07-04 22:15:11 +02:00
PhilippC
766c29b7a9 New translations strings.xml (Spanish) 2025-07-03 14:39:21 +02:00
Philipp Crocoll
507b671448 exclude Mega and OneDrive from NoNet build. Manifest for 1.12-r8c 2025-07-02 21:39:37 +02:00
Philipp Crocoll
3118ffaeb5 update version code and name => r8b (to distinguish from previously incorrect r8 build) 2025-07-02 21:04:22 +02:00
Philipp Crocoll
0abe29bd77 restore NoNet build, including removal of MLKit for that Flavor as it adds internet permission 2025-07-02 21:03:20 +02:00
Philipp Crocoll
f3a7831390 upload release artifacts before rebuilding 2025-06-30 22:29:31 +02:00
Philipp Crocoll
37cd58f7ba Merge branch 'v1.12' 2025-06-30 21:59:46 +02:00
Philipp Crocoll
7dd80a8ef7 remove "net" where no longer appropriate from workflow file 2025-06-30 16:16:55 +02:00
Philipp Crocoll
c78636264b align net and nonet manifests 2025-06-30 16:05:02 +02:00
Philipp Crocoll
035506a5a3 add nonet to release workflow 2025-06-30 16:04:38 +02:00
PhilippC
4cf46ef062 Merge pull request #2933 from PhilippC/v1.12
several updates for V1.12
2025-06-30 15:48:57 +02:00
Philipp Crocoll
c7b8063171 add previously forgotten changes 2025-06-30 14:36:59 +02:00
Philipp Crocoll
0a8b149c9a fix failing AutofillTest by adjusting to changed policy 2025-06-30 14:05:26 +02:00
Philipp Crocoll
9240a27791 run workload update before running tests, otherwise dotnet test can fail 2025-06-30 13:38:54 +02:00
PhilippC
e90d5b903c Merge pull request #2932 from PhilippC/2915-bug-exporting-the-key-file-broken-android-file-browser-doesnt-open
Fix crash when exporting key file
2025-06-30 12:43:08 +02:00
Philipp Crocoll
50b4a9f1b9 add missing Theme attributes 2025-06-30 12:42:05 +02:00
Philipp Crocoll
9783c3b5fe manifest for 1.12-r7 2025-06-24 17:12:22 +02:00
Philipp Crocoll
7a837e3237 show autofill more often without requiring manual requests, might mitigate issues in https://github.com/PhilippC/keepass2android/issues/2898 2025-06-24 17:10:50 +02:00
Philipp Crocoll
c8f6714373 refactoring: simplify autofill code 2025-06-24 17:10:08 +02:00
Philipp Crocoll
bc0313aa6a remove no-longer-needed step in release workflow 2025-06-24 15:41:46 +02:00
Philipp Crocoll
0f98668bcd improve app stability and refactor to get better logs 2025-06-24 15:41:11 +02:00
PhilippC
c4206e58bf New translations strings.xml (German) 2025-06-24 15:34:08 +02:00
PhilippC
630ededf3b New translations strings.xml (German) 2025-06-24 13:51:08 +02:00
Philipp Crocoll
8d1195ac96 manifest for v1.12-r6b. Same as v1.12-r6, but need a new version code for Google Play release based on github-action built apk 2025-06-18 03:00:52 +02:00
Philipp Crocoll
df731ac1b3 add checksum to releases; cleanup workflow 2025-06-18 02:40:03 +02:00
Philipp Crocoll
bd784fa13d fix file path for apk to upload 2025-06-18 02:29:47 +02:00
Philipp Crocoll
a6bc5e657c remove no longer needed installation of sdk 26 2025-06-18 02:09:36 +02:00
Philipp Crocoll
56c4cdb321 add Configuration=Release 2025-06-18 02:04:52 +02:00
Philipp Crocoll
42a4a83c7d add upload apk to release in workflow 2025-06-17 22:20:56 +02:00
Philipp Crocoll
fe3933e154 try escape keystore env for building apk 2025-06-17 22:02:21 +02:00
Philipp Crocoll
3efe130ee8 remove unescaped test 2025-06-17 21:59:46 +02:00
Philipp Crocoll
5808857749 fix quoting for shell 2025-06-17 21:12:51 +02:00
Philipp Crocoll
a7397c3316 explicitly set shell=bash 2025-06-17 21:08:13 +02:00
Philipp Crocoll
d12f936898 next attempt 2025-06-17 20:51:10 +02:00
Philipp Crocoll
67f7d74bb9 and more debugging 2025-06-17 20:44:34 +02:00
Philipp Crocoll
b0d0f06073 more debug 2025-06-17 20:37:24 +02:00
Philipp Crocoll
67ee571c27 fix debugging signing 2025-06-17 20:32:15 +02:00
Philipp Crocoll
a360695271 debug signing 2025-06-17 20:28:05 +02:00
Philipp Crocoll
149857a516 make keystore path absolute 2025-06-17 18:45:17 +02:00
Philipp Crocoll
fb8ffb802f try to fix release workflow 2025-06-17 17:50:10 +02:00
Philipp Crocoll
e957073457 add workflow file for generating the "net" release apk 2025-06-17 17:30:16 +02:00
Philipp Crocoll
da86b0f50b manifest for 1.12-r6 2025-06-17 16:55:47 +02:00
PhilippC
0aa78ffd66 Merge pull request #2862 from Gian-Fr/fix-makefile
Fixed MakeFile for Linux
2025-06-17 16:52:04 +02:00
PhilippC
ce0087af99 Merge pull request #2850 from PhilippC/l10n_master3
New Crowdin updates
2025-06-17 16:46:16 +02:00
PhilippC
576bfeecfe Merge pull request #2918 from PhilippC/2869-fix-bad-behavior-when-starting-passwordactivity-twice
Fix bad behavior when starting passwordactivity twice
2025-06-17 16:45:12 +02:00
Philipp Crocoll
c0e2f34b79 use correct message severity when synchronizing 2025-06-17 16:42:30 +02:00
Philipp Crocoll
84d0c32610 fix issue with loading Yubikey aux file. closes #2880 2025-06-17 16:27:43 +02:00
Philipp Crocoll
40184dbd55 catch exception while sending log data. might throw if too much data is sent. 2025-06-17 14:58:06 +02:00
Philipp Crocoll
2d17bdde19 add more logging while loading database to get more info regarding #2868 2025-06-17 14:58:01 +02:00
Philipp Crocoll
e2babde1fa do not reset activity state in OnNewIntent if the activity hasn't even been initialized. closes #2869, closes #2888 2025-06-17 14:04:15 +02:00
Philipp Crocoll
e1f26fb045 cleanup and improve formatting 2025-06-17 13:58:29 +02:00
Philipp Crocoll
adbbfa0ac1 add more logging to diagnose #2891 2025-06-17 08:27:13 +02:00
PhilippC
37a013135e New translations strings.xml (French) 2025-06-05 23:19:38 +02:00
PhilippC
acc6ea7f85 New translations strings.xml (French) 2025-06-05 22:10:08 +02:00
PhilippC
62f012713a New translations strings.xml (Turkish) 2025-06-03 19:34:43 +02:00
PhilippC
0d726c1789 New translations strings.xml (Turkish) 2025-06-03 17:37:29 +02:00
Philipp Crocoll
f162e868b9 link libargon2.so for x86 and x86_64 to fix #2881 2025-06-03 17:07:17 +02:00
PhilippC
0a1f95653f New translations strings.xml (Spanish) 2025-06-01 16:19:37 +02:00
PhilippC
27451825c6 New translations strings.xml (Italian) 2025-05-29 10:32:52 +02:00
PhilippC
bdd6f1033e New translations strings.xml (Romanian) 2025-05-26 14:25:46 +02:00
PhilippC
c0413f9b74 New translations strings.xml (Turkish) 2025-05-26 12:52:41 +02:00
PhilippC
59d6fc8fdb New translations strings.xml (German) 2025-05-16 08:18:05 +02:00
PhilippC
c500245647 New translations strings.xml (German) 2025-05-16 06:50:12 +02:00
PhilippC
026a263f10 New translations strings.xml (German) 2025-05-13 10:41:52 +02:00
PhilippC
839e6d3cb4 New translations strings.xml (French) 2025-05-12 04:51:42 +02:00
PhilippC
735f4caf89 New translations strings.xml (French) 2025-05-12 03:33:02 +02:00
PhilippC
11af71ef82 New translations strings.xml (Slovak) 2025-05-12 00:26:40 +02:00
PhilippC
e09577d17f New translations strings.xml (Slovak) 2025-05-11 22:34:40 +02:00
Gian-Fr
c1dbf171f5 Fixed MakeFile for Linux 2025-05-01 16:21:02 +02:00
PhilippC
4ca4ec10be New translations strings.xml (Czech) 2025-04-24 11:00:27 +02:00
PhilippC
77fded4964 New translations strings.xml (Chinese Simplified) 2025-04-24 11:00:26 +02:00
PhilippC
578491b1c7 New translations strings.xml (Slovenian) 2025-04-24 08:40:12 +02:00
PhilippC
eee3ffd861 New translations strings.xml (Slovenian) 2025-04-24 07:18:52 +02:00
PhilippC
89696d7f0d New translations strings.xml (Portuguese, Brazilian) 2025-04-23 00:14:27 +02:00
Philipp Crocoll
e5d28f0979 only try to activate the keyboard once in password activity and entry edit activity; explain why the prompt shows up in SwitchImeActivity. closes #1400 2025-04-22 17:48:56 +02:00
Philipp Crocoll
0e581a66c5 treat server certificate failures as error by default, closes #1078 2025-04-22 16:02:02 +02:00
PhilippC
a202c76bf0 New translations strings.xml (Chinese Simplified) 2025-04-22 08:10:50 +02:00
PhilippC
c9936ab76b New translations strings.xml (Italian) 2025-04-16 21:31:25 +02:00
PhilippC
7ac6f7ed51 New translations strings.xml (Italian) 2025-04-16 21:31:24 +02:00
PhilippC
ceb31c54b1 Merge pull request #2847 from PhilippC/2430-remove-plain-storage-uri-from-logs
remove potential plain-text credentials for file storage from logs
2025-04-15 14:27:02 +02:00
Philipp Crocoll
42d8be593e remove potential plain-text credentials for file storage from logs 2025-04-15 14:00:31 +02:00
Philipp Crocoll
313adb6c3e manifest for 1.12-r5 2025-04-15 13:27:47 +02:00
PhilippC
668ba4cdee Merge pull request #2845 from PhilippC/2816-bug-yubikey-not-working-in-112
fix implementation of yubikey secret reading
2025-04-15 13:18:25 +02:00
PhilippC
a36bfa7ff5 Merge pull request #2833 from PhilippC/l10n_master3
New Crowdin updates
2025-04-15 12:45:48 +02:00
Philipp Crocoll
26c37bcd2a fix implementation of yubikey secret reading 2025-04-15 12:44:43 +02:00
PhilippC
1980f05a7c Merge pull request #2844 from PhilippC/2837-improve-error-reporting
Improve error reporting and fix crash
2025-04-15 12:17:18 +02:00
Philipp Crocoll
dbf10ba9fb Improve error reporting: Extract original message when showing "Java exception messages" consistently. Improve error handling in deprecated OneDrive API stub and avoid crash by no-longer throwing in Main-thread methods. 2025-04-15 11:45:16 +02:00
PhilippC
4be18d8373 Merge pull request #2830 from PhilippC/754-crash-when-using-fingerprint-before-otp
Fix crash when combining biometric unlock and KeeChallenge/OTP.
2025-04-15 11:42:59 +02:00
PhilippC
831b290d81 New translations strings.xml (Greek) 2025-04-13 20:35:49 +02:00
PhilippC
9d4c15f7bc New translations strings.xml (Greek) 2025-04-13 19:35:57 +02:00
PhilippC
4c4afa792d New translations strings.xml (Romanian) 2025-04-09 10:41:02 +02:00
Philipp Crocoll
8e256ac94d fix crash when combining biometric unlock and KeeChallenge/OTP. 2025-04-08 16:27:52 +02:00
Philipp Crocoll
ba7b02cd1e remove testing credentials 2025-04-08 15:46:38 +02:00
Philipp Crocoll
65ff09f866 Manifest for 1.12-r4 2025-04-08 15:31:56 +02:00
Philipp Crocoll
aec9441de4 update FluentFTP 2025-04-08 15:26:04 +02:00
Philipp Crocoll
8e9c2824cf add some files for building on Linux 2025-04-08 14:16:39 +02:00
PhilippC
92b8ff5c8d Merge pull request #2827 from PhilippC/2816-bug-yubikey-not-working-in-112
fix reading of cryptostream. Fixes Yubikey/KeeChallenge regression in 1.12-r1 .. 1.12-r3
2025-04-08 12:57:36 +02:00
PhilippC
223c3bfb8e Merge pull request #2826 from PhilippC/feature/snackbar-messages
use snackbars instead of toasts
2025-04-08 12:56:23 +02:00
PhilippC
b4e03a8374 Merge pull request #2797 from PhilippC/l10n_master3
New Crowdin updates
2025-04-08 12:41:50 +02:00
PhilippC
fb2df35d37 Merge pull request #2828 from PhilippC/2818-feat-new-color-scheme-too-beige-green
slightly adjust background color to become true gray.
2025-04-08 12:34:55 +02:00
Philipp Crocoll
50d6598b02 fix reading of cryptostream. The implementation issue became a bug in .net8. Closes #2816 2025-04-08 12:25:29 +02:00
Philipp Crocoll
90f04b76f4 fix issue with MessagePresenter potentially crashing 2025-04-08 12:20:24 +02:00
Philipp Crocoll
8b4314c394 fix bug with messages not always showing as they should. 2025-04-08 11:25:43 +02:00
Philipp Crocoll
17241bc422 use snackbars instead of toasts (in most cases) 2025-04-08 10:37:40 +02:00
PhilippC
c4a73bf107 New translations strings.xml (Czech) 2025-04-04 15:32:59 +02:00
Philipp Crocoll
5edf42254d this is an experiment to use GnuTlsStream (the ftpcredentials.xml have some hardcoded credentials for a public FTP server for testing). Unfortunately, the app restarts when loading the native libraries for GnuTLS. 2025-04-01 15:10:04 +02:00
PhilippC
4ba40ba24f New translations strings.xml (Slovenian) 2025-03-27 12:19:23 +01:00
PhilippC
e2711b709d New translations strings.xml (Portuguese, Brazilian) 2025-03-26 12:13:50 +01:00
PhilippC
4764b15e75 New translations strings.xml (Portuguese, Brazilian) 2025-03-26 09:06:54 +01:00
PhilippC
1b389ef12e New translations strings.xml (Japanese) 2025-03-26 09:06:43 +01:00
PhilippC
b32c2dbc7e New translations strings.xml (Czech) 2025-03-26 09:06:37 +01:00
PhilippC
f06937dab3 New translations strings.xml (French) 2025-03-26 09:06:32 +01:00
PhilippC
14efce62ff New translations strings.xml (Slovenian) 2025-03-26 09:06:29 +01:00
PhilippC
3c8b530e2e New translations strings.xml (Slovak) 2025-03-26 09:06:28 +01:00
PhilippC
9939e07b7d New translations strings.xml (Italian) 2025-03-26 09:06:27 +01:00
PhilippC
ecf416febc New translations strings.xml (German) 2025-03-26 09:06:25 +01:00
PhilippC
1cb036941e New translations strings.xml (Vietnamese) 2025-03-23 09:12:18 +01:00
PhilippC
a53ff37e89 New translations strings.xml (Vietnamese) 2025-03-23 07:30:07 +01:00
PhilippC
dc3ee35c8b New translations strings.xml (Italian) 2025-03-21 14:54:31 +01:00
PhilippC
e05fe94650 New translations strings.xml (Italian) 2025-03-20 21:28:17 +01:00
PhilippC
b0cb0b06a2 New translations strings.xml (German) 2025-03-20 19:11:11 +01:00
PhilippC
6d7b4810da New translations strings.xml (Hungarian) 2025-03-18 13:16:38 +01:00
PhilippC
585b747612 New translations strings.xml (Hungarian) 2025-03-18 11:35:13 +01:00
PhilippC
55887e1a89 New translations strings.xml (Slovak) 2025-03-17 18:31:07 +01:00
PhilippC
39a7a1298a New translations strings.xml (Romanian) 2025-03-14 09:08:28 +01:00
PhilippC
90059c5ae6 New translations strings.xml (Romanian) 2025-03-14 09:08:27 +01:00
PhilippC
ad63179484 New translations strings.xml (Romanian) 2025-03-14 07:51:48 +01:00
PhilippC
6eaba9d3a8 New translations strings.xml (Chinese Simplified) 2025-03-13 08:11:39 +01:00
PhilippC
11ce68902c New translations strings.xml (Slovenian) 2025-03-13 08:11:38 +01:00
PhilippC
70ca059e0f New translations strings.xml (Portuguese, Brazilian) 2025-03-12 00:48:40 +01:00
Philipp Crocoll
a51bfb102f implements Samba support to close #82 2025-03-05 08:14:02 +01:00
153 changed files with 16823 additions and 3801 deletions

View File

@@ -78,7 +78,7 @@ jobs:
# - name: Build keepass2android (net)
# run: |
# make msbuild Flavor=Net
# make dotnetbuild Flavor=Net
# - name: Build APK (net)
# run: |
@@ -96,7 +96,7 @@ jobs:
# - name: Build keepass2android (nonet)
# run: |
# make msbuild Flavor=NoNet
# make dotnetbuild Flavor=NoNet
# - name: Build APK (nonet)
# run: |
@@ -212,7 +212,7 @@ jobs:
# - name: Build keepass2android (net)
# run: |
# make msbuild Flavor=Net
# make dotnetbuild Flavor=Net
# - name: Build APK (net)
# run: |
@@ -230,7 +230,7 @@ jobs:
# - name: Build keepass2android (nonet)
# run: |
# make msbuild Flavor=NoNet
# make dotnetbuild Flavor=NoNet
# - name: Build APK (nonet)
# run: |
@@ -279,7 +279,7 @@ jobs:
with:
minimum-size: 8GB
- name: Add msbuild to PATH
- name: Add dotnetbuild to PATH
uses: microsoft/setup-msbuild@v2
# If we want to also have nmake, use this instead
#uses: ilammy/msvc-dev-cmd@v1
@@ -309,30 +309,49 @@ jobs:
run: |
make java
- name: Update dotnet workloads
run: |
dotnet workload update
- name: Select the manifest
run: |
make manifestlink Flavor=Net
- name: Install NuGet dependencies (net)
run: make nuget Flavor=Net
- name: Build keepass2android (net)
run: |
make msbuild Flavor=Net
make dotnetbuild Flavor=Net
- name: Build APK (net)
if: github.ref == 'refs/heads/master'
env:
DropboxAppKey: ${{ secrets.DROPBOX_APP_KEY }}
DropboxAppSecret: ${{ secrets.DROPBOX_APP_SECRET }}
DropboxAppFolderAppKey: ${{ secrets.DROPBOX_APP_FOLDER_APP_KEY }}
DropboxAppFolderAppSecret: ${{ secrets.DROPBOX_APP_FOLDER_APP_SECRET }}
run: |
make apk Flavor=Net
make apk Configuration=Release Flavor=Net
- name: Archive production artifacts (net)
uses: actions/upload-artifact@v4
with:
name: signed APK ('net' built on ${{ github.job }})
name: archive APK ('net' built on ${{ github.job }})
path: |
src/keepass2android/bin/*/*-Signed.apk
src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk
- name: Select the manifest
run: |
make manifestlink Flavor=NoNet
- name: Install NuGet dependencies (nonet)
run: make nuget Flavor=NoNet
- name: Build keepass2android (nonet)
run: |
make msbuild Flavor=NoNet
make dotnetbuild Flavor=NoNet
- name: Test Autofill
working-directory: ./src/Kp2aAutofillParser.Tests
run: dotnet test
@@ -344,9 +363,7 @@ jobs:
- name: Archive production artifacts (nonet)
uses: actions/upload-artifact@v4
with:
name: signed APK ('nonet' built on ${{ github.job }})
name: archive APK ('nonet' built on ${{ github.job }})
path: |
src/keepass2android/bin/*/*-Signed.apk
src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk
- name: Perform "make distclean"
run: make distclean

146
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,146 @@
name: Create keepass2android release
env:
NAME: 'Release'
on:
# the workflow is always triggered manually. This allows to test the apks
# before publishing the release and not having a broken tag in the repo if that test fails.
workflow_dispatch:
jobs:
build-release:
runs-on: windows-2022
strategy:
matrix:
flavor: [Net, NoNet]
target: [apk, apk_split]
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Extract key store
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
KeyStore: "${{ github.workspace }}/kp2a.keystore"
shell: bash
run: |
echo $KeyStore
echo $KEYSTORE_BASE64 | base64 --decode > $KeyStore
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('src/**/*.csproj', 'src/**/packages.config') }}
restore-keys: |
${{ runner.os }}-nuget-
# Workaround an issue when building on windows-2022. Error was
# D8 : OpenJDK 64-Bit Server VM warning : INFO: os::commit_memory(0x00000000ae400000, 330301440, 0) failed; error='The paging file is too small for this operation to complete' (DOS error/errno=1455) [D:\a\keepass2android\keepass2android\src\keepass2android\keepass2android-app.csproj]
# C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Xamarin\Android\Xamarin.Android.D8.targets(81,5): error MSB6006: "java.exe" exited with code 1. [D:\a\keepass2android\keepass2android\src\keepass2android\keepass2android-app.csproj]
- name: Configure Pagefile
uses: al-cheb/configure-pagefile-action@a3b6ebd6b634da88790d9c58d4b37a7f4a7b8708 # v1.4
with:
minimum-size: 8GB
- name: Add msbuild/dotnet to PATH
uses: microsoft/setup-msbuild@v2
# If we want to also have nmake, use this instead
#uses: ilammy/msvc-dev-cmd@v1
- name: Switch to JDK-17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Display java version
run: java -version
- name: Build native dependencies
shell: cmd
run: |
make native
- name: Build java dependencies
shell: cmd
run: |
make java
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Update dotnet workloads
run: |
dotnet workload update
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Select the manifest
run: |
make manifestlink Flavor=${{ matrix.flavor }}
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Install NuGet dependencies
run: make nuget Flavor=${{ matrix.flavor }}
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Build APK (net)
env:
KeyStore: "${{ github.workspace }}/kp2a.keystore"
MyAndroidSigningStorePass: ${{ secrets.KEY_STORE_PASSWORD }}
MyAndroidSigningKeyPass: ${{ secrets.KEY_PASSWORD }}
DropboxAppKey: ${{ secrets.DROPBOX_APP_KEY }}
DropboxAppSecret: ${{ secrets.DROPBOX_APP_SECRET }}
DropboxAppFolderAppKey: ${{ secrets.DROPBOX_APP_FOLDER_APP_KEY }}
DropboxAppFolderAppSecret: ${{ secrets.DROPBOX_APP_FOLDER_APP_SECRET }}
run: |
make ${{ matrix.target }} Configuration=Release Flavor=${{ matrix.flavor }}
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: keepass2android_${{ matrix.target }}_${{ matrix.flavor }}
# the first line is for "apk" target, the second line is for "apk_split" target
path: |
src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk
src/keepass2android-app/bin/Release/net9.0-android/*/publish/*.apk
- name: List apks
run: find . -type f -name "*.apk"
shell: bash
- name: Upload APK to GitHub Release
uses: softprops/action-gh-release@v2
with:
draft: true
files: |
src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk
src/keepass2android-app/bin/Release/net9.0-android/*/publish/*.apk

2
.gitignore vendored
View File

@@ -64,7 +64,7 @@ Thumbs.db
/src/java/android-filechooser/code/projectzip/project.zip
/src/java/android-filechooser/code/unused.txt
/src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs
/src/Kp2aBusinessLogic/Io/DropboxFileStorage.g.cs
/src/java/workspace/DriveTest

View File

@@ -4,10 +4,10 @@
# This Makefile can be used on both unix-like (use make) & windows (with GNU make)
#
# append the Configuration variable to 'make' call with value to use in '/p:Configuration='
# of msbuild command.
# of dotnetbuild command.
#
# append the Flavor variable to 'make' call with value to use in '/p:Flavor='
# of msbuild command.
# of dotnetbuild command.
#
# Example:
# make Configuration=Release Flavor=NoNet
@@ -18,7 +18,7 @@
# - native: build the native libs
# - java: build the java libs
# - nuget: restore NuGet packages
# - msbuild: build the project
# - dotnetbuild: build the project
# - apk: same as all
# - manifestlink: creates a symlink (to be used in building) to the AndroidManifest corresponding to the selected Flavor
#
@@ -27,7 +27,7 @@
# - clean_native: clean native lib
# - clean_java: call clean target of java libs
# - clean_nuget: cleanup the 'nuget restore'
# - clean_msbuild: call clean target of msbuild
# - clean_dotnet: call clean target of dotnetbuild
#
#
#
@@ -60,45 +60,23 @@ $(info MAKESHELL: $(MAKESHELL))
$(info SHELL: $(SHELL))
$(info )
# On linux use xabuild, on Windows use MSBuild.exe, otherwise (macos?) use msbuild.
ifeq ($(detected_OS),Linux)
MSBUILD_binary := xabuild
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary))
DOTNET_binary := dotnet
DOTNET := $(shell $(WHICH) $(DOTNET_binary))
else ifeq ($(detected_OS),Windows)
MSBUILD_binary := MSBuild.exe
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary) 2> nul)
ifeq ($(MSBUILD),)
# Additional heuristic to find MSBUILD_BINARY on Windows
VSWHERE := "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
VSWHERE_CHECK := $(shell @echo off & $(VSWHERE) 2> nul || echo VSWHERE_NOT_FOUND)
ifneq ($(VSWHERE_CHECK),VSWHERE_NOT_FOUND)
MSBUILD := $(shell @echo off & $(VSWHERE) -latest -prerelease -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe)
VS_INSTALL_PATH := $(shell @echo off & $(VSWHERE) -property installationPath)
endif
endif
DOTNET_binary := dotnet
DOTNET := $(shell $(WHICH) $(DOTNET_binary) 2> nul)
else
MSBUILD_binary := msbuild
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary))
DOTNET_binary := dotnet
DOTNET := $(shell $(WHICH) $(DOTNET_binary))
endif
ifeq ($(MSBUILD),)
ifeq ($(DOTNET),)
$(info )
$(info '$(MSBUILD_binary)' binary could not be found. Check it is in your PATH.)
ifeq ($(detected_OS),Windows)
ifneq ($(VSWHERE_CHECK),VSWHERE_NOT_FOUND)
$(info )
$(info You may retry after running in the command prompt:)
$(info )
$(info "$(VS_INSTALL_PATH)\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64)
$(info )
$(info If this doesn't work, install/find the location of vcvarsall.bat)
$(info or install and add msbuild.exe to your PATH)
$(info )
endif
endif
$(info '$(DOTNET_binary)' binary could not be found. Check it is in your PATH.)
$(error )
endif
$(info MSBUILD: $(MSBUILD))
$(info DOTNET: $(DOTNET))
$(info )
ifeq ($(ANDROID_SDK_ROOT),)
@@ -117,7 +95,7 @@ endif
$(info ANDROID_NDK_ROOT: $(ANDROID_NDK_ROOT))
ifneq ($(Configuration),)
MSBUILD_PARAM = -p:Configuration="$(Configuration)"
DOTNET_PARAM = -p:Configuration="$(Configuration)"
else
$(warning Configuration environment variable not set.)
endif
@@ -127,7 +105,7 @@ CREATE_MANIFEST_LINK :=
MANIFEST_FILE :=
ifneq ($(Flavor),)
MSBUILD_PARAM += -p:Flavor="$(Flavor)"
DOTNET_PARAM += -p:Flavor="$(Flavor)"
ifneq ($(Flavor),)
ifeq ($(Flavor),Debug)
MANIFEST_FILE := AndroidManifest_debug.xml
@@ -152,7 +130,7 @@ else
endif
ifneq ($(KeyStore),)
MSBUILD_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a"
DOTNET_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a"
endif
ifeq ($(detected_OS),Windows)
@@ -176,7 +154,7 @@ endif
# Recursive wildcard: https://stackoverflow.com/a/18258352
rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
$(info MSBUILD_PARAM: $(MSBUILD_PARAM))
$(info DOTNET_PARAM: $(DOTNET_PARAM))
$(info nuget path: $(shell $(WHICH) nuget))
$(info )
@@ -254,7 +232,7 @@ OUTPUT_PluginQR = src/java/Keepass2AndroidPluginSDK2/app/build/outputs/aar/Keepa
.PHONY: native $(NATIVE_COMPONENTS) clean_native $(NATIVE_CLEAN_TARGETS) \
java $(JAVA_COMPONENTS) clean_java $(JAVA_CLEAN_TARGETS) \
nuget clean_nuget \
msbuild clean_msbuild \
dotnetbuild clean_dotnet \
apk all clean
all: apk
@@ -303,7 +281,7 @@ ifeq ($(shell $(WHICH) nuget),)
endif
$(RMFILE) stamp.nuget_*
nuget restore src/KeePass.sln
$(MSBUILD) src/KeePass.sln -t:restore $(MSBUILD_PARAM) -p:RestorePackagesConfig=true
$(DOTNET) restore src/KeePass.sln $(DOTNET_PARAM) -p:RestorePackagesConfig=true
@echo "" > stamp.nuget_$(Flavor)
manifestlink:
@@ -312,20 +290,21 @@ manifestlink:
$(CREATE_MANIFEST_LINK)
#####
src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs:
ifeq ($(detected_OS),Windows)
$(CP) src\Kp2aBusinessLogic\Io\DropboxFileStorageKeysDummy.cs src\Kp2aBusinessLogic\Io\DropboxFileStorageKeys.cs
else
$(CP) src/Kp2aBusinessLogic/Io/DropboxFileStorageKeysDummy.cs $@
endif
msbuild: manifestlink native java nuget src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs
$(MSBUILD) src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(MSBUILD_PARAM) -p:Platform="Any CPU" -m
dotnetbuild: manifestlink native java nuget
$(DOTNET) build src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(DOTNET_PARAM) -p:Platform="Any CPU" -m
apk: msbuild
$(MSBUILD) src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(MSBUILD_PARAM) -p:Platform=AnyCPU -m
apk: manifestlink native java nuget
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m
build_all: msbuild
apk_split: manifestlink native java nuget
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm64
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x86
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x64
src/build-scripts/rename-output-apks.sh src/keepass2android-app/bin/Release/net8.0-android/
build_all: dotnetbuild
##### Cleanup targets
@@ -369,10 +348,10 @@ else
endif
$(RMFILE) stamp.nuget_*
clean_msbuild:
$(MSBUILD) src/KeePass.sln -target:clean $(MSBUILD_PARAM)
clean_dotnet:
$(DOTNET) clean src/KeePass.sln $(DOTNET_PARAM)
clean: clean_native clean_java clean_nuget clean_msbuild
clean: clean_native clean_java clean_nuget clean_dotnet
distclean: clean
ifneq ("$(wildcard ./allow_git_clean)","")

View File

@@ -11,10 +11,10 @@ Regular stable releases of Keepass2Android are available on [Google Play](https:
Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android) or [Beta testing channel for Keepass2Android Offline](https://play.google.com/apps/testing/keepass2android.keepass2android_nonet).
# How can I contribute?
* Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](http://crowdin.net/project/keepass2android)
* Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](https://crowdin.net/project/keepass2android)
* Add features by [creating a plugin](How-to-create-a-plug-in_.md) or creating a pull request. You might want to contact me before you start working so I can coordinate efforts.
* [Become a GitHub sponsor to boost 🚀 development](https://github.com/sponsors/PhilippC)
* [Make a donation](http://philipp.crocoll.net/donate.php)
* [Make a donation](https://philipp.crocoll.net/donate.php)
# How do I learn more?
Please see the [wiki](https://github.com/PhilippC/keepass2android/wiki/Documentation) for further information.

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<TargetFramework>net9.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<TargetFramework>net9.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<TargetFramework>net9.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<TargetFramework>net9.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -29,197 +29,226 @@ using KeePassLib.Utility;
namespace KeePassLib.Cryptography
{
/// <summary>
/// Algorithms supported by <c>CryptoRandomStream</c>.
/// </summary>
public enum CrsAlgorithm
{
/// <summary>
/// Not supported.
/// </summary>
Null = 0,
/// <summary>
/// Algorithms supported by <c>CryptoRandomStream</c>.
/// </summary>
public enum CrsAlgorithm
{
/// <summary>
/// Not supported.
/// </summary>
Null = 0,
/// <summary>
/// A variant of the ARCFour algorithm (RC4 incompatible).
/// </summary>
/// </summary>
ArcFourVariant = 1,
/// <summary>
/// A variant of the ArcFour algorithm (RC4 incompatible).
/// Insecure; for backward compatibility only.
/// </summary>
ArcFourVariant = 1,
/// <summary>
/// Salsa20 stream cipher algorithm.
/// </summary>
Salsa20 = 2,
/// <summary>
/// Salsa20 stream cipher algorithm.
/// </summary>
Salsa20 = 2,
/// <summary>
/// ChaCha20 stream cipher algorithm.
/// </summary>
ChaCha20 = 3,
/// <summary>
/// ChaCha20 stream cipher algorithm.
/// </summary>
ChaCha20 = 3,
Count = 4
}
Count = 4
}
/// <summary>
/// A random stream class. The class is initialized using random
/// bytes provided by the caller. The produced stream has random
/// properties, but for the same seed always the same stream
/// is produced, i.e. this class can be used as stream cipher.
/// </summary>
public sealed class CryptoRandomStream : IDisposable
{
private readonly CrsAlgorithm m_crsAlgorithm;
/// <summary>
/// A random stream class. The class is initialized using random
/// bytes provided by the caller. The produced stream has random
/// properties, but for the same seed always the same stream
/// is produced, i.e. this class can be used as stream cipher.
/// </summary>
public sealed class CryptoRandomStream : IDisposable
{
private readonly CrsAlgorithm m_alg;
private bool m_bDisposed = false;
private byte[] m_pbState = null;
private byte m_i = 0;
private byte m_j = 0;
private readonly byte[] m_pbKey = null;
private readonly byte[] m_pbIV = null;
private Salsa20Cipher m_salsa20 = null;
private ChaCha20Cipher m_chacha20 = null;
private readonly ChaCha20Cipher m_chacha20 = null;
private readonly Salsa20Cipher m_salsa20 = null;
/// <summary>
/// Construct a new cryptographically secure random stream object.
/// </summary>
/// <param name="genAlgorithm">Algorithm to use.</param>
/// <param name="pbKey">Initialization key. Must not be <c>null</c> and
/// must contain at least 1 byte.</param>
public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey)
{
if(pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); }
/// <exception cref="System.ArgumentNullException">Thrown if the
int cbKey = pbKey.Length;
if(cbKey <= 0)
{
Debug.Assert(false); // Need at least one byte
throw new ArgumentOutOfRangeException("pbKey");
}
/// <paramref name="pbKey" /> parameter is <c>null</c>.</exception>
m_crsAlgorithm = a;
/// <exception cref="System.ArgumentException">Thrown if the
if(a == CrsAlgorithm.ChaCha20)
{
byte[] pbKey32 = new byte[32];
byte[] pbIV12 = new byte[12];
/// <paramref name="pbKey" /> parameter contains no bytes or the
using(SHA512Managed h = new SHA512Managed())
{
byte[] pbHash = h.ComputeHash(pbKey);
Array.Copy(pbHash, pbKey32, 32);
Array.Copy(pbHash, 32, pbIV12, 0, 12);
MemUtil.ZeroByteArray(pbHash);
}
/// algorithm is unknown.</exception>
m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true);
}
else if(a == CrsAlgorithm.Salsa20)
{
byte[] pbKey32 = CryptoUtil.HashSha256(pbKey);
byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B,
0x97, 0x20, 0x5D, 0x2A }; // Unique constant
private readonly byte[] m_pbState = null;
private byte m_i = 0;
private byte m_j = 0;
m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8);
}
else if(a == CrsAlgorithm.ArcFourVariant)
{
// Fill the state linearly
m_pbState = new byte[256];
for(int w = 0; w < 256; ++w) m_pbState[w] = (byte)w;
/// <summary>
/// Construct a new cryptographically secure random stream object.
/// </summary>
/// <param name="a">Algorithm to use.</param>
/// <param name="pbKey">Initialization key. Must not be <c>null</c>
/// and must contain at least 1 byte.</param>
public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey)
{
if (pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); }
unchecked
{
byte j = 0, t;
int inxKey = 0;
for(int w = 0; w < 256; ++w) // Key setup
{
j += (byte)(m_pbState[w] + pbKey[inxKey]);
int cbKey = pbKey.Length;
if (cbKey <= 0)
{
Debug.Assert(false); // Need at least one byte
throw new ArgumentOutOfRangeException("pbKey");
}
t = m_pbState[0]; // Swap entries
m_pbState[0] = m_pbState[j];
m_pbState[j] = t;
m_alg = a;
++inxKey;
if(inxKey >= cbKey) inxKey = 0;
}
}
if (a == CrsAlgorithm.ChaCha20)
{
m_pbKey = new byte[32];
m_pbIV = new byte[12];
GetRandomBytes(512); // Increases security, see cryptanalysis
}
else // Unknown algorithm
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("a");
}
}
using (SHA512Managed h = new SHA512Managed())
{
byte[] pbHash = h.ComputeHash(pbKey);
Array.Copy(pbHash, m_pbKey, 32);
Array.Copy(pbHash, 32, m_pbIV, 0, 12);
MemUtil.ZeroByteArray(pbHash);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
m_chacha20 = new ChaCha20Cipher(m_pbKey, m_pbIV, true);
}
else if (a == CrsAlgorithm.Salsa20)
{
m_pbKey = CryptoUtil.HashSha256(pbKey);
m_pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B,
0x97, 0x20, 0x5D, 0x2A }; // Unique constant
private void Dispose(bool disposing)
{
if(disposing)
{
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20)
m_chacha20.Dispose();
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20)
m_salsa20.Dispose();
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant)
{
MemUtil.ZeroByteArray(m_pbState);
m_i = 0;
m_j = 0;
}
else { Debug.Assert(false); }
}
}
m_salsa20 = new Salsa20Cipher(m_pbKey, m_pbIV);
}
else if (a == CrsAlgorithm.ArcFourVariant)
{
// Fill the state linearly
m_pbState = new byte[256];
for (int w = 0; w < 256; ++w) m_pbState[w] = (byte)w;
/// <summary>
/// Get <paramref name="uRequestedCount" /> random bytes.
/// </summary>
/// <param name="uRequestedCount">Number of random bytes to retrieve.</param>
/// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns>
public byte[] GetRandomBytes(uint uRequestedCount)
{
if(uRequestedCount == 0) return MemUtil.EmptyByteArray;
unchecked
{
byte j = 0, t;
int inxKey = 0;
for (int w = 0; w < 256; ++w) // Key setup
{
j += (byte)(m_pbState[w] + pbKey[inxKey]);
if(uRequestedCount > (uint)int.MaxValue)
throw new ArgumentOutOfRangeException("uRequestedCount");
int cb = (int)uRequestedCount;
t = m_pbState[0]; // Swap entries
m_pbState[0] = m_pbState[j];
m_pbState[j] = t;
byte[] pbRet = new byte[cb];
++inxKey;
if (inxKey >= cbKey) inxKey = 0;
}
}
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20)
m_chacha20.Encrypt(pbRet, 0, cb);
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20)
m_salsa20.Encrypt(pbRet, 0, cb);
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant)
{
unchecked
{
for(int w = 0; w < cb; ++w)
{
++m_i;
m_j += m_pbState[m_i];
GetRandomBytes(512); // Increases security, see cryptanalysis
}
else // Unknown algorithm
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("a");
}
}
byte t = m_pbState[m_i]; // Swap entries
m_pbState[m_i] = m_pbState[m_j];
m_pbState[m_j] = t;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
t = (byte)(m_pbState[m_i] + m_pbState[m_j]);
pbRet[w] = m_pbState[t];
}
}
}
else { Debug.Assert(false); }
private void Dispose(bool disposing)
{
if (disposing)
{
if (m_alg == CrsAlgorithm.ChaCha20)
m_chacha20.Dispose();
else if (m_alg == CrsAlgorithm.Salsa20)
m_salsa20.Dispose();
else if (m_alg == CrsAlgorithm.ArcFourVariant)
{
MemUtil.ZeroByteArray(m_pbState);
m_i = 0;
m_j = 0;
}
else { Debug.Assert(false); }
return pbRet;
}
if (m_pbKey != null) MemUtil.ZeroByteArray(m_pbKey);
if (m_pbIV != null) MemUtil.ZeroByteArray(m_pbIV);
public ulong GetRandomUInt64()
{
byte[] pb = GetRandomBytes(8);
return MemUtil.BytesToUInt64(pb);
}
m_bDisposed = true;
}
}
/// <summary>
/// Get <paramref name="uRequestedCount" /> random bytes.
/// </summary>
/// <param name="uRequestedCount">Number of random bytes to retrieve.</param>
/// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns>
public byte[] GetRandomBytes(uint uRequestedCount)
{
if (m_bDisposed) throw new ObjectDisposedException(null);
if (uRequestedCount == 0) return MemUtil.EmptyByteArray;
if (uRequestedCount > (uint)int.MaxValue)
throw new ArgumentOutOfRangeException("uRequestedCount");
int cb = (int)uRequestedCount;
byte[] pbRet = new byte[cb];
if (m_alg == CrsAlgorithm.ChaCha20)
m_chacha20.Encrypt(pbRet, 0, cb);
else if (m_alg == CrsAlgorithm.Salsa20)
m_salsa20.Encrypt(pbRet, 0, cb);
else if (m_alg == CrsAlgorithm.ArcFourVariant)
{
unchecked
{
for (int w = 0; w < cb; ++w)
{
++m_i;
m_j += m_pbState[m_i];
byte t = m_pbState[m_i]; // Swap entries
m_pbState[m_i] = m_pbState[m_j];
m_pbState[m_j] = t;
t = (byte)(m_pbState[m_i] + m_pbState[m_j]);
pbRet[w] = m_pbState[t];
}
}
}
else { Debug.Assert(false); }
return pbRet;
}
public ulong GetRandomUInt64()
{
byte[] pb = GetRandomBytes(8);
return MemUtil.BytesToUInt64(pb);
}
internal ulong GetRandomUInt64(ulong uMaxExcl)
{
if (uMaxExcl == 0) { Debug.Assert(false); throw new ArgumentOutOfRangeException("uMaxExcl"); }
ulong uGen, uRem;
do
{
uGen = GetRandomUInt64();
uRem = uGen % uMaxExcl;
}
while ((uGen - uRem) > (ulong.MaxValue - (uMaxExcl - 1UL)));
// This ensures that the last number of the block (i.e.
// (uGen - uRem) + (uMaxExcl - 1)) is generatable;
// for signed longs, overflow to negative number:
// while((uGen - uRem) + (uMaxExcl - 1) < 0);
return uRem;
}
#if CRSBENCHMARK
public static string Benchmark()
@@ -237,22 +266,21 @@ namespace KeePassLib.Cryptography
return str;
}
private static int BenchTime(CrsAlgorithm cra, int nRounds, int nDataSize)
private static int BenchTime(CrsAlgorithm a, int nRounds, int cbData)
{
byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 };
int nStart = Environment.TickCount;
int tStart = Environment.TickCount;
for(int i = 0; i < nRounds; ++i)
{
using(CryptoRandomStream c = new CryptoRandomStream(cra, pbKey))
using(CryptoRandomStream crs = new CryptoRandomStream(a, pbKey))
{
c.GetRandomBytes((uint)nDataSize);
crs.GetRandomBytes((uint)cbData);
}
}
int nEnd = Environment.TickCount;
return (nEnd - nStart);
return (Environment.TickCount - tStart);
}
#endif
}
}
}

View File

@@ -1,65 +0,0 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using KeePassLib.Security;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator
{
internal static class CharSetBasedGenerator
{
internal static PwgError Generate(out ProtectedString psOut,
PwProfile pwProfile, CryptoRandomStream crsRandomSource)
{
psOut = ProtectedString.Empty;
if(pwProfile.Length == 0) return PwgError.Success;
PwCharSet pcs = new PwCharSet(pwProfile.CharSet.ToString());
char[] vGenerated = new char[pwProfile.Length];
PwGenerator.PrepareCharSet(pcs, pwProfile);
for(int nIndex = 0; nIndex < (int)pwProfile.Length; ++nIndex)
{
char ch = PwGenerator.GenerateCharacter(pwProfile, pcs,
crsRandomSource);
if(ch == char.MinValue)
{
MemUtil.ZeroArray<char>(vGenerated);
return PwgError.TooFewCharacters;
}
vGenerated[nIndex] = ch;
}
byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vGenerated);
psOut = new ProtectedString(true, pbUtf8);
MemUtil.ZeroByteArray(pbUtf8);
MemUtil.ZeroArray<char>(vGenerated);
return PwgError.Success;
}
}
}

View File

@@ -1,173 +0,0 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using KeePassLib.Security;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator
{
internal static class PatternBasedGenerator
{
internal static PwgError Generate(out ProtectedString psOut,
PwProfile pwProfile, CryptoRandomStream crsRandomSource)
{
psOut = ProtectedString.Empty;
LinkedList<char> vGenerated = new LinkedList<char>();
PwCharSet pcsCurrent = new PwCharSet();
PwCharSet pcsCustom = new PwCharSet();
PwCharSet pcsUsed = new PwCharSet();
bool bInCharSetDef = false;
string strPattern = ExpandPattern(pwProfile.Pattern);
if(strPattern.Length == 0) return PwgError.Success;
CharStream csStream = new CharStream(strPattern);
char ch = csStream.ReadChar();
while(ch != char.MinValue)
{
pcsCurrent.Clear();
bool bGenerateChar = false;
if(ch == '\\')
{
ch = csStream.ReadChar();
if(ch == char.MinValue) // Backslash at the end
{
vGenerated.AddLast('\\');
break;
}
if(bInCharSetDef) pcsCustom.Add(ch);
else
{
vGenerated.AddLast(ch);
pcsUsed.Add(ch);
}
}
else if(ch == '[')
{
pcsCustom.Clear();
bInCharSetDef = true;
}
else if(ch == ']')
{
pcsCurrent.Add(pcsCustom.ToString());
bInCharSetDef = false;
bGenerateChar = true;
}
else if(bInCharSetDef)
{
if(pcsCustom.AddCharSet(ch) == false)
pcsCustom.Add(ch);
}
else if(pcsCurrent.AddCharSet(ch) == false)
{
vGenerated.AddLast(ch);
pcsUsed.Add(ch);
}
else bGenerateChar = true;
if(bGenerateChar)
{
PwGenerator.PrepareCharSet(pcsCurrent, pwProfile);
if(pwProfile.NoRepeatingCharacters)
pcsCurrent.Remove(pcsUsed.ToString());
char chGen = PwGenerator.GenerateCharacter(pwProfile,
pcsCurrent, crsRandomSource);
if(chGen == char.MinValue) return PwgError.TooFewCharacters;
vGenerated.AddLast(chGen);
pcsUsed.Add(chGen);
}
ch = csStream.ReadChar();
}
if(vGenerated.Count == 0) return PwgError.Success;
char[] vArray = new char[vGenerated.Count];
vGenerated.CopyTo(vArray, 0);
if(pwProfile.PatternPermutePassword)
PwGenerator.ShufflePassword(vArray, crsRandomSource);
byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vArray);
psOut = new ProtectedString(true, pbUtf8);
MemUtil.ZeroByteArray(pbUtf8);
MemUtil.ZeroArray<char>(vArray);
vGenerated.Clear();
return PwgError.Success;
}
private static string ExpandPattern(string strPattern)
{
Debug.Assert(strPattern != null); if(strPattern == null) return string.Empty;
string str = strPattern;
while(true)
{
int nOpen = FindFirstUnescapedChar(str, '{');
int nClose = FindFirstUnescapedChar(str, '}');
if((nOpen >= 0) && (nOpen < nClose))
{
string strCount = str.Substring(nOpen + 1, nClose - nOpen - 1);
str = str.Remove(nOpen, nClose - nOpen + 1);
uint uRepeat;
if(StrUtil.TryParseUInt(strCount, out uRepeat) && (nOpen >= 1))
{
if(uRepeat == 0)
str = str.Remove(nOpen - 1, 1);
else
str = str.Insert(nOpen, new string(str[nOpen - 1], (int)uRepeat - 1));
}
}
else break;
}
return str;
}
private static int FindFirstUnescapedChar(string str, char ch)
{
for(int i = 0; i < str.Length; ++i)
{
char chCur = str[i];
if(chCur == '\\') ++i; // Next is escaped, skip it
else if(chCur == ch) return i;
}
return -1;
}
}
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,333 +19,311 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Text;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator
{
public sealed class PwCharSet
{
public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public const string LowerCase = "abcdefghijklmnopqrstuvwxyz";
public const string Digits = "0123456789";
public sealed class PwCharSet : IEquatable<PwCharSet>
{
public static readonly string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static readonly string LowerCase = "abcdefghijklmnopqrstuvwxyz";
public static readonly string Digits = "0123456789";
public const string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ";
public const string LowerConsonants = "bcdfghjklmnpqrstvwxyz";
public const string UpperVowels = "AEIOU";
public const string LowerVowels = "aeiou";
public static readonly string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ";
public static readonly string LowerConsonants = "bcdfghjklmnpqrstvwxyz";
public static readonly string UpperVowels = "AEIOU";
public static readonly string LowerVowels = "aeiou";
public const string Punctuation = @",.;:";
public const string Brackets = @"[]{}()<>";
public static readonly string Punctuation = ",.;:";
public static readonly string Brackets = @"[]{}()<>";
public const string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
public static readonly string Special = "!\"#$%&'*+,./:;=?@\\^`|~";
public static readonly string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
public const string UpperHex = "0123456789ABCDEF";
public const string LowerHex = "0123456789abcdef";
public static readonly string UpperHex = "0123456789ABCDEF";
public static readonly string LowerHex = "0123456789abcdef";
public const string Invalid = "\t\r\n";
public const string LookAlike = @"O0l1I|";
public static readonly string LookAlike = "O0Il1|";
internal const string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits;
/// <summary>
/// Latin-1 Supplement except U+00A0 (NBSP) and U+00AD (SHY).
/// </summary>
public static readonly string Latin1S =
"\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7" +
"\u00A8\u00A9\u00AA\u00AB\u00AC\u00AE\u00AF" +
"\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7" +
"\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF" +
"\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7" +
"\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF" +
"\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7" +
"\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF" +
"\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7" +
"\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF" +
"\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7" +
"\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF";
private const int CharTabSize = (0x10000 / 8);
// internal static readonly string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits;
private List<char> m_vChars = new List<char>();
private byte[] m_vTab = new byte[CharTabSize];
[Obsolete]
public static string SpecialChars { get { return PwCharSet.Special; } }
[Obsolete]
public static string HighAnsiChars { get { return PwCharSet.Latin1S; } }
private static string m_strHighAnsi = null;
public static string HighAnsiChars
{
get
{
if(m_strHighAnsi == null) { new PwCharSet(); } // Create string
Debug.Assert(m_strHighAnsi != null);
return m_strHighAnsi;
}
}
private readonly List<char> m_lChars = new List<char>();
private readonly byte[] m_vTab = new byte[0x10000 / 8];
private static string m_strSpecial = null;
public static string SpecialChars
{
get
{
if(m_strSpecial == null) { new PwCharSet(); } // Create string
Debug.Assert(m_strSpecial != null);
return m_strSpecial;
}
}
/// <summary>
/// Create a new, empty character set.
/// </summary>
public PwCharSet()
{
Debug.Assert(PwCharSet.Latin1S.Length == (16 * 6 - 2));
}
/// <summary>
/// Create a new, empty character set collection object.
/// </summary>
public PwCharSet()
{
Initialize(true);
}
public PwCharSet(string strCharSet)
{
Add(strCharSet);
}
public PwCharSet(string strCharSet)
{
Initialize(true);
Add(strCharSet);
}
/// <summary>
/// Number of characters in this set.
/// </summary>
public uint Size
{
get { return (uint)m_lChars.Count; }
}
private PwCharSet(bool bFullInitialize)
{
Initialize(bFullInitialize);
}
/// <summary>
/// Get a character of the set using an index.
/// </summary>
/// <param name="uPos">Index of the character to get.</param>
/// <returns>Character at the specified position. If the index is invalid,
/// an <c>ArgumentOutOfRangeException</c> is thrown.</returns>
public char this[uint uPos]
{
get
{
if (uPos >= (uint)m_lChars.Count)
throw new ArgumentOutOfRangeException("uPos");
private void Initialize(bool bFullInitialize)
{
Clear();
return m_lChars[(int)uPos];
}
}
if(!bFullInitialize) return;
public bool Equals(PwCharSet other)
{
if (object.ReferenceEquals(other, this)) return true;
if (object.ReferenceEquals(other, null)) return false;
if(m_strHighAnsi == null)
{
StringBuilder sbHighAnsi = new StringBuilder();
// [U+0080, U+009F] are C1 control characters,
// U+00A0 is non-breaking space
for(char ch = '\u00A1'; ch <= '\u00AC'; ++ch)
sbHighAnsi.Append(ch);
// U+00AD is soft hyphen (format character)
for(char ch = '\u00AE'; ch < '\u00FF'; ++ch)
sbHighAnsi.Append(ch);
sbHighAnsi.Append('\u00FF');
if (m_lChars.Count != other.m_lChars.Count) return false;
m_strHighAnsi = sbHighAnsi.ToString();
}
return MemUtil.ArraysEqual(m_vTab, other.m_vTab);
}
if(m_strSpecial == null)
{
PwCharSet pcs = new PwCharSet(false);
pcs.AddRange('!', '/');
pcs.AddRange(':', '@');
pcs.AddRange('[', '`');
pcs.Add(@"|~");
pcs.Remove(@"-_ ");
pcs.Remove(PwCharSet.Brackets);
public override bool Equals(object obj)
{
return Equals(obj as PwCharSet);
}
m_strSpecial = pcs.ToString();
}
}
public override int GetHashCode()
{
return (int)MemUtil.Hash32(m_vTab, 0, m_vTab.Length);
}
/// <summary>
/// Number of characters in this set.
/// </summary>
public uint Size
{
get { return (uint)m_vChars.Count; }
}
/// <summary>
/// Remove all characters from this set.
/// </summary>
public void Clear()
{
m_lChars.Clear();
Array.Clear(m_vTab, 0, m_vTab.Length);
}
/// <summary>
/// Get a character of the set using an index.
/// </summary>
/// <param name="uPos">Index of the character to get.</param>
/// <returns>Character at the specified position. If the index is invalid,
/// an <c>ArgumentOutOfRangeException</c> is thrown.</returns>
public char this[uint uPos]
{
get
{
if(uPos >= (uint)m_vChars.Count)
throw new ArgumentOutOfRangeException("uPos");
public bool Contains(char ch)
{
return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue);
}
return m_vChars[(int)uPos];
}
}
public bool Contains(string strCharacters)
{
Debug.Assert(strCharacters != null);
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
/// <summary>
/// Remove all characters from this set.
/// </summary>
public void Clear()
{
m_vChars.Clear();
Array.Clear(m_vTab, 0, m_vTab.Length);
}
foreach (char ch in strCharacters)
{
if (!Contains(ch)) return false;
}
public bool Contains(char ch)
{
return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue);
}
return true;
}
public bool Contains(string strCharacters)
{
Debug.Assert(strCharacters != null);
if(strCharacters == null) throw new ArgumentNullException("strCharacters");
/// <summary>
/// Add characters to the set.
/// </summary>
/// <param name="ch">Character to add.</param>
public void Add(char ch)
{
if (ch == char.MinValue) { Debug.Assert(false); return; }
foreach(char ch in strCharacters)
{
if(!Contains(ch)) return false;
}
if (!Contains(ch))
{
m_lChars.Add(ch);
m_vTab[ch / 8] |= (byte)(1 << (ch % 8));
}
}
return true;
}
/// <summary>
/// Add characters to the set.
/// </summary>
/// <param name="strCharSet">String containing characters to add.</param>
public void Add(string strCharSet)
{
Debug.Assert(strCharSet != null);
if (strCharSet == null) throw new ArgumentNullException("strCharSet");
/// <summary>
/// Add characters to the set.
/// </summary>
/// <param name="ch">Character to add.</param>
public void Add(char ch)
{
if(ch == char.MinValue) { Debug.Assert(false); return; }
foreach (char ch in strCharSet)
Add(ch);
}
if(!Contains(ch))
{
m_vChars.Add(ch);
m_vTab[ch / 8] |= (byte)(1 << (ch % 8));
}
}
public void Add(string strCharSet1, string strCharSet2)
{
Add(strCharSet1);
Add(strCharSet2);
}
/// <summary>
/// Add characters to the set.
/// </summary>
/// <param name="strCharSet">String containing characters to add.</param>
public void Add(string strCharSet)
{
Debug.Assert(strCharSet != null);
if(strCharSet == null) throw new ArgumentNullException("strCharSet");
public void Add(string strCharSet1, string strCharSet2, string strCharSet3)
{
Add(strCharSet1);
Add(strCharSet2);
Add(strCharSet3);
}
m_vChars.Capacity = m_vChars.Count + strCharSet.Length;
public void AddRange(char chMin, char chMax)
{
for (char ch = chMin; ch < chMax; ++ch)
Add(ch);
foreach(char ch in strCharSet)
Add(ch);
}
Add(chMax);
}
public void Add(string strCharSet1, string strCharSet2)
{
Add(strCharSet1);
Add(strCharSet2);
}
public bool AddCharSet(char chCharSetIdentifier)
{
bool bResult = true;
public void Add(string strCharSet1, string strCharSet2, string strCharSet3)
{
Add(strCharSet1);
Add(strCharSet2);
Add(strCharSet3);
}
switch (chCharSetIdentifier)
{
case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break;
case 'A':
Add(PwCharSet.LowerCase, PwCharSet.UpperCase,
PwCharSet.Digits); break;
case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break;
case 'c': Add(PwCharSet.LowerConsonants); break;
case 'C':
Add(PwCharSet.LowerConsonants,
PwCharSet.UpperConsonants); break;
case 'z': Add(PwCharSet.UpperConsonants); break;
case 'd': Add(PwCharSet.Digits); break; // Digit
case 'h': Add(PwCharSet.LowerHex); break;
case 'H': Add(PwCharSet.UpperHex); break;
case 'l': Add(PwCharSet.LowerCase); break;
case 'L': Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break;
case 'u': Add(PwCharSet.UpperCase); break;
case 'p': Add(PwCharSet.Punctuation); break;
case 'b': Add(PwCharSet.Brackets); break;
case 's': Add(PwCharSet.PrintableAsciiSpecial); break;
case 'S':
Add(PwCharSet.UpperCase, PwCharSet.LowerCase);
Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break;
case 'v': Add(PwCharSet.LowerVowels); break;
case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break;
case 'Z': Add(PwCharSet.UpperVowels); break;
case 'x': Add(PwCharSet.Latin1S); break;
default: bResult = false; break;
}
public void AddRange(char chMin, char chMax)
{
m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1;
return bResult;
}
for(char ch = chMin; ch < chMax; ++ch)
Add(ch);
public bool Remove(char ch)
{
m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8)));
return m_lChars.Remove(ch);
}
Add(chMax);
}
public bool Remove(string strCharacters)
{
Debug.Assert(strCharacters != null);
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
public bool AddCharSet(char chCharSetIdentifier)
{
bool bResult = true;
bool bResult = true;
foreach (char ch in strCharacters)
{
if (!Remove(ch)) bResult = false;
}
switch(chCharSetIdentifier)
{
case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break;
case 'A': Add(PwCharSet.LowerCase, PwCharSet.UpperCase,
PwCharSet.Digits); break;
case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break;
case 'c': Add(PwCharSet.LowerConsonants); break;
case 'C': Add(PwCharSet.LowerConsonants,
PwCharSet.UpperConsonants); break;
case 'z': Add(PwCharSet.UpperConsonants); break;
case 'd': Add(PwCharSet.Digits); break; // Digit
case 'h': Add(PwCharSet.LowerHex); break;
case 'H': Add(PwCharSet.UpperHex); break;
case 'l': Add(PwCharSet.LowerCase); break;
case 'L': Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break;
case 'u': Add(PwCharSet.UpperCase); break;
case 'p': Add(PwCharSet.Punctuation); break;
case 'b': Add(PwCharSet.Brackets); break;
case 's': Add(PwCharSet.PrintableAsciiSpecial); break;
case 'S': Add(PwCharSet.UpperCase, PwCharSet.LowerCase);
Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break;
case 'v': Add(PwCharSet.LowerVowels); break;
case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break;
case 'Z': Add(PwCharSet.UpperVowels); break;
case 'x': Add(m_strHighAnsi); break;
default: bResult = false; break;
}
return bResult;
}
return bResult;
}
public bool RemoveIfAllExist(string strCharacters)
{
Debug.Assert(strCharacters != null);
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
public bool Remove(char ch)
{
m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8)));
return m_vChars.Remove(ch);
}
if (!Contains(strCharacters))
return false;
public bool Remove(string strCharacters)
{
Debug.Assert(strCharacters != null);
if(strCharacters == null) throw new ArgumentNullException("strCharacters");
return Remove(strCharacters);
}
bool bResult = true;
foreach(char ch in strCharacters)
{
if(!Remove(ch)) bResult = false;
}
/// <summary>
/// Convert the character set to a string containing all its characters.
/// </summary>
/// <returns>String containing all character set characters.</returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder(m_lChars.Count);
foreach (char ch in m_lChars)
sb.Append(ch);
return bResult;
}
return sb.ToString();
}
public bool RemoveIfAllExist(string strCharacters)
{
Debug.Assert(strCharacters != null);
if(strCharacters == null) throw new ArgumentNullException("strCharacters");
public string PackAndRemoveCharRanges()
{
StringBuilder sb = new StringBuilder();
if(!Contains(strCharacters))
return false;
sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Special) ? 'S' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_');
sb.Append(RemoveIfAllExist("-") ? 'm' : '_');
sb.Append(RemoveIfAllExist("_") ? 'u' : '_');
sb.Append(RemoveIfAllExist(" ") ? 's' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Latin1S) ? 'H' : '_');
return Remove(strCharacters);
}
return sb.ToString();
}
/// <summary>
/// Convert the character set to a string containing all its characters.
/// </summary>
/// <returns>String containing all character set characters.</returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach(char ch in m_vChars)
sb.Append(ch);
public void UnpackCharRanges(string strRanges)
{
if (strRanges == null) { Debug.Assert(false); return; }
if (strRanges.Length < 10) { Debug.Assert(false); return; }
return sb.ToString();
}
public string PackAndRemoveCharRanges()
{
StringBuilder sb = new StringBuilder();
sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_');
sb.Append(RemoveIfAllExist(m_strSpecial) ? 'S' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_');
sb.Append(RemoveIfAllExist(@"-") ? 'm' : '_');
sb.Append(RemoveIfAllExist(@"_") ? 'u' : '_');
sb.Append(RemoveIfAllExist(@" ") ? 's' : '_');
sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_');
sb.Append(RemoveIfAllExist(m_strHighAnsi) ? 'H' : '_');
return sb.ToString();
}
public void UnpackCharRanges(string strRanges)
{
if(strRanges == null) { Debug.Assert(false); return; }
if(strRanges.Length < 10) { Debug.Assert(false); return; }
if(strRanges[0] != '_') Add(PwCharSet.UpperCase);
if(strRanges[1] != '_') Add(PwCharSet.LowerCase);
if(strRanges[2] != '_') Add(PwCharSet.Digits);
if(strRanges[3] != '_') Add(m_strSpecial);
if(strRanges[4] != '_') Add(PwCharSet.Punctuation);
if(strRanges[5] != '_') Add('-');
if(strRanges[6] != '_') Add('_');
if(strRanges[7] != '_') Add(' ');
if(strRanges[8] != '_') Add(PwCharSet.Brackets);
if(strRanges[9] != '_') Add(m_strHighAnsi);
}
}
if (strRanges[0] != '_') Add(PwCharSet.UpperCase);
if (strRanges[1] != '_') Add(PwCharSet.LowerCase);
if (strRanges[2] != '_') Add(PwCharSet.Digits);
if (strRanges[3] != '_') Add(PwCharSet.Special);
if (strRanges[4] != '_') Add(PwCharSet.Punctuation);
if (strRanges[5] != '_') Add('-');
if (strRanges[6] != '_') Add('_');
if (strRanges[7] != '_') Add(' ');
if (strRanges[8] != '_') Add(PwCharSet.Brackets);
if (strRanges[9] != '_') Add(PwCharSet.Latin1S);
}
}
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,133 +20,172 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
#if !KeePassUAP
using System.Security.Cryptography;
#endif
using KeePassLib.Resources;
using KeePassLib.Security;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator
{
public enum PwgError
{
Success = 0,
Unknown = 1,
TooFewCharacters = 2,
UnknownAlgorithm = 3
}
public enum PwgError
{
Success = 0,
Unknown = 1,
TooFewCharacters = 2,
UnknownAlgorithm = 3,
InvalidCharSet = 4,
InvalidPattern = 5
}
/// <summary>
/// Utility functions for generating random passwords.
/// </summary>
public static class PwGenerator
{
public static PwgError Generate(out ProtectedString psOut,
PwProfile pwProfile, byte[] pbUserEntropy,
CustomPwGeneratorPool pwAlgorithmPool)
{
Debug.Assert(pwProfile != null);
if (pwProfile == null) throw new ArgumentNullException("pwProfile");
/// <summary>
/// Password generator.
/// </summary>
public static class PwGenerator
{
CryptoRandomStream crs = CreateCryptoStream(pbUserEntropy);
PwgError e = PwgError.Unknown;
private static CryptoRandomStream CreateRandomStream(byte[] pbAdditionalEntropy,
out byte[] pbKey)
{
pbKey = CryptoRandom.Instance.GetRandomBytes(128);
if (pwProfile.GeneratorType == PasswordGeneratorType.CharSet)
e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs);
else if (pwProfile.GeneratorType == PasswordGeneratorType.Pattern)
e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs);
else if (pwProfile.GeneratorType == PasswordGeneratorType.Custom)
e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool);
else { Debug.Assert(false); psOut = ProtectedString.Empty; }
// Mix in additional entropy
Debug.Assert(pbKey.Length >= 64);
if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length != 0))
{
using (SHA512Managed h = new SHA512Managed())
{
byte[] pbHash = h.ComputeHash(pbAdditionalEntropy);
MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length);
MemUtil.ZeroByteArray(pbHash);
}
}
return e;
}
return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey);
}
private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy)
{
byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128);
internal static char GenerateCharacter(PwCharSet pwCharSet,
CryptoRandomStream crsRandomSource)
{
uint cc = pwCharSet.Size;
if (cc == 0) return char.MinValue;
// Mix in additional entropy
Debug.Assert(pbKey.Length >= 64);
if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0))
{
using (SHA512Managed h = new SHA512Managed())
{
byte[] pbHash = h.ComputeHash(pbAdditionalEntropy);
MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length);
}
}
uint i = (uint)crsRandomSource.GetRandomUInt64(cc);
return pwCharSet[i];
}
return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey);
}
internal static bool PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)
{
uint cc = pwCharSet.Size;
for (uint i = 0; i < cc; ++i)
{
char ch = pwCharSet[i];
if ((ch == char.MinValue) || (ch == '\t') || (ch == '\r') ||
(ch == '\n') || char.IsSurrogate(ch))
return false;
}
internal static char GenerateCharacter(PwProfile pwProfile,
PwCharSet pwCharSet, CryptoRandomStream crsRandomSource)
{
if (pwCharSet.Size == 0) return char.MinValue;
if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike);
ulong uIndex = crsRandomSource.GetRandomUInt64();
uIndex %= (ulong)pwCharSet.Size;
if (!string.IsNullOrEmpty(pwProfile.ExcludeCharacters))
pwCharSet.Remove(pwProfile.ExcludeCharacters);
char ch = pwCharSet[(uint)uIndex];
return true;
}
if (pwProfile.NoRepeatingCharacters)
pwCharSet.Remove(ch);
internal static void Shuffle(char[] v, CryptoRandomStream crsRandomSource)
{
if (v == null) { Debug.Assert(false); return; }
if (crsRandomSource == null) { Debug.Assert(false); return; }
return ch;
}
for (int i = v.Length - 1; i >= 1; --i)
{
int j = (int)crsRandomSource.GetRandomUInt64((ulong)(i + 1));
internal static void PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)
{
pwCharSet.Remove(PwCharSet.Invalid);
char t = v[i];
v[i] = v[j];
v[j] = t;
}
}
if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike);
private static PwgError GenerateCustom(out ProtectedString psOut,
PwProfile pwProfile, CryptoRandomStream crs,
CustomPwGeneratorPool pwAlgorithmPool)
{
psOut = ProtectedString.Empty;
if (pwProfile.ExcludeCharacters.Length > 0)
pwCharSet.Remove(pwProfile.ExcludeCharacters);
}
Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom);
if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm;
internal static void ShufflePassword(char[] pPassword,
CryptoRandomStream crsRandomSource)
{
Debug.Assert(pPassword != null); if (pPassword == null) return;
Debug.Assert(crsRandomSource != null); if (crsRandomSource == null) return;
string strID = pwProfile.CustomAlgorithmUuid;
if (string.IsNullOrEmpty(strID)) return PwgError.UnknownAlgorithm;
if (pPassword.Length <= 1) return; // Nothing to shuffle
byte[] pbUuid = Convert.FromBase64String(strID);
PwUuid uuid = new PwUuid(pbUuid);
CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid);
if (pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; }
for (int nSelect = 0; nSelect < pPassword.Length; ++nSelect)
{
ulong uRandomIndex = crsRandomSource.GetRandomUInt64();
uRandomIndex %= (ulong)(pPassword.Length - nSelect);
ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs);
if (pwd == null) return PwgError.Unknown;
char chTemp = pPassword[nSelect];
pPassword[nSelect] = pPassword[nSelect + (int)uRandomIndex];
pPassword[nSelect + (int)uRandomIndex] = chTemp;
}
}
psOut = pwd;
return PwgError.Success;
}
private static PwgError GenerateCustom(out ProtectedString psOut,
PwProfile pwProfile, CryptoRandomStream crs,
CustomPwGeneratorPool pwAlgorithmPool)
{
psOut = ProtectedString.Empty;
internal static string ErrorToString(PwgError e, bool bHeader)
{
if (e == PwgError.Success) { Debug.Assert(false); return string.Empty; }
if ((e == PwgError.Unknown) && bHeader) return KLRes.PwGenFailed;
Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom);
if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm;
string str = KLRes.UnknownError;
switch (e)
{
// case PwgError.Success:
// break;
string strID = pwProfile.CustomAlgorithmUuid;
if (string.IsNullOrEmpty(strID)) { Debug.Assert(false); return PwgError.UnknownAlgorithm; }
case PwgError.Unknown:
break;
byte[] pbUuid = Convert.FromBase64String(strID);
PwUuid uuid = new PwUuid(pbUuid);
CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid);
if (pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; }
case PwgError.TooFewCharacters:
str = KLRes.CharSetTooFewChars;
break;
ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs);
if (pwd == null) return PwgError.Unknown;
case PwgError.UnknownAlgorithm:
str = KLRes.AlgorithmUnknown;
break;
psOut = pwd;
return PwgError.Success;
}
}
case PwgError.InvalidCharSet:
str = KLRes.CharSetInvalid;
break;
case PwgError.InvalidPattern:
str = KLRes.PatternInvalid;
break;
default:
Debug.Assert(false);
break;
}
if (bHeader)
str = KLRes.PwGenFailed + MessageService.NewParagraph + str;
return str;
}
internal static string ErrorToString(Exception ex, bool bHeader)
{
string str = ((ex == null) ? KLRes.UnknownError :
StrUtil.FormatException(ex));
if (bHeader)
str = KLRes.PwGenFailed + MessageService.NewParagraph + str;
return str;
}
}
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,114 +19,115 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Text;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography
{
public static class PopularPasswords
{
private static Dictionary<int, Dictionary<string, bool>> m_dicts =
new Dictionary<int, Dictionary<string, bool>>();
public static class PopularPasswords
{
private static readonly Dictionary<int, Dictionary<char[], bool>> g_dicts =
new Dictionary<int, Dictionary<char[], bool>>();
internal static int MaxLength
{
get
{
int iMaxLen = 0;
foreach(int iLen in m_dicts.Keys)
{
if(iLen > iMaxLen) iMaxLen = iLen;
}
internal static int MaxLength
{
get
{
Debug.Assert(g_dicts.Count > 0); // Should be initialized
return iMaxLen;
}
}
int iMaxLen = 0;
foreach (int iLen in g_dicts.Keys)
{
if (iLen > iMaxLen) iMaxLen = iLen;
}
internal static bool ContainsLength(int nLength)
{
Dictionary<string, bool> dDummy;
return m_dicts.TryGetValue(nLength, out dDummy);
}
return iMaxLen;
}
}
public static bool IsPopularPassword(char[] vPassword)
{
ulong uDummy;
return IsPopularPassword(vPassword, out uDummy);
}
internal static bool ContainsLength(int nLength)
{
Dictionary<char[], bool> dDummy;
return g_dicts.TryGetValue(nLength, out dDummy);
}
public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize)
{
if(vPassword == null) throw new ArgumentNullException("vPassword");
if(vPassword.Length == 0) { uDictSize = 0; return false; }
public static bool IsPopularPassword(char[] vPassword)
{
ulong uDummy;
return IsPopularPassword(vPassword, out uDummy);
}
string str = new string(vPassword);
public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize)
{
if (vPassword == null) throw new ArgumentNullException("vPassword");
if (vPassword.Length == 0) { uDictSize = 0; return false; }
try { return IsPopularPasswordPriv(str, out uDictSize); }
catch(Exception) { Debug.Assert(false); }
#if DEBUG
Array.ForEach(vPassword, ch => Debug.Assert(ch == char.ToLower(ch)));
#endif
uDictSize = 0;
return false;
}
try { return IsPopularPasswordPriv(vPassword, out uDictSize); }
catch (Exception) { Debug.Assert(false); }
private static bool IsPopularPasswordPriv(string str, out ulong uDictSize)
{
Debug.Assert(m_dicts.Count > 0); // Should be initialized with data
uDictSize = 0;
return false;
}
Dictionary<string, bool> d;
if(!m_dicts.TryGetValue(str.Length, out d))
{
uDictSize = 0;
return false;
}
private static bool IsPopularPasswordPriv(char[] vPassword, out ulong uDictSize)
{
Debug.Assert(g_dicts.Count > 0); // Should be initialized with data
uDictSize = (ulong)d.Count;
return d.ContainsKey(str);
}
Dictionary<char[], bool> d;
if (!g_dicts.TryGetValue(vPassword.Length, out d))
{
uDictSize = 0;
return false;
}
public static void Add(byte[] pbData, bool bGZipped)
{
try
{
if(bGZipped)
pbData = MemUtil.Decompress(pbData);
uDictSize = (ulong)d.Count;
return d.ContainsKey(vPassword);
}
string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length);
if(string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; }
public static void Add(byte[] pbData, bool bGZipped)
{
try
{
if (bGZipped)
pbData = MemUtil.Decompress(pbData);
if(!char.IsWhiteSpace(strData[strData.Length - 1]))
strData += "\n";
string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length);
if (string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; }
StringBuilder sb = new StringBuilder();
for(int i = 0; i < strData.Length; ++i)
{
char ch = strData[i];
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= strData.Length; ++i)
{
char ch = ((i == strData.Length) ? ' ' : strData[i]);
if(char.IsWhiteSpace(ch))
{
int cc = sb.Length;
if(cc > 0)
{
string strWord = sb.ToString();
Debug.Assert(strWord.Length == cc);
if (char.IsWhiteSpace(ch))
{
int cc = sb.Length;
if (cc > 0)
{
char[] vWord = new char[cc];
sb.CopyTo(0, vWord, 0, cc);
Dictionary<string, bool> d;
if(!m_dicts.TryGetValue(cc, out d))
{
d = new Dictionary<string, bool>();
m_dicts[cc] = d;
}
Dictionary<char[], bool> d;
if (!g_dicts.TryGetValue(cc, out d))
{
d = new Dictionary<char[], bool>(MemUtil.ArrayHelperExOfChar);
g_dicts[cc] = d;
}
d[strWord] = true;
sb.Remove(0, cc);
}
}
else sb.Append(char.ToLower(ch));
}
}
catch(Exception) { Debug.Assert(false); }
}
}
d[vWord] = true;
sb.Remove(0, cc);
}
}
else sb.Append(char.ToLower(ch));
}
}
catch (Exception) { Debug.Assert(false); }
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -46,4 +46,12 @@ namespace KeePassLib.Delegates
public delegate void VoidDelegate();
public delegate string StrPwEntryDelegate(string str, PwEntry pe);
public delegate TResult GFunc<TResult>();
public delegate TResult GFunc<T, TResult>(T o);
public delegate TResult GFunc<T1, T2, TResult>(T1 o1, T2 o2);
public delegate TResult GFunc<T1, T2, T3, TResult>(T1 o1, T2 o2, T3 o3);
public delegate TResult GFunc<T1, T2, T3, T4, TResult>(T1 o1, T2 o2, T3 o3, T4 o4);
public delegate TResult GFunc<T1, T2, T3, T4, T5, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5);
public delegate TResult GFunc<T1, T2, T3, T4, T5, T6, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5, T6 o6);
}

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<TargetFramework>net9.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -116,12 +116,23 @@ namespace keepass2android
Intent sendIntent = new Intent();
sendIntent.SetAction(Intent.ActionSend);
sendIntent.PutExtra(Intent.ExtraText, File.ReadAllText(LogFilename));
string logText = File.ReadAllText(LogFilename);
sendIntent.PutExtra(Intent.ExtraText, logText);
sendIntent.PutExtra(Intent.ExtraEmail, "crocoapps@gmail.com");
sendIntent.PutExtra(Intent.ExtraSubject, "Keepass2Android log");
sendIntent.SetType("text/plain");
ctx.StartActivity(Intent.CreateChooser(sendIntent, "Send log to..."));
}
try
{
ctx.StartActivity(Intent.CreateChooser(sendIntent, "Send log to..."));
}
catch (Exception e)
{
Toast.MakeText(ctx, $"Error sending log of length {logText.Length} bytes: " + e.Message, ToastLength.Long)?.Show();
}
}
public static void LogTask(object task, string activityName)
{

View File

@@ -83,6 +83,7 @@ namespace KeePassLib.Serialization
if (m_bUsedOnce)
throw new InvalidOperationException("Do not reuse KdbxFile objects!");
m_bUsedOnce = true;
Kp2aLog.Log("Starting to load KDBX file...");
#if KDBX_BENCHMARK
Stopwatch swTime = Stopwatch.StartNew();
@@ -257,7 +258,8 @@ namespace KeePassLib.Serialization
MessageService.ShowInfo("Loading KDBX took " +
swTime.ElapsedMilliseconds.ToString() + " ms.");
#endif
}
Kp2aLog.Log("Finished loading KDBX file.");
}
private void CommonCleanUpRead(List<Stream> lStreams, HashingStreamEx sHashing)
{

File diff suppressed because it is too large Load Diff

View File

@@ -95,7 +95,7 @@ namespace Kp2aAutofillParserTest
StructureParserBase<TestInputField> parser =
new StructureParserBase<TestInputField>(new TestLogger(), new TestDalSourceTrustAll());
var result = parser.ParseForFill(false, autofillView);
var result = parser.ParseForFill(autofillView);
if (expectedPackageName != null)
Assert.Equal(expectedPackageName, result.PackageName);
if (expectedWebDomain != null)

View File

@@ -58,7 +58,8 @@
"IsFocused": false,
"InputType": 97,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null
"HtmlInfoTypeAttribute": null,
"ExpectedAssignedHints": [ "username" ]
},
{
"IdEntry": "password_text_input_layout",
@@ -81,6 +82,7 @@
"InputType": 129,
"HtmlInfoTag": null,
"HtmlInfoTypeAttribute": null,
"ExpectedAssignedHints": [ "password" ]
},
{

View File

@@ -476,8 +476,16 @@ namespace Kp2aAutofillParser
foreach (var field in autofillFields.HintMap.Values.Distinct())
{
if (field == null || field.AutofillHints == null)
{
continue;
}
foreach (var hint in field.AutofillHints)
{
if (hint == null)
{
continue;
}
if (GetPartitionIndex(hint) == partitionIndex)
{
filteredCollection.Add(field);
@@ -793,14 +801,14 @@ namespace Kp2aAutofillParser
}
}
public AutofillTargetId ParseForFill(bool isManual, AutofillView<FieldT> autofillView)
public AutofillTargetId ParseForFill(AutofillView<FieldT> autofillView)
{
return Parse(true, isManual, autofillView);
return Parse(true, autofillView);
}
public AutofillTargetId ParseForSave(AutofillView<FieldT> autofillView)
{
return Parse(false, true, autofillView);
return Parse(false, autofillView);
}
/// <summary>
@@ -808,8 +816,7 @@ namespace Kp2aAutofillParser
/// </summary>
/// <returns>The parse.</returns>
/// <param name="forFill">If set to <c>true</c> for fill.</param>
/// <param name="isManualRequest"></param>
protected virtual AutofillTargetId Parse(bool forFill, bool isManualRequest, AutofillView<FieldT> autofillView)
protected virtual AutofillTargetId Parse(bool forFill, AutofillView<FieldT> autofillView)
{
AutofillTargetId result = new AutofillTargetId()
{
@@ -876,8 +883,9 @@ namespace Kp2aAutofillParser
}
//for "heuristic determination" we demand that one of the filled fields is focused:
if (passwordFields.Concat(usernameFields).Any(f => f.IsFocused))
//for "heuristic determination" we demand that there is a password field or one of the username fields is focused:
//Note that "IsFocused" might be false even when tapping the field. It might require long-press to autofill.
if (passwordFields.Any() || usernameFields.Any(f => f.IsFocused))
{
foreach (var uf in usernameFields)
AddFieldToHintMap(uf, new string[] { AutofillHintsHelper.AutofillHintUsername });

View File

@@ -29,6 +29,14 @@ namespace keepass2android
}
public enum MessageSeverity
{
Info,
Warning,
Error
}
/// <summary>
/// Interface through which Activities and the logic layer can access some app specific functionalities and Application static data
/// </summary>
@@ -102,10 +110,13 @@ namespace keepass2android
Context ctx,
string messageSuffix = "");
/// <summary>
/// Returns a Handler object which can run tasks on the UI thread
/// </summary>
Handler UiThreadHandler { get; }
void ShowMessage(Context ctx, int resourceId, MessageSeverity severity);
void ShowMessage(Context ctx, string text, MessageSeverity severity);
/// <summary>
/// Returns a Handler object which can run tasks on the UI thread
/// </summary>
Handler UiThreadHandler { get; }
IProgressDialog CreateProgressDialog(Context ctx);
@@ -129,6 +140,10 @@ namespace keepass2android
#endif
int WebDavChunkedUploadSize
{
get;
}
}
}

View File

@@ -13,7 +13,7 @@ using Android.Content.PM;
using Android.OS;
using Android.Preferences;
using Java.IO;
using KeePass.Util;
using KeePassLib.Serialization;
using KeePassLib.Utility;
using File = System.IO.File;
@@ -121,7 +121,7 @@ namespace keepass2android.Io
var response = ex.Response as HttpWebResponse;
if ((response != null) && (response.StatusCode == HttpStatusCode.NotFound))
{
throw new FileNotFoundException(ex.Message, ioc.Path, ex);
throw new FileNotFoundException(ExceptionUtil.GetErrorMessage(ex), ioc.Path, ex);
}
if (ex.Status == WebExceptionStatus.TrustFailure)
{

View File

@@ -15,7 +15,9 @@ namespace keepass2android.Io
{
get { return false; }
}
}
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
}
public partial class DropboxAppFolderFileStorage: JavaFileStorage
{
@@ -29,6 +31,7 @@ namespace keepass2android.Io
get { return false; }
}
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
}
}

View File

@@ -1,13 +0,0 @@
namespace keepass2android.Io
{
public partial class DropboxFileStorage
{
private const string AppKey = "dummy";
private const string AppSecret = "dummy";
}
public partial class DropboxAppFolderFileStorage
{
private const string AppKey = "dummy";
private const string AppSecret = "dummy";
}
}

View File

@@ -0,0 +1,27 @@
<Project>
<Target Name="GenerateDropboxSecrets" BeforeTargets="BeforeCompile"
Inputs="@(DropboxSecretLines)"
Outputs="DropboxFileStorage.g.cs">
<WriteLinesToFile
File="Io/DropboxFileStorage.g.cs"
Lines="@(DropboxSecretLines->'%(Text)')"
Overwrite="true"
/>
</Target>
<ItemGroup>
<DropboxSecretLines Include="GeneratedDropboxSecrets">
<Text>namespace keepass2android.Io {
public partial class DropboxFileStorage {
private const string AppKey = "$(DropboxAppKey)";
private const string AppSecret = "$(DropboxAppSecret)";
}
public partial class DropboxAppFolderFileStorage {
private const string AppKey = "$(DropboxAppFolderAppKey)";
private const string AppSecret = "$(DropboxAppFolderAppSecret)";
}
}</Text>
</DropboxSecretLines>
</ItemGroup>
</Project>

View File

@@ -13,6 +13,7 @@ using Keepass2android.Javafilestorage;
#endif
using Exception = System.Exception;
using FileNotFoundException = Java.IO.FileNotFoundException;
using KeePass.Util;
namespace keepass2android.Io
{
@@ -42,7 +43,7 @@ namespace keepass2android.Io
}
catch (FileNotFoundException e)
{
throw new System.IO.FileNotFoundException(e.Message, e);
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
}
catch (Java.Lang.Exception e)
{
@@ -122,7 +123,7 @@ namespace keepass2android.Io
}
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
public virtual IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
return new JavaFileStorageWriteTransaction(IocToPath(ioc), useFileTransaction, this);
}
@@ -195,7 +196,7 @@ namespace keepass2android.Io
}
catch (FileNotFoundException e)
{
throw new System.IO.FileNotFoundException(e.Message, e);
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
}
catch (Java.Lang.Exception e)
{
@@ -214,7 +215,7 @@ namespace keepass2android.Io
}
catch (FileNotFoundException e)
{
throw new System.IO.FileNotFoundException(e.Message, e);
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
}
catch (Java.Lang.Exception e)
{
@@ -244,7 +245,7 @@ namespace keepass2android.Io
}
catch (FileNotFoundException e)
{
throw new System.IO.FileNotFoundException(e.Message, e);
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
}
catch (Java.Lang.Exception e)
{

View File

@@ -8,6 +8,8 @@ using Android.Content;
using Android.OS;
using FluentFTP;
using FluentFTP.Exceptions;
using FluentFTP.GnuTLS;
using KeePass.Util;
using KeePassLib;
using KeePassLib.Serialization;
using KeePassLib.Utility;
@@ -127,7 +129,7 @@ namespace keepass2android.Io
var ftpEx = (FtpCommandException) exception;
if (ftpEx.CompletionCode == "550")
throw new FileNotFoundException(exception.Message, exception);
throw new FileNotFoundException(ExceptionUtil.GetErrorMessage(exception), exception);
}
return exception;
@@ -139,6 +141,7 @@ namespace keepass2android.Io
var settings = ConnectionSettings.FromIoc(ioc);
FtpClient client = new FtpClient();
client.Config.CustomStream = typeof(GnuTlsStream);
client.Config.RetryAttempts = 3;
if ((settings.Username.Length > 0) || (settings.Password.Length > 0))
client.Credentials = new NetworkCredential(settings.Username, settings.Password);

View File

@@ -3,6 +3,7 @@ using System.Reflection;
using System.Text;
using Android.Content;
using Android.Util;
using KeePass.Util;
using keepass2android.Io.ItemLocation;
using KeePassLib.Serialization;
using KeePassLib.Utility;
@@ -522,10 +523,10 @@ namespace keepass2android.Io
{
if (e.IsMatch(GraphErrorCode.ItemNotFound.ToString()))
return new FileNotFoundException(e.Message);
return new FileNotFoundException(ExceptionUtil.GetErrorMessage(e));
if (e.Message.Contains("\n\n404 : ")
) //hacky solution to check for not found. errorCode was null in my tests so I had to find a workaround.
return new FileNotFoundException(e.Message);
return new FileNotFoundException(ExceptionUtil.GetErrorMessage(e));
return e;
}

View File

@@ -16,20 +16,32 @@ namespace keepass2android.Io
/// </summary>
public class OneDriveFileStorage: IFileStorage
{
public IEnumerable<string> SupportedProtocols
public OneDriveFileStorage(IKp2aApp app)
{
_app = app;
}
private readonly IKp2aApp _app;
public IEnumerable<string> SupportedProtocols
{
get
{
yield return "skydrive";
yield return "onedrive";
}
}
}
private Exception GetDeprecatedMessage()
string GetDeprecatedMessage()
{
return
"You have opened your file through a deprecated Microsoft API. Please select Change database, Open Database and then select OneDrive again.";
}
private Exception GetDeprecatedException()
{
return new Exception(
"You have opened your file through a deprecated Microsoft API. Please select Change database, Open Database and then select One Drive again.");
GetDeprecatedMessage());
}
public bool UserShouldBackup
@@ -39,133 +51,132 @@ namespace keepass2android.Io
public void Delete(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public Stream OpenFileForRead(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public string GetFileExtension(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public bool RequiresCredentials(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
return false;
}
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public FileDescription GetFileDescription(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public bool RequiresSetup(IOConnectionInfo ioConnection)
{
throw GetDeprecatedMessage();
return false;
}
public string IocToPath(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
{
throw GetDeprecatedMessage();
}
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
bool alwaysReturnSuccess)
{
throw GetDeprecatedMessage();
_app.ShowMessage(activity.Activity, GetDeprecatedMessage(), MessageSeverity.Error);
}
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
}
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
{
throw GetDeprecatedMessage();
}
public void OnResume(IFileStorageSetupActivity activity)
{
throw GetDeprecatedMessage();
}
public void OnStart(IFileStorageSetupActivity activity)
{
throw GetDeprecatedMessage();
}
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
{
throw GetDeprecatedMessage();
}
public string GetDisplayName(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
return "File using deprecated Microsoft API. Please update.";
}
public string CreateFilePath(string parent, string newFilename)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public bool IsPermanentLocation(IOConnectionInfo ioc)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null)
{
throw GetDeprecatedMessage();
throw GetDeprecatedException();
}
}
}

View File

@@ -0,0 +1,617 @@
#if !NoNet
using System.Net;
using Android.Content;
using keepass2android;
using keepass2android.Io;
using KeePassLib.Serialization;
using SMBLibrary.Client;
using SMBLibrary;
using FileAttributes = SMBLibrary.FileAttributes;
using KeePassLib.Utility;
using Java.Nio.FileNio;
namespace Kp2aBusinessLogic.Io
{
public class SmbFileStorage : IFileStorage
{
public IEnumerable<string> SupportedProtocols
{
get { yield return "smb"; }
}
public bool UserShouldBackup
{
get { return false; }
}
public void Delete(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
{
return false;
}
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
{
return null;
}
public struct SmbConnectionInfo
{
public string Host;
public string Username;
public string Password;
public string? Domain;
public string? Share;
public string? LocalPath;
public static SmbConnectionInfo FromUrlAndCredentials(string url, string username, string password, string? domain)
{
string userDomain = username;
if (domain != null)
{
userDomain = domain + "\\" + username;
}
if (url.StartsWith("smb://"))
{
url = url.Substring(6);
}
if (url.StartsWith("\\\\"))
{
url = url.Substring(2);
}
url = url.Replace("\\", "/");
string fullPath = "smb://" + WebUtility.UrlEncode(userDomain) + ":" + WebUtility.UrlEncode(password) + "@" + url;
return new SmbConnectionInfo(new IOConnectionInfo() { Path = fullPath} );
}
public SmbConnectionInfo(IOConnectionInfo ioc)
{
string fullpath = ioc.Path;
if (!fullpath.StartsWith("smb://"))
{
throw new Exception("Invalid smb path!");
}
fullpath = fullpath.Substring(6);
string[] authAndPath = fullpath.Split('@');
if (authAndPath.Length != 2)
{
throw new Exception("Invalid smb path!");
}
string[] userAndPwd = authAndPath[0].Split(':');
if (userAndPwd.Length != 2)
{
throw new Exception("Invalid smb path!");
}
string[] pathParts = authAndPath[1].Split('/');
if (pathParts.Length < 1)
{
throw new Exception("Invalid smb path!");
}
Host = pathParts[0];
if (pathParts.Length > 1)
{
Share = pathParts[1];
}
LocalPath = String.Join("/", pathParts.Skip(2));
if (LocalPath.EndsWith("/"))
{
LocalPath = LocalPath.Substring(0, LocalPath.Length - 1);
}
Username = WebUtility.UrlDecode(userAndPwd[0]);
if (Username.Contains("\\"))
{
string[] domainAndUser = Username.Split('\\');
Domain = domainAndUser[0];
Username = domainAndUser[1];
}
else Domain = null;
Password = WebUtility.UrlDecode(userAndPwd[1]);
}
public string ToPath()
{
string domainUser = Username;
if (Domain != null)
{
domainUser = Domain + "\\" + Username;
}
return "smb://" + WebUtility.UrlEncode(domainUser) + ":" + WebUtility.UrlEncode(Password) + "@" + Host +
"/" + Share + "/" + LocalPath;
}
public string GetPathWithoutCredentials()
{
return "smb://" + Host + "/" + Share + "/" + LocalPath;
}
public string GetLocalSmbPath()
{
return LocalPath?.Replace("/", "\\") ?? "";
}
public SmbConnectionInfo GetParent()
{
SmbConnectionInfo parent = new SmbConnectionInfo
{
Host = Host,
Username = Username,
Password = Password,
Domain = Domain,
Share = Share
};
string[] pathParts = LocalPath?.Split('/') ?? [];
if (pathParts.Length > 0)
{
parent.LocalPath = string.Join("/", pathParts.Take(pathParts.Length - 1));
}
else
{
parent.LocalPath = "";
parent.Share = "";
}
return parent;
}
public string Stem()
{
return LocalPath?.Split('/').Last() ?? "";
}
public SmbConnectionInfo GetChild(string childName)
{
SmbConnectionInfo child = new SmbConnectionInfo();
child.Host = Host;
child.Username = Username;
child.Password = Password;
child.Domain = Domain;
if (string.IsNullOrEmpty(Share))
{
child.Share = childName;
}
else
{
child.Share = Share;
var pathPartsList = LocalPath?.Split('/').Where(p => !string.IsNullOrEmpty(p)).ToList() ?? [];
pathPartsList.Add(childName);
child.LocalPath = string.Join("/", pathPartsList);
}
return child;
}
public string ToDisplayString()
{
return "smb://" + Host + "/" + Share + "/" + LocalPath;
}
}
class SmbConnection: IDisposable
{
public SmbConnection(SmbConnectionInfo info)
{
_isLoggedIn = false;
var isConnected = Client.Connect(info.Host, SMBTransportType.DirectTCPTransport);
if (!isConnected)
{
throw new Exception($"Failed to connect to SMB server {info.Host}");
}
var status = Client.Login(info.Domain ?? string.Empty, info.Username, info.Password);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception($"Failed to login to SMB as {info.Username}");
}
_isLoggedIn = true;
if (!string.IsNullOrEmpty(info.Share))
{
FileStore = Client.TreeConnect(info.Share, out status);
}
}
public readonly SMB2Client Client = new SMB2Client();
public readonly ISMBFileStore? FileStore;
private readonly bool _isLoggedIn;
public void Dispose()
{
FileStore?.Disconnect();
if (_isLoggedIn)
Client.Logoff();
if (!Client.IsConnected) return;
Client.Disconnect();
}
}
public Stream OpenFileForRead(IOConnectionInfo ioc)
{
SmbConnectionInfo info = new SmbConnectionInfo(ioc);
using SmbConnection conn = new SmbConnection(info);
if (conn.FileStore == null)
{
throw new Exception($"Failed to read to {info.GetPathWithoutCredentials()}");
}
NTStatus status = conn.FileStore.CreateFile(out var fileHandle, out _, info.GetLocalSmbPath(),
AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.Read,
CreateDisposition.FILE_OPEN,
CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception($"Failed to open file {info.LocalPath}");
}
var stream = new MemoryStream();
long bytesRead = 0;
while (true)
{
status = conn.FileStore.ReadFile(out var data, fileHandle, bytesRead, (int)conn.Client.MaxReadSize);
if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE)
{
throw new Exception("Failed to read from file");
}
if (status == NTStatus.STATUS_END_OF_FILE || data.Length == 0)
{
break;
}
bytesRead += data.Length;
stream.Write(data, 0, data.Length);
}
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
class SmbFileStorageWriteTransaction : IWriteTransaction
{
private bool UseFileTransaction { get; }
private readonly string _path;
private readonly string _uploadPath;
private readonly SmbFileStorage _fileStorage;
private MemoryStream? _memoryStream;
public SmbFileStorageWriteTransaction(string path, SmbFileStorage fileStorage, bool useFileTransaction)
{
UseFileTransaction = useFileTransaction;
_path = path;
if (useFileTransaction)
{
_uploadPath = _path + Guid.NewGuid().ToString().Substring(0, 8) + ".tmp";
}
else
{
_uploadPath = _path;
}
_fileStorage = fileStorage;
_memoryStream = null;
}
public void Dispose()
{
_memoryStream?.Dispose();
}
public Stream OpenFile()
{
_memoryStream = new MemoryStream();
return _memoryStream;
}
public void CommitWrite()
{
_fileStorage.UploadData(new MemoryStream(_memoryStream!.ToArray()), new SmbConnectionInfo(new IOConnectionInfo() { Path = _uploadPath}));
if (UseFileTransaction)
{
SmbConnectionInfo uploadPath = new SmbConnectionInfo(new IOConnectionInfo() { Path = _uploadPath });
SmbConnectionInfo finalPath = new SmbConnectionInfo(new IOConnectionInfo() { Path = _path });
_fileStorage.RenameFile(uploadPath, finalPath);
}
}
}
private void RenameFile(SmbConnectionInfo fromPath, SmbConnectionInfo toPath)
{
using var connection = new SmbConnection(fromPath);
// Open existing file
var status = connection.FileStore!.CreateFile(out var handle, out _, fromPath.GetLocalSmbPath(), AccessMask.MAXIMUM_ALLOWED, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE, null);
if (status != NTStatus.STATUS_SUCCESS)
throw new Exception($"Failed to open {fromPath.LocalPath} for renaming!");
FileRenameInformationType2 renameInfo = new FileRenameInformationType2
{
FileName = toPath.GetLocalSmbPath(),
ReplaceIfExists = true
};
connection.FileStore.SetFileInformation(handle, renameInfo);
connection.FileStore.CloseFile(handle);
}
private void UploadData(Stream data, SmbConnectionInfo uploadPath)
{
using var connection = new SmbConnection(uploadPath);
var status = connection.FileStore!.CreateFile(out var fileHandle, out _, uploadPath.GetLocalSmbPath(), AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_CREATE, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null);
if (status == NTStatus.STATUS_OBJECT_NAME_COLLISION)
status = connection.FileStore!.CreateFile(out fileHandle, out _, uploadPath.GetLocalSmbPath(), AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_OVERWRITE, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception($"Failed to open {uploadPath.LocalPath} for writing!");
}
long writeOffset = 0;
while (data.Position < data.Length)
{
byte[] buffer = new byte[(int)connection.Client.MaxWriteSize];
int bytesRead = data.Read(buffer, 0, buffer.Length);
if (bytesRead < (int)connection.Client.MaxWriteSize)
{
Array.Resize(ref buffer, bytesRead);
}
status = connection.FileStore.WriteFile(out _, fileHandle, writeOffset, buffer);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception("Failed to write to file");
}
writeOffset += bytesRead;
}
}
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
return new SmbFileStorageWriteTransaction(ioc.Path, this, useFileTransaction);
}
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
{
return UrlUtil.StripExtension(
UrlUtil.GetFileName(ioc.Path));
}
public string GetFileExtension(IOConnectionInfo ioc)
{
return UrlUtil.GetExtension(ioc.Path);
}
public bool RequiresCredentials(IOConnectionInfo ioc)
{
return false;
}
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
{
throw new NotImplementedException();
}
private static IEnumerable<FileDescription> ListShares(SmbConnection conn, SmbConnectionInfo parent)
{
foreach (string share in conn.Client.ListShares(out _))
{
yield return new FileDescription()
{
CanRead = true,
CanWrite = true,
DisplayName = share,
IsDirectory = true,
Path = parent.GetChild(share).ToPath()
};
}
}
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
{
List<FileDescription> result = [];
SmbConnectionInfo info = new SmbConnectionInfo(ioc);
using SmbConnection conn = new SmbConnection(info);
if (string.IsNullOrEmpty(info.Share))
{
var shares = ListShares(conn, info).ToList();
return shares;
}
NTStatus status = conn.FileStore!.CreateFile(out var directoryHandle, out _, info.GetLocalSmbPath(), AccessMask.GENERIC_READ, FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
if (status == NTStatus.STATUS_SUCCESS)
{
conn.FileStore.QueryDirectory(out List<QueryDirectoryFileInformation> fileList, directoryHandle, "*", FileInformationClass.FileDirectoryInformation);
foreach (var fi in fileList)
{
var fileDirectoryInformation = fi as FileDirectoryInformation;
if (fileDirectoryInformation == null)
continue;
if (fileDirectoryInformation.FileName is "." or "..")
continue;
var fileDescription = FileDescriptionConvert(ioc, fileDirectoryInformation);
result.Add(fileDescription);
}
conn.FileStore.CloseFile(directoryHandle);
}
return result;
}
private FileDescription FileDescriptionConvert(IOConnectionInfo parentIoc,
FileDirectoryInformation fileDirectoryInformation)
{
FileDescription fileDescription = new FileDescription
{
CanRead = true,
CanWrite = true,
IsDirectory = (fileDirectoryInformation.FileAttributes & FileAttributes.Directory) != 0,
DisplayName = fileDirectoryInformation.FileName
};
fileDescription.Path = CreateFilePath(parentIoc.Path, fileDescription.DisplayName);
fileDescription.LastModified = fileDirectoryInformation.LastWriteTime;
fileDescription.SizeInBytes = fileDirectoryInformation.EndOfFile;
return fileDescription;
}
public FileDescription GetFileDescription(IOConnectionInfo ioc)
{
SmbConnectionInfo info = new SmbConnectionInfo(ioc);
if (string.IsNullOrEmpty(info.Share))
{
return new FileDescription
{
CanRead = true, CanWrite = true,
DisplayName = info.Host,
IsDirectory = true,
Path = info.ToPath()
};
}
using SmbConnection conn = new SmbConnection(info);
NTStatus status = conn.FileStore!.CreateFile(out var directoryHandle, out _, info.GetParent().GetLocalSmbPath(), AccessMask.GENERIC_READ, FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
if (status != NTStatus.STATUS_SUCCESS) throw new Exception($"Failed to query details for {info.LocalPath}");
conn.FileStore.QueryDirectory(out List<QueryDirectoryFileInformation> fileList, directoryHandle, info.Stem(), FileInformationClass.FileDirectoryInformation);
foreach (var fi in fileList)
{
var fileDirectoryInformation = fi as FileDirectoryInformation;
if (fileDirectoryInformation == null)
continue;
if (fileDirectoryInformation.FileName is "." or "..")
continue;
return FileDescriptionConvert(ioc, fileDirectoryInformation);
}
conn.FileStore.CloseFile(directoryHandle);
throw new Exception($"Failed to query details for {info.LocalPath}");
}
public bool RequiresSetup(IOConnectionInfo ioConnection)
{
return false;
}
public string IocToPath(IOConnectionInfo ioc)
{
return ioc.Path;
}
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
{
activity.PerformManualFileSelect(isForSave, requestCode, protocolId);
}
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
bool alwaysReturnSuccess)
{
Intent intent = new Intent();
activity.IocToIntent(intent, ioc);
activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent);
}
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
{
}
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
{
}
public void OnResume(IFileStorageSetupActivity activity)
{
}
public void OnStart(IFileStorageSetupActivity activity)
{
}
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
{
}
public string GetDisplayName(IOConnectionInfo ioc)
{
return new SmbConnectionInfo(ioc).ToDisplayString();
}
public string CreateFilePath(string parent, string newFilename)
{
return new SmbConnectionInfo(new IOConnectionInfo() { Path = parent}).GetChild(newFilename).ToPath();
}
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
{
SmbConnectionInfo connectionInfo = new SmbConnectionInfo(ioc);
return new IOConnectionInfo() { Path = connectionInfo.GetParent().ToPath() };
}
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
{
return new IOConnectionInfo() { Path = CreateFilePath(folderPath.Path, filename)};
}
public bool IsPermanentLocation(IOConnectionInfo ioc)
{
return true;
}
public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null)
{
return false;
}
}
}
#endif

View File

@@ -6,10 +6,12 @@ using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Preferences;
using Android.Runtime;
using Android.Views;
using Android.Widget;
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
using Keepass2android.Javafilestorage;
#endif
using KeePassLib.Serialization;
@@ -19,9 +21,15 @@ namespace keepass2android.Io
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
public class WebDavFileStorage: JavaFileStorage
{
public WebDavFileStorage(IKp2aApp app) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler), app)
{
}
private readonly IKp2aApp _app;
private readonly WebDavStorage baseWebdavStorage;
public WebDavFileStorage(IKp2aApp app, int chunkSize) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler, chunkSize), app)
{
_app = app;
baseWebdavStorage = (WebDavStorage)Jfs;
}
public override IEnumerable<string> SupportedProtocols
{
@@ -75,6 +83,15 @@ namespace keepass2android.Io
}
return base.IocToPath(ioc);
}
}
public override IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
baseWebdavStorage.SetUploadChunkSize(_app.WebDavChunkedUploadSize);
return base.OpenWriteTransaction(ioc, useFileTransaction);
}
}
#endif
}

View File

@@ -1,33 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<TargetFramework>net9.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DefineConstants Condition="'$(Flavor)'=='NoNet'">NO_QR_SCANNER;EXCLUDE_JAVAFILESTORAGE;NoNet</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Folder Include="Resources\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentFTP" Version="51.1.0" />
<PackageReference Include="MegaApiClient" Version="1.10.4" />
<PackageReference Include="Microsoft.Graph" Version="5.68.0" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" />
<PackageReference Include="FluentFTP" Version="52.1.0" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="FluentFTP.GnuTLS" Version="1.0.37" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="MegaApiClient" Version="1.10.4" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="Microsoft.Graph" Version="5.68.0" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="SMBLibrary" Version="1.5.4" Condition="'$(Flavor)'!='NoNet'"/>
<PackageReference Include="Xamarin.AndroidX.Browser" Version="1.8.0" />
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.13.1.5" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.11.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AndroidFileChooserBinding\AndroidFileChooserBinding.csproj" />
<ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj" />
<ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj" Condition="'$(Flavor)'!='NoNet'" />
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj" />
<ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj" />
<ProjectReference Include="..\TwofishCipher\TwofishCipher.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="Io/DropboxFileStorageKeysDummy.cs" />
<Compile Remove="Io/DropboxFileStorageKeysDummy.cs" />
<Content Remove="Io/DropboxFileStorageKeysDummy.cs" />
</ItemGroup>
<ItemGroup Condition="'$(Flavor)'=='NoNet'">
<None Remove="Io/OneDrive2FileStorage.cs" />
<Compile Remove="Io/OneDrive2FileStorage.cs" />
<Content Remove="Io/OneDrive2FileStorage.cs" />
<None Remove="Io/MegaFileStorage.cs" />
<Compile Remove="Io/MegaFileStorage.cs" />
<Content Remove="Io/MegaFileStorage.cs" />
</ItemGroup>
<Import Project="Io/GenerateSecrets.targets" />
<ItemGroup>
<Compile Include="Io/DropboxFileStorage.g.cs" />
</ItemGroup>
</Project>

View File

@@ -4,6 +4,7 @@ using Android.Content;
using Android.OS;
using Android.Widget;
using Java.Net;
using KeePass.Util;
using KeePassLib.Serialization;
using keepass2android.Io;
@@ -94,15 +95,12 @@ namespace keepass2android
}
if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
{
ShowToast(data.GetStringExtra("EXTRA_ERROR_MESSAGE"));
ShowErrorToast(data.GetStringExtra("EXTRA_ERROR_MESSAGE"));
}
if (resultCode == Result.Ok)
{
Kp2aLog.Log("FileSelection returned "+data.DataString);
//TODO: don't try to extract filename if content URI
string filename = IntentToFilename(data);
Kp2aLog.Log("FileSelection returned filename " + filename);
if (filename != null)
{
if (filename.StartsWith("file://"))
@@ -150,7 +148,7 @@ namespace keepass2android
protected abstract void StartFileChooser(string path, int requestCode, bool isForSave);
protected abstract void ShowToast(string text);
protected abstract void ShowErrorToast(string text);
protected abstract void ShowInvalidSchemeMessage(string dataString);
@@ -208,7 +206,7 @@ namespace keepass2android
{
return () =>
{
ShowToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message);
ShowErrorToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + ExceptionUtil.GetErrorMessage(e));
ReturnCancel();
};
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KeePass.Util
{
public class ExceptionUtil
{
public static string GetErrorMessage(Exception e)
{
string errorMessage = e.Message;
if (e is Java.Lang.Exception javaException)
{
try
{
errorMessage = javaException.LocalizedMessage ?? javaException.Message ?? errorMessage;
}
finally
{
}
}
return errorMessage;
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Security.Cryptography;
using System.Text;
using Android.App;
using Android.Content;
using KeePass.Util;
using KeePassLib.Cryptography;
using KeePassLib.Serialization;
using KeePassLib.Utility;
@@ -65,7 +66,7 @@ namespace keepass2android
}
catch (Exception e)
{
Finish(false, e.Message);
Finish(false, ExceptionUtil.GetErrorMessage(e));
}
}

View File

@@ -90,9 +90,12 @@ namespace keepass2android
PwDatabase pwDatabase = new PwDatabase();
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo);
var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo);
PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseFormat);
Kp2aLog.Log("LoadData: Retrieving stream");
Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo);
Kp2aLog.Log("LoadData: GetCurrentFileVersion");
var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo);
Kp2aLog.Log("LoadData: PopulateDatabaseFromStream");
PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseFormat);
LastFileVersion = fileVersion;
status.UpdateSubMessage("");

View File

@@ -10,6 +10,7 @@ using Com.Keepassdroid.Database.Exception;
#endif
using Com.Keepassdroid.Database.Save;
using Java.Util;
using KeePass.Util;
using KeePassLib;
using KeePassLib.Cryptography;
using KeePassLib.Cryptography.Cipher;
@@ -82,15 +83,14 @@ namespace keepass2android
catch (Java.IO.FileNotFoundException e)
{
throw new FileNotFoundException(
e.Message, e);
ExceptionUtil.GetErrorMessage(e), e);
}
catch (Java.Lang.Exception e)
{
if (e.Message == "Invalid key!")
throw new InvalidCompositeKeyException();
throw new Exception(e.LocalizedMessage ??
e.Message ??
e.GetType().Name, e);
throw new Exception(ExceptionUtil.GetErrorMessage(e) ??
e.GetType().Name, e);
}
HashOfLastStream = hashingStream.Hash;

View File

@@ -6,6 +6,7 @@ using Android.App;
using Android.Content;
using KeePassLib.Serialization;
using keepass2android.Io;
using KeePass.Util;
namespace keepass2android
{
@@ -109,7 +110,7 @@ namespace keepass2android
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
Finish(false, e.Message);
Finish(false, ExceptionUtil.GetErrorMessage(e));
}
}

View File

@@ -21,6 +21,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Android.App;
using KeePass.Util;
using keepass2android.database.edit;
using KeePassLib;
using KeePassLib.Keys;
@@ -103,10 +104,10 @@ namespace keepass2android
}
catch (AggregateException e)
{
string message = e.Message;
string message = ExceptionUtil.GetErrorMessage(e);
foreach (var innerException in e.InnerExceptions)
{
message = innerException.Message;
message = ExceptionUtil.GetErrorMessage(innerException);
// Override the message shown with the last (hopefully most recent) inner exception
Kp2aLog.LogUnexpectedError(innerException);
}
@@ -116,14 +117,14 @@ namespace keepass2android
catch (DuplicateUuidsException e)
{
Kp2aLog.Log(e.ToString());
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + e.Message + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + ExceptionUtil.GetErrorMessage(e) + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
return;
}
catch (Exception e)
{
if (!(e is InvalidCompositeKeyException))
Kp2aLog.LogUnexpectedError(e);
Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + (e.Message ?? (e is FileNotFoundException ? _app.GetResourceString(UiStringKey.FileNotFound) : "")), false, Exception);
Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + (ExceptionUtil.GetErrorMessage(e) ?? (e is FileNotFoundException ? _app.GetResourceString(UiStringKey.FileNotFound) : "")), false, Exception);
return;
}
@@ -137,6 +138,7 @@ namespace keepass2android
Database TryLoad(MemoryStream databaseStream)
{
Kp2aLog.Log("LoadDb: Copying database in memory");
//create a copy of the stream so we can try again if we get an exception which indicates we should change parameters
//This is not optimal in terms of (short-time) memory usage but is hard to avoid because the Keepass library closes streams also in case of errors.
//Alternatives would involve increased traffic (if file is on remote) and slower loading times, so this seems to be the best choice.
@@ -145,8 +147,9 @@ namespace keepass2android
workingCopy.Seek(0, SeekOrigin.Begin);
//reset stream if we need to reuse it later:
databaseStream.Seek(0, SeekOrigin.Begin);
//now let's go:
try
Kp2aLog.Log("LoadDb: Ready to start loading");
//now let's go:
try
{
Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent);
Kp2aLog.Log("LoadDB OK");

View File

@@ -130,24 +130,24 @@ namespace keepass2android
if ( !String.IsNullOrEmpty(message) ) {
Kp2aLog.Log("OnFinish message: " + message);
if (makeDialog && ctx != null)
{
try
{
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx);
builder.SetMessage(message)
.SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss())
.Show();
{
try
{
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx);
builder.SetMessage(message)
.SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss())
.Show();
}
catch (Exception)
{
Toast.MakeText(ctx, message, ToastLength.Long).Show();
}
}
{
Toast.MakeText(ctx, message, ToastLength.Long).Show();
}
}
else
Toast.MakeText(ctx ?? Application.Context, message, ToastLength.Long).Show();
}
}
}
}
}

View File

@@ -29,6 +29,7 @@ using KeePassLib.Utility;
using keepass2android.Io;
using Debug = System.Diagnostics.Debug;
using Exception = System.Exception;
using KeePass.Util;
namespace keepass2android
{
@@ -187,7 +188,7 @@ namespace keepass2android
}
*/
Kp2aLog.LogUnexpectedError(e);
Finish(false, e.Message);
Finish(false, ExceptionUtil.GetErrorMessage(e));
return;
}
}
@@ -222,8 +223,8 @@ namespace keepass2android
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
Kp2aLog.Log("Error in worker thread of SaveDb: " + e);
Finish(false, e.Message);
Kp2aLog.Log("Error in worker thread of SaveDb: " + ExceptionUtil.GetErrorMessage(e));
Finish(false, ExceptionUtil.GetErrorMessage(e));
}
});
@@ -233,7 +234,7 @@ namespace keepass2android
{
Kp2aLog.LogUnexpectedError(e);
Kp2aLog.Log("Error starting worker thread of SaveDb: "+e);
Finish(false, e.Message);
Finish(false, ExceptionUtil.GetErrorMessage(e));
}
}

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<TargetFramework>net9.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<TargetFramework>net9.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<TargetFramework>net9.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<TargetFramework>net9.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<TargetFramework>net9.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

25
src/build-scripts/build-java.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
set -e
#unset ANDROID_NDK_HOME ANDROID_NDK
pushd ../java/
pushd JavaFileStorageTest-AS
./gradlew assemble
popd
pushd KP2ASoftkeyboard_AS
./gradlew assemble
popd
pushd Keepass2AndroidPluginSDK2
./gradlew assemble
popd
pushd KP2AKdbLibrary
./gradlew assemble
popd
popd

View File

@@ -0,0 +1,6 @@
#!/bin/bash
set -e
pushd ../java/argon2
ndk-build
popd

View File

@@ -0,0 +1,28 @@
## Setup build environment
* install Android SDK
* install Android NDK
* install dotnet8
```
#from https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/sdk-8.0.407-linux-x64-binaries
wget https://download.visualstudio.microsoft.com/download/pr/9d07577e-f7bc-4d60-838d-f79c50b5c11a/459ef339396783db369e0432d6dc3d7e/dotnet-sdk-8.0.407-linux-x64.tar.gz
mkdir -p $HOME/dotnet && tar zxf dotnet-sdk-8.0.407-linux-x64.tar.gz -C $HOME/dotnet
export DOTNET_ROOT=$HOME/dotnet
export PATH=$PATH:$HOME/dotnet
```
## Build Keepass2Android
```
git clone --recurse-submodules https://github.com/PhilippC/keepass2android.git
cd keepass2android/src/build-scripts
./build-java.sh && ./build-native.sh
cd ..
cd keepass2android-app
ln -s Manifests/AndroidManifest_debug.xml AndroidManifest.xml
dotnet workload restore
dotnet restore
dotnet build
```

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
BASE_DIR="${1}"
for arch_dir in "$BASE_DIR"/android-*/; do
arch=$(basename "$arch_dir")
arch=${arch#android-}
APK_DIR="${arch_dir}publish"
if [[ -d "$APK_DIR" ]]; then
apk_path=$(find "$APK_DIR" -maxdepth 1 -type f -name "*.apk" | head -n1)
if [[ -n "$apk_path" ]]; then
base=$(basename "$apk_path" .apk)
new_path="$APK_DIR/${base}-${arch}.apk"
mv "$apk_path" "$new_path"
echo "Renamed $apk_path to $new_path"
else
echo "No APK found in $APK_DIR"
fi
else
echo "Directory $APK_DIR does not exist"
fi
done

View File

@@ -1,6 +1,7 @@
package keepass2android.javafilestorage;
import android.content.Context;
import java.math.BigInteger;
import android.content.Intent;
import android.net.Uri;
@@ -15,7 +16,10 @@ import com.burgstaller.okhttp.basic.BasicAuthenticator;
import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.DigestAuthenticator;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
@@ -24,6 +28,7 @@ import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@@ -44,23 +49,33 @@ import keepass2android.javafilestorage.webdav.DecoratedTrustManager;
import keepass2android.javafilestorage.webdav.PropfindXmlParser;
import keepass2android.javafilestorage.webdav.WebDavUtil;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.internal.tls.OkHostnameVerifier;
import okio.BufferedSink;
public class WebDavStorage extends JavaFileStorageBase {
private final ICertificateErrorHandler mCertificateErrorHandler;
private Context appContext;
public WebDavStorage(ICertificateErrorHandler certificateErrorHandler)
int chunkSize;
public WebDavStorage(ICertificateErrorHandler certificateErrorHandler, int chunkSize)
{
this.chunkSize = chunkSize;
mCertificateErrorHandler = certificateErrorHandler;
}
public void setUploadChunkSize(int chunkSize)
{
this.chunkSize = chunkSize;
}
public String buildFullPath(String url, String username, String password) throws UnsupportedEncodingException {
String scheme = url.substring(0, url.indexOf("://"));
url = url.substring(scheme.length() + 3);
@@ -181,21 +196,119 @@ public class WebDavStorage extends JavaFileStorageBase {
return client;
}
public void renameOrMoveWebDavResource(String sourcePath, String destinationPath, boolean overwrite) throws Exception {
ConnectionInfo sourceCi = splitStringToConnectionInfo(sourcePath);
ConnectionInfo destinationCi = splitStringToConnectionInfo(destinationPath);
Request.Builder requestBuilder = new Request.Builder()
.url(new URL(sourceCi.URL))
.method("MOVE", null) // "MOVE" is the HTTP method
.header("Destination", destinationCi.URL); // New URI for the resource
// Add Overwrite header
if (overwrite) {
requestBuilder.header("Overwrite", "T"); // 'T' for true
} else {
requestBuilder.header("Overwrite", "F"); // 'F' for false
}
Request request = requestBuilder.build();
Response response = getClient(sourceCi).newCall(request).execute();
// Check the status code
if (response.isSuccessful()) {
// WebDAV MOVE can return 201 (Created) if a new resource was created at dest,
// or 204 (No Content) if moved to a pre-existing destination (e.g., just renamed).
// A 200 OK might also be returned by some servers, though 201/204 are more common.
}
else
{
throw new Exception("Rename/Move failed for " + sourceCi.URL + " to " + destinationCi.URL + ": " + response.code() + " " + response.message());
}
}
public static String generateRandomHexString(int length) {
SecureRandom secureRandom = new SecureRandom();
// Generate enough bytes to ensure we can get the desired number of hex characters.
// Each byte converts to two hex characters.
// For 8 hex characters, we need 4 bytes.
int numBytes = (int) Math.ceil(length / 2.0);
byte[] randomBytes = new byte[numBytes];
secureRandom.nextBytes(randomBytes);
// Convert the byte array to a hexadecimal string
// BigInteger(1, randomBytes) treats the byte array as a positive number.
// toString(16) converts it to a hexadecimal string.
String hexString = new BigInteger(1, randomBytes).toString(16);
// Pad with leading zeros if necessary (e.g., if the generated number is small)
// and then take the first 'length' characters.
// Using String.format to ensure leading zeros if the hexString is shorter.
return String.format("%0" + length + "d", new BigInteger(hexString, 16)).substring(0, length);
}
@Override
public void uploadFile(String path, byte[] data, boolean writeTransactional)
throws Exception {
if (writeTransactional)
{
String randomSuffix = ".tmp." + generateRandomHexString(8);
uploadFile(path + randomSuffix, data, false);
renameOrMoveWebDavResource(path+randomSuffix, path, true);
return;
}
try {
ConnectionInfo ci = splitStringToConnectionInfo(path);
RequestBody requestBody;
if (chunkSize > 0)
{
// use chunked upload
requestBody = new RequestBody() {
@Override
public MediaType contentType() {
return MediaType.parse("application/binary");
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
try (InputStream in = new ByteArrayInputStream(data)) {
byte[] buffer = new byte[chunkSize];
int read;
while ((read = in.read(buffer)) != -1) {
sink.write(buffer, 0, read);
sink.flush();
}
}
}
@Override
public long contentLength() {
return -1; // use chunked upload
}
};
}
else
{
requestBody = new MultipartBody.Builder()
.addPart(RequestBody.create(data, MediaType.parse("application/binary")))
.build();
}
Request request = new Request.Builder()
.url(new URL(ci.URL))
.put(RequestBody.create(MediaType.parse("application/binary"), data))
.put(requestBody)
.build();
//TODO consider writeTransactional
//TODO check for error
Response response = getClient(ci).newCall(request).execute();
checkStatus(response);
@@ -290,7 +403,10 @@ public class WebDavStorage extends JavaFileStorageBase {
e.sizeInBytes = -1;
}
}
e.isDirectory = r.href.endsWith("/");
e.isDirectory = r.href.endsWith("/") || okprop.IsCollection;
e.displayName = okprop.DisplayName;
if (e.displayName == null)
@@ -519,3 +635,4 @@ public class WebDavStorage extends JavaFileStorageBase {
}
}

View File

@@ -57,6 +57,8 @@ public class PropfindXmlParser
public String DisplayName;
public String LastModified;
public String ContentLength;
public boolean IsCollection;
}
public String status;
public Prop prop;
@@ -191,6 +193,8 @@ public class PropfindXmlParser
continue;
}
String name = parser.getName();
String namespace = parser.getNamespace();
android.util.Log.d("PARSE", "4name = " + name);
if (name.equals("getcontentlength"))
@@ -200,6 +204,9 @@ public class PropfindXmlParser
prop.LastModified = readText(parser);
} else if (name.equals("displayname")) {
prop.DisplayName = readText(parser);
} else if (name.equals("resourcetype") && namespace.equals(ns)) {
// We found the <d:resourcetype> tag
prop.IsCollection = readResourceType(parser);
} else {
skip(parser);
}
@@ -208,6 +215,37 @@ public class PropfindXmlParser
return prop;
}
private boolean readResourceType(XmlPullParser parser) throws IOException, XmlPullParserException {
boolean isCollection = false;
parser.require(XmlPullParser.START_TAG, ns, "resourcetype");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
String namespace = parser.getNamespace();
if (name.equals("collection") && namespace.equals(ns)) {
// We found <d:collection/>, so it's a folder
isCollection = true;
// Since <d:collection/> is usually an empty tag, just consume it.
// It might contain text if there's whitespace, so consume text then end tag.
if (parser.next() == XmlPullParser.TEXT) {
parser.nextTag(); // Move to the end tag
}
parser.require(XmlPullParser.END_TAG, ns, "collection");
} else {
// Skip any other unexpected tags within <d:resourcetype>
skip(parser);
}
}
// After reading all children of <d:resourcetype>, ensure we are at its END_TAG
parser.require(XmlPullParser.END_TAG, ns, "resourcetype");
return isCollection;
}
private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
android.util.Log.d("PARSE", "skipping " + parser.getName());

View File

@@ -6,8 +6,7 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:supportsRtl="true">
<activity android:name="com.crocoapps.javafilestoragetest2.MainActivity"
android:exported="true">
<intent-filter>

View File

@@ -548,7 +548,7 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
//storageToTest = new GoogleDriveAppDataFileStorage();
/*storageToTest = new WebDavStorage(new ICertificateErrorHandler() {
storageToTest = new WebDavStorage(new ICertificateErrorHandler() {
@Override
public boolean onValidationError(String error) {
return false;
@@ -558,12 +558,12 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
public boolean alwaysFailOnValidationError() {
return false;
}
});
*/
}, 64*1024);
//storageToTest = new DropboxV2Storage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart);
//storageToTest = new DropboxFileStorage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart);
//storageToTest = new DropboxAppFolderFileStorage(ctx,"ax0268uydp1ya57", "3s86datjhkihwyc", true);
storageToTest = new GoogleDriveFullFileStorage();
// storageToTest = new GoogleDriveFullFileStorage();
return storageToTest;

View File

@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
@@ -216,4 +219,5 @@
android:layout_marginLeft="5dp"
android:text="Resolve" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
@@ -36,4 +39,5 @@
android:hint="@string/hint_pass"
android:importantForAccessibility="no" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -56,15 +56,15 @@
<string name="afc_title_sort_by">Ordina per…</string>
<string name="afc_yesterday">Ieri</string>
<plurals name="afc_title_choose_directories">
<item quantity="one">Scegli la cartella&#8230;</item>
<item quantity="other">Scegli le cartelle&#8230;</item>
<item quantity="one">Scegli cartella...</item>
<item quantity="other">Scegli le cartelle...</item>
</plurals>
<plurals name="afc_title_choose_files">
<item quantity="one">Scegli il file&#8230;</item>
<item quantity="other">Scegli i file&#8230;</item>
<item quantity="one">Scegli il file</item>
<item quantity="other">Scegli i file</item>
</plurals>
<plurals name="afc_title_choose_files_directories">
<item quantity="one">Scegli file/ cartella&#8230;</item>
<item quantity="other">Scegli file/ cartelle&#8230;</item>
<item quantity="one">Scegli file/cartella</item>
<item quantity="other">Scegli file/cartelle</item>
</plurals>
</resources>

View File

@@ -55,4 +55,19 @@
<string name="afc_title_size">Dimensiune</string>
<string name="afc_title_sort_by">Sortează după…</string>
<string name="afc_yesterday">Ieri</string>
<plurals name="afc_title_choose_directories">
<item quantity="one">Alege dosarul…</item>
<item quantity="few">Alege dosarele…</item>
<item quantity="other">Alege dosarele…</item>
</plurals>
<plurals name="afc_title_choose_files">
<item quantity="one">Alege fișierul…</item>
<item quantity="few">Alege fișierele…</item>
<item quantity="other">Alege fișierele…</item>
</plurals>
<plurals name="afc_title_choose_files_directories">
<item quantity="one">Alege fișierul/dosarul…</item>
<item quantity="few">Alege fișierele/dosarele…</item>
<item quantity="other">Alege fișierele/dosarele…</item>
</plurals>
</resources>

View File

@@ -59,7 +59,7 @@ namespace keepass2android
}
catch (ActivityNotFoundException)
{
Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show();
App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler, MessageSeverity.Error);
}
};
@@ -71,7 +71,7 @@ namespace keepass2android
}
catch (ActivityNotFoundException)
{
Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show();
App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler, MessageSeverity.Error);
}
};
FindViewById(Resource.Id.translate).Click += delegate
@@ -82,7 +82,7 @@ namespace keepass2android
}
catch (ActivityNotFoundException)
{
Toast.MakeText(Context, Resource.String.no_url_handler, ToastLength.Long).Show();
App.Kp2a.ShowMessage(Context, Resource.String.no_url_handler, MessageSeverity.Error);
}
}; FindViewById(Resource.Id.donate).Click += delegate
{

View File

@@ -14,7 +14,7 @@ using keepass2android;
namespace keepass2android
{
[Activity(Label = AppNames.AppName)]
[Activity(Label = AppNames.AppName, Theme = "@style/Kp2aTheme_BlueNoActionBar")]
public class AppKilledInfo : Activity, IDialogInterfaceOnDismissListener
{
protected override void OnCreate(Bundle bundle)

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,12 @@ namespace keepass2android
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx);
builder.SetTitle(ctx.GetString(Resource.String.ChangeLog_title));
List<string> changeLog = new List<string>{
#if !NoNet
BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_14_net}, "1.14"),
#endif
BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_13}, "1.13"),
BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_12
#if !NoNet
,Resource.Array.ChangeLog_1_12_net

View File

@@ -183,7 +183,7 @@ namespace keepass2android
// Verify that a password or keyfile is set
if (password.Length == 0 && !keyfileCheckbox.Checked)
{
Toast.MakeText(this, Resource.String.error_nopass, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, Resource.String.error_nopass, MessageSeverity.Error);
return;
}
@@ -207,7 +207,7 @@ namespace keepass2android
}
catch (Exception)
{
Toast.MakeText(this, Resource.String.error_adding_keyfile, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, Resource.String.error_adding_keyfile, MessageSeverity.Error);
return;
}
}
@@ -235,7 +235,7 @@ namespace keepass2android
if (! pass.Equals(confpass))
{
// Passwords do not match
Toast.MakeText(this, Resource.String.error_pass_match, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, Resource.String.error_pass_match, MessageSeverity.Error);
return false;
}
return true;

View File

@@ -28,7 +28,7 @@ namespace keepass2android
}
catch (Exception e)
{
Finish(false, e.Message);
Finish(false, Util.GetErrorMessage(e));
}
}

View File

@@ -19,7 +19,7 @@ using keepass2android.services.AutofillBase;
namespace keepass2android
{
[Activity(Label = "DisableAutofillForQueryActivity")]
[Activity(Label = "DisableAutofillForQueryActivity", Theme = "@style/Kp2aTheme_ActionBar")]
public class DisableAutofillForQueryActivity : Activity
{
public IAutofillIntentBuilder IntentBuilder = new Kp2aAutofillIntentBuilder();
@@ -41,7 +41,7 @@ namespace keepass2android
string requestedUrl = Intent.GetStringExtra(ChooseForAutofillActivityBase.ExtraQueryString);
if (requestedUrl == null)
{
Toast.MakeText(this, "Cannot execute query for null.", ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, "Cannot execute query for null.", MessageSeverity.Error);
RestartApp();
return;
}
@@ -63,8 +63,6 @@ namespace keepass2android
prefs.Edit().PutStringSet("AutoFillDisabledQueries", disabledValues).Commit();
bool isManual = Intent.GetBooleanExtra(ChooseForAutofillActivityBase.ExtraIsManualRequest, false);
Intent reply = new Intent();
FillResponse.Builder builder = new FillResponse.Builder();
AssistStructure structure = (AssistStructure)Intent.GetParcelableExtra(AutofillManager.ExtraAssistStructure);
@@ -77,7 +75,7 @@ namespace keepass2android
StructureParser parser = new StructureParser(this, structure);
try
{
parser.ParseForFill(isManual);
parser.ParseForFill();
}
catch (Java.Lang.SecurityException e)

View File

@@ -78,7 +78,7 @@ namespace keepass2android
var task = new EntryActivity.WriteBinaryTask(_activity, App.Kp2a, new ActionOnFinish(_activity, (success, message, activity) =>
{
if (!success)
Toast.MakeText(activity, message, ToastLength.Long).Show();
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
}
), ((EntryActivity)_activity).Entry.Binaries.Get(_binaryToSave), ioc);
ProgressTask pt = new ProgressTask(App.Kp2a, _activity, task);
@@ -107,8 +107,8 @@ namespace keepass2android
public const int requestCodeBinaryFilename = 42376;
public const int requestCodeSelFileStorageForWriteAttachment = 42377;
protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);
public static void Launch(Activity act, PwEntry pw, int pos, AppTask appTask, ActivityFlags? flags = null, int historyIndex=-1)
{
@@ -767,9 +767,9 @@ namespace keepass2android
if (parent == null || (parent.Exists() && !parent.IsDirectory))
{
Toast.MakeText(this,
App.Kp2a.ShowMessage(this,
Resource.String.error_invalid_path,
ToastLength.Long).Show();
MessageSeverity.Error);
return null;
}
@@ -778,9 +778,9 @@ namespace keepass2android
// Create parent directory
if (!parent.Mkdirs())
{
Toast.MakeText(this,
App.Kp2a.ShowMessage(this,
Resource.String.error_could_not_create_parent,
ToastLength.Long).Show();
MessageSeverity.Error);
return null;
}
@@ -794,18 +794,18 @@ namespace keepass2android
}
catch (Exception exWrite)
{
Toast.MakeText(this,
App.Kp2a.ShowMessage(this,
GetString(Resource.String.SaveAttachment_Failed, new Java.Lang.Object[] {filename})
+ exWrite.Message, ToastLength.Long).Show();
+ Util.GetErrorMessage(exWrite), MessageSeverity.Error);
return null;
}
finally
{
MemUtil.ZeroByteArray(pbData);
}
Toast.MakeText(this,
App.Kp2a.ShowMessage(this,
GetString(Resource.String.SaveAttachment_doneMessage, new Java.Lang.Object[] {filename}),
ToastLength.Short).Show();
MessageSeverity.Info);
return Uri.Parse("content://" + AttachmentContentProvider.Authority + "/"
+ filename);
}
@@ -838,7 +838,7 @@ namespace keepass2android
catch (ActivityNotFoundException)
{
//ignore
Toast.MakeText(this, "Couldn't open file", ToastLength.Short).Show();
App.Kp2a.ShowMessage(this, "Couldn't open file", MessageSeverity.Error);
}
}
@@ -1305,7 +1305,7 @@ namespace keepass2android
}
catch (Exception ex)
{
Finish(false, ex.Message);
Finish(false, Util.GetErrorMessage(ex));
}
@@ -1546,10 +1546,10 @@ namespace keepass2android
string url = _stringViews[urlFieldKey].Text;
if (url == null) return false;
// Default http:// if no protocol specified
// Default https:// if no protocol specified
if ((!url.Contains(":") || (url.StartsWith("www."))))
{
url = "http://" + url;
url = "https://" + url;
}
try
@@ -1558,7 +1558,7 @@ namespace keepass2android
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, Resource.String.no_url_handler, MessageSeverity.Error);
}
return true;
}

View File

@@ -37,8 +37,10 @@ using System.Net;
using System.Text;
using Android.Content.Res;
using Android.Database;
#if !NO_QR_SCANNER
using Android.Gms.Common;
using Android.Gms.Tasks;
#endif
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Runtime;
@@ -55,19 +57,20 @@ using Object = Java.Lang.Object;
using Uri = Android.Net.Uri;
using Resource = keepass2android.Resource;
using Google.Android.Material.TextField;
#if !NO_QR_SCANNER
using Xamarin.Google.MLKit.Vision.Barcode.Common;
using Xamarin.Google.MLKit.Vision.CodeScanner;
#endif
using Console = System.Console;
using Task = Android.Gms.Tasks.Task;
namespace keepass2android
{
[Activity(Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden, Theme = "@style/Kp2aTheme_ActionBar")]
public class EntryEditActivity : LockCloseActivity {
public const String KeyEntry = "entry";
protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);
public const String KeyEntry = "entry";
public const String KeyParent = "parent";
public const String KeyTemplateUuid = "KeyTemplateUuid";
@@ -331,13 +334,18 @@ namespace keepass2android
}
protected override void OnStart()
private bool hasRequestedKeyboardActivation = false;
protected override void OnStart()
{
base.OnStart();
if (PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false))
.GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false)
&& (!hasRequestedKeyboardActivation))
{
CopyToClipboardService.ActivateKeyboard(this);
//only try this once. if the user clicks cancel, we don't want to ask again
// (it may happen that the activity is restarted because of the NewTask flag immediately after the dialog)
hasRequestedKeyboardActivation = true;
CopyToClipboardService.ActivateKeyboard(this);
}
}
@@ -715,7 +723,7 @@ namespace keepass2android
}
catch(Exception exAttach)
{
Toast.MakeText(this, GetString(Resource.String.AttachFailed)+" "+exAttach.Message, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, GetString(Resource.String.AttachFailed)+" "+ Util.GetErrorMessage(exAttach), MessageSeverity.Error);
}
State.EntryModified = true;
PopulateBinaries();
@@ -833,7 +841,7 @@ namespace keepass2android
string s = Util.GetFilenameFromInternalFileChooser(data, this);
if (s == null)
{
Toast.MakeText(this, "No URI retrieved.", ToastLength.Short).Show();
App.Kp2a.ShowMessage(this, "No URI retrieved.", MessageSeverity.Error);
return;
}
uri = Uri.Parse(s);
@@ -1139,7 +1147,7 @@ namespace keepass2android
}
else
{
Toast.MakeText(this, "did not find target field", ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, "did not find target field", MessageSeverity.Error);
}
@@ -1153,12 +1161,15 @@ namespace keepass2android
{
dlgView.FindViewById(Resource.Id.totp_custom_settings_group).Visibility = args.IsChecked ? ViewStates.Visible : ViewStates.Gone;
};
dlgView.FindViewById<Button>(Resource.Id.totp_scan).Click += async (object o, EventArgs args) =>
#if NO_QR_SCANNER
dlgView.FindViewById<Button>(Resource.Id.totp_scan).Visibility = ViewStates.Gone;
#else
dlgView.FindViewById<Button>(Resource.Id.totp_scan).Click += async (object o, EventArgs args) =>
{
if (GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this) != ConnectionResult.Success)
{
Toast.MakeText(this, Resource.String.qr_scanning_error_no_google_play_services, ToastLength.Long);
App.Kp2a.ShowMessage(this, Resource.String.qr_scanning_error_no_google_play_services,
MessageSeverity.Error);
return;
}
@@ -1178,16 +1189,17 @@ namespace keepass2android
}
else
{
Toast.MakeText(this, "Scanned code should contain an otpauth:// text.", ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, "Scanned code should contain an otpauth:// text.", MessageSeverity.Warning);
}
}))
.AddOnFailureListener(new FailureListener((e) =>
{
Console.WriteLine($"Scan failed: {e.Message}");
Console.WriteLine($"Scan failed: {Util.GetErrorMessage(e)}");
}));
};
#endif
//copy values from entry into dialog
View ees = (View)sender.Parent;
@@ -1503,7 +1515,7 @@ namespace keepass2android
// Require title
String title = Util.GetEditText(this, Resource.Id.entry_title);
if ( title.Length == 0 ) {
Toast.MakeText(this, Resource.String.error_title_required, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, Resource.String.error_title_required, MessageSeverity.Error);
return false;
}
@@ -1513,7 +1525,7 @@ namespace keepass2android
DateTime newExpiry = new DateTime();
if ((State.Entry.Expires) && (!DateTime.TryParse( Util.GetEditText(this,Resource.Id.entry_expires), out newExpiry)))
{
Toast.MakeText(this, Resource.String.error_invalid_expiry_date, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, Resource.String.error_invalid_expiry_date, MessageSeverity.Error);
return false;
}
State.Entry.ExpiryTime = newExpiry.ToUniversalTime();
@@ -1527,13 +1539,13 @@ namespace keepass2android
string key = keyView.Text;
if (String.IsNullOrEmpty(key)) {
Toast.MakeText(this, Resource.String.error_string_key, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, Resource.String.error_string_key, MessageSeverity.Error);
return false;
}
if (allKeys.Contains(key))
{
Toast.MakeText(this, GetString(Resource.String.error_string_duplicate_key, new Object[]{key}), ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, GetString(Resource.String.error_string_duplicate_key, new Object[]{key}), MessageSeverity.Error);
return false;
}
@@ -1566,6 +1578,7 @@ namespace keepass2android
}
}
#if !NO_QR_SCANNER
public class SuccessListener : Object, IOnSuccessListener
{
private readonly Action<Barcode> _onSuccess;
@@ -1595,8 +1608,9 @@ namespace keepass2android
_onFailure?.Invoke(e);
}
}
#endif
public class DefaultEdit : EditModeBase
public class DefaultEdit : EditModeBase
{
}

View File

@@ -29,9 +29,9 @@ namespace keepass2android
var exportDb = new ExportDatabaseActivity.ExportDb(_activity, App.Kp2a, new ActionOnFinish(_activity, (success, message, activity) =>
{
if (!success)
Toast.MakeText(activity, message, ToastLength.Long).Show();
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
else
Toast.MakeText(activity, _activity.GetString(Resource.String.export_database_successful), ToastLength.Long).Show();
App.Kp2a.ShowMessage(activity, _activity.GetString(Resource.String.export_database_successful), MessageSeverity.Info);
activity.Finish();
}
), _ffp, ioc);
@@ -140,7 +140,7 @@ namespace keepass2android
}
catch (Exception ex)
{
Finish(false, ex.Message);
Finish(false, Util.GetErrorMessage(ex));
}

View File

@@ -107,7 +107,7 @@ namespace keepass2android
{
if (!success)
{
Toast.MakeText(activity, messageOrFilename, ToastLength.Long).Show();
App.Kp2a.ShowMessage(activity, messageOrFilename, MessageSeverity.Error);
return;
}
SaveFile(new IOConnectionInfo { Path = FileSelectHelper.ConvertFilenameToIocPath(messageOrFilename) });

View File

@@ -4,12 +4,15 @@ using System.Linq;
using System.Net;
#if !NoNet
using FluentFTP;
using static Kp2aBusinessLogic.Io.SmbFileStorage;
#endif
using System.Text;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.OS;
using Android.Preferences;
using Android.Runtime;
using Android.Views;
using Android.Widget;
@@ -23,6 +26,7 @@ using Keepass2android.Javafilestorage;
using KeePassLib.Serialization;
using KeePassLib.Utility;
namespace keepass2android
{
public class FileSelectHelper
@@ -115,6 +119,7 @@ namespace keepass2android
string keyContent = keyContentTxt.Text;
string toastMsg = null;
MessageSeverity severity = MessageSeverity.Info;
if (!string.IsNullOrEmpty(keyName) && !string.IsNullOrEmpty(keyContent))
{
try
@@ -127,8 +132,10 @@ namespace keepass2android
catch (Exception e)
{
toastMsg = ctx.GetString(Resource.String.private_key_save_failed,
new Java.Lang.Object[] { e.Message });
}
new Java.Lang.Object[] { Util.GetErrorMessage(e)});
severity = MessageSeverity.Error;
}
}
else
{
@@ -136,7 +143,7 @@ namespace keepass2android
}
if (toastMsg!= null) {
Toast.MakeText(_activity, toastMsg, ToastLength.Long).Show();
App.Kp2a.ShowMessage(_activity, toastMsg, severity);
}
UpdatePrivateKeyNames(keyNamesAdapter, fileStorage, ctx);
@@ -153,7 +160,7 @@ namespace keepass2android
int msgId = deleted ? Resource.String.private_key_delete : Resource.String.private_key_delete_failed;
string msg = ctx.GetString(msgId, new Java.Lang.Object[] { keyName });
Toast.MakeText(_activity, msg, ToastLength.Long).Show();
App.Kp2a.ShowMessage(_activity, msg, deleted ? MessageSeverity.Info :MessageSeverity.Error);
UpdatePrivateKeyNames(keyNamesAdapter, fileStorage, ctx);
keySpinner.SetSelection(SftpKeySpinnerCreateNewIdx);
@@ -271,7 +278,8 @@ namespace keepass2android
builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
builder.SetTitle(activity.GetString(Resource.String.enter_sftp_login_title));
Dialog dialog = builder.Create();
builder.SetCancelable(false);
Dialog dialog = builder.Create();
dialog.Show();
#endif
@@ -316,7 +324,7 @@ namespace keepass2android
View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.httpcredentials, null);
if (!defaultPath.EndsWith(_schemeSeparator))
{
var webdavStorage = new Keepass2android.Javafilestorage.WebDavStorage(App.Kp2a.CertificateErrorHandler);
var webdavStorage = CreateWebdavStorage(activity);
var connInfo = webdavStorage.SplitStringToConnectionInfo(defaultPath);
dlgContents.FindViewById<EditText>(Resource.Id.http_url).Text = connInfo.Url;
dlgContents.FindViewById<EditText>(Resource.Id.http_user).Text = connInfo.Username;
@@ -336,7 +344,7 @@ namespace keepass2android
string scheme = defaultPath.Substring(0, defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
if (host.Contains(_schemeSeparator) == false)
host = scheme + _schemeSeparator + host;
string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(host, user,
string httpPath = CreateWebdavStorage(activity).BuildFullPath(host, user,
password);
onStartBrowse(httpPath);
});
@@ -344,13 +352,61 @@ namespace keepass2android
builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
builder.SetTitle(activity.GetString(Resource.String.enter_http_login_title));
Dialog dialog = builder.Create();
builder.SetCancelable(false);
Dialog dialog = builder.Create();
dialog.Show();
#endif
}
private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
private void ShowSmbDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
{
#if !EXCLUDE_JAVAFILESTORAGE && !NoNet
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.smbcredentials, null);
if (!defaultPath.EndsWith(_schemeSeparator))
{
SmbConnectionInfo ci = new SmbConnectionInfo(new IOConnectionInfo() { Path = defaultPath });
dlgContents.FindViewById<EditText>(Resource.Id.smb_url).Text = ci.GetPathWithoutCredentials();
dlgContents.FindViewById<EditText>(Resource.Id.smb_domain).Text = ci.Domain;
dlgContents.FindViewById<EditText>(Resource.Id.smb_user).Text = ci.Username;
dlgContents.FindViewById<EditText>(Resource.Id.smb_password).Text = ci.Password;
}
builder.SetView(dlgContents);
builder.SetPositiveButton(Android.Resource.String.Ok,
(sender, args) =>
{
string url = dlgContents.FindViewById<EditText>(Resource.Id.smb_url).Text;
string user = dlgContents.FindViewById<EditText>(Resource.Id.smb_user).Text;
string password = dlgContents.FindViewById<EditText>(Resource.Id.smb_password).Text;
string domain = dlgContents.FindViewById<EditText>(Resource.Id.smb_domain).Text;
string fullPath = SmbConnectionInfo.FromUrlAndCredentials(url, user, password, domain).ToPath();
onStartBrowse(fullPath);
});
builder.SetCancelable(false);
EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel());
builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
builder.SetTitle(activity.GetString(Resource.String.enter_smb_login_title));
Dialog dialog = builder.Create();
dialog.Show();
#endif
}
#if !NoNet
private static WebDavStorage CreateWebdavStorage(Activity activity)
{
return new WebDavStorage(App.Kp2a.CertificateErrorHandler, App.Kp2a.WebDavChunkedUploadSize);
}
#endif
private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
{
#if !NoNet
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
@@ -400,7 +456,8 @@ namespace keepass2android
builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
builder.SetTitle(activity.GetString(Resource.String.enter_ftp_login_title));
Dialog dialog = builder.Create();
builder.SetCancelable(false);
Dialog dialog = builder.Create();
dialog.Show();
#endif
@@ -460,7 +517,8 @@ namespace keepass2android
builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
builder.SetTitle(activity.GetString(Resource.String.enter_mega_login_title));
Dialog dialog = builder.Create();
builder.SetCancelable(false);
Dialog dialog = builder.Create();
dialog.Show();
#endif
@@ -476,7 +534,9 @@ namespace keepass2android
ShowFtpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath);
else if ((defaultPath.StartsWith("http://")) || (defaultPath.StartsWith("https://")))
ShowHttpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath);
else if (defaultPath.StartsWith("owncloud://"))
else if ((defaultPath.StartsWith("smb://")))
ShowSmbDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath);
else if (defaultPath.StartsWith("owncloud://"))
ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "owncloud");
else if (defaultPath.StartsWith("nextcloud://"))
ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "nextcloud");
@@ -515,7 +575,7 @@ namespace keepass2android
string scheme = defaultPath.Substring(0,defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
if (host.Contains(_schemeSeparator) == false)
host = scheme + _schemeSeparator + host;
string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host, subtype == "owncloud" ? WebDavFileStorage.owncloudPrefix : WebDavFileStorage.nextcloudPrefix), user,
string httpPath = CreateWebdavStorage(activity).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host, subtype == "owncloud" ? WebDavFileStorage.owncloudPrefix : WebDavFileStorage.nextcloudPrefix), user,
password);
onStartBrowse(httpPath);
});
@@ -523,7 +583,8 @@ namespace keepass2android
builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
builder.SetTitle(activity.GetString(subtype == "owncloud" ? Resource.String.enter_owncloud_login_title : Resource.String.enter_nextcloud_login_title));
Dialog dialog = builder.Create();
builder.SetCancelable(false);
Dialog dialog = builder.Create();
dlgContents.FindViewById<EditText>(Resource.Id.owncloud_url).SetHint(subtype == "owncloud" ? Resource.String.hint_owncloud_url : Resource.String.hint_nextcloud_url);
dialog.Show();
#endif
@@ -581,9 +642,9 @@ namespace keepass2android
// Make sure file name exists
if (filename.Length == 0)
{
Toast.MakeText(_activity,
App.Kp2a.ShowMessage(_activity,
Resource.String.error_filename_required,
ToastLength.Long).Show();
MessageSeverity.Error);
return false;
}
@@ -604,9 +665,9 @@ namespace keepass2android
}
catch (NoFileStorageFoundException)
{
Toast.MakeText(_activity,
App.Kp2a.ShowMessage(_activity,
"Unexpected scheme in "+filename,
ToastLength.Long).Show();
MessageSeverity.Error);
return false;
}
@@ -620,9 +681,9 @@ namespace keepass2android
if (parent == null || (parent.Exists() && !parent.IsDirectory))
{
Toast.MakeText(_activity,
App.Kp2a.ShowMessage(_activity,
Resource.String.error_invalid_path,
ToastLength.Long).Show();
MessageSeverity.Error);
return false;
}
@@ -631,9 +692,9 @@ namespace keepass2android
// Create parent dircetory
if (!parent.Mkdirs())
{
Toast.MakeText(_activity,
App.Kp2a.ShowMessage(_activity,
Resource.String.error_could_not_create_parent,
ToastLength.Long).Show();
MessageSeverity.Error);
return false;
}
@@ -643,11 +704,11 @@ namespace keepass2android
}
catch (Java.IO.IOException ex)
{
Toast.MakeText(
App.Kp2a.ShowMessage(
_activity,
_activity.GetText(Resource.String.error_file_not_create) + " "
+ ex.LocalizedMessage,
ToastLength.Long).Show();
MessageSeverity.Error);
return false;
}
@@ -700,7 +761,7 @@ namespace keepass2android
_activity.StartActivityForResult(i, _requestCode);
#else
Toast.MakeText(LocaleManager.LocalizedAppContext, "File chooser is excluded!", ToastLength.Long).Show();
App.Kp2a.ShowMessage(LocaleManager.LocalizedAppContext, "File chooser is excluded!", MessageSeverity.Error);
#endif
return true;
}
@@ -782,7 +843,7 @@ namespace keepass2android
{
if (!success)
{
Toast.MakeText(newActivity, messageOrFilename, ToastLength.Long).Show();
App.Kp2a.ShowMessage(newActivity, messageOrFilename, MessageSeverity.Error);
return;
}
var ioc = new IOConnectionInfo { Path = ConvertFilenameToIocPath(messageOrFilename) };

View File

@@ -251,7 +251,7 @@ namespace keepass2android
catch (Exception e)
{
CheckCurrentRadioButton();
Toast.MakeText(this, e.ToString(), ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, e.ToString(), MessageSeverity.Error);
FindViewById(Resource.Id.radio_buttons).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.fingerprint_auth_container).Visibility = ViewStates.Gone;
}

View File

@@ -23,6 +23,7 @@ using Android.App;
using Android.App.Admin;
using Android.Content;
using Android.Content.PM;
using Android.Content.Res;
using Android.Graphics;
using Android.OS;
using Android.Preferences;
@@ -66,10 +67,15 @@ namespace keepass2android
Resource.Id.cb_exclude_lookalike
};
PasswordFont _passwordFont = new PasswordFont();
private static object _popularPasswordsLock = new object();
private static bool _popularPasswordsInitialized = false;
private ActivityDesign _design;
private ActivityDesign _design;
public GeneratePasswordActivity()
{
_design = new ActivityDesign(this);
@@ -139,12 +145,14 @@ namespace keepass2android
protected override void OnCreate(Bundle savedInstanceState) {
_design.ApplyTheme();
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.generate_password);
SetResult(KeePass.ExitNormal);
var prefs = GetPreferences(FileCreationMode.Private);
new Util.InsetListener(FindViewById(Resource.Id.main_container)).Apply();
var prefs = GetPreferences(FileCreationMode.Private);
@@ -302,6 +310,10 @@ namespace keepass2android
EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit);
txtPasswordToSet.TextChanged += (sender, args) =>
{
Task.Run(() => UpdatePasswordStrengthEstimate(txtPasswordToSet.Text));
};
_passwordFont.ApplyTo(txtPasswordToSet);
@@ -467,51 +479,76 @@ namespace keepass2android
return;
String password = "";
uint passwordBits = 0;
Task.Run(() =>
{
password = GeneratePassword();
passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray());
RunOnUiThread(() =>
{
EditText txtPassword = (EditText)FindViewById(Resource.Id.password_edit);
txtPassword.Text = password;
var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength);
progressBar.Progress = (int)passwordBits;
progressBar.Max = 128;
Color color = new Color(196, 63, 49);
if (passwordBits > 40)
{
color = new Color(219, 152, 55);
}
if (passwordBits > 64)
{
color = new Color(96, 138, 38);
}
if (passwordBits > 100)
{
color = new Color(31, 128, 31);
}
progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color,
PorterDuff.Mode.SrcIn));
FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits";
UpdateProfileSpinnerSelection();
});
});
}
private void UpdatePasswordStrengthEstimate(string password)
{
lock (_popularPasswordsLock)
{
if (!_popularPasswordsInitialized)
{
using (StreamReader sr = new StreamReader(Assets.Open("MostPopularPasswords.txt")))
{
var bytes = default(byte[]);
using (var memstream = new MemoryStream())
{
sr.BaseStream.CopyTo(memstream);
bytes = memstream.ToArray();
}
PopularPasswords.Add(bytes, false);
}
}
}
uint passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray());
RunOnUiThread(() =>
{
var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength);
progressBar.Progress = (int)passwordBits;
progressBar.Max = 128;
Color color = new Color(196, 63, 49);
if (passwordBits > 40)
{
color = new Color(219, 152, 55);
}
if (passwordBits > 64)
{
color = new Color(96, 138, 38);
}
if (passwordBits > 100)
{
color = new Color(31, 128, 31);
}
progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color,
PorterDuff.Mode.SrcIn));
FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits";
});
}
private void UpdateProfileSpinnerSelection()
{
int? lastUsedIndex = _profiles.TryFindLastUsedProfileIndex();
@@ -543,7 +580,7 @@ namespace keepass2android
}
catch (Exception e)
{
Toast.MakeText(this, e.Message, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, Util.GetErrorMessage(e), MessageSeverity.Error);
}
return password;

View File

@@ -56,6 +56,8 @@ namespace keepass2android
public const int RequestCodeActivateRealSearch = 12366;
protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);
static readonly Dictionary<int /*resource id*/, int /*prio*/> bottomBarElementsPriority = new Dictionary<int, int>()
{
{ Resource.Id.cancel_insert_element, 20 },
@@ -892,9 +894,14 @@ namespace keepass2android
RegisterInfoTextDisplay(
"DbReadOnly"); //this ensures that we don't show the general info texts too soon
FindViewById<TextView>(Resource.Id.dbreadonly_infotext_text).Text =
(GetString(Resource.String.FileReadOnlyMessagePre) + " " +
App.Kp2a.GetResourceString(reason.Result));
var infotext_view = FindViewById<TextView>(Resource.Id.dbreadonly_infotext_text);
if (infotext_view != null)
{
infotext_view.Text =
(GetString(Resource.String.FileReadOnlyMessagePre) + " " +
App.Kp2a.GetResourceString(reason.Result));
}
}
}
UpdateBottomBarElementVisibility(Resource.Id.dbreadonly_infotext, canShow);
@@ -927,7 +934,7 @@ namespace keepass2android
{
((GroupBaseActivity)activity)?.StopMovingElements();
if (!String.IsNullOrEmpty(message))
Toast.MakeText(activity, message, ToastLength.Long).Show();
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
}));
var progressTask = new ProgressTask(App.Kp2a, this, moveElement);
progressTask.Run();
@@ -1328,7 +1335,7 @@ namespace keepass2android
{
Handler.Post(() =>
{
Toast.MakeText(ActiveActivity ?? LocaleManager.LocalizedAppContext, "Unrecoverable error: " + Message, ToastLength.Long).Show();
App.Kp2a.ShowMessage(ActiveActivity ?? LocaleManager.LocalizedAppContext, "Unrecoverable error: " + Message, MessageSeverity.Error);
});
App.Kp2a.Lock(false);

View File

@@ -111,9 +111,10 @@ namespace keepass2android
SetResult (Result.Ok, intent);
Finish ();
} else {
Toast.MakeText (this, Resource.String.error_no_name, ToastLength.Long).Show ();
}
} else
{
App.Kp2a.ShowMessage(this, Resource.String.error_no_name, MessageSeverity.Error);
}
};
if (Intent.HasExtra(KeyGroupUuid))

View File

@@ -316,7 +316,7 @@ namespace keepass2android
try { ck.AddUserKey(new KcpKeyFile(strAbs)); }
catch (InvalidOperationException)
{
Toast.MakeText(LocaleManager.LocalizedAppContext,Resource.String.error_adding_keyfile,ToastLength.Long).Show();
App.Kp2a.ShowMessage(LocaleManager.LocalizedAppContext,Resource.String.error_adding_keyfile, MessageSeverity.Error);
return false;
}
catch (Exception) { throw; }

View File

@@ -146,7 +146,16 @@ namespace KeeChallenge
{
using (CryptoStream csDecrypt = (CryptoStream)aes.DecryptStream(msDecrypt, key, inf.IV))
{
csDecrypt.Read(secret, 0, secret.Length);
//read the secret from the stream
int totalBytesRead = 0;
int bytesRead = csDecrypt.Read(secret, totalBytesRead, secret.Length - totalBytesRead);
while (bytesRead > 0 && totalBytesRead < secret.Length)
{
totalBytesRead += bytesRead;
bytesRead = csDecrypt.Read(secret, totalBytesRead, secret.Length - totalBytesRead);
}
csDecrypt.Close();
}
msDecrypt.Close();

View File

@@ -21,8 +21,10 @@ using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Google.Android.Material.Dialog;
using keepass2android;
using keepass2android.Utils;
namespace keepass2android
{
@@ -76,19 +78,34 @@ namespace keepass2android
base.OnPause();
TimeoutHelper.Pause(this);
}
App.Kp2a.MessagePresenter = new NonePresenter();
}
protected override void OnDestroy()
{
base.OnDestroy();
GC.Collect();
}
protected override void OnResume() {
base.OnResume();
TimeoutHelper.Resume(this);
}
protected override void OnResume()
{
base.OnResume();
TimeoutHelper.Resume(this);
var snackbarAnchorView = SnackbarAnchorView;
if (snackbarAnchorView != null)
{
App.Kp2a.MessagePresenter = new ChainedSnackbarPresenter(snackbarAnchorView);
}
else
{
App.Kp2a.MessagePresenter = new ToastPresenter();
}
}
protected virtual View? SnackbarAnchorView => null;
public const int RequestCodeChallengeYubikey = 793;

View File

@@ -43,7 +43,7 @@
</queries>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="35" />
<permission android:description="@string/permission_desc2" android:icon="@drawable/ic_notify_locked" android:label="KP2A entry search" android:name="keepass2android.keepass2android_debug.permission.KP2aInternalSearch" android:protectionLevel="signature" />
<permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android_debug.permission.Kp2aChooseAutofill" android:protectionLevel="signature" />
<application

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="204"
android:versionName="1.12-r3"
package="keepass2android.keepass2android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">
android:versionCode="223"
android:versionName="1.14-pre0"
package="keepass2android.keepass2android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">
<queries>
@@ -42,17 +42,15 @@
<action android:name="android.intent.action.VIEW" />
</intent>
</queries>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="35" />
<permission android:description="@string/permission_desc2" android:icon="@drawable/ic_launcher" android:label="KP2A entry search" android:name="keepass2android.keepass2android.permission.KP2aInternalSearch" android:protectionLevel="signature" />
<permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android.permission.Kp2aChooseAutofill" android:protectionLevel="signature" />
<application android:label="keepass2android"
android:icon="@mipmap/ic_launcher_online"
android:roundIcon="@mipmap/ic_launcher_online_round"
android:networkSecurityConfig="@xml/network_security_config"
>
android:icon="@mipmap/ic_launcher_online"
android:roundIcon="@mipmap/ic_launcher_online_round"
android:networkSecurityConfig="@xml/network_security_config"
>
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
@@ -107,16 +105,15 @@
</intent-filter>
</activity>
<activity android:name="keepass2android.softkeyboard.InputLanguageSelection"
android:exported="true">
<!-- android:label="@string/language_selection_title" TODO -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="keepass2android.softkeyboard.INPUT_LANGUAGE_SELECTION"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity" android:windowSoftInputMode="adjustResize" android:exported="true">
<activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity" android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />

View File

@@ -1,24 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="200"
android:versionName="1.11-r0"
package="keepass2android.keepass2android_nonet"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="223"
android:versionName="1.14-pre0"
package="keepass2android.keepass2android_nonet"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">
<queries>
<!-- Specific intents and packages we query for (required since Android 11) -->
<package android:name="keepass2android.plugin.keyboardswap2" />
<package android:name="keepass2android.AncientIconSet" />
<package android:name="com.dropbox.android" />
<package android:name="keepass2android.plugin.qr" />
<package android:name="it.andreacioni.kp2a.plugin.keelink" />
<package android:name="com.inputstick.apps.kp2aplugin" />
<package android:name="com.dropbox.android" />
<intent>
<action android:name="android.intent.action.OPEN_DOCUMENT" />
<data android:mimeType="*/*" />
</intent>
<intent>
<action android:name="android.intent.action.OPEN_DOCUMENT" />
<data android:mimeType="*/*" />
</intent>
<intent>
<action android:name="android.intent.action.GET_DOCUMENT" />
<data android:mimeType="*/*" />
</intent>
@@ -36,59 +39,57 @@
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
</intent>
</queries>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
<action android:name="android.intent.action.VIEW" />
</intent>
</queries>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="35" />
<permission android:description="@string/permission_desc2" android:icon="@drawable/ic_launcher_offline" android:label="KP2A entry search" android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalSearch" android:protectionLevel="signature" />
<permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher_offline" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android_nonet.permission.Kp2aChooseAutofill" android:protectionLevel="signature" />
<application
android:label="keepass2android"
android:icon="@mipmap/ic_launcher_offline"
android:networkSecurityConfig="@xml/network_security_config"
>
<application android:label="keepass2android"
android:icon="@mipmap/ic_launcher_offline"
android:roundIcon="@mipmap/ic_launcher_offline_round"
android:networkSecurityConfig="@xml/network_security_config"
>
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="barcode_ui"/>
<uses-library
<uses-library
android:name="org.apache.http.legacy"
android:required="false"/>
<provider android:name="group.pals.android.lib.ui.filechooser.providers.localfile.LocalFileProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.localfile" android:exported="false" />
<provider android:name="group.pals.android.lib.ui.filechooser.providers.history.HistoryProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.history" android:exported="false" />
<activity android:name="group.pals.android.lib.ui.filechooser.FileChooserActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:screenOrientation="user" android:theme="@style/Afc.Theme.Light">
</activity>
<provider android:name="group.pals.android.lib.ui.filechooser.providers.localfile.LocalFileProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.localfile" android:exported="false" />
<provider android:name="group.pals.android.lib.ui.filechooser.providers.history.HistoryProvider" android:authorities="keepass2android.keepass2android_nonet.android-filechooser.history" android:exported="false" />
<activity android:name="group.pals.android.lib.ui.filechooser.FileChooserActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:screenOrientation="user" android:theme="@style/Afc.Theme.Light">
</activity>
<service android:name="keepass2android.softkeyboard.KP2AKeyboard" android:permission="android.permission.BIND_INPUT_METHOD" android:exported="true">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method" />
</service>
<activity android:name="keepass2android.softkeyboard.LatinIMESettings" android:label="@string/english_ime_settings" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<intent-filter>
<action android:name="keepass2android.softkeyboard.LatinIMESettings" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name="keepass2android.softkeyboard.InputLanguageSelection"
android:label="@string/language_selection_title"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="keepass2android.softkeyboard.INPUT_LANGUAGE_SELECTION"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity" android:windowSoftInputMode="adjustResize"
android:exported="true">
<service android:name="keepass2android.softkeyboard.KP2AKeyboard" android:permission="android.permission.BIND_INPUT_METHOD" android:exported="true">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method" />
</service>
<activity android:name="keepass2android.softkeyboard.LatinIMESettings" android:label="@string/english_ime_settings" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<intent-filter>
<action android:name="keepass2android.softkeyboard.LatinIMESettings" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name="keepass2android.softkeyboard.InputLanguageSelection"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="keepass2android.softkeyboard.INPUT_LANGUAGE_SELECTION"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:configChanges="orientation|keyboard|keyboardHidden" android:label="@string/app_name" android:theme="@style/Kp2aTheme_BlueNoActionBar" android:name="keepass2android.SelectCurrentDbActivity" android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@@ -98,11 +99,11 @@
<data android:host="*" />
</intent-filter>
<intent-filter>
<intent-filter>
<action android:name="kp2a.action.SelectCurrentDbActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
@@ -112,7 +113,7 @@
<data android:mimeType="application/*" />
</intent-filter>
<!-- intent filter for opening database files
<!-- intent filter for opening database files
Note that this stopped working nicely with Android 7, see e.g. https://stackoverflow.com/a/26635162/292233
KP2A was using
<data android:scheme="content" />
@@ -126,7 +127,7 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
-->
<!-- This intent filter is for apps which use content with a URI containing the extension but no specific mimeType, e.g. ASTRO file manager -->
<!-- This intent filter is for apps which use content with a URI containing the extension but no specific mimeType, e.g. ASTRO file manager -->
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
@@ -168,7 +169,7 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
</intent-filter>
<!-- This intent filter is for apps which use content with a URI not containing the extension but at least specify mimeType=application/octet-stream, e.g. GoogleDrive or FolderSync -->
<intent-filter android:label="@string/app_name">
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
@@ -218,24 +219,15 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdb" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="https"
android:host="my.yubico.com"
android:pathPrefix="/neo"/>
<intent-filter android:label="@string/kp2a_findUrl">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="keepass2android.ACTION_START_WITH_TASK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter android:label="@string/kp2a_findUrl">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="keepass2android.ACTION_START_WITH_TASK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@@ -244,24 +236,28 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
<data android:host="totp"/>
<data android:host="hotp"/>
</intent-filter>
</activity>
<uses-library android:required="false" android:name="com.sec.android.app.multiwindow" />
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" />
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="426.0dip" />
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="360.0dip" />
</application>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
</activity>
<uses-library android:required="false" android:name="com.sec.android.app.multiwindow" />
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" />
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="426.0dip" />
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="360.0dip" />
</application>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalFileBrowsing" />
<uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalSearch" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- Samsung Pass permission -->
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />
</manifest>

View File

@@ -145,7 +145,7 @@ namespace keepass2android
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
Toast.MakeText(this, "No Yubikey OTP found!", ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, "No Yubikey OTP found!", MessageSeverity.Error);
Finish();
return;
}

View File

@@ -15,6 +15,36 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Database;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Preferences;
using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using AndroidX.AppCompat.App;
using AndroidX.CoordinatorLayout.Widget;
using AndroidX.Core.Content;
using AndroidX.Core.View;
using AndroidX.DrawerLayout.Widget;
using Google.Android.Material.AppBar;
using Google.Android.Material.Dialog;
using Java.Lang;
using Java.Net;
using KeeChallenge;
using keepass2android;
using keepass2android.Io;
using keepass2android.Utils;
using Keepass2android.Pluginsdk;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using OtpKeyProv;
using System;
using System.Collections.Generic;
using System.IO;
@@ -23,49 +53,17 @@ using System.Net;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using keepass2android;
using Android.App;
using Android.Content;
using Android.Database;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using Java.Net;
using Android.Preferences;
using Android.Text;
using Android.Content.PM;
using Android.Graphics;
using AndroidX.AppCompat.App;
using AndroidX.CoordinatorLayout.Widget;
using AndroidX.Core.View;
using AndroidX.DrawerLayout.Widget;
using Google.Android.Material.AppBar;
using Google.Android.Material.Dialog;
using Java.Lang;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using Keepass2android.Pluginsdk;
using OtpKeyProv;
using keepass2android.Io;
using keepass2android.Utils;
using File = Java.IO.File;
using FileNotFoundException = Java.IO.FileNotFoundException;
using Object = Java.Lang.Object;
using Process = Android.OS.Process;
using KeeChallenge;
using static Android.Locations.GpsStatus;
using AlertDialog = Android.App.AlertDialog;
using ClipboardManager = Android.Content.ClipboardManager;
using Enum = System.Enum;
using Exception = System.Exception;
using File = Java.IO.File;
using FileNotFoundException = Java.IO.FileNotFoundException;
using Object = Java.Lang.Object;
using Process = Android.OS.Process;
using String = System.String;
using Toolbar = AndroidX.AppCompat.Widget.Toolbar;
using AndroidX.Core.Content;
namespace keepass2android
{
@@ -131,7 +129,8 @@ namespace keepass2android
ISharedPreferences _prefs;
private bool _starting;
private OtpInfo _otpInfo;
private bool _resumeCompleted;
private OtpInfo _otpInfo;
private IOConnectionInfo _otpAuxIoc;
private ChallengeInfo _chalInfo;
private byte[] _challengeSecret;
@@ -309,7 +308,7 @@ namespace keepass2android
catch (Exception e)
{
Kp2aLog.Log(e.ToString());
Toast.MakeText(this, "Error: " + e.Message, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, "Error: " + Util.GetErrorMessage(e), MessageSeverity.Error);
return;
}
@@ -328,8 +327,7 @@ namespace keepass2android
ChallengeInfo temp = _challengeProv.Encrypt(_challengeSecret);
if (!temp.Save(_otpAuxIoc))
{
Toast.MakeText(this, Resource.String.ErrorUpdatingChalAuxFile, ToastLength.Long)
.Show();
App.Kp2a.ShowMessage(this, Resource.String.ErrorUpdatingChalAuxFile, MessageSeverity.Error);
return false;
}
@@ -348,7 +346,7 @@ namespace keepass2android
}
else
{
Toast.MakeText(this, Resource.String.bad_resp, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, Resource.String.bad_resp, MessageSeverity.Error);
}
}
}
@@ -420,8 +418,14 @@ namespace keepass2android
try
{
var iocAux = GetDefaultAuxLocation();
LoadFile(iocAux);
}
LoadFile(iocAux);
if (Activity._chalInfo == null)
{
throw new Java.Lang.Exception("Failed to load challenge aux file");
}
}
catch (Exception e)
{
//this can happen e.g. if the file storage does not support GetParentPath
@@ -458,7 +462,7 @@ namespace keepass2android
}
else
{
Toast.MakeText(Activity,GetErrorMessage(), ToastLength.Long).Show();
App.Kp2a.ShowMessage(Activity,GetErrorMessage(), MessageSeverity.Error);
}
return;
@@ -645,7 +649,7 @@ namespace keepass2android
_activityDesign.ApplyTheme();
base.OnCreate(savedInstanceState);
_intentReceiver = new PasswordActivityBroadcastReceiver(this);
_intentReceiver = new PasswordActivityBroadcastReceiver(this);
IntentFilter filter = new IntentFilter();
filter.AddAction(Intent.ActionScreenOff);
ContextCompat.RegisterReceiver(this, _intentReceiver, filter, (int)ReceiverFlags.Exported);
@@ -801,8 +805,6 @@ namespace keepass2android
_password = i.GetStringExtra(KeyPassword) ?? "";
if (!KeyProviderTypes.Any())
{
SetKeyProviderFromString(LoadKeyProviderStringForIoc(_ioConnection.Path));
}
@@ -957,7 +959,7 @@ namespace keepass2android
{
btn.SetImageResource(Resource.Drawable.baseline_fingerprint_24);
}, 1300);
Toast.MakeText(this, message, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, message, MessageSeverity.Error);
}
public void OnBiometricAttemptFailed(string message)
@@ -996,8 +998,12 @@ namespace keepass2android
btn.PostDelayed(() =>
{
//fire
OnOk(true);
//fire if everything else is ready
if (FindViewById(Resource.Id.pass_ok).Enabled)
{
OnOk(true);
}
FindViewById<EditText>(Resource.Id.password_edit).Enabled = true;
}, 500);
@@ -1036,7 +1042,7 @@ namespace keepass2android
if (_appnameclickCount == 6)
{
Kp2aLog.LogUnexpectedError(new Exception("some blabla"));
Toast.MakeText(this, "Once again and the app will crash.", ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, "Once again and the app will crash.", MessageSeverity.Warning);
}
if (_appnameclickCount == 7)
@@ -1123,7 +1129,7 @@ namespace keepass2android
//For security reasons: discard the OTP (otherwise the user might not select a database now and forget
//about the OTP, but it would still be stored in the Intents and later be passed to PasswordActivity again.
Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_no_db), ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_because_no_db), MessageSeverity.Warning);
GoToFileSelectActivity();
return false;
}
@@ -1157,7 +1163,9 @@ namespace keepass2android
changeDbButton.Click += (sender, args) => GoToFileSelectActivity();
Util.MoveBottomBarButtons(Resource.Id.change_db, Resource.Id.pass_ok, Resource.Id.bottom_bar, this);
}
Util.InsetListener.ForBottomElement(FindViewById(Resource.Id.bottom_bar)).Apply();
Util.InsetListener.ForTopElement(FindViewById(Resource.Id.appbar)).Apply();
}
private void OnOk(bool usedFingerprintUnlock = false)
{
@@ -1251,7 +1259,7 @@ namespace keepass2android
case 6:
KeyProviderTypes.Add(KeyProviders.ChallengeXC);
break;
case 7:
case 7:
//don't set to "" to prevent losing the filename. (ItemSelected is also called during recreation!)
Kp2aLog.Log("key file length before: " + _keyFile?.Length);
_keyFile = (FindViewById(Resource.Id.label_keyfilename).Tag ?? "").ToString();
@@ -1400,7 +1408,7 @@ namespace keepass2android
string errorMessage;
if (!CreateCompositeKey(out compositeKey, out errorMessage)) return (() =>
{
Toast.MakeText(this, errorMessage, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, errorMessage, MessageSeverity.Warning);
_performingLoad = false;
});
return () => { PerformLoadDatabaseWithCompositeKey(compositeKey); };
@@ -1415,18 +1423,22 @@ namespace keepass2android
if (cbQuickUnlock == null)
throw new NullPointerException("cpQuickUnlock");
App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked);
App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase =
(((KeyguardManager)GetSystemService(Context.KeyguardService)!)!).IsDeviceSecure;
App.Kp2a.QuickUnlockBlockedWhenDeviceNotSecureWhenOpeningDatabase = PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.QuickUnlockBlockedWhenDeviceNotSecure_key), true);
if ((_loadDbFileTask != null) && (App.Kp2a.OfflineMode != _loadDbTaskOffline))
if ((_loadDbFileTask != null) && (App.Kp2a.OfflineMode != _loadDbTaskOffline))
{
if (App.Kp2a == null)
if (App.Kp2a == null)
throw new NullPointerException("App.Kp2a");
//keep the loading result if we loaded in online-mode (now offline) and the task is completed
if (!App.Kp2a.OfflineMode || !_loadDbFileTask.IsCompleted)
{
//discard the pre-loading task
_loadDbFileTask = null;
_loadDbFileTask = null;
}
}
//avoid password being visible while loading:
@@ -1481,7 +1493,7 @@ namespace keepass2android
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
errorMessage = e.Message;
errorMessage = Util.GetErrorMessage(e);
return false;
}
}
@@ -1566,15 +1578,21 @@ namespace keepass2android
base.OnPause();
}
protected override void OnStart()
private bool hasRequestedKeyboardActivation = false;
protected override void OnStart()
{
base.OnStart();
_starting = true;
if (PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false))
{
CopyToClipboardService.ActivateKeyboard(this);
.GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false)
&& !hasRequestedKeyboardActivation)
{
hasRequestedKeyboardActivation = true;
CopyToClipboardService.ActivateKeyboard(this);
}
DonateReminder.ShowDonateReminderIfAppropriate(this);
@@ -1639,60 +1657,65 @@ namespace keepass2android
if (intent != null)
{
if (intent.HasExtra(Intents.OtpExtraKey))
{
string otp = intent.GetStringExtra(Intents.OtpExtraKey);
_keepPasswordInOnResume = true;
if (KeyProviderTypes.Contains(KeyProviders.Otp))
{
string otp = intent.GetStringExtra(Intents.OtpExtraKey);
_keepPasswordInOnResume = true;
if (KeyProviderTypes.Contains(KeyProviders.Otp))
{
if (_otpInfo == null)
{
//Entering OTPs not yet initialized:
_pendingOtps.Add(otp);
UpdateKeyProviderUiState();
}
else
{
//Entering OTPs is initialized. Write OTP into first empty field:
bool foundEmptyField = false;
foreach (int otpId in _otpTextViewIds)
{
EditText otpEdit = FindViewById<EditText>(otpId);
if ((otpEdit.Visibility == ViewStates.Visible) && String.IsNullOrEmpty(otpEdit.Text))
{
otpEdit.Text = otp;
foundEmptyField = true;
break;
}
}
//did we find a field?
if (!foundEmptyField)
{
App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_no_space), MessageSeverity.Error);
}
}
Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
if (passwordModeSpinner.SelectedItemPosition != (int)KeyProviders.Otp)
{
passwordModeSpinner.SetSelection((int)KeyProviders.Otp);
}
}
else
{
//assume the key should be used as static password
FindViewById<EditText>(Resource.Id.password_edit).Text += otp;
}
}
else
{
// if the activity is launched twice and the first initialization hasn't even finished, we cannot
// reset the state and re-initialize the activity.
// This can happen with autofill in some cases (#2869)
if (_resumeCompleted)
{
if (_otpInfo == null)
{
//Entering OTPs not yet initialized:
_pendingOtps.Add(otp);
UpdateKeyProviderUiState();
}
else
{
//Entering OTPs is initialized. Write OTP into first empty field:
bool foundEmptyField = false;
foreach (int otpId in _otpTextViewIds)
{
EditText otpEdit = FindViewById<EditText>(otpId);
if ((otpEdit.Visibility == ViewStates.Visible) && String.IsNullOrEmpty(otpEdit.Text))
{
otpEdit.Text = otp;
foundEmptyField = true;
break;
}
}
//did we find a field?
if (!foundEmptyField)
{
Toast.MakeText(this, GetString(Resource.String.otp_discarded_no_space), ToastLength.Long).Show();
}
}
Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
if (passwordModeSpinner.SelectedItemPosition != (int)KeyProviders.Otp)
{
passwordModeSpinner.SetSelection((int)KeyProviders.Otp);
}
ResetState();
GetIocFromLaunchIntent(intent);
InitializeAfterSetIoc();
OnStart();
}
else
{
//assume the key should be used as static password
FindViewById<EditText>(Resource.Id.password_edit).Text += otp;
}
}
else
{
ResetState();
GetIocFromLaunchIntent(intent);
InitializeAfterSetIoc();
OnStart();
}
}
}
}
@@ -1729,19 +1752,26 @@ namespace keepass2android
UsedFingerprintUnlock = false;
}
protected override void OnResume()
protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);
protected override void OnResume()
{
base.OnResume();
_activityDesign.ReapplyTheme();
Kp2aLog.Log("starting: " + _starting + ", Finishing: " + IsFinishing + ", _performingLoad: " + _performingLoad);
Kp2aLog.Log("starting: " + _starting + ", Finishing: " + IsFinishing + ", _performingLoad: " +
_performingLoad);
CheckBox cbOfflineMode = (CheckBox)FindViewById(Resource.Id.work_offline);
App.Kp2a.OfflineMode = cbOfflineMode.Checked = App.Kp2a.OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings
App.Kp2a.OfflineMode =
cbOfflineMode.Checked =
App.Kp2a
.OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings
LinearLayout offlineModeContainer = FindViewById<LinearLayout>(Resource.Id.work_offline_container);
var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage;
if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection))
{
{
offlineModeContainer.Visibility = ViewStates.Visible;
}
else
@@ -1749,18 +1779,18 @@ namespace keepass2android
offlineModeContainer.Visibility = ViewStates.Gone;
App.Kp2a.OfflineMode = false;
}
View killButton = FindViewById(Resource.Id.kill_app);
if (PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.show_kill_app_key), false))
.GetBoolean(GetString(Resource.String.show_kill_app_key), false))
{
killButton.Click += (sender, args) =>
{
_killOnDestroy = true;
SetResult(Result.Canceled);
SetResult(Result.Canceled);
Finish();
};
@@ -1772,20 +1802,23 @@ namespace keepass2android
killButton.Visibility = ViewStates.Gone;
}
TryGetOtpFromClipboard();
TryGetOtpFromClipboard();
if (!_keepPasswordInOnResume)
{
if (
_lastOnPauseTime < DateTime.Now - TimeSpan.FromSeconds(5) //only clear when user left the app for more than 5 seconds (allows to use Yubiclip, also allows to switch shortly to another app)
&&
PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.ClearPasswordOnLeave_key), true))
{
ClearEnteredPassword();
}
if (!_keepPasswordInOnResume)
{
if (
_lastOnPauseTime <
DateTime.Now -
TimeSpan.FromSeconds(
5) //only clear when user left the app for more than 5 seconds (allows to use Yubiclip, also allows to switch shortly to another app)
&&
PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.ClearPasswordOnLeave_key), true))
{
ClearEnteredPassword();
}
}
}
@@ -1797,71 +1830,74 @@ namespace keepass2android
if (KeyProviderTypes.Contains(KeyProviders.Challenge))
{
FindViewById(Resource.Id.otpInitView).Visibility = _challengeSecret == null ? ViewStates.Visible : ViewStates.Gone;
FindViewById(Resource.Id.otpInitView).Visibility =
_challengeSecret == null ? ViewStates.Visible : ViewStates.Gone;
}
//use !IsFinishing to make sure we're not starting another activity when we're already finishing (e.g. due to TaskComplete in OnActivityResult)
//use !performingLoad to make sure we're not already loading the database (after ActivityResult from File-Prepare-Activity; this would cause _loadDbFileTask to exist when we reload later!)
if ( !IsFinishing && !_performingLoad)
if (!IsFinishing && !_performingLoad)
{
// OnResume is run every time the activity comes to the foreground. This code should only run when the activity is started (OnStart), but must
// OnResume is run every time the activity comes to the foreground. This code should only run when the activity is started (OnStart), but must
// be run in OnResume rather than OnStart so that it always occurrs after OnActivityResult (when re-creating a killed activity, OnStart occurs before OnActivityResult)
if (_starting)
{
if (_starting)
{
_starting = false;
_starting = false;
//database not yet loaded.
//database not yet loaded.
//check if pre-loading is enabled but wasn't started yet:
if (_loadDbFileTask == null &&
_prefs.GetBoolean(GetString(Resource.String.PreloadDatabaseEnabled_key), true))
{
// Create task to kick off file loading while the user enters the password
_loadDbFileTask = Task.Factory.StartNew(PreloadDbFile);
_loadDbTaskOffline = App.Kp2a.OfflineMode;
}
}
//check if pre-loading is enabled but wasn't started yet:
if (_loadDbFileTask == null &&
_prefs.GetBoolean(GetString(Resource.String.PreloadDatabaseEnabled_key), true))
{
// Create task to kick off file loading while the user enters the password
_loadDbFileTask = Task.Factory.StartNew(PreloadDbFile);
_loadDbTaskOffline = App.Kp2a.OfflineMode;
}
}
}
if (compositeKeyForImmediateLoad != null)
{
//reload the database (without most other stuff performed in PerformLoadDatabase.
// We're assuming that the db file (and if appropriate also the key file) are still available
// and there's no need to re-init the file storage. if it is, loading will fail and the user has
// to retry with typing the full password, but that's intended to avoid showing the password to a
// a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274)
Handler handler = new Handler();
OnFinish onFinish = new AfterLoad(handler, this, _ioConnection);
_performingLoad = true;
LoadDb task = new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
onFinish, false, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use
new ProgressTask(App.Kp2a, this, task).Run();
compositeKeyForImmediateLoad = null; //don't reuse or keep in memory
if (compositeKeyForImmediateLoad != null)
{
//reload the database (without most other stuff performed in PerformLoadDatabase.
// We're assuming that the db file (and if appropriate also the key file) are still available
// and there's no need to re-init the file storage. if it is, loading will fail and the user has
// to retry with typing the full password, but that's intended to avoid showing the password to a
// a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274)
Handler handler = new Handler();
OnFinish onFinish = new AfterLoad(handler, this, _ioConnection);
_performingLoad = true;
LoadDb task = new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
onFinish, false, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use
new ProgressTask(App.Kp2a, this, task).Run();
compositeKeyForImmediateLoad = null; //don't reuse or keep in memory
}
else
{
bool showKeyboard = true;
}
else
{
bool showKeyboard = true;
EditText pwd = (EditText) FindViewById(Resource.Id.password_edit);
pwd.PostDelayed(() =>
{
InputMethodManager keyboard = (InputMethodManager) GetSystemService(InputMethodService);
if (showKeyboard)
{
pwd.RequestFocus();
keyboard.ShowSoftInput(pwd, 0);
}
else
keyboard.HideSoftInputFromWindow(pwd.WindowToken, HideSoftInputFlags.ImplicitOnly);
}, 50);
}
EditText pwd = (EditText)FindViewById(Resource.Id.password_edit);
pwd.PostDelayed(() =>
{
InputMethodManager keyboard = (InputMethodManager)GetSystemService(InputMethodService);
if (showKeyboard)
{
pwd.RequestFocus();
keyboard.ShowSoftInput(pwd, 0);
}
else
keyboard.HideSoftInputFromWindow(pwd.WindowToken, HideSoftInputFlags.ImplicitOnly);
}, 50);
}
_resumeCompleted = true;
}
private void TryGetOtpFromClipboard()
@@ -1954,7 +1990,7 @@ namespace keepass2android
btn.Tag = error;
Toast.MakeText(this, Resource.String.fingerprint_reenable2, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, Resource.String.fingerprint_reenable2, MessageSeverity.Error);
_biometricDec = null;
return false;
@@ -2024,7 +2060,7 @@ namespace keepass2android
/*
private void errorMessage(CharSequence text)
{
Toast.MakeText(this, text, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, text, MessageSeverity.Error);
}
*/
@@ -2084,7 +2120,9 @@ namespace keepass2android
_act.LoadingErrorCount++;
}
if ((Exception != null) && (Exception.Message == KeePassLib.Resources.KLRes.FileCorrupted))
if ((Exception != null) && (Exception.Message == KeePassLib.Resources.KLRes.FileCorrupted))
{
Message = _act.GetString(Resource.String.CorruptDatabaseHelp);
}
@@ -2150,7 +2188,8 @@ namespace keepass2android
}
else
{
DisplayMessage(_act);
MessageSeverity severity = Success ? MessageSeverity.Info : MessageSeverity.Error;
App.Kp2a.ShowMessage(_act, Message, severity);
if (Success)
{
_act.LaunchNextActivity();
@@ -2218,7 +2257,7 @@ namespace keepass2android
{
Kp2aLog.LogUnexpectedError(e);
ShowError( _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile) + " " + e.Message);
ShowError( _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile) + " " + Util.GetErrorMessage(e));
}
@@ -2234,7 +2273,7 @@ namespace keepass2android
private void ShowError(string message)
{
App.Kp2a.ShowToast(message);
App.Kp2a.ShowToast(message, MessageSeverity.Error);
}
}
private class PasswordActivityBroadcastReceiver : BroadcastReceiver

View File

@@ -128,11 +128,11 @@ namespace keepass2android
Kp2aLog.LogUnexpectedError(e);
}
if (String.IsNullOrEmpty(_requestedUrl))
Toast.MakeText(this, GetString(Resource.String.query_credentials, new Java.Lang.Object[] {pluginDisplayName}), ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, GetString(Resource.String.query_credentials, new Java.Lang.Object[] {pluginDisplayName}), MessageSeverity.Info);
else
Toast.MakeText(this,
App.Kp2a.ShowMessage(this,
GetString(Resource.String.query_credentials_for_url,
new Java.Lang.Object[] { pluginDisplayName, _requestedUrl }), ToastLength.Long).Show(); ;
new Java.Lang.Object[] { pluginDisplayName, _requestedUrl }), MessageSeverity.Info); ;
}
private void StartQuery()

View File

@@ -25,6 +25,7 @@ using Android.Widget;
using Android.Content.PM;
using KeePassLib.Keys;
using Android.Preferences;
using Android.Provider;
using Android.Runtime;
using Android.Views.InputMethods;
@@ -35,6 +36,7 @@ using KeePassLib;
using KeePassLib.Serialization;
using Toolbar = AndroidX.AppCompat.Widget.Toolbar;
using AndroidX.Core.Content;
using keepass2android.Utils;
namespace keepass2android
{
@@ -79,8 +81,11 @@ namespace keepass2android
SetContentView(Resource.Layout.QuickUnlock);
Util.InsetListener.ForBottomElement(FindViewById(Resource.Id.bottom_bar)).Apply();
Util.InsetListener.ForTopElement(FindViewById(Resource.Id.appbar)).Apply();
var collapsingToolbar = FindViewById<CollapsingToolbarLayout>(Resource.Id.collapsing_toolbar);
var collapsingToolbar = FindViewById<CollapsingToolbarLayout>(Resource.Id.collapsing_toolbar);
collapsingToolbar.SetTitle(GetString(Resource.String.QuickUnlock_prefs));
SetSupportActionBar(FindViewById<Toolbar>(Resource.Id.toolbar));
@@ -161,6 +166,29 @@ namespace keepass2android
if (bundle != null)
numFailedAttempts = bundle.GetInt(NumFailedAttemptsKey, 0);
FindViewById(Resource.Id.QuickUnlock_buttonEnableLock).Click += (object sender, EventArgs e) =>
{
Intent intent = new Intent(Settings.ActionSecuritySettings);
StartActivity(intent);
};
FindViewById(Resource.Id.QuickUnlock_buttonCloseDb).Click += (object sender, EventArgs e) =>
{
App.Kp2a.Lock(false);
};
if (App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase == false && App.Kp2a.QuickUnlockBlockedWhenDeviceNotSecureWhenOpeningDatabase)
{
FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Visible;
}
else
{
FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Gone;
}
}
@@ -203,7 +231,7 @@ namespace keepass2android
btn.SetImageResource(Resource.Drawable.baseline_fingerprint_24);
}, 1300);
Toast.MakeText(this, message, ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, message, MessageSeverity.Error);
}
@@ -325,7 +353,7 @@ namespace keepass2android
{
Kp2aLog.Log("QuickUnlock not successful!");
App.Kp2a.Lock(false);
Toast.MakeText(this, GetString(Resource.String.QuickUnlock_fail), ToastLength.Long).Show();
App.Kp2a.ShowMessage(this, GetString(Resource.String.QuickUnlock_fail), MessageSeverity.Error);
Finish();
}
@@ -383,8 +411,9 @@ namespace keepass2android
{
base.OnResume();
_design.ReapplyTheme();
CheckIfUnloaded();
App.Kp2a.MessagePresenter = new ChainedSnackbarPresenter(FindViewById(Resource.Id.main_content));
CheckIfUnloaded();
InitFingerprintUnlock();
@@ -449,7 +478,8 @@ namespace keepass2android
protected override void OnPause()
{
if (_biometryIdentifier != null)
App.Kp2a.MessagePresenter = new NonePresenter();
if (_biometryIdentifier != null)
{
Kp2aLog.Log("FP: Stop listening");
_biometryIdentifier.StopListening();

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fitsSystemWindows="true">
android:fitsSystemWindows="false">
<keepass2android.MeasuringLinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
@@ -17,7 +17,7 @@ android:fitsSystemWindows="true">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:fitsSystemWindows="true">
android:fitsSystemWindows="false">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
@@ -28,7 +28,7 @@ android:fitsSystemWindows="true">
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
android:fitsSystemWindows="true"
android:fitsSystemWindows="false"
app:expandedTitleMarginStart="16dp"
app:expandedTitleMarginEnd="24dp"
app:expandedTitleMarginBottom="20sp">
@@ -78,20 +78,27 @@ android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<TextView
android:id="@+id/QuickUnlock_label"
android:text="@string/QuickUnlock_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/filename_label"
android:textSize="14sp"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:orientation="vertical"
android:id="@+id/QuickUnlockForm">
<TextView
android:id="@+id/QuickUnlock_label"
android:text="@string/QuickUnlock_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/filename_label"
android:textSize="14sp"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:inputType="textPassword"
@@ -116,11 +123,65 @@ android:paddingRight="16dp"
android:src="@drawable/baseline_fingerprint_24"
android:scaleType="fitXY"
android:background="?android:selectableItemBackground" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/md_theme_secondaryContainer"
android:id="@+id/QuickUnlockBlocked"
android:padding="16dp"
android:layout_gravity="center">
<TextView
android:id="@+id/quick_unlock_blocked_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/password_based_quick_unlock_not_available"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:paddingBottom="8dp"/>
<TextView
android:id="@+id/alert_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/password_based_quick_unlock_not_available_text"
android:textSize="16sp"
android:paddingBottom="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/QuickUnlock_buttonEnableLock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:backgroundTint="@color/md_theme_secondary"
android:textColor="@android:color/white"
android:text="@string/enable_screen_lock"
android:fontFamily="sans-serif-medium" />
<Button
android:id="@+id/QuickUnlock_buttonCloseDb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:backgroundTint="@color/md_theme_secondary"
android:textColor="@android:color/white"
android:fontFamily="sans-serif-medium"
android:text="@string/QuickUnlock_lockButton" />
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/spacing"

View File

@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<ScrollView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/entry_scroll"
android:background="?android:attr/colorBackground"
@@ -267,4 +273,5 @@
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
@@ -75,4 +78,5 @@
/>
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_container"
android:layout_width="fill_parent"
android:background="?android:attr/colorBackground"
android:layout_height="fill_parent">
@@ -322,4 +323,3 @@
</ScrollView>
</RelativeLayout>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
@@ -41,4 +44,5 @@
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
@@ -27,4 +30,5 @@
android:importantForAccessibility="no"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
@@ -42,4 +45,5 @@
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<keepass2android.FixedDrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fitsSystemWindows="true">
>
<!-- activity view -->
<keepass2android.MeasuringLinearLayout
android:layout_width="fill_parent"
@@ -17,14 +17,13 @@
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:fitsSystemWindows="true">
android:layout_weight="1">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="@dimen/detail_backdrop_height"
android:fitsSystemWindows="true"
android:fitsSystemWindows="false"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
<com.google.android.material.appbar.CollapsingToolbarLayout
@@ -32,14 +31,17 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
android:fitsSystemWindows="true"
app:expandedTitleMarginStart="48dp"
android:fitsSystemWindows="false"
app:collapsedTitleGravity="center"
app:expandedTitleGravity="bottom|left"
app:expandedTitleMarginStart="8dp"
app:expandedTitleMarginEnd="24dp"
app:expandedTitleMarginBottom="20sp">
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:fitsSystemWindows="true"
android:fitsSystemWindows="false"
android:layout_height="fill_parent">
<ImageView
android:id="@+id/backdrop"
@@ -52,7 +54,7 @@
android:layout_alignParentBottom="true"
android:layout_marginBottom="0sp"
android:layout_marginLeft="48dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
@@ -446,4 +448,4 @@
</LinearLayout>
</ScrollView>
</com.google.android.material.navigation.NavigationView>
</keepass2android.FixedDrawerLayout>
</androidx.drawerlayout.widget.DrawerLayout>

View File

@@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="12dip">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/sftp_host"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textNoSuggestions"
android:text="144.76.169.229"
android:hint="@string/hint_sftp_host" />
<TextView
android:id="@+id/portsep"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":" />
<EditText
android:id="@+id/sftp_port"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="number"
android:text="22"
android:hint="@string/hint_sftp_port" />
</LinearLayout>
<EditText
android:id="@+id/sftp_user"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="philipp"
android:hint="@string/hint_username" />
<EditText
android:id="@+id/sftp_password"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:singleLine="true"
android:text="l2uientTjVhvyfzNpksa"
android:hint="@string/hint_pass" />
<TextView
android:id="@+id/initial_dir"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dip"
android:layout_marginTop="4dip"
android:text="@string/initial_directory" />
<EditText
android:id="@+id/sftp_initial_dir"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="/home/philipp" />
</LinearLayout>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
@@ -197,3 +200,5 @@
/>
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:layout_margin="12dip"
>
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hint_smb_credentials" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/smb_url"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:layout_weight="1"
android:text=""
android:inputType="textWebEmailAddress"
android:hint="@string/hint_smb_url" />
</LinearLayout>
<EditText
android:id="@+id/smb_domain"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:text=""
android:hint="@string/hint_smb_domain" />
<EditText
android:id="@+id/smb_user"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:text=""
android:hint="@string/hint_smb_username" />
<EditText
android:id="@+id/smb_password"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:singleLine="true"
android:text=""
android:hint="@string/hint_pass"
android:importantForAccessibility="no"/>
</LinearLayout>
</ScrollView>

Some files were not shown because too many files have changed in this diff Show More