Compare commits

...

139 Commits

Author SHA1 Message Date
J-Jamet
33beb57e9d Merge branch 'release/2.9.16' 2021-04-07 09:53:07 +02:00
J-Jamet
66eeadca0b Upgrade version code 2021-04-06 17:39:27 +02:00
J-Jamet
a10d1c98a8 Fix KDB parcelable 2021-04-06 17:32:40 +02:00
J-Jamet
59ead4986f Move Parent Parcelable 2021-04-06 15:05:11 +02:00
J-Jamet
09f6c18189 Small changes 2021-04-06 15:02:24 +02:00
J-Jamet
0f3ad7c8b1 Fix select custom icon 2021-04-05 19:06:54 +02:00
J-Jamet
0487dea7fc Fix null cache directory 2021-04-05 11:32:07 +02:00
J-Jamet
8cac1ee284 Merge branch 'feature/ExternalFileHelper' into develop 2021-04-05 00:06:03 +02:00
J-Jamet
196620e1bd Remove unused methods 2021-04-05 00:04:25 +02:00
J-Jamet
43d6c76873 Refactor open document click listener 2021-04-04 23:44:25 +02:00
J-Jamet
b864c39a0d Fix add database workflow in some devices 2021-04-04 23:13:24 +02:00
J-Jamet
818b975111 Change default type verification to create document 2021-04-04 09:56:09 +02:00
J-Jamet
d5fbc8393f Change file creation methods 2021-04-03 19:40:55 +02:00
J-Jamet
7b5e9d2344 Better parcelable entry CREATOR implementation #948 2021-04-02 09:37:18 +02:00
J-Jamet
7fc2d95886 Fix document file retrievment 2021-04-01 10:52:00 +02:00
J-Jamet
78d3b369bb Move Parcelable inheritance 2021-03-31 19:39:07 +02:00
J-Jamet
bb3620680b Upgrade version 2021-03-31 17:58:49 +02:00
J-Jamet
d4a45655ca Merge tag '2.9.15' into develop
2.9.15
2021-03-29 22:01:52 +02:00
J-Jamet
c9c739fd52 Merge branch 'release/2.9.15' 2021-03-29 22:01:44 +02:00
J-Jamet
2b359cc592 Remove unused code 2021-03-29 21:00:10 +02:00
J-Jamet
151b7a323d Upgrade version code 2021-03-29 13:58:30 +02:00
J-Jamet
1063dc2b63 Add TODO SparseArray 2021-03-29 13:57:30 +02:00
J-Jamet
f9f59a6eb1 Replace serializable UUID by Parcelable UUID 2021-03-29 13:49:49 +02:00
J-Jamet
73156cc337 Fix clipboard null exception 2021-03-29 13:09:22 +02:00
J-Jamet
7d53607f49 Capture exception when launching cipher action 2021-03-29 13:07:55 +02:00
J-Jamet
7539945465 Capture exception when error when launching database action 2021-03-29 13:01:31 +02:00
J-Jamet
51df8e7bb1 Try to fix rare bug 2021-03-29 12:52:12 +02:00
J-Jamet
17029ce67c Fix bad padding exception 2021-03-29 12:42:53 +02:00
J-Jamet
8cedc313cf Upgrade version code 2021-03-27 10:54:26 +01:00
J-Jamet
5afe3acac1 Remove unused string 2021-03-27 10:23:38 +01:00
J-Jamet
9887b58b71 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2021-03-27 10:18:55 +01:00
J-Jamet
ec8363ba6a Merge branch 'develop' into release/2.9.15 2021-03-27 10:13:53 +01:00
J-Jamet
fcfb71f13b Fix disable Memory Usage setting with AES #941 2021-03-27 10:12:29 +01:00
J-Jamet
3a12e431ff Update CHANGELOG 2021-03-27 05:49:29 +01:00
J-Jamet
bc4ed8e123 Update CHANGELOG 2021-03-27 05:44:09 +01:00
J-Jamet
445e9540a5 Merge branch 'feature/Dynamic_Memory_And_Encrypt_Module' into develop 2021-03-26 20:06:12 +01:00
Joan Jaume Oliver
bbc2a2a9dd Translated using Weblate (Spanish)
Currently translated at 98.6% (515 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2021-03-26 19:29:42 +01:00
Joan Jaume Oliver
5117bc78b6 Translated using Weblate (Catalan)
Currently translated at 48.2% (252 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2021-03-26 19:29:41 +01:00
J-Jamet
10bf149a07 Rename final-key to aes and remove unused string resource 2021-03-26 19:27:19 +01:00
J-Jamet
0a976bd012 Rollback AES & Argon2 as native C lib 2021-03-26 19:09:30 +01:00
J-Jamet
df31c43e59 Rename crypto module 2021-03-26 16:53:09 +01:00
J-Jamet
c5d30b9b23 Native AES call refactoring 2021-03-26 16:10:55 +01:00
J-Jamet
2e18beff27 Native AES call refactoring 2021-03-26 16:06:28 +01:00
J-Jamet
25e0cec2cc Fix CPU usage with RecyclerView (Weird that the recyclerview uses so much CPU continuously, probably an Android view bug) 2021-03-26 13:51:51 +01:00
J-Jamet
16cc4c5c97 Better activity_password layout implementation 2021-03-26 13:45:08 +01:00
J-Jamet
e5cfb6b7eb Rollback AES thread implementation 2021-03-25 14:59:39 +01:00
J-Jamet
a882ba07e9 Fix create database education 2021-03-25 12:42:40 +01:00
J-Jamet
801f3f99aa Fix app timeout and decrease keyboard timeout #934 2021-03-25 12:36:56 +01:00
J-Jamet
2338b9b57d Fix SHA-512 2021-03-25 12:23:31 +01:00
J-Jamet
8ba396c693 Remove pro guard files 2021-03-25 12:16:04 +01:00
J-Jamet
1164022765 Upgrade gradle and kotlin 2021-03-25 11:29:24 +01:00
J-Jamet
b0c5519da5 Upgrade build tool version to 30.0.3 in icon packs 2021-03-25 11:27:25 +01:00
J-Jamet
f5073238d8 Better KDBX version implementation (new code) 2021-03-25 11:07:51 +01:00
J-Jamet
3ffa89bfaf Revert "Better KDBX version implementation"
This reverts commit 9fae343668.
2021-03-25 10:57:19 +01:00
Reza Almanda
26d8b2fa22 Translated using Weblate (Indonesian)
Currently translated at 74.3% (388 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2021-03-25 01:29:42 +01:00
J-Jamet
6b17502694 Better hash implementation 2021-03-24 22:17:15 +01:00
J-Jamet
a69d57a4f4 Move classes in right places 2021-03-24 21:29:58 +01:00
J-Jamet
430bc6150f Remove manual cipher input stream 2021-03-24 21:09:44 +01:00
J-Jamet
b888615e0d Fix small variable name 2021-03-24 21:04:12 +01:00
J-Jamet
9fae343668 Better KDBX version implementation 2021-03-24 21:00:41 +01:00
J-Jamet
db467889b0 Encapsulate SHA-256 2021-03-24 20:30:13 +01:00
J-Jamet
153b8d1f37 Better getHmacKey64 method 2021-03-24 20:05:40 +01:00
J-Jamet
87858762d4 Remove NullOutputStream 2021-03-24 19:50:56 +01:00
J-Jamet
50d3282a65 Refactor HMAC methods 2021-03-24 19:15:45 +01:00
J-Jamet
aee0500b38 Merge branch 'develop' into feature/Dynamic_Memory_And_Encrypt_Module 2021-03-24 18:25:28 +01:00
J-Jamet
f2ef6eb94e Decrease default clipboard time #934 2021-03-24 18:25:10 +01:00
J-Jamet
151a5a7e73 Select Twofish with KDB database 2021-03-24 18:08:11 +01:00
J-Jamet
6a088c58de Fix Twofish algorithm 2021-03-24 17:50:08 +01:00
J-Jamet
23e7bf9f89 Update test 2021-03-24 16:13:28 +01:00
J-Jamet
59f24206ad Merge branch 'feature/Dynamic_Memory' into feature/Dynamic_Memory_And_Encrypt_Module 2021-03-24 16:02:23 +01:00
J-Jamet
e45ef019c0 Small encapsulation 2021-03-24 14:57:41 +01:00
J-Jamet
2830d3c8fa Small encapsulation 2021-03-24 14:21:19 +01:00
J-Jamet
088816dfab Change Unsigned Long Implementation 2021-03-24 14:05:45 +01:00
J-Jamet
453a29b81c Rename .java to .kt 2021-03-24 14:05:44 +01:00
J-Jamet
e5cb160aa4 Remove Little Endian output stream 2021-03-24 12:22:34 +01:00
J-Jamet
844588a0d4 Remove Little Endian input stream 2021-03-24 11:55:41 +01:00
J-Jamet
cfcfd47705 Fix test 2021-03-24 11:53:36 +01:00
J-Jamet
f416c0ec7d Merge branch 'feature/Dynamic_Memory' into feature/Dynamic_Memory_And_Encrypt_Module 2021-03-24 11:32:17 +01:00
J-Jamet
78f707c07c Simpler method 2021-03-24 11:29:15 +01:00
J-Jamet
d28a59a2fe Throw exception if bad header 2021-03-24 11:20:30 +01:00
J-Jamet
edf3525a3f Remove unecessary TODO 2021-03-24 11:17:52 +01:00
random r
2b7fe35305 Translated using Weblate (Italian)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-03-23 21:30:04 +01:00
Vít Šindlář
d5819ea4d0 Translated using Weblate (Czech)
Currently translated at 99.6% (520 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2021-03-23 21:30:04 +01:00
J-Jamet
1099126def Merge branch 'feature/Dynamic_Memory' into feature/Dynamic_Memory_And_Encrypt_Module 2021-03-23 21:24:54 +01:00
J-Jamet
4ba3a797e3 Simpler dataExists 2021-03-23 21:24:30 +01:00
J-Jamet
51b9bb88e5 Merge branch 'feature/Dynamic_Memory' into feature/Dynamic_Memory_And_Encrypt_Module 2021-03-23 21:06:04 +01:00
J-Jamet
fa376148bd Move CustomIconPool in the right package 2021-03-23 21:05:48 +01:00
J-Jamet
452b9677da Merge branch 'feature/Dynamic_Memory' into feature/Dynamic_Memory_And_Encrypt_Module 2021-03-23 21:00:40 +01:00
J-Jamet
02a779f9a2 Fix binary save 2021-03-23 21:00:23 +01:00
J-Jamet
ea60645247 Merge branch 'feature/Encrypt_Module' into feature/Dynamic_Memory_And_Encrypt_Module 2021-03-23 20:25:49 +01:00
J-Jamet
b444a13285 Fix small warnings 2021-03-23 20:24:53 +01:00
J-Jamet
0c6b2a13eb Merge branch 'feature/Encrypt_Module' into feature/Dynamic_Memory_And_Encrypt_Module 2021-03-23 20:23:19 +01:00
J-Jamet
e10bdc1169 Fix clear 2021-03-23 20:02:39 +01:00
J-Jamet
7512cffca3 Code refactoring 2021-03-23 20:00:56 +01:00
J-Jamet
203440e9b8 Better cache management 2021-03-23 19:44:14 +01:00
J-Jamet
145030e854 Refactoring Binary cache 2021-03-23 17:44:07 +01:00
J-Jamet
2b81dfb100 Refactor KeyBinaryByte 2021-03-23 14:57:56 +01:00
J-Jamet
c96ace5281 Add binary byte as an encrypted element of the entire app 2021-03-23 14:45:26 +01:00
J-Jamet
520c6b60be First commit to allocate dynamic memory 2021-03-23 13:07:49 +01:00
J-Jamet
492382d552 Upgrade max binary bytes 2021-03-23 10:57:45 +01:00
J-Jamet
7ca55dd531 Fix themes 2021-03-23 10:38:06 +01:00
J-Jamet
ce4ba73fc4 Replace version by 2.9.15 2021-03-23 09:59:20 +01:00
J-Jamet
5622d92cbb Better native AES KDF method 2021-03-22 22:13:16 +01:00
J-Jamet
8de1c5fd36 Remove unused variables 2021-03-22 21:26:50 +01:00
J-Jamet
6c84fea8dc Update AES and SHA libs 2021-03-22 20:55:27 +01:00
J-Jamet
0b94070086 Simpler AES KDF implementation 2021-03-22 20:36:44 +01:00
J-Jamet
2f209182f5 Replace Argon2 lib 2021-03-22 15:41:57 +01:00
J-Jamet
d7bc572f3e Rename .java to .kt 2021-03-22 14:47:50 +01:00
J-Jamet
4985b49194 Refactor AES tests 2021-03-22 14:46:32 +01:00
J-Jamet
a792df2021 Create timers to calculate Database opening 2021-03-22 12:56:20 +01:00
J-Jamet
a42ec74723 Small refactoring 2021-03-22 11:04:17 +01:00
J-Jamet
de3dbe3b36 Better exception 2021-03-22 10:37:06 +01:00
J-Jamet
874fdb7da0 Refactor KDB cipher and better tests 2021-03-22 10:33:32 +01:00
J-Jamet
3bf0de3888 Refactor ciphers 2021-03-21 16:02:56 +01:00
J-Jamet
d4020c5e0f Better inner random stream implementation 2021-03-21 11:02:52 +01:00
J-Jamet
6175fc00ad Rename .java to .kt 2021-03-21 11:02:52 +01:00
J-Jamet
76e996f429 Remove unused stream 2021-03-21 10:22:05 +01:00
J-Jamet
fd222b73ce Fix unit tests 2021-03-21 10:20:24 +01:00
J-Jamet
a8cc0b1edf Change ObjectNameResource dependencies 2021-03-21 10:06:26 +01:00
J-Jamet
ea2f3545a6 Create encrypt module 2021-03-20 17:35:08 +01:00
Hosted Weblate
0cf136712a Merge branch 'origin/develop' into Weblate. 2021-03-20 14:07:16 +01:00
Milo Ivir
34a453873a Translated using Weblate (Croatian)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2021-03-20 14:07:16 +01:00
Kunzisoft
0acac3b096 Translated using Weblate (French)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2021-03-20 14:07:04 +01:00
J-Jamet
37141410e0 Upgrade to version 3.0.0 2021-03-20 12:03:56 +01:00
J-Jamet
69b0e276e3 Merge tag '2.9.14' into develop
2.9.14
2021-03-20 11:42:44 +01:00
Oymate
ede6070e43 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 4.2% (22 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn_BD/
2021-03-17 08:18:07 +01:00
jan madsen
a61b1d4337 Translated using Weblate (Danish)
Currently translated at 93.8% (490 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2021-03-17 08:18:06 +01:00
WaldiS
0f8c71a9df Translated using Weblate (Polish)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-03-14 13:02:53 +01:00
gnu-ewm
cb0b6e010d Translated using Weblate (Polish)
Currently translated at 99.8% (521 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-03-12 21:02:54 +01:00
Hisikawa Mizuki
23dc7be1ab Translated using Weblate (Japanese)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2021-03-12 21:02:54 +01:00
Oliver Cervera
4b14ad07d2 Translated using Weblate (Italian)
Currently translated at 99.8% (521 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-03-12 21:02:54 +01:00
Oğuz Ersen
8a4bf7896f Translated using Weblate (Turkish)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2021-03-11 17:14:10 +01:00
Allan Nordhøy
208ea29643 Translated using Weblate (Norwegian Bokmål)
Currently translated at 76.6% (400 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2021-03-11 17:14:09 +01:00
Eric
7c52ec731a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2021-03-11 17:14:09 +01:00
Ihor Hordiichuk
c6ee38e435 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2021-03-11 17:14:08 +01:00
solokot
65253cc5b9 Translated using Weblate (Russian)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2021-03-11 17:14:08 +01:00
Stephan Paternotte
d1a1a23cbc Translated using Weblate (Dutch)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2021-03-11 17:14:08 +01:00
HARADA Hiroyuki
e8bb3a5ba7 Translated using Weblate (Japanese)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2021-03-11 17:14:07 +01:00
Retrial
22b8f82770 Translated using Weblate (Greek)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2021-03-11 17:14:07 +01:00
235 changed files with 15620 additions and 16205 deletions

View File

@@ -1,3 +1,12 @@
KeePassDX(2.9.16)
* Fix small bugs #948
KeePassDX(2.9.15)
* Fix themes #935 #926
* Decrease default clipboard time #934
* Better opening performance #929 #933
* Fix memory usage setting #941
KeePassDX(2.9.14)
* Add custom icons #96
* Dark Themes #532 #714

View File

@@ -9,10 +9,10 @@ android {
defaultConfig {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
minSdkVersion 15
targetSdkVersion 30
versionCode = 65
versionName = "2.9.14"
versionCode = 70
versionName = "2.9.16"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -29,12 +29,6 @@ android {
}
}
externalNativeBuild {
cmake {
path "src/main/jni/CMakeLists.txt"
}
}
buildTypes {
release {
minifyEnabled = false
@@ -126,8 +120,6 @@ dependencies {
kapt "androidx.room:room-compiler:$room_version"
// Autofill
implementation "androidx.autofill:autofill:1.1.0"
// Crypto
implementation 'org.bouncycastle:bcprov-jdk15on:1.65.01'
// Time
implementation 'joda-time:joda-time:2.10.6'
// Color
@@ -137,6 +129,8 @@ dependencies {
// Apache Commons
implementation 'commons-io:commons-io:2.8.0'
implementation 'commons-codec:commons-codec:1.15'
// Encrypt lib
implementation project(path: ':crypto')
// Icon pack
implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material')

View File

@@ -1,67 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.crypto
import org.junit.Assert.assertArrayEquals
import java.io.IOException
import java.util.Random
import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.finalkey.AndroidAESKeyTransformer
import com.kunzisoft.keepass.crypto.finalkey.NativeAESKeyTransformer
class AESKeyTest : TestCase() {
private lateinit var mRand: Random
@Throws(Exception::class)
override fun setUp() {
super.setUp()
mRand = Random()
}
@Throws(IOException::class)
fun testAES() {
// Test both an old and an even number to test my flip variable
testAESFinalKey(5)
testAESFinalKey(6)
}
@Throws(IOException::class)
private fun testAESFinalKey(rounds: Long) {
val seed = ByteArray(32)
val key = ByteArray(32)
val nativeKey: ByteArray?
val androidKey: ByteArray?
mRand.nextBytes(seed)
mRand.nextBytes(key)
val androidAESKey = AndroidAESKeyTransformer()
androidKey = androidAESKey.transformMasterKey(seed, key, rounds)
val nativeAESKey = NativeAESKeyTransformer()
nativeKey = nativeAESKey.transformMasterKey(seed, key, rounds)
assertArrayEquals("Does not match", androidKey, nativeKey)
}
}

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.crypto
import com.kunzisoft.keepass.crypto.CipherFactory
import junit.framework.TestCase
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.Random
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.IllegalBlockSizeException
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import org.junit.Assert.assertArrayEquals
class AESTest : TestCase() {
private val mRand = Random()
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, IllegalBlockSizeException::class, BadPaddingException::class, InvalidAlgorithmParameterException::class)
fun testEncrypt() {
// Test above below and at the blocksize
testFinal(15)
testFinal(16)
testFinal(17)
// Test random larger sizes
val size = mRand.nextInt(494) + 18
testFinal(size)
}
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, IllegalBlockSizeException::class, BadPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
private fun testFinal(dataSize: Int) {
// Generate some input
val input = ByteArray(dataSize)
mRand.nextBytes(input)
// Generate key
val keyArray = ByteArray(32)
mRand.nextBytes(keyArray)
val key = SecretKeySpec(keyArray, "AES")
// Generate IV
val ivArray = ByteArray(16)
mRand.nextBytes(ivArray)
val iv = IvParameterSpec(ivArray)
val android = CipherFactory.getInstance("AES/CBC/PKCS5Padding", true)
android.init(Cipher.ENCRYPT_MODE, key, iv)
val outAndroid = android.doFinal(input, 0, dataSize)
val nat = CipherFactory.getInstance("AES/CBC/PKCS5Padding")
nat.init(Cipher.ENCRYPT_MODE, key, iv)
val outNative = nat.doFinal(input, 0, dataSize)
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -19,44 +19,32 @@
*/
package com.kunzisoft.keepass.tests.crypto
import com.kunzisoft.keepass.utils.readBytesLength
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import org.junit.Assert.assertArrayEquals
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.Random
import javax.crypto.BadPaddingException
import java.util.*
import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream
import javax.crypto.IllegalBlockSizeException
import javax.crypto.NoSuchPaddingException
import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.stream.BetterCipherInputStream
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
class CipherTest : TestCase() {
class EncryptionTest {
private val rand = Random()
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidAlgorithmParameterException::class, IllegalBlockSizeException::class, BadPaddingException::class)
@Test
fun testCipherFactory() {
val key = ByteArray(32)
rand.nextBytes(key)
val iv = ByteArray(16)
rand.nextBytes(iv)
val plaintext = ByteArray(1024)
rand.nextBytes(key)
rand.nextBytes(iv)
rand.nextBytes(plaintext)
val aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID)
val aes = EncryptionAlgorithm.AESRijndael.cipherEngine
val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv)
val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv)
@@ -66,20 +54,20 @@ class CipherTest : TestCase() {
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext)
}
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidAlgorithmParameterException::class, IllegalBlockSizeException::class, BadPaddingException::class, IOException::class)
@Test
fun testCipherStreams() {
val MESSAGE_LENGTH = 1024
val length = 1024
val key = ByteArray(32)
val iv = ByteArray(16)
val plaintext = ByteArray(MESSAGE_LENGTH)
rand.nextBytes(key)
val iv = ByteArray(16)
rand.nextBytes(iv)
val plaintext = ByteArray(length)
rand.nextBytes(plaintext)
val aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID)
val aes = EncryptionAlgorithm.AESRijndael.cipherEngine
val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv)
val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv)
@@ -91,10 +79,9 @@ class CipherTest : TestCase() {
val secrettext = bos.toByteArray()
val bis = ByteArrayInputStream(secrettext)
val cis = BetterCipherInputStream(bis, decrypt)
val lis = LittleEndianDataInputStream(cis)
val cis = CipherInputStream(bis, decrypt)
val decrypttext = lis.readBytes(MESSAGE_LENGTH)
val decrypttext = cis.readBytesLength(length)
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext)
}

View File

@@ -2,10 +2,9 @@ package com.kunzisoft.keepass.tests.stream
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.BinaryByte
import com.kunzisoft.keepass.database.element.database.BinaryFile
import com.kunzisoft.keepass.stream.readAllBytes
import com.kunzisoft.keepass.utils.readAllBytes
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryFile
import com.kunzisoft.keepass.utils.UriUtil
import junit.framework.TestCase.assertEquals
import org.junit.Test
@@ -25,11 +24,11 @@ class BinaryDataTest {
private val fileB = File(cacheDirectory, TEST_FILE_CACHE_B)
private val fileC = File(cacheDirectory, TEST_FILE_CACHE_C)
private val loadedKey = Database.LoadedKey.generateNewCipherKey()
private val binaryCache = BinaryCache()
private fun saveBinary(asset: String, binaryData: BinaryFile) {
context.assets.open(asset).use { assetInputStream ->
binaryData.getOutputDataStream(loadedKey).use { binaryOutputStream ->
binaryData.getOutputDataStream(binaryCache).use { binaryOutputStream ->
assetInputStream.readAllBytes(DEFAULT_BUFFER_SIZE) { buffer ->
binaryOutputStream.write(buffer)
}
@@ -65,11 +64,11 @@ class BinaryDataTest {
saveBinary(TEST_TEXT_ASSET, binaryA)
saveBinary(TEST_TEXT_ASSET, binaryB)
saveBinary(TEST_TEXT_ASSET, binaryC)
binaryA.compress(loadedKey)
binaryB.compress(loadedKey)
binaryA.compress(binaryCache)
binaryB.compress(binaryCache)
assertEquals("Compress text length failed.", binaryA.getSize(), binaryB.getSize())
assertEquals("Compress text MD5 failed.", binaryA.binaryHash(), binaryB.binaryHash())
binaryB.decompress(loadedKey)
binaryB.decompress(binaryCache)
assertEquals("Decompress text length failed.", binaryB.getSize(), binaryC.getSize())
assertEquals("Decompress text MD5 failed.", binaryB.binaryHash(), binaryC.binaryHash())
}
@@ -82,29 +81,46 @@ class BinaryDataTest {
saveBinary(TEST_IMAGE_ASSET, binaryA)
saveBinary(TEST_IMAGE_ASSET, binaryB)
saveBinary(TEST_IMAGE_ASSET, binaryC)
binaryA.compress(loadedKey)
binaryB.compress(loadedKey)
binaryA.compress(binaryCache)
binaryB.compress(binaryCache)
assertEquals("Compress image length failed.", binaryA.getSize(), binaryA.getSize())
assertEquals("Compress image failed.", binaryA.binaryHash(), binaryA.binaryHash())
binaryB = BinaryFile(fileB, true)
binaryB.decompress(loadedKey)
binaryB.decompress(binaryCache)
assertEquals("Decompress image length failed.", binaryB.getSize(), binaryC.getSize())
assertEquals("Decompress image failed.", binaryB.binaryHash(), binaryC.binaryHash())
}
@Test
fun testCompressBytes() {
// Test random byte array
val byteArray = ByteArray(50)
Random.nextBytes(byteArray)
val binaryA = BinaryByte(byteArray)
val binaryB = BinaryByte(byteArray)
val binaryC = BinaryByte(byteArray)
binaryA.compress(loadedKey)
binaryB.compress(loadedKey)
testCompressBytes(byteArray)
// Test empty byte array
testCompressBytes(ByteArray(0))
}
private fun testCompressBytes(byteArray: ByteArray) {
val binaryA = binaryCache.getBinaryData("0", true)
binaryA.getOutputDataStream(binaryCache).use { outputStream ->
outputStream.write(byteArray)
}
val binaryB = binaryCache.getBinaryData("1", true)
binaryB.getOutputDataStream(binaryCache).use { outputStream ->
outputStream.write(byteArray)
}
val binaryC = binaryCache.getBinaryData("2", true)
binaryC.getOutputDataStream(binaryCache).use { outputStream ->
outputStream.write(byteArray)
}
binaryA.compress(binaryCache)
binaryB.compress(binaryCache)
assertEquals("Compress bytes decompressed failed.", binaryA.isCompressed, true)
assertEquals("Compress bytes length failed.", binaryA.getSize(), binaryA.getSize())
assertEquals("Compress bytes failed.", binaryA.binaryHash(), binaryA.binaryHash())
binaryB.decompress(loadedKey)
binaryB.decompress(binaryCache)
assertEquals("Decompress bytes decompressed failed.", binaryB.isCompressed, false)
assertEquals("Decompress bytes length failed.", binaryB.getSize(), binaryC.getSize())
assertEquals("Decompress bytes failed.", binaryB.binaryHash(), binaryC.binaryHash())
@@ -115,7 +131,7 @@ class BinaryDataTest {
val binaryA = BinaryFile(fileA)
saveBinary(TEST_TEXT_ASSET, binaryA)
assert(streamAreEquals(context.assets.open(TEST_TEXT_ASSET),
binaryA.getInputDataStream(loadedKey)))
binaryA.getInputDataStream(binaryCache)))
}
@Test
@@ -123,7 +139,7 @@ class BinaryDataTest {
val binaryA = BinaryFile(fileA)
saveBinary(TEST_IMAGE_ASSET, binaryA)
assert(streamAreEquals(context.assets.open(TEST_IMAGE_ASSET),
binaryA.getInputDataStream(loadedKey)))
binaryA.getInputDataStream(binaryCache)))
}
private fun streamAreEquals(inputStreamA: InputStream,

View File

@@ -20,9 +20,7 @@
package com.kunzisoft.keepass.tests.utils
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.utils.*
import junit.framework.TestCase
import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream
@@ -54,7 +52,7 @@ class ValuesTest : TestCase() {
val orig = ByteArray(8)
setArray(orig, value, 8)
assertArrayEquals(orig, longTo8Bytes(bytes64ToLong(orig)))
assertArrayEquals(orig, uLongTo8Bytes(bytes64ToULong(orig)))
}
fun testReadWriteIntZero() {
@@ -133,7 +131,7 @@ class ValuesTest : TestCase() {
}
private fun testReadWriteByte(value: Byte) {
val dest: Byte = UnsignedInt(UnsignedInt.fromKotlinByte(value)).toKotlinByte()
val dest: Byte = UnsignedInt(value.toInt() and 0xFF).toKotlinByte()
assert(value == dest)
}
@@ -144,13 +142,11 @@ class ValuesTest : TestCase() {
expected.set(2008, 1, 2, 3, 4, 5)
val actual = Calendar.getInstance()
dateTo5Bytes(expected.time, cal)?.let { buf ->
actual.time = bytes5ToDate(buf, cal).date
}
actual.time = DateInstant(bytes5ToDate(dateTo5Bytes(expected.time, cal), cal)).date
val jDate = DateInstant(System.currentTimeMillis())
val intermediate = DateInstant(jDate)
val cDate = bytes5ToDate(dateTo5Bytes(intermediate.date)!!)
val cDate = DateInstant(bytes5ToDate(dateTo5Bytes(intermediate.date)))
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR))
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH))
@@ -183,12 +179,10 @@ class ValuesTest : TestCase() {
ulongBytes[i] = -1
}
val bos = ByteArrayOutputStream()
val leos = LittleEndianDataOutputStream(bos)
leos.writeLong(UnsignedLong.MAX_VALUE)
leos.close()
val uLongMax = bos.toByteArray()
val byteArrayOutputStream = ByteArrayOutputStream()
byteArrayOutputStream.write(UnsignedLong.MAX_BYTES)
byteArrayOutputStream.close()
val uLongMax = byteArrayOutputStream.toByteArray()
assertArrayEquals(ulongBytes, uLongMax)
}

View File

@@ -39,6 +39,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
@@ -93,6 +94,8 @@ class EntryActivity : LockingActivity() {
private var clipboardHelper: ClipboardHelper? = null
private var mFirstLaunchOfActivity: Boolean = false
private var mExternalFileHelper: ExternalFileHelper? = null
private var iconColor: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
@@ -125,7 +128,7 @@ class EntryActivity : LockingActivity() {
historyView = findViewById(R.id.history_container)
entryContentsView = findViewById(R.id.entry_contents)
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
entryContentsView?.setAttachmentCipherKey(mDatabase?.loadedCipherKey)
entryContentsView?.setAttachmentCipherKey(mDatabase)
entryProgress = findViewById(R.id.entry_progress)
lockView = findViewById(R.id.lock_button)
@@ -140,6 +143,9 @@ class EntryActivity : LockingActivity() {
clipboardHelper = ClipboardHelper(this)
mFirstLaunchOfActivity = savedInstanceState?.getBoolean(KEY_FIRST_LAUNCH_ACTIVITY) ?: true
// Init SAF manager
mExternalFileHelper = ExternalFileHelper(this)
// Init attachment service binder manager
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
@@ -344,7 +350,7 @@ class EntryActivity : LockingActivity() {
// Manage attachments
entryContentsView?.assignAttachments(entryInfo.attachments.toSet(), StreamDirection.DOWNLOAD) { attachmentItem ->
createDocument(this, attachmentItem.name)?.let { requestCode ->
mExternalFileHelper?.createDocument(attachmentItem.name)?.let { requestCode ->
mAttachmentsToDownload[requestCode] = attachmentItem
}
}
@@ -380,7 +386,7 @@ class EntryActivity : LockingActivity() {
}
}
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
if (createdFileUri != null) {
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
mAttachmentFileBinderManager

View File

@@ -45,7 +45,7 @@ import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE
import com.kunzisoft.keepass.activities.fragments.EntryEditFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.autofill.AutofillComponent
@@ -103,7 +103,7 @@ class EntryEditActivity : LockingActivity(),
private var lockView: View? = null
// To manage attachments
private var mSelectFileHelper: SelectFileHelper? = null
private var mExternalFileHelper: ExternalFileHelper? = null
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAllowMultipleAttachments: Boolean = false
private var mTempAttachments = ArrayList<EntryAttachmentState>()
@@ -202,7 +202,7 @@ class EntryEditActivity : LockingActivity(),
// Build fragment to manage entry modification
entryEditFragment = supportFragmentManager.findFragmentByTag(ENTRY_EDIT_FRAGMENT_TAG) as? EntryEditFragment?
if (entryEditFragment == null) {
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo, mDatabase?.loadedCipherKey)
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo)
}
supportFragmentManager.beginTransaction()
.replace(R.id.entry_edit_contents, entryEditFragment!!, ENTRY_EDIT_FRAGMENT_TAG)
@@ -241,7 +241,7 @@ class EntryEditActivity : LockingActivity(),
}
// To retrieve attachment
mSelectFileHelper = SelectFileHelper(this)
mExternalFileHelper = ExternalFileHelper(this)
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
// Save button
@@ -458,8 +458,8 @@ class EntryEditActivity : LockingActivity(),
/**
* Add a new attachment
*/
private fun addNewAttachment(item: MenuItem) {
mSelectFileHelper?.selectFileOnClickViewListener?.onMenuItemClick(item)
private fun addNewAttachment() {
mExternalFileHelper?.openDocument()
}
override fun onValidateUploadFileTooBig(attachmentToUploadUri: Uri?, fileName: String?) {
@@ -485,7 +485,7 @@ class EntryEditActivity : LockingActivity(),
private fun buildNewAttachment(attachmentToUploadUri: Uri, fileName: String) {
val compression = mDatabase?.compressionForNewEntry() ?: false
mDatabase?.buildNewBinaryAttachment(UriUtil.getBinaryDir(this), compression)?.let { binaryAttachment ->
mDatabase?.buildNewBinaryAttachment(compression)?.let { binaryAttachment ->
val entryAttachment = Attachment(fileName, binaryAttachment)
// Ask to replace the current attachment
if ((mDatabase?.allowMultipleAttachments != true && entryEditFragment?.containsAttachment() == true) ||
@@ -505,7 +505,7 @@ class EntryEditActivity : LockingActivity(),
entryEditFragment?.icon = icon
}
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
mExternalFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
uri?.let { attachmentToUploadUri ->
UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile ->
documentFile.name?.let { fileName ->
@@ -655,7 +655,7 @@ class EntryEditActivity : LockingActivity(),
&& entryEditActivityEducation.checkAndPerformedAttachmentEducation(
attachmentView,
{
mSelectFileHelper?.selectFileOnClickViewListener?.onClick(attachmentView)
mExternalFileHelper?.openDocument()
},
{
performedNextEducation(entryEditActivityEducation)
@@ -683,7 +683,7 @@ class EntryEditActivity : LockingActivity(),
return true
}
R.id.menu_add_attachment -> {
addNewAttachment(item)
addNewAttachment()
return true
}
R.id.menu_add_otp -> {

View File

@@ -42,8 +42,9 @@ import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
@@ -82,7 +83,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
private var mDatabaseFileUri: Uri? = null
private var mSelectFileHelper: SelectFileHelper? = null
private var mExternalFileHelper: ExternalFileHelper? = null
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
@@ -103,14 +104,9 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
createDatabaseButtonView?.setOnClickListener { createNewFile() }
// Open database button
mSelectFileHelper = SelectFileHelper(this)
mExternalFileHelper = ExternalFileHelper(this)
openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
openDatabaseButtonView?.apply {
mSelectFileHelper?.selectFileOnClickViewListener?.let {
setOnClickListener(it)
setOnLongClickListener(it)
}
}
openDatabaseButtonView?.setOpenDocumentClickListener(mExternalFileHelper)
// History list
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
@@ -162,29 +158,31 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
// Observe list of databases
databaseFilesViewModel.databaseFilesLoaded.observe(this) { databaseFiles ->
when (databaseFiles.databaseFileAction) {
DatabaseFilesViewModel.DatabaseFileAction.NONE -> {
mAdapterDatabaseHistory?.replaceAllDatabaseFileHistoryList(databaseFiles.databaseFileList)
}
DatabaseFilesViewModel.DatabaseFileAction.ADD -> {
databaseFiles.databaseFileToActivate?.let { databaseFileToAdd ->
mAdapterDatabaseHistory?.addDatabaseFileHistory(databaseFileToAdd)
try {
when (databaseFiles.databaseFileAction) {
DatabaseFilesViewModel.DatabaseFileAction.NONE -> {
mAdapterDatabaseHistory?.replaceAllDatabaseFileHistoryList(databaseFiles.databaseFileList)
}
GroupActivity.launch(this@FileDatabaseSelectActivity,
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity))
}
DatabaseFilesViewModel.DatabaseFileAction.UPDATE -> {
databaseFiles.databaseFileToActivate?.let { databaseFileToUpdate ->
mAdapterDatabaseHistory?.updateDatabaseFileHistory(databaseFileToUpdate)
}
}
DatabaseFilesViewModel.DatabaseFileAction.DELETE -> {
databaseFiles.databaseFileToActivate?.let { databaseFileToDelete ->
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileToDelete)
DatabaseFilesViewModel.DatabaseFileAction.ADD -> {
databaseFiles.databaseFileToActivate?.let { databaseFileToAdd ->
mAdapterDatabaseHistory?.addDatabaseFileHistory(databaseFileToAdd)
}
}
DatabaseFilesViewModel.DatabaseFileAction.UPDATE -> {
databaseFiles.databaseFileToActivate?.let { databaseFileToUpdate ->
mAdapterDatabaseHistory?.updateDatabaseFileHistory(databaseFileToUpdate)
}
}
DatabaseFilesViewModel.DatabaseFileAction.DELETE -> {
databaseFiles.databaseFileToActivate?.let { databaseFileToDelete ->
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileToDelete)
}
}
}
databaseFilesViewModel.consumeAction()
} catch (e: Exception) {
Log.e(TAG, "Unable to observe database action", e)
}
databaseFilesViewModel.consumeAction()
}
// Observe default database
@@ -202,6 +200,8 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential()
databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri)
}
GroupActivity.launch(this@FileDatabaseSelectActivity,
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity))
}
ACTION_DATABASE_LOAD_TASK -> {
val database = Database.getInstance()
@@ -230,7 +230,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
* Create a new file by calling the content provider
*/
private fun createNewFile() {
createDocument(this, getString(R.string.database_file_name_default) +
mExternalFileHelper?.createDocument( getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default), "application/x-keepass")
}
@@ -282,7 +282,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
// Show open and create button or special mode
when (mSpecialMode) {
SpecialMode.DEFAULT -> {
if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
if (ExternalFileHelper.allowCreateDocumentByStorageAccessFramework(packageManager)) {
// There is an activity which can handle this intent.
createDatabaseButtonView?.visibility = View.VISIBLE
} else{
@@ -355,14 +355,14 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
}
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
mExternalFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
if (uri != null) {
launchPasswordActivityWithPath(uri)
}
}
// Retrieve the created URI from the file manager
onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true)
@@ -390,9 +390,10 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
// If no recent files
val createDatabaseEducationPerformed =
createDatabaseButtonView != null && createDatabaseButtonView!!.visibility == View.VISIBLE
createDatabaseButtonView != null
&& createDatabaseButtonView!!.visibility == View.VISIBLE
&& mAdapterDatabaseHistory != null
&& mAdapterDatabaseHistory!!.itemCount > 0
&& mAdapterDatabaseHistory!!.itemCount == 0
&& fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
createDatabaseButtonView!!,
{
@@ -407,9 +408,9 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
openDatabaseButtonView != null
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
openDatabaseButtonView!!,
{tapTargetView ->
{ tapTargetView ->
tapTargetView?.let {
mSelectFileHelper?.selectFileOnClickViewListener?.onClick(it)
mExternalFileHelper?.openDocument()
}
},
{}

View File

@@ -34,7 +34,8 @@ import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.IconPickerFragment
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.database.element.Database
@@ -66,7 +67,7 @@ class IconPickerActivity : LockingActivity() {
private var mDatabase: Database? = null
private var mSelectFileHelper: SelectFileHelper? = null
private var mExternalFileHelper: ExternalFileHelper? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -84,15 +85,11 @@ class IconPickerActivity : LockingActivity() {
coordinatorLayout = findViewById(R.id.icon_picker_coordinator)
mExternalFileHelper = ExternalFileHelper(this)
uploadButton = findViewById(R.id.icon_picker_upload)
if (mDatabase?.allowCustomIcons == true) {
uploadButton.setOnClickListener {
mSelectFileHelper?.selectFileOnClickViewListener?.onClick(it)
}
uploadButton.setOnLongClickListener {
mSelectFileHelper?.selectFileOnClickViewListener?.onLongClick(it)
true
}
uploadButton.setOpenDocumentClickListener(mExternalFileHelper)
} else {
uploadButton.visibility = View.GONE
}
@@ -124,8 +121,6 @@ class IconPickerActivity : LockingActivity() {
// Focus view to reinitialize timeout
findViewById<ViewGroup>(R.id.icon_picker_container)?.resetAppTimeoutWhenViewFocusedOrChanged(this)
mSelectFileHelper = SelectFileHelper(this)
iconPickerViewModel.standardIconPicked.observe(this) { iconStandard ->
mIconImage.standard = iconStandard
// Remove the custom icon if a standard one is selected
@@ -229,21 +224,26 @@ class IconPickerActivity : LockingActivity() {
if (documentFile.length() > MAX_ICON_SIZE) {
iconCustomState.errorStringId = R.string.error_file_to_big
} else {
mDatabase?.buildNewCustomIcon(UriUtil.getBinaryDir(this@IconPickerActivity)) { customIcon, binary ->
mDatabase?.buildNewCustomIcon { customIcon, binary ->
if (customIcon != null) {
iconCustomState.iconCustom = customIcon
BinaryDatabaseManager.resizeBitmapAndStoreDataInBinaryFile(contentResolver,
iconToUploadUri, binary)
when {
binary == null -> {
}
binary.getSize() <= 0 -> {
}
mDatabase?.isCustomIconBinaryDuplicate(binary) == true -> {
iconCustomState.errorStringId = R.string.error_duplicate_file
}
else -> {
iconCustomState.error = false
mDatabase?.let { database ->
BinaryDatabaseManager.resizeBitmapAndStoreDataInBinaryFile(
contentResolver,
database,
iconToUploadUri,
binary)
when {
binary == null -> {
}
binary.getSize() <= 0 -> {
}
database.isCustomIconBinaryDuplicate(binary) -> {
iconCustomState.errorStringId = R.string.error_duplicate_file
}
else -> {
iconCustomState.error = false
}
}
}
if (iconCustomState.error) {
@@ -276,7 +276,7 @@ class IconPickerActivity : LockingActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
mExternalFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
addCustomIcon(uri)
}
}

View File

@@ -39,6 +39,8 @@ import kotlin.math.max
class ImageViewerActivity : LockingActivity() {
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -59,23 +61,29 @@ class ImageViewerActivity : LockingActivity() {
resources.displayMetrics.heightPixels * 2
)
mDatabase = Database.getInstance()
try {
progressView.visibility = View.VISIBLE
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
supportActionBar?.title = attachment.name
supportActionBar?.subtitle = Formatter.formatFileSize(this, attachment.binaryData.getSize())
BinaryDatabaseManager.loadBitmap(
attachment.binaryData,
Database.getInstance().loadedCipherKey,
mImagePreviewMaxWidth
) { bitmapLoaded ->
if (bitmapLoaded == null) {
finish()
} else {
progressView.visibility = View.GONE
imageView.setImageBitmap(bitmapLoaded)
val size = attachment.binaryData.getSize()
supportActionBar?.subtitle = Formatter.formatFileSize(this, size)
mDatabase?.let { database ->
BinaryDatabaseManager.loadBitmap(
database,
attachment.binaryData,
mImagePreviewMaxWidth
) { bitmapLoaded ->
if (bitmapLoaded == null) {
finish()
} else {
progressView.visibility = View.GONE
imageView.setImageBitmap(bitmapLoaded)
}
}
}
} ?: finish()

View File

@@ -42,10 +42,7 @@ import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.helpers.*
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
@@ -95,7 +92,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
private var mDatabaseKeyFileUri: Uri? = null
private var mRememberKeyFile: Boolean = false
private var mSelectFileHelper: SelectFileHelper? = null
private var mExternalFileHelper: ExternalFileHelper? = null
private var mPermissionAsked = false
private var readOnly: Boolean = false
@@ -138,13 +135,8 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
mSelectFileHelper = SelectFileHelper(this@PasswordActivity)
keyFileSelectionView?.apply {
mSelectFileHelper?.selectFileOnClickViewListener?.let {
setOnClickListener(it)
setOnLongClickListener(it)
}
}
mExternalFileHelper = ExternalFileHelper(this@PasswordActivity)
keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
passwordView?.setOnEditorActionListener(onEditorActionListener)
passwordView?.addTextChangedListener(object : TextWatcher {
@@ -702,7 +694,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
}
var keyFileResult = false
mSelectFileHelper?.let {
mExternalFileHelper?.let {
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
) { uri ->
if (uri != null) {

View File

@@ -30,13 +30,13 @@ import android.text.SpannableStringBuilder
import android.text.TextWatcher
import android.view.View
import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.KeyFileSelectionView
@@ -60,7 +60,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
private var mListener: AssignPasswordDialogListener? = null
private var mSelectFileHelper: SelectFileHelper? = null
private var mExternalFileHelper: ExternalFileHelper? = null
private var mEmptyPasswordConfirmationDialog: AlertDialog? = null
private var mNoKeyConfirmationDialog: AlertDialog? = null
@@ -133,11 +133,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
mSelectFileHelper = SelectFileHelper(this)
keyFileSelectionView?.apply {
setOnClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
setOnLongClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
}
mExternalFileHelper = ExternalFileHelper(this)
keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
val dialog = builder.create()
@@ -289,7 +286,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
mExternalFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
uri?.let { pathUri ->
UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile ->
keyFileSelectionView?.error = null

View File

@@ -84,7 +84,6 @@ class EntryEditFragment: StylishFragment() {
// Elements to modify the current entry
private var mEntryInfo = EntryInfo()
private var mBinaryCipherKey: Database.LoadedKey? = null
private var mLastFocusedEditField: FocusedEditField? = null
private var mExtraViewToRequestFocus: EditText? = null
@@ -122,7 +121,9 @@ class EntryEditFragment: StylishFragment() {
attachmentsContainerView = rootView.findViewById(R.id.entry_attachments_container)
attachmentsListView = rootView.findViewById(R.id.entry_attachments_list)
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
attachmentsAdapter.binaryCipherKey = arguments?.getSerializable(KEY_BINARY_CIPHER_KEY) as? Database.LoadedKey?
// TODO retrieve current database with its unique key
attachmentsAdapter.database = Database.getInstance()
//attachmentsAdapter.database = arguments?.getInt(KEY_DATABASE)
attachmentsAdapter.onListSizeChangedListener = { previousSize, newSize ->
if (previousSize > 0 && newSize == 0) {
attachmentsContainerView.collapse(true)
@@ -502,7 +503,6 @@ class EntryEditFragment: StylishFragment() {
override fun onSaveInstanceState(outState: Bundle) {
populateEntryWithViews()
outState.putParcelable(KEY_TEMP_ENTRY_INFO, mEntryInfo)
outState.putSerializable(KEY_BINARY_CIPHER_KEY, mBinaryCipherKey)
outState.putParcelable(KEY_LAST_FOCUSED_FIELD, mLastFocusedEditField)
super.onSaveInstanceState(outState)
@@ -510,15 +510,16 @@ class EntryEditFragment: StylishFragment() {
companion object {
const val KEY_TEMP_ENTRY_INFO = "KEY_TEMP_ENTRY_INFO"
const val KEY_BINARY_CIPHER_KEY = "KEY_BINARY_CIPHER_KEY"
const val KEY_DATABASE = "KEY_DATABASE"
const val KEY_LAST_FOCUSED_FIELD = "KEY_LAST_FOCUSED_FIELD"
fun getInstance(entryInfo: EntryInfo?,
loadedKey: Database.LoadedKey?): EntryEditFragment {
fun getInstance(entryInfo: EntryInfo?): EntryEditFragment {
//database: Database?): EntryEditFragment {
return EntryEditFragment().apply {
arguments = Bundle().apply {
putParcelable(KEY_TEMP_ENTRY_INFO, entryInfo)
putSerializable(KEY_BINARY_CIPHER_KEY, loadedKey)
// TODO Unique database key database.key
putInt(KEY_DATABASE, 0)
}
}
}

View File

@@ -20,30 +20,25 @@
package com.kunzisoft.keepass.activities.helpers
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.util.Log
import android.view.MenuItem
import android.view.View
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
import com.kunzisoft.keepass.utils.UriUtil
class SelectFileHelper {
class ExternalFileHelper {
private var activity: Activity? = null
private var activity: FragmentActivity? = null
private var fragment: Fragment? = null
val selectFileOnClickViewListener: SelectFileOnClickViewListener
get() = SelectFileOnClickViewListener()
constructor(context: Activity) {
constructor(context: FragmentActivity) {
this.activity = context
this.fragment = null
}
@@ -53,56 +48,33 @@ class SelectFileHelper {
this.fragment = context
}
inner class SelectFileOnClickViewListener :
View.OnClickListener,
View.OnLongClickListener,
MenuItem.OnMenuItemClickListener {
private fun onAbstractClick(longClick: Boolean = false) {
fun openDocument(getContent: Boolean = false,
typeString: String = "*/*") {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
if (longClick) {
try {
openActivityWithActionGetContent()
} catch (e: Exception) {
openActivityWithActionOpenDocument()
}
if (getContent) {
openActivityWithActionGetContent(typeString)
} else {
try {
openActivityWithActionOpenDocument()
} catch (e: Exception) {
openActivityWithActionGetContent()
}
openActivityWithActionOpenDocument(typeString)
}
} catch (e: Exception) {
Log.e(TAG, "Enable to start the file picker activity", e)
// Open browser dialog
if (lookForOpenIntentsFilePicker())
showBrowserDialog()
Log.e(TAG, "Unable to open document", e)
showFileManagerDialogFragment()
}
}
override fun onClick(v: View) {
onAbstractClick()
}
override fun onLongClick(v: View?): Boolean {
onAbstractClick(true)
return true
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
onAbstractClick()
return true
} else {
showFileManagerDialogFragment()
}
}
@SuppressLint("InlinedApi")
private fun openActivityWithActionOpenDocument() {
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun openActivityWithActionOpenDocument(typeString: String) {
val intentOpenDocument = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
type = typeString
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
}
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
@@ -112,13 +84,15 @@ class SelectFileHelper {
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
}
@SuppressLint("InlinedApi")
private fun openActivityWithActionGetContent() {
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun openActivityWithActionGetContent(typeString: String) {
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
type = typeString
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
}
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
@@ -128,69 +102,12 @@ class SelectFileHelper {
activity?.startActivityForResult(intentGetContent, GET_CONTENT)
}
private fun lookForOpenIntentsFilePicker(): Boolean {
var showBrowser = false
try {
if (isIntentAvailable(activity!!, OPEN_INTENTS_FILE_BROWSE)) {
val intent = Intent(OPEN_INTENTS_FILE_BROWSE)
if (fragment != null)
fragment?.startActivityForResult(intent, FILE_BROWSE)
else
activity?.startActivityForResult(intent, FILE_BROWSE)
} else {
showBrowser = true
}
} catch (e: Exception) {
Log.w(TAG, "Enable to start OPEN_INTENTS_FILE_BROWSE", e)
showBrowser = true
}
return showBrowser
}
/**
* Indicates whether the specified action can be used as an intent. This
* method queries the package manager for installed packages that can
* respond to an intent with the specified action. If no suitable package is
* found, this method returns false.
*
* @param context The application's environment.
* @param action The Intent action to check for availability.
*
* @return True if an Intent with the specified action can be sent and
* responded to, false otherwise.
*/
private fun isIntentAvailable(context: Context, action: String): Boolean {
val packageManager = context.packageManager
val intent = Intent(action)
val list = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY)
return list.size > 0
}
/**
* Show Browser dialog to select file picker app
*/
private fun showBrowserDialog() {
try {
val fileManagerDialogFragment = FileManagerDialogFragment()
fragment?.let {
fileManagerDialogFragment.show(it.parentFragmentManager, "browserDialog")
} ?: fileManagerDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
} catch (e: Exception) {
Log.e(TAG, "Can't open BrowserDialog", e)
}
}
/**
* To use in onActivityResultCallback in Fragment or Activity
* @param keyFileCallback Callback retrieve from data
* @return true if requestCode was captured, false elsechere
*/
fun onActivityResultCallback(
requestCode: Int,
resultCode: Int,
data: Intent?,
fun onActivityResultCallback(requestCode: Int, resultCode: Int, data: Intent?,
keyFileCallback: ((uri: Uri?) -> Unit)?): Boolean {
when (requestCode) {
@@ -231,14 +148,102 @@ class SelectFileHelper {
return false
}
/**
* Show Browser dialog to select file picker app
*/
private fun showFileManagerDialogFragment() {
try {
if (fragment != null) {
fragment?.parentFragmentManager
} else {
activity?.supportFragmentManager
}?.let { fragmentManager ->
FileManagerDialogFragment().show(fragmentManager, "browserDialog")
}
} catch (e: Exception) {
Log.e(TAG, "Can't open BrowserDialog", e)
}
}
fun createDocument(titleString: String,
typeString: String = "application/octet-stream"): Int? {
val idCode = getUnusedCreateFileRequestCode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
putExtra(Intent.EXTRA_TITLE, titleString)
}
if (fragment != null)
fragment?.startActivityForResult(intent, idCode)
else
activity?.startActivityForResult(intent, idCode)
return idCode
} catch (e: Exception) {
Log.e(TAG, "Unable to create document", e)
showFileManagerDialogFragment()
}
} else {
showFileManagerDialogFragment()
}
return null
}
fun onCreateDocumentResult(requestCode: Int, resultCode: Int, data: Intent?,
action: (fileCreated: Uri?)->Unit) {
// Retrieve the created URI from the file manager
if (fileRequestCodes.contains(requestCode) && resultCode == RESULT_OK) {
action.invoke(data?.data)
fileRequestCodes.remove(requestCode)
}
}
companion object {
private const val TAG = "OpenFileHelper"
const val OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"
private const val GET_CONTENT = 25745
private const val OPEN_DOC = 25845
private const val FILE_BROWSE = 25645
private var CREATE_FILE_REQUEST_CODE_DEFAULT = 3853
private var fileRequestCodes = ArrayList<Int>()
private fun getUnusedCreateFileRequestCode(): Int {
val newCreateFileRequestCode = CREATE_FILE_REQUEST_CODE_DEFAULT++
fileRequestCodes.add(newCreateFileRequestCode)
return newCreateFileRequestCode
}
@SuppressLint("InlinedApi")
fun allowCreateDocumentByStorageAccessFramework(packageManager: PackageManager,
typeString: String = "application/octet-stream"): Boolean {
return when {
// To check if a custom file manager can manage the ACTION_CREATE_DOCUMENT
Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT -> {
packageManager.queryIntentActivities(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
}, PackageManager.MATCH_DEFAULT_ONLY).isNotEmpty()
}
else -> true
}
}
}
}
fun View.setOpenDocumentClickListener(externalFileHelper: ExternalFileHelper?) {
externalFileHelper?.let { fileHelper ->
setOnClickListener {
fileHelper.openDocument()
}
setOnLongClickListener {
fileHelper.openDocument(true)
true
}
} ?: kotlin.run {
setOnClickListener(null)
setOnLongClickListener(null)
}
}

View File

@@ -45,7 +45,7 @@ import kotlin.math.max
class EntryAttachmentsItemsAdapter(context: Context)
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
var binaryCipherKey: Database.LoadedKey? = null
var database: Database? = null
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
var onBinaryPreviewLoaded: ((item: EntryAttachmentState) -> Unit)? = null
@@ -82,21 +82,23 @@ class EntryAttachmentsItemsAdapter(context: Context)
if (entryAttachmentState.previewState == AttachmentState.NULL) {
entryAttachmentState.previewState = AttachmentState.IN_PROGRESS
// Load the bitmap image
BinaryDatabaseManager.loadBitmap(
entryAttachmentState.attachment.binaryData,
binaryCipherKey,
mImagePreviewMaxWidth
) { imageLoaded ->
if (imageLoaded == null) {
entryAttachmentState.previewState = AttachmentState.ERROR
visibility = View.GONE
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
} else {
entryAttachmentState.previewState = AttachmentState.COMPLETE
setImageBitmap(imageLoaded)
if (visibility != View.VISIBLE) {
expand(true, resources.getDimensionPixelSize(R.dimen.item_file_info_height)) {
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
database?.let { database ->
BinaryDatabaseManager.loadBitmap(
database,
entryAttachmentState.attachment.binaryData,
mImagePreviewMaxWidth
) { imageLoaded ->
if (imageLoaded == null) {
entryAttachmentState.previewState = AttachmentState.ERROR
visibility = View.GONE
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
} else {
entryAttachmentState.previewState = AttachmentState.COMPLETE
setImageBitmap(imageLoaded)
if (visibility != View.VISIBLE) {
expand(true, resources.getDimensionPixelSize(R.dimen.item_file_info_height)) {
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
}
}
}
}
@@ -123,8 +125,9 @@ class EntryAttachmentsItemsAdapter(context: Context)
} else {
holder.binaryFileTitle.setTextColor(mTitleColor)
}
holder.binaryFileSize.text = Formatter.formatFileSize(context,
entryAttachmentState.attachment.binaryData.getSize())
val size = entryAttachmentState.attachment.binaryData.getSize()
holder.binaryFileSize.text = Formatter.formatFileSize(context, size)
holder.binaryFileCompression.apply {
if (entryAttachmentState.attachment.binaryData.isCompressed) {
text = CompressionAlgorithm.GZip.getName(context.resources)

View File

@@ -25,6 +25,7 @@ import android.content.Intent
import android.content.ServiceConnection
import android.net.Uri
import android.os.IBinder
import android.util.Log
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.SingletonHolderParameter
@@ -76,7 +77,11 @@ class CipherDatabaseAction(context: Context) {
mServiceConnection!!,
Context.BIND_ABOVE_CLIENT)
if (mBinder == null) {
applicationContext.startService(mIntentAdvancedUnlockService)
try {
applicationContext.startService(mIntentAdvancedUnlockService)
} catch (e: Exception) {
Log.e(TAG, "Unable to start cipher action", e)
}
}
}
}
@@ -173,5 +178,7 @@ class CipherDatabaseAction(context: Context) {
).execute()
}
companion object : SingletonHolderParameter<CipherDatabaseAction, Context>(::CipherDatabaseAction)
companion object : SingletonHolderParameter<CipherDatabaseAction, Context>(::CipherDatabaseAction) {
private val TAG = CipherDatabaseAction::class.java.name
}
}

View File

@@ -1,79 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto
import android.os.Build
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.ChaCha20Engine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.engine.TwofishEngine
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.NoSuchAlgorithmException
import java.security.Security
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
object CipherFactory {
private var blacklistInit = false
private var blacklisted: Boolean = false
init {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
Security.addProvider(BouncyCastleProvider())
}
fun deviceBlacklisted(): Boolean {
if (!blacklistInit) {
blacklistInit = true
// The Acer Iconia A500 is special and seems to always crash in the native crypto libraries
blacklisted = Build.MODEL == "A500"
}
return blacklisted
}
private fun hasNativeImplementation(transformation: String): Boolean {
return transformation == "AES/CBC/PKCS5Padding"
}
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class)
fun getInstance(transformation: String, androidOverride: Boolean = false): Cipher {
// Return the native AES if it is possible
return if (!deviceBlacklisted() && !androidOverride && hasNativeImplementation(transformation) && NativeLib.loaded()) {
Cipher.getInstance(transformation, AESProvider())
} else {
Cipher.getInstance(transformation)
}
}
/**
* Generate appropriate cipher based on KeePass 2.x UUID's
*/
@Throws(NoSuchAlgorithmException::class)
fun getInstance(uuid: UUID): CipherEngine {
return when (uuid) {
AesEngine.CIPHER_UUID -> AesEngine()
TwofishEngine.CIPHER_UUID -> TwofishEngine()
ChaCha20Engine.CIPHER_UUID -> ChaCha20Engine()
else -> throw NoSuchAlgorithmException("UUID unrecognized.")
}
}
}

View File

@@ -1,105 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto
import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.stream.longTo8Bytes
import java.io.IOException
import java.security.DigestOutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
import javax.crypto.Mac
import kotlin.math.min
object CryptoUtil {
fun resizeKey(inBytes: ByteArray, inOffset: Int, cbIn: Int, cbOut: Int): ByteArray {
if (cbOut == 0) return ByteArray(0)
val hash: ByteArray = if (cbOut <= 32) {
hashSha256(inBytes, inOffset, cbIn)
} else {
hashSha512(inBytes, inOffset, cbIn)
}
if (cbOut == hash.size) {
return hash
}
val ret = ByteArray(cbOut)
if (cbOut < hash.size) {
System.arraycopy(hash, 0, ret, 0, cbOut)
} else {
var pos = 0
var r: Long = 0
while (pos < cbOut) {
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
}
val pbR = longTo8Bytes(r)
val part = hmac.doFinal(pbR)
val copy = min(cbOut - pos, part.size)
System.arraycopy(part, 0, ret, pos, copy)
pos += copy
r++
Arrays.fill(part, 0.toByte())
}
}
Arrays.fill(hash, 0.toByte())
return ret
}
fun hashSha256(data: ByteArray, offset: Int = 0, count: Int = data.size): ByteArray {
return hashGen("SHA-256", data, offset, count)
}
fun hashSha512(data: ByteArray, offset: Int = 0, count: Int = data.size): ByteArray {
return hashGen("SHA-512", data, offset, count)
}
private fun hashGen(transform: String, data: ByteArray, offset: Int, count: Int): ByteArray {
val hash: MessageDigest
try {
hash = MessageDigest.getInstance(transform)
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
}
val nos = NullOutputStream()
val dos = DigestOutputStream(nos, hash)
try {
dos.write(data, offset, count)
dos.close()
} catch (e: IOException) {
throw RuntimeException(e)
}
return hash.digest()
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto
import org.bouncycastle.crypto.StreamCipher
import org.bouncycastle.crypto.engines.ChaCha7539Engine
import org.bouncycastle.crypto.engines.Salsa20Engine
import org.bouncycastle.crypto.params.KeyParameter
import org.bouncycastle.crypto.params.ParametersWithIV
object StreamCipherFactory {
private val SALSA_IV = byteArrayOf(0xE8.toByte(), 0x30, 0x09, 0x4B, 0x97.toByte(), 0x20, 0x5D, 0x2A)
@Throws(Exception::class)
fun getInstance(alg: CrsAlgorithm?, key: ByteArray): StreamCipher {
return when {
alg === CrsAlgorithm.Salsa20 -> getSalsa20(key)
alg === CrsAlgorithm.ChaCha20 -> getChaCha20(key)
else -> throw Exception("Invalid random cipher")
}
}
private fun getSalsa20(key: ByteArray): StreamCipher {
// Build stream cipher key
val key32 = CryptoUtil.hashSha256(key)
val keyParam = KeyParameter(key32)
val ivParam = ParametersWithIV(keyParam, SALSA_IV)
val cipher = Salsa20Engine()
cipher.init(true, ivParam)
return cipher
}
private fun getChaCha20(key: ByteArray): StreamCipher {
// Build stream cipher key
val hash = CryptoUtil.hashSha512(key)
val key32 = ByteArray(32)
val iv = ByteArray(12)
System.arraycopy(hash, 0, key32, 0, 32)
System.arraycopy(hash, 32, iv, 0, 12)
val keyParam = KeyParameter(key32)
val ivParam = ParametersWithIV(keyParam, iv)
val cipher = ChaCha7539Engine()
cipher.init(true, ivParam)
return cipher
}
}

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class AesEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray, androidOverride: Boolean): Cipher {
val cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding", androidOverride)
cipher.init(opmode, SecretKeySpec(key, "AES"), IvParameterSpec(IV))
return cipher
}
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.AESRijndael
}
companion object {
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0x31.toByte(),
0xC1.toByte(),
0xF2.toByte(),
0xE6.toByte(),
0xBF.toByte(),
0x71.toByte(),
0x43.toByte(),
0x50.toByte(),
0xBE.toByte(),
0x58.toByte(),
0x05.toByte(),
0x21.toByte(),
0x6A.toByte(),
0xFC.toByte(),
0x5A.toByte(),
0xFF.toByte()))
}
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class TwofishEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray, androidOverride: Boolean): Cipher {
val cipher: Cipher = if (opmode == Cipher.ENCRYPT_MODE) {
CipherFactory.getInstance("Twofish/CBC/ZeroBytePadding", androidOverride)
} else {
CipherFactory.getInstance("Twofish/CBC/NoPadding", androidOverride)
}
cipher.init(opmode, SecretKeySpec(key, "AES"), IvParameterSpec(IV))
return cipher
}
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.Twofish
}
companion object {
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xAD.toByte(),
0x68.toByte(),
0xF2.toByte(),
0x9F.toByte(),
0x57.toByte(),
0x6F.toByte(),
0x4B.toByte(),
0xB9.toByte(),
0xA3.toByte(),
0x6A.toByte(),
0xD4.toByte(),
0x7A.toByte(),
0xF9.toByte(),
0x65.toByte(),
0x34.toByte(),
0x6C.toByte()))
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey
import com.kunzisoft.keepass.crypto.CipherFactory.deviceBlacklisted
object AESKeyTransformerFactory : KeyTransformer() {
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
// Prefer the native final key implementation
val keyTransformer = if (!deviceBlacklisted()
&& NativeAESKeyTransformer.available()) {
NativeAESKeyTransformer()
} else {
// Fall back on the android crypto implementation
AndroidAESKeyTransformer()
}
return keyTransformer.transformMasterKey(seed, key, rounds)
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation;
import com.kunzisoft.keepass.crypto.NativeLib;
import com.kunzisoft.keepass.utils.UnsignedInt;
import java.io.IOException;
public class Argon2Native {
enum CType {
ARGON2_D(0),
ARGON2_I(1),
ARGON2_ID(2);
int cValue = 0;
CType(int i) {
cValue = i;
}
}
public static byte[] transformKey(Argon2Kdf.Type type, byte[] password, byte[] salt, UnsignedInt parallelism,
UnsignedInt memory, UnsignedInt iterations, byte[] secretKey,
byte[] associatedData, UnsignedInt version) throws IOException {
NativeLib.INSTANCE.init();
CType cType = CType.ARGON2_D;
if (type.equals(Argon2Kdf.Type.ARGON2_ID))
cType = CType.ARGON2_ID;
return nTransformMasterKey(
cType.cValue,
password,
salt,
parallelism.toKotlinInt(),
memory.toKotlinInt(),
iterations.toKotlinInt(),
secretKey,
associatedData,
version.toKotlinInt());
}
private static native byte[] nTransformMasterKey(int type, byte[] password, byte[] salt, int parallelism,
int memory, int iterations, byte[] secretKey,
byte[] associatedData, int version) throws IOException;
}

View File

@@ -25,6 +25,8 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -55,7 +57,10 @@ class LoadDatabaseRunnable(private val context: Context,
mReadonly,
context.contentResolver,
UriUtil.getBinaryDir(context),
Database.LoadedKey.generateNewCipherKey(),
{ memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
LoadedKey.generateNewCipherKey(),
mFixDuplicateUUID,
progressTaskUpdater)
}

View File

@@ -25,18 +25,21 @@ import android.content.Context.BIND_NOT_FOREGROUND
import android.net.Uri
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
@@ -48,8 +51,8 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
@@ -251,11 +254,16 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
}
private fun start(bundle: Bundle? = null, actionTask: String) {
activity.stopService(intentDatabaseTask)
if (bundle != null)
intentDatabaseTask.putExtras(bundle)
intentDatabaseTask.action = actionTask
activity.startService(intentDatabaseTask)
try {
activity.stopService(intentDatabaseTask)
if (bundle != null)
intentDatabaseTask.putExtras(bundle)
intentDatabaseTask.action = actionTask
activity.startService(intentDatabaseTask)
} catch (e: Exception) {
Log.e(TAG, "Unable to perform database action", e)
Toast.makeText(activity, R.string.error_start_database_action, Toast.LENGTH_LONG).show()
}
}
/*
@@ -591,4 +599,8 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
}
, ACTION_DATABASE_SAVE)
}
companion object {
private val TAG = ProgressDatabaseTaskProvider::class.java.name
}
}

View File

@@ -21,6 +21,8 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
@@ -33,10 +35,10 @@ class ReloadDatabaseRunnable(private val context: Context,
private val mLoadDatabaseResult: ((Result) -> Unit)?)
: ActionRunnable() {
private var tempCipherKey: Database.LoadedKey? = null
private var tempCipherKey: LoadedKey? = null
override fun onStartRun() {
tempCipherKey = mDatabase.loadedCipherKey
tempCipherKey = mDatabase.binaryCache.loadedCipherKey
// Clear before we load
mDatabase.clear(UriUtil.getBinaryDir(context))
mDatabase.wasReloaded = true
@@ -46,7 +48,10 @@ class ReloadDatabaseRunnable(private val context: Context,
try {
mDatabase.reloadData(context.contentResolver,
UriUtil.getBinaryDir(context),
tempCipherKey ?: Database.LoadedKey.generateNewCipherKey(),
{ memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
tempCipherKey ?: LoadedKey.generateNewCipherKey(),
progressTaskUpdater)
} catch (e: LoadDatabaseException) {
setError(e)

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.encrypt.CipherFactory
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
class AesEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return CipherFactory.getAES(opmode, key, IV)
}
override fun getEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.AESRijndael
}
}

View File

@@ -17,19 +17,14 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.engine
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import org.bouncycastle.jce.provider.BouncyCastleProvider
import com.kunzisoft.encrypt.CipherFactory
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class ChaCha20Engine : CipherEngine() {
@@ -38,34 +33,11 @@ class ChaCha20Engine : CipherEngine() {
}
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray, androidOverride: Boolean): Cipher {
val cipher = Cipher.getInstance("Chacha7539", BouncyCastleProvider())
cipher.init(opmode, SecretKeySpec(key, "ChaCha7539"), IvParameterSpec(IV))
return cipher
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return CipherFactory.getChacha20(opmode, key, IV)
}
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
override fun getEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.ChaCha20
}
companion object {
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xD6.toByte(),
0x03.toByte(),
0x8A.toByte(),
0x2B.toByte(),
0x8B.toByte(),
0x6F.toByte(),
0x4C.toByte(),
0xB5.toByte(),
0xA5.toByte(),
0x24.toByte(),
0x33.toByte(),
0x9A.toByte(),
0x31.toByte(),
0xDB.toByte(),
0xB5.toByte(),
0x9A.toByte()))
}
}

View File

@@ -17,9 +17,7 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
package com.kunzisoft.keepass.database.crypto
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
@@ -39,13 +37,8 @@ abstract class CipherEngine {
}
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
abstract fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray, androidOverride: Boolean): Cipher
abstract fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return getCipher(opmode, key, IV, false)
}
abstract fun getPwEncryptionAlgorithm(): EncryptionAlgorithm
abstract fun getEncryptionAlgorithm(): EncryptionAlgorithm
}

View File

@@ -17,11 +17,13 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.encrypt.StreamCipher
enum class CrsAlgorithm constructor(val id: UnsignedInt) {
enum class CrsAlgorithm(val id: UnsignedInt) {
Null(UnsignedInt(0)),
ArcFourVariant(UnsignedInt(1)),
@@ -30,6 +32,15 @@ enum class CrsAlgorithm constructor(val id: UnsignedInt) {
companion object {
@Throws(Exception::class)
fun getCipher(algorithm: CrsAlgorithm?, key: ByteArray): StreamCipher {
return when (algorithm) {
Salsa20 -> HashManager.getSalsa20(key)
ChaCha20 -> HashManager.getChaCha20(key)
else -> throw Exception("Invalid random cipher")
}
}
fun fromId(num: UnsignedInt): CrsAlgorithm? {
for (e in values()) {
if (e.id == num) {

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.keepass.utils.bytes16ToUuid
import java.security.NoSuchAlgorithmException
import java.util.*
enum class EncryptionAlgorithm {
AESRijndael,
Twofish,
ChaCha20;
val cipherEngine: CipherEngine
get() {
return when (this) {
AESRijndael -> AesEngine()
Twofish -> TwofishEngine()
ChaCha20 -> ChaCha20Engine()
}
}
val uuid: UUID
get() {
return when (this) {
AESRijndael -> AES_UUID
Twofish -> TWOFISH_UUID
ChaCha20 -> CHACHA20_UUID
}
}
override fun toString(): String {
return when (this) {
AESRijndael -> "Rijndael (AES)"
Twofish -> "Twofish"
ChaCha20 -> "ChaCha20"
}
}
companion object {
/**
* Generate appropriate cipher based on KeePass 2.x UUID's
*/
@Throws(NoSuchAlgorithmException::class)
fun getFrom(uuid: UUID): EncryptionAlgorithm {
return when (uuid) {
AES_UUID -> AESRijndael
TWOFISH_UUID -> Twofish
CHACHA20_UUID -> ChaCha20
else -> throw NoSuchAlgorithmException("UUID unrecognized.")
}
}
private val AES_UUID: UUID by lazy {
bytes16ToUuid(
byteArrayOf(0x31.toByte(),
0xC1.toByte(),
0xF2.toByte(),
0xE6.toByte(),
0xBF.toByte(),
0x71.toByte(),
0x43.toByte(),
0x50.toByte(),
0xBE.toByte(),
0x58.toByte(),
0x05.toByte(),
0x21.toByte(),
0x6A.toByte(),
0xFC.toByte(),
0x5A.toByte(),
0xFF.toByte()))
}
private val TWOFISH_UUID: UUID by lazy {
bytes16ToUuid(
byteArrayOf(0xAD.toByte(),
0x68.toByte(),
0xF2.toByte(),
0x9F.toByte(),
0x57.toByte(),
0x6F.toByte(),
0x4B.toByte(),
0xB9.toByte(),
0xA3.toByte(),
0x6A.toByte(),
0xD4.toByte(),
0x7A.toByte(),
0xF9.toByte(),
0x65.toByte(),
0x34.toByte(),
0x6C.toByte()))
}
private val CHACHA20_UUID: UUID by lazy {
bytes16ToUuid(
byteArrayOf(0xD6.toByte(),
0x03.toByte(),
0x8A.toByte(),
0x2B.toByte(),
0x8B.toByte(),
0x6F.toByte(),
0x4C.toByte(),
0xB5.toByte(),
0xA5.toByte(),
0x24.toByte(),
0x33.toByte(),
0x9A.toByte(),
0x31.toByte(),
0xDB.toByte(),
0xB5.toByte(),
0x9A.toByte()))
}
}
}

View File

@@ -17,35 +17,40 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.stream
package com.kunzisoft.keepass.database.crypto
import java.io.IOException
import java.security.DigestOutputStream
import java.security.InvalidKeyException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
object HmacBlockStream {
fun getHmacKey64(key: ByteArray, blockIndex: Long): ByteArray {
object HmacBlock {
fun getHmacSha256(blockKey: ByteArray): Mac {
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(blockKey, "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw IOException("No HmacAlogirthm")
} catch (e: InvalidKeyException) {
throw IOException("Invalid Hmac Key")
}
return hmac
}
fun getHmacKey64(key: ByteArray, blockIndex: ByteArray): ByteArray {
val hash: MessageDigest
try {
hash = MessageDigest.getInstance("SHA-512")
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
}
val nos = NullOutputStream()
val dos = DigestOutputStream(nos, hash)
val leos = LittleEndianDataOutputStream(dos)
try {
leos.writeLong(blockIndex)
leos.write(key)
leos.close()
} catch (e: IOException) {
throw RuntimeException(e)
}
//assert(hashKey.length == 64);
hash.update(blockIndex)
hash.update(key)
return hash.digest()
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.encrypt.CipherFactory
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
class TwofishEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return CipherFactory.getTwofish(opmode, key, IV)
}
override fun getEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.Twofish
}
}

View File

@@ -17,13 +17,10 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.stream.*
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import com.kunzisoft.keepass.utils.*
import java.io.*
import java.nio.charset.Charset
import java.util.*
@@ -55,12 +52,12 @@ open class VariantDictionary {
return dict[name]?.value as UnsignedInt?
}
fun setUInt64(name: String, value: Long) {
fun setUInt64(name: String, value: UnsignedLong) {
putType(VdType.UInt64, name, value)
}
fun getUInt64(name: String): Long? {
return dict[name]?.value as Long?
fun getUInt64(name: String): UnsignedLong? {
return dict[name]?.value as UnsignedLong?
}
fun setBool(name: String, value: Boolean) {
@@ -115,22 +112,21 @@ open class VariantDictionary {
@Throws(IOException::class)
fun deserialize(data: ByteArray): VariantDictionary {
val inputStream = LittleEndianDataInputStream(ByteArrayInputStream(data))
val inputStream = ByteArrayInputStream(data)
return deserialize(inputStream)
}
@Throws(IOException::class)
fun serialize(kdfParameters: KdfParameters): ByteArray {
fun serialize(variantDictionary: VariantDictionary): ByteArray {
val byteArrayOutputStream = ByteArrayOutputStream()
val outputStream = LittleEndianDataOutputStream(byteArrayOutputStream)
serialize(kdfParameters, outputStream)
serialize(variantDictionary, byteArrayOutputStream)
return byteArrayOutputStream.toByteArray()
}
@Throws(IOException::class)
fun deserialize(inputStream: LittleEndianDataInputStream): VariantDictionary {
fun deserialize(inputStream: InputStream): VariantDictionary {
val dictionary = VariantDictionary()
val version = inputStream.readUShort()
val version = inputStream.readBytes2ToUShort()
if (version and VdmCritical > VdVersion and VdmCritical) {
throw IOException("Invalid format")
}
@@ -143,14 +139,14 @@ open class VariantDictionary {
if (bType == VdType.None) {
break
}
val nameLen = inputStream.readUInt().toKotlinInt()
val nameBuf = inputStream.readBytes(nameLen)
val nameLen = inputStream.readBytes4ToUInt().toKotlinInt()
val nameBuf = inputStream.readBytesLength(nameLen)
if (nameLen != nameBuf.size) {
throw IOException("Invalid format")
}
val name = String(nameBuf, UTF8Charset)
val valueLen = inputStream.readUInt().toKotlinInt()
val valueBuf = inputStream.readBytes(valueLen)
val valueLen = inputStream.readBytes4ToUInt().toKotlinInt()
val valueBuf = inputStream.readBytesLength(valueLen)
if (valueLen != valueBuf.size) {
throw IOException("Invalid format")
}
@@ -159,7 +155,7 @@ open class VariantDictionary {
dictionary.setUInt32(name, bytes4ToUInt(valueBuf))
}
VdType.UInt64 -> if (valueLen == 8) {
dictionary.setUInt64(name, bytes64ToLong(valueBuf))
dictionary.setUInt64(name, bytes64ToULong(valueBuf))
}
VdType.Bool -> if (valueLen == 1) {
dictionary.setBool(name, valueBuf[0] != 0.toByte())
@@ -181,48 +177,47 @@ open class VariantDictionary {
@Throws(IOException::class)
fun serialize(variantDictionary: VariantDictionary,
outputStream: LittleEndianDataOutputStream?) {
outputStream: OutputStream?) {
if (outputStream == null) {
return
}
outputStream.writeUShort(VdVersion)
outputStream.write2BytesUShort(VdVersion)
for ((name, vd) in variantDictionary.dict) {
val nameBuf = name.toByteArray(UTF8Charset)
outputStream.write(vd.type.toInt())
outputStream.writeInt(nameBuf.size)
outputStream.writeByte(vd.type)
outputStream.write4BytesUInt(UnsignedInt(nameBuf.size))
outputStream.write(nameBuf)
var buf: ByteArray
when (vd.type) {
VdType.UInt32 -> {
outputStream.writeInt(4)
outputStream.writeUInt((vd.value as UnsignedInt))
outputStream.write4BytesUInt(UnsignedInt(4))
outputStream.write4BytesUInt(vd.value as UnsignedInt)
}
VdType.UInt64 -> {
outputStream.writeInt(8)
outputStream.writeLong(vd.value as Long)
outputStream.write4BytesUInt(UnsignedInt(8))
outputStream.write8BytesLong(vd.value as UnsignedLong)
}
VdType.Bool -> {
outputStream.writeInt(1)
val bool = if (vd.value as Boolean) 1.toByte() else 0.toByte()
outputStream.write(bool.toInt())
outputStream.write4BytesUInt(UnsignedInt(1))
outputStream.writeBooleanByte(vd.value as Boolean)
}
VdType.Int32 -> {
outputStream.writeInt(4)
outputStream.writeInt(vd.value as Int)
outputStream.write4BytesUInt(UnsignedInt(4))
outputStream.write4BytesUInt(UnsignedInt(vd.value as Int))
}
VdType.Int64 -> {
outputStream.writeInt(8)
outputStream.writeLong(vd.value as Long)
outputStream.write4BytesUInt(UnsignedInt(8))
outputStream.write8BytesLong(vd.value as Long)
}
VdType.String -> {
val value = vd.value as String
buf = value.toByteArray(UTF8Charset)
outputStream.writeInt(buf.size)
outputStream.write4BytesUInt(UnsignedInt(buf.size))
outputStream.write(buf)
}
VdType.ByteArray -> {
buf = vd.value as ByteArray
outputStream.writeInt(buf.size)
outputStream.write4BytesUInt(UnsignedInt(buf.size))
outputStream.write(buf)
}
else -> {

View File

@@ -17,13 +17,12 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation
package com.kunzisoft.keepass.database.crypto.kdf
import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.encrypt.aes.AESTransformer
import com.kunzisoft.keepass.utils.bytes16ToUuid
import java.io.IOException
import java.security.SecureRandom
import java.util.*
@@ -38,32 +37,28 @@ class AesKdf : KdfEngine() {
get() {
return KdfParameters(uuid!!).apply {
setParamUUID()
setUInt64(PARAM_ROUNDS, defaultKeyRounds)
setUInt64(PARAM_ROUNDS, UnsignedLong(defaultKeyRounds))
}
}
override val defaultKeyRounds: Long = 500000L
override fun getName(resources: Resources): String {
return resources.getString(R.string.kdf_AES)
}
override val defaultKeyRounds = 500000L
@Throws(IOException::class)
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
var seed = kdfParameters.getByteArray(PARAM_SEED)
if (seed != null && seed.size != 32) {
seed = CryptoUtil.hashSha256(seed)
seed = HashManager.hashSha256(seed)
}
var currentMasterKey = masterKey
if (currentMasterKey.size != 32) {
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
currentMasterKey = HashManager.hashSha256(currentMasterKey)
}
val rounds = kdfParameters.getUInt64(PARAM_ROUNDS)
val rounds = kdfParameters.getUInt64(PARAM_ROUNDS)?.toKotlinLong()
return AESKeyTransformerFactory.transformMasterKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
return AESTransformer.transformKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
}
override fun randomize(kdfParameters: KdfParameters) {
@@ -76,11 +71,15 @@ class AesKdf : KdfEngine() {
}
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_ROUNDS) ?: defaultKeyRounds
return kdfParameters.getUInt64(PARAM_ROUNDS)?.toKotlinLong() ?: defaultKeyRounds
}
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
kdfParameters.setUInt64(PARAM_ROUNDS, keyRounds)
kdfParameters.setUInt64(PARAM_ROUNDS, UnsignedLong(keyRounds))
}
override fun toString(): String {
return "AES"
}
companion object {

View File

@@ -17,13 +17,13 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation
package com.kunzisoft.keepass.database.crypto.kdf
import android.content.res.Resources
import androidx.annotation.StringRes
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.encrypt.argon2.Argon2Transformer
import com.kunzisoft.encrypt.argon2.Argon2Type
import com.kunzisoft.keepass.utils.bytes16ToUuid
import java.io.IOException
import java.security.SecureRandom
import java.util.*
@@ -48,40 +48,30 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
}
override val defaultKeyRounds: Long
get() = DEFAULT_ITERATIONS
override fun getName(resources: Resources): String {
return resources.getString(type.nameId)
}
get() = DEFAULT_ITERATIONS.toKotlinLong()
@Throws(IOException::class)
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
val salt = kdfParameters.getByteArray(PARAM_SALT)
val parallelism = kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
UnsignedInt(it)
}
val memory = kdfParameters.getUInt64(PARAM_MEMORY)?.div(MEMORY_BLOCK_SIZE)?.let {
UnsignedInt.fromKotlinLong(it)
}
val iterations = kdfParameters.getUInt64(PARAM_ITERATIONS)?.let {
UnsignedInt.fromKotlinLong(it)
}
val version = kdfParameters.getUInt32(PARAM_VERSION)?.let {
UnsignedInt(it)
}
val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
val salt = kdfParameters.getByteArray(PARAM_SALT) ?: ByteArray(0)
val parallelism = kdfParameters.getUInt32(PARAM_PARALLELISM)?.toKotlinLong() ?: DEFAULT_PARALLELISM.toKotlinLong()
val memory = kdfParameters.getUInt64(PARAM_MEMORY)?.toKotlinLong()?.div(MEMORY_BLOCK_SIZE) ?: DEFAULT_MEMORY.toKotlinLong()
val iterations = kdfParameters.getUInt64(PARAM_ITERATIONS)?.toKotlinLong() ?: DEFAULT_ITERATIONS.toKotlinLong()
val version = kdfParameters.getUInt32(PARAM_VERSION)?.toKotlinInt() ?: MAX_VERSION.toKotlinInt()
return Argon2Native.transformKey(
type,
// Not used
// val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
// val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
val argonType = if (type == Type.ARGON2_ID) Argon2Type.ARGON2_ID else Argon2Type.ARGON2_D
return Argon2Transformer.transformKey(
argonType,
masterKey,
salt,
parallelism,
memory,
iterations,
secretKey,
assocData,
version)
}
@@ -95,32 +85,32 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
}
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_ITERATIONS) ?: defaultKeyRounds
return kdfParameters.getUInt64(PARAM_ITERATIONS)?.toKotlinLong() ?: defaultKeyRounds
}
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
kdfParameters.setUInt64(PARAM_ITERATIONS, keyRounds)
kdfParameters.setUInt64(PARAM_ITERATIONS, UnsignedLong(keyRounds))
}
override val minKeyRounds: Long
get() = MIN_ITERATIONS
get() = MIN_ITERATIONS.toKotlinLong()
override val maxKeyRounds: Long
get() = MAX_ITERATIONS
get() = MAX_ITERATIONS.toKotlinLong()
override fun getMemoryUsage(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_MEMORY) ?: defaultMemoryUsage
return kdfParameters.getUInt64(PARAM_MEMORY)?.toKotlinLong() ?: defaultMemoryUsage
}
override fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
kdfParameters.setUInt64(PARAM_MEMORY, memory)
kdfParameters.setUInt64(PARAM_MEMORY, UnsignedLong(memory))
}
override val defaultMemoryUsage: Long
get() = DEFAULT_MEMORY
get() = DEFAULT_MEMORY.toKotlinLong()
override val minMemoryUsage: Long
get() = MIN_MEMORY
get() = MIN_MEMORY.toKotlinLong()
override val maxMemoryUsage: Long
get() = MAX_MEMORY
@@ -135,16 +125,20 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
kdfParameters.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromKotlinLong(parallelism))
}
override fun toString(): String {
return "$type"
}
override val defaultParallelism: Long
get() = DEFAULT_PARALLELISM.toKotlinLong()
override val minParallelism: Long
get() = MIN_PARALLELISM
get() = MIN_PARALLELISM.toKotlinLong()
override val maxParallelism: Long
get() = MAX_PARALLELISM
get() = MAX_PARALLELISM.toKotlinLong()
enum class Type(val CIPHER_UUID: UUID, @StringRes val nameId: Int) {
enum class Type(val CIPHER_UUID: UUID, private val typeName: String) {
ARGON2_D(bytes16ToUuid(
byteArrayOf(0xEF.toByte(),
0x63.toByte(),
@@ -161,7 +155,7 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
0x03.toByte(),
0xE3.toByte(),
0x0A.toByte(),
0x0C.toByte())), R.string.kdf_Argon2d),
0x0C.toByte())), "Argon2d"),
ARGON2_ID(bytes16ToUuid(
byteArrayOf(0x9E.toByte(),
0x29.toByte(),
@@ -178,7 +172,11 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
0xC6.toByte(),
0xF0.toByte(),
0xA1.toByte(),
0xE6.toByte())), R.string.kdf_Argon2id);
0xE6.toByte())), "Argon2id");
override fun toString(): String {
return typeName
}
}
companion object {
@@ -194,21 +192,17 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
private val MIN_VERSION = UnsignedInt(0x10)
private val MAX_VERSION = UnsignedInt(0x13)
private const val MIN_SALT = 8
private val MAX_SALT = UnsignedInt.MAX_VALUE.toKotlinLong()
private val DEFAULT_ITERATIONS = UnsignedLong(2L)
private val MIN_ITERATIONS = UnsignedLong(1L)
private val MAX_ITERATIONS = UnsignedLong(4294967295L)
private const val MIN_ITERATIONS: Long = 1L
private const val MAX_ITERATIONS = 4294967295L
private const val MIN_MEMORY = (1024 * 8).toLong()
private val DEFAULT_MEMORY = UnsignedLong((1024L * 1024L))
private val MIN_MEMORY = UnsignedLong(1024L * 8L)
private val MAX_MEMORY = UnsignedInt.MAX_VALUE.toKotlinLong()
private const val MEMORY_BLOCK_SIZE: Long = 1024L
private const val MIN_PARALLELISM: Long = 1L
private const val MAX_PARALLELISM: Long = ((1 shl 24) - 1).toLong()
private const val DEFAULT_ITERATIONS: Long = 2L
private const val DEFAULT_MEMORY = (1024 * 1024).toLong()
private val DEFAULT_PARALLELISM = UnsignedInt(2)
private val MIN_PARALLELISM = UnsignedInt.fromKotlinLong(1L)
private val MAX_PARALLELISM = UnsignedInt.fromKotlinLong(((1 shl 24) - 1))
}
}

View File

@@ -17,17 +17,15 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation
package com.kunzisoft.keepass.database.crypto.kdf
import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.Serializable
import java.util.UUID
import java.util.*
// TODO Parcelable
abstract class KdfEngine : ObjectNameResource, Serializable {
abstract class KdfEngine : Serializable {
var uuid: UUID? = null

View File

@@ -17,7 +17,7 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation
package com.kunzisoft.keepass.database.crypto.kdf
object KdfFactory {
var aesKdf = AesKdf()

View File

@@ -17,11 +17,11 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation
package com.kunzisoft.keepass.database.crypto.kdf
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.stream.uuidTo16Bytes
import com.kunzisoft.keepass.utils.VariantDictionary
import com.kunzisoft.keepass.utils.bytes16ToUuid
import com.kunzisoft.keepass.utils.uuidTo16Bytes
import com.kunzisoft.keepass.database.crypto.VariantDictionary
import java.io.IOException
import java.util.*

View File

@@ -21,8 +21,8 @@ package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.BinaryByte
import com.kunzisoft.keepass.database.element.database.BinaryData
import com.kunzisoft.keepass.database.element.binary.BinaryByte
import com.kunzisoft.keepass.database.element.binary.BinaryData
data class Attachment(var name: String,

View File

@@ -23,19 +23,27 @@ import android.content.ContentResolver
import android.content.res.Resources
import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.utils.readBytes4ToUInt
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.database.*
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.icon.IconsManager
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
@@ -44,15 +52,11 @@ import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.stream.readBytes4ToUInt
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.SingletonHolder
import com.kunzisoft.keepass.utils.UriUtil
import java.io.*
import java.security.Key
import java.security.SecureRandom
import java.util.*
import javax.crypto.KeyGenerator
import kotlin.collections.ArrayList
@@ -70,7 +74,7 @@ class Database {
var isReadOnly = false
val iconDrawableFactory = IconDrawableFactory(
{ loadedCipherKey },
{ binaryCache },
{ iconId -> iconsManager.getBinaryForCustomIcon(iconId) }
)
@@ -92,18 +96,18 @@ class Database {
* Cipher key regenerated when the database is loaded and closed
* Can be used to temporarily store database elements
*/
var loadedCipherKey: LoadedKey?
var binaryCache: BinaryCache
private set(value) {
mDatabaseKDB?.loadedCipherKey = value
mDatabaseKDBX?.loadedCipherKey = value
mDatabaseKDB?.binaryCache = value
mDatabaseKDBX?.binaryCache = value
}
get() {
return mDatabaseKDB?.loadedCipherKey ?: mDatabaseKDBX?.loadedCipherKey
return mDatabaseKDB?.binaryCache ?: mDatabaseKDBX?.binaryCache ?: BinaryCache()
}
private val iconsManager: IconsManager
get() {
return mDatabaseKDB?.iconsManager ?: mDatabaseKDBX?.iconsManager ?: IconsManager()
return mDatabaseKDB?.iconsManager ?: mDatabaseKDBX?.iconsManager ?: IconsManager(binaryCache)
}
fun doForEachStandardIcons(action: (IconImageStandard) -> Unit) {
@@ -125,9 +129,8 @@ class Database {
return iconsManager.getIcon(iconId)
}
fun buildNewCustomIcon(cacheDirectory: File,
result: (IconImageCustom?, BinaryData?) -> Unit) {
mDatabaseKDBX?.buildNewCustomIcon(cacheDirectory, null, result)
fun buildNewCustomIcon(result: (IconImageCustom?, BinaryData?) -> Unit) {
mDatabaseKDBX?.buildNewCustomIcon(null, result)
}
fun isCustomIconBinaryDuplicate(binaryData: BinaryData): Boolean {
@@ -136,7 +139,7 @@ class Database {
fun removeCustomIcon(customIcon: IconImageCustom) {
iconDrawableFactory.clearFromCache(customIcon)
iconsManager.removeCustomIcon(customIcon.uuid)
iconsManager.removeCustomIcon(binaryCache, customIcon.uuid)
}
val allowName: Boolean
@@ -219,7 +222,7 @@ class Database {
// Default compression not necessary if stored in header
mDatabaseKDBX?.let {
return it.compressionAlgorithm == CompressionAlgorithm.GZip
&& it.kdbxVersion.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()
&& it.kdbxVersion.isBefore(FILE_VERSION_32_4)
}
return false
}
@@ -232,12 +235,9 @@ class Database {
val allowNoMasterKey: Boolean
get() = mDatabaseKDBX != null
val allowEncryptionAlgorithmModification: Boolean
get() = availableEncryptionAlgorithms.size > 1
fun getEncryptionAlgorithmName(resources: Resources): String {
return mDatabaseKDB?.encryptionAlgorithm?.getName(resources)
?: mDatabaseKDBX?.encryptionAlgorithm?.getName(resources)
fun getEncryptionAlgorithmName(): String {
return mDatabaseKDB?.encryptionAlgorithm?.toString()
?: mDatabaseKDBX?.encryptionAlgorithm?.toString()
?: ""
}
@@ -250,7 +250,7 @@ class Database {
algorithm?.let {
mDatabaseKDBX?.encryptionAlgorithm = algorithm
mDatabaseKDBX?.setDataEngine(algorithm.cipherEngine)
mDatabaseKDBX?.dataCipher = algorithm.dataCipher
mDatabaseKDBX?.cipherUuid = algorithm.uuid
}
}
@@ -272,8 +272,8 @@ class Database {
}
}
fun getKeyDerivationName(resources: Resources): String {
return kdfEngine?.getName(resources) ?: ""
fun getKeyDerivationName(): String {
return kdfEngine?.toString() ?: ""
}
var numberKeyEncryptionRounds: Long
@@ -381,25 +381,12 @@ class Database {
fun createData(databaseUri: Uri, databaseName: String, rootName: String) {
val newDatabase = DatabaseKDBX(databaseName, rootName)
newDatabase.loadedCipherKey = LoadedKey.generateNewCipherKey()
setDatabaseKDBX(newDatabase)
this.fileUri = databaseUri
// Set Database state
this.loaded = true
}
class LoadedKey(val key: Key, val iv: ByteArray): Serializable {
companion object {
const val BINARY_CIPHER = "Blowfish/CBC/PKCS5Padding"
fun generateNewCipherKey(): LoadedKey {
val iv = ByteArray(8)
SecureRandom().nextBytes(iv)
return LoadedKey(KeyGenerator.getInstance("Blowfish").generateKey(), iv)
}
}
}
@Throws(LoadDatabaseException::class)
private fun readDatabaseStream(contentResolver: ContentResolver, uri: Uri,
openDatabaseKDB: (InputStream) -> DatabaseKDB,
@@ -453,6 +440,7 @@ class Database {
readOnly: Boolean,
contentResolver: ContentResolver,
cacheDirectory: File,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
tempCipherKey: LoadedKey,
fixDuplicateUUID: Boolean,
progressTaskUpdater: ProgressTaskUpdater?) {
@@ -474,7 +462,7 @@ class Database {
// Read database stream for the first time
readDatabaseStream(contentResolver, uri,
{ databaseInputStream ->
DatabaseInputKDB(cacheDirectory)
DatabaseInputKDB(cacheDirectory, isRAMSufficient)
.openDatabase(databaseInputStream,
mainCredential.masterPassword,
keyFileInputStream,
@@ -483,7 +471,7 @@ class Database {
fixDuplicateUUID)
},
{ databaseInputStream ->
DatabaseInputKDBX(cacheDirectory)
DatabaseInputKDBX(cacheDirectory, isRAMSufficient)
.openDatabase(databaseInputStream,
mainCredential.masterPassword,
keyFileInputStream,
@@ -507,6 +495,7 @@ class Database {
@Throws(LoadDatabaseException::class)
fun reloadData(contentResolver: ContentResolver,
cacheDirectory: File,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
tempCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?) {
@@ -515,14 +504,14 @@ class Database {
fileUri?.let { oldDatabaseUri ->
readDatabaseStream(contentResolver, oldDatabaseUri,
{ databaseInputStream ->
DatabaseInputKDB(cacheDirectory)
DatabaseInputKDB(cacheDirectory, isRAMSufficient)
.openDatabase(databaseInputStream,
masterKey,
tempCipherKey,
progressTaskUpdater)
},
{ databaseInputStream ->
DatabaseInputKDBX(cacheDirectory)
DatabaseInputKDBX(cacheDirectory, isRAMSufficient)
.openDatabase(databaseInputStream,
masterKey,
tempCipherKey,
@@ -576,8 +565,7 @@ class Database {
val attachmentPool: AttachmentPool
get() {
// Binary pool is functionally only in KDBX
return mDatabaseKDBX?.binaryPool ?: AttachmentPool()
return mDatabaseKDB?.attachmentPool ?: mDatabaseKDBX?.attachmentPool ?: AttachmentPool(binaryCache)
}
val allowMultipleAttachments: Boolean
@@ -589,11 +577,10 @@ class Database {
return false
}
fun buildNewBinaryAttachment(cacheDirectory: File,
compressed: Boolean = false,
fun buildNewBinaryAttachment(compressed: Boolean = false,
protected: Boolean = false): BinaryData? {
return mDatabaseKDB?.buildNewAttachment(cacheDirectory)
?: mDatabaseKDBX?.buildNewAttachment(cacheDirectory, compressed, protected)
return mDatabaseKDB?.buildNewAttachment()
?: mDatabaseKDBX?.buildNewAttachment( false, compressed, protected)
}
fun removeAttachmentIfNotUsed(attachment: Attachment) {
@@ -668,6 +655,7 @@ class Database {
}
fun clear(filesDirectory: File? = null) {
binaryCache.clear()
iconsManager.clearCache()
iconDrawableFactory.clearCache()
// Delete the cache of the database if present

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
@@ -311,7 +311,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
fun getAttachments(attachmentPool: AttachmentPool, inHistory: Boolean = false): List<Attachment> {
val attachments = ArrayList<Attachment>()
entryKDB?.getAttachment()?.let {
entryKDB?.getAttachment(attachmentPool)?.let {
attachments.add(it)
}
entryKDBX?.getAttachments(attachmentPool, inHistory)?.let {
@@ -336,7 +336,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
}
private fun putAttachment(attachment: Attachment, attachmentPool: AttachmentPool) {
entryKDB?.putAttachment(attachment)
entryKDB?.putAttachment(attachment, attachmentPool)
entryKDBX?.putAttachment(attachment, attachmentPool)
}
@@ -466,16 +466,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
return result
}
companion object CREATOR : Parcelable.Creator<Entry> {
override fun createFromParcel(parcel: Parcel): Entry {
return Entry(parcel)
}
override fun newArray(size: Int): Array<Entry?> {
return arrayOfNulls(size)
}
companion object {
const val PMS_TAN_ENTRY = "<TAN>"
/**
@@ -484,5 +475,16 @@ class Entry : Node, EntryVersionedInterface<Group> {
fun newExtraFieldNameAllowed(field: Field): Boolean {
return EntryKDBX.newCustomNameAllowed(field.name)
}
@JvmField
val CREATOR: Parcelable.Creator<Entry> = object : Parcelable.Creator<Entry> {
override fun createFromParcel(parcel: Parcel): Entry {
return Entry(parcel)
}
override fun newArray(size: Int): Array<Entry?> {
return arrayOfNulls(size)
}
}
}
}

View File

@@ -17,9 +17,9 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
package com.kunzisoft.keepass.database.element.binary
class AttachmentPool : BinaryPool<Int>() {
class AttachmentPool(binaryCache: BinaryCache) : BinaryPool<Int>(binaryCache) {
/**
* Utility method to find an unused key in the pool

View File

@@ -17,51 +17,62 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
package com.kunzisoft.keepass.database.element.binary
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.stream.readAllBytes
import android.util.Base64
import android.util.Base64InputStream
import android.util.Base64OutputStream
import com.kunzisoft.keepass.utils.readAllBytes
import com.kunzisoft.keepass.database.element.binary.BinaryCache.Companion.UNKNOWN
import java.io.*
import java.util.zip.GZIPOutputStream
class BinaryByte : BinaryData {
private var mDataByte: ByteArray = ByteArray(0)
private var mDataByteId: String
/**
* Empty protected binary
*/
constructor() : super()
private fun getByteArray(binaryCache: BinaryCache): ByteArray {
val keyData = binaryCache.getByteArray(mDataByteId)
mDataByteId = keyData.key
return keyData.data
}
constructor(byteArray: ByteArray,
constructor() : super() {
mDataByteId = UNKNOWN
}
constructor(id: String,
compressed: Boolean = false,
protected: Boolean = false) : super(compressed, protected) {
this.mDataByte = byteArray
mDataByteId = id
}
constructor(parcel: Parcel) : super(parcel) {
val byteArray = ByteArray(parcel.readInt())
parcel.readByteArray(byteArray)
mDataByte = byteArray
mDataByteId = parcel.readString() ?: UNKNOWN
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(mDataByteId)
}
@Throws(IOException::class)
override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream {
return ByteArrayInputStream(mDataByte)
override fun getInputDataStream(binaryCache: BinaryCache): InputStream {
return Base64InputStream(ByteArrayInputStream(getByteArray(binaryCache)), Base64.NO_WRAP)
}
@Throws(IOException::class)
override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
return ByteOutputStream()
override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream {
return BinaryCountingOutputStream(Base64OutputStream(ByteOutputStream(binaryCache), Base64.NO_WRAP))
}
@Throws(IOException::class)
override fun compress(cipherKey: Database.LoadedKey) {
override fun compress(binaryCache: BinaryCache) {
if (!isCompressed) {
GZIPOutputStream(getOutputDataStream(cipherKey)).use { outputStream ->
getInputDataStream(cipherKey).use { inputStream ->
GZIPOutputStream(getOutputDataStream(binaryCache)).use { outputStream ->
getInputDataStream(binaryCache).use { inputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
@@ -72,10 +83,10 @@ class BinaryByte : BinaryData {
}
@Throws(IOException::class)
override fun decompress(cipherKey: Database.LoadedKey) {
override fun decompress(binaryCache: BinaryCache) {
if (isCompressed) {
getUnGzipInputDataStream(cipherKey).use { inputStream ->
getOutputDataStream(cipherKey).use { outputStream ->
getUnGzipInputDataStream(binaryCache).use { inputStream ->
getOutputDataStream(binaryCache).use { outputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
@@ -86,36 +97,8 @@ class BinaryByte : BinaryData {
}
@Throws(IOException::class)
override fun clear() {
mDataByte = ByteArray(0)
}
override fun dataExists(): Boolean {
return mDataByte.isNotEmpty()
}
override fun getSize(): Long {
return mDataByte.size.toLong()
}
/**
* Hash of the raw encrypted file in temp folder, only to compare binary data
*/
override fun binaryHash(): Int {
return if (dataExists())
mDataByte.contentHashCode()
else
0
}
override fun toString(): String {
return mDataByte.toString()
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeInt(mDataByte.size)
dest.writeByteArray(mDataByte)
override fun clear(binaryCache: BinaryCache) {
binaryCache.removeByteArray(mDataByteId)
}
override fun equals(other: Any?): Boolean {
@@ -123,31 +106,29 @@ class BinaryByte : BinaryData {
if (other !is BinaryByte) return false
if (!super.equals(other)) return false
if (!mDataByte.contentEquals(other.mDataByte)) return false
if (mDataByteId != other.mDataByteId) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + mDataByte.contentHashCode()
result = 31 * result + mDataByteId.hashCode()
return result
}
/**
* Custom OutputStream to calculate the size and hash of binary file
*/
private inner class ByteOutputStream : ByteArrayOutputStream() {
private inner class ByteOutputStream(private val binaryCache: BinaryCache) : ByteArrayOutputStream() {
override fun close() {
mDataByte = this.toByteArray()
binaryCache.setByteArray(mDataByteId, this.toByteArray())
super.close()
}
}
companion object {
private val TAG = BinaryByte::class.java.name
const val MAX_BINARY_BYTES = 10240
@JvmField
val CREATOR: Parcelable.Creator<BinaryByte> = object : Parcelable.Creator<BinaryByte> {

View File

@@ -0,0 +1,82 @@
package com.kunzisoft.keepass.database.element.binary
import java.io.File
import java.util.*
class BinaryCache {
/**
* Cipher key generated when the database is loaded, and destroyed when the database is closed
* Can be used to temporarily store database elements
*/
var loadedCipherKey: LoadedKey = LoadedKey.generateNewCipherKey()
var cacheDirectory: File? = null
private val voidBinary = KeyByteArray(UNKNOWN, ByteArray(0))
fun getBinaryData(binaryId: String,
smallSize: Boolean = false,
compression: Boolean = false,
protection: Boolean = false): BinaryData {
val cacheDir = cacheDirectory
return if (smallSize || cacheDir == null) {
BinaryByte(binaryId, compression, protection)
} else {
val fileInCache = File(cacheDir, binaryId)
BinaryFile(fileInCache, compression, protection)
}
}
// Similar to file storage but much faster TODO SparseArray
private val byteArrayList = HashMap<String, ByteArray>()
fun getByteArray(key: String): KeyByteArray {
if (key == UNKNOWN) {
return voidBinary
}
if (!byteArrayList.containsKey(key)) {
val newItem = KeyByteArray(key, ByteArray(0))
byteArrayList[newItem.key] = newItem.data
return newItem
}
return KeyByteArray(key, byteArrayList[key]!!)
}
fun setByteArray(key: String, data: ByteArray): KeyByteArray {
if (key == UNKNOWN) {
return voidBinary
}
byteArrayList[key] = data
return KeyByteArray(key, data)
}
fun removeByteArray(key: String?) {
key?.let {
byteArrayList.remove(it)
}
}
fun clear() {
byteArrayList.clear()
}
companion object {
const val UNKNOWN = "UNKNOWN"
}
data class KeyByteArray(val key: String, val data: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is KeyByteArray) return false
if (key != other.key) return false
return true
}
override fun hashCode(): Int {
return key.hashCode()
}
}
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.binary
import android.app.ActivityManager
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import org.apache.commons.io.output.CountingOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
abstract class BinaryData : Parcelable {
var isCompressed: Boolean = false
protected set
var isProtected: Boolean = false
protected set
var isCorrupted: Boolean = false
private var mLength: Long = 0
private var mBinaryHash = 0
protected constructor(compressed: Boolean = false, protected: Boolean = false) {
this.isCompressed = compressed
this.isProtected = protected
this.mLength = 0
this.mBinaryHash = 0
}
protected constructor(parcel: Parcel) {
isCompressed = parcel.readByte().toInt() != 0
isProtected = parcel.readByte().toInt() != 0
isCorrupted = parcel.readByte().toInt() != 0
mLength = parcel.readLong()
mBinaryHash = parcel.readInt()
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (isCompressed) 1 else 0).toByte())
dest.writeByte((if (isProtected) 1 else 0).toByte())
dest.writeByte((if (isCorrupted) 1 else 0).toByte())
dest.writeLong(mLength)
dest.writeInt(mBinaryHash)
}
@Throws(IOException::class)
abstract fun getInputDataStream(binaryCache: BinaryCache): InputStream
@Throws(IOException::class)
abstract fun getOutputDataStream(binaryCache: BinaryCache): OutputStream
@Throws(IOException::class)
fun getUnGzipInputDataStream(binaryCache: BinaryCache): InputStream {
return if (isCompressed) {
GZIPInputStream(getInputDataStream(binaryCache))
} else {
getInputDataStream(binaryCache)
}
}
@Throws(IOException::class)
fun getGzipOutputDataStream(binaryCache: BinaryCache): OutputStream {
return if (isCompressed) {
GZIPOutputStream(getOutputDataStream(binaryCache))
} else {
getOutputDataStream(binaryCache)
}
}
@Throws(IOException::class)
abstract fun compress(binaryCache: BinaryCache)
@Throws(IOException::class)
abstract fun decompress(binaryCache: BinaryCache)
@Throws(IOException::class)
fun dataExists(): Boolean {
return mLength > 0
}
@Throws(IOException::class)
fun getSize(): Long {
return mLength
}
@Throws(IOException::class)
fun binaryHash(): Int {
return mBinaryHash
}
@Throws(IOException::class)
abstract fun clear(binaryCache: BinaryCache)
override fun describeContents(): Int {
return 0
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BinaryData) return false
if (isCompressed != other.isCompressed) return false
if (isProtected != other.isProtected) return false
if (isCorrupted != other.isCorrupted) return false
return true
}
override fun hashCode(): Int {
var result = isCompressed.hashCode()
result = 31 * result + isProtected.hashCode()
result = 31 * result + isCorrupted.hashCode()
result = 31 * result + mLength.hashCode()
result = 31 * result + mBinaryHash
return result
}
/**
* Custom OutputStream to calculate the size and hash of binary file
*/
protected inner class BinaryCountingOutputStream(out: OutputStream): CountingOutputStream(out) {
private val mMessageDigest: MessageDigest
init {
mLength = 0
mMessageDigest = MessageDigest.getInstance("MD5")
mBinaryHash = 0
}
override fun beforeWrite(n: Int) {
super.beforeWrite(n)
mLength = byteCount
}
override fun write(idx: Int) {
super.write(idx)
mMessageDigest.update(idx.toByte())
}
override fun write(bts: ByteArray) {
super.write(bts)
mMessageDigest.update(bts)
}
override fun write(bts: ByteArray, st: Int, end: Int) {
super.write(bts, st, end)
mMessageDigest.update(bts, st, end)
}
override fun close() {
super.close()
mLength = byteCount
val bytes = mMessageDigest.digest()
mBinaryHash = ByteBuffer.wrap(bytes).int
}
}
companion object {
private val TAG = BinaryData::class.java.name
fun canMemoryBeAllocatedInRAM(context: Context, memoryWanted: Long): Boolean {
val memoryInfo = ActivityManager.MemoryInfo()
(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).getMemoryInfo(memoryInfo)
val availableMemory = memoryInfo.availMem
return availableMemory > memoryWanted * 3
}
}
}

View File

@@ -17,19 +17,15 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
package com.kunzisoft.keepass.database.element.binary
import android.os.Parcel
import android.os.Parcelable
import android.util.Base64
import android.util.Base64InputStream
import android.util.Base64OutputStream
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.stream.readAllBytes
import org.apache.commons.io.output.CountingOutputStream
import com.kunzisoft.keepass.utils.readAllBytes
import java.io.*
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher
import javax.crypto.CipherInputStream
@@ -39,44 +35,43 @@ import javax.crypto.spec.IvParameterSpec
class BinaryFile : BinaryData {
private var mDataFile: File? = null
private var mLength: Long = 0
private var mBinaryHash = 0
// Cipher to encrypt temp file
@Transient
private var cipherEncryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER)
private var cipherEncryption: Cipher = Cipher.getInstance(LoadedKey.BINARY_CIPHER)
@Transient
private var cipherDecryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER)
constructor() : super()
private var cipherDecryption: Cipher = Cipher.getInstance(LoadedKey.BINARY_CIPHER)
constructor(dataFile: File,
compressed: Boolean = false,
protected: Boolean = false) : super(compressed, protected) {
this.mDataFile = dataFile
this.mLength = 0
this.mBinaryHash = 0
}
constructor(parcel: Parcel) : super(parcel) {
parcel.readString()?.let {
mDataFile = File(it)
}
mLength = parcel.readLong()
mBinaryHash = parcel.readInt()
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(mDataFile?.absolutePath)
}
@Throws(IOException::class)
override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream {
return buildInputStream(mDataFile, cipherKey)
override fun getInputDataStream(binaryCache: BinaryCache): InputStream {
return buildInputStream(mDataFile, binaryCache)
}
@Throws(IOException::class)
override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
return buildOutputStream(mDataFile, cipherKey)
override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream {
return buildOutputStream(mDataFile, binaryCache)
}
@Throws(IOException::class)
private fun buildInputStream(file: File?, cipherKey: Database.LoadedKey): InputStream {
private fun buildInputStream(file: File?, binaryCache: BinaryCache): InputStream {
val cipherKey = binaryCache.loadedCipherKey
return when {
file != null && file.length() > 0 -> {
cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
@@ -87,7 +82,8 @@ class BinaryFile : BinaryData {
}
@Throws(IOException::class)
private fun buildOutputStream(file: File?, cipherKey: Database.LoadedKey): OutputStream {
private fun buildOutputStream(file: File?, binaryCache: BinaryCache): OutputStream {
val cipherKey = binaryCache.loadedCipherKey
return when {
file != null -> {
cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
@@ -98,14 +94,14 @@ class BinaryFile : BinaryData {
}
@Throws(IOException::class)
override fun compress(cipherKey: Database.LoadedKey) {
override fun compress(binaryCache: BinaryCache) {
mDataFile?.let { concreteDataFile ->
// To compress, create a new binary with file
if (!isCompressed) {
// Encrypt the new gzipped temp file
val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
getInputDataStream(cipherKey).use { inputStream ->
GZIPOutputStream(buildOutputStream(fileBinaryCompress, cipherKey)).use { outputStream ->
getInputDataStream(binaryCache).use { inputStream ->
GZIPOutputStream(buildOutputStream(fileBinaryCompress, binaryCache)).use { outputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
@@ -123,13 +119,13 @@ class BinaryFile : BinaryData {
}
@Throws(IOException::class)
override fun decompress(cipherKey: Database.LoadedKey) {
override fun decompress(binaryCache: BinaryCache) {
mDataFile?.let { concreteDataFile ->
if (isCompressed) {
// Encrypt the new ungzipped temp file
val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
getUnGzipInputDataStream(cipherKey).use { inputStream ->
buildOutputStream(fileBinaryDecompress, cipherKey).use { outputStream ->
getUnGzipInputDataStream(binaryCache).use { inputStream ->
buildOutputStream(fileBinaryDecompress, binaryCache).use { outputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
@@ -146,39 +142,15 @@ class BinaryFile : BinaryData {
}
}
@Throws(IOException::class)
override fun clear() {
override fun clear(binaryCache: BinaryCache) {
if (mDataFile != null && !mDataFile!!.delete())
throw IOException("Unable to delete temp file " + mDataFile!!.absolutePath)
}
override fun dataExists(): Boolean {
return mDataFile != null && mLength > 0
}
override fun getSize(): Long {
return mLength
}
/**
* Hash of the raw encrypted file in temp folder, only to compare binary data
*/
@Throws(FileNotFoundException::class)
override fun binaryHash(): Int {
return mBinaryHash
}
override fun toString(): String {
return mDataFile.toString()
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(mDataFile?.absolutePath)
dest.writeLong(mLength)
dest.writeInt(mBinaryHash)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BinaryFile) return false
@@ -190,53 +162,10 @@ class BinaryFile : BinaryData {
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + (mDataFile?.hashCode() ?: 0)
result = 31 * result + mLength.hashCode()
result = 31 * result + mBinaryHash
return result
}
/**
* Custom OutputStream to calculate the size and hash of binary file
*/
private inner class BinaryCountingOutputStream(out: OutputStream): CountingOutputStream(out) {
private val mMessageDigest: MessageDigest
init {
mLength = 0
mMessageDigest = MessageDigest.getInstance("MD5")
mBinaryHash = 0
}
override fun beforeWrite(n: Int) {
super.beforeWrite(n)
mLength = byteCount
}
override fun write(idx: Int) {
super.write(idx)
mMessageDigest.update(idx.toByte())
}
override fun write(bts: ByteArray) {
super.write(bts)
mMessageDigest.update(bts)
}
override fun write(bts: ByteArray, st: Int, end: Int) {
super.write(bts, st, end)
mMessageDigest.update(bts, st, end)
}
override fun close() {
super.close()
mLength = byteCount
val bytes = mMessageDigest.digest()
mBinaryHash = ByteBuffer.wrap(bytes).int
}
}
companion object {
private val TAG = BinaryFile::class.java.name
@JvmField

View File

@@ -17,20 +17,19 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
package com.kunzisoft.keepass.database.element.binary
import android.util.Log
import java.io.File
import java.io.IOException
import kotlin.math.abs
abstract class BinaryPool<T> {
abstract class BinaryPool<T>(private val mBinaryCache: BinaryCache) {
protected val pool = LinkedHashMap<T, BinaryData>()
// To build unique file id
private var creationId: String = System.currentTimeMillis().toString()
private var poolId: String = abs(javaClass.simpleName.hashCode()).toString()
private var creationId: Long = System.currentTimeMillis()
private var poolId: Int = abs(javaClass.simpleName.hashCode())
private var binaryFileIncrement = 0L
/**
@@ -196,7 +195,7 @@ abstract class BinaryPool<T> {
* Different from doForEach, provide an ordered index to each binary
*/
private fun doForEachBinaryWithoutDuplication(action: (keyBinary: KeyBinary<T>) -> Unit,
conditionToAdd: (binary: BinaryData) -> Boolean) {
conditionToAdd: (binary: BinaryData) -> Boolean) {
orderedBinariesWithoutDuplication(conditionToAdd).forEach { keyBinary ->
action.invoke(keyBinary)
}
@@ -227,7 +226,7 @@ abstract class BinaryPool<T> {
@Throws(IOException::class)
fun clear() {
doForEachBinary { _, binary ->
binary.clear()
binary.clear(mBinaryCache)
}
pool.clear()
}

View File

@@ -1,8 +1,8 @@
package com.kunzisoft.keepass.database.element.database
package com.kunzisoft.keepass.database.element.binary
import java.util.*
class CustomIconPool : BinaryPool<UUID>() {
class CustomIconPool(binaryCache: BinaryCache) : BinaryPool<UUID>(binaryCache) {
override fun findUnusedKey(): UUID {
var newUUID = UUID.randomUUID()

View File

@@ -0,0 +1,18 @@
package com.kunzisoft.keepass.database.element.binary
import java.io.Serializable
import java.security.Key
import java.security.SecureRandom
import javax.crypto.KeyGenerator
class LoadedKey(val key: Key, val iv: ByteArray): Serializable {
companion object {
const val BINARY_CIPHER = "Blowfish/CBC/PKCS5Padding"
fun generateNewCipherKey(): LoadedKey {
val iv = ByteArray(8)
SecureRandom().nextBytes(iv)
return LoadedKey(KeyGenerator.getInstance("Blowfish").generateKey(), iv)
}
}
}

View File

@@ -1,126 +0,0 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Database
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
abstract class BinaryData : Parcelable {
var isCompressed: Boolean = false
protected set
var isProtected: Boolean = false
protected set
var isCorrupted: Boolean = false
/**
* Empty protected binary
*/
protected constructor()
protected constructor(compressed: Boolean = false, protected: Boolean = false) {
this.isCompressed = compressed
this.isProtected = protected
}
protected constructor(parcel: Parcel) {
isCompressed = parcel.readByte().toInt() != 0
isProtected = parcel.readByte().toInt() != 0
isCorrupted = parcel.readByte().toInt() != 0
}
@Throws(IOException::class)
abstract fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream
@Throws(IOException::class)
abstract fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream
@Throws(IOException::class)
fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream {
return if (isCompressed) {
GZIPInputStream(getInputDataStream(cipherKey))
} else {
getInputDataStream(cipherKey)
}
}
@Throws(IOException::class)
fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
return if (isCompressed) {
GZIPOutputStream(getOutputDataStream(cipherKey))
} else {
getOutputDataStream(cipherKey)
}
}
@Throws(IOException::class)
abstract fun compress(cipherKey: Database.LoadedKey)
@Throws(IOException::class)
abstract fun decompress(cipherKey: Database.LoadedKey)
@Throws(IOException::class)
abstract fun clear()
abstract fun dataExists(): Boolean
abstract fun getSize(): Long
abstract fun binaryHash(): Int
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (isCompressed) 1 else 0).toByte())
dest.writeByte((if (isProtected) 1 else 0).toByte())
dest.writeByte((if (isCorrupted) 1 else 0).toByte())
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BinaryData) return false
if (isCompressed != other.isCompressed) return false
if (isProtected != other.isProtected) return false
if (isCorrupted != other.isCorrupted) return false
return true
}
override fun hashCode(): Int {
var result = isCompressed.hashCode()
result = 31 * result + isProtected.hashCode()
result = 31 * result + isCorrupted.hashCode()
return result
}
companion object {
private val TAG = BinaryData::class.java.name
}
}

View File

@@ -19,23 +19,20 @@
package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.encrypt.aes.AESTransformer
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.security.DigestOutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
import kotlin.collections.ArrayList
@@ -45,9 +42,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
private var kdfListV3: MutableList<KdfEngine> = ArrayList()
// Only to generate unique file name
private var binaryPool = AttachmentPool()
override val version: String
get() = "KeePass 1"
@@ -80,6 +74,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
get() {
val list = ArrayList<EncryptionAlgorithm>()
list.add(EncryptionAlgorithm.AESRijndael)
list.add(EncryptionAlgorithm.Twofish)
return list
}
@@ -145,24 +140,11 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
}
@Throws(IOException::class)
fun makeFinalKey(masterSeed: ByteArray, masterSeed2: ByteArray, numRounds: Long) {
// Write checksum Checksum
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not implemented here.")
}
val nos = NullOutputStream()
val dos = DigestOutputStream(nos, messageDigest)
fun makeFinalKey(masterSeed: ByteArray, transformSeed: ByteArray, numRounds: Long) {
// Encrypt the master key a few times to make brute-force key-search harder
dos.write(masterSeed)
dos.write(AESKeyTransformerFactory.transformMasterKey(masterSeed2, masterKey, numRounds) ?: ByteArray(0))
finalKey = messageDigest.digest()
val transformedKey = AESTransformer.transformKey(transformSeed, masterKey, numRounds) ?: ByteArray(0)
// Write checksum Checksum
finalKey = HashManager.hashSha256(masterSeed, transformedKey)
}
override fun createGroup(): GroupKDB {
@@ -275,11 +257,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
addEntryTo(entry, origParent)
}
fun buildNewAttachment(cacheDirectory: File): BinaryData {
fun buildNewAttachment(): BinaryData {
// Generate an unique new file
return binaryPool.put { uniqueBinaryId ->
val fileInCache = File(cacheDirectory, uniqueBinaryId)
BinaryFile(fileInCache)
return attachmentPool.put { uniqueBinaryId ->
binaryCache.getBinaryData(uniqueBinaryId, false)
}.binary
}

View File

@@ -22,16 +22,21 @@ package com.kunzisoft.keepass.database.element.database
import android.content.res.Resources
import android.util.Base64
import android.util.Log
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.longTo8Bytes
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.AesEngine
import com.kunzisoft.keepass.database.crypto.CipherEngine
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.VariantDictionary
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
@@ -39,33 +44,31 @@ import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.VariantDictionary
import org.apache.commons.codec.binary.Hex
import org.w3c.dom.Node
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
import javax.crypto.Mac
import javax.xml.XMLConstants
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException
import kotlin.math.min
class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var hmacKey: ByteArray? = null
private set
var dataCipher = AesEngine.CIPHER_UUID
var cipherUuid = EncryptionAlgorithm.AESRijndael.uuid
private var dataEngine: CipherEngine = AesEngine()
var compressionAlgorithm = CompressionAlgorithm.GZip
var kdfParameters: KdfParameters? = null
@@ -108,8 +111,6 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
val deletedObjects = ArrayList<DeletedObject>()
val customData = HashMap<String, String>()
var binaryPool = AttachmentPool()
var localizedAppName = "KeePassDX"
init {
@@ -186,7 +187,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
CompressionAlgorithm.GZip -> {
// Only in databaseV3.1, in databaseV4 the header is zipped during the save
if (kdbxVersion.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) {
if (kdbxVersion.isBefore(FILE_VERSION_32_4)) {
compressAllBinaries()
}
}
@@ -194,9 +195,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
CompressionAlgorithm.GZip -> {
// In databaseV4 the header is zipped during the save, so not necessary here
if (kdbxVersion.toKotlinLong() >= FILE_VERSION_32_4.toKotlinLong()) {
decompressAllBinaries()
} else {
if (kdbxVersion.isBefore(FILE_VERSION_32_4)) {
when (newCompression) {
CompressionAlgorithm.None -> {
decompressAllBinaries()
@@ -204,18 +203,18 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
CompressionAlgorithm.GZip -> {
}
}
} else {
decompressAllBinaries()
}
}
}
}
private fun compressAllBinaries() {
binaryPool.doForEachBinary { _, binary ->
attachmentPool.doForEachBinary { _, binary ->
try {
val cipherKey = loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to compress binaries")
// To compress, create a new binary with file
binary.compress(cipherKey)
binary.compress(binaryCache)
} catch (e: Exception) {
Log.e(TAG, "Unable to compress $binary", e)
}
@@ -223,11 +222,9 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
private fun decompressAllBinaries() {
binaryPool.doForEachBinary { _, binary ->
attachmentPool.doForEachBinary { _, binary ->
try {
val cipherKey = loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to decompress binaries")
binary.decompress(cipherKey)
binary.decompress(binaryCache)
} catch (e: Exception) {
Log.e(TAG, "Unable to decompress $binary", e)
}
@@ -310,17 +307,15 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return this.iconsManager.getIcon(iconId)
}
fun buildNewCustomIcon(cacheDirectory: File,
customIconId: UUID? = null,
fun buildNewCustomIcon(customIconId: UUID? = null,
result: (IconImageCustom, BinaryData?) -> Unit) {
iconsManager.buildNewCustomIcon(cacheDirectory, customIconId, result)
iconsManager.buildNewCustomIcon(customIconId, result)
}
fun addCustomIcon(cacheDirectory: File,
customIconId: UUID? = null,
dataSize: Int,
fun addCustomIcon(customIconId: UUID? = null,
smallSize: Boolean,
result: (IconImageCustom, BinaryData?) -> Unit) {
iconsManager.addCustomIcon(cacheDirectory, customIconId, dataSize, result)
iconsManager.addCustomIcon(customIconId, smallSize, result)
}
fun isCustomIconBinaryDuplicate(binary: BinaryData): Boolean {
@@ -352,14 +347,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
masterKey = getFileKey(keyInputStream)
}
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("No SHA-256 implementation")
}
return messageDigest.digest(masterKey)
return HashManager.hashSha256(masterKey)
}
@Throws(IOException::class)
@@ -370,13 +358,13 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var transformedMasterKey = kdfEngine.transform(masterKey, keyDerivationFunctionParameters)
if (transformedMasterKey.size != 32) {
transformedMasterKey = CryptoUtil.hashSha256(transformedMasterKey)
transformedMasterKey = HashManager.hashSha256(transformedMasterKey)
}
val cmpKey = ByteArray(65)
System.arraycopy(masterSeed, 0, cmpKey, 0, 32)
System.arraycopy(transformedMasterKey, 0, cmpKey, 32, 32)
finalKey = CryptoUtil.resizeKey(cmpKey, 0, 64, dataEngine.keyLength())
finalKey = resizeKey(cmpKey, dataEngine.keyLength())
val messageDigest: MessageDigest
try {
@@ -391,6 +379,47 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
}
private fun resizeKey(inBytes: ByteArray, cbOut: Int): ByteArray {
if (cbOut == 0) return ByteArray(0)
val messageDigest = if (cbOut <= 32) HashManager.getHash256() else HashManager.getHash512()
messageDigest.update(inBytes, 0, 64)
val hash: ByteArray = messageDigest.digest()
if (cbOut == hash.size) {
return hash
}
val ret = ByteArray(cbOut)
if (cbOut < hash.size) {
System.arraycopy(hash, 0, ret, 0, cbOut)
} else {
var pos = 0
var r: Long = 0
while (pos < cbOut) {
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
}
val pbR = longTo8Bytes(r)
val part = hmac.doFinal(pbR)
val copy = min(cbOut - pos, part.size)
System.arraycopy(part, 0, ret, pos, copy)
pos += copy
r++
Arrays.fill(part, 0.toByte())
}
}
Arrays.fill(hash, 0.toByte())
return ret
}
override fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
try {
val documentBuilderFactory = DocumentBuilderFactory.newInstance()
@@ -488,17 +517,13 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
private fun checkKeyFileHash(data: String, hash: String): Boolean {
val digest: MessageDigest?
var success = false
try {
digest = MessageDigest.getInstance("SHA-256")
digest?.reset()
// hexadecimal encoding of the first 4 bytes of the SHA-256 hash of the key.
val dataDigest = digest.digest(Hex.decodeHex(data.toCharArray()))
.copyOfRange(0, 4)
.toHexString()
val dataDigest = HashManager.hashSha256(Hex.decodeHex(data.toCharArray()))
.copyOfRange(0, 4).toHexString()
success = dataDigest == hash
} catch (e: NoSuchAlgorithmException) {
} catch (e: Exception) {
e.printStackTrace()
}
return success
@@ -641,13 +666,12 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return publicCustomData.size() > 0
}
fun buildNewAttachment(cacheDirectory: File,
fun buildNewAttachment(smallSize: Boolean,
compression: Boolean,
protection: Boolean,
binaryPoolId: Int? = null): BinaryData {
return binaryPool.put(binaryPoolId) { uniqueBinaryId ->
val fileInCache = File(cacheDirectory, uniqueBinaryId)
BinaryFile(fileInCache, compression, protection)
return attachmentPool.put(binaryPoolId) { uniqueBinaryId ->
binaryCache.getBinaryData(uniqueBinaryId, smallSize, compression, protection)
}.binary
}
@@ -665,7 +689,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
// Build binaries to remove with all binaries known
val binariesToRemove = ArrayList<BinaryData>()
if (binaries.isEmpty()) {
binaryPool.doForEachBinary { _, binary ->
attachmentPool.doForEachBinary { _, binary ->
binariesToRemove.add(binary)
}
} else {
@@ -674,7 +698,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
// Remove binaries from the list
rootGroup?.doForEachChild(object : NodeHandler<EntryKDBX>() {
override fun operate(node: EntryKDBX): Boolean {
node.getAttachments(binaryPool, true).forEach {
node.getAttachments(attachmentPool, true).forEach {
binariesToRemove.remove(it.binaryData)
}
return binariesToRemove.isNotEmpty()
@@ -683,9 +707,9 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
// Effective removing
binariesToRemove.forEach {
try {
binaryPool.remove(it)
attachmentPool.remove(it)
if (clear)
it.clear()
it.clear(binaryCache)
} catch (e: Exception) {
Log.w(TAG, "Unable to clean binaries", e)
}
@@ -701,7 +725,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
override fun clearCache() {
try {
super.clearCache()
binaryPool.clear()
attachmentPool.clear()
} catch (e: Exception) {
Log.e(TAG, "Unable to clear cache", e)
}

View File

@@ -19,15 +19,16 @@
*/
package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.group.GroupVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.icon.IconsManager
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import org.apache.commons.codec.binary.Hex
import java.io.ByteArrayInputStream
@@ -35,7 +36,6 @@ import java.io.IOException
import java.io.InputStream
import java.io.UnsupportedEncodingException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
abstract class DatabaseVersioned<
@@ -48,21 +48,22 @@ abstract class DatabaseVersioned<
// Algorithm used to encrypt the database
protected var algorithm: EncryptionAlgorithm? = null
abstract val kdfEngine: KdfEngine?
abstract val kdfEngine: com.kunzisoft.keepass.database.crypto.kdf.KdfEngine?
abstract val kdfAvailableList: List<KdfEngine>
abstract val kdfAvailableList: List<com.kunzisoft.keepass.database.crypto.kdf.KdfEngine>
var masterKey = ByteArray(32)
var finalKey: ByteArray? = null
protected set
/**
* To manage binaries in faster way
* Cipher key generated when the database is loaded, and destroyed when the database is closed
* Can be used to temporarily store database elements
*/
var loadedCipherKey: Database.LoadedKey? = null
val iconsManager = IconsManager()
var binaryCache = BinaryCache()
val iconsManager = IconsManager(binaryCache)
var attachmentPool = AttachmentPool(binaryCache)
var changeDuplicateId = false
@@ -99,42 +100,21 @@ abstract class DatabaseVersioned<
protected fun getCompositeKey(key: String, keyfileInputStream: InputStream): ByteArray {
val fileKey = getFileKey(keyfileInputStream)
val passwordKey = getPasswordKey(key)
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not supported")
}
messageDigest.update(passwordKey)
return messageDigest.digest(fileKey)
return HashManager.hashSha256(passwordKey, fileKey)
}
@Throws(IOException::class)
protected fun getPasswordKey(key: String): ByteArray {
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not supported")
}
val bKey: ByteArray = try {
key.toByteArray(charset(passwordEncoding))
} catch (e: UnsupportedEncodingException) {
key.toByteArray()
}
messageDigest.update(bKey, 0, bKey.size)
return messageDigest.digest()
return HashManager.hashSha256(bKey)
}
@Throws(IOException::class)
protected fun getFileKey(keyInputStream: InputStream): ByteArray {
val keyData = keyInputStream.readBytes()
// Check XML key file
@@ -152,13 +132,8 @@ abstract class DatabaseVersioned<
// Key is not base 64, treat it as binary data
}
}
// Hash file as binary data
try {
return MessageDigest.getInstance("SHA-256").digest(keyData)
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not supported")
}
return HashManager.hashSha256(keyData)
}
protected open fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {

View File

@@ -22,7 +22,8 @@ package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.database.BinaryData
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID
import com.kunzisoft.keepass.database.element.node.NodeId
@@ -56,7 +57,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
/** A string describing what is in binaryData */
var binaryDescription = ""
var binaryData: BinaryData? = null
private var binaryDataId: Int? = null
// Determine if this is a MetaStream entry
val isMetaStream: Boolean
@@ -89,7 +90,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
url = parcel.readString() ?: url
notes = parcel.readString() ?: notes
binaryDescription = parcel.readString() ?: binaryDescription
binaryData = parcel.readParcelable(BinaryData::class.java.classLoader)
val rawBinaryDataId = parcel.readInt()
binaryDataId = if (rawBinaryDataId == -1) null else rawBinaryDataId
}
override fun readParentParcelable(parcel: Parcel): GroupKDB? {
@@ -108,7 +110,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
dest.writeString(url)
dest.writeString(notes)
dest.writeString(binaryDescription)
dest.writeParcelable(binaryData, flags)
dest.writeInt(binaryDataId ?: -1)
}
fun updateWith(source: EntryKDB) {
@@ -119,7 +121,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
url = source.url
notes = source.notes
binaryDescription = source.binaryDescription
binaryData = source.binaryData
binaryDataId = source.binaryDataId
}
override var username = ""
@@ -138,26 +140,39 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
override val type: Type
get() = Type.ENTRY
fun getAttachment(): Attachment? {
val binary = binaryData
return if (binary != null)
Attachment(binaryDescription, binary)
else null
fun getAttachment(attachmentPool: AttachmentPool): Attachment? {
binaryDataId?.let { poolId ->
attachmentPool[poolId]?.let { binary ->
return Attachment(binaryDescription, binary)
}
}
return null
}
fun containsAttachment(): Boolean {
return binaryData != null
return binaryDataId != null
}
fun putAttachment(attachment: Attachment) {
fun getBinary(attachmentPool: AttachmentPool): BinaryData? {
this.binaryDataId?.let {
return attachmentPool[it]
}
return null
}
fun putBinary(binaryData: BinaryData, attachmentPool: AttachmentPool) {
this.binaryDataId = attachmentPool.put(binaryData)
}
fun putAttachment(attachment: Attachment, attachmentPool: AttachmentPool) {
this.binaryDescription = attachment.name
this.binaryData = attachment.binaryData
this.binaryDataId = attachmentPool.put(attachment.binaryData)
}
fun removeAttachment(attachment: Attachment? = null) {
if (attachment == null || this.binaryDescription == attachment.name) {
this.binaryDescription = ""
this.binaryData = null
this.binaryDataId = null
}
}

View File

@@ -21,9 +21,10 @@ package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.database.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeId
@@ -32,7 +33,6 @@ import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashMap
@@ -56,32 +56,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
var additional = ""
var tags = ""
fun getSize(attachmentPool: AttachmentPool): Long {
var size = FIXED_LENGTH_SIZE
for (entry in fields.entries) {
size += entry.key.length.toLong()
size += entry.value.length().toLong()
}
size += getAttachmentsSize(attachmentPool)
size += autoType.defaultSequence.length.toLong()
for ((key, value) in autoType.entrySet()) {
size += key.length.toLong()
size += value.length.toLong()
}
for (entry in history) {
size += entry.getSize(attachmentPool)
}
size += overrideURL.length.toLong()
size += tags.length.toLong()
return size
}
override var expires: Boolean = false
constructor() : super()
@@ -102,6 +76,14 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
tags = parcel.readString() ?: tags
}
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
return parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeLong(usageCount.toKotlinLong())
@@ -164,14 +146,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
return NodeIdUUID(nodeId.id)
}
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
return parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
/**
* Decode a reference key with the FieldReferencesEngine
* @param decodeRef
@@ -228,6 +202,32 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override var locationChanged = DateInstant()
fun getSize(attachmentPool: AttachmentPool): Long {
var size = FIXED_LENGTH_SIZE
for (entry in fields.entries) {
size += entry.key.length.toLong()
size += entry.value.length().toLong()
}
size += getAttachmentsSize(attachmentPool)
size += autoType.defaultSequence.length.toLong()
for ((key, value) in autoType.entrySet()) {
size += key.length.toLong()
size += value.length.toLong()
}
for (entry in history) {
size += entry.getSize(attachmentPool)
}
size += overrideURL.length.toLong()
size += tags.length.toLong()
return size
}
fun afterChangeParent() {
locationChanged = DateInstant()
}
@@ -338,8 +338,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override fun touch(modified: Boolean, touchParents: Boolean) {
super.touch(modified, touchParents)
// TODO unsigned long
usageCount = UnsignedLong(usageCount.toKotlinLong() + 1)
usageCount.plusOne()
}
companion object {
@@ -350,6 +349,8 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
const val STR_URL = "URL"
const val STR_NOTES = "Notes"
private const val FIXED_LENGTH_SIZE: Long = 128 // Approximate fixed length size
fun newCustomNameAllowed(name: String): Boolean {
return !(name.equals(STR_TITLE, true)
|| name.equals(STR_USERNAME, true)
@@ -368,7 +369,5 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
return arrayOfNulls(size)
}
}
private const val FIXED_LENGTH_SIZE: Long = 128 // Approximate fixed length size
}
}

View File

@@ -36,6 +36,10 @@ abstract class EntryVersioned
constructor(parcel: Parcel) : super(parcel)
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
}
override fun nodeIndexInParentForNaturalOrder(): Int {
if (nodeIndexInParentForNaturalOrder == -1) {
val numberOfGroups = parent?.getChildGroups()?.size

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel
import android.os.Parcelable
class IconImage() : IconImageDraw(), Parcelable {
class IconImage() : IconImageDraw() {
var standard: IconImageStandard = IconImageStandard()
var custom: IconImageCustom = IconImageCustom()

View File

@@ -20,11 +20,12 @@
package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.*
class IconImageCustom : Parcelable, IconImageDraw {
class IconImageCustom : IconImageDraw {
var uuid: UUID
@@ -37,17 +38,17 @@ class IconImageCustom : Parcelable, IconImageDraw {
}
constructor(parcel: Parcel) {
uuid = parcel.readSerializable() as UUID
uuid = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(ParcelUuid(uuid), flags)
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeSerializable(uuid)
}
override fun hashCode(): Int {
val prime = 31
var result = 1

View File

@@ -19,7 +19,9 @@
*/
package com.kunzisoft.keepass.database.element.icon
abstract class IconImageDraw {
import android.os.Parcelable
abstract class IconImageDraw : Parcelable {
var selected = false
/**

View File

@@ -23,7 +23,7 @@ import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
class IconImageStandard : Parcelable, IconImageDraw {
class IconImageStandard : IconImageDraw {
val id: Int

View File

@@ -20,22 +20,19 @@
package com.kunzisoft.keepass.database.element.icon
import android.util.Log
import com.kunzisoft.keepass.database.element.database.BinaryByte
import com.kunzisoft.keepass.database.element.database.BinaryByte.Companion.MAX_BINARY_BYTES
import com.kunzisoft.keepass.database.element.database.BinaryData
import com.kunzisoft.keepass.database.element.database.BinaryFile
import com.kunzisoft.keepass.database.element.database.CustomIconPool
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.CustomIconPool
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
import java.io.File
import java.util.*
class IconsManager {
class IconsManager(private val binaryCache: BinaryCache) {
private val standardCache = List(NB_ICONS) {
IconImageStandard(it)
}
private val customCache = CustomIconPool()
private val customCache = CustomIconPool(binaryCache)
fun getIcon(iconId: Int): IconImageStandard {
val searchIconId = if (IconImageStandard.isCorrectIconId(iconId)) iconId else KEY_ID
@@ -52,25 +49,18 @@ class IconsManager {
* Custom
*/
fun buildNewCustomIcon(cacheDirectory: File,
key: UUID? = null,
fun buildNewCustomIcon(key: UUID? = null,
result: (IconImageCustom, BinaryData?) -> Unit) {
// Create a binary file for a brand new custom icon
addCustomIcon(cacheDirectory, key, -1, result)
addCustomIcon(key, false, result)
}
fun addCustomIcon(cacheDirectory: File,
key: UUID? = null,
dataSize: Int,
fun addCustomIcon(key: UUID? = null,
smallSize: Boolean,
result: (IconImageCustom, BinaryData?) -> Unit) {
val keyBinary = customCache.put(key) { uniqueBinaryId ->
// Create a byte array for better performance with small data
if (dataSize in 1..MAX_BINARY_BYTES) {
BinaryByte()
} else {
val fileInCache = File(cacheDirectory, uniqueBinaryId)
BinaryFile(fileInCache)
}
binaryCache.getBinaryData(uniqueBinaryId, smallSize)
}
result.invoke(IconImageCustom(keyBinary.keys.first()), keyBinary.binary)
}
@@ -83,11 +73,11 @@ class IconsManager {
return customCache.isBinaryDuplicate(binaryData)
}
fun removeCustomIcon(iconUuid: UUID) {
fun removeCustomIcon(binaryCache: BinaryCache, iconUuid: UUID) {
val binary = customCache[iconUuid]
customCache.remove(iconUuid)
try {
binary?.clear()
binary?.clear(binaryCache)
} catch (e: Exception) {
Log.w(TAG, "Unable to remove custom icon binary", e)
}

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.database.element.node
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import java.util.*
@@ -35,12 +36,12 @@ class NodeIdUUID : NodeId<UUID> {
}
constructor(parcel: Parcel) {
id = parcel.readSerializable() as UUID
id = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: id
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeSerializable(id)
dest.writeParcelable(ParcelUuid(id), flags)
}
override fun equals(other: Any?): Boolean {

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.security
import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.ChaCha20Engine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.engine.TwofishEngine
import com.kunzisoft.keepass.utils.ObjectNameResource
import java.util.UUID
enum class EncryptionAlgorithm : ObjectNameResource {
AESRijndael,
Twofish,
ChaCha20;
val cipherEngine: CipherEngine
get() {
return when (this) {
AESRijndael -> AesEngine()
Twofish -> TwofishEngine()
ChaCha20 -> ChaCha20Engine()
}
}
val dataCipher: UUID
get() {
return when (this) {
AESRijndael -> AesEngine.CIPHER_UUID
Twofish -> TwofishEngine.CIPHER_UUID
ChaCha20 -> ChaCha20Engine.CIPHER_UUID
}
}
override fun getName(resources: Resources): String {
return when (this) {
AESRijndael -> resources.getString(R.string.encryption_rijndael)
Twofish -> resources.getString(R.string.encryption_twofish)
ChaCha20 -> resources.getString(R.string.encryption_chacha20)
}
}
}

View File

@@ -21,9 +21,9 @@
package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.stream.readBytesLength
import com.kunzisoft.keepass.stream.readBytes4ToUInt
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.readBytes4ToUInt
import com.kunzisoft.keepass.utils.readBytesLength
import java.io.IOException
import java.io.InputStream

View File

@@ -19,30 +19,26 @@
*/
package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.crypto.CrsAlgorithm
import com.kunzisoft.keepass.crypto.keyDerivation.AesKdf
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.VariantDictionary
import com.kunzisoft.keepass.database.crypto.kdf.AesKdf
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.exception.VersionDatabaseException
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.utils.VariantDictionary
import com.kunzisoft.keepass.stream.CopyInputStream
import com.kunzisoft.keepass.utils.*
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
import java.security.DigestInputStream
import java.security.InvalidKeyException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader() {
var innerRandomStreamKey: ByteArray = ByteArray(32)
@@ -140,33 +136,27 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
*/
@Throws(IOException::class, VersionDatabaseException::class)
fun loadFromFile(inputStream: InputStream): HeaderAndHash {
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("No SHA-256 implementation")
}
val messageDigest: MessageDigest = HashManager.getHash256()
val headerBOS = ByteArrayOutputStream()
val copyInputStream = CopyInputStream(inputStream, headerBOS)
val digestInputStream = DigestInputStream(copyInputStream, messageDigest)
val littleEndianDataInputStream = LittleEndianDataInputStream(digestInputStream)
val sig1 = littleEndianDataInputStream.readUInt()
val sig2 = littleEndianDataInputStream.readUInt()
val sig1 = digestInputStream.readBytes4ToUInt()
val sig2 = digestInputStream.readBytes4ToUInt()
if (!matchesHeader(sig1, sig2)) {
throw VersionDatabaseException()
}
version = littleEndianDataInputStream.readUInt() // Erase previous value
version = digestInputStream.readBytes4ToUInt() // Erase previous value
if (!validVersion(version)) {
throw VersionDatabaseException()
}
var done = false
while (!done) {
done = readHeaderField(littleEndianDataInputStream)
done = readHeaderField(digestInputStream)
}
val hash = messageDigest.digest()
@@ -174,13 +164,13 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
}
@Throws(IOException::class)
private fun readHeaderField(dis: LittleEndianDataInputStream): Boolean {
private fun readHeaderField(dis: InputStream): Boolean {
val fieldID = dis.read().toByte()
val fieldSize: Int = if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) {
dis.readUShort()
val fieldSize: Int = if (version.isBefore(FILE_VERSION_32_4)) {
dis.readBytes2ToUShort()
} else {
dis.readUInt().toKotlinInt()
dis.readBytes4ToUInt().toKotlinInt()
}
var fieldData: ByteArray? = null
@@ -204,20 +194,20 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData
PwDbHeaderV4Fields.TransformSeed -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong())
PwDbHeaderV4Fields.TransformSeed -> if (version.isBefore(FILE_VERSION_32_4))
transformSeed = fieldData
PwDbHeaderV4Fields.TransformRounds -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong())
PwDbHeaderV4Fields.TransformRounds -> if (version.isBefore(FILE_VERSION_32_4))
setTransformRound(fieldData)
PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong())
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.isBefore(FILE_VERSION_32_4))
innerRandomStreamKey = fieldData
PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong())
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.isBefore(FILE_VERSION_32_4))
setRandomStreamID(fieldData)
PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData)
@@ -244,14 +234,14 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid cipher ID.")
}
databaseV4.dataCipher = bytes16ToUuid(pbId)
databaseV4.cipherUuid = bytes16ToUuid(pbId)
}
private fun setTransformRound(roundsByte: ByteArray) {
assignAesKdfEngineIfNotExists()
val rounds = bytes64ToLong(roundsByte)
val rounds = bytes64ToULong(roundsByte)
databaseV4.kdfParameters?.setUInt64(AesKdf.PARAM_ROUNDS, rounds)
databaseV4.numberKeyEncryptionRounds = rounds
databaseV4.numberKeyEncryptionRounds = rounds.toKotlinLong()
}
@Throws(IOException::class)
@@ -323,23 +313,5 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
fun matchesHeader(sig1: UnsignedInt, sig2: UnsignedInt): Boolean {
return sig1 == PWM_DBSIG_1 && (sig2 == DBSIG_PRE2 || sig2 == DBSIG_2)
}
@Throws(IOException::class)
fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray {
val blockKey = HmacBlockStream.getHmacKey64(key, UnsignedLong.MAX_VALUE)
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(blockKey, "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw IOException("No HmacAlogirthm")
} catch (e: InvalidKeyException) {
throw IOException("Invalid Hmac Key")
}
return hmac.doFinal(header)
}
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.file;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
import java.util.Date;
public class DateKDBXUtil {
private static final DateTime dotNetEpoch = new DateTime(1, 1, 1, 0, 0, 0, DateTimeZone.UTC);
private static final DateTime javaEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeZone.UTC);
private static final long epochOffset;
static {
epochOffset = (javaEpoch.getMillis() - dotNetEpoch.getMillis()) / 1000L;
}
public static Date convertKDBX4Time(long seconds) {
DateTime dt = dotNetEpoch.plus(seconds * 1000L);
// Switch corrupted dates to a more recent date that won't cause issues on the client
if (dt.isBefore(javaEpoch)) {
return javaEpoch.toDate();
}
return dt.toDate();
}
public static long convertDateToKDBX4Time(DateTime dt) {
Duration duration = new Duration( javaEpoch, dt );
long seconds = ( duration.getMillis() / 1000L );
return seconds + epochOffset;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.file
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.Duration
import java.util.*
object DateKDBXUtil {
private val dotNetEpoch = DateTime(1, 1, 1, 0, 0, 0, DateTimeZone.UTC)
private val javaEpoch = DateTime(1970, 1, 1, 0, 0, 0, DateTimeZone.UTC)
private val epochOffset = (javaEpoch.millis - dotNetEpoch.millis) / 1000L
fun convertKDBX4Time(seconds: Long): Date {
val dt = dotNetEpoch.plus(seconds * 1000L)
// Switch corrupted dates to a more recent date that won't cause issues on the client
return if (dt.isBefore(javaEpoch)) {
javaEpoch.toDate()
} else dt.toDate()
}
fun convertDateToKDBX4Time(dt: DateTime?): Long {
val duration = Duration(javaEpoch, dt)
val seconds = duration.millis / 1000L
return seconds + epochOffset
}
}

View File

@@ -19,15 +19,21 @@
*/
package com.kunzisoft.keepass.database.file.input
import com.kunzisoft.keepass.database.element.Database
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import java.io.File
import java.io.InputStream
abstract class DatabaseInput<PwDb : DatabaseVersioned<*, *, *, *>>
(protected val cacheDirectory: File) {
abstract class DatabaseInput<D : DatabaseVersioned<*, *, *, *>>
(protected val cacheDirectory: File,
protected val isRAMSufficient: (memoryWanted: Long) -> Boolean) {
private var startTimeKey = System.currentTimeMillis()
private var startTimeContent = System.currentTimeMillis()
/**
* Load a versioned database file, return contents in a new DatabaseVersioned.
@@ -43,15 +49,39 @@ abstract class DatabaseInput<PwDb : DatabaseVersioned<*, *, *, *>>
abstract fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyfileInputStream: InputStream?,
loadedCipherKey: Database.LoadedKey,
loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean = false): PwDb
fixDuplicateUUID: Boolean = false): D
@Throws(LoadDatabaseException::class)
abstract fun openDatabase(databaseInputStream: InputStream,
masterKey: ByteArray,
loadedCipherKey: Database.LoadedKey,
loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean = false): PwDb
fixDuplicateUUID: Boolean = false): D
protected fun startKeyTimer(progressTaskUpdater: ProgressTaskUpdater?) {
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
Log.d(TAG, "Start retrieving database key...")
startTimeKey = System.currentTimeMillis()
}
protected fun stopKeyTimer() {
Log.d(TAG, "Stop retrieving database key... ${System.currentTimeMillis() - startTimeKey} ms")
}
protected fun startContentTimer(progressTaskUpdater: ProgressTaskUpdater?) {
progressTaskUpdater?.updateMessage(R.string.decrypting_db)
Log.d(TAG, "Start decrypting database content...")
startTimeContent = System.currentTimeMillis()
}
protected fun stopContentTimer() {
Log.d(TAG, "Stop retrieving database content... ${System.currentTimeMillis() - startTimeContent} ms")
}
companion object {
private val TAG = DatabaseInput::class.java.name
}
}

View File

@@ -20,34 +20,34 @@
package com.kunzisoft.keepass.database.file.input
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.*
import java.io.*
import java.security.*
import java.security.DigestInputStream
import java.security.MessageDigest
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import javax.crypto.CipherInputStream
/**
* Load a KDB database file.
*/
class DatabaseInputKDB(cacheDirectory: File)
: DatabaseInput<DatabaseKDB>(cacheDirectory) {
class DatabaseInputKDB(cacheDirectory: File,
isRAMSufficient: (memoryWanted: Long) -> Boolean)
: DatabaseInput<DatabaseKDB>(cacheDirectory, isRAMSufficient) {
private lateinit var mDatabase: DatabaseKDB
@@ -55,11 +55,11 @@ class DatabaseInputKDB(cacheDirectory: File)
override fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyfileInputStream: InputStream?,
loadedCipherKey: Database.LoadedKey,
loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDB {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.retrieveMasterKey(password, keyfileInputStream)
}
}
@@ -67,11 +67,11 @@ class DatabaseInputKDB(cacheDirectory: File)
@Throws(LoadDatabaseException::class)
override fun openDatabase(databaseInputStream: InputStream,
masterKey: ByteArray,
loadedCipherKey: Database.LoadedKey,
loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDB {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.masterKey = masterKey
}
}
@@ -83,6 +83,7 @@ class DatabaseInputKDB(cacheDirectory: File)
assignMasterKey: (() -> Unit)? = null): DatabaseKDB {
try {
startKeyTimer(progressTaskUpdater)
// Load entire file, most of it's encrypted.
val fileSize = databaseInputStream.available()
@@ -105,8 +106,8 @@ class DatabaseInputKDB(cacheDirectory: File)
throw VersionDatabaseException()
}
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
mDatabase = DatabaseKDB()
mDatabase.binaryCache.cacheDirectory = cacheDirectory
mDatabase.changeDuplicateId = fixDuplicateUUID
assignMasterKey?.invoke()
@@ -130,45 +131,23 @@ class DatabaseInputKDB(cacheDirectory: File)
header.transformSeed,
mDatabase.numberKeyEncryptionRounds)
progressTaskUpdater?.updateMessage(R.string.decrypting_db)
// Initialize Rijndael algorithm
stopKeyTimer()
startContentTimer(progressTaskUpdater)
val cipher: Cipher = try {
when {
mDatabase.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> {
CipherFactory.getInstance("AES/CBC/PKCS5Padding")
}
mDatabase.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> {
CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING")
}
else -> throw IOException("Encryption algorithm is not supported")
}
} catch (e1: NoSuchAlgorithmException) {
throw IOException("No such algorithm")
} catch (e1: NoSuchPaddingException) {
throw IOException("No such pdading")
}
try {
cipher.init(Cipher.DECRYPT_MODE,
SecretKeySpec(mDatabase.finalKey, "AES"),
IvParameterSpec(header.encryptionIV))
} catch (e1: InvalidKeyException) {
throw IOException("Invalid key")
} catch (e1: InvalidAlgorithmParameterException) {
throw IOException("Invalid algorithm parameter.")
}
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("No SHA-256 algorithm")
mDatabase.encryptionAlgorithm
.cipherEngine.getCipher(Cipher.DECRYPT_MODE,
mDatabase.finalKey ?: ByteArray(0),
header.encryptionIV)
} catch (e: Exception) {
throw IOException("Algorithm not supported.", e)
}
// Decrypt content
val messageDigest: MessageDigest = HashManager.getHash256()
val cipherInputStream = BufferedInputStream(
DigestInputStream(
BetterCipherInputStream(databaseInputStream, cipher),
CipherInputStream(databaseInputStream, cipher),
messageDigest
)
)
@@ -224,7 +203,7 @@ class DatabaseInputKDB(cacheDirectory: File)
}
0x0003 -> {
newGroup?.let { group ->
group.creationTime = cipherInputStream.readBytes5ToDate()
group.creationTime = DateInstant(cipherInputStream.readBytes5ToDate())
} ?:
newEntry?.let { entry ->
var iconId = cipherInputStream.readBytes4ToUInt().toKotlinInt()
@@ -237,7 +216,7 @@ class DatabaseInputKDB(cacheDirectory: File)
}
0x0004 -> {
newGroup?.let { group ->
group.lastModificationTime = cipherInputStream.readBytes5ToDate()
group.lastModificationTime = DateInstant(cipherInputStream.readBytes5ToDate())
} ?:
newEntry?.let { entry ->
entry.title = cipherInputStream.readBytesToString(fieldSize)
@@ -245,7 +224,7 @@ class DatabaseInputKDB(cacheDirectory: File)
}
0x0005 -> {
newGroup?.let { group ->
group.lastAccessTime = cipherInputStream.readBytes5ToDate()
group.lastAccessTime = DateInstant(cipherInputStream.readBytes5ToDate())
} ?:
newEntry?.let { entry ->
entry.url = cipherInputStream.readBytesToString(fieldSize)
@@ -253,7 +232,7 @@ class DatabaseInputKDB(cacheDirectory: File)
}
0x0006 -> {
newGroup?.let { group ->
group.expiryTime = cipherInputStream.readBytes5ToDate()
group.expiryTime = DateInstant(cipherInputStream.readBytes5ToDate())
} ?:
newEntry?.let { entry ->
entry.username = cipherInputStream.readBytesToString(fieldSize)
@@ -280,22 +259,22 @@ class DatabaseInputKDB(cacheDirectory: File)
group.groupFlags = cipherInputStream.readBytes4ToUInt().toKotlinInt()
} ?:
newEntry?.let { entry ->
entry.creationTime = cipherInputStream.readBytes5ToDate()
entry.creationTime = DateInstant(cipherInputStream.readBytes5ToDate())
}
}
0x000A -> {
newEntry?.let { entry ->
entry.lastModificationTime = cipherInputStream.readBytes5ToDate()
entry.lastModificationTime = DateInstant(cipherInputStream.readBytes5ToDate())
}
}
0x000B -> {
newEntry?.let { entry ->
entry.lastAccessTime = cipherInputStream.readBytes5ToDate()
entry.lastAccessTime = DateInstant(cipherInputStream.readBytes5ToDate())
}
}
0x000C -> {
newEntry?.let { entry ->
entry.expiryTime = cipherInputStream.readBytes5ToDate()
entry.expiryTime = DateInstant(cipherInputStream.readBytes5ToDate())
}
}
0x000D -> {
@@ -306,11 +285,9 @@ class DatabaseInputKDB(cacheDirectory: File)
0x000E -> {
newEntry?.let { entry ->
if (fieldSize > 0) {
val binaryAttachment = mDatabase.buildNewAttachment(cacheDirectory)
entry.binaryData = binaryAttachment
val cipherKey = mDatabase.loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to load binaries")
BufferedOutputStream(binaryAttachment.getOutputDataStream(cipherKey)).use { outputStream ->
val binaryData = mDatabase.buildNewAttachment()
entry.putBinary(binaryData, mDatabase.attachmentPool)
BufferedOutputStream(binaryData.getOutputDataStream(mDatabase.binaryCache)).use { outputStream ->
cipherInputStream.readBytes(fieldSize) { buffer ->
outputStream.write(buffer)
}
@@ -343,6 +320,8 @@ class DatabaseInputKDB(cacheDirectory: File)
}
constructTreeFromIndex()
stopContentTimer()
} catch (e: LoadDatabaseException) {
mDatabase.clearCache()
throw e

View File

@@ -21,15 +21,16 @@ package com.kunzisoft.keepass.database.file.input
import android.util.Base64
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.encrypt.StreamCipher
import com.kunzisoft.keepass.database.crypto.CipherEngine
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.HmacBlock
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.database.BinaryData
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
@@ -41,13 +42,13 @@ import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.stream.HashedBlockInputStream
import com.kunzisoft.keepass.stream.HmacBlockInputStream
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import org.bouncycastle.crypto.StreamCipher
import com.kunzisoft.keepass.utils.*
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory
@@ -61,10 +62,12 @@ import java.util.*
import java.util.zip.GZIPInputStream
import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import javax.crypto.Mac
import kotlin.math.min
class DatabaseInputKDBX(cacheDirectory: File)
: DatabaseInput<DatabaseKDBX>(cacheDirectory) {
class DatabaseInputKDBX(cacheDirectory: File,
isRAMSufficient: (memoryWanted: Long) -> Boolean)
: DatabaseInput<DatabaseKDBX>(cacheDirectory, isRAMSufficient) {
private var randomStream: StreamCipher? = null
private lateinit var mDatabase: DatabaseKDBX
@@ -97,11 +100,11 @@ class DatabaseInputKDBX(cacheDirectory: File)
override fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyfileInputStream: InputStream?,
loadedCipherKey: Database.LoadedKey,
loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDBX {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.retrieveMasterKey(password, keyfileInputStream)
}
}
@@ -109,11 +112,11 @@ class DatabaseInputKDBX(cacheDirectory: File)
@Throws(LoadDatabaseException::class)
override fun openDatabase(databaseInputStream: InputStream,
masterKey: ByteArray,
loadedCipherKey: Database.LoadedKey,
loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDBX {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.masterKey = masterKey
}
}
@@ -124,8 +127,9 @@ class DatabaseInputKDBX(cacheDirectory: File)
fixDuplicateUUID: Boolean,
assignMasterKey: (() -> Unit)? = null): DatabaseKDBX {
try {
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
startKeyTimer(progressTaskUpdater)
mDatabase = DatabaseKDBX()
mDatabase.binaryCache.cacheDirectory = cacheDirectory
mDatabase.changeDuplicateId = fixDuplicateUUID
@@ -140,26 +144,27 @@ class DatabaseInputKDBX(cacheDirectory: File)
assignMasterKey?.invoke()
mDatabase.makeFinalKey(header.masterSeed)
progressTaskUpdater?.updateMessage(R.string.decrypting_db)
stopKeyTimer()
startContentTimer(progressTaskUpdater)
val engine: CipherEngine
val cipher: Cipher
try {
engine = CipherFactory.getInstance(mDatabase.dataCipher)
engine = EncryptionAlgorithm.getFrom(mDatabase.cipherUuid).cipherEngine
mDatabase.setDataEngine(engine)
mDatabase.encryptionAlgorithm = engine.getPwEncryptionAlgorithm()
mDatabase.encryptionAlgorithm = engine.getEncryptionAlgorithm()
cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV)
} catch (e: Exception) {
throw InvalidAlgorithmDatabaseException(e)
}
val isPlain: InputStream
if (mDatabase.kdbxVersion.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
val plainInputStream: InputStream
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
val decrypted = attachCipherStream(databaseInputStream, cipher)
val dataDecrypted = LittleEndianDataInputStream(decrypted)
val dataDecrypted = CipherInputStream(databaseInputStream, cipher)
val storedStartBytes: ByteArray?
try {
storedStartBytes = dataDecrypted.readBytes(32)
storedStartBytes = dataDecrypted.readBytesLength(32)
if (storedStartBytes.size != 32) {
throw InvalidCredentialsDatabaseException()
}
@@ -171,47 +176,57 @@ class DatabaseInputKDBX(cacheDirectory: File)
throw InvalidCredentialsDatabaseException()
}
isPlain = HashedBlockInputStream(dataDecrypted)
plainInputStream = HashedBlockInputStream(dataDecrypted)
} else { // KDBX 4
val isData = LittleEndianDataInputStream(databaseInputStream)
val storedHash = isData.readBytes(32)
if (!Arrays.equals(storedHash, hashOfHeader)) {
val storedHash = databaseInputStream.readBytesLength(32)
if (!storedHash.contentEquals(hashOfHeader)) {
throw InvalidCredentialsDatabaseException()
}
val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException()
val headerHmac = DatabaseHeaderKDBX.computeHeaderHmac(pbHeader, hmacKey)
val storedHmac = isData.readBytes(32)
val blockKey = HmacBlock.getHmacKey64(hmacKey, UnsignedLong.MAX_BYTES)
val hmac: Mac = HmacBlock.getHmacSha256(blockKey)
val headerHmac = hmac.doFinal(pbHeader)
val storedHmac = databaseInputStream.readBytesLength(32)
if (storedHmac.size != 32) {
throw InvalidCredentialsDatabaseException()
}
// Mac doesn't match
if (!Arrays.equals(headerHmac, storedHmac)) {
if (!headerHmac.contentEquals(storedHmac)) {
throw InvalidCredentialsDatabaseException()
}
val hmIs = HmacBlockInputStream(isData, true, hmacKey)
val hmIs = HmacBlockInputStream(databaseInputStream, true, hmacKey)
isPlain = attachCipherStream(hmIs, cipher)
plainInputStream = CipherInputStream(hmIs, cipher)
}
val inputStreamXml: InputStream
inputStreamXml = when (mDatabase.compressionAlgorithm) {
CompressionAlgorithm.GZip -> GZIPInputStream(isPlain)
else -> isPlain
val inputStreamXml: InputStream = when (mDatabase.compressionAlgorithm) {
CompressionAlgorithm.GZip -> GZIPInputStream(plainInputStream)
else -> plainInputStream
}
if (mDatabase.kdbxVersion.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
loadInnerHeader(inputStreamXml, header)
if (!mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
readInnerHeader(inputStreamXml, header)
}
try {
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
randomStream = CrsAlgorithm.getCipher(header.innerRandomStream, header.innerRandomStreamKey)
} catch (e: Exception) {
throw LoadDatabaseException(e)
}
readDocumentStreamed(createPullParser(inputStreamXml))
val xmlPullParserFactory = XmlPullParserFactory.newInstance().apply {
isNamespaceAware = false
}
val xmlPullParser = xmlPullParserFactory.newPullParser().apply {
setInput(inputStreamXml, null)
}
readDocumentStreamed(xmlPullParser)
stopContentTimer()
} catch (e: LoadDatabaseException) {
throw e
@@ -231,63 +246,55 @@ class DatabaseInputKDBX(cacheDirectory: File)
return mDatabase
}
private fun attachCipherStream(inputStream: InputStream, cipher: Cipher): InputStream {
return CipherInputStream(inputStream, cipher)
}
@Throws(IOException::class)
private fun loadInnerHeader(inputStream: InputStream, header: DatabaseHeaderKDBX) {
val lis = LittleEndianDataInputStream(inputStream)
private fun readInnerHeader(dataInputStream: InputStream,
header: DatabaseHeaderKDBX) {
while (true) {
if (!readInnerHeader(lis, header)) break
}
}
var readStream = true
while (readStream) {
val fieldId = dataInputStream.read().toByte()
@Throws(IOException::class)
private fun readInnerHeader(dataInputStream: LittleEndianDataInputStream,
header: DatabaseHeaderKDBX): Boolean {
val fieldId = dataInputStream.read().toByte()
val size = dataInputStream.readBytes4ToUInt().toKotlinInt()
if (size < 0) throw IOException("Corrupted file")
val size = dataInputStream.readUInt().toKotlinInt()
if (size < 0) throw IOException("Corrupted file")
var data = ByteArray(0)
if (size > 0) {
if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) {
// TODO OOM here
data = dataInputStream.readBytes(size)
var data = ByteArray(0)
try {
if (size > 0) {
if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) {
data = dataInputStream.readBytesLength(size)
}
}
} catch (e: Exception) {
// OOM only if corrupted file
throw IOException("Corrupted file")
}
}
var result = true
when (fieldId) {
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader -> {
result = false
}
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID -> {
header.setRandomStreamID(data)
}
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey -> {
header.innerRandomStreamKey = data
}
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> {
// Read in a file
val protectedFlag = dataInputStream.read().toByte() == DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
val byteLength = size - 1
// No compression at this level
val protectedBinary = mDatabase.buildNewAttachment(cacheDirectory, false, protectedFlag)
val cipherKey = mDatabase.loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to load binaries")
protectedBinary.getOutputDataStream(cipherKey).use { outputStream ->
dataInputStream.readBytes(byteLength) { buffer ->
outputStream.write(buffer)
readStream = true
when (fieldId) {
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader -> {
readStream = false
}
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID -> {
header.setRandomStreamID(data)
}
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey -> {
header.innerRandomStreamKey = data
}
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> {
// Read in a file
val protectedFlag = dataInputStream.read().toByte() == DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
val byteLength = size - 1
// No compression at this level
val protectedBinary = mDatabase.buildNewAttachment(
isRAMSufficient.invoke(byteLength.toLong()), false, protectedFlag)
protectedBinary.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
dataInputStream.readBytes(byteLength) { buffer ->
outputStream.write(buffer)
}
}
}
}
}
return result
}
private enum class KdbContext {
@@ -703,12 +710,11 @@ class DatabaseInputKDBX(cacheDirectory: File)
} else if (ctx == KdbContext.CustomIcons && name.equals(DatabaseKDBXXML.ElemCustomIcons, ignoreCase = true)) {
return KdbContext.Meta
} else if (ctx == KdbContext.CustomIcon && name.equals(DatabaseKDBXXML.ElemCustomIconItem, ignoreCase = true)) {
if (customIconID != DatabaseVersioned.UUID_ZERO && customIconData != null) {
mDatabase.addCustomIcon(cacheDirectory, customIconID, customIconData!!.size) { _, binary ->
mDatabase.loadedCipherKey?.let { cipherKey ->
binary?.getOutputDataStream(cipherKey)?.use { outputStream ->
outputStream.write(customIconData)
}
val iconData = customIconData
if (customIconID != DatabaseVersioned.UUID_ZERO && iconData != null) {
mDatabase.addCustomIcon(customIconID, isRAMSufficient.invoke(iconData.size.toLong())) { _, binary ->
binary?.getOutputDataStream(mDatabase.binaryCache)?.use { outputStream ->
outputStream.write(iconData)
}
}
}
@@ -784,7 +790,7 @@ class DatabaseInputKDBX(cacheDirectory: File)
return KdbContext.Entry
} else if (ctx == KdbContext.EntryBinary && name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) {
if (ctxBinaryName != null && ctxBinaryValue != null) {
ctxEntry?.putAttachment(Attachment(ctxBinaryName!!, ctxBinaryValue!!), mDatabase.binaryPool)
ctxEntry?.putAttachment(Attachment(ctxBinaryName!!, ctxBinaryValue!!), mDatabase.attachmentPool)
}
ctxBinaryName = null
ctxBinaryValue = null
@@ -837,7 +843,13 @@ class DatabaseInputKDBX(cacheDirectory: File)
val sDate = readString(xpp)
var utcDate: Date? = null
if (mDatabase.kdbxVersion.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
try {
utcDate = DatabaseKDBXXML.DateFormatter.parse(sDate)
} catch (e: ParseException) {
// Catch with null test below
}
} else {
var buf = Base64.decode(sDate, BASE_64_FLAG)
if (buf.size != 8) {
val buf8 = ByteArray(8)
@@ -847,14 +859,6 @@ class DatabaseInputKDBX(cacheDirectory: File)
val seconds = bytes64ToLong(buf)
utcDate = DateKDBXUtil.convertKDBX4Time(seconds)
} else {
try {
utcDate = DatabaseKDBXXML.DateFormatter.parse(sDate)
} catch (e: ParseException) {
// Catch with null test below
}
}
return utcDate ?: Date(0L)
@@ -978,11 +982,14 @@ class DatabaseInputKDBX(cacheDirectory: File)
xpp.next() // Consume end tag
val id = Integer.parseInt(ref)
// A ref is not necessarily an index in Database V3.1
var binaryRetrieve = mDatabase.binaryPool[id]
var binaryRetrieve = mDatabase.attachmentPool[id]
// Create empty binary if not retrieved in pool
if (binaryRetrieve == null) {
binaryRetrieve = mDatabase.buildNewAttachment(cacheDirectory,
compression = false, protection = false, binaryPoolId = id)
binaryRetrieve = mDatabase.buildNewAttachment(
smallSize = false,
compression = false,
protection = false,
binaryPoolId = id)
}
return binaryRetrieve
}
@@ -1018,17 +1025,16 @@ class DatabaseInputKDBX(cacheDirectory: File)
return null
// Build the new binary and compress
val binaryAttachment = mDatabase.buildNewAttachment(cacheDirectory, compressed, protected, binaryId)
val binaryCipherKey = mDatabase.loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to load binaries")
val binaryAttachment = mDatabase.buildNewAttachment(
isRAMSufficient.invoke(base64.length.toLong()), compressed, protected, binaryId)
try {
binaryAttachment.getOutputDataStream(binaryCipherKey).use { outputStream ->
binaryAttachment.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
outputStream.write(Base64.decode(base64, BASE_64_FLAG))
}
} catch (e: Exception) {
Log.e(TAG, "Unable to read base 64 attachment", e)
binaryAttachment.isCorrupted = true
binaryAttachment.getOutputDataStream(binaryCipherKey).use { outputStream ->
binaryAttachment.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
outputStream.write(base64.toByteArray())
}
}
@@ -1057,9 +1063,7 @@ class DatabaseInputKDBX(cacheDirectory: File)
val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected)
if (protect != null && protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)) {
Base64.decode(xpp.safeNextText(), BASE_64_FLAG)?.let { data ->
val plainText = ByteArray(data.size)
randomStream?.processBytes(data, 0, data.size, plainText, 0)
return plainText
return randomStream?.processBytes(data)
}
return ByteArray(0)
}
@@ -1083,17 +1087,6 @@ class DatabaseInputKDBX(cacheDirectory: File)
private val TAG = DatabaseInputKDBX::class.java.name
private val DEFAULT_HISTORY_DAYS = UnsignedInt(365)
@Throws(XmlPullParserException::class)
private fun createPullParser(readerStream: InputStream): XmlPullParser {
val xmlPullParserFactory = XmlPullParserFactory.newInstance()
xmlPullParserFactory.isNamespaceAware = false
val xpp = xmlPullParserFactory.newPullParser()
xpp.setInput(readerStream, null)
return xpp
}
}
}

View File

@@ -19,8 +19,8 @@
*/
package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.utils.uIntTo4Bytes
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.uIntTo4Bytes
import java.io.IOException
import java.io.OutputStream

View File

@@ -19,30 +19,29 @@
*/
package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.crypto.HmacBlock
import com.kunzisoft.keepass.database.crypto.VariantDictionary
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.utils.VariantDictionary
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.stream.MacOutputStream
import com.kunzisoft.keepass.utils.*
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.OutputStream
import java.security.DigestOutputStream
import java.security.InvalidKeyException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class)
class DatabaseHeaderOutputKDBX @Throws(IOException::class)
constructor(private val databaseKDBX: DatabaseKDBX,
private val header: DatabaseHeaderKDBX,
outputStream: OutputStream) {
private val los: LittleEndianDataOutputStream
private val mos: MacOutputStream
private val dos: DigestOutputStream
lateinit var headerHmac: ByteArray
@@ -51,14 +50,6 @@ constructor(private val databaseKDBX: DatabaseKDBX,
private set
init {
val md: MessageDigest
try {
md = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException("SHA-256 not implemented here.", e)
}
try {
databaseKDBX.makeFinalKey(header.masterSeed)
} catch (e: IOException) {
@@ -66,34 +57,26 @@ constructor(private val databaseKDBX: DatabaseKDBX,
}
val hmacKey = databaseKDBX.hmacKey ?: throw DatabaseOutputException("HmacKey is not defined")
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, UnsignedLong.MAX_VALUE), "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException(e)
} catch (e: InvalidKeyException) {
throw DatabaseOutputException(e)
}
val blockKey = HmacBlock.getHmacKey64(hmacKey, UnsignedLong.MAX_BYTES)
val hmac: Mac = HmacBlock.getHmacSha256(blockKey)
dos = DigestOutputStream(outputStream, md)
val messageDigest: MessageDigest = HashManager.getHash256()
dos = DigestOutputStream(outputStream, messageDigest)
mos = MacOutputStream(dos, hmac)
los = LittleEndianDataOutputStream(mos)
}
@Throws(IOException::class)
fun output() {
los.writeUInt(DatabaseHeader.PWM_DBSIG_1)
los.writeUInt(DatabaseHeaderKDBX.DBSIG_2)
los.writeUInt(header.version)
mos.write4BytesUInt(DatabaseHeader.PWM_DBSIG_1)
mos.write4BytesUInt(DatabaseHeaderKDBX.DBSIG_2)
mos.write4BytesUInt(header.version)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.dataCipher))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.cipherUuid))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, uIntTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed)
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (header.version.isBefore(FILE_VERSION_32_4)) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds))
} else {
@@ -104,7 +87,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV)
}
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (header.version.isBefore(FILE_VERSION_32_4)) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, uIntTo4Bytes(header.innerRandomStream!!.id))
@@ -112,14 +95,13 @@ constructor(private val databaseKDBX: DatabaseKDBX,
if (databaseKDBX.containsPublicCustomData()) {
val bos = ByteArrayOutputStream()
val los = LittleEndianDataOutputStream(bos)
VariantDictionary.serialize(databaseKDBX.publicCustomData, los)
VariantDictionary.serialize(databaseKDBX.publicCustomData, bos)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray())
}
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EndOfHeader, EndHeaderValue)
los.flush()
mos.flush()
hashOfHeader = dos.messageDigest.digest()
headerHmac = mos.mac
}
@@ -127,11 +109,11 @@ constructor(private val databaseKDBX: DatabaseKDBX,
@Throws(IOException::class)
private fun writeHeaderField(fieldId: Byte, pbData: ByteArray?) {
// Write the field id
los.write(fieldId.toInt())
mos.write(fieldId.toInt())
if (pbData != null) {
writeHeaderFieldSize(pbData.size)
los.write(pbData)
mos.write(pbData)
} else {
writeHeaderFieldSize(0)
}
@@ -139,10 +121,10 @@ constructor(private val databaseKDBX: DatabaseKDBX,
@Throws(IOException::class)
private fun writeHeaderFieldSize(size: Int) {
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
los.writeUShort(size)
if (header.version.isBefore(FILE_VERSION_32_4)) {
mos.write2BytesUShort(size)
} else {
los.writeInt(size)
mos.write4BytesUInt(UnsignedInt(size))
}
}

View File

@@ -19,16 +19,16 @@
*/
package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.write2BytesUShort
import com.kunzisoft.keepass.utils.write4BytesUInt
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
@@ -37,8 +37,6 @@ import java.security.*
import java.util.*
import javax.crypto.Cipher
import javax.crypto.CipherOutputStream
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
outputStream: OutputStream)
@@ -67,30 +65,21 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
val finalKey = getFinalKey(header)
val cipher: Cipher
cipher = try {
when {
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael->
CipherFactory.getInstance("AES/CBC/PKCS5Padding")
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.Twofish ->
CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING")
else ->
throw Exception()
}
val cipher: Cipher = try {
mDatabaseKDB.encryptionAlgorithm
.cipherEngine.getCipher(Cipher.ENCRYPT_MODE,
finalKey ?: ByteArray(0),
header.encryptionIV)
} catch (e: Exception) {
throw DatabaseOutputException("Algorithm not supported.", e)
throw IOException("Algorithm not supported.", e)
}
try {
cipher.init(Cipher.ENCRYPT_MODE,
SecretKeySpec(finalKey, "AES"),
IvParameterSpec(header.encryptionIV))
val cos = CipherOutputStream(mOutputStream, cipher)
val bos = BufferedOutputStream(cos)
outputPlanGroupAndEntries(bos)
bos.flush()
bos.close()
} catch (e: InvalidKeyException) {
throw DatabaseOutputException("Invalid key", e)
} catch (e: InvalidAlgorithmParameterException) {
@@ -116,11 +105,11 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
header.signature2 = DatabaseHeaderKDB.DBSIG_2
header.flags = DatabaseHeaderKDB.FLAG_SHA2
when {
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> {
when (mDatabaseKDB.encryptionAlgorithm) {
EncryptionAlgorithm.AESRijndael -> {
header.flags = UnsignedInt(header.flags.toKotlinInt() or DatabaseHeaderKDB.FLAG_RIJNDAEL.toKotlinInt())
}
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> {
EncryptionAlgorithm.Twofish -> {
header.flags = UnsignedInt(header.flags.toKotlinInt() or DatabaseHeaderKDB.FLAG_TWOFISH.toKotlinInt())
}
else -> throw DatabaseOutputException("Unsupported algorithm.")
@@ -133,26 +122,11 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
setIVs(header)
// Content checksum
val messageDigest: MessageDigest?
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException("SHA-256 not implemented here.", e)
}
// Header checksum
val headerDigest: MessageDigest
try {
headerDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException("SHA-256 not implemented here.", e)
}
var nos = NullOutputStream()
val headerDos = DigestOutputStream(nos, headerDigest)
val headerDigest: MessageDigest = HashManager.getHash256()
// Output header for the purpose of calculating the header checksum
val headerDos = DigestOutputStream(NullOutputStream(), headerDigest)
var pho = DatabaseHeaderOutputKDB(header, headerDos)
try {
pho.outputStart()
@@ -165,9 +139,11 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
val headerHash = headerDigest.digest()
headerHashBlock = getHeaderHashBuffer(headerHash)
// Content checksum
val messageDigest: MessageDigest = HashManager.getHash256()
// Output database for the purpose of calculating the content checksum
nos = NullOutputStream()
val dos = DigestOutputStream(nos, messageDigest)
val dos = DigestOutputStream(NullOutputStream(), messageDigest)
val bos = BufferedOutputStream(dos)
try {
outputPlanGroupAndEntries(bos)
@@ -177,7 +153,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
throw DatabaseOutputException("Failed to generate checksum.", e)
}
header.contentsHash = messageDigest!!.digest()
header.contentsHash = messageDigest.digest()
// Output header for real output, containing content hash
pho = DatabaseHeaderOutputKDB(header, outputStream)
@@ -195,17 +171,19 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
return header
}
@Suppress("CAST_NEVER_SUCCEEDS")
class NullOutputStream : OutputStream() {
override fun write(oneByte: Int) {}
}
@Throws(DatabaseOutputException::class)
fun outputPlanGroupAndEntries(outputStream: OutputStream) {
val littleEndianDataOutputStream = LittleEndianDataOutputStream(outputStream)
// useHeaderHash
if (headerHashBlock != null) {
try {
littleEndianDataOutputStream.writeUShort(0x0000)
littleEndianDataOutputStream.writeInt(headerHashBlock!!.size)
littleEndianDataOutputStream.write(headerHashBlock!!)
outputStream.write2BytesUShort(0x0000)
outputStream.write4BytesUInt(UnsignedInt(headerHashBlock!!.size))
outputStream.write(headerHashBlock!!)
} catch (e: IOException) {
throw DatabaseOutputException("Failed to output header hash.", e)
}
@@ -217,7 +195,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
}
// Entries
mDatabaseKDB.doForEachEntryInIndex { entry ->
EntryOutputKDB(entry, outputStream, mDatabaseKDB.loadedCipherKey).output()
EntryOutputKDB(mDatabaseKDB, entry, outputStream).output()
}
}
@@ -252,24 +230,22 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
}
@Throws(IOException::class)
private fun writeExtData(headerDigest: ByteArray, os: OutputStream) {
val los = LittleEndianDataOutputStream(os)
writeExtDataField(los, 0x0001, headerDigest, headerDigest.size)
private fun writeExtData(headerDigest: ByteArray, outputStream: OutputStream) {
writeExtDataField(outputStream, 0x0001, headerDigest, headerDigest.size)
val headerRandom = ByteArray(32)
val rand = SecureRandom()
rand.nextBytes(headerRandom)
writeExtDataField(los, 0x0002, headerRandom, headerRandom.size)
writeExtDataField(los, 0xFFFF, null, 0)
writeExtDataField(outputStream, 0x0002, headerRandom, headerRandom.size)
writeExtDataField(outputStream, 0xFFFF, null, 0)
}
@Throws(IOException::class)
private fun writeExtDataField(los: LittleEndianDataOutputStream, fieldType: Int, data: ByteArray?, fieldSize: Int) {
los.writeUShort(fieldType)
los.writeInt(fieldSize)
private fun writeExtDataField(outputStream: OutputStream, fieldType: Int, data: ByteArray?, fieldSize: Int) {
outputStream.write2BytesUShort(fieldType)
outputStream.write4BytesUInt(UnsignedInt(fieldSize))
if (data != null) {
los.write(data)
outputStream.write(data)
}
}
}

View File

@@ -22,12 +22,12 @@ package com.kunzisoft.keepass.database.file.output
import android.util.Base64
import android.util.Log
import android.util.Xml
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.CrsAlgorithm
import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.encrypt.StreamCipher
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.CipherEngine
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
@@ -41,11 +41,12 @@ import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.UnsignedInt
import org.bouncycastle.crypto.StreamCipher
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
import com.kunzisoft.keepass.stream.HmacBlockOutputStream
import com.kunzisoft.keepass.utils.*
import org.joda.time.DateTime
import org.xmlpull.v1.XmlSerializer
import java.io.IOException
@@ -75,15 +76,14 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
try {
try {
engine = CipherFactory.getInstance(mDatabaseKDBX.dataCipher)
engine = EncryptionAlgorithm.getFrom(mDatabaseKDBX.cipherUuid).cipherEngine
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException("No such cipher", e)
}
header = outputHeader(mOutputStream)
val osPlain: OutputStream
osPlain = if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
val osPlain: OutputStream = if (header!!.version.isBefore(FILE_VERSION_32_4)) {
val cos = attachStreamEncryptor(header!!, mOutputStream)
cos.write(header!!.streamStartBytes)
@@ -95,19 +95,19 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
attachStreamEncryptor(header!!, HmacBlockOutputStream(mOutputStream, mDatabaseKDBX.hmacKey!!))
}
val osXml: OutputStream
val xmlOutputStream: OutputStream
try {
osXml = when(mDatabaseKDBX.compressionAlgorithm) {
xmlOutputStream = when(mDatabaseKDBX.compressionAlgorithm) {
CompressionAlgorithm.GZip -> GZIPOutputStream(osPlain)
else -> osPlain
}
if (header!!.version.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
outputInnerHeader(mDatabaseKDBX, header!!, osXml)
if (!header!!.version.isBefore(FILE_VERSION_32_4)) {
outputInnerHeader(mDatabaseKDBX, header!!, xmlOutputStream)
}
outputDatabase(osXml)
osXml.close()
outputDatabase(xmlOutputStream)
xmlOutputStream.close()
} catch (e: IllegalArgumentException) {
throw DatabaseOutputException(e)
} catch (e: IllegalStateException) {
@@ -122,45 +122,42 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IOException::class)
private fun outputInnerHeader(database: DatabaseKDBX,
header: DatabaseHeaderKDBX,
outputStream: OutputStream) {
val dataOutputStream = LittleEndianDataOutputStream(outputStream)
dataOutputStream: OutputStream) {
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID)
dataOutputStream.writeInt(4)
dataOutputStream.write4BytesUInt(UnsignedInt(4))
if (header.innerRandomStream == null)
throw IOException("Can't write innerRandomStream")
dataOutputStream.writeUInt(header.innerRandomStream!!.id)
dataOutputStream.write4BytesUInt(header.innerRandomStream!!.id)
val streamKeySize = header.innerRandomStreamKey.size
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey)
dataOutputStream.writeInt(streamKeySize)
dataOutputStream.write4BytesUInt(UnsignedInt(streamKeySize))
dataOutputStream.write(header.innerRandomStreamKey)
database.loadedCipherKey?.let { binaryCipherKey ->
database.binaryPool.doForEachOrderedBinaryWithoutDuplication { _, binary ->
// Force decompression to add binary in header
binary.decompress(binaryCipherKey)
// Write type binary
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
// Write size
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1))
// Write protected flag
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
if (binary.isProtected) {
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
}
dataOutputStream.writeByte(flag)
val binaryCache = database.binaryCache
database.attachmentPool.doForEachOrderedBinaryWithoutDuplication { _, binary ->
// Force decompression to add binary in header
binary.decompress(binaryCache)
// Write type binary
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
// Write size
dataOutputStream.write4BytesUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1))
// Write protected flag
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
if (binary.isProtected) {
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
}
dataOutputStream.writeByte(flag)
binary.getInputDataStream(binaryCipherKey).use { inputStream ->
inputStream.readAllBytes { buffer ->
dataOutputStream.write(buffer)
}
binary.getInputDataStream(binaryCache).use { inputStream ->
inputStream.readAllBytes { buffer ->
dataOutputStream.write(buffer)
}
}
} ?: Log.e(TAG, "Unable to retrieve cipher key to write head binaries")
}
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader)
dataOutputStream.writeInt(0)
dataOutputStream.write4BytesUInt(UnsignedInt(0))
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
@@ -270,7 +267,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID)
// Seem to work properly if always in meta
if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong())
if (header!!.version.isBefore(FILE_VERSION_32_4))
writeMetaBinaries()
writeCustomData(mDatabaseKDBX.customData)
@@ -282,8 +279,6 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
private fun attachStreamEncryptor(header: DatabaseHeaderKDBX, os: OutputStream): CipherOutputStream {
val cipher: Cipher
try {
//mDatabaseKDBX.makeFinalKey(header.masterSeed, mDatabaseKDBX.kdfParameters);
cipher = engine!!.getCipher(Cipher.ENCRYPT_MODE, mDatabaseKDBX.finalKey!!, header.encryptionIV)
} catch (e: Exception) {
throw DatabaseOutputException("Invalid algorithm.", e)
@@ -314,7 +309,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
Log.e(TAG, "Unable to retrieve header", unknownKDF)
}
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (header.version.isBefore(FILE_VERSION_32_4)) {
header.innerRandomStream = CrsAlgorithm.Salsa20
header.innerRandomStreamKey = ByteArray(32)
} else {
@@ -324,12 +319,12 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
random.nextBytes(header.innerRandomStreamKey)
try {
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
randomStream = CrsAlgorithm.getCipher(header.innerRandomStream, header.innerRandomStreamKey)
} catch (e: Exception) {
throw DatabaseOutputException(e)
}
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (header.version.isBefore(FILE_VERSION_32_4)) {
random.nextBytes(header.streamStartBytes)
}
@@ -338,21 +333,20 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(DatabaseOutputException::class)
override fun outputHeader(outputStream: OutputStream): DatabaseHeaderKDBX {
val header = DatabaseHeaderKDBX(mDatabaseKDBX)
setIVs(header)
val pho = DatabaseHeaderOutputKDBX(mDatabaseKDBX, header, outputStream)
try {
val header = DatabaseHeaderKDBX(mDatabaseKDBX)
setIVs(header)
val pho = DatabaseHeaderOutputKDBX(mDatabaseKDBX, header, outputStream)
pho.output()
hashOfHeader = pho.hashOfHeader
headerHmac = pho.headerHmac
return header
} catch (e: IOException) {
throw DatabaseOutputException("Failed to output the header.", e)
}
hashOfHeader = pho.hashOfHeader
headerHmac = pho.headerHmac
return header
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
@@ -429,7 +423,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, value: Date) {
if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (header!!.version.isBefore(FILE_VERSION_32_4)) {
writeObject(name, DatabaseKDBXXML.DateFormatter.format(value))
} else {
val dt = DateTime(value)
@@ -494,31 +488,30 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
// With kdbx4, don't use this method because binaries are in header file
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeMetaBinaries() {
mDatabaseKDBX.loadedCipherKey?.let { binaryCipherKey ->
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
// Use indexes because necessarily (binary header ref is the order)
mDatabaseKDBX.binaryPool.doForEachOrderedBinaryWithoutDuplication { index, binary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
if (binary.getSize() > 0) {
if (binary.isCompressed) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
}
try {
// Write the XML
binary.getInputDataStream(binaryCipherKey).use { inputStream ->
inputStream.readAllBytes { buffer ->
xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to write binary", e)
}
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
// Use indexes because necessarily (binary header ref is the order)
val binaryCache = mDatabaseKDBX.binaryCache
mDatabaseKDBX.attachmentPool.doForEachOrderedBinaryWithoutDuplication { index, binary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
if (binary.getSize() > 0) {
if (binary.isCompressed) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
}
try {
// Write the XML
binary.getInputDataStream(binaryCache).use { inputStream ->
inputStream.readAllBytes { buffer ->
xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to write binary", e)
}
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
}
xml.endTag(null, DatabaseKDBXXML.ElemBinaries)
} ?: Log.e(TAG, "Unable to retrieve cipher key to write binaries")
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
}
xml.endTag(null, DatabaseKDBXXML.ElemBinaries)
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
@@ -584,12 +577,8 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
if (protect) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
val data = value.toString().toByteArray()
val dataLength = data.size
if (data.isNotEmpty()) {
val encoded = ByteArray(dataLength)
randomStream!!.processBytes(data, 0, dataLength, encoded, 0)
xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
}
val encoded = randomStream?.processBytes(data) ?: ByteArray(0)
xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
} else {
xml.text(value.toString())
}
@@ -612,7 +601,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
private fun writeEntryBinaries(binaries: LinkedHashMap<String, Int>) {
for ((label, poolId) in binaries) {
// Retrieve the right index with the poolId, don't use ref because of header in DatabaseV4
mDatabaseKDBX.binaryPool.getBinaryIndexFromKey(poolId)?.toString()?.let { indexString ->
mDatabaseKDBX.attachmentPool.getBinaryIndexFromKey(poolId)?.toString()?.let { indexString ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.startTag(null, DatabaseKDBXXML.ElemKey)
xml.text(safeXmlString(label))
@@ -699,39 +688,38 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeCustomIconList() {
mDatabaseKDBX.loadedCipherKey?.let { cipherKey ->
var firstElement = true
mDatabaseKDBX.iconsManager.doForEachCustomIcon { iconCustom, binary ->
if (binary.dataExists()) {
// Write the parent tag
if (firstElement) {
xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons)
firstElement = false
}
xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem)
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, iconCustom.uuid)
var customImageData = ByteArray(0)
try {
binary.getInputDataStream(cipherKey).use { inputStream ->
customImageData = inputStream.readBytes()
}
} catch (e: Exception) {
Log.e(TAG, "Unable to write custom icon", e)
} finally {
writeObject(DatabaseKDBXXML.ElemCustomIconItemData,
String(Base64.encode(customImageData, BASE_64_FLAG)))
}
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
var firstElement = true
val binaryCache = mDatabaseKDBX.binaryCache
mDatabaseKDBX.iconsManager.doForEachCustomIcon { iconCustom, binary ->
if (binary.dataExists()) {
// Write the parent tag
if (firstElement) {
xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons)
firstElement = false
}
xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem)
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, iconCustom.uuid)
var customImageData = ByteArray(0)
try {
binary.getInputDataStream(binaryCache).use { inputStream ->
customImageData = inputStream.readBytes()
}
} catch (e: Exception) {
Log.e(TAG, "Unable to write custom icon", e)
} finally {
writeObject(DatabaseKDBXXML.ElemCustomIconItemData,
String(Base64.encode(customImageData, BASE_64_FLAG)))
}
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
}
// Close the parent tag
if (!firstElement) {
xml.endTag(null, DatabaseKDBXXML.ElemCustomIcons)
}
} ?: Log.e(TAG, "Unable to retrieve cipher key to write custom icons")
}
// Close the parent tag
if (!firstElement) {
xml.endTag(null, DatabaseKDBXXML.ElemCustomIcons)
}
}
private fun safeXmlString(text: String): String {

View File

@@ -19,13 +19,10 @@
*/
package com.kunzisoft.keepass.database.file.output
import android.util.Log
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.*
import java.io.IOException
import java.io.OutputStream
import java.nio.charset.Charset
@@ -33,9 +30,9 @@ import java.nio.charset.Charset
/**
* Output the GroupKDB to the stream
*/
class EntryOutputKDB(private val mEntry: EntryKDB,
private val mOutputStream: OutputStream,
private val mCipherKey: Database.LoadedKey?) {
class EntryOutputKDB(private val mDatabase: DatabaseKDB,
private val mEntry: EntryKDB,
private val mOutputStream: OutputStream) {
//NOTE: Need be to careful about using ints. The actual type written to file is a unsigned int
@Throws(DatabaseOutputException::class)
@@ -59,15 +56,15 @@ class EntryOutputKDB(private val mEntry: EntryKDB,
// Title
//byte[] title = mEntry.title.getBytes("UTF-8");
mOutputStream.write(TITLE_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.title)
writeStringToStream(mOutputStream, mEntry.title)
// URL
mOutputStream.write(URL_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.url)
writeStringToStream(mOutputStream, mEntry.url)
// Username
mOutputStream.write(USERNAME_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.username)
writeStringToStream(mOutputStream, mEntry.username)
// Password
mOutputStream.write(PASSWORD_FIELD_TYPE)
@@ -75,7 +72,7 @@ class EntryOutputKDB(private val mEntry: EntryKDB,
// Additional
mOutputStream.write(ADDITIONAL_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.notes)
writeStringToStream(mOutputStream, mEntry.notes)
// Create date
writeDate(CREATE_FIELD_TYPE, dateTo5Bytes(mEntry.creationTime.date))
@@ -91,25 +88,23 @@ class EntryOutputKDB(private val mEntry: EntryKDB,
// Binary description
mOutputStream.write(BINARY_DESC_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.binaryDescription)
writeStringToStream(mOutputStream, mEntry.binaryDescription)
// Binary
mCipherKey?.let { cipherKey ->
mOutputStream.write(BINARY_DATA_FIELD_TYPE)
val binaryData = mEntry.binaryData
val binaryDataLength = binaryData?.getSize() ?: 0L
// Write data length
mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromKotlinLong(binaryDataLength)))
// Write data
if (binaryDataLength > 0) {
binaryData?.getInputDataStream(cipherKey).use { inputStream ->
inputStream?.readAllBytes { buffer ->
mOutputStream.write(buffer)
}
inputStream?.close()
mOutputStream.write(BINARY_DATA_FIELD_TYPE)
val binaryData = mEntry.getBinary(mDatabase.attachmentPool)
val binaryDataLength = binaryData?.getSize() ?: 0L
// Write data length
mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromKotlinLong(binaryDataLength)))
// Write data
if (binaryDataLength > 0) {
binaryData?.getInputDataStream(mDatabase.binaryCache).use { inputStream ->
inputStream?.readAllBytes { buffer ->
mOutputStream.write(buffer)
}
inputStream?.close()
}
} ?: Log.e(TAG, "Unable to retrieve cipher key to write entry binary")
}
// End
mOutputStream.write(END_FIELD_TYPE)

View File

@@ -19,13 +19,13 @@
*/
package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.dateTo5Bytes
import com.kunzisoft.keepass.utils.uIntTo4Bytes
import com.kunzisoft.keepass.utils.uShortTo2Bytes
import com.kunzisoft.keepass.utils.writeStringToStream
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.stream.dateTo5Bytes
import com.kunzisoft.keepass.stream.uIntTo4Bytes
import com.kunzisoft.keepass.stream.uShortTo2Bytes
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.OutputStream
@@ -47,7 +47,7 @@ class GroupOutputKDB(private val mGroup: GroupKDB,
// Name
mOutputStream.write(NAME_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mGroup.title)
writeStringToStream(mOutputStream, mGroup.title)
// Create date
mOutputStream.write(CREATE_FIELD_TYPE)

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database.search;
import java.util.UUID;
import static com.kunzisoft.keepass.stream.StreamBytesUtilsKt.uuidTo16Bytes;
import static com.kunzisoft.keepass.utils.StreamBytesUtilsKt.uuidTo16Bytes;
public class UuidUtil {

View File

@@ -33,8 +33,8 @@ import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.widget.ImageViewCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.BinaryData
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageDraw
import kotlinx.coroutines.CoroutineScope
@@ -48,7 +48,7 @@ import kotlin.collections.HashMap
/**
* Factory class who build database icons dynamically, can assign an icon of IconPack, or a custom icon to an ImageView with a tint
*/
class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedKey?,
class IconDrawableFactory(private val retrieveBinaryCache : () -> BinaryCache?,
private val retrieveCustomIconBinary : (iconId: UUID) -> BinaryData?) {
/** customIconMap
@@ -69,7 +69,8 @@ class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedK
private fun getIconSuperDrawable(context: Context, iconDraw: IconImageDraw, width: Int, tintColor: Int = Color.WHITE): SuperDrawable {
val icon = iconDraw.getIconImageToDraw()
val customIconBinary = retrieveCustomIconBinary(icon.custom.uuid)
if (customIconBinary != null && customIconBinary.dataExists()) {
val binaryCache = retrieveBinaryCache()
if (binaryCache != null && customIconBinary != null && customIconBinary.dataExists()) {
getIconDrawable(context.resources, icon.custom, customIconBinary)?.let {
return SuperDrawable(it)
}
@@ -87,13 +88,13 @@ class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedK
*/
private fun getIconDrawable(resources: Resources, icon: IconImageCustom, iconCustomBinary: BinaryData?): Drawable? {
val patternIcon = PatternIcon(resources)
val cipherKey = retrieveCipherKey()
if (cipherKey != null) {
val binaryManager = retrieveBinaryCache()
if (binaryManager != null) {
val draw: Drawable? = customIconMap[icon.uuid]?.get()
if (draw == null) {
iconCustomBinary?.let { binaryFile ->
try {
var bitmap: Bitmap? = BitmapFactory.decodeStream(binaryFile.getInputDataStream(cipherKey))
var bitmap: Bitmap? = BitmapFactory.decodeStream(binaryFile.getInputDataStream(binaryManager))
bitmap?.let { bitmapIcon ->
bitmap = resize(bitmapIcon, patternIcon)
val createdDraw = BitmapDrawable(resources, bitmap)

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.model
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.database.BinaryByte
import com.kunzisoft.keepass.database.element.binary.BinaryByte
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum

View File

@@ -26,13 +26,14 @@ import android.net.Uri
import android.os.Binder
import android.os.IBinder
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import com.kunzisoft.keepass.utils.UriUtil
import kotlinx.coroutines.*
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList
@@ -48,6 +49,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
private val mainScope = CoroutineScope(Dispatchers.Main)
private var mDatabase: Database? = Database.getInstance()
override fun retrieveChannelId(): String {
return CHANNEL_ATTACHMENT_ID
}
@@ -170,7 +173,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
putExtra(FILE_URI_KEY, attachmentNotification.uri)
}, PendingIntent.FLAG_CANCEL_CURRENT)
val fileName = DocumentFile.fromSingleUri(this, attachmentNotification.uri)?.name ?: ""
val fileName = UriUtil.getFileData(this, attachmentNotification.uri)?.name
?: attachmentNotification.uri.path
val builder = buildNewNotification().apply {
when (attachmentNotification.entryAttachmentState.streamDirection) {
@@ -285,11 +289,14 @@ class AttachmentFileNotificationService: LockNotificationService() {
// Add action to the list on start
attachmentNotificationList.add(attachmentNotification)
mainScope.launch {
AttachmentFileAction(attachmentNotification,
contentResolver).apply {
listener = attachmentFileActionListener
}.executeAction()
mDatabase?.let { database ->
mainScope.launch {
AttachmentFileAction(attachmentNotification,
database,
contentResolver).apply {
listener = attachmentFileActionListener
}.executeAction()
}
}
}
} catch (e: Exception) {
@@ -313,6 +320,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
private class AttachmentFileAction(
private val attachmentNotification: AttachmentNotification,
private val database: Database,
private val contentResolver: ContentResolver) {
private val updateMinFrequency = 1000
@@ -345,6 +353,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
when (streamDirection) {
StreamDirection.UPLOAD -> {
BinaryDatabaseManager.uploadToDatabase(
database,
attachmentNotification.uri,
attachment.binaryData,
contentResolver,
@@ -358,6 +367,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
}
StreamDirection.DOWNLOAD -> {
BinaryDatabaseManager.downloadFromDatabase(
database,
attachmentNotification.uri,
attachment.binaryData,
contentResolver,

View File

@@ -31,10 +31,10 @@ import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.settings.preference.*
import com.kunzisoft.keepass.settings.preferencedialogfragment.*
@@ -217,12 +217,12 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
if (mDatabase.loaded) {
// Encryption Algorithm
mEncryptionAlgorithmPref = findPreference<DialogListExplanationPreference>(getString(R.string.encryption_algorithm_key))?.apply {
summary = mDatabase.getEncryptionAlgorithmName(resources)
summary = mDatabase.getEncryptionAlgorithmName()
}
// Key derivation function
mKeyDerivationPref = findPreference<DialogListExplanationPreference>(getString(R.string.key_derivation_function_key))?.apply {
summary = mDatabase.getKeyDerivationName(resources)
summary = mDatabase.getKeyDerivationName()
}
// Round encryption
@@ -398,7 +398,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
mDatabase.encryptionAlgorithm = oldEncryption
oldEncryption
}
mEncryptionAlgorithmPref?.summary = algorithmToShow.getName(resources)
mEncryptionAlgorithmPref?.summary = algorithmToShow.toString()
}
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK -> {
val oldKeyDerivationEngine = data.getSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) as KdfEngine
@@ -410,7 +410,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
mDatabase.kdfEngine = oldKeyDerivationEngine
oldKeyDerivationEngine
}
mKeyDerivationPref?.summary = kdfEngineToShow.getName(resources)
mKeyDerivationPref?.summary = kdfEngineToShow.toString()
mRoundPref?.summary = kdfEngineToShow.defaultKeyRounds.toString()
// Disable memory and parallelism if not available

View File

@@ -207,7 +207,7 @@ object PreferencesUtil {
return try {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
(prefs.getString(context.getString(R.string.app_timeout_key),
context.getString(R.string.clipboard_timeout_default)) ?: "300000").toLong()
context.getString(R.string.timeout_default)) ?: "300000").toLong()
} catch (e: NumberFormatException) {
TimeoutHelper.DEFAULT_TIMEOUT
}

View File

@@ -23,7 +23,7 @@ import android.content.Context
import android.util.AttributeSet
import androidx.preference.DialogPreference
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
open class InputKdfNumberPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,

View File

@@ -32,7 +32,7 @@ class InputKdfSizePreference @JvmOverloads constructor(context: Context,
override fun setSummary(summary: CharSequence) {
if (summary == UNKNOWN_VALUE_STRING) {
super.setSummary("")
super.setSummary(summary)
} else {
var summaryString = summary
try {

View File

@@ -20,11 +20,11 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
@@ -58,15 +58,13 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
if (positiveResult) {
database?.let { database ->
if (database.allowEncryptionAlgorithmModification) {
if (algorithmSelected != null) {
val newAlgorithm = algorithmSelected
val oldAlgorithm = database.encryptionAlgorithm
database.encryptionAlgorithm = newAlgorithm
if (algorithmSelected != null) {
val newAlgorithm = algorithmSelected
val oldAlgorithm = database.encryptionAlgorithm
database.encryptionAlgorithm = newAlgorithm
if (oldAlgorithm != null && newAlgorithm != null)
mProgressDatabaseTaskProvider?.startDatabaseSaveEncryption(oldAlgorithm, newAlgorithm, mDatabaseAutoSaveEnable)
}
if (oldAlgorithm != null && newAlgorithm != null)
mProgressDatabaseTaskProvider?.startDatabaseSaveEncryption(oldAlgorithm, newAlgorithm, mDatabaseAutoSaveEnable)
}
}
}

View File

@@ -20,12 +20,12 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import androidx.preference.Preference
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
class DatabaseKeyDerivationPreferenceDialogFragmentCompat

View File

@@ -20,18 +20,15 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment.adapter
import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RadioButton
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.ObjectNameResource
import java.util.*
import java.util.ArrayList
class ListRadioItemAdapter<T : ObjectNameResource>(private val context: Context)
class ListRadioItemAdapter<T>(private val context: Context)
: RecyclerView.Adapter<ListRadioItemAdapter.ListRadioViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
@@ -48,7 +45,7 @@ class ListRadioItemAdapter<T : ObjectNameResource>(private val context: Context)
override fun onBindViewHolder(holder: ListRadioViewHolder, position: Int) {
val item = this.radioItemList[position]
holder.radioButton.text = item.getName(context.resources)
holder.radioButton.text = item.toString()
holder.radioButton.isChecked = radioItemUsed != null && radioItemUsed == item
holder.radioButton.setOnClickListener(OnItemClickListener(item))
}

View File

@@ -1,247 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kunzisoft.keepass.stream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.NullCipher;
/**
* This class wraps an {@code InputStream} and a cipher so that {@code read()}
* methods return data that are read from the underlying {@code InputStream} and
* processed by the cipher.
* <p>
* The cipher must be initialized for the requested operation before being used
* by a {@code BetterCipherInputStream}. For example, if a cipher initialized for
* decryption is used with a {@code BetterCipherInputStream}, the {@code
* BetterCipherInputStream} tries to read the data an decrypt them before returning.
*/
public class BetterCipherInputStream extends FilterInputStream {
private final Cipher cipher;
private static final int I_DEFAULT_BUFFER_SIZE = 8 * 1024;
private final byte[] i_buffer;
private int index; // index of the bytes to return from o_buffer
private byte[] o_buffer;
private boolean finished;
/**
* Creates a new {@code BetterCipherInputStream} instance for an {@code
* InputStream} and a cipher.
*
* @param is
* the input stream to read data from.
* @param c
* the cipher to process the data with.
*/
public BetterCipherInputStream(InputStream is, Cipher c) {
this(is, c, I_DEFAULT_BUFFER_SIZE);
}
/**
* Creates a new {@code BetterCipherInputStream} instance for an {@code
* InputStream} and a cipher.
*
* @param is
* the input stream to read data from.
* @param c
* the cipher to process the data with.
* @param bufferSize
* size to buffer output from the cipher
*/
public BetterCipherInputStream(InputStream is, Cipher c, int bufferSize) {
super(is);
this.cipher = c;
i_buffer = new byte[bufferSize];
}
/**
* Creates a new {@code BetterCipherInputStream} instance for an {@code
* InputStream} without a cipher.
* <p>
* A {@code NullCipher} is created and used to process the data.
*
* @param is
* the input stream to read data from.
*/
protected BetterCipherInputStream(InputStream is) {
this(is, new NullCipher());
}
/**
* Reads the next byte from this cipher input stream.
*
* @return the next byte, or {@code -1} if the end of the stream is reached.
* @throws IOException
* if an error occurs.
*/
@Override
public int read() throws IOException {
if (finished) {
return ((o_buffer == null) || (index == o_buffer.length))
? -1
: o_buffer[index++] & 0xFF;
}
if ((o_buffer != null) && (index < o_buffer.length)) {
return o_buffer[index++] & 0xFF;
}
index = 0;
o_buffer = null;
int num_read;
while (o_buffer == null) {
if ((num_read = in.read(i_buffer)) == -1) {
try {
o_buffer = cipher.doFinal();
} catch (Exception e) {
throw new IOException(e.getMessage());
}
finished = true;
break;
}
o_buffer = cipher.update(i_buffer, 0, num_read);
}
return read();
}
/**
* Reads the next {@code b.length} bytes from this input stream into buffer
* {@code b}.
*
* @param b
* the buffer to be filled with data.
* @return the number of bytes filled into buffer {@code b}, or {@code -1}
* if the end of the stream is reached.
* @throws IOException
* if an error occurs.
*/
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
/**
* Reads the next {@code len} bytes from this input stream into buffer
* {@code b} starting at offset {@code off}.
* <p>
* if {@code b} is {@code null}, the next {@code len} bytes are read and
* discarded.
*
* @param b
* the buffer to be filled with data.
* @param off
* the offset to start in the buffer.
* @param len
* the maximum number of bytes to read.
* @return the number of bytes filled into buffer {@code b}, or {@code -1}
* of the of the stream is reached.
* @throws IOException
* if an error occurs.
* @throws NullPointerException
* if the underlying input stream is {@code null}.
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (in == null) {
throw new NullPointerException("Underlying input stream is null");
}
int read_b;
int i;
for (i=0; i<len; i++) {
if ((read_b = read()) == -1) {
return (i == 0) ? -1 : i;
}
if (b != null) {
b[off+i] = (byte) read_b;
}
}
return i;
}
/**
* Skips up to n bytes from this input stream.
* <p>
* The number of bytes skipped depends on the result of a call to
* {@link BetterCipherInputStream#available() available}. The smaller of n and the
* result are the number of bytes being skipped.
*
* @param n
* the number of bytes that should be skipped.
* @return the number of bytes actually skipped.
* @throws IOException
* if an error occurs
*/
@Override
public long skip(long n) throws IOException {
long i = 0;
int available = available();
if (available < n) {
n = available;
}
while ((i < n) && (read() != -1)) {
i++;
}
return i;
}
/**
* Returns the number of bytes available without blocking.
*
* @return the number of bytes available, currently zero.
* @throws IOException
* if an error occurs
*/
@Override
public int available() throws IOException {
return 0;
}
/**
* Closes this {@code BetterCipherInputStream}, also closes the underlying input
* stream and call {@code doFinal} on the cipher object.
*
* @throws IOException
* if an error occurs.
*/
@Override
public void close() throws IOException {
in.close();
try {
cipher.doFinal();
} catch (GeneralSecurityException ignore) {
//do like RI does
}
}
/**
* Returns whether this input stream supports {@code mark} and
* {@code reset}, which it does not.
*
* @return false, since this input stream does not support {@code mark} and
* {@code reset}.
*/
@Override
public boolean markSupported() {
return false;
}
}

View File

@@ -19,17 +19,17 @@
*/
package com.kunzisoft.keepass.stream
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.readBytes4ToUInt
import com.kunzisoft.keepass.utils.readBytesLength
import java.io.IOException
import java.io.InputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
class HashedBlockInputStream(inputStream: InputStream) : InputStream() {
class HashedBlockInputStream(private val baseStream: InputStream) : InputStream() {
private val baseStream: LittleEndianDataInputStream = LittleEndianDataInputStream(inputStream)
private var bufferPos = 0
private var buffer: ByteArray = ByteArray(0)
private var bufferIndex: Long = 0
@@ -53,7 +53,6 @@ class HashedBlockInputStream(inputStream: InputStream) : InputStream() {
if (!readHashedBlock()) {
return length - remaining
}
}
// Copy from buffer out
@@ -80,13 +79,13 @@ class HashedBlockInputStream(inputStream: InputStream) : InputStream() {
bufferPos = 0
val index = baseStream.readUInt()
val index = baseStream.readBytes4ToUInt()
if (index.toKotlinLong() != bufferIndex) {
throw IOException("Invalid data format")
}
bufferIndex++
val storedHash = baseStream.readBytes(32)
val storedHash = baseStream.readBytesLength(32)
if (storedHash.size != HASH_SIZE) {
throw IOException("Invalid data format")
}
@@ -104,24 +103,17 @@ class HashedBlockInputStream(inputStream: InputStream) : InputStream() {
return false
}
buffer = baseStream.readBytes(bufferSize)
buffer = baseStream.readBytesLength(bufferSize)
if (buffer.size != bufferSize) {
throw IOException("Invalid data format")
}
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not implemented here.")
}
val computedHash = messageDigest.digest(buffer)
val computedHash = HashManager.hashSha256(buffer)
if (computedHash.size != HASH_SIZE) {
throw IOException("Hash wrong size")
}
if (!Arrays.equals(storedHash, computedHash)) {
if (!storedHash.contentEquals(computedHash)) {
throw IOException("Hashes didn't match.")
}
@@ -141,7 +133,7 @@ class HashedBlockInputStream(inputStream: InputStream) : InputStream() {
if (!readHashedBlock()) return -1
}
val output = UnsignedInt.fromKotlinByte(buffer[bufferPos]).toKotlinInt()
val output = buffer[bufferPos].toInt() and 0xFF
bufferPos++
return output

View File

@@ -19,16 +19,18 @@
*/
package com.kunzisoft.keepass.stream
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.write4BytesUInt
import com.kunzisoft.keepass.utils.write8BytesLong
import java.io.IOException
import java.io.OutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import kotlin.math.min
class HashedBlockOutputStream : OutputStream {
private lateinit var baseStream: LittleEndianDataOutputStream
private lateinit var baseStream: OutputStream
private lateinit var buffer: ByteArray
private var bufferPos = 0
private var bufferIndex: Long = 0
@@ -47,7 +49,7 @@ class HashedBlockOutputStream : OutputStream {
}
private fun init(os: OutputStream, bufferSize: Int) {
baseStream = LittleEndianDataOutputStream(os)
baseStream = os
buffer = ByteArray(bufferSize)
}
@@ -99,31 +101,24 @@ class HashedBlockOutputStream : OutputStream {
@Throws(IOException::class)
private fun writeHashedBlock() {
baseStream.writeUInt(UnsignedInt.fromKotlinLong(bufferIndex))
baseStream.write4BytesUInt(UnsignedInt.fromKotlinLong(bufferIndex))
bufferIndex++
if (bufferPos > 0) {
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not implemented here.")
}
val hash: ByteArray
val messageDigest: MessageDigest = HashManager.getHash256()
messageDigest.update(buffer, 0, bufferPos)
hash = messageDigest.digest()
val hash: ByteArray = messageDigest.digest()
baseStream.write(hash)
} else {
// Write 32-bits of zeros
baseStream.writeLong(0L)
baseStream.writeLong(0L)
baseStream.writeLong(0L)
baseStream.writeLong(0L)
baseStream.write8BytesLong(0L)
baseStream.write8BytesLong(0L)
baseStream.write8BytesLong(0L)
baseStream.write8BytesLong(0L)
}
baseStream.writeInt(bufferPos)
baseStream.write4BytesUInt(UnsignedInt(bufferPos))
if (bufferPos > 0) {
baseStream.write(buffer, 0, bufferPos)

View File

@@ -19,21 +19,21 @@
*/
package com.kunzisoft.keepass.stream
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.utils.bytes4ToUInt
import com.kunzisoft.keepass.utils.readBytesLength
import com.kunzisoft.keepass.utils.uLongTo8Bytes
import com.kunzisoft.keepass.database.crypto.HmacBlock
import java.io.IOException
import java.io.InputStream
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class HmacBlockInputStream(baseStream: InputStream, private val verify: Boolean, private val key: ByteArray) : InputStream() {
class HmacBlockInputStream(private val baseStream: InputStream, private val verify: Boolean, private val key: ByteArray) : InputStream() {
private val baseStream: LittleEndianDataInputStream = LittleEndianDataInputStream(baseStream)
private var buffer: ByteArray = ByteArray(0)
private var bufferPos = 0
private var blockIndex: Long = 0
private var blockIndex = UnsignedLong(0L)
private var endOfStream = false
@Throws(IOException::class)
@@ -44,7 +44,7 @@ class HmacBlockInputStream(baseStream: InputStream, private val verify: Boolean,
if (!readSafeBlock()) return -1
}
val output = UnsignedInt.fromKotlinByte(buffer[bufferPos]).toKotlinInt()
val output = (buffer[bufferPos]).toInt() and 0xFF
bufferPos++
return output
@@ -67,8 +67,6 @@ class HmacBlockInputStream(baseStream: InputStream, private val verify: Boolean,
}
val copy = (buffer.size - bufferPos).coerceAtMost(remaining)
assert(copy > 0)
System.arraycopy(buffer, bufferPos, outBuffer, offset, copy)
offset += copy
bufferPos += copy
@@ -88,35 +86,25 @@ class HmacBlockInputStream(baseStream: InputStream, private val verify: Boolean,
private fun readSafeBlock(): Boolean {
if (endOfStream) return false
val storedHmac = baseStream.readBytes(32)
val storedHmac = baseStream.readBytesLength(32)
if (storedHmac.size != 32) {
throw IOException("File corrupted")
}
val pbBlockIndex = longTo8Bytes(blockIndex)
val pbBlockSize = baseStream.readBytes(4)
val pbBlockSize = baseStream.readBytesLength(4)
if (pbBlockSize.size != 4) {
throw IOException("File corrupted")
}
val blockSize = bytes4ToUInt(pbBlockSize)
bufferPos = 0
buffer = baseStream.readBytes(blockSize.toKotlinInt())
buffer = baseStream.readBytesLength(blockSize.toKotlinInt())
if (verify) {
val cmpHmac: ByteArray
val blockKey = HmacBlockStream.getHmacKey64(key, blockIndex)
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(blockKey, "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw IOException("Invalid Hmac")
} catch (e: InvalidKeyException) {
throw IOException("Invalid Hmac")
}
val pbBlockIndex = uLongTo8Bytes(blockIndex)
val blockKey = HmacBlock.getHmacKey64(key, pbBlockIndex)
val hmac: Mac = HmacBlock.getHmacSha256(blockKey)
hmac.update(pbBlockIndex)
hmac.update(pbBlockSize)
@@ -124,16 +112,16 @@ class HmacBlockInputStream(baseStream: InputStream, private val verify: Boolean,
hmac.update(buffer)
}
cmpHmac = hmac.doFinal()
val cmpHmac: ByteArray = hmac.doFinal()
Arrays.fill(blockKey, 0.toByte())
if (!Arrays.equals(cmpHmac, storedHmac)) {
if (!cmpHmac.contentEquals(storedHmac)) {
throw IOException("Invalid Hmac")
}
}
blockIndex++
blockIndex.plusOne()
if (blockSize.toKotlinLong() == 0L) {
endOfStream = true

View File

@@ -20,23 +20,21 @@
package com.kunzisoft.keepass.stream
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.utils.uIntTo4Bytes
import com.kunzisoft.keepass.utils.uLongTo8Bytes
import com.kunzisoft.keepass.database.crypto.HmacBlock
import java.io.IOException
import java.io.OutputStream
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class HmacBlockOutputStream(outputStream: OutputStream,
class HmacBlockOutputStream(private val baseStream: OutputStream,
private val key: ByteArray)
: OutputStream() {
private val baseStream: LittleEndianDataOutputStream = LittleEndianDataOutputStream(outputStream)
private val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
private var bufferPos = 0
private var blockIndex: Long = 0
private var blockIndex = UnsignedLong(0L)
@Throws(IOException::class)
override fun close() {
@@ -87,23 +85,11 @@ class HmacBlockOutputStream(outputStream: OutputStream,
@Throws(IOException::class)
private fun writeSafeBlock() {
val bufBlockIndex = longTo8Bytes(blockIndex)
val bufBlockIndex = uLongTo8Bytes(blockIndex)
val blockSizeBuf = uIntTo4Bytes(UnsignedInt(bufferPos))
val blockHmac: ByteArray
val blockKey = HmacBlockStream.getHmacKey64(key, blockIndex)
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(blockKey, "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw IOException("Invalid Hmac")
} catch (e: InvalidKeyException) {
throw IOException("Invalid HMAC")
}
val blockKey = HmacBlock.getHmacKey64(key, bufBlockIndex)
val hmac: Mac = HmacBlock.getHmacSha256(blockKey)
hmac.update(bufBlockIndex)
hmac.update(blockSizeBuf)
@@ -111,8 +97,7 @@ class HmacBlockOutputStream(outputStream: OutputStream,
hmac.update(buffer, 0, bufferPos)
}
blockHmac = hmac.doFinal()
val blockHmac: ByteArray = hmac.doFinal()
baseStream.write(blockHmac)
baseStream.write(blockSizeBuf)
@@ -120,7 +105,7 @@ class HmacBlockOutputStream(outputStream: OutputStream,
baseStream.write(buffer, 0, bufferPos)
}
blockIndex++
blockIndex.plusOne()
bufferPos = 0
}
}

View File

@@ -1,114 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.stream
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.InputStream
/**
* Little endian version of the DataInputStream
*/
class LittleEndianDataInputStream(private val baseStream: InputStream) : InputStream() {
/**
* Read a 32-bit value and return it as a long, so that it can
* be interpreted as an unsigned integer.
*/
@Throws(IOException::class)
fun readUInt(): UnsignedInt {
return baseStream.readBytes4ToUInt()
}
@Throws(IOException::class)
fun readUShort(): Int {
val buf = ByteArray(2)
if (baseStream.read(buf, 0, 2) != 2)
throw IOException("Unable to read UShort value")
return bytes2ToUShort(buf)
}
@Throws(IOException::class)
override fun available(): Int {
return baseStream.available()
}
@Throws(IOException::class)
override fun close() {
baseStream.close()
}
override fun mark(readlimit: Int) {
baseStream.mark(readlimit)
}
override fun markSupported(): Boolean {
return baseStream.markSupported()
}
@Throws(IOException::class)
override fun read(): Int {
return baseStream.read()
}
@Throws(IOException::class)
override fun read(b: ByteArray, offset: Int, length: Int): Int {
return baseStream.read(b, offset, length)
}
@Throws(IOException::class)
override fun read(b: ByteArray): Int {
return baseStream.read(b)
}
@Synchronized
@Throws(IOException::class)
override fun reset() {
baseStream.reset()
}
@Throws(IOException::class)
override fun skip(n: Long): Long {
return baseStream.skip(n)
}
@Throws(IOException::class)
fun readBytes(length: Int): ByteArray {
// TODO Exception max length < buffer size
val buf = ByteArray(length)
var count = 0
while (count < length) {
val read = read(buf, count, length - count)
// Reached end
if (read == -1) {
// Stop early
val early = ByteArray(count)
System.arraycopy(buf, 0, early, 0, count)
return early
}
count += read
}
return buf
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright 2019 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.stream
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.OutputStream
/**
* Little Endian version of the DataOutputStream
* @author bpellin
*/
class LittleEndianDataOutputStream(private val baseStream: OutputStream) : OutputStream() {
@Throws(IOException::class)
fun writeUInt(uInt: UnsignedInt) {
baseStream.write(uIntTo4Bytes(uInt))
}
@Throws(IOException::class)
override fun close() {
baseStream.close()
}
@Throws(IOException::class)
override fun flush() {
baseStream.flush()
}
@Throws(IOException::class)
override fun write(buffer: ByteArray, offset: Int, count: Int) {
baseStream.write(buffer, offset, count)
}
@Throws(IOException::class)
override fun write(buffer: ByteArray) {
baseStream.write(buffer)
}
@Throws(IOException::class)
override fun write(oneByte: Int) {
baseStream.write(oneByte)
}
@Throws(IOException::class)
fun writeByte(byte: Byte) {
baseStream.write(byte.toInt())
}
@Throws(IOException::class)
fun writeLong(value: Long) {
baseStream.write(longTo8Bytes(value))
}
@Throws(IOException::class)
fun writeInt(value: Int) {
baseStream.write(uIntTo4Bytes(UnsignedInt(value)))
}
@Throws(IOException::class)
fun writeUShort(value: Int) {
baseStream.write(uShortTo2Bytes(value))
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.stream
import java.io.IOException
import java.io.OutputStream
class NullOutputStream : OutputStream() {
@Throws(IOException::class)
override fun close() {
super.close()
}
@Throws(IOException::class)
override fun flush() {
super.flush()
}
@Throws(IOException::class)
override fun write(buffer: ByteArray, offset: Int, count: Int) {
super.write(buffer, offset, count)
}
@Throws(IOException::class)
override fun write(buffer: ByteArray) {
super.write(buffer)
}
@Throws(IOException::class)
override fun write(oneByte: Int) {
}
}

View File

@@ -5,9 +5,10 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.utils.readAllBytes
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.BinaryData
import com.kunzisoft.keepass.stream.readAllBytes
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.utils.UriUtil
import kotlinx.coroutines.*
import java.io.ByteArrayInputStream
@@ -21,41 +22,42 @@ import kotlin.math.pow
object BinaryDatabaseManager {
fun downloadFromDatabase(attachmentToUploadUri: Uri,
fun downloadFromDatabase(database: Database,
attachmentToUploadUri: Uri,
binaryData: BinaryData,
contentResolver: ContentResolver,
update: ((percent: Int)->Unit)? = null,
canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
UriUtil.getUriOutputStream(contentResolver, attachmentToUploadUri)?.use { outputStream ->
downloadFromDatabase(outputStream, binaryData, update, canceled, bufferSize)
downloadFromDatabase(database.binaryCache, outputStream, binaryData, update, canceled, bufferSize)
}
}
private fun downloadFromDatabase(outputStream: OutputStream,
private fun downloadFromDatabase(binaryCache: BinaryCache,
outputStream: OutputStream,
binaryData: BinaryData,
update: ((percent: Int)->Unit)? = null,
canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
val fileSize = binaryData.getSize()
var dataDownloaded = 0L
Database.getInstance().loadedCipherKey?.let { binaryCipherKey ->
binaryData.getUnGzipInputDataStream(binaryCipherKey).use { inputStream ->
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
outputStream.write(buffer)
dataDownloaded += buffer.size
try {
val percentDownload = (100 * dataDownloaded / fileSize).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {
Log.w(TAG, "Unable to call update callback during download", e)
}
binaryData.getUnGzipInputDataStream(binaryCache).use { inputStream ->
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
outputStream.write(buffer)
dataDownloaded += buffer.size
try {
val percentDownload = (100 * dataDownloaded / fileSize).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {
Log.w(TAG, "Unable to call update callback during download", e)
}
}
}
}
fun uploadToDatabase(attachmentFromDownloadUri: Uri,
fun uploadToDatabase(database: Database,
attachmentFromDownloadUri: Uri,
binaryData: BinaryData,
contentResolver: ContentResolver,
update: ((percent: Int)->Unit)? = null,
@@ -63,34 +65,34 @@ object BinaryDatabaseManager {
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0
UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.use { inputStream ->
uploadToDatabase(inputStream, fileSize, binaryData, update, canceled, bufferSize)
uploadToDatabase(database.binaryCache, inputStream, fileSize, binaryData, update, canceled, bufferSize)
}
}
private fun uploadToDatabase(inputStream: InputStream,
private fun uploadToDatabase(binaryCache: BinaryCache,
inputStream: InputStream,
fileSize: Long,
binaryData: BinaryData,
update: ((percent: Int)->Unit)? = null,
canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
var dataUploaded = 0L
Database.getInstance().loadedCipherKey?.let { binaryCipherKey ->
binaryData.getGzipOutputDataStream(binaryCipherKey).use { outputStream ->
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
outputStream.write(buffer)
dataUploaded += buffer.size
try {
val percentDownload = (100 * dataUploaded / fileSize).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {
Log.w(TAG, "Unable to call update callback during upload", e)
}
binaryData.getGzipOutputDataStream(binaryCache).use { outputStream ->
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
outputStream.write(buffer)
dataUploaded += buffer.size
try {
val percentDownload = (100 * dataUploaded / fileSize).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {
Log.w(TAG, "Unable to call update callback during upload", e)
}
}
}
}
fun resizeBitmapAndStoreDataInBinaryFile(contentResolver: ContentResolver,
database: Database,
bitmapUri: Uri?,
binaryData: BinaryData?) {
try {
@@ -103,6 +105,7 @@ object BinaryDatabaseManager {
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
val byteArrayInputStream = ByteArrayInputStream(bitmapData)
uploadToDatabase(
database.binaryCache,
byteArrayInputStream,
bitmapData.size.toLong(),
binaryData
@@ -118,7 +121,6 @@ object BinaryDatabaseManager {
/**
* reduces the size of the image
* @param image
* @param maxSize
* @return
*/
@@ -136,20 +138,18 @@ object BinaryDatabaseManager {
return Bitmap.createScaledBitmap(this, width, height, true)
}
fun loadBitmap(binaryData: BinaryData,
binaryCipherKey: Database.LoadedKey?,
fun loadBitmap(database: Database,
binaryData: BinaryData,
maxWidth: Int,
actionOnFinish: (Bitmap?) -> Unit) {
CoroutineScope(Dispatchers.Main).launch {
withContext(Dispatchers.IO) {
val asyncResult: Deferred<Bitmap?> = async {
runCatching {
binaryCipherKey?.let { binaryKey ->
val bitmap: Bitmap? = decodeSampledBitmap(binaryData,
binaryKey,
maxWidth)
bitmap
}
val bitmap: Bitmap? = decodeSampledBitmap(binaryData,
database.binaryCache,
maxWidth)
bitmap
}.getOrNull()
}
withContext(Dispatchers.Main) {
@@ -160,13 +160,13 @@ object BinaryDatabaseManager {
}
private fun decodeSampledBitmap(binaryData: BinaryData,
binaryCipherKey: Database.LoadedKey,
binaryCache: BinaryCache,
maxWidth: Int): Bitmap? {
// First decode with inJustDecodeBounds=true to check dimensions
return BitmapFactory.Options().run {
try {
inJustDecodeBounds = true
binaryData.getUnGzipInputDataStream(binaryCipherKey).use {
binaryData.getUnGzipInputDataStream(binaryCache).use {
BitmapFactory.decodeStream(it, null, this)
}
// Calculate inSampleSize
@@ -178,7 +178,7 @@ object BinaryDatabaseManager {
// Decode bitmap with inSampleSize set
inJustDecodeBounds = false
binaryData.getUnGzipInputDataStream(binaryCipherKey).use {
binaryData.getUnGzipInputDataStream(binaryCache).use {
BitmapFactory.decodeStream(it, null, this)
}
} catch (e: Exception) {

View File

@@ -67,7 +67,7 @@ class ClipboardHelper(private val context: Context) {
fun getClipboard(context: Context): CharSequence {
if (getClipboardManager()?.hasPrimaryClip() == true) {
val data = getClipboardManager()?.primaryClip
if (data!!.itemCount > 0) {
if (data != null && data.itemCount > 0) {
val text = data.getItemAt(0).coerceToText(context)
if (text != null) {
return text

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