diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/AesKdf.java b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/AesKdf.java index b45710a2b..a640a0ab2 100644 --- a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/AesKdf.java +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/AesKdf.java @@ -58,6 +58,8 @@ public class AesKdf extends KdfEngine { @Override public KdfParameters getDefaultParameters() { KdfParameters p = new KdfParameters(uuid); + + p.setParamUUID(); p.setUInt32(ParamRounds, DEFAULT_ROUNDS); return p; diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.java b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.java index 0f5e7e2dc..4a1a3c8e2 100644 --- a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.java +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.java @@ -79,10 +79,11 @@ public class Argon2Kdf extends KdfEngine { public KdfParameters getDefaultParameters() { KdfParameters p = new KdfParameters(uuid); - p.setUInt32(ParamVersion, MaxVersion); - p.setUInt64(ParamMemory, DefaultMemory); + p.setParamUUID(); p.setUInt32(ParamParallelism, DefaultParallelism); + p.setUInt64(ParamMemory, DefaultMemory); p.setUInt64(ParamIterations, DefaultIterations); + p.setUInt32(ParamVersion, MaxVersion); return p; } diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfParameters.java b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfParameters.java index c14425d88..9255e6242 100644 --- a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfParameters.java +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfParameters.java @@ -39,6 +39,10 @@ public class KdfParameters extends VariantDictionary { kdfUUID = uuid; } + protected void setParamUUID() { + setByteArray(ParamUUID, Types.UUIDtoBytes(kdfUUID)); + } + public static KdfParameters deserialize(byte[] data) throws IOException { ByteArrayInputStream bis = new ByteArrayInputStream(data); LEDataInputStream lis = new LEDataInputStream(bis); diff --git a/app/src/main/java/com/kunzisoft/keepass/database/Database.java b/app/src/main/java/com/kunzisoft/keepass/database/Database.java index d3f8b9f14..6c7252795 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/Database.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/Database.java @@ -152,11 +152,11 @@ public class Database { loadData(ctx, is, password, kfIs, status, debug, roundsFix); } - public void loadData(Context ctx, InputStream is, String password, InputStream kfIs, boolean debug) throws IOException, InvalidDBException { - loadData(ctx, is, password, kfIs, null, debug, 0); + public void loadData(Context ctx, InputStream is, String password, InputStream keyFileInputStream, boolean debug) throws IOException, InvalidDBException { + loadData(ctx, is, password, keyFileInputStream, null, debug, 0); } - private void loadData(Context ctx, InputStream is, String password, InputStream kfIs, ProgressTaskUpdater progressTaskUpdater, boolean debug, long roundsFix) throws IOException, InvalidDBException { + private void loadData(Context ctx, InputStream is, String password, InputStream keyFileInputStream, ProgressTaskUpdater progressTaskUpdater, boolean debug, long roundsFix) throws IOException, InvalidDBException { BufferedInputStream bis = new BufferedInputStream(is); if ( ! bis.markSupported() ) { @@ -166,11 +166,11 @@ public class Database { // We'll end up reading 8 bytes to identify the header. Might as well use two extra. bis.mark(10); - Importer imp = ImporterFactory.createImporter(bis, debug); + Importer databaseImporter = ImporterFactory.createImporter(bis, debug); bis.reset(); // Return to the start - pm = imp.openDatabase(bis, password, kfIs, progressTaskUpdater, roundsFix); + pm = databaseImporter.openDatabase(bis, password, keyFileInputStream, progressTaskUpdater, roundsFix); if ( pm != null ) { try { switch (pm.getVersion()) { @@ -392,7 +392,8 @@ public class Database { switch (getPwDatabase().getVersion()) { case V4: PwDatabaseV4 db = ((PwDatabaseV4) getPwDatabase()); - db.setKdfParameters(kdfEngine.getDefaultParameters()); + if (!db.getKdfParameters().kdfUUID.equals(kdfEngine.getDefaultParameters().kdfUUID)) + db.setKdfParameters(kdfEngine.getDefaultParameters()); setNumberKeyEncryptionRounds(kdfEngine.getDefaultKeyRounds()); break; } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/PwDatabaseV4.java b/app/src/main/java/com/kunzisoft/keepass/database/PwDatabaseV4.java index 028006d0e..d2d43e1b0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/PwDatabaseV4.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/PwDatabaseV4.java @@ -738,64 +738,4 @@ public class PwDatabaseV4 extends PwDatabase { return filename.substring(0, lastExtDot); } - private class GroupHasCustomData extends GroupHandler { - - public boolean hasCustomData = false; - - @Override - public boolean operate(PwGroupV4 group) { - if (group == null) { - return true; - } - if (group.containsCustomData()) { - hasCustomData = true; - return false; - } - - return true; - } - } - - private class EntryHasCustomData extends EntryHandler { - - public boolean hasCustomData = false; - - @Override - public boolean operate(PwEntryV4 entry) { - if (entry == null) { - return true; - } - - if (entry.containsCustomData()) { - hasCustomData = true; - return false; - } - - return true; - } - } - - public int getMinKdbxVersion() { - if (kdfParameters != null && !AesKdf.CIPHER_UUID.equals(kdfParameters.kdfUUID)) { - return PwDbHeaderV4.FILE_VERSION_32; - } - - if (publicCustomData.size() > 0) { - return PwDbHeaderV4.FILE_VERSION_32; - } - - EntryHasCustomData entryHandler = new EntryHasCustomData(); - GroupHasCustomData groupHandler = new GroupHasCustomData(); - - if (rootGroup == null ) { - return PwDbHeaderV4.FILE_VERSION_32_3; - } - rootGroup.preOrderTraverseTree(groupHandler, entryHandler); - if (groupHandler.hasCustomData || entryHandler.hasCustomData) { - return PwDbHeaderV4.FILE_VERSION_32; - } - - return PwDbHeaderV4.FILE_VERSION_32_3; - } - } \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/PwDbHeaderV4.java b/app/src/main/java/com/kunzisoft/keepass/database/PwDbHeaderV4.java index 1e7cc73da..45a3d2146 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/PwDbHeaderV4.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/PwDbHeaderV4.java @@ -42,7 +42,7 @@ import javax.crypto.spec.SecretKeySpec; 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; @@ -92,12 +92,82 @@ public class PwDbHeaderV4 extends PwDbHeader { public CrsAlgorithm innerRandomStream; public long version; - public PwDbHeaderV4(PwDatabaseV4 d) { - this.db = d; - this.version = d.getMinKdbxVersion(); + public PwDbHeaderV4(PwDatabaseV4 databaseV4) { + this.db = databaseV4; + this.version = getMinKdbxVersion(databaseV4); // TODO move Only for writing this.masterSeed = new byte[32]; } + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + + private class GroupHasCustomData extends GroupHandler { + + boolean hasCustomData = false; + + @Override + public boolean operate(PwGroupV4 group) { + if (group == null) { + return true; + } + if (group.containsCustomData()) { + hasCustomData = true; + return false; + } + + return true; + } + } + + private class EntryHasCustomData extends EntryHandler { + + 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) { + return PwDbHeaderV4.FILE_VERSION_32; + } + + // Return V4 if custom data are present + if (databaseV4.containsPublicCustomData()) { + return PwDbHeaderV4.FILE_VERSION_32; + } + + EntryHasCustomData entryHandler = new EntryHasCustomData(); + GroupHasCustomData groupHandler = new GroupHasCustomData(); + + if (databaseV4.getRootGroup() == null ) { + return PwDbHeaderV4.FILE_VERSION_32_3; + } + databaseV4.getRootGroup().preOrderTraverseTree(groupHandler, entryHandler); + if (groupHandler.hasCustomData || entryHandler.hasCustomData) { + return PwDbHeaderV4.FILE_VERSION_32; + } + + return PwDbHeaderV4.FILE_VERSION_32_3; + } + /** Assumes the input stream is at the beginning of the .kdbx file * @param is * @throws IOException @@ -123,7 +193,7 @@ public class PwDbHeaderV4 extends PwDbHeader { throw new InvalidDBVersionException(); } - version = lis.readUInt(); + version = lis.readUInt(); // Erase previous value if ( ! validVersion(version) ) { throw new InvalidDBVersionException(); } @@ -279,8 +349,7 @@ public class PwDbHeaderV4 extends PwDbHeader { } public static boolean matchesHeader(int sig1, int sig2) { - return (sig1 == PWM_DBSIG_1) && ( (sig2 == DBSIG_2) ); - //return (sig1 == PWM_DBSIG_1) && ( (sig2 == DBSIG_PRE2) || (sig2 == DBSIG_2) ); // TODO verify add DBSIG_PRE2 + return (sig1 == PWM_DBSIG_1) && ( (sig2 == DBSIG_PRE2) || (sig2 == DBSIG_2) ); // TODO verify add DBSIG_PRE2 } public static byte[] computeHeaderHmac(byte[] header, byte[] key) throws IOException{ diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDBRunnable.java b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDBRunnable.java index 289226c81..f275a13ef 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDBRunnable.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDBRunnable.java @@ -23,6 +23,8 @@ import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; import android.preference.PreferenceManager; +import android.support.annotation.StringRes; +import android.util.Log; import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.app.App; @@ -41,6 +43,8 @@ import java.io.FileNotFoundException; import java.io.IOException; public class LoadDBRunnable extends RunnableOnFinish { + private static final String TAG = LoadDBRunnable.class.getName(); + private Uri mUri; private String mPass; private Uri mKey; @@ -69,42 +73,46 @@ public class LoadDBRunnable extends RunnableOnFinish { saveFileData(mUri, mKey); } catch (ArcFourException e) { - finish(false, mCtx.getString(R.string.error_arc4)); + catchError(e, R.string.error_arc4); return; } catch (InvalidPasswordException e) { - finish(false, mCtx.getString(R.string.InvalidPassword)); + catchError(e, R.string.InvalidPassword); return; } catch (ContentFileNotFoundException e) { - finish(false, mCtx.getString(R.string.file_not_found_content)); + catchError(e, R.string.file_not_found_content); return; } catch (FileNotFoundException e) { - finish(false, mCtx.getString(R.string.file_not_found)); + catchError(e, R.string.file_not_found); return; } catch (IOException e) { + Log.e(TAG, "Database can't be read", e); finish(false, e.getMessage()); return; } catch (KeyFileEmptyException e) { - finish(false, mCtx.getString(R.string.keyfile_is_empty)); + catchError(e, R.string.keyfile_is_empty); return; } catch (InvalidAlgorithmException e) { - finish(false, mCtx.getString(R.string.invalid_algorithm)); + catchError(e, R.string.invalid_algorithm); return; } catch (InvalidKeyFileException e) { - finish(false, mCtx.getString(R.string.keyfile_does_not_exist)); + catchError(e, R.string.keyfile_does_not_exist); return; } catch (InvalidDBSignatureException e) { - finish(false, mCtx.getString(R.string.invalid_db_sig)); + catchError(e, R.string.invalid_db_sig); return; } catch (InvalidDBVersionException e) { - finish(false, mCtx.getString(R.string.unsupported_db_version)); + catchError(e, R.string.unsupported_db_version); return; } catch (InvalidDBException e) { - finish(false, mCtx.getString(R.string.error_invalid_db)); + catchError(e, R.string.error_invalid_db); return; } catch (OutOfMemoryError e) { - finish(false, mCtx.getString(R.string.error_out_of_memory)); + String errorMessage = mCtx.getString(R.string.error_out_of_memory); + Log.e(TAG, errorMessage, e); + finish(false, errorMessage); return; } catch (Exception e) { + Log.e(TAG, "Database can't be load", e); finish(false, e.getMessage()); return; } @@ -112,6 +120,12 @@ public class LoadDBRunnable extends RunnableOnFinish { finish(true); } + private void catchError(Exception e, @StringRes int messageId) { + String errorMessage = mCtx.getString(messageId); + Log.e(TAG, errorMessage, e); + finish(false, errorMessage); + } + private void saveFileData(Uri uri, Uri key) { if ( ! mRememberKeyfile ) { key = null; diff --git a/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterFactory.java b/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterFactory.java index adfe548f6..385159fd7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterFactory.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterFactory.java @@ -28,13 +28,11 @@ import java.io.IOException; import java.io.InputStream; public class ImporterFactory { - public static Importer createImporter(InputStream is) throws InvalidDBSignatureException, IOException - { + public static Importer createImporter(InputStream is) throws InvalidDBSignatureException, IOException { return createImporter(is, false); } - public static Importer createImporter(InputStream is, boolean debug) throws InvalidDBSignatureException, IOException - { + public static Importer createImporter(InputStream is, boolean debug) throws InvalidDBSignatureException, IOException { int sig1 = LEDataInputStream.readInt(is); int sig2 = LEDataInputStream.readInt(is); @@ -49,6 +47,5 @@ public class ImporterFactory { } throw new InvalidDBSignatureException(); - } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.java b/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.java index 2269a21a9..3802971d3 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.java @@ -108,7 +108,7 @@ public class ImporterV4 extends Importer { db.getBinPool().clear(); PwDbHeaderV4.HeaderAndHash hh = header.loadFromFile(inStream); - version = header.version; + version = header.getVersion(); hashOfHeader = hh.hash; pbHeader = hh.header; diff --git a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV4.java b/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV4.java index 379f2e047..cfa054e30 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV4.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbHeaderOutputV4.java @@ -88,25 +88,26 @@ public class PwDbHeaderOutputV4 extends PwDbHeaderOutput { los.writeUInt(PwDbHeader.PWM_DBSIG_1); los.writeUInt(PwDbHeaderV4.DBSIG_2); - los.writeUInt(header.version); + los.writeUInt(header.getVersion()); writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.CipherID, Types.UUIDtoBytes(db.getDataCipher())); writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.CompressionFlags, LEDataOutputStream.writeIntBuf(db.getCompressionAlgorithm().id)); writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.MasterSeed, header.masterSeed); - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + 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())); + // TODO verify serialize in all cases } if (header.encryptionIV.length > 0) { writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV); } - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + 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.id)); @@ -139,7 +140,7 @@ public class PwDbHeaderOutputV4 extends PwDbHeaderOutput { } private void writeHeaderFieldSize(int size) throws IOException { - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { los.writeUShort(size); } else { los.writeInt(size); diff --git a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV4Output.java b/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV4Output.java index a225a951b..11cd55f3b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV4Output.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/save/PwDbV4Output.java @@ -101,7 +101,7 @@ public class PwDbV4Output extends PwDbOutput { header = outputHeader(mOS); OutputStream osPlain; - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { CipherOutputStream cos = attachStreamEncryptor(header, mOS); cos.write(header.streamStartBytes); @@ -122,7 +122,7 @@ public class PwDbV4Output extends PwDbOutput { osXml = osPlain; } - if (header.version >= PwDbHeaderV4.FILE_VERSION_32_4) { + if (header.getVersion() >= PwDbHeaderV4.FILE_VERSION_32_4) { PwDbInnerHeaderOutputV4 ihOut = new PwDbInnerHeaderOutputV4(mPM, header, osXml); ihOut.output(); } @@ -258,7 +258,7 @@ public class PwDbV4Output extends PwDbOutput { writeObject(PwDatabaseV4XML.ElemLastSelectedGroup, mPM.getLastSelectedGroup()); writeObject(PwDatabaseV4XML.ElemLastTopVisibleGroup, mPM.getLastTopVisibleGroup()); - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { writeBinPool(); } writeList(PwDatabaseV4XML.ElemCustomData, mPM.getCustomData()); @@ -296,7 +296,7 @@ public class PwDbV4Output extends PwDbOutput { KdfEngine kdf = KdfFactory.get(mPM.getKdfParameters()); kdf.randomize(mPM.getKdfParameters()); - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { header.innerRandomStream = CrsAlgorithm.Salsa20; header.innerRandomStreamKey = new byte[32]; } else { @@ -310,7 +310,7 @@ public class PwDbV4Output extends PwDbOutput { throw new PwDbOutputException("Invalid random cipher"); } - if ( header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + if ( header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { random.nextBytes(header.streamStartBytes); } @@ -463,7 +463,7 @@ public class PwDbV4Output extends PwDbOutput { } private void writeObject(String name, Date value) throws IllegalArgumentException, IllegalStateException, IOException { - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + if (header.getVersion() < PwDbHeaderV4.FILE_VERSION_32_4) { writeObject(name, PwDatabaseV4XML.dateFormatter.get().format(value)); } else { DateTime dt = new DateTime(value);