mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Kotlinized importers
This commit is contained in:
@@ -31,7 +31,7 @@ abstract class Importer<PwDb : PwDatabase<*, *>> {
|
||||
/**
|
||||
* Load a versioned database file, return contents in a new PwDatabase.
|
||||
*
|
||||
* @param inStream Existing file to load.
|
||||
* @param databaseInputStream Existing file to load.
|
||||
* @param password Pass phrase for infile.
|
||||
* @return new PwDatabase container.
|
||||
*
|
||||
@@ -39,6 +39,6 @@ abstract class Importer<PwDb : PwDatabase<*, *>> {
|
||||
* @throws InvalidDBException on database error.
|
||||
*/
|
||||
@Throws(IOException::class, InvalidDBException::class)
|
||||
abstract fun openDatabase(inStream: InputStream, password: String?, keyInputStream: InputStream?, updater: ProgressTaskUpdater?): PwDb
|
||||
abstract fun openDatabase(databaseInputStream: InputStream, password: String?, keyInputStream: InputStream?, progressTaskUpdater: ProgressTaskUpdater?): PwDb
|
||||
|
||||
}
|
||||
|
||||
@@ -1,338 +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.load;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
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.stream.LEDataInputStream;
|
||||
import com.kunzisoft.keepass.stream.NullOutputStream;
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater;
|
||||
import com.kunzisoft.keepass.utils.Types;
|
||||
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Load a v3 database file.
|
||||
*
|
||||
* @author Naomaru Itoi <nao@phoneid.org>
|
||||
* @author Bill Zwicky <wrzwicky@pobox.com>
|
||||
*/
|
||||
public class ImporterV3 extends Importer<PwDatabaseV3> {
|
||||
|
||||
private static final String TAG = ImporterV3.class.getName();
|
||||
|
||||
private PwDatabaseV3 databaseToOpen;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PwDatabaseV3 openDatabase(@NonNull InputStream inStream,
|
||||
String password,
|
||||
InputStream kfIs,
|
||||
ProgressTaskUpdater progressTaskUpdater)
|
||||
throws IOException, InvalidDBException {
|
||||
|
||||
// Load entire file, most of it's encrypted.
|
||||
int fileSize = inStream.available();
|
||||
byte[] filebuf = new byte[fileSize + 16]; // Pad with a blocksize (Twofish uses 128 bits), since Android 4.3 tries to write more to the buffer
|
||||
inStream.read(filebuf, 0, fileSize); // TODO remove
|
||||
inStream.close();
|
||||
|
||||
// Parse header (unencrypted)
|
||||
if( fileSize < PwDbHeaderV3.BUF_SIZE )
|
||||
throw new IOException( "File too short for header" );
|
||||
PwDbHeaderV3 hdr = new PwDbHeaderV3();
|
||||
hdr.loadFromFile(filebuf, 0 );
|
||||
|
||||
if( (hdr.signature1 != PwDbHeader.PWM_DBSIG_1) || (hdr.signature2 != PwDbHeaderV3.DBSIG_2) ) {
|
||||
throw new InvalidDBSignatureException();
|
||||
}
|
||||
|
||||
if( !hdr.matchesVersion() ) {
|
||||
throw new InvalidDBVersionException();
|
||||
}
|
||||
|
||||
if (progressTaskUpdater != null)
|
||||
progressTaskUpdater.updateMessage(R.string.retrieving_db_key);
|
||||
databaseToOpen = new PwDatabaseV3();
|
||||
databaseToOpen.retrieveMasterKey(password, kfIs);
|
||||
|
||||
// Select algorithm
|
||||
if( (hdr.flags & PwDbHeaderV3.FLAG_RIJNDAEL) != 0 ) {
|
||||
databaseToOpen.setEncryptionAlgorithm(PwEncryptionAlgorithm.AESRijndael);
|
||||
} else if( (hdr.flags & PwDbHeaderV3.FLAG_TWOFISH) != 0 ) {
|
||||
databaseToOpen.setEncryptionAlgorithm(PwEncryptionAlgorithm.Twofish);
|
||||
} else {
|
||||
throw new InvalidAlgorithmException();
|
||||
}
|
||||
|
||||
databaseToOpen.setNumberKeyEncryptionRounds(hdr.numKeyEncRounds);
|
||||
|
||||
// Generate transformedMasterKey from masterKey
|
||||
databaseToOpen.makeFinalKey(hdr.getMasterSeed(), hdr.transformSeed, databaseToOpen.getNumberKeyEncryptionRounds());
|
||||
|
||||
if (progressTaskUpdater != null)
|
||||
progressTaskUpdater.updateMessage(R.string.decrypting_db);
|
||||
// Initialize Rijndael algorithm
|
||||
Cipher cipher;
|
||||
try {
|
||||
if ( databaseToOpen.getEncryptionAlgorithm() == PwEncryptionAlgorithm.AESRijndael) {
|
||||
cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding");
|
||||
} else if ( databaseToOpen.getEncryptionAlgorithm() == PwEncryptionAlgorithm.Twofish ) {
|
||||
cipher = CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING");
|
||||
} else {
|
||||
throw new IOException( "Encryption algorithm is not supported" );
|
||||
}
|
||||
|
||||
} catch (NoSuchAlgorithmException e1) {
|
||||
throw new IOException("No such algorithm");
|
||||
} catch (NoSuchPaddingException e1) {
|
||||
throw new IOException("No such pdading");
|
||||
}
|
||||
|
||||
try {
|
||||
cipher.init( Cipher.DECRYPT_MODE, new SecretKeySpec( databaseToOpen.getFinalKey(), "AES" ), new IvParameterSpec(hdr.getEncryptionIV()) );
|
||||
} catch (InvalidKeyException e1) {
|
||||
throw new IOException("Invalid key");
|
||||
} catch (InvalidAlgorithmParameterException e1) {
|
||||
throw new IOException("Invalid algorithm parameter.");
|
||||
}
|
||||
|
||||
// Decrypt! The first bytes aren't encrypted (that's the header)
|
||||
int encryptedPartSize;
|
||||
try {
|
||||
encryptedPartSize = cipher.doFinal(filebuf, PwDbHeaderV3.BUF_SIZE, fileSize - PwDbHeaderV3.BUF_SIZE, filebuf, PwDbHeaderV3.BUF_SIZE );
|
||||
} catch (ShortBufferException e1) {
|
||||
throw new IOException("Buffer too short");
|
||||
} catch (IllegalBlockSizeException e1) {
|
||||
throw new IOException("Invalid block size");
|
||||
} catch (BadPaddingException e1) {
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IOException("No SHA-256 algorithm");
|
||||
}
|
||||
NullOutputStream nos = new NullOutputStream();
|
||||
DigestOutputStream dos = new DigestOutputStream(nos, md);
|
||||
dos.write(filebuf, PwDbHeaderV3.BUF_SIZE, encryptedPartSize);
|
||||
dos.close();
|
||||
byte[] hash = md.digest();
|
||||
|
||||
if( ! Arrays.equals(hash, hdr.contentsHash) ) {
|
||||
|
||||
Log.w(TAG,"Database file did not decrypt correctly. (checksum code is broken)");
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
|
||||
// New manual root because V3 contains multiple root groups (here available with getRootGroups())
|
||||
PwGroupV3 newRoot = databaseToOpen.createGroup();
|
||||
newRoot.setLevel(-1);
|
||||
databaseToOpen.setRootGroup(newRoot);
|
||||
|
||||
// Import all groups
|
||||
int pos = PwDbHeaderV3.BUF_SIZE;
|
||||
PwGroupV3 newGrp = databaseToOpen.createGroup();
|
||||
for( int i = 0; i < hdr.numGroups; ) {
|
||||
int fieldType = LEDataInputStream.readUShort( filebuf, pos );
|
||||
pos += 2;
|
||||
int fieldSize = LEDataInputStream.readInt( filebuf, pos );
|
||||
pos += 4;
|
||||
|
||||
if( fieldType == 0xFFFF ) {
|
||||
// End-Group record. Save group and count it.
|
||||
databaseToOpen.addGroupIndex(newGrp);
|
||||
newGrp = databaseToOpen.createGroup();
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
readGroupField(databaseToOpen, newGrp, fieldType, filebuf, pos);
|
||||
}
|
||||
pos += fieldSize;
|
||||
}
|
||||
|
||||
// Import all entries
|
||||
PwEntryV3 newEnt = databaseToOpen.createEntry();
|
||||
for( int i = 0; i < hdr.numEntries; ) {
|
||||
int fieldType = LEDataInputStream.readUShort( filebuf, pos );
|
||||
int fieldSize = LEDataInputStream.readInt( filebuf, pos + 2 );
|
||||
|
||||
if( fieldType == 0xFFFF ) {
|
||||
// End-Group record. Save group and count it.
|
||||
databaseToOpen.addEntryIndex(newEnt);
|
||||
newEnt = databaseToOpen.createEntry();
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
readEntryField(databaseToOpen, newEnt, filebuf, pos);
|
||||
}
|
||||
pos += 2 + 4 + fieldSize;
|
||||
}
|
||||
|
||||
databaseToOpen.constructTreeFromIndex();
|
||||
|
||||
return databaseToOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and save one record from binary file.
|
||||
* @param buf
|
||||
* @param offset
|
||||
* @return If >0,
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
private void readGroupField(PwDatabaseV3 db, PwGroupV3 grp, int fieldType, byte[] buf, int offset) throws UnsupportedEncodingException {
|
||||
switch( fieldType ) {
|
||||
case 0x0000 :
|
||||
// Ignore field
|
||||
break;
|
||||
case 0x0001 :
|
||||
grp.setGroupId(LEDataInputStream.readInt(buf, offset));
|
||||
break;
|
||||
case 0x0002 :
|
||||
grp.setTitle(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x0003 :
|
||||
grp.setCreationTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x0004 :
|
||||
grp.setLastModificationTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x0005 :
|
||||
grp.setLastAccessTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x0006 :
|
||||
grp.setExpiryTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x0007 :
|
||||
grp.setIcon(db.getIconFactory().getIcon(LEDataInputStream.readInt(buf, offset)));
|
||||
break;
|
||||
case 0x0008 :
|
||||
grp.setLevel(LEDataInputStream.readUShort(buf, offset));
|
||||
break;
|
||||
case 0x0009 :
|
||||
grp.setFlags(LEDataInputStream.readInt(buf, offset));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void readEntryField(PwDatabaseV3 db, PwEntryV3 ent, byte[] buf, int offset) throws UnsupportedEncodingException {
|
||||
int fieldType = LEDataInputStream.readUShort(buf, offset);
|
||||
offset += 2;
|
||||
int fieldSize = LEDataInputStream.readInt(buf, offset);
|
||||
offset += 4;
|
||||
|
||||
switch( fieldType ) {
|
||||
case 0x0000 :
|
||||
// Ignore field
|
||||
break;
|
||||
case 0x0001 :
|
||||
ent.setNodeId(new PwNodeIdUUID(Types.bytestoUUID(buf, offset)));
|
||||
break;
|
||||
case 0x0002 :
|
||||
PwGroupV3 pwGroupV3 = databaseToOpen.createGroup();
|
||||
pwGroupV3.setNodeId(new PwNodeIdInt(LEDataInputStream.readInt(buf, offset)));
|
||||
ent.setParent(pwGroupV3);
|
||||
break;
|
||||
case 0x0003 :
|
||||
int iconId = LEDataInputStream.readInt(buf, offset);
|
||||
|
||||
// Clean up after bug that set icon ids to -1
|
||||
if (iconId == -1) {
|
||||
iconId = 0;
|
||||
}
|
||||
|
||||
ent.setIcon(db.getIconFactory().getIcon(iconId));
|
||||
break;
|
||||
case 0x0004 :
|
||||
ent.setTitle(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x0005 :
|
||||
ent.setUrl(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x0006 :
|
||||
ent.setUsername(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x0007 :
|
||||
ent.setPassword(buf, offset, Types.strlen(buf, offset));
|
||||
break;
|
||||
case 0x0008 :
|
||||
ent.setNotes(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x0009 :
|
||||
ent.setCreationTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x000A :
|
||||
ent.setLastModificationTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x000B :
|
||||
ent.setLastAccessTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x000C :
|
||||
ent.setExpiryTime(new PwDate(buf, offset));
|
||||
break;
|
||||
case 0x000D :
|
||||
ent.setBinaryDesc(Types.readCString(buf, offset));
|
||||
break;
|
||||
case 0x000E :
|
||||
ent.setBinaryData(buf, offset, fieldSize);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* 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.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.stream.LEDataInputStream
|
||||
import com.kunzisoft.keepass.stream.NullOutputStream
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
import com.kunzisoft.keepass.utils.Types
|
||||
|
||||
import javax.crypto.*
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.security.*
|
||||
import java.util.Arrays
|
||||
|
||||
/**
|
||||
* Load a v3 database file.
|
||||
*
|
||||
* @author Naomaru Itoi <nao></nao>@phoneid.org>
|
||||
* @author Bill Zwicky <wrzwicky></wrzwicky>@pobox.com>
|
||||
*/
|
||||
class ImporterV3 : Importer<PwDatabaseV3>() {
|
||||
|
||||
private lateinit var mDatabaseToOpen: PwDatabaseV3
|
||||
|
||||
@Throws(IOException::class, InvalidDBException::class)
|
||||
override fun openDatabase(databaseInputStream: InputStream,
|
||||
password: String?,
|
||||
keyInputStream: InputStream?,
|
||||
progressTaskUpdater: ProgressTaskUpdater?): PwDatabaseV3 {
|
||||
|
||||
// Load entire file, most of it's encrypted.
|
||||
val fileSize = databaseInputStream.available()
|
||||
val filebuf = ByteArray(fileSize + 16) // Pad with a blocksize (Twofish uses 128 bits), since Android 4.3 tries to write more to the buffer
|
||||
databaseInputStream.read(filebuf, 0, fileSize) // TODO remove
|
||||
databaseInputStream.close()
|
||||
|
||||
// Parse header (unencrypted)
|
||||
if (fileSize < PwDbHeaderV3.BUF_SIZE)
|
||||
throw IOException("File too short for header")
|
||||
val hdr = PwDbHeaderV3()
|
||||
hdr.loadFromFile(filebuf, 0)
|
||||
|
||||
if (hdr.signature1 != PwDbHeader.PWM_DBSIG_1 || hdr.signature2 != PwDbHeaderV3.DBSIG_2) {
|
||||
throw InvalidDBSignatureException()
|
||||
}
|
||||
|
||||
if (!hdr.matchesVersion()) {
|
||||
throw InvalidDBVersionException()
|
||||
}
|
||||
|
||||
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
|
||||
mDatabaseToOpen = PwDatabaseV3()
|
||||
mDatabaseToOpen.retrieveMasterKey(password, keyInputStream)
|
||||
|
||||
// Select algorithm
|
||||
if (hdr.flags and PwDbHeaderV3.FLAG_RIJNDAEL != 0) {
|
||||
mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.AESRijndael
|
||||
} else if (hdr.flags and PwDbHeaderV3.FLAG_TWOFISH != 0) {
|
||||
mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.Twofish
|
||||
} else {
|
||||
throw InvalidAlgorithmException()
|
||||
}
|
||||
|
||||
mDatabaseToOpen.numberKeyEncryptionRounds = hdr.numKeyEncRounds.toLong()
|
||||
|
||||
// Generate transformedMasterKey from masterKey
|
||||
mDatabaseToOpen.makeFinalKey(hdr.masterSeed, hdr.transformSeed, mDatabaseToOpen.numberKeyEncryptionRounds)
|
||||
|
||||
progressTaskUpdater?.updateMessage(R.string.decrypting_db)
|
||||
// Initialize Rijndael algorithm
|
||||
val cipher: Cipher
|
||||
try {
|
||||
if (mDatabaseToOpen.encryptionAlgorithm === PwEncryptionAlgorithm.AESRijndael) {
|
||||
cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding")
|
||||
} else if (mDatabaseToOpen.encryptionAlgorithm === PwEncryptionAlgorithm.Twofish) {
|
||||
cipher = CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING")
|
||||
} else {
|
||||
throw IOException("Encryption algorithm is not supported")
|
||||
}
|
||||
|
||||
} catch (e1: NoSuchAlgorithmException) {
|
||||
throw IOException("No such algorithm")
|
||||
} catch (e1: NoSuchPaddingException) {
|
||||
throw IOException("No such pdading")
|
||||
}
|
||||
|
||||
try {
|
||||
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(mDatabaseToOpen.finalKey, "AES"), IvParameterSpec(hdr.encryptionIV))
|
||||
} catch (e1: InvalidKeyException) {
|
||||
throw IOException("Invalid key")
|
||||
} catch (e1: InvalidAlgorithmParameterException) {
|
||||
throw IOException("Invalid algorithm parameter.")
|
||||
}
|
||||
|
||||
// Decrypt! The first bytes aren't encrypted (that's the header)
|
||||
val encryptedPartSize: Int
|
||||
try {
|
||||
encryptedPartSize = cipher.doFinal(filebuf, PwDbHeaderV3.BUF_SIZE, fileSize - PwDbHeaderV3.BUF_SIZE, filebuf, PwDbHeaderV3.BUF_SIZE)
|
||||
} catch (e1: ShortBufferException) {
|
||||
throw IOException("Buffer too short")
|
||||
} catch (e1: IllegalBlockSizeException) {
|
||||
throw IOException("Invalid block size")
|
||||
} catch (e1: BadPaddingException) {
|
||||
throw InvalidPasswordException()
|
||||
}
|
||||
|
||||
var md: MessageDigest? = null
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
throw IOException("No SHA-256 algorithm")
|
||||
}
|
||||
|
||||
val nos = NullOutputStream()
|
||||
val dos = DigestOutputStream(nos, md)
|
||||
dos.write(filebuf, PwDbHeaderV3.BUF_SIZE, encryptedPartSize)
|
||||
dos.close()
|
||||
val hash = md!!.digest()
|
||||
|
||||
if (!Arrays.equals(hash, hdr.contentsHash)) {
|
||||
|
||||
Log.w(TAG, "Database file did not decrypt correctly. (checksum code is broken)")
|
||||
throw InvalidPasswordException()
|
||||
}
|
||||
|
||||
// New manual root because V3 contains multiple root groups (here available with getRootGroups())
|
||||
val newRoot = mDatabaseToOpen.createGroup()
|
||||
newRoot.level = -1
|
||||
mDatabaseToOpen.rootGroup = newRoot
|
||||
|
||||
// Import all groups
|
||||
var pos = PwDbHeaderV3.BUF_SIZE
|
||||
var newGrp = mDatabaseToOpen.createGroup()
|
||||
run {
|
||||
var i = 0
|
||||
while (i < hdr.numGroups) {
|
||||
val fieldType = LEDataInputStream.readUShort(filebuf, pos)
|
||||
pos += 2
|
||||
val fieldSize = LEDataInputStream.readInt(filebuf, pos)
|
||||
pos += 4
|
||||
|
||||
if (fieldType == 0xFFFF) {
|
||||
// End-Group record. Save group and count it.
|
||||
mDatabaseToOpen.addGroupIndex(newGrp)
|
||||
newGrp = mDatabaseToOpen.createGroup()
|
||||
i++
|
||||
} else {
|
||||
readGroupField(mDatabaseToOpen, newGrp, fieldType, filebuf, pos)
|
||||
}
|
||||
pos += fieldSize
|
||||
}
|
||||
}
|
||||
|
||||
// Import all entries
|
||||
var newEnt = mDatabaseToOpen.createEntry()
|
||||
var i = 0
|
||||
while (i < hdr.numEntries) {
|
||||
val fieldType = LEDataInputStream.readUShort(filebuf, pos)
|
||||
val fieldSize = LEDataInputStream.readInt(filebuf, pos + 2)
|
||||
|
||||
if (fieldType == 0xFFFF) {
|
||||
// End-Group record. Save group and count it.
|
||||
mDatabaseToOpen.addEntryIndex(newEnt)
|
||||
newEnt = mDatabaseToOpen.createEntry()
|
||||
i++
|
||||
} else {
|
||||
readEntryField(mDatabaseToOpen, newEnt, filebuf, pos)
|
||||
}
|
||||
pos += 2 + 4 + fieldSize
|
||||
}
|
||||
|
||||
mDatabaseToOpen.constructTreeFromIndex()
|
||||
|
||||
return mDatabaseToOpen
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and save one record from binary file.
|
||||
* @param buf
|
||||
* @param offset
|
||||
* @return If >0,
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
@Throws(UnsupportedEncodingException::class)
|
||||
private fun readGroupField(db: PwDatabaseV3, grp: PwGroupV3, fieldType: Int, buf: ByteArray, offset: Int) {
|
||||
when (fieldType) {
|
||||
0x0000 -> {
|
||||
}
|
||||
0x0001 -> grp.setGroupId(LEDataInputStream.readInt(buf, offset))
|
||||
0x0002 -> grp.title = Types.readCString(buf, offset)
|
||||
0x0003 -> grp.creationTime = PwDate(buf, offset)
|
||||
0x0004 -> grp.lastModificationTime = PwDate(buf, offset)
|
||||
0x0005 -> grp.lastAccessTime = PwDate(buf, offset)
|
||||
0x0006 -> grp.expiryTime = PwDate(buf, offset)
|
||||
0x0007 -> grp.icon = db.iconFactory.getIcon(LEDataInputStream.readInt(buf, offset))
|
||||
0x0008 -> grp.level = LEDataInputStream.readUShort(buf, offset)
|
||||
0x0009 -> grp.flags = LEDataInputStream.readInt(buf, offset)
|
||||
}// Ignore field
|
||||
}
|
||||
|
||||
@Throws(UnsupportedEncodingException::class)
|
||||
private fun readEntryField(db: PwDatabaseV3, ent: PwEntryV3, buf: ByteArray, offset: Int) {
|
||||
var offsetMutable = offset
|
||||
val fieldType = LEDataInputStream.readUShort(buf, offsetMutable)
|
||||
offsetMutable += 2
|
||||
val fieldSize = LEDataInputStream.readInt(buf, offsetMutable)
|
||||
offsetMutable += 4
|
||||
|
||||
when (fieldType) {
|
||||
0x0000 -> {
|
||||
}
|
||||
0x0001 -> ent.nodeId = PwNodeIdUUID(Types.bytestoUUID(buf, offsetMutable))
|
||||
0x0002 -> {
|
||||
val pwGroupV3 = mDatabaseToOpen.createGroup()
|
||||
pwGroupV3.nodeId = PwNodeIdInt(LEDataInputStream.readInt(buf, offsetMutable))
|
||||
ent.parent = pwGroupV3
|
||||
}
|
||||
0x0003 -> {
|
||||
var iconId = LEDataInputStream.readInt(buf, offsetMutable)
|
||||
|
||||
// Clean up after bug that set icon ids to -1
|
||||
if (iconId == -1) {
|
||||
iconId = 0
|
||||
}
|
||||
|
||||
ent.icon = db.iconFactory.getIcon(iconId)
|
||||
}
|
||||
0x0004 -> ent.title = Types.readCString(buf, offsetMutable)
|
||||
0x0005 -> ent.url = Types.readCString(buf, offsetMutable)
|
||||
0x0006 -> ent.username = Types.readCString(buf, offsetMutable)
|
||||
0x0007 -> ent.setPassword(buf, offsetMutable, Types.strlen(buf, offsetMutable))
|
||||
0x0008 -> ent.notes = Types.readCString(buf, offsetMutable)
|
||||
0x0009 -> ent.creationTime = PwDate(buf, offsetMutable)
|
||||
0x000A -> ent.lastModificationTime = PwDate(buf, offsetMutable)
|
||||
0x000B -> ent.lastAccessTime = PwDate(buf, offsetMutable)
|
||||
0x000C -> ent.expiryTime = PwDate(buf, offsetMutable)
|
||||
0x000D -> ent.binaryDesc = Types.readCString(buf, offsetMutable)
|
||||
0x000E -> ent.setBinaryData(buf, offsetMutable, fieldSize)
|
||||
}// Ignore field
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = ImporterV3::class.java.name
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
1071
app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.kt
Normal file
1071
app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.kt
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user