From 93222990b538e90b2e7bddc2a406fdbe696f543d Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Sun, 2 Jun 2019 16:10:57 +0200 Subject: [PATCH] Kotlinized database package --- .../keepass/activities/EntryActivity.java | 3 +- .../keepass/activities/EntryEditActivity.java | 2 +- .../keepass/activities/GroupActivity.java | 2 +- .../{database => crypto}/CrsAlgorithm.kt | 2 +- .../keepass/crypto/PwStreamCipherFactory.java | 2 - .../database/cursor/ExtraFieldCursor.kt | 2 +- .../keepass/database/element/BinaryPool.kt | 2 +- .../keepass/database/element/Database.kt | 10 +- .../database/element/EntryVersioned.kt | 2 +- .../keepass/database/element/ExtraFields.kt | 2 +- .../database/element/PwDatabaseV4.java | 3 +- .../database/element/PwDbHeaderV3.java | 138 ---- .../database/element/PwDbHeaderV4.java | 378 --------- .../keepass/database/element/PwEntryV4.kt | 4 +- .../element/security/ProtectedBinary.kt | 154 ++++ .../element/security/ProtectedString.kt | 78 ++ .../{ => file}/PwCompressionAlgorithm.kt | 2 +- .../database/{element => file}/PwDbHeader.kt | 3 +- .../keepass/database/file/PwDbHeaderV3.kt | 117 +++ .../keepass/database/file/PwDbHeaderV4.kt | 334 ++++++++ .../database/{ => file}/load/Importer.kt | 2 +- .../database/{ => file}/load/ImporterV3.kt | 4 +- .../database/{ => file}/load/ImporterV4.kt | 11 +- .../save/PwDbHeaderOutput.kt} | 10 +- .../database/file/save/PwDbHeaderOutputV3.kt | 64 ++ .../database/file/save/PwDbHeaderOutputV4.kt | 151 ++++ .../file/save/PwDbInnerHeaderOutputV4.kt | 68 ++ .../keepass/database/file/save/PwDbOutput.kt | 52 ++ .../database/file/save/PwDbV3Output.kt | 279 +++++++ .../database/file/save/PwDbV4Output.kt | 743 +++++++++++++++++ .../database/file/save/PwEntryOutputV3.kt | 166 ++++ .../database/file/save/PwGroupOutputV3.kt | 110 +++ .../database/save/PwDbHeaderOutputV3.java | 66 -- .../database/save/PwDbHeaderOutputV4.java | 149 ---- .../save/PwDbInnerHeaderOutputV4.java | 71 -- .../keepass/database/save/PwDbOutput.java | 54 -- .../keepass/database/save/PwDbV3Output.java | 278 ------- .../keepass/database/save/PwDbV4Output.java | 765 ------------------ .../database/save/PwEntryOutputV3.java | 176 ---- .../database/save/PwGroupOutputV3.java | 113 --- .../database/search/EntrySearchHandlerV4.kt | 2 +- .../keepass/database/search/SearchDbHelper.kt | 6 +- .../iterator/EntrySearchStringIterator.kt | 2 +- .../iterator/EntrySearchStringIteratorV3.kt | 2 +- .../iterator/EntrySearchStringIteratorV4.kt | 4 +- .../database/security/ProtectedBinary.java | 158 ---- .../database/security/ProtectedString.java | 89 -- .../keepass/view/EntryContentsView.java | 2 +- .../keepass/view/EntryCustomField.java | 2 +- .../view/EntryCustomFieldProtected.java | 2 +- .../keepass/view/EntryEditCustomField.java | 2 +- 51 files changed, 2362 insertions(+), 2481 deletions(-) rename app/src/main/java/com/kunzisoft/keepass/{database => crypto}/CrsAlgorithm.kt (96%) delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/element/PwDbHeaderV3.java delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/element/PwDbHeaderV4.java create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedString.kt rename app/src/main/java/com/kunzisoft/keepass/database/{ => file}/PwCompressionAlgorithm.kt (96%) rename app/src/main/java/com/kunzisoft/keepass/database/{element => file}/PwDbHeader.kt (95%) create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV3.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV4.kt rename app/src/main/java/com/kunzisoft/keepass/database/{ => file}/load/Importer.kt (97%) rename app/src/main/java/com/kunzisoft/keepass/database/{ => file}/load/ImporterV3.kt (98%) rename app/src/main/java/com/kunzisoft/keepass/database/{ => file}/load/ImporterV4.kt (99%) rename app/src/main/java/com/kunzisoft/keepass/database/{save/PwDbHeaderOutput.java => file/save/PwDbHeaderOutput.kt} (81%) create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV3.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV4.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbInnerHeaderOutputV4.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbOutput.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV3Output.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV4Output.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/save/PwEntryOutputV3.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/save/PwGroupOutputV3.kt delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV3.java delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV4.java delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/save/PwDbInnerHeaderOutputV4.java delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/save/PwDbOutput.java delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV3Output.java delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV4Output.java delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/save/PwEntryOutputV3.java delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/save/PwGroupOutputV3.java rename app/src/main/java/com/kunzisoft/keepass/database/{ => search}/iterator/EntrySearchStringIterator.kt (93%) rename app/src/main/java/com/kunzisoft/keepass/database/{ => search}/iterator/EntrySearchStringIteratorV3.kt (97%) rename app/src/main/java/com/kunzisoft/keepass/database/{ => search}/iterator/EntrySearchStringIteratorV4.kt (95%) delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/security/ProtectedBinary.java delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/security/ProtectedString.java diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.java b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.java index 4dc4ad970..4ee2698da 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.java +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.java @@ -42,11 +42,10 @@ import com.getkeepsafe.taptargetview.TapTargetView; import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.activities.lock.LockingHideActivity; import com.kunzisoft.keepass.app.App; -import com.kunzisoft.keepass.database.element.ExtraFields; import com.kunzisoft.keepass.database.element.Database; import com.kunzisoft.keepass.database.element.EntryVersioned; import com.kunzisoft.keepass.database.element.PwNodeId; -import com.kunzisoft.keepass.database.security.ProtectedString; +import com.kunzisoft.keepass.database.element.security.ProtectedString; import com.kunzisoft.keepass.notifications.NotificationCopyingService; import com.kunzisoft.keepass.notifications.NotificationField; import com.kunzisoft.keepass.settings.PreferencesUtil; diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.java b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.java index 66f53fcd0..30c9f9503 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.java +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.java @@ -38,7 +38,7 @@ import com.kunzisoft.keepass.database.action.node.AddEntryRunnable; import com.kunzisoft.keepass.database.action.node.AfterActionNodeFinishRunnable; import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable; import com.kunzisoft.keepass.database.element.*; -import com.kunzisoft.keepass.database.security.ProtectedString; +import com.kunzisoft.keepass.database.element.security.ProtectedString; import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment; import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment; import com.kunzisoft.keepass.settings.PreferencesUtil; diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java index 694634a49..e90217a6d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java @@ -72,7 +72,7 @@ import com.kunzisoft.keepass.database.action.node.MoveEntryRunnable; import com.kunzisoft.keepass.database.action.node.MoveGroupRunnable; import com.kunzisoft.keepass.database.action.node.UpdateGroupRunnable; import com.kunzisoft.keepass.database.element.*; -import com.kunzisoft.keepass.database.security.ProtectedString; +import com.kunzisoft.keepass.database.element.security.ProtectedString; import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment; import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment; import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment; diff --git a/app/src/main/java/com/kunzisoft/keepass/database/CrsAlgorithm.kt b/app/src/main/java/com/kunzisoft/keepass/crypto/CrsAlgorithm.kt similarity index 96% rename from app/src/main/java/com/kunzisoft/keepass/database/CrsAlgorithm.kt rename to app/src/main/java/com/kunzisoft/keepass/crypto/CrsAlgorithm.kt index b50c1522d..eb492fb4d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/CrsAlgorithm.kt +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/CrsAlgorithm.kt @@ -17,7 +17,7 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database +package com.kunzisoft.keepass.crypto enum class CrsAlgorithm constructor(val id: Int) { diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/PwStreamCipherFactory.java b/app/src/main/java/com/kunzisoft/keepass/crypto/PwStreamCipherFactory.java index 98d369858..d4a8f9866 100644 --- a/app/src/main/java/com/kunzisoft/keepass/crypto/PwStreamCipherFactory.java +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/PwStreamCipherFactory.java @@ -19,8 +19,6 @@ */ package com.kunzisoft.keepass.crypto; -import com.kunzisoft.keepass.database.CrsAlgorithm; - import org.spongycastle.crypto.StreamCipher; import org.spongycastle.crypto.engines.ChaCha7539Engine; import org.spongycastle.crypto.engines.Salsa20Engine; diff --git a/app/src/main/java/com/kunzisoft/keepass/database/cursor/ExtraFieldCursor.kt b/app/src/main/java/com/kunzisoft/keepass/database/cursor/ExtraFieldCursor.kt index a79321a61..33913ab08 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/cursor/ExtraFieldCursor.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/cursor/ExtraFieldCursor.kt @@ -4,7 +4,7 @@ import android.database.MatrixCursor import android.provider.BaseColumns import com.kunzisoft.keepass.database.element.PwEntryV4 -import com.kunzisoft.keepass.database.security.ProtectedString +import com.kunzisoft.keepass.database.element.security.ProtectedString class ExtraFieldCursor : MatrixCursor(arrayOf( _ID, diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/BinaryPool.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/BinaryPool.kt index 5a90d01e8..3f3757442 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/BinaryPool.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/BinaryPool.kt @@ -19,7 +19,7 @@ */ package com.kunzisoft.keepass.database.element -import com.kunzisoft.keepass.database.security.ProtectedBinary +import com.kunzisoft.keepass.database.element.security.ProtectedBinary import java.util.HashMap import kotlin.collections.Map.Entry diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt index 86e1561c1..4048ca694 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt @@ -31,10 +31,12 @@ import com.kunzisoft.keepass.database.NodeHandler import com.kunzisoft.keepass.database.cursor.EntryCursorV3 import com.kunzisoft.keepass.database.cursor.EntryCursorV4 import com.kunzisoft.keepass.database.exception.* -import com.kunzisoft.keepass.database.load.ImporterV3 -import com.kunzisoft.keepass.database.load.ImporterV4 -import com.kunzisoft.keepass.database.save.PwDbV3Output -import com.kunzisoft.keepass.database.save.PwDbV4Output +import com.kunzisoft.keepass.database.file.PwDbHeaderV3 +import com.kunzisoft.keepass.database.file.PwDbHeaderV4 +import com.kunzisoft.keepass.database.file.load.ImporterV3 +import com.kunzisoft.keepass.database.file.load.ImporterV4 +import com.kunzisoft.keepass.database.file.save.PwDbV3Output +import com.kunzisoft.keepass.database.file.save.PwDbV4Output import com.kunzisoft.keepass.database.search.SearchDbHelper import com.kunzisoft.keepass.icons.IconDrawableFactory import com.kunzisoft.keepass.settings.PreferencesUtil diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/EntryVersioned.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/EntryVersioned.kt index affea85f5..876382e00 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/EntryVersioned.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/EntryVersioned.kt @@ -2,7 +2,7 @@ package com.kunzisoft.keepass.database.element import android.os.Parcel import android.os.Parcelable -import com.kunzisoft.keepass.database.security.ProtectedString +import com.kunzisoft.keepass.database.element.security.ProtectedString import java.util.* import kotlin.collections.ArrayList diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/ExtraFields.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/ExtraFields.kt index e833d6c6d..cbbec73f1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/ExtraFields.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/ExtraFields.kt @@ -22,7 +22,7 @@ package com.kunzisoft.keepass.database.element import android.os.Parcel import android.os.Parcelable -import com.kunzisoft.keepass.database.security.ProtectedString +import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.utils.MemUtil import java.util.HashMap diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.java b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.java index 5ec388b76..3f20a2821 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.java @@ -28,9 +28,10 @@ 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.*; import com.kunzisoft.keepass.database.exception.InvalidKeyFileException; import com.kunzisoft.keepass.database.exception.UnknownKDF; +import com.kunzisoft.keepass.database.file.PwCompressionAlgorithm; + import org.w3c.dom.*; import javax.annotation.Nullable; diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDbHeaderV3.java b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDbHeaderV3.java deleted file mode 100644 index 3f57f5bc7..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDbHeaderV3.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - * - -Derived from - -KeePass for J2ME - - -Copyright 2007 Naomaru Itoi - -This file was derived from - -Java clone of KeePass - A KeePass file viewer for Java -Copyright 2006 Bill Zwicky - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; version 2 - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package com.kunzisoft.keepass.database.element; - -import com.kunzisoft.keepass.stream.LEDataInputStream; - -import java.io.IOException; - -public class PwDbHeaderV3 extends PwDbHeader { - - // DB sig from KeePass 1.03 - public static final int DBSIG_2 = 0xB54BFB65; - // DB sig from KeePass 1.03 - public static final int DBVER_DW = 0x00030003; - - public static final int FLAG_SHA2 = 1; - public static final int FLAG_RIJNDAEL = 2; - public static final int FLAG_ARCFOUR = 4; - public static final int FLAG_TWOFISH = 8; - - /** Size of byte buffer needed to hold this struct. */ - public static final int BUF_SIZE = 124; - - /** - * Used for the dwKeyEncRounds AES transformations - */ - public byte[] transformSeed = new byte[32]; - - public int signature1; // = PWM_DBSIG_1 - public int signature2; // = DBSIG_2 - public int flags; - public int version; - - /** Number of groups in the database */ - public int numGroups; - /** Number of entries in the database */ - public int numEntries; - - /** - * SHA-256 hash of the database, used for integrity check - */ - public byte[] contentsHash = new byte[32]; - - public int numKeyEncRounds; - - /** - * Parse given buf, as read from file. - * @param buf - * @throws IOException - */ - public void loadFromFile(byte[] buf, int offset ) throws IOException { - signature1 = LEDataInputStream.readInt( buf, offset); - signature2 = LEDataInputStream.readInt( buf, offset + 4 ); - flags = LEDataInputStream.readInt( buf, offset + 8 ); - version = LEDataInputStream.readInt( buf, offset + 12 ); - - System.arraycopy( buf, offset + 16, getMasterSeed(), 0, 16 ); - System.arraycopy( buf, offset + 32, getEncryptionIV(), 0, 16 ); - - numGroups = LEDataInputStream.readInt( buf, offset + 48 ); - numEntries = LEDataInputStream.readInt( buf, offset + 52 ); - - System.arraycopy( buf, offset + 56, contentsHash, 0, 32 ); - - System.arraycopy( buf, offset + 88, transformSeed, 0, 32 ); - numKeyEncRounds = LEDataInputStream.readInt( buf, offset + 120 ); - if ( numKeyEncRounds < 0 ) { - // TODO: Really treat this like an unsigned integer - throw new IOException("Does not support more than " + Integer.MAX_VALUE + " rounds."); - } - } - - public PwDbHeaderV3() { - setMasterSeed(new byte[16]); - } - - public static boolean matchesHeader(int sig1, int sig2) { - return (sig1 == PwDbHeader.PWM_DBSIG_1) && (sig2 == DBSIG_2); - } - - - /** Determine if the database version is compatible with this application - * @return true, if it is compatible - */ - public boolean matchesVersion() { - return compatibleHeaders(version, DBVER_DW); - } - - public static boolean compatibleHeaders(int one, int two) { - return (one & 0xFFFFFF00) == (two & 0xFFFFFF00); - } - - -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDbHeaderV4.java b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDbHeaderV4.java deleted file mode 100644 index e60a8b26a..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDbHeaderV4.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.database.element; - -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.CrsAlgorithm; -import com.kunzisoft.keepass.database.NodeHandler; -import com.kunzisoft.keepass.database.PwCompressionAlgorithm; -import com.kunzisoft.keepass.database.exception.InvalidDBVersionException; -import com.kunzisoft.keepass.stream.CopyInputStream; -import com.kunzisoft.keepass.stream.HmacBlockStream; -import com.kunzisoft.keepass.stream.LEDataInputStream; -import com.kunzisoft.keepass.utils.Types; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -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; - -public class PwDbHeaderV4 extends PwDbHeader { - public static final int DBSIG_PRE2 = 0xB54BFB66; - public static final int DBSIG_2 = 0xB54BFB67; - - private static final int FILE_VERSION_CRITICAL_MASK = 0xFFFF0000; - public static final int FILE_VERSION_32_3 = 0x00030001; - public static final int FILE_VERSION_32_4 = 0x00040000; - - public class PwDbHeaderV4Fields { - public static final byte EndOfHeader = 0; - public static final byte Comment = 1; - public static final byte CipherID = 2; - public static final byte CompressionFlags = 3; - public static final byte MasterSeed = 4; - public static final byte TransformSeed = 5; - public static final byte TransformRounds = 6; - public static final byte EncryptionIV = 7; - public static final byte InnerRandomstreamKey = 8; - public static final byte StreamStartBytes = 9; - public static final byte InnerRandomStreamID = 10; - public static final byte KdfParameters = 11; - public static final byte PublicCustomData = 12; - } - - public class PwDbInnerHeaderV4Fields { - public static final byte EndOfHeader = 0; - public static final byte InnerRandomStreamID = 1; - public static final byte InnerRandomstreamKey = 2; - public static final byte Binary = 3; - } - - public class KdbxBinaryFlags { - public static final byte None = 0; - public static final byte Protected = 1; - } - - public class HeaderAndHash { - public byte[] header; - public byte[] hash; - - public HeaderAndHash (byte[] header, byte[] hash) { - this.header = header; - this.hash = hash; - } - } - - private PwDatabaseV4 db; - public byte[] innerRandomStreamKey = new byte[32]; - public byte[] streamStartBytes = new byte[32]; - public CrsAlgorithm innerRandomStream; - public long version; - - public PwDbHeaderV4(PwDatabaseV4 databaseV4) { - this.db = databaseV4; - this.version = getMinKdbxVersion(databaseV4); // Only for writing - this.setMasterSeed(new byte[32]); - } - - public long getVersion() { - return version; - } - - public void setVersion(long version) { - this.version = version; - } - - private class GroupHasCustomData extends NodeHandler { - - boolean hasCustomData = false; - - @Override - public boolean operate(PwGroupV4 node) { - if (node == null) { - return true; - } - if (node.containsCustomData()) { - hasCustomData = true; - return false; - } - - return true; - } - } - - private class EntryHasCustomData extends NodeHandler { - - boolean hasCustomData = false; - - @Override - public boolean operate(PwEntryV4 entry) { - if (entry == null) { - return true; - } - - if (entry.containsCustomData()) { - hasCustomData = true; - return false; - } - - return true; - } - } - - private int getMinKdbxVersion(PwDatabaseV4 databaseV4) { - // Return v4 if AES is not use - if (databaseV4.getKdfParameters() != null - && !databaseV4.getKdfParameters().getUUID().equals(AesKdf.CIPHER_UUID)) { - return PwDbHeaderV4.FILE_VERSION_32_4; - } - - // Return V4 if custom data are present - if (databaseV4.containsPublicCustomData()) { - return PwDbHeaderV4.FILE_VERSION_32_4; - } - - EntryHasCustomData entryHandler = new EntryHasCustomData(); - GroupHasCustomData groupHandler = new GroupHasCustomData(); - - if (databaseV4.getRootGroup() == null ) { - return PwDbHeaderV4.FILE_VERSION_32_3; - } - databaseV4.getRootGroup().doForEachChildAndForIt(entryHandler, groupHandler); - if (groupHandler.hasCustomData || entryHandler.hasCustomData) { - return PwDbHeaderV4.FILE_VERSION_32_4; - } - - return PwDbHeaderV4.FILE_VERSION_32_3; - } - - /** Assumes the input stream is at the beginning of the .kdbx file - * @param is - * @throws IOException - * @throws InvalidDBVersionException - */ - public HeaderAndHash loadFromFile(InputStream is) throws IOException, InvalidDBVersionException { - MessageDigest md; - try { - md = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new IOException("No SHA-256 implementation"); - } - - ByteArrayOutputStream headerBOS = new ByteArrayOutputStream(); - CopyInputStream cis = new CopyInputStream(is, headerBOS); - DigestInputStream dis = new DigestInputStream(cis, md); - LEDataInputStream lis = new LEDataInputStream(dis); - - int sig1 = lis.readInt(); - int sig2 = lis.readInt(); - - if ( ! matchesHeader(sig1, sig2) ) { - throw new InvalidDBVersionException(); - } - - version = lis.readUInt(); // Erase previous value - if ( ! validVersion(version) ) { - throw new InvalidDBVersionException(); - } - - boolean done = false; - while ( ! done ) { - done = readHeaderField(lis); - } - - byte[] hash = md.digest(); - return new HeaderAndHash(headerBOS.toByteArray(), hash); - } - - private boolean readHeaderField(LEDataInputStream dis) throws IOException { - byte fieldID = (byte) dis.read(); - - int fieldSize; - if (version < FILE_VERSION_32_4) { - fieldSize = dis.readUShort(); - } else { - fieldSize = dis.readInt(); - } - - byte[] fieldData = null; - if ( fieldSize > 0 ) { - fieldData = new byte[fieldSize]; - - int readSize = dis.read(fieldData); - if ( readSize != fieldSize ) { - throw new IOException("Header ended early."); - } - } - - switch ( fieldID ) { - case PwDbHeaderV4Fields.EndOfHeader: - return true; - - case PwDbHeaderV4Fields.CipherID: - setCipher(fieldData); - break; - - case PwDbHeaderV4Fields.CompressionFlags: - setCompressionFlags(fieldData); - break; - - case PwDbHeaderV4Fields.MasterSeed: - setMasterSeed(fieldData); - break; - - case PwDbHeaderV4Fields.TransformSeed: - if(version < PwDbHeaderV4.FILE_VERSION_32_4) - setTransformSeed(fieldData); - break; - - case PwDbHeaderV4Fields.TransformRounds: - if(version < PwDbHeaderV4.FILE_VERSION_32_4) - setTransformRound(fieldData); - break; - - case PwDbHeaderV4Fields.EncryptionIV: - setEncryptionIV(fieldData); - break; - - case PwDbHeaderV4Fields.InnerRandomstreamKey: - if(version < PwDbHeaderV4.FILE_VERSION_32_4) - innerRandomStreamKey = fieldData; - break; - - case PwDbHeaderV4Fields.StreamStartBytes: - streamStartBytes = fieldData; - break; - - case PwDbHeaderV4Fields.InnerRandomStreamID: - if(version < PwDbHeaderV4.FILE_VERSION_32_4) - setRandomStreamID(fieldData); - break; - - case PwDbHeaderV4Fields.KdfParameters: - db.setKdfParameters(KdfParameters.deserialize(fieldData)); - break; - - case PwDbHeaderV4Fields.PublicCustomData: - db.setPublicCustomData(KdfParameters.deserialize(fieldData)); // TODO verify - default: - throw new IOException("Invalid header type: " + fieldID); - - } - - return false; - } - - private void assignAesKdfEngineIfNotExists() { - if (db.getKdfParameters() == null || !db.getKdfParameters().getUUID().equals(KdfFactory.aesKdf.getUUID())) { - db.setKdfParameters(KdfFactory.aesKdf.getDefaultParameters()); - } - } - - private void setCipher(byte[] pbId) throws IOException { - if ( pbId == null || pbId.length != 16 ) { - throw new IOException("Invalid cipher ID."); - } - - db.setDataCipher(Types.bytestoUUID(pbId)); - } - - private void setTransformSeed(byte[] seed) { - assignAesKdfEngineIfNotExists(); - db.getKdfParameters().setByteArray(AesKdf.ParamSeed, seed); - } - - private void setTransformRound(byte[] roundsByte) { - assignAesKdfEngineIfNotExists(); - long rounds = LEDataInputStream.readLong(roundsByte, 0); - db.getKdfParameters().setUInt64(AesKdf.ParamRounds, rounds); - db.setNumberKeyEncryptionRounds(rounds); - } - - private void setCompressionFlags(byte[] pbFlags) throws IOException { - if ( pbFlags == null || pbFlags.length != 4 ) { - throw new IOException("Invalid compression flags."); - } - - int flag = LEDataInputStream.readInt(pbFlags, 0); - if ( flag < 0 || flag >= PwCompressionAlgorithm.values().length ) { - throw new IOException("Unrecognized compression flag."); - } - - db.setCompressionAlgorithm(PwCompressionAlgorithm.Companion.fromId(flag)); - } - - public void setRandomStreamID(byte[] streamID) throws IOException { - if ( streamID == null || streamID.length != 4 ) { - throw new IOException("Invalid stream id."); - } - - int id = LEDataInputStream.readInt(streamID, 0); - if ( id < 0 || id >= CrsAlgorithm.values().length) { - throw new IOException("Invalid stream id."); - } - - innerRandomStream = CrsAlgorithm.Companion.fromId(id); - } - - /** - * Determines if this is a supported version. - * - * A long is needed here to represent the unsigned int since we perform arithmetic on it. - * @param version Database version - * @return true if it's a supported version - */ - private boolean validVersion(long version) { - return ! ((version & FILE_VERSION_CRITICAL_MASK) > (FILE_VERSION_32_4 & FILE_VERSION_CRITICAL_MASK)); - } - - public static boolean matchesHeader(int sig1, int sig2) { - return (sig1 == PwDbHeader.PWM_DBSIG_1) && ( (sig2 == DBSIG_PRE2) || (sig2 == DBSIG_2) ); - } - - public static byte[] computeHeaderHmac(byte[] header, byte[] key) throws IOException{ - byte[] blockKey = HmacBlockStream.GetHmacKey64(key, Types.ULONG_MAX_VALUE); - - Mac hmac; - try { - hmac = Mac.getInstance("HmacSHA256"); - SecretKeySpec signingKey = new SecretKeySpec(blockKey, "HmacSHA256"); - hmac.init(signingKey); - } catch (NoSuchAlgorithmException e) { - throw new IOException("No HmacAlogirthm"); - } catch (InvalidKeyException e) { - throw new IOException("Invalid Hmac Key"); - } - - return hmac.doFinal(header); - } - - public byte[] getTransformSeed() { - // version < FILE_VERSION_32_4) - return db.getKdfParameters().getByteArray(AesKdf.ParamSeed); - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV4.kt index 7eacb931f..663cff5b8 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV4.kt @@ -21,8 +21,8 @@ package com.kunzisoft.keepass.database.element import android.os.Parcel import android.os.Parcelable -import com.kunzisoft.keepass.database.security.ProtectedBinary -import com.kunzisoft.keepass.database.security.ProtectedString +import com.kunzisoft.keepass.database.element.security.ProtectedBinary +import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.utils.MemUtil import com.kunzisoft.keepass.utils.SprEngineV4 import java.util.* diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt new file mode 100644 index 000000000..bd229647c --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt @@ -0,0 +1,154 @@ +/* + * Copyright 2018 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.element.security + +import android.os.Parcel +import android.os.Parcelable +import android.util.Log + +import java.io.ByteArrayInputStream +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.io.InputStream +import java.util.Arrays + +class ProtectedBinary : Parcelable { + + var isProtected: Boolean = false + private set + private var data: ByteArray? = null + private var dataFile: File? = null + private var size: Int = 0 + + fun length(): Long { + if (data != null) + return data!!.size.toLong() + return if (dataFile != null) size.toLong() else 0 + } + + /** + * Empty protected binary + */ + constructor() { + this.isProtected = false + this.data = null + this.dataFile = null + this.size = 0 + } + + constructor(protectedBinary: ProtectedBinary) { + this.isProtected = protectedBinary.isProtected + this.data = protectedBinary.data + this.dataFile = protectedBinary.dataFile + this.size = protectedBinary.size + } + + constructor(enableProtection: Boolean, data: ByteArray?) { + this.isProtected = enableProtection + this.data = data + this.dataFile = null + if (data != null) + this.size = data.size + else + this.size = 0 + } + + constructor(enableProtection: Boolean, dataFile: File, size: Int) { + this.isProtected = enableProtection + this.data = null + this.dataFile = dataFile + this.size = size + } + + private constructor(parcel: Parcel) { + isProtected = parcel.readByte().toInt() != 0 + parcel.readByteArray(data) + dataFile = File(parcel.readString()) + size = parcel.readInt() + } + + @Throws(IOException::class) + fun getData(): InputStream? { + return when { + data != null -> ByteArrayInputStream(data) + dataFile != null -> FileInputStream(dataFile!!) + else -> null + } + } + + fun clear() { + data = null + if (dataFile != null && !dataFile!!.delete()) + Log.e(TAG, "Unable to delete temp file " + dataFile!!.absolutePath) + } + + override fun equals(other: Any?): Boolean { + if (this === other) + return true + if (other == null || javaClass != other.javaClass) + return false + if (other !is ProtectedBinary) + return false + return isProtected == other.isProtected && + size == other.size && + Arrays.equals(data, other.data) && + dataFile != null && + dataFile == other.dataFile + } + + override fun hashCode(): Int { + + var result = 0 + result = 31 * result + if (isProtected) 1 else 0 + result = 31 * result + dataFile!!.hashCode() + result = 31 * result + Integer.valueOf(size)!!.hashCode() + result = 31 * result + Arrays.hashCode(data) + return result + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeByte((if (isProtected) 1 else 0).toByte()) + dest.writeByteArray(data) + dest.writeString(dataFile!!.absolutePath) + dest.writeInt(size) + } + + companion object { + + private val TAG = ProtectedBinary::class.java.name + + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): ProtectedBinary { + return ProtectedBinary(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedString.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedString.kt new file mode 100644 index 000000000..fdf4c33c8 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedString.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.element.security + +import android.os.Parcel +import android.os.Parcelable + +class ProtectedString : Parcelable { + + var isProtected: Boolean = false + private set + private var string: String = "" + + constructor(toCopy: ProtectedString) { + this.isProtected = toCopy.isProtected + this.string = toCopy.string + } + + @JvmOverloads + constructor(enableProtection: Boolean = false, string: String = "") { + this.isProtected = enableProtection + this.string = string + } + + constructor(parcel: Parcel) { + isProtected = parcel.readByte().toInt() != 0 + string = parcel.readString() + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeByte((if (isProtected) 1 else 0).toByte()) + dest.writeString(string) + } + + fun length(): Int { + return string.length + } + + override fun toString(): String { + return string + } + + companion object { + + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): ProtectedString { + return ProtectedString(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/PwCompressionAlgorithm.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/PwCompressionAlgorithm.kt similarity index 96% rename from app/src/main/java/com/kunzisoft/keepass/database/PwCompressionAlgorithm.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/PwCompressionAlgorithm.kt index a292d5e90..a560e5c0b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/PwCompressionAlgorithm.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/PwCompressionAlgorithm.kt @@ -17,7 +17,7 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database +package com.kunzisoft.keepass.database.file // Note: We can get away with using int's to store unsigned 32-bit ints // since we won't do arithmetic on these values (also unlikely to diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDbHeader.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeader.kt similarity index 95% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwDbHeader.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeader.kt index 0a2148c1e..966bbce60 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDbHeader.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeader.kt @@ -17,7 +17,7 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.file abstract class PwDbHeader { @@ -32,7 +32,6 @@ abstract class PwDbHeader { var encryptionIV = ByteArray(16) companion object { - const val PWM_DBSIG_1 = -0x655d26fd } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV3.kt new file mode 100644 index 000000000..3d856da20 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV3.kt @@ -0,0 +1,117 @@ +/* + * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. Derived from KeePass for J2ME + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + * + */ + +package com.kunzisoft.keepass.database.file + +import com.kunzisoft.keepass.stream.LEDataInputStream + +import java.io.IOException + +class PwDbHeaderV3 : PwDbHeader() { + + /** + * Used for the dwKeyEncRounds AES transformations + */ + var transformSeed = ByteArray(32) + + var signature1: Int = 0 // = PWM_DBSIG_1 + var signature2: Int = 0 // = DBSIG_2 + var flags: Int = 0 + var version: Int = 0 + + /** Number of groups in the database */ + var numGroups: Int = 0 + /** Number of entries in the database */ + var numEntries: Int = 0 + + /** + * SHA-256 hash of the database, used for integrity check + */ + var contentsHash = ByteArray(32) + + var numKeyEncRounds: Int = 0 + + /** + * Parse given buf, as read from file. + * @param buf + * @throws IOException + */ + @Throws(IOException::class) + fun loadFromFile(buf: ByteArray, offset: Int) { + signature1 = LEDataInputStream.readInt(buf, offset) + signature2 = LEDataInputStream.readInt(buf, offset + 4) + flags = LEDataInputStream.readInt(buf, offset + 8) + version = LEDataInputStream.readInt(buf, offset + 12) + + System.arraycopy(buf, offset + 16, masterSeed!!, 0, 16) + System.arraycopy(buf, offset + 32, encryptionIV, 0, 16) + + numGroups = LEDataInputStream.readInt(buf, offset + 48) + numEntries = LEDataInputStream.readInt(buf, offset + 52) + + System.arraycopy(buf, offset + 56, contentsHash, 0, 32) + + System.arraycopy(buf, offset + 88, transformSeed, 0, 32) + numKeyEncRounds = LEDataInputStream.readInt(buf, offset + 120) + if (numKeyEncRounds < 0) { + // TODO: Really treat this like an unsigned integer + throw IOException("Does not support more than " + Integer.MAX_VALUE + " rounds.") + } + } + + init { + masterSeed = ByteArray(16) + } + + + /** Determine if the database version is compatible with this application + * @return true, if it is compatible + */ + fun matchesVersion(): Boolean { + return compatibleHeaders(version, DBVER_DW) + } + + companion object { + + // DB sig from KeePass 1.03 + const val DBSIG_2 = -0x4ab4049b + // DB sig from KeePass 1.03 + const val DBVER_DW = 0x00030003 + + const val FLAG_SHA2 = 1 + const val FLAG_RIJNDAEL = 2 + const val FLAG_ARCFOUR = 4 + const val FLAG_TWOFISH = 8 + + /** Size of byte buffer needed to hold this struct. */ + const val BUF_SIZE = 124 + + fun matchesHeader(sig1: Int, sig2: Int): Boolean { + return sig1 == PWM_DBSIG_1 && sig2 == DBSIG_2 + } + + fun compatibleHeaders(one: Int, two: Int): Boolean { + return one and -0x100 == two and -0x100 + } + } + + +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV4.kt new file mode 100644 index 000000000..0de6566dc --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV4.kt @@ -0,0 +1,334 @@ +/* + * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.file + +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.crypto.CrsAlgorithm +import com.kunzisoft.keepass.database.NodeHandler +import com.kunzisoft.keepass.database.element.PwDatabaseV4 +import com.kunzisoft.keepass.database.element.PwEntryV4 +import com.kunzisoft.keepass.database.element.PwGroupV4 +import com.kunzisoft.keepass.database.exception.InvalidDBVersionException +import com.kunzisoft.keepass.stream.CopyInputStream +import com.kunzisoft.keepass.stream.HmacBlockStream +import com.kunzisoft.keepass.stream.LEDataInputStream +import com.kunzisoft.keepass.utils.Types + +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +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 + +class PwDbHeaderV4(private val db: PwDatabaseV4) : PwDbHeader() { + var innerRandomStreamKey: ByteArray = ByteArray(32) + var streamStartBytes: ByteArray = ByteArray(32) + var innerRandomStream: CrsAlgorithm? = null + var version: Long = 0 + + // version < FILE_VERSION_32_4) + var transformSeed: ByteArray? + get() = db.kdfParameters.getByteArray(AesKdf.ParamSeed) + private set(seed) { + assignAesKdfEngineIfNotExists() + db.kdfParameters.setByteArray(AesKdf.ParamSeed, seed) + } + + object PwDbHeaderV4Fields { + const val EndOfHeader: Byte = 0 + const val Comment: Byte = 1 + const val CipherID: Byte = 2 + const val CompressionFlags: Byte = 3 + const val MasterSeed: Byte = 4 + const val TransformSeed: Byte = 5 + const val TransformRounds: Byte = 6 + const val EncryptionIV: Byte = 7 + const val InnerRandomstreamKey: Byte = 8 + const val StreamStartBytes: Byte = 9 + const val InnerRandomStreamID: Byte = 10 + const val KdfParameters: Byte = 11 + const val PublicCustomData: Byte = 12 + } + + object PwDbInnerHeaderV4Fields { + const val EndOfHeader: Byte = 0 + const val InnerRandomStreamID: Byte = 1 + const val InnerRandomstreamKey: Byte = 2 + const val Binary: Byte = 3 + } + + object KdbxBinaryFlags { + const val None: Byte = 0 + const val Protected: Byte = 1 + } + + inner class HeaderAndHash(var header: ByteArray, var hash: ByteArray) + + init { + this.version = getMinKdbxVersion(db).toLong() // Only for writing + this.masterSeed = ByteArray(32) + } + + private inner class GroupHasCustomData : NodeHandler() { + + internal var hasCustomData = false + + override fun operate(node: PwGroupV4): Boolean { + if (node.containsCustomData()) { + hasCustomData = true + return false + } + return true + } + } + + private inner class EntryHasCustomData : NodeHandler() { + + internal var hasCustomData = false + + override fun operate(node: PwEntryV4): Boolean { + if (node.containsCustomData()) { + hasCustomData = true + return false + } + return true + } + } + + private fun getMinKdbxVersion(databaseV4: PwDatabaseV4): Long { + // Return v4 if AES is not use + if (databaseV4.kdfParameters != null && databaseV4.kdfParameters.uuid != AesKdf.CIPHER_UUID) { + return FILE_VERSION_32_4 + } + + // Return V4 if custom data are present + if (databaseV4.containsPublicCustomData()) { + return FILE_VERSION_32_4 + } + + val entryHandler = EntryHasCustomData() + val groupHandler = GroupHasCustomData() + + if (databaseV4.rootGroup == null) { + return FILE_VERSION_32_3 + } + databaseV4.rootGroup.doForEachChildAndForIt(entryHandler, groupHandler) + return if (groupHandler.hasCustomData || entryHandler.hasCustomData) { + FILE_VERSION_32_4 + } else FILE_VERSION_32_3 + + } + + /** Assumes the input stream is at the beginning of the .kdbx file + * @param `is` + * @throws IOException + * @throws InvalidDBVersionException + */ + @Throws(IOException::class, InvalidDBVersionException::class) + fun loadFromFile(inputStream: InputStream): HeaderAndHash { + val md: MessageDigest + try { + md = MessageDigest.getInstance("SHA-256") + } catch (e: NoSuchAlgorithmException) { + throw IOException("No SHA-256 implementation") + } + + val headerBOS = ByteArrayOutputStream() + val cis = CopyInputStream(inputStream, headerBOS) + val dis = DigestInputStream(cis, md) + val lis = LEDataInputStream(dis) + + val sig1 = lis.readInt() + val sig2 = lis.readInt() + + if (!matchesHeader(sig1, sig2)) { + throw InvalidDBVersionException() + } + + version = lis.readUInt() // Erase previous value + if (!validVersion(version)) { + throw InvalidDBVersionException() + } + + var done = false + while (!done) { + done = readHeaderField(lis) + } + + val hash = md.digest() + return HeaderAndHash(headerBOS.toByteArray(), hash) + } + + @Throws(IOException::class) + private fun readHeaderField(dis: LEDataInputStream): Boolean { + val fieldID = dis.read().toByte() + + val fieldSize: Int = if (version < FILE_VERSION_32_4) { + dis.readUShort() + } else { + dis.readInt() + } + + var fieldData: ByteArray? = null + if (fieldSize > 0) { + fieldData = ByteArray(fieldSize) + + val readSize = dis.read(fieldData) + if (readSize != fieldSize) { + throw IOException("Header ended early.") + } + } + + if (fieldData != null) + when (fieldID) { + PwDbHeaderV4Fields.EndOfHeader -> return true + + PwDbHeaderV4Fields.CipherID -> setCipher(fieldData) + + PwDbHeaderV4Fields.CompressionFlags -> setCompressionFlags(fieldData) + + PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData + + PwDbHeaderV4Fields.TransformSeed -> if (version < FILE_VERSION_32_4) + transformSeed = fieldData + + PwDbHeaderV4Fields.TransformRounds -> if (version < FILE_VERSION_32_4) + setTransformRound(fieldData) + + PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData + + PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version < FILE_VERSION_32_4) + innerRandomStreamKey = fieldData + + PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData + + PwDbHeaderV4Fields.InnerRandomStreamID -> if (version < FILE_VERSION_32_4) + setRandomStreamID(fieldData) + + PwDbHeaderV4Fields.KdfParameters -> db.kdfParameters = KdfParameters.deserialize(fieldData) + + PwDbHeaderV4Fields.PublicCustomData -> { + db.publicCustomData = KdfParameters.deserialize(fieldData) // TODO verify + throw IOException("Invalid header type: $fieldID") + } + else -> throw IOException("Invalid header type: $fieldID") + } + + return false + } + + private fun assignAesKdfEngineIfNotExists() { + if (db.kdfParameters == null || db.kdfParameters.uuid != KdfFactory.aesKdf.uuid) { + db.kdfParameters = KdfFactory.aesKdf.defaultParameters + } + } + + @Throws(IOException::class) + private fun setCipher(pbId: ByteArray?) { + if (pbId == null || pbId.size != 16) { + throw IOException("Invalid cipher ID.") + } + + db.dataCipher = Types.bytestoUUID(pbId) + } + + private fun setTransformRound(roundsByte: ByteArray?) { + assignAesKdfEngineIfNotExists() + val rounds = LEDataInputStream.readLong(roundsByte!!, 0) + db.kdfParameters.setUInt64(AesKdf.ParamRounds, rounds) + db.numberKeyEncryptionRounds = rounds + } + + @Throws(IOException::class) + private fun setCompressionFlags(pbFlags: ByteArray?) { + if (pbFlags == null || pbFlags.size != 4) { + throw IOException("Invalid compression flags.") + } + + val flag = LEDataInputStream.readInt(pbFlags, 0) + if (flag < 0 || flag >= PwCompressionAlgorithm.values().size) { + throw IOException("Unrecognized compression flag.") + } + + db.compressionAlgorithm = PwCompressionAlgorithm.fromId(flag) + } + + @Throws(IOException::class) + fun setRandomStreamID(streamID: ByteArray?) { + if (streamID == null || streamID.size != 4) { + throw IOException("Invalid stream id.") + } + + val id = LEDataInputStream.readInt(streamID, 0) + if (id < 0 || id >= CrsAlgorithm.values().size) { + throw IOException("Invalid stream id.") + } + + innerRandomStream = CrsAlgorithm.fromId(id) + } + + /** + * Determines if this is a supported version. + * + * A long is needed here to represent the unsigned int since we perform arithmetic on it. + * @param version Database version + * @return true if it's a supported version + */ + private fun validVersion(version: Long): Boolean { + return version and FILE_VERSION_CRITICAL_MASK <= FILE_VERSION_32_4 and FILE_VERSION_CRITICAL_MASK + } + + companion object { + const val DBSIG_PRE2 = -0x4ab4049a + const val DBSIG_2 = -0x4ab40499 + + private const val FILE_VERSION_CRITICAL_MASK: Long = -0x10000 + const val FILE_VERSION_32_3: Long = 0x00030001 + const val FILE_VERSION_32_4: Long = 0x00040000 + + fun matchesHeader(sig1: Int, sig2: Int): 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, Types.ULONG_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) + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/load/Importer.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/load/Importer.kt similarity index 97% rename from app/src/main/java/com/kunzisoft/keepass/database/load/Importer.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/load/Importer.kt index 5f8f1866a..ccd24df4c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/load/Importer.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/load/Importer.kt @@ -17,7 +17,7 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.load +package com.kunzisoft.keepass.database.file.load import com.kunzisoft.keepass.database.element.PwDatabase import com.kunzisoft.keepass.database.exception.InvalidDBException diff --git a/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV3.kt similarity index 98% rename from app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV3.kt index 074bc88e7..18385e01f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV3.kt @@ -43,13 +43,15 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -package com.kunzisoft.keepass.database.load +package com.kunzisoft.keepass.database.file.load import android.util.Log import com.kunzisoft.keepass.R import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.exception.* +import com.kunzisoft.keepass.database.file.PwDbHeader +import com.kunzisoft.keepass.database.file.PwDbHeaderV3 import com.kunzisoft.keepass.stream.LEDataInputStream import com.kunzisoft.keepass.stream.NullOutputStream import com.kunzisoft.keepass.tasks.ProgressTaskUpdater diff --git a/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt similarity index 99% rename from app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt index c5b7a65f1..e415a6c6f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt @@ -17,20 +17,21 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.load +package com.kunzisoft.keepass.database.file.load import biz.source_code.base64Coder.Base64Coder import com.kunzisoft.keepass.R import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.PwStreamCipherFactory import com.kunzisoft.keepass.crypto.engine.CipherEngine -import com.kunzisoft.keepass.database.PwCompressionAlgorithm +import com.kunzisoft.keepass.database.file.PwCompressionAlgorithm import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.exception.ArcFourException import com.kunzisoft.keepass.database.exception.InvalidDBException import com.kunzisoft.keepass.database.exception.InvalidPasswordException -import com.kunzisoft.keepass.database.security.ProtectedBinary -import com.kunzisoft.keepass.database.security.ProtectedString +import com.kunzisoft.keepass.database.file.PwDbHeaderV4 +import com.kunzisoft.keepass.database.element.security.ProtectedBinary +import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.stream.BetterCipherInputStream import com.kunzisoft.keepass.stream.HashedBlockInputStream import com.kunzisoft.keepass.stream.HmacBlockInputStream @@ -101,7 +102,7 @@ class ImporterV4(private val streamDir: File) : Importer() { mDatabase.binPool.clear() val hh = header.loadFromFile(databaseInputStream) - version = header.getVersion() + version = header.version hashOfHeader = hh.hash val pbHeader = hh.header diff --git a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutput.java b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutput.kt similarity index 81% rename from app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutput.java rename to app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutput.kt index 3e1b76ca3..c72a3fba0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutput.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutput.kt @@ -17,11 +17,9 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.save; +package com.kunzisoft.keepass.database.file.save -public class PwDbHeaderOutput { - protected byte[] hashOfHeader = null; - - public byte[] getHashOfHeader() { return hashOfHeader; } - +open class PwDbHeaderOutput { + var hashOfHeader: ByteArray? = null + protected set } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV3.kt new file mode 100644 index 000000000..19f31b503 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV3.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.file.save + +import com.kunzisoft.keepass.database.file.PwDbHeaderV3 +import com.kunzisoft.keepass.stream.LEDataOutputStream + +import java.io.IOException +import java.io.OutputStream + +class PwDbHeaderOutputV3(private val mHeader: PwDbHeaderV3, private val mOS: OutputStream) { + + @Throws(IOException::class) + fun outputStart() { + mOS.write(LEDataOutputStream.writeIntBuf(mHeader.signature1)) + mOS.write(LEDataOutputStream.writeIntBuf(mHeader.signature2)) + mOS.write(LEDataOutputStream.writeIntBuf(mHeader.flags)) + mOS.write(LEDataOutputStream.writeIntBuf(mHeader.version)) + mOS.write(mHeader.masterSeed!!) + mOS.write(mHeader.encryptionIV) + mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numGroups)) + mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numEntries)) + } + + @Throws(IOException::class) + fun outputContentHash() { + mOS.write(mHeader.contentsHash) + } + + @Throws(IOException::class) + fun outputEnd() { + mOS.write(mHeader.transformSeed) + mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numKeyEncRounds)) + } + + @Throws(IOException::class) + fun output() { + outputStart() + outputContentHash() + outputEnd() + } + + @Throws(IOException::class) + fun close() { + mOS.close() + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV4.kt new file mode 100644 index 000000000..8d90bee4b --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV4.kt @@ -0,0 +1,151 @@ +/* + * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.file.save + +import com.kunzisoft.keepass.collections.VariantDictionary +import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters +import com.kunzisoft.keepass.database.element.PwDatabaseV4 +import com.kunzisoft.keepass.database.file.PwDbHeader +import com.kunzisoft.keepass.database.file.PwDbHeaderV4 +import com.kunzisoft.keepass.database.exception.PwDbOutputException +import com.kunzisoft.keepass.stream.HmacBlockStream +import com.kunzisoft.keepass.stream.LEDataOutputStream +import com.kunzisoft.keepass.stream.MacOutputStream +import com.kunzisoft.keepass.utils.Types + +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 PwDbHeaderOutputV4 @Throws(PwDbOutputException::class) +constructor(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os: OutputStream) : PwDbHeaderOutput() { + private val los: LEDataOutputStream + private val mos: MacOutputStream + private val dos: DigestOutputStream + lateinit var headerHmac: ByteArray + + init { + + val md: MessageDigest + try { + md = MessageDigest.getInstance("SHA-256") + } catch (e: NoSuchAlgorithmException) { + throw PwDbOutputException("SHA-256 not implemented here.") + } + + try { + db.makeFinalKey(header.masterSeed) + } catch (e: IOException) { + throw PwDbOutputException(e) + } + + val hmac: Mac + try { + hmac = Mac.getInstance("HmacSHA256") + val signingKey = SecretKeySpec(HmacBlockStream.GetHmacKey64(db.hmacKey, Types.ULONG_MAX_VALUE), "HmacSHA256") + hmac.init(signingKey) + } catch (e: NoSuchAlgorithmException) { + throw PwDbOutputException(e) + } catch (e: InvalidKeyException) { + throw PwDbOutputException(e) + } + + dos = DigestOutputStream(os, md) + mos = MacOutputStream(dos, hmac) + los = LEDataOutputStream(mos) + } + + @Throws(IOException::class) + fun output() { + + los.writeUInt(PwDbHeader.PWM_DBSIG_1.toLong()) + los.writeUInt(PwDbHeaderV4.DBSIG_2.toLong()) + los.writeUInt(header.version) + + + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.CipherID, Types.UUIDtoBytes(db.dataCipher)) + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.CompressionFlags, LEDataOutputStream.writeIntBuf(db.compressionAlgorithm.id)) + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.MasterSeed, header.masterSeed) + + if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.TransformSeed, header.transformSeed) + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.TransformRounds, LEDataOutputStream.writeLongBuf(db.numberKeyEncryptionRounds)) + } else { + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.KdfParameters, KdfParameters.serialize(db.kdfParameters)) + } + + if (header.encryptionIV.isNotEmpty()) { + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV) + } + + if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey) + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes) + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.InnerRandomStreamID, LEDataOutputStream.writeIntBuf(header.innerRandomStream!!.id)) + } + + if (db.containsPublicCustomData()) { + val bos = ByteArrayOutputStream() + val los = LEDataOutputStream(bos) + VariantDictionary.serialize(db.publicCustomData, los) + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray()) + } + + writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.EndOfHeader, EndHeaderValue) + + los.flush() + hashOfHeader = dos.messageDigest.digest() + headerHmac = mos.mac + } + + @Throws(IOException::class) + private fun writeHeaderField(fieldId: Byte, pbData: ByteArray?) { + // Write the field id + los.write(fieldId.toInt()) + + if (pbData != null) { + writeHeaderFieldSize(pbData.size) + los.write(pbData) + } else { + writeHeaderFieldSize(0) + } + } + + @Throws(IOException::class) + private fun writeHeaderFieldSize(size: Int) { + if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + los.writeUShort(size) + } else { + los.writeInt(size) + } + + } + + companion object { + private val EndHeaderValue = byteArrayOf('\r'.toByte(), '\n'.toByte(), '\r'.toByte(), '\n'.toByte()) + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbInnerHeaderOutputV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbInnerHeaderOutputV4.kt new file mode 100644 index 000000000..46703ffde --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbInnerHeaderOutputV4.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Brian Pellin. + * + * This file is part of KeePass DX. + * + * KeePassDroid 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. + * + * KeePassDroid 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 KeePassDroid. If not, see . + * + */ +package com.kunzisoft.keepass.database.file.save + +import com.kunzisoft.keepass.database.element.PwDatabaseV4 +import com.kunzisoft.keepass.database.file.PwDbHeaderV4 +import com.kunzisoft.keepass.database.element.security.ProtectedBinary +import com.kunzisoft.keepass.stream.LEDataOutputStream +import com.kunzisoft.keepass.utils.MemUtil + +import java.io.IOException +import java.io.OutputStream +import kotlin.experimental.or + +class PwDbInnerHeaderOutputV4(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os: OutputStream) { + + private val los: LEDataOutputStream = LEDataOutputStream(os) + + @Throws(IOException::class) + fun output() { + los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomStreamID.toInt()) + los.writeInt(4) + if (header.innerRandomStream == null) + throw IOException("Can't write innerRandomStream") + los.writeInt(header.innerRandomStream!!.id) + + val streamKeySize = header.innerRandomStreamKey.size + los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomstreamKey.toInt()) + los.writeInt(streamKeySize) + los.write(header.innerRandomStreamKey) + + for (protectedBinary in db.binPool.binaries()) { + var flag = PwDbHeaderV4.KdbxBinaryFlags.None + if (protectedBinary.isProtected) { + flag = flag or PwDbHeaderV4.KdbxBinaryFlags.Protected + } + + los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary.toInt()) + los.writeInt(protectedBinary.length().toInt() + 1) // TODO verify + los.write(flag.toInt()) + + protectedBinary.getData()?.let { + MemUtil.readBytes(it) { buffer -> los.write(buffer) } + } ?: throw IOException("Can't write protected binary") + } + + los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader.toInt()) + los.writeInt(0) + } + +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbOutput.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbOutput.kt new file mode 100644 index 000000000..8cc712980 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbOutput.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.file.save + +import com.kunzisoft.keepass.database.file.PwDbHeader +import com.kunzisoft.keepass.database.exception.PwDbOutputException + +import java.io.OutputStream +import java.security.NoSuchAlgorithmException +import java.security.SecureRandom + +abstract class PwDbOutput
protected constructor(protected var mOS: OutputStream) { + + @Throws(PwDbOutputException::class) + protected open fun setIVs(header: Header): SecureRandom { + val random: SecureRandom + try { + random = SecureRandom.getInstance("SHA1PRNG") + } catch (e: NoSuchAlgorithmException) { + throw PwDbOutputException("Does not support secure random number generation.") + } + + random.nextBytes(header.encryptionIV) + random.nextBytes(header.masterSeed) + + return random + } + + @Throws(PwDbOutputException::class) + abstract fun output() + + @Throws(PwDbOutputException::class) + abstract fun outputHeader(outputStream: OutputStream): Header + +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV3Output.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV3Output.kt new file mode 100644 index 000000000..7b1b7dc12 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV3Output.kt @@ -0,0 +1,279 @@ +/* +` * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.file.save + +import com.kunzisoft.keepass.crypto.CipherFactory +import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.exception.PwDbOutputException +import com.kunzisoft.keepass.database.file.PwDbHeader +import com.kunzisoft.keepass.database.file.PwDbHeaderV3 +import com.kunzisoft.keepass.stream.LEDataOutputStream +import com.kunzisoft.keepass.stream.NullOutputStream + +import javax.crypto.Cipher +import javax.crypto.CipherOutputStream +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec +import java.io.BufferedOutputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.OutputStream +import java.security.* +import java.util.ArrayList + +class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : PwDbOutput(os) { + + private var headerHashBlock: ByteArray? = null + + @Throws(PwDbOutputException::class) + fun getFinalKey(header: PwDbHeader): ByteArray { + try { + val h3 = header as PwDbHeaderV3 + mDatabaseV3.makeFinalKey(h3.masterSeed, h3.transformSeed, mDatabaseV3.numberKeyEncryptionRounds) + return mDatabaseV3.finalKey + } catch (e: IOException) { + throw PwDbOutputException("Key creation failed.", e) + } + + } + + @Throws(PwDbOutputException::class) + override fun output() { + // Before we output the header, we should sort our list of groups + // and remove any orphaned nodes that are no longer part of the tree hierarchy + sortGroupsForOutput() + + val header = outputHeader(mOS) + + val finalKey = getFinalKey(header) + + val cipher: Cipher + cipher = try { + when { + mDatabaseV3.encryptionAlgorithm === PwEncryptionAlgorithm.AESRijndael-> + CipherFactory.getInstance("AES/CBC/PKCS5Padding") + mDatabaseV3.encryptionAlgorithm === PwEncryptionAlgorithm.Twofish -> + CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING") + else -> + throw Exception() + } + } catch (e: Exception) { + throw PwDbOutputException("Algorithm not supported.", e) + } + + try { + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(finalKey, "AES"), IvParameterSpec(header.encryptionIV)) + val cos = CipherOutputStream(mOS, cipher) + val bos = BufferedOutputStream(cos) + outputPlanGroupAndEntries(bos) + bos.flush() + bos.close() + + } catch (e: InvalidKeyException) { + throw PwDbOutputException("Invalid key", e) + } catch (e: InvalidAlgorithmParameterException) { + throw PwDbOutputException("Invalid algorithm parameter.", e) + } catch (e: IOException) { + throw PwDbOutputException("Failed to output final encrypted part.", e) + } + + } + + @Throws(PwDbOutputException::class) + override fun setIVs(header: PwDbHeaderV3): SecureRandom { + val random = super.setIVs(header) + random.nextBytes(header.transformSeed) + return random + } + + @Throws(PwDbOutputException::class) + override fun outputHeader(outputStream: OutputStream): PwDbHeaderV3 { + // Build header + val header = PwDbHeaderV3() + header.signature1 = PwDbHeader.PWM_DBSIG_1 + header.signature2 = PwDbHeaderV3.DBSIG_2 + header.flags = PwDbHeaderV3.FLAG_SHA2 + + if (mDatabaseV3.encryptionAlgorithm === PwEncryptionAlgorithm.AESRijndael) { + header.flags = header.flags or PwDbHeaderV3.FLAG_RIJNDAEL + } else if (mDatabaseV3.encryptionAlgorithm === PwEncryptionAlgorithm.Twofish) { + header.flags = header.flags or PwDbHeaderV3.FLAG_TWOFISH + } else { + throw PwDbOutputException("Unsupported algorithm.") + } + + header.version = PwDbHeaderV3.DBVER_DW + header.numGroups = mDatabaseV3.numberOfGroups() + header.numEntries = mDatabaseV3.numberOfEntries() + header.numKeyEncRounds = mDatabaseV3.numberKeyEncryptionRounds.toInt() + + setIVs(header) + + // Content checksum + var md: MessageDigest? = null + try { + md = MessageDigest.getInstance("SHA-256") + } catch (e: NoSuchAlgorithmException) { + throw PwDbOutputException("SHA-256 not implemented here.", e) + } + + // Header checksum + val headerDigest: MessageDigest + try { + headerDigest = MessageDigest.getInstance("SHA-256") + } catch (e: NoSuchAlgorithmException) { + throw PwDbOutputException("SHA-256 not implemented here.", e) + } + + var nos = NullOutputStream() + val headerDos = DigestOutputStream(nos, headerDigest) + + // Output header for the purpose of calculating the header checksum + var pho = PwDbHeaderOutputV3(header, headerDos) + try { + pho.outputStart() + pho.outputEnd() + headerDos.flush() + } catch (e: IOException) { + throw PwDbOutputException(e) + } + + val headerHash = headerDigest.digest() + headerHashBlock = getHeaderHashBuffer(headerHash) + + // Output database for the purpose of calculating the content checksum + nos = NullOutputStream() + val dos = DigestOutputStream(nos, md) + val bos = BufferedOutputStream(dos) + try { + outputPlanGroupAndEntries(bos) + bos.flush() + bos.close() + } catch (e: IOException) { + throw PwDbOutputException("Failed to generate checksum.", e) + } + + header.contentsHash = md!!.digest() + + // Output header for real output, containing content hash + pho = PwDbHeaderOutputV3(header, outputStream) + try { + pho.outputStart() + dos.on(false) + pho.outputContentHash() + dos.on(true) + pho.outputEnd() + dos.flush() + } catch (e: IOException) { + throw PwDbOutputException(e) + } + + return header + } + + @Throws(PwDbOutputException::class) + fun outputPlanGroupAndEntries(os: OutputStream) { + val los = LEDataOutputStream(os) + + // useHeaderHash + if (headerHashBlock != null) { + try { + los.writeUShort(0x0000) + los.writeInt(headerHashBlock!!.size) + los.write(headerHashBlock) + } catch (e: IOException) { + throw PwDbOutputException("Failed to output header hash.", e) + } + } + + // Groups + for (group in mDatabaseV3.groupIndexes) { + val pgo = PwGroupOutputV3(group, os) + try { + pgo.output() + } catch (e: IOException) { + throw PwDbOutputException("Failed to output a tree", e) + } + } + + // Entries + for (entry in mDatabaseV3.entryIndexes) { + val peo = PwEntryOutputV3(entry, os) + try { + peo.output() + } catch (e: IOException) { + throw PwDbOutputException("Failed to output an entry.", e) + } + } + } + + private fun sortGroupsForOutput() { + val groupList = ArrayList() + // Rebuild list according to coalation sorting order removing any orphaned groups + for (rootGroup in mDatabaseV3.rootGroups) { + sortGroup(rootGroup, groupList) + } + mDatabaseV3.setGroupIndexes(groupList) + } + + private fun sortGroup(group: PwGroupV3, groupList: MutableList) { + // Add current tree + groupList.add(group) + + // Recurse over children + for (childGroup in group.getChildGroups()) { + sortGroup(childGroup, groupList) + } + } + + private fun getHeaderHashBuffer(headerDigest: ByteArray): ByteArray? { + return try { + val byteArrayOutputStream = ByteArrayOutputStream() + writeExtData(headerDigest, byteArrayOutputStream) + byteArrayOutputStream.toByteArray() + } catch (e: IOException) { + null + } + + } + + @Throws(IOException::class) + private fun writeExtData(headerDigest: ByteArray, os: OutputStream) { + val los = LEDataOutputStream(os) + + writeExtDataField(los, 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) + + } + + @Throws(IOException::class) + private fun writeExtDataField(los: LEDataOutputStream, fieldType: Int, data: ByteArray?, fieldSize: Int) { + los.writeUShort(fieldType) + los.writeInt(fieldSize) + if (data != null) { + los.write(data) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV4Output.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV4Output.kt new file mode 100644 index 000000000..9dfc7547b --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV4Output.kt @@ -0,0 +1,743 @@ +/* + * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.file.save + +import android.util.Log +import android.util.Xml +import biz.source_code.base64Coder.Base64Coder +import com.kunzisoft.keepass.crypto.CipherFactory +import com.kunzisoft.keepass.crypto.CrsAlgorithm +import com.kunzisoft.keepass.crypto.PwStreamCipherFactory +import com.kunzisoft.keepass.crypto.engine.CipherEngine +import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory +import com.kunzisoft.keepass.database.* +import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.exception.PwDbOutputException +import com.kunzisoft.keepass.database.exception.UnknownKDF +import com.kunzisoft.keepass.database.file.PwCompressionAlgorithm +import com.kunzisoft.keepass.database.file.PwDbHeaderV4 +import com.kunzisoft.keepass.database.element.security.ProtectedBinary +import com.kunzisoft.keepass.database.element.security.ProtectedString +import com.kunzisoft.keepass.stream.HashedBlockOutputStream +import com.kunzisoft.keepass.stream.HmacBlockOutputStream +import com.kunzisoft.keepass.stream.LEDataOutputStream +import com.kunzisoft.keepass.utils.DateUtil +import com.kunzisoft.keepass.utils.EmptyUtils +import com.kunzisoft.keepass.utils.MemUtil +import com.kunzisoft.keepass.utils.Types +import org.joda.time.DateTime +import org.spongycastle.crypto.StreamCipher +import org.xmlpull.v1.XmlSerializer + +import javax.crypto.Cipher +import javax.crypto.CipherOutputStream +import java.io.IOException +import java.io.OutputStream +import java.security.NoSuchAlgorithmException +import java.security.SecureRandom +import java.util.* +import java.util.zip.GZIPOutputStream + +class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputStream) : PwDbOutput(outputStream) { + + private var randomStream: StreamCipher? = null + private lateinit var xml: XmlSerializer + private var header: PwDbHeaderV4? = null + private var hashOfHeader: ByteArray? = null + private var headerHmac: ByteArray? = null + private var engine: CipherEngine? = null + + @Throws(PwDbOutputException::class) + override fun output() { + + try { + try { + engine = CipherFactory.getInstance(mDatabaseV4.dataCipher) + } catch (e: NoSuchAlgorithmException) { + throw PwDbOutputException("No such cipher", e) + } + + header = outputHeader(mOS) + + val osPlain: OutputStream + if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { + val cos = attachStreamEncryptor(header!!, mOS) + cos.write(header!!.streamStartBytes) + + osPlain = HashedBlockOutputStream(cos) + } else { + mOS.write(hashOfHeader!!) + mOS.write(headerHmac!!) + + val hbos = HmacBlockOutputStream(mOS, mDatabaseV4.hmacKey) + osPlain = attachStreamEncryptor(header!!, hbos) + } + + val osXml: OutputStream + try { + if (mDatabaseV4.compressionAlgorithm === PwCompressionAlgorithm.Gzip) { + osXml = GZIPOutputStream(osPlain) + } else { + osXml = osPlain + } + + if (header!!.version >= PwDbHeaderV4.FILE_VERSION_32_4) { + val ihOut = PwDbInnerHeaderOutputV4(mDatabaseV4, header!!, osXml) + ihOut.output() + } + + outputDatabase(osXml) + osXml.close() + } catch (e: IllegalArgumentException) { + throw PwDbOutputException(e) + } catch (e: IllegalStateException) { + throw PwDbOutputException(e) + } + + } catch (e: IOException) { + throw PwDbOutputException(e) + } + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun outputDatabase(os: OutputStream) { + + xml = Xml.newSerializer() + + xml.setOutput(os, "UTF-8") + xml.startDocument("UTF-8", true) + + xml.startTag(null, PwDatabaseV4XML.ElemDocNode) + + writeMeta() + + val root = mDatabaseV4.rootGroup + xml.startTag(null, PwDatabaseV4XML.ElemRoot) + startGroup(root) + val groupStack = Stack() + groupStack.push(root) + + if (!root.doForEachChild( + object : NodeHandler() { + override fun operate(node: PwEntryV4): Boolean { + try { + writeEntry(node, false) + } catch (ex: IOException) { + throw RuntimeException(ex) + } + + return true + } + }, + object : NodeHandler() { + override fun operate(node: PwGroupV4): Boolean { + while (true) { + try { + if (node.parent === groupStack.peek()) { + groupStack.push(node) + startGroup(node) + break + } else { + groupStack.pop() + if (groupStack.size <= 0) return false + endGroup() + } + } catch (e: IOException) { + throw RuntimeException(e) + } + + } + return true + } + })) + throw RuntimeException("Writing groups failed") + + while (groupStack.size > 1) { + xml.endTag(null, PwDatabaseV4XML.ElemGroup) + groupStack.pop() + } + + endGroup() + + writeList(PwDatabaseV4XML.ElemDeletedObjects, mDatabaseV4.deletedObjects) + + xml.endTag(null, PwDatabaseV4XML.ElemRoot) + + xml.endTag(null, PwDatabaseV4XML.ElemDocNode) + xml.endDocument() + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeMeta() { + xml.startTag(null, PwDatabaseV4XML.ElemMeta) + + writeObject(PwDatabaseV4XML.ElemGenerator, mDatabaseV4.localizedAppName) + + if (hashOfHeader != null) { + writeObject(PwDatabaseV4XML.ElemHeaderHash, String(Base64Coder.encode(hashOfHeader!!))) + } + + writeObject(PwDatabaseV4XML.ElemDbName, mDatabaseV4.name, true) + writeObject(PwDatabaseV4XML.ElemDbNameChanged, mDatabaseV4.nameChanged.date) + writeObject(PwDatabaseV4XML.ElemDbDesc, mDatabaseV4.description, true) + writeObject(PwDatabaseV4XML.ElemDbDescChanged, mDatabaseV4.descriptionChanged.date) + writeObject(PwDatabaseV4XML.ElemDbDefaultUser, mDatabaseV4.defaultUserName, true) + writeObject(PwDatabaseV4XML.ElemDbDefaultUserChanged, mDatabaseV4.defaultUserNameChanged.date) + writeObject(PwDatabaseV4XML.ElemDbMntncHistoryDays, mDatabaseV4.maintenanceHistoryDays) + writeObject(PwDatabaseV4XML.ElemDbColor, mDatabaseV4.color) + writeObject(PwDatabaseV4XML.ElemDbKeyChanged, mDatabaseV4.keyLastChanged.date) + writeObject(PwDatabaseV4XML.ElemDbKeyChangeRec, mDatabaseV4.keyChangeRecDays) + writeObject(PwDatabaseV4XML.ElemDbKeyChangeForce, mDatabaseV4.keyChangeForceDays) + + writeList(PwDatabaseV4XML.ElemMemoryProt, mDatabaseV4.memoryProtection) + + writeCustomIconList() + + writeObject(PwDatabaseV4XML.ElemRecycleBinEnabled, mDatabaseV4.isRecycleBinEnabled) + writeObject(PwDatabaseV4XML.ElemRecycleBinUuid, mDatabaseV4.recycleBinUUID) + writeObject(PwDatabaseV4XML.ElemRecycleBinChanged, mDatabaseV4.recycleBinChanged) + writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroup, mDatabaseV4.entryTemplatesGroup) + writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroupChanged, mDatabaseV4.entryTemplatesGroupChanged.date) + writeObject(PwDatabaseV4XML.ElemHistoryMaxItems, mDatabaseV4.historyMaxItems.toLong()) + writeObject(PwDatabaseV4XML.ElemHistoryMaxSize, mDatabaseV4.historyMaxSize) + writeObject(PwDatabaseV4XML.ElemLastSelectedGroup, mDatabaseV4.lastSelectedGroup) + writeObject(PwDatabaseV4XML.ElemLastTopVisibleGroup, mDatabaseV4.lastTopVisibleGroup) + + if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { + writeBinPool() + } + writeList(PwDatabaseV4XML.ElemCustomData, mDatabaseV4.customData) + + xml.endTag(null, PwDatabaseV4XML.ElemMeta) + } + + @Throws(PwDbOutputException::class) + private fun attachStreamEncryptor(header: PwDbHeaderV4, os: OutputStream): CipherOutputStream { + val cipher: Cipher + try { + //mDatabaseV4.makeFinalKey(header.masterSeed, mDatabaseV4.kdfParameters); + + cipher = engine!!.getCipher(Cipher.ENCRYPT_MODE, mDatabaseV4.finalKey, header.encryptionIV) + } catch (e: Exception) { + throw PwDbOutputException("Invalid algorithm.", e) + } + + return CipherOutputStream(os, cipher) + } + + @Throws(PwDbOutputException::class) + override fun setIVs(header: PwDbHeaderV4): SecureRandom { + val random = super.setIVs(header) + random.nextBytes(header.masterSeed) + + val ivLength = engine!!.ivLength() + if (ivLength != header.encryptionIV.size) { + header.encryptionIV = ByteArray(ivLength) + } + random.nextBytes(header.encryptionIV) + + if (mDatabaseV4.kdfParameters == null) { + mDatabaseV4.kdfParameters = KdfFactory.aesKdf.defaultParameters + } + + try { + val kdf = KdfFactory.getEngineV4(mDatabaseV4.kdfParameters) + kdf.randomize(mDatabaseV4.kdfParameters) + } catch (unknownKDF: UnknownKDF) { + Log.e(TAG, "Unable to retrieve header", unknownKDF) + } + + if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + header.innerRandomStream = CrsAlgorithm.Salsa20 + header.innerRandomStreamKey = ByteArray(32) + } else { + header.innerRandomStream = CrsAlgorithm.ChaCha20 + header.innerRandomStreamKey = ByteArray(64) + } + random.nextBytes(header.innerRandomStreamKey) + + randomStream = PwStreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey) + if (randomStream == null) { + throw PwDbOutputException("Invalid random cipher") + } + + if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + random.nextBytes(header.streamStartBytes) + } + + return random + } + + @Throws(PwDbOutputException::class) + override fun outputHeader(outputStream: OutputStream): PwDbHeaderV4 { + + val header = PwDbHeaderV4(mDatabaseV4) + setIVs(header) + + val pho = PwDbHeaderOutputV4(mDatabaseV4, header, outputStream) + try { + pho.output() + } catch (e: IOException) { + throw PwDbOutputException("Failed to output the header.", e) + } + + hashOfHeader = pho.hashOfHeader + headerHmac = pho.headerHmac + + return header + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun startGroup(group: PwGroupV4) { + xml.startTag(null, PwDatabaseV4XML.ElemGroup) + writeObject(PwDatabaseV4XML.ElemUuid, group.id) + writeObject(PwDatabaseV4XML.ElemName, group.title) + writeObject(PwDatabaseV4XML.ElemNotes, group.notes) + writeObject(PwDatabaseV4XML.ElemIcon, group.icon.iconId.toLong()) + + if (group.iconCustom != PwIconCustom.ZERO) { + writeObject(PwDatabaseV4XML.ElemCustomIconID, group.iconCustom.uuid) + } + + writeList(PwDatabaseV4XML.ElemTimes, group) + writeObject(PwDatabaseV4XML.ElemIsExpanded, group.isExpanded) + writeObject(PwDatabaseV4XML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence) + writeObject(PwDatabaseV4XML.ElemEnableAutoType, group.enableAutoType) + writeObject(PwDatabaseV4XML.ElemEnableSearching, group.enableSearching) + writeObject(PwDatabaseV4XML.ElemLastTopVisibleEntry, group.lastTopVisibleEntry) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun endGroup() { + xml.endTag(null, PwDatabaseV4XML.ElemGroup) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeEntry(entry: PwEntryV4, isHistory: Boolean) { + + xml.startTag(null, PwDatabaseV4XML.ElemEntry) + + writeObject(PwDatabaseV4XML.ElemUuid, entry.id) + writeObject(PwDatabaseV4XML.ElemIcon, entry.icon.iconId.toLong()) + + if (entry.iconCustom != PwIconCustom.ZERO) { + writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.iconCustom.uuid) + } + + writeObject(PwDatabaseV4XML.ElemFgColor, entry.foregroundColor) + writeObject(PwDatabaseV4XML.ElemBgColor, entry.backgroundColor) + writeObject(PwDatabaseV4XML.ElemOverrideUrl, entry.overrideURL) + writeObject(PwDatabaseV4XML.ElemTags, entry.tags) + + writeList(PwDatabaseV4XML.ElemTimes, entry) + + writeList(entry.fields.listOfAllFields, true) + writeList(entry.binaries) + writeList(PwDatabaseV4XML.ElemAutoType, entry.autoType) + + if (!isHistory) { + writeList(PwDatabaseV4XML.ElemHistory, entry.history, true) + } + // else entry.sizeOfHistory() == 0 + + xml.endTag(null, PwDatabaseV4XML.ElemEntry) + } + + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(key: String, value: ProtectedBinary) { + + xml.startTag(null, PwDatabaseV4XML.ElemBinary) + xml.startTag(null, PwDatabaseV4XML.ElemKey) + xml.text(safeXmlString(key)) + xml.endTag(null, PwDatabaseV4XML.ElemKey) + + xml.startTag(null, PwDatabaseV4XML.ElemValue) + val ref = mDatabaseV4.binPool.findKey(value) + val strRef = Integer.toString(ref) + + if (strRef != null) { + xml.attribute(null, PwDatabaseV4XML.AttrRef, strRef) + } else { + subWriteValue(value) + } + xml.endTag(null, PwDatabaseV4XML.ElemValue) + + xml.endTag(null, PwDatabaseV4XML.ElemBinary) + } + + /* + TODO Make with pipe + private void subWriteValue(ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException { + try (InputStream inputStream = value.getData()) { + if (inputStream == null) { + Log.e(TAG, "Can't write a null input stream."); + return; + } + + if (value.isProtected()) { + xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue); + + try (InputStream cypherInputStream = + IOUtil.pipe(inputStream, + o -> new org.spongycastle.crypto.io.CipherOutputStream(o, randomStream))) { + writeInputStreamInBase64(cypherInputStream); + } + + } else { + if (mDatabaseV4.getCompressionAlgorithm() == PwCompressionAlgorithm.Gzip) { + + xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue); + + try (InputStream gZipInputStream = + IOUtil.pipe(inputStream, GZIPOutputStream::new, (int) value.length())) { + writeInputStreamInBase64(gZipInputStream); + } + + } else { + writeInputStreamInBase64(inputStream); + } + } + } + } + + private void writeInputStreamInBase64(InputStream inputStream) throws IOException { + try (InputStream base64InputStream = + IOUtil.pipe(inputStream, + o -> new Base64OutputStream(o, DEFAULT))) { + MemUtil.readBytes(base64InputStream, + buffer -> xml.text(Arrays.toString(buffer))); + } + } + */ + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun subWriteValue(value: ProtectedBinary) { + + val valLength = value.length().toInt() + if (valLength > 0) { + val buffer = ByteArray(valLength) + if (valLength == value.getData()!!.read(buffer, 0, valLength)) { + + if (value.isProtected) { + xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue) + + val encoded = ByteArray(valLength) + randomStream!!.processBytes(buffer, 0, valLength, encoded, 0) + xml.text(String(Base64Coder.encode(encoded))) + + } else { + if (mDatabaseV4.compressionAlgorithm === PwCompressionAlgorithm.Gzip) { + xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue) + + val compressData = MemUtil.compress(buffer) + xml.text(String(Base64Coder.encode(compressData))) + + } else { + xml.text(String(Base64Coder.encode(buffer))) + } + } + } else { + Log.e(TAG, "Unable to read the stream of the protected binary") + } + } + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String, value: String, filterXmlChars: Boolean = false) { + var xmlString = value + + xml.startTag(null, name) + + if (filterXmlChars) { + xmlString = safeXmlString(xmlString) + } + + xml.text(xmlString) + xml.endTag(null, name) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String, value: Date?) { + if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { + writeObject(name, PwDatabaseV4XML.dateFormatter.get().format(value)) + } else { + val dt = DateTime(value) + val seconds = DateUtil.convertDateToKDBX4Time(dt) + val buf = LEDataOutputStream.writeLongBuf(seconds) + val b64 = String(Base64Coder.encode(buf)) + writeObject(name, b64) + } + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String, value: Long) { + writeObject(name, value.toString()) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String, value: Boolean?) { + val text: String = when { + value == null -> "null" + value -> PwDatabaseV4XML.ValTrue + else -> PwDatabaseV4XML.ValFalse + } + + writeObject(name, text) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String, uuid: UUID) { + val data = Types.UUIDtoBytes(uuid) + writeObject(name, String(Base64Coder.encode(data))) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String, keyName: String, keyValue: String, valueName: String, valueValue: String) { + xml.startTag(null, name) + + xml.startTag(null, keyName) + xml.text(safeXmlString(keyValue)) + xml.endTag(null, keyName) + + xml.startTag(null, valueName) + xml.text(safeXmlString(valueValue)) + xml.endTag(null, valueName) + + xml.endTag(null, name) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeList(name: String, autoType: AutoType) { + xml.startTag(null, name) + + writeObject(PwDatabaseV4XML.ElemAutoTypeEnabled, autoType.enabled) + writeObject(PwDatabaseV4XML.ElemAutoTypeObfuscation, autoType.obfuscationOptions) + + if (autoType.defaultSequence.isNotEmpty()) { + writeObject(PwDatabaseV4XML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true) + } + + for ((key, value) in autoType.entrySet()) { + writeObject(PwDatabaseV4XML.ElemAutoTypeItem, PwDatabaseV4XML.ElemWindow, key, PwDatabaseV4XML.ElemKeystrokeSequence, value) + } + + xml.endTag(null, name) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeList(strings: Map, isEntryString: Boolean) { + + for ((key, value) in strings) { + writeObject(key, value, isEntryString) + } + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(key: String, value: ProtectedString, isEntryString: Boolean) { + + xml.startTag(null, PwDatabaseV4XML.ElemString) + xml.startTag(null, PwDatabaseV4XML.ElemKey) + xml.text(safeXmlString(key)) + xml.endTag(null, PwDatabaseV4XML.ElemKey) + + xml.startTag(null, PwDatabaseV4XML.ElemValue) + var protect = value.isProtected + if (isEntryString) { + when (key) { + MemoryProtectionConfig.ProtectDefinition.TITLE_FIELD -> protect = mDatabaseV4.memoryProtection.protectTitle + MemoryProtectionConfig.ProtectDefinition.USERNAME_FIELD -> protect = mDatabaseV4.memoryProtection.protectUserName + MemoryProtectionConfig.ProtectDefinition.PASSWORD_FIELD -> protect = mDatabaseV4.memoryProtection.protectPassword + MemoryProtectionConfig.ProtectDefinition.URL_FIELD -> protect = mDatabaseV4.memoryProtection.protectUrl + MemoryProtectionConfig.ProtectDefinition.NOTES_FIELD -> protect = mDatabaseV4.memoryProtection.protectNotes + } + } + + if (protect) { + xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue) + + val data = value.toString().toByteArray(charset("UTF-8")) + val valLength = data.size + + if (valLength > 0) { + val encoded = ByteArray(valLength) + randomStream!!.processBytes(data, 0, valLength, encoded, 0) + xml.text(String(Base64Coder.encode(encoded))) + } + } else { + xml.text(safeXmlString(value.toString())) + } + + xml.endTag(null, PwDatabaseV4XML.ElemValue) + xml.endTag(null, PwDatabaseV4XML.ElemString) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String?, value: PwDeletedObject) { + assert(name != null) + + xml.startTag(null, name) + + writeObject(PwDatabaseV4XML.ElemUuid, value.uuid) + writeObject(PwDatabaseV4XML.ElemDeletionTime, value.deletionTime) + + xml.endTag(null, name) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeList(binaries: Map) { + for ((key, value) in binaries) { + writeObject(key, value) + } + } + + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeList(name: String?, value: List) { + assert(name != null) + + xml.startTag(null, name) + + for (pdo in value) { + writeObject(PwDatabaseV4XML.ElemDeletedObject, pdo) + } + + xml.endTag(null, name) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeList(name: String?, value: MemoryProtectionConfig) { + assert(name != null) + + xml.startTag(null, name) + + writeObject(PwDatabaseV4XML.ElemProtTitle, value.protectTitle) + writeObject(PwDatabaseV4XML.ElemProtUserName, value.protectUserName) + writeObject(PwDatabaseV4XML.ElemProtPassword, value.protectPassword) + writeObject(PwDatabaseV4XML.ElemProtURL, value.protectUrl) + writeObject(PwDatabaseV4XML.ElemProtNotes, value.protectNotes) + + xml.endTag(null, name) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeList(name: String?, customData: Map) { + assert(name != null) + + xml.startTag(null, name) + + for ((key, value) in customData) { + writeObject(PwDatabaseV4XML.ElemStringDictExItem, PwDatabaseV4XML.ElemKey, key, PwDatabaseV4XML.ElemValue, value) + + } + + xml.endTag(null, name) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeList(name: String?, it: NodeV4Interface) { + assert(name != null) + + xml.startTag(null, name) + + writeObject(PwDatabaseV4XML.ElemLastModTime, it.lastModificationTime.date) + writeObject(PwDatabaseV4XML.ElemCreationTime, it.creationTime.date) + writeObject(PwDatabaseV4XML.ElemLastAccessTime, it.lastAccessTime.date) + writeObject(PwDatabaseV4XML.ElemExpiryTime, it.expiryTime.date) + writeObject(PwDatabaseV4XML.ElemExpires, it.isExpires) + writeObject(PwDatabaseV4XML.ElemUsageCount, it.usageCount) + writeObject(PwDatabaseV4XML.ElemLocationChanged, it.locationChanged.date) + + xml.endTag(null, name) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeList(name: String?, value: List, isHistory: Boolean) { + assert(name != null) + + xml.startTag(null, name) + + for (entry in value) { + writeEntry(entry, isHistory) + } + + xml.endTag(null, name) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeCustomIconList() { + val customIcons = mDatabaseV4.customIcons + if (customIcons.size == 0) return + + xml.startTag(null, PwDatabaseV4XML.ElemCustomIcons) + + for (icon in customIcons) { + xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem) + + writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid) + writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String(Base64Coder.encode(icon.imageData))) + + xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem) + } + + xml.endTag(null, PwDatabaseV4XML.ElemCustomIcons) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeBinPool() { + xml.startTag(null, PwDatabaseV4XML.ElemBinaries) + + for ((key, value) in mDatabaseV4.binPool.entrySet()) { + xml.startTag(null, PwDatabaseV4XML.ElemBinary) + xml.attribute(null, PwDatabaseV4XML.AttrId, Integer.toString(key)) + + subWriteValue(value) + + xml.endTag(null, PwDatabaseV4XML.ElemBinary) + + } + + xml.endTag(null, PwDatabaseV4XML.ElemBinaries) + } + + private fun safeXmlString(text: String): String { + if (EmptyUtils.isNullOrEmpty(text)) { + return text + } + + val stringBuilder = StringBuilder() + var ch: Char + for (i in 0 until text.length) { + ch = text[i] + if ( + ch.toInt() in 0x20..0xD7FF || + ch.toInt() == 0x9 || ch.toInt() == 0xA || ch.toInt() == 0xD || + ch.toInt() in 0xE000..0xFFFD + ) { + stringBuilder.append(ch) + } + } + return stringBuilder.toString() + } + + companion object { + private val TAG = PwDbV4Output::class.java.name + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwEntryOutputV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwEntryOutputV3.kt new file mode 100644 index 000000000..a24c7c1e6 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwEntryOutputV3.kt @@ -0,0 +1,166 @@ +/* + * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.file.save + +import com.kunzisoft.keepass.database.element.PwEntryV3 +import com.kunzisoft.keepass.stream.LEDataOutputStream +import com.kunzisoft.keepass.utils.Types + +import java.io.IOException +import java.io.OutputStream + +class PwEntryOutputV3 +/** + * Output the PwGroupV3 to the stream + */ +(private val mPE: PwEntryV3, private val mOS: OutputStream) { + /** + * Returns the number of bytes written by the stream + * @return Number of bytes written + */ + var length: Long = 0 + private set + + //NOTE: Need be to careful about using ints. The actual type written to file is a unsigned int + @Throws(IOException::class) + fun output() { + + length += 134 // Length of fixed size fields + + // UUID + mOS.write(UUID_FIELD_TYPE) + mOS.write(UUID_FIELD_SIZE) + mOS.write(Types.UUIDtoBytes(mPE.id)) + + // Group ID + mOS.write(GROUPID_FIELD_TYPE) + mOS.write(LONG_FOUR) + mOS.write(LEDataOutputStream.writeIntBuf(mPE.parent!!.id)) + + // Image ID + mOS.write(IMAGEID_FIELD_TYPE) + mOS.write(LONG_FOUR) + mOS.write(LEDataOutputStream.writeIntBuf(mPE.icon.iconId)) + + // Title + //byte[] title = mPE.title.getBytes("UTF-8"); + mOS.write(TITLE_FIELD_TYPE) + val titleLen = Types.writeCString(mPE.title, mOS) + length += titleLen.toLong() + + // URL + mOS.write(URL_FIELD_TYPE) + val urlLen = Types.writeCString(mPE.url, mOS) + length += urlLen.toLong() + + // Username + mOS.write(USERNAME_FIELD_TYPE) + val userLen = Types.writeCString(mPE.username, mOS) + length += userLen.toLong() + + // Password + val password = mPE.passwordBytes + mOS.write(PASSWORD_FIELD_TYPE) + mOS.write(LEDataOutputStream.writeIntBuf(password.size + 1)) + mOS.write(password) + mOS.write(0) + length += (password.size + 1).toLong() + + // Additional + mOS.write(ADDITIONAL_FIELD_TYPE) + val addlLen = Types.writeCString(mPE.notes, mOS) + length += addlLen.toLong() + + // Create date + writeDate(CREATE_FIELD_TYPE, mPE.creationTime.cDate) + + // Modification date + writeDate(MOD_FIELD_TYPE, mPE.lastModificationTime.cDate) + + // Access date + writeDate(ACCESS_FIELD_TYPE, mPE.lastAccessTime.cDate) + + // Expiration date + writeDate(EXPIRE_FIELD_TYPE, mPE.expiryTime.cDate) + + // Binary desc + mOS.write(BINARY_DESC_FIELD_TYPE) + val descLen = Types.writeCString(mPE.binaryDesc, mOS) + length += descLen.toLong() + + // Binary data + val dataLen = writeByteArray(mPE.binaryData) + length += dataLen.toLong() + + // End + mOS.write(END_FIELD_TYPE) + mOS.write(ZERO_FIELD_SIZE) + } + + @Throws(IOException::class) + private fun writeByteArray(data: ByteArray?): Int { + val dataLen: Int = data?.size ?: 0 + mOS.write(BINARY_DATA_FIELD_TYPE) + mOS.write(LEDataOutputStream.writeIntBuf(dataLen)) + if (data != null) { + mOS.write(data) + } + + return dataLen + } + + @Throws(IOException::class) + private fun writeDate(type: ByteArray, date: ByteArray?) { + mOS.write(type) + mOS.write(DATE_FIELD_SIZE) + if (date != null) { + mOS.write(date) + } else { + mOS.write(ZERO_FIVE) + } + } + + companion object { + // Constants + val UUID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(1) + val GROUPID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(2) + val IMAGEID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(3) + val TITLE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(4) + val URL_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(5) + val USERNAME_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(6) + val PASSWORD_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(7) + val ADDITIONAL_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(8) + val CREATE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(9) + val MOD_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(10) + val ACCESS_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(11) + val EXPIRE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(12) + val BINARY_DESC_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(13) + val BINARY_DATA_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(14) + val END_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(0xFFFF) + val LONG_FOUR:ByteArray = LEDataOutputStream.writeIntBuf(4) + val UUID_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(16) + val DATE_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(5) + val IMAGEID_FIELD_SIZE:ByteArray = LONG_FOUR + val LEVEL_FIELD_SIZE:ByteArray = LONG_FOUR + val FLAGS_FIELD_SIZE:ByteArray = LONG_FOUR + val ZERO_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(0) + val ZERO_FIVE:ByteArray = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00) + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwGroupOutputV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwGroupOutputV3.kt new file mode 100644 index 000000000..247b9c5ae --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwGroupOutputV3.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.file.save + +import com.kunzisoft.keepass.database.element.PwGroupV3 +import com.kunzisoft.keepass.stream.LEDataOutputStream +import com.kunzisoft.keepass.utils.Types + +import java.io.IOException +import java.io.OutputStream + +class PwGroupOutputV3 +/** Output the PwGroupV3 to the stream + * @param pg + * @param os + */ +(private val mPG: PwGroupV3, private val mOS: OutputStream) { + + @Throws(IOException::class) + fun output() { + //NOTE: Need be to careful about using ints. The actual type written to file is a unsigned int, but most values can't be greater than 2^31, so it probably doesn't matter. + + // Group ID + mOS.write(GROUPID_FIELD_TYPE) + mOS.write(GROUPID_FIELD_SIZE) + mOS.write(LEDataOutputStream.writeIntBuf(mPG.id)) + + // Name + mOS.write(NAME_FIELD_TYPE) + Types.writeCString(mPG.title, mOS) + + // Create date + mOS.write(CREATE_FIELD_TYPE) + mOS.write(DATE_FIELD_SIZE) + mOS.write(mPG.creationTime.cDate) + + // Modification date + mOS.write(MOD_FIELD_TYPE) + mOS.write(DATE_FIELD_SIZE) + mOS.write(mPG.lastModificationTime.cDate) + + // Access date + mOS.write(ACCESS_FIELD_TYPE) + mOS.write(DATE_FIELD_SIZE) + mOS.write(mPG.lastAccessTime.cDate) + + // Expiration date + mOS.write(EXPIRE_FIELD_TYPE) + mOS.write(DATE_FIELD_SIZE) + mOS.write(mPG.expiryTime.cDate) + + // Image ID + mOS.write(IMAGEID_FIELD_TYPE) + mOS.write(IMAGEID_FIELD_SIZE) + mOS.write(LEDataOutputStream.writeIntBuf(mPG.icon.iconId)) + + // Level + mOS.write(LEVEL_FIELD_TYPE) + mOS.write(LEVEL_FIELD_SIZE) + mOS.write(LEDataOutputStream.writeUShortBuf(mPG.level)) + + // Flags + mOS.write(FLAGS_FIELD_TYPE) + mOS.write(FLAGS_FIELD_SIZE) + mOS.write(LEDataOutputStream.writeIntBuf(mPG.flags)) + + // End + mOS.write(END_FIELD_TYPE) + mOS.write(ZERO_FIELD_SIZE) + } + + companion object { + // Constants + val GROUPID_FIELD_TYPE: ByteArray = LEDataOutputStream.writeUShortBuf(1) + val NAME_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(2) + val CREATE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(3) + val MOD_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(4) + val ACCESS_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(5) + val EXPIRE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(6) + val IMAGEID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(7) + val LEVEL_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(8) + val FLAGS_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(9) + val END_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(0xFFFF) + val LONG_FOUR:ByteArray = LEDataOutputStream.writeIntBuf(4) + val GROUPID_FIELD_SIZE:ByteArray = LONG_FOUR + val DATE_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(5) + val IMAGEID_FIELD_SIZE:ByteArray = LONG_FOUR + val LEVEL_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(2) + val FLAGS_FIELD_SIZE:ByteArray = LONG_FOUR + val ZERO_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(0) + } + +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV3.java b/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV3.java deleted file mode 100644 index ad40de81d..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV3.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.database.save; - -import com.kunzisoft.keepass.database.element.PwDbHeaderV3; -import com.kunzisoft.keepass.stream.LEDataOutputStream; - -import java.io.IOException; -import java.io.OutputStream; - -public class PwDbHeaderOutputV3 { - private PwDbHeaderV3 mHeader; - private OutputStream mOS; - - public PwDbHeaderOutputV3(PwDbHeaderV3 header, OutputStream os) { - mHeader = header; - mOS = os; - } - - public void outputStart() throws IOException { - mOS.write(LEDataOutputStream.writeIntBuf(mHeader.signature1)); - mOS.write(LEDataOutputStream.writeIntBuf(mHeader.signature2)); - mOS.write(LEDataOutputStream.writeIntBuf(mHeader.flags)); - mOS.write(LEDataOutputStream.writeIntBuf(mHeader.version)); - mOS.write(mHeader.getMasterSeed()); - mOS.write(mHeader.getEncryptionIV()); - mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numGroups)); - mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numEntries)); - } - - public void outputContentHash() throws IOException { - mOS.write(mHeader.contentsHash); - } - - public void outputEnd() throws IOException { - mOS.write(mHeader.transformSeed); - mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numKeyEncRounds)); - } - - public void output() throws IOException { - outputStart(); - outputContentHash(); - outputEnd(); - } - - public void close() throws IOException { - mOS.close(); - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV4.java b/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV4.java deleted file mode 100644 index eee7b7b2a..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV4.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.database.save; - -import com.kunzisoft.keepass.collections.VariantDictionary; -import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters; -import com.kunzisoft.keepass.database.element.PwDatabaseV4; -import com.kunzisoft.keepass.database.element.PwDbHeader; -import com.kunzisoft.keepass.database.element.PwDbHeaderV4; -import com.kunzisoft.keepass.database.exception.PwDbOutputException; -import com.kunzisoft.keepass.stream.HmacBlockStream; -import com.kunzisoft.keepass.stream.LEDataOutputStream; -import com.kunzisoft.keepass.stream.MacOutputStream; -import com.kunzisoft.keepass.utils.Types; - -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; - -public class PwDbHeaderOutputV4 extends PwDbHeaderOutput { - private PwDbHeaderV4 header; - private LEDataOutputStream los; - private MacOutputStream mos; - private DigestOutputStream dos; - private PwDatabaseV4 db; - public byte[] headerHmac; - - private static byte[] EndHeaderValue = {'\r', '\n', '\r', '\n'}; - - public PwDbHeaderOutputV4(PwDatabaseV4 d, PwDbHeaderV4 h, OutputStream os) throws PwDbOutputException { - db = d; - header = h; - - MessageDigest md; - try { - md = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new PwDbOutputException("SHA-256 not implemented here."); - } - - try { - d.makeFinalKey(header.getMasterSeed()); - } catch (IOException e) { - throw new PwDbOutputException(e); - } - - Mac hmac; - try { - hmac = Mac.getInstance("HmacSHA256"); - SecretKeySpec signingKey = new SecretKeySpec(HmacBlockStream.GetHmacKey64(db.getHmacKey(), Types.ULONG_MAX_VALUE), "HmacSHA256"); - hmac.init(signingKey); - } catch (NoSuchAlgorithmException e) { - throw new PwDbOutputException(e); - } catch (InvalidKeyException e) { - throw new PwDbOutputException(e); - } - - dos = new DigestOutputStream(os, md); - mos = new MacOutputStream(dos, hmac); - los = new LEDataOutputStream(mos); - } - - public void output() throws IOException { - - los.writeUInt(PwDbHeader.PWM_DBSIG_1); - los.writeUInt(PwDbHeaderV4.DBSIG_2); - los.writeUInt(header.getVersion()); - - - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.CipherID, Types.UUIDtoBytes(db.getDataCipher())); - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.CompressionFlags, LEDataOutputStream.writeIntBuf(db.getCompressionAlgorithm().getId())); - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.MasterSeed, header.getMasterSeed()); - - if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.TransformSeed, header.getTransformSeed()); - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.TransformRounds, LEDataOutputStream.writeLongBuf(db.getNumberKeyEncryptionRounds())); - } else { - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.KdfParameters, KdfParameters.serialize(db.getKdfParameters())); - } - - if (header.getEncryptionIV().length > 0) { - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.EncryptionIV, header.getEncryptionIV()); - } - - if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey); - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes); - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.InnerRandomStreamID, LEDataOutputStream.writeIntBuf(header.innerRandomStream.getId())); - } - - if (db.containsPublicCustomData()) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - LEDataOutputStream los = new LEDataOutputStream(bos); - VariantDictionary.serialize(db.getPublicCustomData(), los); - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray()); - } - - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.EndOfHeader, EndHeaderValue); - - los.flush(); - hashOfHeader = dos.getMessageDigest().digest(); - headerHmac = mos.getMac(); - } - - private void writeHeaderField(byte fieldId, byte[] pbData) throws IOException { - // Write the field id - los.write(fieldId); - - if (pbData != null) { - writeHeaderFieldSize(pbData.length); - los.write(pbData); - } else { - writeHeaderFieldSize(0); - } - } - - private void writeHeaderFieldSize(int size) throws IOException { - if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { - los.writeUShort(size); - } else { - los.writeInt(size); - } - - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbInnerHeaderOutputV4.java b/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbInnerHeaderOutputV4.java deleted file mode 100644 index 851b72987..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbInnerHeaderOutputV4.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2017 Brian Pellin. - * - * This file is part of KeePass DX. - * - * KeePassDroid 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. - * - * KeePassDroid 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 KeePassDroid. If not, see . - * - */ -package com.kunzisoft.keepass.database.save; - -import com.kunzisoft.keepass.database.element.PwDatabaseV4; -import com.kunzisoft.keepass.database.element.PwDbHeaderV4; -import com.kunzisoft.keepass.database.security.ProtectedBinary; -import com.kunzisoft.keepass.stream.LEDataOutputStream; -import com.kunzisoft.keepass.utils.MemUtil; - -import java.io.IOException; -import java.io.OutputStream; - -public class PwDbInnerHeaderOutputV4 { - private PwDatabaseV4 db; - private PwDbHeaderV4 header; - private LEDataOutputStream los; - - public PwDbInnerHeaderOutputV4(PwDatabaseV4 db, PwDbHeaderV4 header, OutputStream os) { - this.db = db; - this.header = header; - - this.los = new LEDataOutputStream(os); - } - - public void output() throws IOException { - los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomStreamID); - los.writeInt(4); - los.writeInt(header.innerRandomStream.getId()); - - int streamKeySize = header.innerRandomStreamKey.length; - los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomstreamKey); - los.writeInt(streamKeySize); - los.write(header.innerRandomStreamKey); - - for (ProtectedBinary protectedBinary : db.getBinPool().binaries()) { - byte flag = PwDbHeaderV4.KdbxBinaryFlags.None; - if (protectedBinary.isProtected()) { - flag |= PwDbHeaderV4.KdbxBinaryFlags.Protected; - } - - los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary); - los.writeInt((int) protectedBinary.length() + 1); // TODO verify - los.write(flag); - - MemUtil.readBytes(protectedBinary.getData(), - buffer -> los.write(buffer)); - } - - los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader); - los.writeInt(0); - } - -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbOutput.java b/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbOutput.java deleted file mode 100644 index 6b8d5ab89..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbOutput.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.database.save; - -import com.kunzisoft.keepass.database.element.PwDbHeader; -import com.kunzisoft.keepass.database.exception.PwDbOutputException; - -import java.io.OutputStream; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -public abstract class PwDbOutput
{ - - protected OutputStream mOS; - - protected PwDbOutput(OutputStream os) { - mOS = os; - } - - protected SecureRandom setIVs(Header header) throws PwDbOutputException { - SecureRandom random; - try { - random = SecureRandom.getInstance("SHA1PRNG"); - } catch (NoSuchAlgorithmException e) { - throw new PwDbOutputException("Does not support secure random number generation."); - } - random.nextBytes(header.getEncryptionIV()); - random.nextBytes(header.getMasterSeed()); - - return random; - } - - public abstract void output() throws PwDbOutputException; - - public abstract Header outputHeader(OutputStream os) throws PwDbOutputException; - -} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV3Output.java b/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV3Output.java deleted file mode 100644 index b8cda3f05..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV3Output.java +++ /dev/null @@ -1,278 +0,0 @@ -/* -` * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.database.save; - -import com.kunzisoft.keepass.crypto.CipherFactory; -import com.kunzisoft.keepass.database.element.*; -import com.kunzisoft.keepass.database.exception.PwDbOutputException; -import com.kunzisoft.keepass.stream.LEDataOutputStream; -import com.kunzisoft.keepass.stream.NullOutputStream; - -import javax.crypto.Cipher; -import javax.crypto.CipherOutputStream; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.security.*; -import java.util.ArrayList; -import java.util.List; - -public class PwDbV3Output extends PwDbOutput { - - private PwDatabaseV3 mDatabaseV3; - private byte[] headerHashBlock; - - public PwDbV3Output(PwDatabaseV3 pm, OutputStream os) { - super(os); - mDatabaseV3 = pm; - } - - public byte[] getFinalKey(PwDbHeader header) throws PwDbOutputException { - try { - PwDbHeaderV3 h3 = (PwDbHeaderV3) header; - mDatabaseV3.makeFinalKey(h3.getMasterSeed(), h3.transformSeed, mDatabaseV3.getNumberKeyEncryptionRounds()); - return mDatabaseV3.getFinalKey(); - } catch (IOException e) { - throw new PwDbOutputException("Key creation failed.", e); - } - } - - @Override - public void output() throws PwDbOutputException { - // Before we output the header, we should sort our list of groups - // and remove any orphaned nodes that are no longer part of the tree hierarchy - sortGroupsForOutput(); - - PwDbHeader header = outputHeader(mOS); - - byte[] finalKey = getFinalKey(header); - - Cipher cipher; - try { - if (mDatabaseV3.getEncryptionAlgorithm() == PwEncryptionAlgorithm.AESRijndael) { - cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding"); - } else if (mDatabaseV3.getEncryptionAlgorithm() == PwEncryptionAlgorithm.Twofish){ - cipher = CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING"); - } else { - throw new Exception(); - } - } catch (Exception e) { - throw new PwDbOutputException("Algorithm not supported.", e); - } - - try { - cipher.init( Cipher.ENCRYPT_MODE, new SecretKeySpec(finalKey, "AES" ), new IvParameterSpec(header.getEncryptionIV()) ); - CipherOutputStream cos = new CipherOutputStream(mOS, cipher); - BufferedOutputStream bos = new BufferedOutputStream(cos); - outputPlanGroupAndEntries(bos); - bos.flush(); - bos.close(); - - } catch (InvalidKeyException e) { - throw new PwDbOutputException("Invalid key", e); - } catch (InvalidAlgorithmParameterException e) { - throw new PwDbOutputException("Invalid algorithm parameter.", e); - } catch (IOException e) { - throw new PwDbOutputException("Failed to output final encrypted part.", e); - } - } - - @Override - protected SecureRandom setIVs(PwDbHeaderV3 header) throws PwDbOutputException { - SecureRandom random = super.setIVs(header); - random.nextBytes(header.transformSeed); - return random; - } - - @Override - public PwDbHeaderV3 outputHeader(OutputStream os) throws PwDbOutputException { - // Build header - PwDbHeaderV3 header = new PwDbHeaderV3(); - header.signature1 = PwDbHeader.PWM_DBSIG_1; - header.signature2 = PwDbHeaderV3.DBSIG_2; - header.flags = PwDbHeaderV3.FLAG_SHA2; - - if ( mDatabaseV3.getEncryptionAlgorithm() == PwEncryptionAlgorithm.AESRijndael) { - header.flags |= PwDbHeaderV3.FLAG_RIJNDAEL; - } else if ( mDatabaseV3.getEncryptionAlgorithm() == PwEncryptionAlgorithm.Twofish ) { - header.flags |= PwDbHeaderV3.FLAG_TWOFISH; - } else { - throw new PwDbOutputException("Unsupported algorithm."); - } - - header.version = PwDbHeaderV3.DBVER_DW; - header.numGroups = mDatabaseV3.numberOfGroups(); - header.numEntries = mDatabaseV3.numberOfEntries(); - header.numKeyEncRounds = (int) mDatabaseV3.getNumberKeyEncryptionRounds(); - - setIVs(header); - - // Content checksum - MessageDigest md = null; - try { - md = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new PwDbOutputException("SHA-256 not implemented here.", e); - } - - // Header checksum - MessageDigest headerDigest; - try { - headerDigest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new PwDbOutputException("SHA-256 not implemented here.", e); - } - NullOutputStream nos; - nos = new NullOutputStream(); - DigestOutputStream headerDos = new DigestOutputStream(nos, headerDigest); - - // Output header for the purpose of calculating the header checksum - PwDbHeaderOutputV3 pho = new PwDbHeaderOutputV3(header, headerDos); - try { - pho.outputStart(); - pho.outputEnd(); - headerDos.flush(); - } catch (IOException e) { - throw new PwDbOutputException(e); - } - byte[] headerHash = headerDigest.digest(); - headerHashBlock = getHeaderHashBuffer(headerHash); - - // Output database for the purpose of calculating the content checksum - nos = new NullOutputStream(); - DigestOutputStream dos = new DigestOutputStream(nos, md); - BufferedOutputStream bos = new BufferedOutputStream(dos); - try { - outputPlanGroupAndEntries(bos); - bos.flush(); - bos.close(); - } catch (IOException e) { - throw new PwDbOutputException("Failed to generate checksum.", e); - } - - header.contentsHash = md.digest(); - - // Output header for real output, containing content hash - pho = new PwDbHeaderOutputV3(header, os); - try { - pho.outputStart(); - dos.on(false); - pho.outputContentHash(); - dos.on(true); - pho.outputEnd(); - dos.flush(); - } catch (IOException e) { - throw new PwDbOutputException(e); - } - - return header; - } - - public void outputPlanGroupAndEntries(OutputStream os) throws PwDbOutputException { - LEDataOutputStream los = new LEDataOutputStream(os); - - if (useHeaderHash() && headerHashBlock != null) { - try { - los.writeUShort(0x0000); - los.writeInt(headerHashBlock.length); - los.write(headerHashBlock); - } catch (IOException e) { - throw new PwDbOutputException("Failed to output header hash.", e); - } - } - - // Groups - for (PwGroupV3 group: mDatabaseV3.getGroupIndexes()) { - PwGroupOutputV3 pgo = new PwGroupOutputV3(group, os); - try { - pgo.output(); - } catch (IOException e) { - throw new PwDbOutputException("Failed to output a tree", e); - } - } - - // Entries - for (PwEntryV3 entry : mDatabaseV3.getEntryIndexes()) { - PwEntryOutputV3 peo = new PwEntryOutputV3(entry, os); - try { - peo.output(); - } catch (IOException e) { - throw new PwDbOutputException("Failed to output an entry.", e); - } - } - } - - private void sortGroupsForOutput() { - List groupList = new ArrayList<>(); - // Rebuild list according to coalation sorting order removing any orphaned groups - for (PwGroupV3 rootGroup : mDatabaseV3.getRootGroups()) { - sortGroup(rootGroup, groupList); - } - mDatabaseV3.setGroupIndexes(groupList); - } - - private void sortGroup(PwGroupV3 group, List groupList) { - // Add current tree - groupList.add(group); - - // Recurse over children - for (PwGroupV3 childGroup : group.getChildGroups()) { - sortGroup(childGroup, groupList); - } - } - - private byte[] getHeaderHashBuffer(byte[] headerDigest) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - writeExtData(headerDigest, baos); - return baos.toByteArray(); - } catch (IOException e) { - return null; - } - } - - private void writeExtData(byte[] headerDigest, OutputStream os) throws IOException { - LEDataOutputStream los = new LEDataOutputStream(os); - - writeExtDataField(los, 0x0001, headerDigest, headerDigest.length); - byte[] headerRandom = new byte[32]; - SecureRandom rand = new SecureRandom(); - rand.nextBytes(headerRandom); - writeExtDataField(los, 0x0002, headerRandom, headerRandom.length); - writeExtDataField(los, 0xFFFF, null, 0); - - } - - private void writeExtDataField(LEDataOutputStream los, int fieldType, byte[] data, int fieldSize) throws IOException { - los.writeUShort(fieldType); - los.writeInt(fieldSize); - if (data != null) { - los.write(data); - } - - } - - protected boolean useHeaderHash() { - return true; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV4Output.java b/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV4Output.java deleted file mode 100644 index 1b9e6f8f1..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV4Output.java +++ /dev/null @@ -1,765 +0,0 @@ -/* - * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.database.save; - -import android.util.Log; -import android.util.Xml; -import biz.source_code.base64Coder.Base64Coder; -import com.kunzisoft.keepass.crypto.CipherFactory; -import com.kunzisoft.keepass.crypto.PwStreamCipherFactory; -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.database.*; -import com.kunzisoft.keepass.database.element.*; -import com.kunzisoft.keepass.database.exception.PwDbOutputException; -import com.kunzisoft.keepass.database.exception.UnknownKDF; -import com.kunzisoft.keepass.database.security.ProtectedBinary; -import com.kunzisoft.keepass.database.security.ProtectedString; -import com.kunzisoft.keepass.stream.HashedBlockOutputStream; -import com.kunzisoft.keepass.stream.HmacBlockOutputStream; -import com.kunzisoft.keepass.stream.LEDataOutputStream; -import com.kunzisoft.keepass.utils.DateUtil; -import com.kunzisoft.keepass.utils.EmptyUtils; -import com.kunzisoft.keepass.utils.MemUtil; -import com.kunzisoft.keepass.utils.Types; -import org.joda.time.DateTime; -import org.spongycastle.crypto.StreamCipher; -import org.xmlpull.v1.XmlSerializer; - -import javax.crypto.Cipher; -import javax.crypto.CipherOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.*; -import java.util.Map.Entry; -import java.util.zip.GZIPOutputStream; - -public class PwDbV4Output extends PwDbOutput { - private static final String TAG = PwDbV4Output.class.getName(); - - private PwDatabaseV4 mPM; - private StreamCipher randomStream; - private XmlSerializer xml; - private PwDbHeaderV4 header; - private byte[] hashOfHeader; - private byte[] headerHmac; - private CipherEngine engine = null; - - public PwDbV4Output(PwDatabaseV4 pm, OutputStream os) { - super(os); - this.mPM = pm; - } - - @Override - public void output() throws PwDbOutputException { - - try { - try { - engine = CipherFactory.getInstance(mPM.getDataCipher()); - } catch (NoSuchAlgorithmException e) { - throw new PwDbOutputException("No such cipher", e); - } - - header = outputHeader(mOS); - - OutputStream osPlain; - if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { - CipherOutputStream cos = attachStreamEncryptor(header, mOS); - cos.write(header.streamStartBytes); - - osPlain = new HashedBlockOutputStream(cos); - } else { - mOS.write(hashOfHeader); - mOS.write(headerHmac); - - HmacBlockOutputStream hbos = new HmacBlockOutputStream(mOS, mPM.getHmacKey()); - osPlain = attachStreamEncryptor(header, hbos); - } - - OutputStream osXml; - try { - if (mPM.getCompressionAlgorithm() == PwCompressionAlgorithm.Gzip) { - osXml = new GZIPOutputStream(osPlain); - } else { - osXml = osPlain; - } - - if (header.getVersion() >= PwDbHeaderV4.FILE_VERSION_32_4) { - PwDbInnerHeaderOutputV4 ihOut = new PwDbInnerHeaderOutputV4(mPM, header, osXml); - ihOut.output(); - } - - outputDatabase(osXml); - osXml.close(); - } catch (IllegalArgumentException e) { - throw new PwDbOutputException(e); - } catch (IllegalStateException e) { - throw new PwDbOutputException(e); - } - } catch (IOException e) { - throw new PwDbOutputException(e); - } - } - - private void outputDatabase(OutputStream os) throws IllegalArgumentException, IllegalStateException, IOException { - - xml = Xml.newSerializer(); - - xml.setOutput(os, "UTF-8"); - xml.startDocument("UTF-8", true); - - xml.startTag(null, PwDatabaseV4XML.ElemDocNode); - - writeMeta(); - - PwGroupV4 root = mPM.getRootGroup(); - xml.startTag(null, PwDatabaseV4XML.ElemRoot); - startGroup(root); - Stack groupStack = new Stack<>(); - groupStack.push(root); - - if (!root.doForEachChild( - new NodeHandler() { - @Override - public boolean operate(PwEntryV4 entry) { - try { - writeEntry(entry, false); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - - return true; - } - }, - new NodeHandler() { - @Override - public boolean operate(PwGroupV4 node) { - while (true) { - try { - if (node.getParent() == groupStack.peek()) { - groupStack.push(node); - startGroup(node); - break; - } else { - groupStack.pop(); - if (groupStack.size() <= 0) return false; - endGroup(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - return true; - } - })) throw new RuntimeException("Writing groups failed"); - - while (groupStack.size() > 1) { - xml.endTag(null, PwDatabaseV4XML.ElemGroup); - groupStack.pop(); - } - - endGroup(); - - writeList(PwDatabaseV4XML.ElemDeletedObjects, mPM.getDeletedObjects()); - - xml.endTag(null, PwDatabaseV4XML.ElemRoot); - - xml.endTag(null, PwDatabaseV4XML.ElemDocNode); - xml.endDocument(); - - } - - private void writeMeta() throws IllegalArgumentException, IllegalStateException, IOException { - xml.startTag(null, PwDatabaseV4XML.ElemMeta); - - writeObject(PwDatabaseV4XML.ElemGenerator, mPM.localizedAppName); - - if (hashOfHeader != null) { - writeObject(PwDatabaseV4XML.ElemHeaderHash, String.valueOf(Base64Coder.encode(hashOfHeader))); - } - - writeObject(PwDatabaseV4XML.ElemDbName, mPM.getName(), true); - writeObject(PwDatabaseV4XML.ElemDbNameChanged, mPM.getNameChanged().getDate()); - writeObject(PwDatabaseV4XML.ElemDbDesc, mPM.getDescription(), true); - writeObject(PwDatabaseV4XML.ElemDbDescChanged, mPM.getDescriptionChanged().getDate()); - writeObject(PwDatabaseV4XML.ElemDbDefaultUser, mPM.getDefaultUserName(), true); - writeObject(PwDatabaseV4XML.ElemDbDefaultUserChanged, mPM.getDefaultUserNameChanged().getDate()); - writeObject(PwDatabaseV4XML.ElemDbMntncHistoryDays, mPM.getMaintenanceHistoryDays()); - writeObject(PwDatabaseV4XML.ElemDbColor, mPM.getColor()); - writeObject(PwDatabaseV4XML.ElemDbKeyChanged, mPM.getKeyLastChanged().getDate()); - writeObject(PwDatabaseV4XML.ElemDbKeyChangeRec, mPM.getKeyChangeRecDays()); - writeObject(PwDatabaseV4XML.ElemDbKeyChangeForce, mPM.getKeyChangeForceDays()); - - writeList(PwDatabaseV4XML.ElemMemoryProt, mPM.getMemoryProtection()); - - writeCustomIconList(); - - writeObject(PwDatabaseV4XML.ElemRecycleBinEnabled, mPM.isRecycleBinEnabled()); - writeObject(PwDatabaseV4XML.ElemRecycleBinUuid, mPM.getRecycleBinUUID()); - writeObject(PwDatabaseV4XML.ElemRecycleBinChanged, mPM.getRecycleBinChanged()); - writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroup, mPM.getEntryTemplatesGroup()); - writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroupChanged, mPM.getEntryTemplatesGroupChanged().getDate()); - writeObject(PwDatabaseV4XML.ElemHistoryMaxItems, mPM.getHistoryMaxItems()); - writeObject(PwDatabaseV4XML.ElemHistoryMaxSize, mPM.getHistoryMaxSize()); - writeObject(PwDatabaseV4XML.ElemLastSelectedGroup, mPM.getLastSelectedGroup()); - writeObject(PwDatabaseV4XML.ElemLastTopVisibleGroup, mPM.getLastTopVisibleGroup()); - - if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { - writeBinPool(); - } - writeList(PwDatabaseV4XML.ElemCustomData, mPM.getCustomData()); - - xml.endTag(null, PwDatabaseV4XML.ElemMeta); - - } - - private CipherOutputStream attachStreamEncryptor(PwDbHeaderV4 header, OutputStream os) throws PwDbOutputException { - Cipher cipher; - try { - //mPM.makeFinalKey(header.masterSeed, mPM.kdfParameters); - - cipher = engine.getCipher(Cipher.ENCRYPT_MODE, mPM.getFinalKey(), header.getEncryptionIV()); - } catch (Exception e) { - throw new PwDbOutputException("Invalid algorithm.", e); - } - - return new CipherOutputStream(os, cipher); - } - - @Override - protected SecureRandom setIVs(PwDbHeaderV4 header) throws PwDbOutputException { - SecureRandom random = super.setIVs(header); - random.nextBytes(header.getMasterSeed()); - - int ivLength = engine.ivLength(); - if (ivLength != header.getEncryptionIV().length) { - header.setEncryptionIV(new byte[ivLength]); - } - random.nextBytes(header.getEncryptionIV()); - - if (mPM.getKdfParameters() == null) { - mPM.setKdfParameters(KdfFactory.aesKdf.getDefaultParameters()); - } - - try { - KdfEngine kdf = KdfFactory.getEngineV4(mPM.getKdfParameters()); - kdf.randomize(mPM.getKdfParameters()); - } catch (UnknownKDF unknownKDF) { - Log.e(TAG, "Unable to retrieve header", unknownKDF); - } - - if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { - header.innerRandomStream = CrsAlgorithm.Salsa20; - header.innerRandomStreamKey = new byte[32]; - } else { - header.innerRandomStream = CrsAlgorithm.ChaCha20; - header.innerRandomStreamKey = new byte[64]; - } - random.nextBytes(header.innerRandomStreamKey); - - randomStream = PwStreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey); - if (randomStream == null) { - throw new PwDbOutputException("Invalid random cipher"); - } - - if ( header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { - random.nextBytes(header.streamStartBytes); - } - - return random; - } - - @Override - public PwDbHeaderV4 outputHeader(OutputStream os) throws PwDbOutputException { - - PwDbHeaderV4 header = new PwDbHeaderV4(mPM); - setIVs(header); - - PwDbHeaderOutputV4 pho = new PwDbHeaderOutputV4(mPM, header, os); - try { - pho.output(); - } catch (IOException e) { - throw new PwDbOutputException("Failed to output the header.", e); - } - - hashOfHeader = pho.getHashOfHeader(); - headerHmac = pho.headerHmac; - - return header; - } - - private void startGroup(PwGroupV4 group) throws IllegalArgumentException, IllegalStateException, IOException { - xml.startTag(null, PwDatabaseV4XML.ElemGroup); - writeObject(PwDatabaseV4XML.ElemUuid, group.getId()); - writeObject(PwDatabaseV4XML.ElemName, group.getTitle()); - writeObject(PwDatabaseV4XML.ElemNotes, group.getNotes()); - writeObject(PwDatabaseV4XML.ElemIcon, group.getIcon().getIconId()); - - if (!group.getIconCustom().equals(PwIconCustom.Companion.getZERO())) { - writeObject(PwDatabaseV4XML.ElemCustomIconID, group.getIconCustom().getUuid()); - } - - writeList(PwDatabaseV4XML.ElemTimes, group); - writeObject(PwDatabaseV4XML.ElemIsExpanded, group.isExpanded()); - writeObject(PwDatabaseV4XML.ElemGroupDefaultAutoTypeSeq, group.getDefaultAutoTypeSequence()); - writeObject(PwDatabaseV4XML.ElemEnableAutoType, group.getEnableAutoType()); - writeObject(PwDatabaseV4XML.ElemEnableSearching, group.getEnableSearching()); - writeObject(PwDatabaseV4XML.ElemLastTopVisibleEntry, group.getLastTopVisibleEntry()); - - } - - private void endGroup() throws IllegalArgumentException, IllegalStateException, IOException { - xml.endTag(null, PwDatabaseV4XML.ElemGroup); - } - - private void writeEntry(PwEntryV4 entry, boolean isHistory) throws IllegalArgumentException, IllegalStateException, IOException { - assert(entry != null); - - xml.startTag(null, PwDatabaseV4XML.ElemEntry); - - writeObject(PwDatabaseV4XML.ElemUuid, entry.getId()); - writeObject(PwDatabaseV4XML.ElemIcon, entry.getIcon().getIconId()); - - if (!entry.getIconCustom().equals(PwIconCustom.Companion.getZERO())) { - writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.getIconCustom().getUuid()); - } - - writeObject(PwDatabaseV4XML.ElemFgColor, entry.getForegroundColor()); - writeObject(PwDatabaseV4XML.ElemBgColor, entry.getBackgroundColor()); - writeObject(PwDatabaseV4XML.ElemOverrideUrl, entry.getOverrideURL()); - writeObject(PwDatabaseV4XML.ElemTags, entry.getTags()); - - writeList(PwDatabaseV4XML.ElemTimes, entry); - - writeList(entry.getFields().getListOfAllFields(), true); - writeList(entry.getBinaries()); - writeList(PwDatabaseV4XML.ElemAutoType, entry.getAutoType()); - - if (!isHistory) { - writeList(PwDatabaseV4XML.ElemHistory, entry.getHistory(), true); - } - // else entry.sizeOfHistory() == 0 - - xml.endTag(null, PwDatabaseV4XML.ElemEntry); - } - - - private void writeObject(String key, ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException { - assert(key != null && value != null); - - xml.startTag(null, PwDatabaseV4XML.ElemBinary); - xml.startTag(null, PwDatabaseV4XML.ElemKey); - xml.text(safeXmlString(key)); - xml.endTag(null, PwDatabaseV4XML.ElemKey); - - xml.startTag(null, PwDatabaseV4XML.ElemValue); - int ref = mPM.getBinPool().findKey(value); - String strRef = Integer.toString(ref); - - if (strRef != null) { - xml.attribute(null, PwDatabaseV4XML.AttrRef, strRef); - } - else { - subWriteValue(value); - } - xml.endTag(null, PwDatabaseV4XML.ElemValue); - - xml.endTag(null, PwDatabaseV4XML.ElemBinary); - } - - /* - TODO Make with pipe - private void subWriteValue(ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException { - try (InputStream inputStream = value.getData()) { - if (inputStream == null) { - Log.e(TAG, "Can't write a null input stream."); - return; - } - - if (value.isProtected()) { - xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue); - - try (InputStream cypherInputStream = - IOUtil.pipe(inputStream, - o -> new org.spongycastle.crypto.io.CipherOutputStream(o, randomStream))) { - writeInputStreamInBase64(cypherInputStream); - } - - } else { - if (mPM.getCompressionAlgorithm() == PwCompressionAlgorithm.Gzip) { - - xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue); - - try (InputStream gZipInputStream = - IOUtil.pipe(inputStream, GZIPOutputStream::new, (int) value.length())) { - writeInputStreamInBase64(gZipInputStream); - } - - } else { - writeInputStreamInBase64(inputStream); - } - } - } - } - - private void writeInputStreamInBase64(InputStream inputStream) throws IOException { - try (InputStream base64InputStream = - IOUtil.pipe(inputStream, - o -> new Base64OutputStream(o, DEFAULT))) { - MemUtil.readBytes(base64InputStream, - buffer -> xml.text(Arrays.toString(buffer))); - } - } - //*/ - - //* - private void subWriteValue(ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException { - - int valLength = (int) value.length(); - if (valLength > 0) { - byte[] buffer = new byte[valLength]; - if (valLength == value.getData().read(buffer, 0, valLength)) { - - if (value.isProtected()) { - xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue); - - byte[] encoded = new byte[valLength]; - randomStream.processBytes(buffer, 0, valLength, encoded, 0); - xml.text(String.valueOf(Base64Coder.encode(encoded))); - - } else { - if (mPM.getCompressionAlgorithm() == PwCompressionAlgorithm.Gzip) { - xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue); - - byte[] compressData = MemUtil.compress(buffer); - xml.text(String.valueOf(Base64Coder.encode(compressData))); - - } else { - xml.text(String.valueOf(Base64Coder.encode(buffer))); - } - } - } else { - Log.e(TAG, "Unable to read the stream of the protected binary"); - } - } - } - //*/ - - private void writeObject(String name, String value, boolean filterXmlChars) throws IllegalArgumentException, IllegalStateException, IOException { - assert(name != null && value != null); - - xml.startTag(null, name); - - if (filterXmlChars) { - value = safeXmlString(value); - } - - xml.text(value); - xml.endTag(null, name); - } - - private void writeObject(String name, String value) throws IllegalArgumentException, IllegalStateException, IOException { - writeObject(name, value, false); - } - - private void writeObject(String name, Date value) throws IllegalArgumentException, IllegalStateException, IOException { - if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { - writeObject(name, PwDatabaseV4XML.dateFormatter.get().format(value)); - } else { - DateTime dt = new DateTime(value); - long seconds = DateUtil.convertDateToKDBX4Time(dt); - byte[] buf = LEDataOutputStream.writeLongBuf(seconds); - String b64 = new String(Base64Coder.encode(buf)); - writeObject(name, b64); - } - - } - - private void writeObject(String name, long value) throws IllegalArgumentException, IllegalStateException, IOException { - writeObject(name, String.valueOf(value)); - } - - private void writeObject(String name, Boolean value) throws IllegalArgumentException, IllegalStateException, IOException { - String text; - if (value == null) { - text = "null"; - } - else if (value) { - text = PwDatabaseV4XML.ValTrue; - } - else { - text = PwDatabaseV4XML.ValFalse; - } - - writeObject(name, text); - } - - private void writeObject(String name, UUID uuid) throws IllegalArgumentException, IllegalStateException, IOException { - byte[] data = Types.UUIDtoBytes(uuid); - writeObject(name, String.valueOf(Base64Coder.encode(data))); - } - - private void writeObject(String name, String keyName, String keyValue, String valueName, String valueValue) throws IllegalArgumentException, IllegalStateException, IOException { - xml.startTag(null, name); - - xml.startTag(null, keyName); - xml.text(safeXmlString(keyValue)); - xml.endTag(null, keyName); - - xml.startTag(null, valueName); - xml.text(safeXmlString(valueValue)); - xml.endTag(null, valueName); - - xml.endTag(null, name); - } - - private void writeList(String name, AutoType autoType) throws IllegalArgumentException, IllegalStateException, IOException { - assert(name != null && autoType != null); - - xml.startTag(null, name); - - writeObject(PwDatabaseV4XML.ElemAutoTypeEnabled, autoType.getEnabled()); - writeObject(PwDatabaseV4XML.ElemAutoTypeObfuscation, autoType.getObfuscationOptions()); - - if (autoType.getDefaultSequence().length() > 0) { - writeObject(PwDatabaseV4XML.ElemAutoTypeDefaultSeq, autoType.getDefaultSequence(), true); - } - - for (Entry pair : autoType.entrySet()) { - writeObject(PwDatabaseV4XML.ElemAutoTypeItem, PwDatabaseV4XML.ElemWindow, pair.getKey(), PwDatabaseV4XML.ElemKeystrokeSequence, pair.getValue()); - } - - xml.endTag(null, name); - - } - - private void writeList(Map strings, boolean isEntryString) throws IllegalArgumentException, IllegalStateException, IOException { - assert (strings != null); - - for (Entry pair : strings.entrySet()) { - writeObject(pair.getKey(), pair.getValue(), isEntryString); - - } - - } - - private void writeObject(String key, ProtectedString value, boolean isEntryString) throws IllegalArgumentException, IllegalStateException, IOException { - assert(key !=null && value != null); - - xml.startTag(null, PwDatabaseV4XML.ElemString); - xml.startTag(null, PwDatabaseV4XML.ElemKey); - xml.text(safeXmlString(key)); - xml.endTag(null, PwDatabaseV4XML.ElemKey); - - xml.startTag(null, PwDatabaseV4XML.ElemValue); - boolean protect = value.isProtected(); - if (isEntryString) { - if (key.equals(MemoryProtectionConfig.ProtectDefinition.TITLE_FIELD)) { - protect = mPM.getMemoryProtection().getProtectTitle(); - } - else if (key.equals(MemoryProtectionConfig.ProtectDefinition.USERNAME_FIELD)) { - protect = mPM.getMemoryProtection().getProtectUserName(); - } - else if (key.equals(MemoryProtectionConfig.ProtectDefinition.PASSWORD_FIELD)) { - protect = mPM.getMemoryProtection().getProtectPassword(); - } - else if (key.equals(MemoryProtectionConfig.ProtectDefinition.URL_FIELD)) { - protect = mPM.getMemoryProtection().getProtectUrl(); - } - else if (key.equals(MemoryProtectionConfig.ProtectDefinition.NOTES_FIELD)) { - protect = mPM.getMemoryProtection().getProtectNotes(); - } - } - - if (protect) { - xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue); - - byte[] data = value.toString().getBytes("UTF-8"); - int valLength = data.length; - - if (valLength > 0) { - byte[] encoded = new byte[valLength]; - randomStream.processBytes(data, 0, valLength, encoded, 0); - xml.text(String.valueOf(Base64Coder.encode(encoded))); - } - } - else { - xml.text(safeXmlString(value.toString())); - } - - xml.endTag(null, PwDatabaseV4XML.ElemValue); - xml.endTag(null, PwDatabaseV4XML.ElemString); - - } - - private void writeObject(String name, PwDeletedObject value) throws IllegalArgumentException, IllegalStateException, IOException { - assert(name != null && value != null); - - xml.startTag(null, name); - - writeObject(PwDatabaseV4XML.ElemUuid, value.getUuid()); - writeObject(PwDatabaseV4XML.ElemDeletionTime, value.getDeletionTime()); - - xml.endTag(null, name); - } - - private void writeList(Map binaries) throws IllegalArgumentException, IllegalStateException, IOException { - assert(binaries != null); - - for (Entry pair : binaries.entrySet()) { - writeObject(pair.getKey(), pair.getValue()); - } - } - - - private void writeList(String name, List value) throws IllegalArgumentException, IllegalStateException, IOException { - assert(name != null && value != null); - - xml.startTag(null, name); - - for (PwDeletedObject pdo : value) { - writeObject(PwDatabaseV4XML.ElemDeletedObject, pdo); - } - - xml.endTag(null, name); - - } - - private void writeList(String name, MemoryProtectionConfig value) throws IllegalArgumentException, IllegalStateException, IOException { - assert(name != null && value != null); - - xml.startTag(null, name); - - writeObject(PwDatabaseV4XML.ElemProtTitle, value.getProtectTitle()); - writeObject(PwDatabaseV4XML.ElemProtUserName, value.getProtectUserName()); - writeObject(PwDatabaseV4XML.ElemProtPassword, value.getProtectPassword()); - writeObject(PwDatabaseV4XML.ElemProtURL, value.getProtectUrl()); - writeObject(PwDatabaseV4XML.ElemProtNotes, value.getProtectNotes()); - - xml.endTag(null, name); - - } - - private void writeList(String name, Map customData) throws IllegalArgumentException, IllegalStateException, IOException { - assert(name != null && customData != null); - - xml.startTag(null, name); - - for (Entry pair : customData.entrySet()) { - writeObject(PwDatabaseV4XML.ElemStringDictExItem, PwDatabaseV4XML.ElemKey, pair.getKey(), PwDatabaseV4XML.ElemValue, pair.getValue()); - - } - - xml.endTag(null, name); - - } - - private void writeList(String name, NodeV4Interface it) throws IllegalArgumentException, IllegalStateException, IOException { - assert(name != null && it != null); - - xml.startTag(null, name); - - writeObject(PwDatabaseV4XML.ElemLastModTime, it.getLastModificationTime().getDate()); - writeObject(PwDatabaseV4XML.ElemCreationTime, it.getCreationTime().getDate()); - writeObject(PwDatabaseV4XML.ElemLastAccessTime, it.getLastAccessTime().getDate()); - writeObject(PwDatabaseV4XML.ElemExpiryTime, it.getExpiryTime().getDate()); - writeObject(PwDatabaseV4XML.ElemExpires, it.isExpires()); - writeObject(PwDatabaseV4XML.ElemUsageCount, it.getUsageCount()); - writeObject(PwDatabaseV4XML.ElemLocationChanged, it.getLocationChanged().getDate()); - - xml.endTag(null, name); - } - - private void writeList(String name, List value, boolean isHistory) throws IllegalArgumentException, IllegalStateException, IOException { - assert(name != null && value != null); - - xml.startTag(null, name); - - for (PwEntryV4 entry : value) { - writeEntry(entry, isHistory); - } - - xml.endTag(null, name); - - } - - private void writeCustomIconList() throws IllegalArgumentException, IllegalStateException, IOException { - List customIcons = mPM.getCustomIcons(); - if (customIcons.size() == 0) return; - - xml.startTag(null, PwDatabaseV4XML.ElemCustomIcons); - - for (PwIconCustom icon : customIcons) { - xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem); - - writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.getUuid()); - writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String.valueOf(Base64Coder.encode(icon.getImageData()))); - - xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem); - } - - xml.endTag(null, PwDatabaseV4XML.ElemCustomIcons); - } - - private void writeBinPool() throws IllegalArgumentException, IllegalStateException, IOException { - xml.startTag(null, PwDatabaseV4XML.ElemBinaries); - - for (Entry pair : mPM.getBinPool().entrySet()) { - xml.startTag(null, PwDatabaseV4XML.ElemBinary); - xml.attribute(null, PwDatabaseV4XML.AttrId, Integer.toString(pair.getKey())); - - subWriteValue(pair.getValue()); - - xml.endTag(null, PwDatabaseV4XML.ElemBinary); - - } - - xml.endTag(null, PwDatabaseV4XML.ElemBinaries); - - } - - private String safeXmlString(String text) { - if (EmptyUtils.isNullOrEmpty(text)) { - return text; - } - - StringBuilder sb = new StringBuilder(); - - char ch; - for (int i = 0; i < text.length(); i++) { - ch = text.charAt(i); - - if(((ch >= 0x20) && (ch <= 0xD7FF)) || - (ch == 0x9) || (ch == 0xA) || (ch == 0xD) || - ((ch >= 0xE000) && (ch <= 0xFFFD))) { - - sb.append(ch); - } - - } - - return sb.toString(); - } - -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/save/PwEntryOutputV3.java b/app/src/main/java/com/kunzisoft/keepass/database/save/PwEntryOutputV3.java deleted file mode 100644 index 37f37590e..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/save/PwEntryOutputV3.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.database.save; - -import com.kunzisoft.keepass.database.element.PwEntryV3; -import com.kunzisoft.keepass.stream.LEDataOutputStream; -import com.kunzisoft.keepass.utils.Types; - -import java.io.IOException; -import java.io.OutputStream; - -public class PwEntryOutputV3 { - // Constants - public static final byte[] UUID_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(1); - public static final byte[] GROUPID_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(2); - public static final byte[] IMAGEID_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(3); - public static final byte[] TITLE_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(4); - public static final byte[] URL_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(5); - public static final byte[] USERNAME_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(6); - public static final byte[] PASSWORD_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(7); - public static final byte[] ADDITIONAL_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(8); - public static final byte[] CREATE_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(9); - public static final byte[] MOD_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(10); - public static final byte[] ACCESS_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(11); - public static final byte[] EXPIRE_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(12); - public static final byte[] BINARY_DESC_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(13); - public static final byte[] BINARY_DATA_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(14); - public static final byte[] END_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(0xFFFF); - public static final byte[] LONG_FOUR = LEDataOutputStream.writeIntBuf(4); - public static final byte[] UUID_FIELD_SIZE = LEDataOutputStream.writeIntBuf(16); - public static final byte[] DATE_FIELD_SIZE = LEDataOutputStream.writeIntBuf(5); - public static final byte[] IMAGEID_FIELD_SIZE = LONG_FOUR; - public static final byte[] LEVEL_FIELD_SIZE = LONG_FOUR; - public static final byte[] FLAGS_FIELD_SIZE = LONG_FOUR; - public static final byte[] ZERO_FIELD_SIZE = LEDataOutputStream.writeIntBuf(0); - public static final byte[] ZERO_FIVE = {0x00, 0x00, 0x00, 0x00, 0x00}; - public static final byte[] TEST = {0x33, 0x33, 0x33, 0x33}; - - private OutputStream mOS; - private PwEntryV3 mPE; - private long outputBytes = 0; - - /** Output the PwGroupV3 to the stream - * @param pe - * @param os - */ - public PwEntryOutputV3(PwEntryV3 pe, OutputStream os) { - mPE = pe; - mOS = os; - } - - //NOTE: Need be to careful about using ints. The actual type written to file is a unsigned int - public void output() throws IOException { - - outputBytes += 134; // Length of fixed size fields - - // UUID - mOS.write(UUID_FIELD_TYPE); - mOS.write(UUID_FIELD_SIZE); - mOS.write(Types.UUIDtoBytes(mPE.getId())); - - // Group ID - mOS.write(GROUPID_FIELD_TYPE); - mOS.write(LONG_FOUR); - mOS.write(LEDataOutputStream.writeIntBuf(mPE.getParent().getId())); - - // Image ID - mOS.write(IMAGEID_FIELD_TYPE); - mOS.write(LONG_FOUR); - mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIcon().getIconId())); - - // Title - //byte[] title = mPE.title.getBytes("UTF-8"); - mOS.write(TITLE_FIELD_TYPE); - int titleLen = Types.writeCString(mPE.getTitle(), mOS); - outputBytes += titleLen; - - // URL - mOS.write(URL_FIELD_TYPE); - int urlLen = Types.writeCString(mPE.getUrl(), mOS); - outputBytes += urlLen; - - // Username - mOS.write(USERNAME_FIELD_TYPE); - int userLen = Types.writeCString(mPE.getUsername(), mOS); - outputBytes += userLen; - - // Password - byte[] password = mPE.getPasswordBytes(); - mOS.write(PASSWORD_FIELD_TYPE); - mOS.write(LEDataOutputStream.writeIntBuf(password.length+1)); - mOS.write(password); - mOS.write(0); - outputBytes += password.length + 1; - - // Additional - mOS.write(ADDITIONAL_FIELD_TYPE); - int addlLen = Types.writeCString(mPE.getNotes(), mOS); - outputBytes += addlLen; - - // Create date - writeDate(CREATE_FIELD_TYPE, mPE.getCreationTime().getCDate()); - - // Modification date - writeDate(MOD_FIELD_TYPE, mPE.getLastModificationTime().getCDate()); - - // Access date - writeDate(ACCESS_FIELD_TYPE, mPE.getLastAccessTime().getCDate()); - - // Expiration date - writeDate(EXPIRE_FIELD_TYPE, mPE.getExpiryTime().getCDate()); - - // Binary desc - mOS.write(BINARY_DESC_FIELD_TYPE); - int descLen = Types.writeCString(mPE.getBinaryDesc(), mOS); - outputBytes += descLen; - - // Binary data - int dataLen = writeByteArray(mPE.getBinaryData()); - outputBytes += dataLen; - - // End - mOS.write(END_FIELD_TYPE); - mOS.write(ZERO_FIELD_SIZE); - } - - private int writeByteArray(byte[] data) throws IOException { - int dataLen; - if ( data != null ) { - dataLen = data.length; - } else { - dataLen = 0; - } - mOS.write(BINARY_DATA_FIELD_TYPE); - mOS.write(LEDataOutputStream.writeIntBuf(dataLen)); - if ( data != null ) { - mOS.write(data); - } - - return dataLen; - } - - private void writeDate(byte[] type, byte[] date) throws IOException { - mOS.write(type); - mOS.write(DATE_FIELD_SIZE); - if ( date != null ) { - mOS.write(date); - } else { - mOS.write(ZERO_FIVE); - } - } - - /** Returns the number of bytes written by the stream - * @return Number of bytes written - */ - public long getLength() { - return outputBytes; - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/save/PwGroupOutputV3.java b/app/src/main/java/com/kunzisoft/keepass/database/save/PwGroupOutputV3.java deleted file mode 100644 index 0f3f2384e..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/save/PwGroupOutputV3.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.database.save; - -import com.kunzisoft.keepass.database.element.PwGroupV3; -import com.kunzisoft.keepass.stream.LEDataOutputStream; -import com.kunzisoft.keepass.utils.Types; - -import java.io.IOException; -import java.io.OutputStream; - -public class PwGroupOutputV3 { - // Constants - public static final byte[] GROUPID_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(1); - public static final byte[] NAME_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(2); - public static final byte[] CREATE_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(3); - public static final byte[] MOD_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(4); - public static final byte[] ACCESS_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(5); - public static final byte[] EXPIRE_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(6); - public static final byte[] IMAGEID_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(7); - public static final byte[] LEVEL_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(8); - public static final byte[] FLAGS_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(9); - public static final byte[] END_FIELD_TYPE = LEDataOutputStream.writeUShortBuf(0xFFFF); - public static final byte[] LONG_FOUR = LEDataOutputStream.writeIntBuf(4); - public static final byte[] GROUPID_FIELD_SIZE = LONG_FOUR; - public static final byte[] DATE_FIELD_SIZE = LEDataOutputStream.writeIntBuf(5); - public static final byte[] IMAGEID_FIELD_SIZE = LONG_FOUR; - public static final byte[] LEVEL_FIELD_SIZE = LEDataOutputStream.writeIntBuf(2); - public static final byte[] FLAGS_FIELD_SIZE = LONG_FOUR; - public static final byte[] ZERO_FIELD_SIZE = LEDataOutputStream.writeIntBuf(0); - - private OutputStream mOS; - private PwGroupV3 mPG; - - /** Output the PwGroupV3 to the stream - * @param pg - * @param os - */ - public PwGroupOutputV3(PwGroupV3 pg, OutputStream os) { - mPG = pg; - mOS = os; - } - - public void output() throws IOException { - //NOTE: Need be to careful about using ints. The actual type written to file is a unsigned int, but most values can't be greater than 2^31, so it probably doesn't matter. - - // Group ID - mOS.write(GROUPID_FIELD_TYPE); - mOS.write(GROUPID_FIELD_SIZE); - mOS.write(LEDataOutputStream.writeIntBuf(mPG.getId())); - - // Name - mOS.write(NAME_FIELD_TYPE); - Types.writeCString(mPG.getTitle(), mOS); - - // Create date - mOS.write(CREATE_FIELD_TYPE); - mOS.write(DATE_FIELD_SIZE); - mOS.write(mPG.getCreationTime().getCDate()); - - // Modification date - mOS.write(MOD_FIELD_TYPE); - mOS.write(DATE_FIELD_SIZE); - mOS.write(mPG.getLastModificationTime().getCDate()); - - // Access date - mOS.write(ACCESS_FIELD_TYPE); - mOS.write(DATE_FIELD_SIZE); - mOS.write(mPG.getLastAccessTime().getCDate()); - - // Expiration date - mOS.write(EXPIRE_FIELD_TYPE); - mOS.write(DATE_FIELD_SIZE); - mOS.write(mPG.getExpiryTime().getCDate()); - - // Image ID - mOS.write(IMAGEID_FIELD_TYPE); - mOS.write(IMAGEID_FIELD_SIZE); - mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIcon().getIconId())); - - // Level - mOS.write(LEVEL_FIELD_TYPE); - mOS.write(LEVEL_FIELD_SIZE); - mOS.write(LEDataOutputStream.writeUShortBuf(mPG.getLevel())); - - // Flags - mOS.write(FLAGS_FIELD_TYPE); - mOS.write(FLAGS_FIELD_SIZE); - mOS.write(LEDataOutputStream.writeIntBuf(mPG.getFlags())); - - // End - mOS.write(END_FIELD_TYPE); - mOS.write(ZERO_FIELD_SIZE); - } - -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/search/EntrySearchHandlerV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/EntrySearchHandlerV4.kt index 4b5d3b41b..c649098f7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/search/EntrySearchHandlerV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/EntrySearchHandlerV4.kt @@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database.search import com.kunzisoft.keepass.database.NodeHandler import com.kunzisoft.keepass.database.element.PwEntryV4 -import com.kunzisoft.keepass.database.iterator.EntrySearchStringIteratorV4 +import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorV4 import com.kunzisoft.keepass.utils.StringUtil import com.kunzisoft.keepass.utils.UuidUtil diff --git a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchDbHelper.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchDbHelper.kt index a963eae38..ee3445b34 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchDbHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchDbHelper.kt @@ -23,9 +23,9 @@ import com.kunzisoft.keepass.database.NodeHandler import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.EntryVersioned import com.kunzisoft.keepass.database.element.GroupVersioned -import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator -import com.kunzisoft.keepass.database.iterator.EntrySearchStringIteratorV3 -import com.kunzisoft.keepass.database.iterator.EntrySearchStringIteratorV4 +import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIterator +import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorV3 +import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorV4 import java.util.* class SearchDbHelper(private val isOmitBackup: Boolean) { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/iterator/EntrySearchStringIterator.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIterator.kt similarity index 93% rename from app/src/main/java/com/kunzisoft/keepass/database/iterator/EntrySearchStringIterator.kt rename to app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIterator.kt index 43c6bb9a1..d98d5a65e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/iterator/EntrySearchStringIterator.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIterator.kt @@ -17,6 +17,6 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.iterator +package com.kunzisoft.keepass.database.search.iterator abstract class EntrySearchStringIterator : Iterator \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/iterator/EntrySearchStringIteratorV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV3.kt similarity index 97% rename from app/src/main/java/com/kunzisoft/keepass/database/iterator/EntrySearchStringIteratorV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV3.kt index 2ff81e61b..cf786054d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/iterator/EntrySearchStringIteratorV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV3.kt @@ -17,7 +17,7 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.iterator +package com.kunzisoft.keepass.database.search.iterator import com.kunzisoft.keepass.database.element.PwEntryV3 import com.kunzisoft.keepass.database.search.SearchParameters diff --git a/app/src/main/java/com/kunzisoft/keepass/database/iterator/EntrySearchStringIteratorV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV4.kt similarity index 95% rename from app/src/main/java/com/kunzisoft/keepass/database/iterator/EntrySearchStringIteratorV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV4.kt index 3886040fb..71f7453e2 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/iterator/EntrySearchStringIteratorV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV4.kt @@ -17,11 +17,11 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.iterator +package com.kunzisoft.keepass.database.search.iterator import com.kunzisoft.keepass.database.element.PwEntryV4 import com.kunzisoft.keepass.database.search.SearchParametersV4 -import com.kunzisoft.keepass.database.security.ProtectedString +import com.kunzisoft.keepass.database.element.security.ProtectedString import java.util.* import kotlin.collections.Map.Entry diff --git a/app/src/main/java/com/kunzisoft/keepass/database/security/ProtectedBinary.java b/app/src/main/java/com/kunzisoft/keepass/database/security/ProtectedBinary.java deleted file mode 100644 index 91ba1e415..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/security/ProtectedBinary.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2018 Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.database.security; - -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; - -public class ProtectedBinary implements Parcelable { - - private static final String TAG = ProtectedBinary.class.getName(); - - private boolean protect; - private byte[] data; - private File dataFile; - private int size; - - public boolean isProtected() { - return protect; - } - - public long length() { - if (data != null) - return data.length; - if (dataFile != null) - return size; - return 0; - } - - /** - * Empty protected binary - */ - public ProtectedBinary() { - this.protect = false; - this.data = null; - this.dataFile = null; - this.size = 0; - } - - public ProtectedBinary(ProtectedBinary protectedBinary) { - this.protect = protectedBinary.protect; - this.data = protectedBinary.data; - this.dataFile = protectedBinary.dataFile; - this.size = protectedBinary.size; - } - - public ProtectedBinary(boolean enableProtection, byte[] data) { - this.protect = enableProtection; - this.data = data; - this.dataFile = null; - if (data != null) - this.size = data.length; - else - this.size = 0; - } - - public ProtectedBinary(boolean enableProtection, File dataFile, int size) { - this.protect = enableProtection; - this.data = null; - this.dataFile = dataFile; - this.size = size; - } - - private ProtectedBinary(Parcel in) { - protect = in.readByte() != 0; - in.readByteArray(data); - dataFile = new File(in.readString()); - size = in.readInt(); - } - - public InputStream getData() throws IOException { - if (data != null) - return new ByteArrayInputStream(data); - else if (dataFile != null) - return new FileInputStream(dataFile); - else - return null; - } - - public void clear() { - data = null; - if (dataFile != null && !dataFile.delete()) - Log.e(TAG, "Unable to delete temp file " + dataFile.getAbsolutePath()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ProtectedBinary that = (ProtectedBinary) o; - return protect == that.protect && - size == that.size && - Arrays.equals(data, that.data) && - dataFile != null && - dataFile.equals(that.dataFile); - } - - @Override - public int hashCode() { - - int result = 0; - result = 31 * result + (protect ? 1 : 0); - result = 31 * result + dataFile.hashCode(); - result = 31 * result + Integer.valueOf(size).hashCode(); - result = 31 * result + Arrays.hashCode(data); - return result; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeByte((byte) (protect ? 1 : 0)); - dest.writeByteArray(data); - dest.writeString(dataFile.getAbsolutePath()); - dest.writeInt(size); - } - - public static final Creator CREATOR = new Creator() { - @Override - public ProtectedBinary createFromParcel(Parcel in) { - return new ProtectedBinary(in); - } - - @Override - public ProtectedBinary[] newArray(int size) { - return new ProtectedBinary[size]; - } - }; - -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/security/ProtectedString.java b/app/src/main/java/com/kunzisoft/keepass/database/security/ProtectedString.java deleted file mode 100644 index c0decee4a..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/security/ProtectedString.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.database.security; - -import android.os.Parcel; -import android.os.Parcelable; - -public class ProtectedString implements Parcelable { - - private boolean protect; - private String string; - - public ProtectedString() { - this(false, ""); - } - - public ProtectedString(ProtectedString toCopy) { - this.protect = toCopy.protect; - this.string = toCopy.string; - } - - public ProtectedString(boolean enableProtection, String string) { - this.protect = enableProtection; - this.string = string; - } - - public ProtectedString(Parcel in) { - protect = in.readByte() != 0; - string = in.readString(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeByte((byte) (protect ? 1 : 0)); - dest.writeString(string); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public ProtectedString createFromParcel(Parcel in) { - return new ProtectedString(in); - } - - @Override - public ProtectedString[] newArray(int size) { - return new ProtectedString[size]; - } - }; - - public boolean isProtected() { - return protect; - } - - public int length() { - if (string == null) { - return 0; - } - - return string.length(); - } - - @Override - public String toString() { - return string; - } - -} diff --git a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.java b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.java index 6da530de5..9eeea705d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.java +++ b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.java @@ -33,7 +33,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.kunzisoft.keepass.R; -import com.kunzisoft.keepass.database.security.ProtectedString; +import com.kunzisoft.keepass.database.element.security.ProtectedString; import com.kunzisoft.keepass.utils.Util; import java.text.DateFormat; diff --git a/app/src/main/java/com/kunzisoft/keepass/view/EntryCustomField.java b/app/src/main/java/com/kunzisoft/keepass/view/EntryCustomField.java index ecdafd6ef..56491e61d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/EntryCustomField.java +++ b/app/src/main/java/com/kunzisoft/keepass/view/EntryCustomField.java @@ -28,7 +28,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.kunzisoft.keepass.R; -import com.kunzisoft.keepass.database.security.ProtectedString; +import com.kunzisoft.keepass.database.element.security.ProtectedString; import com.kunzisoft.keepass.utils.Util; public class EntryCustomField extends LinearLayout { diff --git a/app/src/main/java/com/kunzisoft/keepass/view/EntryCustomFieldProtected.java b/app/src/main/java/com/kunzisoft/keepass/view/EntryCustomFieldProtected.java index 117b100ef..59c353a6c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/EntryCustomFieldProtected.java +++ b/app/src/main/java/com/kunzisoft/keepass/view/EntryCustomFieldProtected.java @@ -23,7 +23,7 @@ import android.content.Context; import android.text.method.PasswordTransformationMethod; import android.util.AttributeSet; -import com.kunzisoft.keepass.database.security.ProtectedString; +import com.kunzisoft.keepass.database.element.security.ProtectedString; public class EntryCustomFieldProtected extends EntryCustomField{ diff --git a/app/src/main/java/com/kunzisoft/keepass/view/EntryEditCustomField.java b/app/src/main/java/com/kunzisoft/keepass/view/EntryEditCustomField.java index 85a7a8094..2a3ef0bcb 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/EntryEditCustomField.java +++ b/app/src/main/java/com/kunzisoft/keepass/view/EntryEditCustomField.java @@ -31,7 +31,7 @@ import android.widget.RelativeLayout; import android.widget.TextView; import com.kunzisoft.keepass.R; -import com.kunzisoft.keepass.database.security.ProtectedString; +import com.kunzisoft.keepass.database.element.security.ProtectedString; import com.kunzisoft.keepass.utils.Util; public class EntryEditCustomField extends RelativeLayout {