mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Kotlinized database package
This commit is contained in:
@@ -42,11 +42,10 @@ import com.getkeepsafe.taptargetview.TapTargetView;
|
|||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.keepass.activities.lock.LockingHideActivity;
|
import com.kunzisoft.keepass.activities.lock.LockingHideActivity;
|
||||||
import com.kunzisoft.keepass.app.App;
|
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.Database;
|
||||||
import com.kunzisoft.keepass.database.element.EntryVersioned;
|
import com.kunzisoft.keepass.database.element.EntryVersioned;
|
||||||
import com.kunzisoft.keepass.database.element.PwNodeId;
|
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.NotificationCopyingService;
|
||||||
import com.kunzisoft.keepass.notifications.NotificationField;
|
import com.kunzisoft.keepass.notifications.NotificationField;
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||||
|
|||||||
@@ -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.AfterActionNodeFinishRunnable;
|
||||||
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable;
|
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable;
|
||||||
import com.kunzisoft.keepass.database.element.*;
|
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.GeneratePasswordDialogFragment;
|
||||||
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
|
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||||
|
|||||||
@@ -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.MoveGroupRunnable;
|
||||||
import com.kunzisoft.keepass.database.action.node.UpdateGroupRunnable;
|
import com.kunzisoft.keepass.database.action.node.UpdateGroupRunnable;
|
||||||
import com.kunzisoft.keepass.database.element.*;
|
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.AssignMasterKeyDialogFragment;
|
||||||
import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment;
|
import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment;
|
||||||
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
|
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database
|
package com.kunzisoft.keepass.crypto
|
||||||
|
|
||||||
enum class CrsAlgorithm constructor(val id: Int) {
|
enum class CrsAlgorithm constructor(val id: Int) {
|
||||||
|
|
||||||
@@ -19,8 +19,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto;
|
package com.kunzisoft.keepass.crypto;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.CrsAlgorithm;
|
|
||||||
|
|
||||||
import org.spongycastle.crypto.StreamCipher;
|
import org.spongycastle.crypto.StreamCipher;
|
||||||
import org.spongycastle.crypto.engines.ChaCha7539Engine;
|
import org.spongycastle.crypto.engines.ChaCha7539Engine;
|
||||||
import org.spongycastle.crypto.engines.Salsa20Engine;
|
import org.spongycastle.crypto.engines.Salsa20Engine;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import android.database.MatrixCursor
|
|||||||
import android.provider.BaseColumns
|
import android.provider.BaseColumns
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.PwEntryV4
|
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(
|
class ExtraFieldCursor : MatrixCursor(arrayOf(
|
||||||
_ID,
|
_ID,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.element
|
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 java.util.HashMap
|
||||||
import kotlin.collections.Map.Entry
|
import kotlin.collections.Map.Entry
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ import com.kunzisoft.keepass.database.NodeHandler
|
|||||||
import com.kunzisoft.keepass.database.cursor.EntryCursorV3
|
import com.kunzisoft.keepass.database.cursor.EntryCursorV3
|
||||||
import com.kunzisoft.keepass.database.cursor.EntryCursorV4
|
import com.kunzisoft.keepass.database.cursor.EntryCursorV4
|
||||||
import com.kunzisoft.keepass.database.exception.*
|
import com.kunzisoft.keepass.database.exception.*
|
||||||
import com.kunzisoft.keepass.database.load.ImporterV3
|
import com.kunzisoft.keepass.database.file.PwDbHeaderV3
|
||||||
import com.kunzisoft.keepass.database.load.ImporterV4
|
import com.kunzisoft.keepass.database.file.PwDbHeaderV4
|
||||||
import com.kunzisoft.keepass.database.save.PwDbV3Output
|
import com.kunzisoft.keepass.database.file.load.ImporterV3
|
||||||
import com.kunzisoft.keepass.database.save.PwDbV4Output
|
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.database.search.SearchDbHelper
|
||||||
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.kunzisoft.keepass.database.element
|
|||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.database.element
|
|||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
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 com.kunzisoft.keepass.utils.MemUtil
|
||||||
|
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ import com.kunzisoft.keepass.crypto.engine.CipherEngine;
|
|||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters;
|
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.InvalidKeyFileException;
|
||||||
import com.kunzisoft.keepass.database.exception.UnknownKDF;
|
import com.kunzisoft.keepass.database.exception.UnknownKDF;
|
||||||
|
import com.kunzisoft.keepass.database.file.PwCompressionAlgorithm;
|
||||||
|
|
||||||
import org.w3c.dom.*;
|
import org.w3c.dom.*;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
|
|
||||||
Derived from
|
|
||||||
|
|
||||||
KeePass for J2ME
|
|
||||||
|
|
||||||
|
|
||||||
Copyright 2007 Naomaru Itoi <nao@phoneid.org>
|
|
||||||
|
|
||||||
This file was derived from
|
|
||||||
|
|
||||||
Java clone of KeePass - A KeePass file viewer for Java
|
|
||||||
Copyright 2006 Bill Zwicky <billzwicky@users.sourceforge.net>
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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<PwGroupV4> {
|
|
||||||
|
|
||||||
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<PwEntryV4> {
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,8 +21,8 @@ package com.kunzisoft.keepass.database.element
|
|||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedBinary
|
import com.kunzisoft.keepass.database.element.security.ProtectedBinary
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.utils.MemUtil
|
import com.kunzisoft.keepass.utils.MemUtil
|
||||||
import com.kunzisoft.keepass.utils.SprEngineV4
|
import com.kunzisoft.keepass.utils.SprEngineV4
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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<ProtectedBinary> = object : Parcelable.Creator<ProtectedBinary> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): ProtectedBinary {
|
||||||
|
return ProtectedBinary(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<ProtectedBinary?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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<ProtectedString> = object : Parcelable.Creator<ProtectedString> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): ProtectedString {
|
||||||
|
return ProtectedString(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<ProtectedString?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
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
|
// 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
|
// since we won't do arithmetic on these values (also unlikely to
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.element
|
package com.kunzisoft.keepass.database.file
|
||||||
|
|
||||||
abstract class PwDbHeader {
|
abstract class PwDbHeader {
|
||||||
|
|
||||||
@@ -32,7 +32,6 @@ abstract class PwDbHeader {
|
|||||||
var encryptionIV = ByteArray(16)
|
var encryptionIV = ByteArray(16)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val PWM_DBSIG_1 = -0x655d26fd
|
const val PWM_DBSIG_1 = -0x655d26fd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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<PwGroupV4>() {
|
||||||
|
|
||||||
|
internal var hasCustomData = false
|
||||||
|
|
||||||
|
override fun operate(node: PwGroupV4): Boolean {
|
||||||
|
if (node.containsCustomData()) {
|
||||||
|
hasCustomData = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class EntryHasCustomData : NodeHandler<PwEntryV4>() {
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
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.element.PwDatabase
|
||||||
import com.kunzisoft.keepass.database.exception.InvalidDBException
|
import com.kunzisoft.keepass.database.exception.InvalidDBException
|
||||||
@@ -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
|
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 android.util.Log
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||||
import com.kunzisoft.keepass.database.element.*
|
import com.kunzisoft.keepass.database.element.*
|
||||||
import com.kunzisoft.keepass.database.exception.*
|
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.LEDataInputStream
|
||||||
import com.kunzisoft.keepass.stream.NullOutputStream
|
import com.kunzisoft.keepass.stream.NullOutputStream
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||||
@@ -17,20 +17,21 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.load
|
package com.kunzisoft.keepass.database.file.load
|
||||||
|
|
||||||
import biz.source_code.base64Coder.Base64Coder
|
import biz.source_code.base64Coder.Base64Coder
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||||
import com.kunzisoft.keepass.crypto.PwStreamCipherFactory
|
import com.kunzisoft.keepass.crypto.PwStreamCipherFactory
|
||||||
import com.kunzisoft.keepass.crypto.engine.CipherEngine
|
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.element.*
|
||||||
import com.kunzisoft.keepass.database.exception.ArcFourException
|
import com.kunzisoft.keepass.database.exception.ArcFourException
|
||||||
import com.kunzisoft.keepass.database.exception.InvalidDBException
|
import com.kunzisoft.keepass.database.exception.InvalidDBException
|
||||||
import com.kunzisoft.keepass.database.exception.InvalidPasswordException
|
import com.kunzisoft.keepass.database.exception.InvalidPasswordException
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedBinary
|
import com.kunzisoft.keepass.database.file.PwDbHeaderV4
|
||||||
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.stream.BetterCipherInputStream
|
import com.kunzisoft.keepass.stream.BetterCipherInputStream
|
||||||
import com.kunzisoft.keepass.stream.HashedBlockInputStream
|
import com.kunzisoft.keepass.stream.HashedBlockInputStream
|
||||||
import com.kunzisoft.keepass.stream.HmacBlockInputStream
|
import com.kunzisoft.keepass.stream.HmacBlockInputStream
|
||||||
@@ -101,7 +102,7 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
|
|||||||
mDatabase.binPool.clear()
|
mDatabase.binPool.clear()
|
||||||
|
|
||||||
val hh = header.loadFromFile(databaseInputStream)
|
val hh = header.loadFromFile(databaseInputStream)
|
||||||
version = header.getVersion()
|
version = header.version
|
||||||
|
|
||||||
hashOfHeader = hh.hash
|
hashOfHeader = hh.hash
|
||||||
val pbHeader = hh.header
|
val pbHeader = hh.header
|
||||||
@@ -17,11 +17,9 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.save;
|
package com.kunzisoft.keepass.database.file.save
|
||||||
|
|
||||||
public class PwDbHeaderOutput {
|
open class PwDbHeaderOutput {
|
||||||
protected byte[] hashOfHeader = null;
|
var hashOfHeader: ByteArray? = null
|
||||||
|
protected set
|
||||||
public byte[] getHashOfHeader() { return hashOfHeader; }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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<Header : PwDbHeader> 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
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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<PwDbHeaderV3>(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<PwGroupV3>()
|
||||||
|
// 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<PwGroupV3>) {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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<PwDbHeaderV4>(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<PwGroupV4>()
|
||||||
|
groupStack.push(root)
|
||||||
|
|
||||||
|
if (!root.doForEachChild(
|
||||||
|
object : NodeHandler<PwEntryV4>() {
|
||||||
|
override fun operate(node: PwEntryV4): Boolean {
|
||||||
|
try {
|
||||||
|
writeEntry(node, false)
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
throw RuntimeException(ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
object : NodeHandler<PwGroupV4>() {
|
||||||
|
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<String, ProtectedString>, 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<String, ProtectedBinary>) {
|
||||||
|
for ((key, value) in binaries) {
|
||||||
|
writeObject(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||||
|
private fun writeList(name: String?, value: List<PwDeletedObject>) {
|
||||||
|
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<String, String>) {
|
||||||
|
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<PwEntryV4>, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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<Header extends PwDbHeader> {
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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<PwDbHeaderV3> {
|
|
||||||
|
|
||||||
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<PwGroupV3> 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<PwGroupV3> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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<PwDbHeaderV4> {
|
|
||||||
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<PwGroupV4> groupStack = new Stack<>();
|
|
||||||
groupStack.push(root);
|
|
||||||
|
|
||||||
if (!root.doForEachChild(
|
|
||||||
new NodeHandler<PwEntryV4>() {
|
|
||||||
@Override
|
|
||||||
public boolean operate(PwEntryV4 entry) {
|
|
||||||
try {
|
|
||||||
writeEntry(entry, false);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new NodeHandler<PwGroupV4>() {
|
|
||||||
@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<String, String> pair : autoType.entrySet()) {
|
|
||||||
writeObject(PwDatabaseV4XML.ElemAutoTypeItem, PwDatabaseV4XML.ElemWindow, pair.getKey(), PwDatabaseV4XML.ElemKeystrokeSequence, pair.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
xml.endTag(null, name);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeList(Map<String, ProtectedString> strings, boolean isEntryString) throws IllegalArgumentException, IllegalStateException, IOException {
|
|
||||||
assert (strings != null);
|
|
||||||
|
|
||||||
for (Entry<String, ProtectedString> 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<String, ProtectedBinary> binaries) throws IllegalArgumentException, IllegalStateException, IOException {
|
|
||||||
assert(binaries != null);
|
|
||||||
|
|
||||||
for (Entry<String, ProtectedBinary> pair : binaries.entrySet()) {
|
|
||||||
writeObject(pair.getKey(), pair.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void writeList(String name, List<PwDeletedObject> 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<String, String> customData) throws IllegalArgumentException, IllegalStateException, IOException {
|
|
||||||
assert(name != null && customData != null);
|
|
||||||
|
|
||||||
xml.startTag(null, name);
|
|
||||||
|
|
||||||
for (Entry<String, String> 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<PwEntryV4> 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<PwIconCustom> 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<Integer, ProtectedBinary> 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database.search
|
|||||||
|
|
||||||
import com.kunzisoft.keepass.database.NodeHandler
|
import com.kunzisoft.keepass.database.NodeHandler
|
||||||
import com.kunzisoft.keepass.database.element.PwEntryV4
|
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.StringUtil
|
||||||
import com.kunzisoft.keepass.utils.UuidUtil
|
import com.kunzisoft.keepass.utils.UuidUtil
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import com.kunzisoft.keepass.database.NodeHandler
|
|||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.EntryVersioned
|
import com.kunzisoft.keepass.database.element.EntryVersioned
|
||||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
import com.kunzisoft.keepass.database.element.GroupVersioned
|
||||||
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator
|
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIterator
|
||||||
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIteratorV3
|
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorV3
|
||||||
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIteratorV4
|
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorV4
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class SearchDbHelper(private val isOmitBackup: Boolean) {
|
class SearchDbHelper(private val isOmitBackup: Boolean) {
|
||||||
|
|||||||
@@ -17,6 +17,6 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.iterator
|
package com.kunzisoft.keepass.database.search.iterator
|
||||||
|
|
||||||
abstract class EntrySearchStringIterator : Iterator<String>
|
abstract class EntrySearchStringIterator : Iterator<String>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
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.element.PwEntryV3
|
||||||
import com.kunzisoft.keepass.database.search.SearchParameters
|
import com.kunzisoft.keepass.database.search.SearchParameters
|
||||||
@@ -17,11 +17,11 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
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.element.PwEntryV4
|
||||||
import com.kunzisoft.keepass.database.search.SearchParametersV4
|
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 java.util.*
|
||||||
import kotlin.collections.Map.Entry
|
import kotlin.collections.Map.Entry
|
||||||
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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<ProtectedBinary> CREATOR = new Creator<ProtectedBinary>() {
|
|
||||||
@Override
|
|
||||||
public ProtectedBinary createFromParcel(Parcel in) {
|
|
||||||
return new ProtectedBinary(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ProtectedBinary[] newArray(int size) {
|
|
||||||
return new ProtectedBinary[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
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<ProtectedString> CREATOR = new Parcelable.Creator<ProtectedString>() {
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -33,7 +33,7 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R;
|
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 com.kunzisoft.keepass.utils.Util;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R;
|
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 com.kunzisoft.keepass.utils.Util;
|
||||||
|
|
||||||
public class EntryCustomField extends LinearLayout {
|
public class EntryCustomField extends LinearLayout {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import android.content.Context;
|
|||||||
import android.text.method.PasswordTransformationMethod;
|
import android.text.method.PasswordTransformationMethod;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedString;
|
import com.kunzisoft.keepass.database.element.security.ProtectedString;
|
||||||
|
|
||||||
public class EntryCustomFieldProtected extends EntryCustomField{
|
public class EntryCustomFieldProtected extends EntryCustomField{
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import android.widget.RelativeLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R;
|
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 com.kunzisoft.keepass.utils.Util;
|
||||||
|
|
||||||
public class EntryEditCustomField extends RelativeLayout {
|
public class EntryEditCustomField extends RelativeLayout {
|
||||||
|
|||||||
Reference in New Issue
Block a user