Kotlinized database package

This commit is contained in:
J-Jamet
2019-06-02 16:10:57 +02:00
parent 8890296beb
commit 93222990b5
51 changed files with 2362 additions and 2481 deletions

View File

@@ -42,11 +42,10 @@ import com.getkeepsafe.taptargetview.TapTargetView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.activities.lock.LockingHideActivity;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.element.ExtraFields;
import com.kunzisoft.keepass.database.element.Database;
import com.kunzisoft.keepass.database.element.EntryVersioned;
import com.kunzisoft.keepass.database.element.PwNodeId;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.database.element.security.ProtectedString;
import com.kunzisoft.keepass.notifications.NotificationCopyingService;
import com.kunzisoft.keepass.notifications.NotificationField;
import com.kunzisoft.keepass.settings.PreferencesUtil;

View File

@@ -38,7 +38,7 @@ import com.kunzisoft.keepass.database.action.node.AddEntryRunnable;
import com.kunzisoft.keepass.database.action.node.AfterActionNodeFinishRunnable;
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable;
import com.kunzisoft.keepass.database.element.*;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.database.element.security.ProtectedString;
import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment;
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
import com.kunzisoft.keepass.settings.PreferencesUtil;

View File

@@ -72,7 +72,7 @@ import com.kunzisoft.keepass.database.action.node.MoveEntryRunnable;
import com.kunzisoft.keepass.database.action.node.MoveGroupRunnable;
import com.kunzisoft.keepass.database.action.node.UpdateGroupRunnable;
import com.kunzisoft.keepass.database.element.*;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.database.element.security.ProtectedString;
import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment;
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;

View File

@@ -17,7 +17,7 @@
* 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) {

View File

@@ -19,8 +19,6 @@
*/
package com.kunzisoft.keepass.crypto;
import com.kunzisoft.keepass.database.CrsAlgorithm;
import org.spongycastle.crypto.StreamCipher;
import org.spongycastle.crypto.engines.ChaCha7539Engine;
import org.spongycastle.crypto.engines.Salsa20Engine;

View File

@@ -4,7 +4,7 @@ import android.database.MatrixCursor
import android.provider.BaseColumns
import com.kunzisoft.keepass.database.element.PwEntryV4
import com.kunzisoft.keepass.database.security.ProtectedString
import com.kunzisoft.keepass.database.element.security.ProtectedString
class ExtraFieldCursor : MatrixCursor(arrayOf(
_ID,

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.database.security.ProtectedBinary
import com.kunzisoft.keepass.database.element.security.ProtectedBinary
import java.util.HashMap
import kotlin.collections.Map.Entry

View File

@@ -31,10 +31,12 @@ import com.kunzisoft.keepass.database.NodeHandler
import com.kunzisoft.keepass.database.cursor.EntryCursorV3
import com.kunzisoft.keepass.database.cursor.EntryCursorV4
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.load.ImporterV3
import com.kunzisoft.keepass.database.load.ImporterV4
import com.kunzisoft.keepass.database.save.PwDbV3Output
import com.kunzisoft.keepass.database.save.PwDbV4Output
import com.kunzisoft.keepass.database.file.PwDbHeaderV3
import com.kunzisoft.keepass.database.file.PwDbHeaderV4
import com.kunzisoft.keepass.database.file.load.ImporterV3
import com.kunzisoft.keepass.database.file.load.ImporterV4
import com.kunzisoft.keepass.database.file.save.PwDbV3Output
import com.kunzisoft.keepass.database.file.save.PwDbV4Output
import com.kunzisoft.keepass.database.search.SearchDbHelper
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.settings.PreferencesUtil

View File

@@ -2,7 +2,7 @@ package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.security.ProtectedString
import com.kunzisoft.keepass.database.element.security.ProtectedString
import java.util.*
import kotlin.collections.ArrayList

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.security.ProtectedString
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.MemUtil
import java.util.HashMap

View File

@@ -28,9 +28,10 @@ import com.kunzisoft.keepass.crypto.engine.CipherEngine;
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters;
import com.kunzisoft.keepass.database.*;
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException;
import com.kunzisoft.keepass.database.exception.UnknownKDF;
import com.kunzisoft.keepass.database.file.PwCompressionAlgorithm;
import org.w3c.dom.*;
import javax.annotation.Nullable;

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -21,8 +21,8 @@ package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.security.ProtectedBinary
import com.kunzisoft.keepass.database.security.ProtectedString
import com.kunzisoft.keepass.database.element.security.ProtectedBinary
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.MemUtil
import com.kunzisoft.keepass.utils.SprEngineV4
import java.util.*

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -17,7 +17,7 @@
* 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
// since we won't do arithmetic on these values (also unlikely to

View File

@@ -17,7 +17,7 @@
* 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 {
@@ -32,7 +32,6 @@ abstract class PwDbHeader {
var encryptionIV = ByteArray(16)
companion object {
const val PWM_DBSIG_1 = -0x655d26fd
}

View File

@@ -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
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -17,7 +17,7 @@
* 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.exception.InvalidDBException

View File

@@ -43,13 +43,15 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.kunzisoft.keepass.database.load
package com.kunzisoft.keepass.database.file.load
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.PwDbHeader
import com.kunzisoft.keepass.database.file.PwDbHeaderV3
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater

View File

@@ -17,20 +17,21 @@
* 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 com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.PwStreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.database.PwCompressionAlgorithm
import com.kunzisoft.keepass.database.file.PwCompressionAlgorithm
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.exception.ArcFourException
import com.kunzisoft.keepass.database.exception.InvalidDBException
import com.kunzisoft.keepass.database.exception.InvalidPasswordException
import com.kunzisoft.keepass.database.security.ProtectedBinary
import com.kunzisoft.keepass.database.security.ProtectedString
import com.kunzisoft.keepass.database.file.PwDbHeaderV4
import com.kunzisoft.keepass.database.element.security.ProtectedBinary
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.stream.BetterCipherInputStream
import com.kunzisoft.keepass.stream.HashedBlockInputStream
import com.kunzisoft.keepass.stream.HmacBlockInputStream
@@ -101,7 +102,7 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
mDatabase.binPool.clear()
val hh = header.loadFromFile(databaseInputStream)
version = header.getVersion()
version = header.version
hashOfHeader = hh.hash
val pbHeader = hh.header

View File

@@ -17,11 +17,9 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.save;
public class PwDbHeaderOutput {
protected byte[] hashOfHeader = null;
public byte[] getHashOfHeader() { return hashOfHeader; }
package com.kunzisoft.keepass.database.file.save
open class PwDbHeaderOutput {
var hashOfHeader: ByteArray? = null
protected set
}

View File

@@ -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()
}
}

View File

@@ -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())
}
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database.search
import com.kunzisoft.keepass.database.NodeHandler
import com.kunzisoft.keepass.database.element.PwEntryV4
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIteratorV4
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorV4
import com.kunzisoft.keepass.utils.StringUtil
import com.kunzisoft.keepass.utils.UuidUtil

View File

@@ -23,9 +23,9 @@ import com.kunzisoft.keepass.database.NodeHandler
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIteratorV3
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIteratorV4
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIterator
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorV3
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorV4
import java.util.*
class SearchDbHelper(private val isOmitBackup: Boolean) {

View File

@@ -17,6 +17,6 @@
* 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>

View File

@@ -17,7 +17,7 @@
* 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.search.SearchParameters

View File

@@ -17,11 +17,11 @@
* 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.search.SearchParametersV4
import com.kunzisoft.keepass.database.security.ProtectedString
import com.kunzisoft.keepass.database.element.security.ProtectedString
import java.util.*
import kotlin.collections.Map.Entry

View File

@@ -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];
}
};
}

View File

@@ -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;
}
}

View File

@@ -33,7 +33,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.database.element.security.ProtectedString;
import com.kunzisoft.keepass.utils.Util;
import java.text.DateFormat;

View File

@@ -28,7 +28,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.database.element.security.ProtectedString;
import com.kunzisoft.keepass.utils.Util;
public class EntryCustomField extends LinearLayout {

View File

@@ -23,7 +23,7 @@ import android.content.Context;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.database.element.security.ProtectedString;
public class EntryCustomFieldProtected extends EntryCustomField{

View File

@@ -31,7 +31,7 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.database.element.security.ProtectedString;
import com.kunzisoft.keepass.utils.Util;
public class EntryEditCustomField extends RelativeLayout {