mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Support for outputing the database header.
This commit is contained in:
51
src/com/android/keepass/keepasslib/PwDbHeaderOutput.java
Normal file
51
src/com/android/keepass/keepasslib/PwDbHeaderOutput.java
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2009 Brian Pellin.
|
||||
*
|
||||
* This file is part of KeePassDroid.
|
||||
*
|
||||
* 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 2 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.android.keepass.keepasslib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.phoneid.keepassj2me.PwDbHeader;
|
||||
import org.phoneid.keepassj2me.Types;
|
||||
|
||||
public class PwDbHeaderOutput {
|
||||
private PwDbHeader mHeader;
|
||||
private OutputStream mOS;
|
||||
|
||||
public PwDbHeaderOutput(PwDbHeader header, OutputStream os) {
|
||||
mHeader = header;
|
||||
mOS = os;
|
||||
}
|
||||
|
||||
public void output() throws IOException {
|
||||
mOS.write(Types.writeInt(mHeader.signature1));
|
||||
mOS.write(Types.writeInt(mHeader.signature2));
|
||||
mOS.write(Types.writeInt(mHeader.flags));
|
||||
mOS.write(Types.writeInt(mHeader.version));
|
||||
mOS.write(mHeader.masterSeed);
|
||||
mOS.write(mHeader.encryptionIV);
|
||||
mOS.write(Types.writeInt(mHeader.numGroups));
|
||||
mOS.write(Types.writeInt(mHeader.numEntries));
|
||||
mOS.write(mHeader.contentsHash);
|
||||
mOS.write(mHeader.masterSeed2);
|
||||
mOS.write(Types.writeInt(mHeader.numKeyEncRounds));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -26,95 +26,7 @@ import org.phoneid.keepassj2me.PwEntry;
|
||||
import org.phoneid.keepassj2me.Types;
|
||||
|
||||
public class PwEntryOutput {
|
||||
private OutputStream mOS;
|
||||
private PwEntry mPE;
|
||||
|
||||
/** Output the PwGroup to the stream
|
||||
* @param pe
|
||||
* @param os
|
||||
*/
|
||||
public PwEntryOutput(PwEntry 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 {
|
||||
|
||||
// UUID
|
||||
mOS.write(UUID_FIELD_TYPE);
|
||||
mOS.write(UUID_FIELD_SIZE);
|
||||
mOS.write(mPE.uuid);
|
||||
|
||||
// Group ID
|
||||
mOS.write(GROUPID_FIELD_TYPE);
|
||||
mOS.write(LONG_FOUR);
|
||||
mOS.write(Types.writeInt(mPE.groupId));
|
||||
|
||||
// Image ID
|
||||
mOS.write(IMAGEID_FIELD_TYPE);
|
||||
mOS.write(LONG_FOUR);
|
||||
mOS.write(Types.writeInt(mPE.imageId));
|
||||
|
||||
// Title
|
||||
//byte[] title = mPE.title.getBytes("UTF-8");
|
||||
mOS.write(TITLE_FIELD_TYPE);
|
||||
Types.writeCString(mPE.title, mOS);
|
||||
|
||||
// URL
|
||||
mOS.write(URL_FIELD_TYPE);
|
||||
Types.writeCString(mPE.url, mOS);
|
||||
|
||||
// Username
|
||||
mOS.write(USERNAME_FIELD_TYPE);
|
||||
Types.writeCString(mPE.username, mOS);
|
||||
|
||||
// Password
|
||||
byte[] password = mPE.getPassword();
|
||||
mOS.write(PASSWORD_FIELD_TYPE);
|
||||
mOS.write(Types.writeInt(password.length+1));
|
||||
mOS.write(password);
|
||||
mOS.write(0);
|
||||
|
||||
// Additional
|
||||
mOS.write(ADDITIONAL_FIELD_TYPE);
|
||||
Types.writeCString(mPE.additional, mOS);
|
||||
|
||||
// Create date
|
||||
mOS.write(CREATE_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(Types.writeTime(mPE.tCreation));
|
||||
|
||||
// Modification date
|
||||
mOS.write(MOD_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(Types.writeTime(mPE.tLastMod));
|
||||
|
||||
// Access date
|
||||
mOS.write(ACCESS_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(Types.writeTime(mPE.tLastAccess));
|
||||
|
||||
// Expiration date
|
||||
mOS.write(EXPIRE_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(Types.writeTime(mPE.tExpire));
|
||||
|
||||
// Binary desc
|
||||
mOS.write(BINARY_DESC_FIELD_TYPE);
|
||||
Types.writeCString(mPE.binaryDesc, mOS);
|
||||
|
||||
// Binary data
|
||||
byte[] data = mPE.getBinaryData();
|
||||
mOS.write(BINARY_DATA_FIELD_TYPE);
|
||||
mOS.write(Types.writeInt(data.length));
|
||||
mOS.write(data);
|
||||
|
||||
// End
|
||||
mOS.write(END_FIELD_TYPE);
|
||||
mOS.write(ZERO_FIELD_SIZE);
|
||||
}
|
||||
|
||||
// Constants
|
||||
public static final byte[] UUID_FIELD_TYPE = Types.writeShort(1);
|
||||
public static final byte[] GROUPID_FIELD_TYPE = Types.writeShort(2);
|
||||
public static final byte[] IMAGEID_FIELD_TYPE = Types.writeShort(3);
|
||||
@@ -139,5 +51,109 @@ public class PwEntryOutput {
|
||||
public static final byte[] ZERO_FIELD_SIZE = Types.writeInt(0);
|
||||
public static final byte[] TEST = {0x33, 0x33, 0x33, 0x33};
|
||||
|
||||
private OutputStream mOS;
|
||||
private PwEntry mPE;
|
||||
private long outputBytes = 0;
|
||||
|
||||
/** Output the PwGroup to the stream
|
||||
* @param pe
|
||||
* @param os
|
||||
*/
|
||||
public PwEntryOutput(PwEntry 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(mPE.uuid);
|
||||
|
||||
// Group ID
|
||||
mOS.write(GROUPID_FIELD_TYPE);
|
||||
mOS.write(LONG_FOUR);
|
||||
mOS.write(Types.writeInt(mPE.groupId));
|
||||
|
||||
// Image ID
|
||||
mOS.write(IMAGEID_FIELD_TYPE);
|
||||
mOS.write(LONG_FOUR);
|
||||
mOS.write(Types.writeInt(mPE.imageId));
|
||||
|
||||
// Title
|
||||
//byte[] title = mPE.title.getBytes("UTF-8");
|
||||
mOS.write(TITLE_FIELD_TYPE);
|
||||
int titleLen = Types.writeCString(mPE.title, mOS);
|
||||
outputBytes += titleLen;
|
||||
|
||||
// URL
|
||||
mOS.write(URL_FIELD_TYPE);
|
||||
int urlLen = Types.writeCString(mPE.url, mOS);
|
||||
outputBytes += urlLen;
|
||||
|
||||
// Username
|
||||
mOS.write(USERNAME_FIELD_TYPE);
|
||||
int userLen = Types.writeCString(mPE.username, mOS);
|
||||
outputBytes += userLen;
|
||||
|
||||
// Password
|
||||
byte[] password = mPE.getPassword();
|
||||
mOS.write(PASSWORD_FIELD_TYPE);
|
||||
mOS.write(Types.writeInt(password.length+1));
|
||||
mOS.write(password);
|
||||
mOS.write(0);
|
||||
outputBytes += password.length + 1;
|
||||
|
||||
// Additional
|
||||
mOS.write(ADDITIONAL_FIELD_TYPE);
|
||||
int addlLen = Types.writeCString(mPE.additional, mOS);
|
||||
outputBytes += addlLen;
|
||||
|
||||
// Create date
|
||||
mOS.write(CREATE_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(Types.writeTime(mPE.tCreation));
|
||||
|
||||
// Modification date
|
||||
mOS.write(MOD_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(Types.writeTime(mPE.tLastMod));
|
||||
|
||||
// Access date
|
||||
mOS.write(ACCESS_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(Types.writeTime(mPE.tLastAccess));
|
||||
|
||||
// Expiration date
|
||||
mOS.write(EXPIRE_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
mOS.write(Types.writeTime(mPE.tExpire));
|
||||
|
||||
// Binary desc
|
||||
mOS.write(BINARY_DESC_FIELD_TYPE);
|
||||
int descLen = Types.writeCString(mPE.binaryDesc, mOS);
|
||||
outputBytes += descLen;
|
||||
|
||||
// Binary data
|
||||
byte[] data = mPE.getBinaryData();
|
||||
mOS.write(BINARY_DATA_FIELD_TYPE);
|
||||
mOS.write(Types.writeInt(data.length));
|
||||
mOS.write(data);
|
||||
outputBytes += data.length;
|
||||
|
||||
// End
|
||||
mOS.write(END_FIELD_TYPE);
|
||||
mOS.write(ZERO_FIELD_SIZE);
|
||||
}
|
||||
|
||||
/** Returns the number of bytes written by the stream
|
||||
* @return Number of bytes written
|
||||
*/
|
||||
public long getLength() {
|
||||
return outputBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,28 @@ import org.phoneid.keepassj2me.PwGroup;
|
||||
import org.phoneid.keepassj2me.Types;
|
||||
|
||||
public class PwGroupOutput {
|
||||
// Constants
|
||||
public static final byte[] GROUPID_FIELD_TYPE = Types.writeShort(1);
|
||||
public static final byte[] NAME_FIELD_TYPE = Types.writeShort(2);
|
||||
public static final byte[] CREATE_FIELD_TYPE = Types.writeShort(3);
|
||||
public static final byte[] MOD_FIELD_TYPE = Types.writeShort(4);
|
||||
public static final byte[] ACCESS_FIELD_TYPE = Types.writeShort(5);
|
||||
public static final byte[] EXPIRE_FIELD_TYPE = Types.writeShort(6);
|
||||
public static final byte[] IMAGEID_FIELD_TYPE = Types.writeShort(7);
|
||||
public static final byte[] LEVEL_FIELD_TYPE = Types.writeShort(8);
|
||||
public static final byte[] FLAGS_FIELD_TYPE = Types.writeShort(9);
|
||||
public static final byte[] END_FIELD_TYPE = Types.writeShort(0xFFFF);
|
||||
public static final byte[] LONG_FOUR = Types.writeInt(4);
|
||||
public static final byte[] GROUPID_FIELD_SIZE = LONG_FOUR;
|
||||
public static final byte[] DATE_FIELD_SIZE = Types.writeInt(5);
|
||||
public static final byte[] IMAGEID_FIELD_SIZE = LONG_FOUR;
|
||||
public static final byte[] LEVEL_FIELD_SIZE = Types.writeInt(2);
|
||||
public static final byte[] FLAGS_FIELD_SIZE = LONG_FOUR;
|
||||
public static final byte[] ZERO_FIELD_SIZE = Types.writeInt(0);
|
||||
|
||||
private OutputStream mOS;
|
||||
private PwGroup mPG;
|
||||
private long outputBytes = 0;
|
||||
|
||||
/** Output the PwGroup to the stream
|
||||
* @param pg
|
||||
@@ -38,9 +58,11 @@ public class PwGroupOutput {
|
||||
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 {
|
||||
|
||||
//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.
|
||||
|
||||
outputBytes += 94; // Length of fixed size fields
|
||||
|
||||
// Group ID
|
||||
mOS.write(GROUPID_FIELD_TYPE);
|
||||
mOS.write(GROUPID_FIELD_SIZE);
|
||||
@@ -48,8 +70,9 @@ public class PwGroupOutput {
|
||||
|
||||
// Name
|
||||
mOS.write(NAME_FIELD_TYPE);
|
||||
Types.writeCString(mPG.name, mOS);
|
||||
|
||||
int nameLen = Types.writeCString(mPG.name, mOS);
|
||||
outputBytes += nameLen;
|
||||
|
||||
// Create date
|
||||
mOS.write(CREATE_FIELD_TYPE);
|
||||
mOS.write(DATE_FIELD_SIZE);
|
||||
@@ -90,21 +113,11 @@ public class PwGroupOutput {
|
||||
mOS.write(ZERO_FIELD_SIZE);
|
||||
}
|
||||
|
||||
public static final byte[] GROUPID_FIELD_TYPE = Types.writeShort(1);
|
||||
public static final byte[] NAME_FIELD_TYPE = Types.writeShort(2);
|
||||
public static final byte[] CREATE_FIELD_TYPE = Types.writeShort(3);
|
||||
public static final byte[] MOD_FIELD_TYPE = Types.writeShort(4);
|
||||
public static final byte[] ACCESS_FIELD_TYPE = Types.writeShort(5);
|
||||
public static final byte[] EXPIRE_FIELD_TYPE = Types.writeShort(6);
|
||||
public static final byte[] IMAGEID_FIELD_TYPE = Types.writeShort(7);
|
||||
public static final byte[] LEVEL_FIELD_TYPE = Types.writeShort(8);
|
||||
public static final byte[] FLAGS_FIELD_TYPE = Types.writeShort(9);
|
||||
public static final byte[] END_FIELD_TYPE = Types.writeShort(0xFFFF);
|
||||
public static final byte[] LONG_FOUR = Types.writeInt(4);
|
||||
public static final byte[] GROUPID_FIELD_SIZE = LONG_FOUR;
|
||||
public static final byte[] DATE_FIELD_SIZE = Types.writeInt(5);
|
||||
public static final byte[] IMAGEID_FIELD_SIZE = LONG_FOUR;
|
||||
public static final byte[] LEVEL_FIELD_SIZE = Types.writeInt(2);
|
||||
public static final byte[] FLAGS_FIELD_SIZE = LONG_FOUR;
|
||||
public static final byte[] ZERO_FIELD_SIZE = Types.writeInt(0);
|
||||
/** Returns the number of bytes written by the stream
|
||||
* @return Number of bytes written
|
||||
*/
|
||||
public long getLength() {
|
||||
return outputBytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
179
src/com/android/keepass/keepasslib/PwManagerOutput.java
Normal file
179
src/com/android/keepass/keepasslib/PwManagerOutput.java
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2009 Brian Pellin.
|
||||
*
|
||||
* This file is part of KeePassDroid.
|
||||
*
|
||||
* 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 2 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.android.keepass.keepasslib;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.phoneid.keepassj2me.PwDbHeader;
|
||||
import org.phoneid.keepassj2me.PwEntry;
|
||||
import org.phoneid.keepassj2me.PwGroup;
|
||||
import org.phoneid.keepassj2me.PwManager;
|
||||
|
||||
public class PwManagerOutput {
|
||||
private PwManager mPM;
|
||||
private OutputStream mOS;
|
||||
private final boolean mDebug;
|
||||
public static final boolean DEBUG = true;
|
||||
|
||||
public PwManagerOutput(PwManager pm, OutputStream os) {
|
||||
mPM = pm;
|
||||
mOS = os;
|
||||
mDebug = false;
|
||||
}
|
||||
|
||||
public PwManagerOutput(PwManager pm, OutputStream os, boolean debug) {
|
||||
mPM = pm;
|
||||
mOS = os;
|
||||
mDebug = debug;
|
||||
}
|
||||
|
||||
public void output() throws IOException, PwManagerOutputException {
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
int filePadding = (int)(16 - (fileSize % 16)); // Pad file to 16-byte boundary
|
||||
if ( filePadding > 0 ) {
|
||||
byte[] padding = new byte[filePadding];
|
||||
fos.write(padding);
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
public void outputHeader(OutputStream os) throws PwManagerOutputException {
|
||||
// Build header
|
||||
PwDbHeader header = new PwDbHeader();
|
||||
header.signature1 = PwDbHeader.PWM_DBSIG_1;
|
||||
header.signature2 = PwDbHeader.PWM_DBSIG_2;
|
||||
header.flags = PwDbHeader.PWM_FLAG_SHA2;
|
||||
|
||||
if ( mPM.getAlgorithm() == PwDbHeader.ALGO_AES ) {
|
||||
header.flags |= PwDbHeader.PWM_FLAG_RIJNDAEL;
|
||||
} else if ( mPM.getAlgorithm() == PwDbHeader.ALGO_TWOFISH ) {
|
||||
header.flags |= PwDbHeader.PWM_FLAG_TWOFISH;
|
||||
throw new PwManagerOutputException("Unsupported algorithm.");
|
||||
} else {
|
||||
throw new PwManagerOutputException("Unsupported algorithm.");
|
||||
}
|
||||
|
||||
header.version = PwDbHeader.PWM_DBVER_DW;
|
||||
header.numGroups = mPM.groups.size();
|
||||
header.numEntries = mPM.entries.size();
|
||||
header.numKeyEncRounds = mPM.getNumKeyEncRecords();
|
||||
|
||||
// Reuse random values to test equivalence in debug mode
|
||||
if ( mDebug ) {
|
||||
System.arraycopy(mPM.dbHeader.encryptionIV, 0, header.encryptionIV, 0, header.encryptionIV.length);
|
||||
System.arraycopy(mPM.dbHeader.masterSeed, 0, header.masterSeed, 0, header.masterSeed.length);
|
||||
System.arraycopy(mPM.dbHeader.masterSeed2, 0, header.masterSeed2, 0, header.masterSeed2.length);
|
||||
} else {
|
||||
SecureRandom random;
|
||||
try {
|
||||
random = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new PwManagerOutputException("Does not support secure random number generation.");
|
||||
}
|
||||
random.nextBytes(header.encryptionIV);
|
||||
random.nextBytes(header.masterSeed);
|
||||
random.nextBytes(header.masterSeed2);
|
||||
}
|
||||
|
||||
// Write checksum Checksum
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
assert true;
|
||||
throw new PwManagerOutputException("SHA-256 not implemented here.");
|
||||
}
|
||||
|
||||
FileOutputStream fos;
|
||||
try {
|
||||
fos = new FileOutputStream("/dev/null");
|
||||
} catch (FileNotFoundException e1) {
|
||||
throw new PwManagerOutputException("Could not open /dev/null");
|
||||
}
|
||||
DigestOutputStream dos = new DigestOutputStream(fos, md);
|
||||
try {
|
||||
outputPlanGroupAndEntries(dos);
|
||||
dos.close();
|
||||
} catch (IOException e) {
|
||||
throw new PwManagerOutputException("Failed to generate checksum.");
|
||||
}
|
||||
|
||||
header.contentsHash = md.digest();
|
||||
|
||||
|
||||
// Output header
|
||||
PwDbHeaderOutput pho = new PwDbHeaderOutput(header, os);
|
||||
try {
|
||||
pho.output();
|
||||
} catch (IOException e) {
|
||||
throw new PwManagerOutputException("Failed to output the header.");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void outputPlanGroupAndEntries(OutputStream os) throws IOException {
|
||||
//long size = 0;
|
||||
|
||||
// Groups
|
||||
for (int i = 0; i < mPM.groups.size(); i++ ) {
|
||||
PwGroup pg = mPM.groups.get(i);
|
||||
PwGroupOutput pgo = new PwGroupOutput(pg, os);
|
||||
pgo.output();
|
||||
//size += pgo.getLength();
|
||||
}
|
||||
|
||||
// Entries
|
||||
for (int i = 0; i < mPM.entries.size(); i++ ) {
|
||||
PwEntry pe = mPM.entries.get(i);
|
||||
PwEntryOutput peo = new PwEntryOutput(pe, os);
|
||||
peo.output();
|
||||
//size += peo.getLength();
|
||||
}
|
||||
|
||||
//return size;
|
||||
}
|
||||
|
||||
class PwManagerOutputException extends Exception {
|
||||
|
||||
public PwManagerOutputException(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 3321212743159473368L;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2009 Brian Pellin.
|
||||
*
|
||||
* This file is part of KeePassDroid.
|
||||
*
|
||||
* 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 2 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.android.keepass.keepasslib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class RandomFileOutputStream extends OutputStream {
|
||||
|
||||
RandomAccessFile mFile;
|
||||
|
||||
RandomFileOutputStream(RandomAccessFile file) {
|
||||
mFile = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
|
||||
mFile.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer, int offset, int count) throws IOException {
|
||||
super.write(buffer, offset, count);
|
||||
|
||||
mFile.write(buffer, offset, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer) throws IOException {
|
||||
super.write(buffer);
|
||||
|
||||
mFile.write(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int oneByte) throws IOException {
|
||||
mFile.write(oneByte);
|
||||
}
|
||||
|
||||
public void seek(long pos) throws IOException {
|
||||
mFile.seek(pos);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,8 +58,16 @@ import com.android.keepass.keepasslib.InvalidKeyFileException;
|
||||
*/
|
||||
public class ImporterV3 {
|
||||
|
||||
public static final boolean DEBUG = true;
|
||||
|
||||
private final boolean mDebug;
|
||||
|
||||
public ImporterV3() {
|
||||
//super();
|
||||
mDebug = false;
|
||||
}
|
||||
|
||||
public ImporterV3(boolean debug) {
|
||||
mDebug = debug;
|
||||
}
|
||||
|
||||
|
||||
@@ -102,12 +110,12 @@ public class ImporterV3 {
|
||||
throw new IOException( "File too short for header" );
|
||||
PwDbHeader hdr = new PwDbHeader( filebuf, 0 );
|
||||
|
||||
if( (hdr.signature1 != PwManager.PWM_DBSIG_1) || (hdr.signature2 != PwManager.PWM_DBSIG_2) ) {
|
||||
if( (hdr.signature1 != PwDbHeader.PWM_DBSIG_1) || (hdr.signature2 != PwDbHeader.PWM_DBSIG_2) ) {
|
||||
//KeePassMIDlet.logS ( "Bad database file signature" );
|
||||
throw new IOException( "Bad database file signature" );
|
||||
}
|
||||
|
||||
if( hdr.version != PwManager.PWM_DBVER_DW ) {
|
||||
if( hdr.version != PwDbHeader.PWM_DBVER_DW ) {
|
||||
//KeePassMIDlet.logS ( "Bad database file version");
|
||||
//throw new IOException( "Bad database file version" );
|
||||
}
|
||||
@@ -116,20 +124,22 @@ public class ImporterV3 {
|
||||
newManager.setMasterKey( password, keyfile );
|
||||
|
||||
// Select algorithm
|
||||
if( (hdr.flags & PwManager.PWM_FLAG_RIJNDAEL) != 0 ) {
|
||||
if( (hdr.flags & PwDbHeader.PWM_FLAG_RIJNDAEL) != 0 ) {
|
||||
//KeePassMIDlet.logS ( "Algorithm AES");
|
||||
newManager.algorithm = PwManager.ALGO_AES;
|
||||
} else if( (hdr.flags & PwManager.PWM_FLAG_TWOFISH) != 0 ) {
|
||||
newManager.algorithm = PwDbHeader.ALGO_AES;
|
||||
} else if( (hdr.flags & PwDbHeader.PWM_FLAG_TWOFISH) != 0 ) {
|
||||
//KeePassMIDlet.logS ( "Algorithm TWOFISH");
|
||||
newManager.algorithm = PwManager.ALGO_TWOFISH;
|
||||
newManager.algorithm = PwDbHeader.ALGO_TWOFISH;
|
||||
} else {
|
||||
throw new IOException( "Unknown algorithm." );
|
||||
}
|
||||
|
||||
if( newManager.algorithm == PwManager.ALGO_TWOFISH )
|
||||
if( newManager.algorithm == PwDbHeader.ALGO_TWOFISH )
|
||||
throw new IOException( "TwoFish algorithm is not supported" );
|
||||
|
||||
newManager.dbHeader = hdr;
|
||||
if ( mDebug ) {
|
||||
newManager.dbHeader = hdr;
|
||||
}
|
||||
|
||||
newManager.numKeyEncRounds = hdr.numKeyEncRounds;
|
||||
|
||||
@@ -180,10 +190,10 @@ public class ImporterV3 {
|
||||
System.arraycopy(filebuf, PwDbHeader.BUF_SIZE, plainContent, 0, encryptedPartSize);
|
||||
*/
|
||||
|
||||
// TODO: Delete Me, temp for debugging
|
||||
newManager.postHeader = new byte[encryptedPartSize];
|
||||
System.arraycopy(filebuf, PwDbHeader.BUF_SIZE, newManager.postHeader, 0, encryptedPartSize);
|
||||
|
||||
if ( mDebug ) {
|
||||
newManager.postHeader = new byte[encryptedPartSize];
|
||||
System.arraycopy(filebuf, PwDbHeader.BUF_SIZE, newManager.postHeader, 0, encryptedPartSize);
|
||||
}
|
||||
|
||||
//if( pRepair == null ) {
|
||||
md = new SHA256Digest();
|
||||
|
||||
@@ -25,6 +25,22 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
package org.phoneid.keepassj2me;
|
||||
|
||||
public class PwDbHeader {
|
||||
|
||||
// DB sig from KeePass 1.03
|
||||
public static final int PWM_DBSIG_1 = 0x9AA2D903;
|
||||
// DB sig from KeePass 1.03
|
||||
public static final int PWM_DBSIG_2 = 0xB54BFB65;
|
||||
// DB sig from KeePass 1.03
|
||||
public static final int PWM_DBVER_DW = 0x00030002;
|
||||
|
||||
public static final int PWM_FLAG_SHA2 = 1;
|
||||
public static final int PWM_FLAG_RIJNDAEL = 2;
|
||||
public static final int PWM_FLAG_ARCFOUR = 4;
|
||||
public static final int PWM_FLAG_TWOFISH = 8;
|
||||
|
||||
public static final int ALGO_AES = 0;
|
||||
public static final int ALGO_TWOFISH = 1;
|
||||
|
||||
/**
|
||||
* Parse given buf, as read from file.
|
||||
* @param buf
|
||||
@@ -46,11 +62,9 @@ public class PwDbHeader {
|
||||
System.arraycopy( buf, offset + 88, masterSeed2, 0, 32 );
|
||||
numKeyEncRounds = Types.readInt( buf, offset + 120 );
|
||||
}
|
||||
|
||||
public PwDbHeader() {
|
||||
|
||||
|
||||
|
||||
public void toBuf( byte[] buf, int offset ) {
|
||||
throw new RuntimeException("Method 'toBuf' not implemented yet");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -47,20 +47,6 @@ public class PwManager {
|
||||
|
||||
// Constants
|
||||
// private static final int PWM_SESSION_KEY_SIZE = 12;
|
||||
// DB sig from KeePass 1.03
|
||||
static final int PWM_DBSIG_1 = 0x9AA2D903;
|
||||
// DB sig from KeePass 1.03
|
||||
static final int PWM_DBSIG_2 = 0xB54BFB65;
|
||||
// DB sig from KeePass 1.03
|
||||
static final int PWM_DBVER_DW = 0x00030002;
|
||||
|
||||
static final int PWM_FLAG_SHA2 = 1;
|
||||
static final int PWM_FLAG_RIJNDAEL = 2;
|
||||
static final int PWM_FLAG_ARCFOUR = 4;
|
||||
static final int PWM_FLAG_TWOFISH = 8;
|
||||
|
||||
static final int ALGO_AES = 0;
|
||||
static final int ALGO_TWOFISH = 1;
|
||||
|
||||
// Descriptive name for database, used in GUI.
|
||||
public String name = "KeePass database";
|
||||
@@ -88,7 +74,15 @@ public class PwManager {
|
||||
|
||||
// root group
|
||||
PwGroup rootGroup;
|
||||
|
||||
|
||||
public int getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public int getNumKeyEncRecords() {
|
||||
return numKeyEncRounds;
|
||||
}
|
||||
|
||||
public void setMasterKey( String key, String keyFileName ) throws InvalidKeyFileException, IOException {
|
||||
assert( key != null && keyFileName != null );
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ package org.phoneid.keepassj2me;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
@@ -219,16 +218,6 @@ public class Types {
|
||||
//return null;
|
||||
}
|
||||
|
||||
public static void writeCString(String str, OutputStream os) throws IOException {
|
||||
byte[] initial = str.getBytes("UTF-8");
|
||||
|
||||
int length = initial.length;
|
||||
os.write(writeInt(length+1));
|
||||
os.write(initial);
|
||||
os.write(0x00);
|
||||
|
||||
}
|
||||
|
||||
public static byte[] writeTime(Date date) {
|
||||
byte[] buf = new byte[5];
|
||||
Calendar cal = Calendar.getInstance();
|
||||
@@ -250,4 +239,16 @@ public class Types {
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
public static int writeCString(String str, OutputStream os) throws IOException {
|
||||
byte[] initial = str.getBytes("UTF-8");
|
||||
|
||||
int length = initial.length+1;
|
||||
os.write(writeInt(length));
|
||||
os.write(initial);
|
||||
os.write(0x00);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 Brian Pellin.
|
||||
*
|
||||
* This file is part of KeePassDroid.
|
||||
*
|
||||
* 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.android.keepass.tests;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Calendar;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.phoneid.keepassj2me.PwEntry;
|
||||
import org.phoneid.keepassj2me.PwGroup;
|
||||
import org.phoneid.keepassj2me.PwManager;
|
||||
import org.phoneid.keepassj2me.Types;
|
||||
|
||||
import com.android.keepass.keepasslib.PwEntryOutput;
|
||||
import com.android.keepass.keepasslib.PwGroupOutput;
|
||||
|
||||
public class PwGroupOutputTest extends TestCase {
|
||||
PwManager mPM;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
mPM = TestData.GetTest1();
|
||||
|
||||
}
|
||||
|
||||
public void testPlainContent() throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
// Groups
|
||||
for (int i = 0; i < mPM.groups.size(); i++ ) {
|
||||
PwGroup pg = mPM.groups.get(i);
|
||||
PwGroupOutput pgo = new PwGroupOutput(pg, bos);
|
||||
pgo.output();
|
||||
}
|
||||
|
||||
// Entries
|
||||
for (int i = 0; i < mPM.entries.size(); i++ ) {
|
||||
boolean debug;
|
||||
PwEntry pe = mPM.entries.get(i);
|
||||
PwEntryOutput peo = new PwEntryOutput(pe, bos);
|
||||
|
||||
debug = (i == 1);
|
||||
peo.output();
|
||||
}
|
||||
|
||||
byte[] buf = bos.toByteArray();
|
||||
assertArrayEquals(mPM.postHeader, bos.toByteArray());
|
||||
|
||||
}
|
||||
|
||||
public void testChecksum() throws NoSuchAlgorithmException, IOException {
|
||||
FileOutputStream fos = new FileOutputStream("/dev/null");
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
DigestOutputStream dos = new DigestOutputStream(fos, md);
|
||||
|
||||
// Groups
|
||||
for (int i = 0; i < mPM.groups.size(); i++ ) {
|
||||
PwGroup pg = mPM.groups.get(i);
|
||||
PwGroupOutput pgo = new PwGroupOutput(pg, dos);
|
||||
pgo.output();
|
||||
}
|
||||
|
||||
// Entries
|
||||
for (int i = 0; i < mPM.entries.size(); i++ ) {
|
||||
PwEntry pe = mPM.entries.get(i);
|
||||
PwEntryOutput peo = new PwEntryOutput(pe, dos);
|
||||
peo.output();
|
||||
}
|
||||
|
||||
assertArrayEquals("Hash of groups and entries failed.", md.digest(), mPM.dbHeader.contentsHash);
|
||||
}
|
||||
}
|
||||
96
tests/src/com/android/keepass/tests/PwManagerOutputTest.java
Normal file
96
tests/src/com/android/keepass/tests/PwManagerOutputTest.java
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2009 Brian Pellin.
|
||||
*
|
||||
* This file is part of KeePassDroid.
|
||||
*
|
||||
* 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.android.keepass.tests;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.phoneid.keepassj2me.PwManager;
|
||||
|
||||
import com.android.keepass.keepasslib.PwDbHeaderOutput;
|
||||
import com.android.keepass.keepasslib.PwManagerOutput;
|
||||
|
||||
public class PwManagerOutputTest extends TestCase {
|
||||
PwManager mPM;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
mPM = TestData.GetTest1();
|
||||
|
||||
}
|
||||
|
||||
public void testPlainContent() throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
PwManagerOutput pos = new PwManagerOutput(mPM, bos, PwManagerOutput.DEBUG);
|
||||
pos.outputPlanGroupAndEntries(bos);
|
||||
|
||||
assertArrayEquals("Group and entry output doesn't match.", mPM.postHeader, bos.toByteArray());
|
||||
|
||||
}
|
||||
|
||||
public void testChecksum() throws NoSuchAlgorithmException, IOException {
|
||||
FileOutputStream fos = new FileOutputStream("/dev/null");
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
DigestOutputStream dos = new DigestOutputStream(fos, md);
|
||||
|
||||
PwManagerOutput pos = new PwManagerOutput(mPM, dos, PwManagerOutput.DEBUG);
|
||||
pos.outputPlanGroupAndEntries(dos);
|
||||
|
||||
assertArrayEquals("Hash of groups and entries failed.", md.digest(), mPM.dbHeader.contentsHash);
|
||||
}
|
||||
|
||||
public void testHeader() throws Exception {
|
||||
ByteArrayOutputStream bActual = new ByteArrayOutputStream();
|
||||
PwManagerOutput pActual = new PwManagerOutput(mPM, bActual, PwManagerOutput.DEBUG);
|
||||
pActual.outputHeader(bActual);
|
||||
|
||||
ByteArrayOutputStream bExpected = new ByteArrayOutputStream();
|
||||
PwDbHeaderOutput outExpected = new PwDbHeaderOutput(mPM.dbHeader, bExpected);
|
||||
outExpected.output();
|
||||
|
||||
assertArrayEquals("Header does not match.", bExpected.toByteArray(), bActual.toByteArray());
|
||||
}
|
||||
|
||||
/*
|
||||
public void testEncryptedPart() throws Exception {
|
||||
File file = new File("/sdcard/test1.kdb");
|
||||
long length = file.length();
|
||||
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
byte[] expected = new byte[(int)(length-PwDbHeader.BUF_SIZE)];
|
||||
|
||||
fis.skip(PwDbHeader.BUF_SIZE);
|
||||
fis.read(expected);
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public class TestData {
|
||||
|
||||
if ( test1 == null ) {
|
||||
FileInputStream fis = new FileInputStream("/sdcard/test1.kdb");
|
||||
ImporterV3 importer = new ImporterV3();
|
||||
ImporterV3 importer = new ImporterV3(ImporterV3.DEBUG);
|
||||
test1 = importer.openDatabase(fis, "12345", "");
|
||||
if (test1 != null) {
|
||||
test1.constructTree(null);
|
||||
|
||||
Reference in New Issue
Block a user