Get enough in to fully parse a .kdbx database and load it into java objects.

This commit is contained in:
Brian Pellin
2010-06-02 20:33:07 -05:00
parent bd7f48e0e7
commit 2380579654
39 changed files with 2569 additions and 149 deletions

15
LICENSE
View File

@@ -119,3 +119,18 @@ Files under jni/openssl-0.98l/*:
*
*/
Files under biz.source_code.base64Coder
// Copyright 2003-2010 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
// www.source-code.biz, www.inventec.ch/chdh
//
// This module is multi-licensed and may be used under the terms
// of any of the following licenses:
//
// EPL, Eclipse Public License, http://www.eclipse.org/legal
// LGPL, GNU Lesser General Public License, http://www.gnu.org/licenses/lgpl.html
// AL, Apache License, http://www.apache.org/licenses
// BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php
//
// Please contact the author if you need another license.
// This module is provided "as is", without warranties of any kind.

View File

@@ -0,0 +1,225 @@
// Copyright 2003-2010 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
// www.source-code.biz, www.inventec.ch/chdh
//
// This module is multi-licensed and may be used under the terms
// of any of the following licenses:
//
// EPL, Eclipse Public License, http://www.eclipse.org/legal
// LGPL, GNU Lesser General Public License, http://www.gnu.org/licenses/lgpl.html
// AL, Apache License, http://www.apache.org/licenses
// BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php
//
// Please contact the author if you need another license.
// This module is provided "as is", without warranties of any kind.
package biz.source_code.base64Coder;
/**
* A Base64 encoder/decoder.
*
* <p>
* This class is used to encode and decode data in Base64 format as described in RFC 1521.
*
* <p>
* Project home page: <a href="http://www.source-code.biz/base64coder/java/">www.source-code.biz/base64coder/java</a><br>
* Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>
* Multi-licensed: EPL / LGPL / AL / BSD.
*/
public class Base64Coder {
// The line separator string of the operating system.
private static final String systemLineSeparator = System.getProperty("line.separator");
// Mapping table from 6-bit nibbles to Base64 characters.
private static char[] map1 = new char[64];
static {
int i=0;
for (char c='A'; c<='Z'; c++) map1[i++] = c;
for (char c='a'; c<='z'; c++) map1[i++] = c;
for (char c='0'; c<='9'; c++) map1[i++] = c;
map1[i++] = '+'; map1[i++] = '/'; }
// Mapping table from Base64 characters to 6-bit nibbles.
private static byte[] map2 = new byte[128];
static {
for (int i=0; i<map2.length; i++) map2[i] = -1;
for (int i=0; i<64; i++) map2[map1[i]] = (byte)i; }
/**
* Encodes a string into Base64 format.
* No blanks or line breaks are inserted.
* @param s A String to be encoded.
* @return A String containing the Base64 encoded data.
*/
public static String encodeString (String s) {
return new String(encode(s.getBytes())); }
/**
* Encodes a byte array into Base 64 format and breaks the output into lines of 76 characters.
* This method is compatible with <code>sun.misc.BASE64Encoder.encodeBuffer(byte[])</code>.
* @param in An array containing the data bytes to be encoded.
* @return A String containing the Base64 encoded data, broken into lines.
*/
public static String encodeLines (byte[] in) {
return encodeLines(in, 0, in.length, 76, systemLineSeparator); }
/**
* Encodes a byte array into Base 64 format and breaks the output into lines.
* @param in An array containing the data bytes to be encoded.
* @param iOff Offset of the first byte in <code>in</code> to be processed.
* @param iLen Number of bytes to be processed in <code>in</code>, starting at <code>iOff</code>.
* @param lineLen Line length for the output data. Should be a multiple of 4.
* @param lineSeparator The line separator to be used to separate the output lines.
* @return A String containing the Base64 encoded data, broken into lines.
*/
public static String encodeLines (byte[] in, int iOff, int iLen, int lineLen, String lineSeparator) {
int blockLen = (lineLen*3) / 4;
if (blockLen <= 0) throw new IllegalArgumentException();
int lines = (iLen+blockLen-1) / blockLen;
int bufLen = ((iLen+2)/3)*4 + lines*lineSeparator.length();
StringBuilder buf = new StringBuilder(bufLen);
int ip = 0;
while (ip < iLen) {
int l = Math.min(iLen-ip, blockLen);
buf.append (encode(in, iOff+ip, l));
buf.append (lineSeparator);
ip += l; }
return buf.toString(); }
/**
* Encodes a byte array into Base64 format.
* No blanks or line breaks are inserted in the output.
* @param in An array containing the data bytes to be encoded.
* @return A character array containing the Base64 encoded data.
*/
public static char[] encode (byte[] in) {
return encode(in, 0, in.length); }
/**
* Encodes a byte array into Base64 format.
* No blanks or line breaks are inserted in the output.
* @param in An array containing the data bytes to be encoded.
* @param iLen Number of bytes to process in <code>in</code>.
* @return A character array containing the Base64 encoded data.
*/
public static char[] encode (byte[] in, int iLen) {
return encode(in, 0, iLen); }
/**
* Encodes a byte array into Base64 format.
* No blanks or line breaks are inserted in the output.
* @param in An array containing the data bytes to be encoded.
* @param iOff Offset of the first byte in <code>in</code> to be processed.
* @param iLen Number of bytes to process in <code>in</code>, starting at <code>iOff</code>.
* @return A character array containing the Base64 encoded data.
*/
public static char[] encode (byte[] in, int iOff, int iLen) {
int oDataLen = (iLen*4+2)/3; // output length without padding
int oLen = ((iLen+2)/3)*4; // output length including padding
char[] out = new char[oLen];
int ip = iOff;
int iEnd = iOff + iLen;
int op = 0;
while (ip < iEnd) {
int i0 = in[ip++] & 0xff;
int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
int o0 = i0 >>> 2;
int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
int o3 = i2 & 0x3F;
out[op++] = map1[o0];
out[op++] = map1[o1];
out[op] = op < oDataLen ? map1[o2] : '='; op++;
out[op] = op < oDataLen ? map1[o3] : '='; op++; }
return out; }
/**
* Decodes a string from Base64 format.
* No blanks or line breaks are allowed within the Base64 encoded input data.
* @param s A Base64 String to be decoded.
* @return A String containing the decoded data.
* @throws IllegalArgumentException If the input is not valid Base64 encoded data.
*/
public static String decodeString (String s) {
return new String(decode(s)); }
/**
* Decodes a byte array from Base64 format and ignores line separators, tabs and blanks.
* CR, LF, Tab and Space characters are ignored in the input data.
* This method is compatible with <code>sun.misc.BASE64Decoder.decodeBuffer(String)</code>.
* @param s A Base64 String to be decoded.
* @return An array containing the decoded data bytes.
* @throws IllegalArgumentException If the input is not valid Base64 encoded data.
*/
public static byte[] decodeLines (String s) {
char[] buf = new char[s.length()];
int p = 0;
for (int ip = 0; ip < s.length(); ip++) {
char c = s.charAt(ip);
if (c != ' ' && c != '\r' && c != '\n' && c != '\t')
buf[p++] = c; }
return decode(buf, 0, p); }
/**
* Decodes a byte array from Base64 format.
* No blanks or line breaks are allowed within the Base64 encoded input data.
* @param s A Base64 String to be decoded.
* @return An array containing the decoded data bytes.
* @throws IllegalArgumentException If the input is not valid Base64 encoded data.
*/
public static byte[] decode (String s) {
return decode(s.toCharArray()); }
/**
* Decodes a byte array from Base64 format.
* No blanks or line breaks are allowed within the Base64 encoded input data.
* @param in A character array containing the Base64 encoded data.
* @return An array containing the decoded data bytes.
* @throws IllegalArgumentException If the input is not valid Base64 encoded data.
*/
public static byte[] decode (char[] in) {
return decode(in, 0, in.length); }
/**
* Decodes a byte array from Base64 format.
* No blanks or line breaks are allowed within the Base64 encoded input data.
* @param in A character array containing the Base64 encoded data.
* @param iOff Offset of the first character in <code>in</code> to be processed.
* @param iLen Number of characters to process in <code>in</code>, starting at <code>iOff</code>.
* @return An array containing the decoded data bytes.
* @throws IllegalArgumentException If the input is not valid Base64 encoded data.
*/
public static byte[] decode (char[] in, int iOff, int iLen) {
if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4.");
while (iLen > 0 && in[iOff+iLen-1] == '=') iLen--;
int oLen = (iLen*3) / 4;
byte[] out = new byte[oLen];
int ip = iOff;
int iEnd = iOff + iLen;
int op = 0;
while (ip < iEnd) {
int i0 = in[ip++];
int i1 = in[ip++];
int i2 = ip < iEnd ? in[ip++] : 'A';
int i3 = ip < iEnd ? in[ip++] : 'A';
if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
int b0 = map2[i0];
int b1 = map2[i1];
int b2 = map2[i2];
int b3 = map2[i3];
if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
int o0 = ( b0 <<2) | (b1>>>4);
int o1 = ((b1 & 0xf)<<4) | (b2>>>2);
int o2 = ((b2 & 3)<<6) | b3;
out[op++] = (byte)o0;
if (op<oLen) out[op++] = (byte)o1;
if (op<oLen) out[op++] = (byte)o2; }
return out; }
// Dummy constructor.
private Base64Coder() {}
} // end class Base64Coder

View File

@@ -50,7 +50,6 @@ import com.keepassdroid.database.load.ImporterFactory;
import com.keepassdroid.database.load.ImporterV3;
import com.keepassdroid.database.save.PwDbOutput;
import com.keepassdroid.search.SearchDbHelper;
import com.keepassdroid.utils.Types;
/**
* @author bpellin
@@ -206,7 +205,7 @@ public class Database {
for (int i = 0; i < childEntries.size(); i++ ) {
PwEntry cur = childEntries.elementAt(i);
entries.put(Types.bytestoUUID(cur.uuid), new WeakReference<PwEntry>(cur));
entries.put(cur.getUUID(), new WeakReference<PwEntry>(cur));
}
for (int i = 0; i < childGroups.size(); i++ ) {

View File

@@ -77,7 +77,7 @@ public class EntryActivity extends LockCloseActivity {
public static void Launch(Activity act, PwEntry pw, int pos) {
Intent i = new Intent(act, EntryActivity.class);
i.putExtra(KEY_ENTRY, pw.uuid);
i.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
i.putExtra(KEY_REFRESH_POS, pos);
act.startActivityForResult(i,0);

View File

@@ -70,7 +70,7 @@ public class EntryEditActivity extends LockCloseActivity {
Intent i = new Intent(act, EntryEditActivity.class);
i.putExtra(KEY_ENTRY, pw.uuid);
i.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
act.startActivityForResult(i, 0);
}
@@ -155,7 +155,7 @@ public class EntryEditActivity extends LockCloseActivity {
newEntry.parent = mEntry.parent;
newEntry.tCreation = mEntry.tCreation;
newEntry.tExpire = mEntry.tExpire;
newEntry.uuid = mEntry.uuid;
newEntry.setUUID(mEntry.getUUID());
Date now = Calendar.getInstance().getTime();
newEntry.tLastAccess = new PwDate(now);

View File

@@ -0,0 +1,65 @@
/*
* 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.keepassdroid.crypto;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.bouncycastle.crypto.StreamCipher;
import org.bouncycastle.crypto.engines.Salsa20Engine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import com.keepassdroid.database.CrsAlgorithm;
public class PwStreamCipherFactory {
public static StreamCipher getInstance(CrsAlgorithm alg, byte[] key) {
if ( alg == CrsAlgorithm.Salsa20 ) {
return getSalsa20(key);
} else {
return null;
}
}
private static final byte[] SALSA_IV = new byte[]{ (byte)0xE8, 0x30, 0x09, 0x4B,
(byte)0x97, 0x20, 0x5D, 0x2A };
private static StreamCipher getSalsa20(byte[] key) {
// Build stream cipher key
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("SHA 256 not supported");
}
byte[] key32 = md.digest(key);
KeyParameter keyParam = new KeyParameter(key32);
ParametersWithIV ivParam = new ParametersWithIV(keyParam, SALSA_IV);
StreamCipher cipher = new Salsa20Engine();
cipher.init(true, ivParam);
return cipher;
}
}

View File

@@ -19,10 +19,27 @@
*/
package com.keepassdroid.database;
public class CrsAlgorithm {
public static final int Null = 0;
public static final int ArcFourVariant = 1;
public static final int Salsa20 = 2;
public enum CrsAlgorithm {
public static final int Count = 3;
Null(0),
ArcFourVariant(1),
Salsa20(2);
public static final int count = 3;
private final int id;
private CrsAlgorithm(int num) {
id = num;
}
public static CrsAlgorithm fromId(int num) {
for ( CrsAlgorithm e : CrsAlgorithm.values() ) {
if ( e.id == num ) {
return e;
}
}
return null;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2010 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.keepassdroid.database;
import java.util.Date;
public interface ITimeLogger {
Date getLastModificationTime();
void setLastModificationTime(Date date);
Date getCreationTime();
void setCreationTime(Date date);
Date getLastAccessTime();
void setLastAccessTime(Date date);
Date getExpiryTime();
void setExpiryTime(Date date);
boolean expires();
void setExpires(boolean exp);
long getUsageCount();
void setUsageCount(long count);
Date getLocationChanged();
void setLocationChanged(Date date);
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2010 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.keepassdroid.database;
import java.util.UUID;
public class PwCustomIcon {
private UUID uuid;
private byte[] imageData;
public PwCustomIcon(UUID u, byte[] data) {
uuid = u;
imageData = data;
}
}

View File

@@ -38,6 +38,7 @@ public abstract class PwDatabase {
public byte masterKey[] = new byte[32];
public byte[] finalKey;
public String name = "KeePass database";
public void makeFinalKey(byte[] masterSeed, byte[] masterSeed2, int numRounds) throws IOException {

View File

@@ -48,9 +48,6 @@ public class PwDatabaseV3 extends PwDatabase {
// Constants
// private static final int PWM_SESSION_KEY_SIZE = 12;
// Descriptive name for database, used in GUI.
public String name = "KeePass database";
// Special entry for settings
public PwEntry metaInfo;
@@ -153,7 +150,7 @@ public class PwDatabaseV3 extends PwDatabase {
groups.addElement(group);
}
public void addEntry(PwEntryV3 entry)
public void addEntry(PwEntry entry)
{
entries.addElement(entry);
}

View File

@@ -20,15 +20,17 @@
package com.keepassdroid.database;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.Vector;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.keepassdroid.database.exception.InconsistentDBException;
import com.keepassdroid.database.exception.InvalidKeyFileException;
@@ -38,10 +40,48 @@ public class PwDatabaseV4 extends PwDatabase {
public UUID dataCipher;
public PwCompressionAlgorithm compressionAlgorithm;
public long numKeyEncRounds;
private Document doc;
public Date nameChanged;
public String description;
public Date descriptionChanged;
public String defaultUserName;
public Date defaultUserNameChanged;
public long maintenanceHistoryDays;
public boolean recycleBinEnabled;
public UUID recycleBinUUID;
public Date recycleBinChanged;
public UUID entryTemplatesGroup;
public Date entryTemplatesGroupChanged;
public UUID lastSelectedGroup;
public UUID lastTopVisibleGroup;
public MemoryProtectionConfig memoryProtection = new MemoryProtectionConfig();
public List<PwDeletedObject> deletedObjects = new ArrayList<PwDeletedObject>();
public List<PwCustomIcon> customIcons;
public Map<String, String> customData = new HashMap<String, String>();
public class MemoryProtectionConfig {
public boolean protectTitle = false;
public boolean protectUserName = false;
public boolean protectPassword = false;
public boolean protectUrl = false;
public boolean protectNotes = false;
public boolean autoEnableVisualHiding = false;
public boolean GetProtection(String field) {
if ( field.equalsIgnoreCase(PwDefsV4.TITLE_FIELD)) return protectTitle;
if ( field.equalsIgnoreCase(PwDefsV4.USERNAME_FIELD)) return protectUserName;
if ( field.equalsIgnoreCase(PwDefsV4.PASSWORD_FIELD)) return protectPassword;
if ( field.equalsIgnoreCase(PwDefsV4.URL_FIELD)) return protectUrl;
if ( field.equalsIgnoreCase(PwDefsV4.NOTES_FIELD)) return protectNotes;
return false;
}
}
public static final UUID UUID_ZERO = new UUID(0,0);
//private Vector<PwGroupV4> groups = new Vector<PwGroupV4>();
private PwGroupV4 rootGroup;
public PwGroupV4 rootGroup;
@Override
public byte[] getMasterKey(String key, String keyFileName)
@@ -83,19 +123,8 @@ public class PwDatabaseV4 extends PwDatabase {
return list;
}
public void parseDB(Document d) throws InconsistentDBException {
doc = d;
NodeList list = doc.getElementsByTagName("Root");
int len = list.getLength();
if ( len < 0 || len > 1 ) {
throw new InconsistentDBException("Missing root node");
}
Node root = list.item(0);
rootGroup = new PwGroupV4(root);
public void parseDB(InputStream in) throws InconsistentDBException {
//TODO Implement Me
}
@Override

View File

@@ -52,7 +52,7 @@ public class PwDbHeaderV4 extends PwDbHeader {
private PwDatabaseV4 db;
public byte[] protectedStreamKey;
public byte[] streamStartBytes;
public int innerRandomStream;
public CrsAlgorithm innerRandomStream;
public PwDbHeaderV4(PwDatabaseV4 d) {
db = d;
@@ -190,11 +190,11 @@ public class PwDbHeaderV4 extends PwDbHeader {
}
int id = LEDataInputStream.readInt(streamID, 0);
if ( id < 0 || id >= CrsAlgorithm.Count ) {
if ( id < 0 || id >= CrsAlgorithm.count ) {
throw new IOException("Invalid stream id.");
}
innerRandomStream = id;
innerRandomStream = CrsAlgorithm.fromId(id);
}
/** Determines if this is a supported version.

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2010 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.keepassdroid.database;
public class PwDefsV4 {
public static final String TITLE_FIELD = "Title";
public static final String USERNAME_FIELD = "UserName";
public static final String PASSWORD_FIELD = "Password";
public static final String URL_FIELD = "URL";
public static final String NOTES_FIELD = "Notes";
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2010 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.keepassdroid.database;
import java.util.Date;
import java.util.UUID;
public class PwDeletedObject {
public UUID uuid;
private Date deletionTime;
public Date getDeletionTime() {
if ( deletionTime == null ) {
return new Date(System.currentTimeMillis());
}
return deletionTime;
}
public void setDeletionTime(Date date) {
deletionTime = date;
}
}

View File

@@ -20,13 +20,15 @@
package com.keepassdroid.database;
import java.util.Date;
import java.util.UUID;
public abstract class PwEntry implements Cloneable {
public byte uuid[] = new byte[16];
//public byte uuid[] = new byte[16];
public String title;
public String url;
public String additional;
public int imageId;
public PwEntry() {
@@ -42,7 +44,7 @@ public abstract class PwEntry implements Cloneable {
throw new RuntimeException("Clone should be supported");
}
System.arraycopy(uuid, 0, newEntry.uuid, 0, uuid.length);
newEntry.setUUID(getUUID());
newEntry.title = title;
newEntry.url = url;
newEntry.additional = additional;
@@ -51,14 +53,16 @@ public abstract class PwEntry implements Cloneable {
}
public void assign(PwEntry source) {
System.arraycopy(source.uuid, 0, uuid, 0, source.uuid.length);
setUUID(source.getUUID());
title = source.title;
url = source.url;
additional = source.additional;
}
public abstract void stampLastAccess();
public abstract UUID getUUID();
public abstract void setUUID(UUID u);
public abstract String getUsername();
public abstract String getPassword();
public abstract Date getCreate();

View File

@@ -29,11 +29,13 @@ import java.lang.ref.WeakReference;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
import android.util.Log;
import com.keepassdroid.Database;
import com.keepassdroid.utils.Types;
/**
@@ -71,11 +73,9 @@ public class PwEntryV3 extends PwEntry {
public int groupId;
public int imageId;
public String username;
public String username;
private byte[] password;
private byte[] uuid;
public PwDate tCreation;
public PwDate tLastMod;
@@ -315,11 +315,6 @@ public class PwEntryV3 extends PwEntry {
}
@Override
public String getUsername() {
return username;
}
@Override
public Date getAccess() {
return tLastAccess.getJDate();
@@ -345,4 +340,19 @@ public class PwEntryV3 extends PwEntry {
return parent;
}
@Override
public UUID getUUID() {
return Types.bytestoUUID(uuid);
}
@Override
public void setUUID(UUID u) {
uuid = Types.UUIDtoBytes(u);
}
@Override
public String getUsername() {
return username;
}
}

View File

@@ -19,15 +19,50 @@
*/
package com.keepassdroid.database;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.w3c.dom.Node;
public class PwEntryV4 extends PwEntry {
private Node node;
public class PwEntryV4 extends PwEntry implements ITimeLogger {
private static final String STR_USERNAME = "UserName";
public PwEntryV4(Node n) {
node = n;
public PwGroupV4 parent;
public UUID uuid;
public Map<String, String> strings = new HashMap<String, String>();
public Map<String, byte[]> binaries = new HashMap<String, byte[]>();
public UUID customIconUuid;
public String foregroundColor;
public String backgroupColor;
public String overrideURL;
public AutoType autoType = new AutoType();
public List<PwEntryV4> history = new ArrayList<PwEntryV4>();
private Date parentGroupLastMod;
private Date creation;
private Date lastMod;
private Date lastAccess;
private Date expireDate;
private boolean expires = false;
private long usageCount = 0;
public class AutoType {
public boolean enabled;
public long obfuscationOptions;
public String defaultSequence;
private Map<String, String> windowSeqPairs = new HashMap<String, String>();
public void put(String key, String value) {
windowSeqPairs.put(key, value);
}
}
public PwEntryV4() {
}
@Override
@@ -43,7 +78,7 @@ public class PwEntryV4 extends PwEntry {
}
private void assign(PwEntryV4 source) {
node = source.node;
// TODO: Implement me
}
@Override
@@ -60,50 +95,131 @@ public class PwEntryV4 extends PwEntry {
@Override
public String getUsername() {
// TODO Implement me
return null;
return getString(STR_USERNAME);
}
@Override
public String getPassword() {
// TODO Implement me
return null;
}
@Override
public Date getAccess() {
// TODO Auto-generated method stub
return null;
}
@Override
public Date getCreate() {
// TODO Auto-generated method stub
return null;
}
@Override
public Date getExpire() {
// TODO Auto-generated method stub
return null;
}
@Override
public Date getMod() {
// TODO Auto-generated method stub
return null;
return parentGroupLastMod;
}
@Override
public String getDisplayTitle() {
// TOOD: Add special TAN handling for V4?
return title;
}
@Override
public PwGroupV4 getParent() {
// TODO Auto-generated method stub
return null;
return parent;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public void setUUID(UUID u) {
uuid = u;
}
public String getString(String key) {
String value = strings.get(key);
if ( value == null ) return new String("");
return value;
}
@Override
public Date getCreationTime() {
return creation;
}
@Override
public Date getExpiryTime() {
return expireDate;
}
@Override
public Date getLastAccessTime() {
return lastAccess;
}
@Override
public Date getLastModificationTime() {
return lastMod;
}
@Override
public Date getLocationChanged() {
return parentGroupLastMod;
}
@Override
public long getUsageCount() {
return usageCount;
}
@Override
public void setCreationTime(Date date) {
creation = date;
}
@Override
public void setExpiryTime(Date date) {
expireDate = date;
}
@Override
public void setLastAccessTime(Date date) {
lastAccess = date;
}
@Override
public void setLastModificationTime(Date date) {
lastMod = date;
}
@Override
public void setLocationChanged(Date date) {
parentGroupLastMod = date;
}
@Override
public void setUsageCount(long count) {
usageCount = count;
}
@Override
public boolean expires() {
return expires;
}
@Override
public void setExpires(boolean exp) {
expires = exp;
}
}

View File

@@ -19,6 +19,7 @@
*/
package com.keepassdroid.database;
import java.util.Date;
import java.util.Vector;
public abstract class PwGroup {
@@ -30,5 +31,7 @@ public abstract class PwGroup {
public abstract PwGroupId getId();
public abstract String getName();
public abstract Date getLastMod();
}

View File

@@ -118,4 +118,9 @@ public class PwGroupV3 extends PwGroup {
return name;
}
@Override
public Date getLastMod() {
return tLastMod.getJDate();
}
}

View File

@@ -19,45 +19,70 @@
*/
package com.keepassdroid.database;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class PwGroupV4 extends PwGroup implements ITimeLogger {
import com.keepassdroid.database.exception.InconsistentDBException;
public class PwGroupV4 extends PwGroup {
private Node node;
private PwGroup parent;
public PwGroupV4(Node n) throws InconsistentDBException {
this(n, null);
}
public PwGroupV4(Node n, PwGroup p) throws InconsistentDBException {
node = n;
parent = p;
buildTree();
}
private void buildTree() throws InconsistentDBException {
NodeList children = node.getChildNodes();
public PwGroupV4 parent = null;
public UUID uuid;
public String name;
public String notes;
public int iconId;
public UUID customIconUuid;
public boolean isExpanded;
public String defaultAutoTypeSequence;
public Boolean enableAutoType;
public Boolean enableSearching;
public UUID lastTopVisibleEntry;
private Date parentGroupLastMod;
private Date creation;
private Date lastMod;
private Date lastAccess;
private Date expireDate;
private boolean expires = false;
private long usageCount = 0;
public List<PwGroupV4> listGroups = new ArrayList<PwGroupV4>();
public List<PwEntryV4> listEntries = new ArrayList<PwEntryV4>();
public PwGroupV4() {
for ( int i = 0; i < children.getLength(); i++ ) {
Node child = children.item(i);
String name = child.getNodeName();
if ( name.equalsIgnoreCase("Group") ) {
PwGroupV4 group = new PwGroupV4(child, this);
childGroups.add(group);
} else if ( name.equalsIgnoreCase("Entry") ) {
PwEntryV4 entry = new PwEntryV4(child);
childEntries.add(entry);
}
}
}
public void AddGroup(PwGroupV4 subGroup, boolean takeOwnership) {
AddGroup(subGroup, takeOwnership, false);
}
public void AddGroup(PwGroupV4 subGroup, boolean takeOwnership, boolean updateLocationChanged) {
if ( subGroup == null ) throw new RuntimeException("subGroup");
listGroups.add(subGroup);
if ( takeOwnership ) subGroup.parent = this;
if ( updateLocationChanged ) subGroup.parentGroupLastMod = new Date(System.currentTimeMillis());
}
public void AddEntry(PwEntryV4 pe, boolean takeOwnership) {
AddEntry(pe, takeOwnership, false);
}
public void AddEntry(PwEntryV4 pe, boolean takeOwnership, boolean updateLocationChanged) {
assert(pe != null);
listEntries.add(pe);
if ( takeOwnership ) pe.parent = this;
if ( updateLocationChanged ) pe.setLocationChanged(new Date(System.currentTimeMillis()));
}
@Override
public PwGroup getParent() {
return parent;
@@ -87,18 +112,88 @@ public class PwGroupV4 extends PwGroup {
@Override
public PwGroupId getId() {
return new PwGroupIdV4(getUUID());
}
public UUID getUUID() {
// TODO: Get UUID from document
return null;
return new PwGroupIdV4(uuid);
}
@Override
public String getName() {
// TODO Auto-generated method stub
return null;
return name;
}
@Override
public Date getLastMod() {
return parentGroupLastMod;
}
@Override
public Date getCreationTime() {
return creation;
}
@Override
public Date getExpiryTime() {
return expireDate;
}
@Override
public Date getLastAccessTime() {
return lastAccess;
}
@Override
public Date getLastModificationTime() {
return lastMod;
}
@Override
public Date getLocationChanged() {
return parentGroupLastMod;
}
@Override
public long getUsageCount() {
return usageCount;
}
@Override
public void setCreationTime(Date date) {
creation = date;
}
@Override
public void setExpiryTime(Date date) {
expireDate = date;
}
@Override
public void setLastAccessTime(Date date) {
lastAccess = date;
}
@Override
public void setLastModificationTime(Date date) {
lastMod = date;
}
@Override
public void setLocationChanged(Date date) {
parentGroupLastMod = date;
}
@Override
public void setUsageCount(long count) {
usageCount = count;
}
@Override
public boolean expires() {
return expires;
}
@Override
public void setExpires(boolean exp) {
expires = exp;
}

View File

@@ -34,7 +34,7 @@ public abstract class AddEntry extends RunnableOnFinish {
public static AddEntry getInstance(Database db, PwEntry entry, OnFinish finish) {
if ( entry instanceof PwEntryV3 ) {
return new AddEntryV3(db, (PwEntryV3) entry, finish);
return new AddEntryV3(db, (PwEntry) entry, finish);
} else {
// TODO: Implement me
throw new RuntimeException("Not implemented yet.");
@@ -76,7 +76,7 @@ public abstract class AddEntry extends RunnableOnFinish {
mDb.dirty.put(parent, new WeakReference<PwGroup>(parent));
// Add entry to global
mDb.entries.put(Types.bytestoUUID(mEntry.uuid), new WeakReference<PwEntry>(mEntry));
mDb.entries.put(mEntry.getUUID(), new WeakReference<PwEntry>(mEntry));
if ( mDb.indexBuilt ) {
// Add entry to search index

View File

@@ -20,15 +20,15 @@
package com.keepassdroid.database.edit;
import com.keepassdroid.Database;
import com.keepassdroid.database.PwDatabaseV3;
import com.keepassdroid.database.PwEntryV3;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroupV3;
public class AddEntryV3 extends AddEntry {
private PwEntryV3 mEntry;
private PwEntry mEntry;
protected AddEntryV3(Database db, PwEntryV3 entry, OnFinish finish) {
protected AddEntryV3(Database db, PwEntry entry, OnFinish finish) {
super(db, entry, finish);
mEntry = entry;
@@ -36,13 +36,13 @@ public class AddEntryV3 extends AddEntry {
public void addEntry() {
PwGroupV3 parent = mEntry.getParent();
PwGroupV3 parent = (PwGroupV3) mEntry.getParent();
// Add entry to group
parent.childEntries.add(mEntry);
// Add entry to PwDatabaseV3
PwDatabaseV3 pm = (PwDatabaseV3) mDb.pm;
PwDatabase pm = (PwDatabase) mDb.pm;
pm.getEntries().add(mEntry);
// Sort entries

View File

@@ -107,7 +107,7 @@ public class ImporterV3 extends Importer {
return openDatabase(inStream, password, keyfile, new UpdateStatus());
}
public PwDatabaseV3 openDatabase( InputStream inStream, String password, String keyfile, UpdateStatus status )
public PwDatabase openDatabase( InputStream inStream, String password, String keyfile, UpdateStatus status )
throws IOException, InvalidKeyFileException, InvalidPasswordException, InvalidDBSignatureException, InvalidDBVersionException
{
PwDatabaseV3 newManager;
@@ -375,7 +375,7 @@ public class ImporterV3 extends Importer {
// Ignore field
break;
case 0x0001 :
System.arraycopy(buf, offset, ent.uuid, 0, 16);
ent.setUUID(Types.bytestoUUID(buf, offset));
break;
case 0x0002 :
ent.groupId = LEDataInputStream.readInt(buf, offset);

View File

@@ -19,30 +19,41 @@
*/
package com.keepassdroid.database.load;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Stack;
import java.util.UUID;
import java.util.zip.GZIPInputStream;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.bouncycastle.crypto.StreamCipher;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import biz.source_code.base64Coder.Base64Coder;
import com.keepassdroid.UpdateStatus;
import com.keepassdroid.crypto.CipherFactory;
import com.keepassdroid.crypto.PwStreamCipherFactory;
import com.keepassdroid.database.ITimeLogger;
import com.keepassdroid.database.PwCompressionAlgorithm;
import com.keepassdroid.database.PwCustomIcon;
import com.keepassdroid.database.PwDatabaseV4;
import com.keepassdroid.database.PwDbHeaderV4;
import com.keepassdroid.database.exception.InconsistentDBException;
import com.keepassdroid.database.PwDeletedObject;
import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.PwGroupV4;
import com.keepassdroid.database.exception.InvalidDBSignatureException;
import com.keepassdroid.database.exception.InvalidDBVersionException;
import com.keepassdroid.database.exception.InvalidKeyFileException;
@@ -50,8 +61,12 @@ import com.keepassdroid.database.exception.InvalidPasswordException;
import com.keepassdroid.stream.BetterCipherInputStream;
import com.keepassdroid.stream.HashedBlockInputStream;
import com.keepassdroid.stream.LEDataInputStream;
import com.keepassdroid.utils.Types;
public class ImporterV4 extends Importer {
private StreamCipher randomStream;
private PwDatabaseV4 db;
@Override
public PwDatabaseV4 openDatabase(InputStream inStream, String password,
@@ -67,7 +82,7 @@ public class ImporterV4 extends Importer {
InvalidKeyFileException, InvalidPasswordException,
InvalidDBSignatureException, InvalidDBVersionException {
PwDatabaseV4 db = new PwDatabaseV4();
db = new PwDatabaseV4();
PwDbHeaderV4 header = new PwDbHeaderV4(db);
@@ -112,6 +127,26 @@ public class ImporterV4 extends Importer {
decompressed = hashed;
}
if ( header.protectedStreamKey == null ) {
assert(false);
throw new IOException("Invalid stream key.");
}
randomStream = PwStreamCipherFactory.getInstance(header.innerRandomStream, header.protectedStreamKey);
// TODO: Probably good to add a special note here for Arc4 mode
if ( randomStream == null ) {
throw new IOException("Protected stream type not supported.");
}
try {
ReadXmlStreamed(decompressed);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
/*
// TODO: Measure whether this buffer is better or worse for performance
BufferedInputStream bis2 = new BufferedInputStream(decompressed);
@@ -124,18 +159,15 @@ public class ImporterV4 extends Importer {
throw new IOException("Couldn't create document builder.");
}
Document doc;
try {
doc = docB.parse(bis2);
} catch (SAXException e) {
throw new IOException("Failed to parse db xml: " + e.getLocalizedMessage());
}
try {
db.parseDB(doc);
} catch (InconsistentDBException e) {
throw new IOException(e.getLocalizedMessage());
}
*/
/*
FileOutputStream fos = new FileOutputStream("/sdcard/outputx.xml");
@@ -151,6 +183,800 @@ public class ImporterV4 extends Importer {
}
private enum KdbContext {
Null,
KeePassFile,
Meta,
Root,
MemoryProtection,
CustomIcons,
CustomIcon,
CustomData,
CustomDataItem,
RootDeletedObjects,
DeletedObject,
Group,
GroupTimes,
Entry,
EntryTimes,
EntryString,
EntryBinary,
EntryAutoType,
EntryAutoTypeItem,
EntryHistory
}
private static final String ElemDocNode = "KeePassFile";
private static final String ElemMeta = "Meta";
private static final String ElemRoot = "Root";
private static final String ElemGroup = "Group";
private static final String ElemEntry = "Entry";
private static final String ElemGenerator = "Generator";
private static final String ElemDbName = "DatabaseName";
private static final String ElemDbNameChanged = "DatabaseNameChanged";
private static final String ElemDbDesc = "DatabaseDescription";
private static final String ElemDbDescChanged = "DatabaseDescriptionChanged";
private static final String ElemDbDefaultUser = "DefaultUserName";
private static final String ElemDbDefaultUserChanged = "DefaultUserNameChanged";
private static final String ElemDbMntncHistoryDays = "MaintenanceHistoryDays";
private static final String ElemRecycleBinEnabled = "RecycleBinEnabled";
private static final String ElemRecycleBinUuid = "RecycleBinUUID";
private static final String ElemRecycleBinChanged = "RecycleBinChanged";
private static final String ElemEntryTemplatesGroup = "EntryTemplatesGroup";
private static final String ElemEntryTemplatesGroupChanged = "EntryTemplatesGroupChanged";
private static final String ElemLastSelectedGroup = "LastSelectedGroup";
private static final String ElemLastTopVisibleGroup = "LastTopVisibleGroup";
private static final String ElemMemoryProt = "MemoryProtection";
private static final String ElemProtTitle = "ProtectTitle";
private static final String ElemProtUserName = "ProtectUserName";
private static final String ElemProtPassword = "ProtectPassword";
private static final String ElemProtURL = "ProtectURL";
private static final String ElemProtNotes = "ProtectNotes";
private static final String ElemProtAutoHide = "AutoEnableVisualHiding";
private static final String ElemCustomIcons = "CustomIcons";
private static final String ElemCustomIconItem = "Icon";
private static final String ElemCustomIconItemID = "UUID";
private static final String ElemCustomIconItemData = "Data";
private static final String ElemAutoType = "AutoType";
private static final String ElemHistory = "History";
private static final String ElemName = "Name";
private static final String ElemNotes = "Notes";
private static final String ElemUuid = "UUID";
private static final String ElemIcon = "IconID";
private static final String ElemCustomIconID = "CustomIconUUID";
private static final String ElemFgColor = "ForegroundColor";
private static final String ElemBgColor = "BackgroundColor";
private static final String ElemOverrideUrl = "OverrideURL";
private static final String ElemTimes = "Times";
private static final String ElemCreationTime = "CreationTime";
private static final String ElemLastModTime = "LastModificationTime";
private static final String ElemLastAccessTime = "LastAccessTime";
private static final String ElemExpiryTime = "ExpiryTime";
private static final String ElemExpires = "Expires";
private static final String ElemUsageCount = "UsageCount";
private static final String ElemLocationChanged = "LocationChanged";
private static final String ElemGroupDefaultAutoTypeSeq = "DefaultAutoTypeSequence";
private static final String ElemEnableAutoType = "EnableAutoType";
private static final String ElemEnableSearching = "EnableSearching";
private static final String ElemString = "String";
private static final String ElemBinary = "Binary";
private static final String ElemKey = "Key";
private static final String ElemValue = "Value";
private static final String ElemAutoTypeEnabled = "Enabled";
private static final String ElemAutoTypeObfuscation = "DataTransferObfuscation";
private static final String ElemAutoTypeDefaultSeq = "DefaultSequence";
private static final String ElemAutoTypeItem = "Association";
private static final String ElemWindow = "Window";
private static final String ElemKeystrokeSequence = "KeystrokeSequence";
private static final String AttrProtected = "Protected";
private static final String ElemIsExpanded = "IsExpanded";
private static final String ElemLastTopVisibleEntry = "LastTopVisibleEntry";
private static final String ElemDeletedObjects = "DeletedObjects";
private static final String ElemDeletedObject = "DeletedObject";
private static final String ElemDeletionTime = "DeletionTime";
private static final String ValFalse = "False";
private static final String ValTrue = "True";
private static final String ElemCustomData = "CustomData";
private static final String ElemStringDictExItem = "Item";
private static final long DEFAULT_HISTORY_DAYS = 365;
private boolean readNextNode = true;
private Stack<PwGroupV4> ctxGroups = new Stack<PwGroupV4>();
private PwGroupV4 ctxGroup = null;
private PwEntryV4 ctxEntry = null;
private String ctxStringName = null;
private String ctxStringValue = null;
private String ctxBinaryName = null;
private byte[] ctxBinaryValue = null;
private String ctxATName = null;
private String ctxATSeq = null;
private boolean entryInHistory = false;
private PwEntryV4 ctxHistoryBase = null;
private PwDeletedObject ctxDeletedObject = null;
private UUID customIconID = PwDatabaseV4.UUID_ZERO;
private byte[] customIconData;
private String customDataKey = null;
private String customDataValue = null;
private void ReadXmlStreamed(InputStream readerStream) throws IOException {
try {
ReadDocumentStreamed(CreatePullParser(readerStream));
} catch (XmlPullParserException e) {
e.printStackTrace();
throw new IOException(e.getLocalizedMessage());
}
}
private static XmlPullParser CreatePullParser(InputStream readerStream) throws XmlPullParserException {
XmlPullParserFactory xppf = XmlPullParserFactory.newInstance();
xppf.setNamespaceAware(false);
XmlPullParser xpp = xppf.newPullParser();
xpp.setInput(readerStream, null);
return xpp;
}
private void ReadDocumentStreamed(XmlPullParser xpp) throws XmlPullParserException, IOException {
ctxGroups.clear();
KdbContext ctx = KdbContext.Null;
readNextNode = true;
while (true) {
if ( readNextNode ) {
if( xpp.next() == XmlPullParser.END_DOCUMENT ) break;
} else {
readNextNode = true;
}
switch ( xpp.getEventType() ) {
case XmlPullParser.START_TAG:
ctx = ReadXmlElement(ctx, xpp);
break;
case XmlPullParser.END_TAG:
ctx = EndXmlElement(ctx, xpp);
break;
default:
assert(false);
break;
}
}
// Error checks
if ( ctx != KdbContext.Null ) throw new IOException("Malformed");
if ( ctxGroups.size() != 0 ) throw new IOException("Malformed");
}
private KdbContext ReadXmlElement(KdbContext ctx, XmlPullParser xpp) throws XmlPullParserException, IOException {
String name = xpp.getName();
switch (ctx) {
case Null:
if ( name.equalsIgnoreCase(ElemDocNode) ) {
return SwitchContext(ctx, KdbContext.KeePassFile, xpp);
} else ReadUnknown(xpp);
break;
case KeePassFile:
if ( name.equalsIgnoreCase(ElemMeta) ) {
return SwitchContext(ctx, KdbContext.Meta, xpp);
} else if ( name.equalsIgnoreCase(ElemRoot) ) {
return SwitchContext(ctx, KdbContext.Root, xpp);
} else {
ReadUnknown(xpp);
}
break;
case Meta:
if ( name.equalsIgnoreCase(ElemGenerator) ) {
ReadString(xpp); // Ignore
} else if ( name.equalsIgnoreCase(ElemDbName) ) {
db.name = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemDbNameChanged) ) {
db.nameChanged = ReadTime(xpp);
} else if ( name.equalsIgnoreCase(ElemDbDesc) ) {
db.description = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemDbDescChanged) ) {
db.descriptionChanged = ReadTime(xpp);
} else if ( name.equalsIgnoreCase(ElemDbDefaultUser) ) {
db.defaultUserName = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemDbDefaultUserChanged) ) {
db.defaultUserNameChanged = ReadTime(xpp);
} else if ( name.equalsIgnoreCase(ElemDbMntncHistoryDays) ) {
db.maintenanceHistoryDays = ReadUInt(xpp, DEFAULT_HISTORY_DAYS);
} else if ( name.equalsIgnoreCase(ElemMemoryProt) ) {
return SwitchContext(ctx, KdbContext.MemoryProtection, xpp);
} else if ( name.equalsIgnoreCase(ElemCustomIcons) ) {
return SwitchContext(ctx, KdbContext.CustomIcons, xpp);
} else if ( name.equalsIgnoreCase(ElemRecycleBinEnabled) ) {
db.recycleBinEnabled = ReadBool(xpp, true);
} else if ( name.equalsIgnoreCase(ElemRecycleBinUuid) ) {
db.recycleBinUUID = ReadUuid(xpp);
} else if ( name.equalsIgnoreCase(ElemRecycleBinChanged) ) {
db.recycleBinChanged = ReadTime(xpp);
} else if ( name.equalsIgnoreCase(ElemEntryTemplatesGroup) ) {
db.entryTemplatesGroup = ReadUuid(xpp);
} else if ( name.equalsIgnoreCase(ElemEntryTemplatesGroupChanged) ) {
db.entryTemplatesGroupChanged = ReadTime(xpp);
} else if ( name.equalsIgnoreCase(ElemLastSelectedGroup) ) {
db.lastSelectedGroup = ReadUuid(xpp);
} else if ( name.equalsIgnoreCase(ElemLastTopVisibleGroup) ) {
db.lastTopVisibleGroup = ReadUuid(xpp);
} else if ( name.equalsIgnoreCase(ElemCustomData) ) {
return SwitchContext(ctx, KdbContext.CustomData, xpp);
}
break;
case MemoryProtection:
if ( name.equalsIgnoreCase(ElemProtTitle) ) {
db.memoryProtection.protectTitle = ReadBool(xpp, false);
} else if ( name.equalsIgnoreCase(ElemProtUserName) ) {
db.memoryProtection.protectUserName = ReadBool(xpp, false);
} else if ( name.equalsIgnoreCase(ElemProtPassword) ) {
db.memoryProtection.protectPassword = ReadBool(xpp, false);
} else if ( name.equalsIgnoreCase(ElemProtURL) ) {
db.memoryProtection.protectUrl = ReadBool(xpp, false);
} else if ( name.equalsIgnoreCase(ElemProtNotes) ) {
db.memoryProtection.protectNotes = ReadBool(xpp, false);
} else if ( name.equalsIgnoreCase(ElemProtAutoHide) ) {
db.memoryProtection.autoEnableVisualHiding = ReadBool(xpp, false);
} else {
ReadUnknown(xpp);
}
break;
case CustomIcons:
if ( name.equalsIgnoreCase(ElemCustomIconItem) ) {
return SwitchContext(ctx, KdbContext.CustomIcon, xpp);
} else {
ReadUnknown(xpp);
}
break;
case CustomIcon:
if ( name.equalsIgnoreCase(ElemCustomIconItemID) ) {
customIconID = ReadUuid(xpp);
} else if ( name.equalsIgnoreCase(ElemCustomIconItemData) ) {
String strData = ReadString(xpp);
if ( strData != null && strData.length() > 0 ) {
customIconData = Base64Coder.decode(strData);
} else {
assert(false);
}
} else {
ReadUnknown(xpp);
}
break;
case CustomData:
if ( name.equalsIgnoreCase(ElemStringDictExItem) ) {
return SwitchContext(ctx, KdbContext.CustomDataItem, xpp);
} else {
ReadUnknown(xpp);
}
break;
case CustomDataItem:
if ( name.equalsIgnoreCase(ElemKey) ) {
customDataKey = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemValue) ) {
customDataValue = ReadString(xpp);
} else {
ReadUnknown(xpp);
}
break;
case Root:
if ( name.equalsIgnoreCase(ElemGroup) ) {
assert(ctxGroups.size() == 0);
if ( ctxGroups.size() != 0 ) throw new IOException("Group list should be empty.");
db.rootGroup = new PwGroupV4();
ctxGroups.push(db.rootGroup);
ctxGroup = ctxGroups.peek();
return SwitchContext(ctx, KdbContext.Group, xpp);
} else if ( name.equalsIgnoreCase(ElemDeletedObjects) ) {
return SwitchContext(ctx, KdbContext.RootDeletedObjects, xpp);
} else {
ReadUnknown(xpp);
}
break;
case Group:
if ( name.equalsIgnoreCase(ElemUuid) ) {
ctxGroup.uuid = ReadUuid(xpp);
} else if ( name.equalsIgnoreCase(ElemName) ) {
ctxGroup.name = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemNotes) ) {
ctxGroup.notes = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemIcon) ) {
ctxGroup.iconId = (int)ReadUInt(xpp, 0);
} else if ( name.equalsIgnoreCase(ElemCustomIconID) ) {
ctxGroup.customIconUuid = ReadUuid(xpp);
} else if ( name.equalsIgnoreCase(ElemTimes) ) {
return SwitchContext(ctx, KdbContext.GroupTimes, xpp);
} else if ( name.equalsIgnoreCase(ElemIsExpanded) ) {
ctxGroup.isExpanded = ReadBool(xpp, true);
} else if ( name.equalsIgnoreCase(ElemGroupDefaultAutoTypeSeq) ) {
ctxGroup.defaultAutoTypeSequence = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemEnableAutoType) ) {
ctxGroup.enableAutoType = StringToBoolean(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemEnableSearching) ) {
ctxGroup.enableSearching = StringToBoolean(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemLastTopVisibleEntry) ) {
ctxGroup.lastTopVisibleEntry = ReadUuid(xpp);
} else if ( name.equalsIgnoreCase(ElemGroup) ) {
ctxGroup = new PwGroupV4();
ctxGroups.peek().AddGroup(ctxGroup, true);
ctxGroups.push(ctxGroup);
return SwitchContext(ctx, KdbContext.Group, xpp);
} else if ( name.equalsIgnoreCase(ElemEntry) ) {
ctxEntry = new PwEntryV4();
ctxGroup.AddEntry(ctxEntry, true);
entryInHistory = false;
return SwitchContext(ctx, KdbContext.Entry, xpp);
} else {
ReadUnknown(xpp);
}
break;
case Entry:
if ( name.equalsIgnoreCase(ElemUuid) ) {
ctxEntry.setUUID(ReadUuid(xpp));
} else if ( name.equalsIgnoreCase(ElemIcon) ) {
ctxEntry.imageId = (int)ReadUInt(xpp, 0);
} else if ( name.equalsIgnoreCase(ElemCustomIconID) ) {
ctxEntry.customIconUuid = ReadUuid(xpp);
} else if ( name.equalsIgnoreCase(ElemFgColor) ) {
ctxEntry.foregroundColor = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemBgColor) ) {
ctxEntry.backgroupColor = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemOverrideUrl) ) {
ctxEntry.overrideURL = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemTimes) ) {
return SwitchContext(ctx, KdbContext.EntryTimes, xpp);
} else if ( name.equalsIgnoreCase(ElemString) ) {
return SwitchContext(ctx, KdbContext.EntryString, xpp);
} else if ( name.equalsIgnoreCase(ElemBinary) ) {
return SwitchContext(ctx, KdbContext.EntryBinary, xpp);
} else if ( name.equalsIgnoreCase(ElemAutoType) ) {
return SwitchContext(ctx, KdbContext.EntryAutoType, xpp);
} else if ( name.equalsIgnoreCase(ElemHistory) ) {
assert(!entryInHistory);
if ( ! entryInHistory ) {
ctxHistoryBase = ctxEntry;
return SwitchContext(ctx, KdbContext.EntryHistory, xpp);
} else {
ReadUnknown(xpp);
}
} else {
ReadUnknown(xpp);
}
break;
case GroupTimes:
case EntryTimes:
ITimeLogger tl;
if ( ctx == KdbContext.GroupTimes ) {
tl = ctxGroup;
} else {
tl = ctxEntry;
}
if ( name.equalsIgnoreCase(ElemLastModTime) ) {
tl.setLastModificationTime(ReadTime(xpp));
} else if ( name.equalsIgnoreCase(ElemCreationTime) ) {
tl.setCreationTime(ReadTime(xpp));
} else if ( name.equalsIgnoreCase(ElemLastAccessTime) ) {
tl.setLastAccessTime(ReadTime(xpp));
} else if ( name.equalsIgnoreCase(ElemExpiryTime) ) {
tl.setExpiryTime(ReadTime(xpp));
} else if ( name.equalsIgnoreCase(ElemExpires) ) {
tl.setExpires(ReadBool(xpp, false));
} else if ( name.equalsIgnoreCase(ElemUsageCount) ) {
tl.setUsageCount(ReadULong(xpp, 0));
} else if ( name.equalsIgnoreCase(ElemLocationChanged) ) {
tl.setLocationChanged(ReadTime(xpp));
} else {
ReadUnknown(xpp);
}
break;
case EntryString:
if ( name.equalsIgnoreCase(ElemKey) ) {
ctxStringName = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemValue) ) {
ctxStringValue = ReadProtectedString(xpp);
} else {
ReadUnknown(xpp);
}
break;
case EntryBinary:
if ( name.equalsIgnoreCase(ElemKey) ) {
ctxBinaryName = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemValue) ) {
ctxBinaryValue = ReadProtectedBinary(xpp);
}
break;
case EntryAutoType:
if ( name.equalsIgnoreCase(ElemAutoTypeEnabled) ) {
ctxEntry.autoType.enabled = ReadBool(xpp, true);
} else if ( name.equalsIgnoreCase(ElemAutoTypeObfuscation) ) {
ctxEntry.autoType.obfuscationOptions = ReadUInt(xpp, 0);
} else if ( name.equalsIgnoreCase(ElemAutoTypeDefaultSeq) ) {
ctxEntry.autoType.defaultSequence = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemAutoTypeItem) ) {
return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xpp);
} else {
ReadUnknown(xpp);
}
break;
case EntryAutoTypeItem:
if ( name.equalsIgnoreCase(ElemWindow) ) {
ctxATName = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemKeystrokeSequence) ) {
ctxATSeq = ReadString(xpp);
} else {
ReadUnknown(xpp);
}
break;
case EntryHistory:
if ( name.equalsIgnoreCase(ElemEntry) ) {
ctxEntry = new PwEntryV4();
ctxHistoryBase.history.add(ctxEntry);
entryInHistory = true;
return SwitchContext(ctx, KdbContext.Entry, xpp);
} else {
ReadUnknown(xpp);
}
break;
case RootDeletedObjects:
if ( name.equalsIgnoreCase(ElemDeletedObject) ) {
ctxDeletedObject = new PwDeletedObject();
db.deletedObjects.add(ctxDeletedObject);
return SwitchContext(ctx, KdbContext.DeletedObject, xpp);
} else {
ReadUnknown(xpp);
}
break;
case DeletedObject:
if ( name.equalsIgnoreCase(ElemUuid) ) {
ctxDeletedObject.uuid = ReadUuid(xpp);
} else if ( name.equalsIgnoreCase(ElemDeletionTime) ) {
ctxDeletedObject.setDeletionTime(ReadTime(xpp));
} else {
ReadUnknown(xpp);
}
break;
default:
ReadUnknown(xpp);
break;
}
return ctx;
}
private KdbContext EndXmlElement(KdbContext ctx, XmlPullParser xpp) throws XmlPullParserException {
assert(xpp.getEventType() == XmlPullParser.END_TAG);
String name = xpp.getName();
if ( ctx == KdbContext.KeePassFile && name.equalsIgnoreCase(ElemDocNode) ) {
return KdbContext.Null;
} else if ( ctx == KdbContext.Meta && name.equalsIgnoreCase(ElemMeta) ) {
return KdbContext.KeePassFile;
} else if ( ctx == KdbContext.Root && name.equalsIgnoreCase(ElemRoot) ) {
return KdbContext.KeePassFile;
} else if ( ctx == KdbContext.MemoryProtection && name.equalsIgnoreCase(ElemMemoryProt) ) {
return KdbContext.Meta;
} else if ( ctx == KdbContext.CustomIcons && name.equalsIgnoreCase(ElemCustomIcons) ) {
return KdbContext.Meta;
} else if ( ctx == KdbContext.CustomIcon && name.equalsIgnoreCase(ElemCustomIconItem) ) {
if ( ! customIconID.equals(PwDatabaseV4.UUID_ZERO) ) {
db.customIcons.add(new PwCustomIcon(customIconID, customIconData));
} else assert(false);
customIconID = PwDatabaseV4.UUID_ZERO;
customIconData = null;
return KdbContext.CustomIcons;
} else if ( ctx == KdbContext.CustomData && name.equalsIgnoreCase(ElemCustomData) ) {
return KdbContext.Meta;
} else if ( ctx == KdbContext.CustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem) ) {
if ( customDataKey != null && customDataValue != null) {
db.customData.put(customDataKey, customDataValue);
} else assert(false);
customDataKey = null;
customDataValue = null;
return KdbContext.CustomData;
} else if ( ctx == KdbContext.Group && name.equalsIgnoreCase(ElemGroup) ) {
if ( ctxGroup.uuid == null || ctxGroup.uuid.equals(PwDatabaseV4.UUID_ZERO) ) {
ctxGroup.uuid = UUID.randomUUID();
}
ctxGroups.pop();
if ( ctxGroups.size() == 0 ) {
ctxGroup = null;
return KdbContext.Root;
} else {
ctxGroup = ctxGroups.peek();
return KdbContext.Group;
}
} else if ( ctx == KdbContext.GroupTimes && name.equalsIgnoreCase(ElemTimes) ) {
return KdbContext.Group;
} else if ( ctx == KdbContext.Entry && name.equalsIgnoreCase(ElemEntry) ) {
if ( ctxEntry.uuid == null || ctxEntry.uuid.equals(PwDatabaseV4.UUID_ZERO) ) {
ctxEntry.uuid = UUID.randomUUID();
}
if ( entryInHistory ) {
ctxEntry = ctxHistoryBase;
return KdbContext.EntryHistory;
}
return KdbContext.Group;
} else if ( ctx == KdbContext.EntryTimes && name.equalsIgnoreCase(ElemTimes) ) {
return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryString && name.equalsIgnoreCase(ElemString) ) {
ctxEntry.strings.put(ctxStringName, ctxStringValue);
ctxStringName = null;
ctxStringValue = null;
return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryBinary && name.equalsIgnoreCase(ElemBinary) ) {
ctxEntry.binaries.put(ctxBinaryName, ctxBinaryValue);
ctxBinaryName = null;
ctxBinaryValue = null;
return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryAutoType && name.equalsIgnoreCase(ElemAutoType) ) {
return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryAutoTypeItem && name.equalsIgnoreCase(ElemAutoTypeItem) ) {
ctxEntry.autoType.put(ctxATName, ctxATSeq);
ctxATName = null;
ctxATSeq = null;
return KdbContext.EntryAutoType;
} else if ( ctx == KdbContext.EntryHistory && name.equalsIgnoreCase(ElemHistory) ) {
entryInHistory = false;
return KdbContext.Entry;
} else if ( ctx == KdbContext.RootDeletedObjects && name.equalsIgnoreCase(ElemDeletedObjects) ) {
return KdbContext.Root;
} else if ( ctx == KdbContext.DeletedObject && name.equalsIgnoreCase(ElemDeletedObject) ) {
ctxDeletedObject = null;
return KdbContext.RootDeletedObjects;
} else {
assert(false);
throw new RuntimeException("Invalid end element");
}
}
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd'T'HH:mm:ss'Z'");
private Date ReadTime(XmlPullParser xpp) throws IOException, XmlPullParserException {
String sDate = ReadString(xpp);
Date utcDate;
try {
utcDate = dateFormat.parse(sDate);
} catch (ParseException e) {
e.printStackTrace();
throw new IOException(e.getLocalizedMessage());
}
return utcDate;
}
private void ReadUnknown(XmlPullParser xpp) throws XmlPullParserException, IOException {
assert(false);
if ( xpp.isEmptyElementTag() ) return;
String unknownName = xpp.getName();
ProcessNode(xpp);
while (xpp.next() != XmlPullParser.END_DOCUMENT ) {
if ( xpp.getEventType() == XmlPullParser.END_TAG ) break;
if ( xpp.getEventType() == XmlPullParser.START_TAG ) continue;
ReadUnknown(xpp);
}
assert(xpp.getName() == unknownName);
}
private boolean ReadBool(XmlPullParser xpp, boolean bDefault) throws IOException, XmlPullParserException {
String str = ReadString(xpp);
if ( str.equalsIgnoreCase("true") ) {
return true;
} else if ( str.equalsIgnoreCase("false") ) {
return false;
} else {
return bDefault;
}
}
private UUID ReadUuid(XmlPullParser xpp) throws IOException, XmlPullParserException {
String encoded = ReadString(xpp);
if (encoded == null || encoded.length() == 0 ) {
return PwDatabaseV4.UUID_ZERO;
}
// TODO: Switch to framework Base64 once API level 8 is the minimum
byte[] buf = Base64Coder.decode(encoded);
return Types.bytestoUUID(buf);
}
private static final long MAX_UINT = 4294967296L; // 2^32
private long ReadUInt(XmlPullParser xpp, long uDefault) throws IOException, XmlPullParserException {
long u;
u = ReadULong(xpp, uDefault);
if ( u < 0 || u > MAX_UINT ) {
throw new NumberFormatException("Outside of the uint size");
}
return u;
}
private long ReadULong(XmlPullParser xpp, long uDefault) throws IOException, XmlPullParserException {
String str = ReadString(xpp);
long u;
try {
u = Long.parseLong(str);
} catch( NumberFormatException e) {
u = uDefault;
}
return u;
}
private String ReadProtectedString(XmlPullParser xpp) throws XmlPullParserException, IOException {
byte[] buf = ProcessNode(xpp);
if ( buf != null) {
try {
return new String(buf, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new IOException(e.getLocalizedMessage());
}
}
return ReadString(xpp);
}
private byte[] ReadProtectedBinary(XmlPullParser xpp) throws XmlPullParserException, IOException {
byte[] buf = ProcessNode(xpp);
if ( buf != null ) return buf;
String base64 = ReadString(xpp);
if ( base64.length() == 0 ) return new byte[0];
return Base64Coder.decode(base64);
}
private String ReadString(XmlPullParser xpp) throws IOException, XmlPullParserException {
byte[] buf = ProcessNode(xpp);
if ( buf != null ) {
try {
return new String(buf, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IOException(e.getLocalizedMessage());
}
}
//readNextNode = false;
return xpp.nextText();
}
private String ReadStringRaw(XmlPullParser xpp) throws XmlPullParserException, IOException {
//readNextNode = false;
return xpp.nextText();
}
private byte[] ProcessNode(XmlPullParser xpp) throws XmlPullParserException, IOException {
assert(xpp.getEventType() == XmlPullParser.START_TAG);
byte[] buf = null;
if ( xpp.getAttributeCount() > 0 ) {
String protect = xpp.getAttributeValue(null, AttrProtected);
if ( protect != null && protect.equalsIgnoreCase(ValTrue) ) {
String encrypted = ReadStringRaw(xpp);
if ( encrypted.length() > 0 ) {
buf = Base64Coder.decode(encrypted);
byte[] plainText = new byte[buf.length];
randomStream.processBytes(buf, 0, buf.length, plainText, 0);
return plainText;
} else {
buf = new byte[0];
}
}
}
return buf;
}
private KdbContext SwitchContext(KdbContext ctxCurrent, KdbContext ctxNew,
XmlPullParser xpp) throws XmlPullParserException, IOException {
if ( xpp.isEmptyElementTag() ) {
xpp.next(); // Consume the end tag
return ctxCurrent;
}
return ctxNew;
}
private Boolean StringToBoolean(String str) {
if ( str == null || str.length() == 0 ) {
return null;
}
String trimmed = str.trim();
if ( trimmed.equalsIgnoreCase("true") ) {
return true;
} else if ( trimmed.equalsIgnoreCase("false") ) {
return false;
}
return null;
}
}

View File

@@ -74,7 +74,7 @@ public class PwEntryOutputV3 {
// UUID
mOS.write(UUID_FIELD_TYPE);
mOS.write(UUID_FIELD_SIZE);
mOS.write(mPE.uuid);
mOS.write(Types.UUIDtoBytes(mPE.getUUID()));
// Group ID
mOS.write(GROUPID_FIELD_TYPE);

View File

@@ -32,10 +32,8 @@ import android.util.Log;
import com.keepassdroid.Database;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwEntryV3;
import com.keepassdroid.database.PwGroup;
import com.keepassdroid.database.PwGroupV3;
import com.keepassdroid.utils.Types;
public class SearchDbHelper {
private static final String DATABASE_NAME = "search";
@@ -100,7 +98,7 @@ public class SearchDbHelper {
private ContentValues buildNewEntryContent(PwEntry entry) {
ContentValues cv = new ContentValues();
UUID uuid = Types.bytestoUUID(entry.uuid);
UUID uuid = entry.getUUID();
String uuidStr = uuid.toString();
cv.put(KEY_UUID, uuidStr);
@@ -137,7 +135,7 @@ public class SearchDbHelper {
}
public void deleteEntry(PwEntry entry) {
UUID uuid = Types.bytestoUUID(entry.uuid);
UUID uuid = entry.getUUID();
String uuidStr = uuid.toString();
mDb.delete(SEARCH_TABLE, KEY_UUID + " = ?", new String[] {uuidStr});
@@ -157,7 +155,7 @@ public class SearchDbHelper {
String sUUID = cursor.getString(0);
UUID uuid = UUID.fromString(sUUID);
Log.d("TAG", uuid.toString());
PwEntryV3 entry = (PwEntryV3) db.entries.get(uuid).get();
PwEntry entry = (PwEntry) db.entries.get(uuid).get();
group.childEntries.add(entry);
cursor.moveToNext();

View File

@@ -178,19 +178,31 @@ public class Types {
}
public static UUID bytestoUUID(byte[] buf) {
long msb = 0;
for (int i = 0; i < 8; i++) {
msb = (msb << 8) | (buf[i] & 0xff);
}
return bytestoUUID(buf, 0);
}
public static UUID bytestoUUID(byte[] buf, int offset) {
long lsb = 0;
for (int i = 8; i < 16; i++) {
lsb = (lsb << 8) | (buf[i] & 0xff);
for (int i = 15; i >= 8; i--) {
lsb = (lsb << 8) | (buf[i + offset] & 0xff);
}
long msb = 0;
for (int i = 7; i >= 0; i--) {
msb = (msb << 8) | (buf[i + offset] & 0xff);
}
return new UUID(msb, lsb);
}
public static byte[] UUIDtoBytes(UUID uuid) {
byte[] buf = new byte[16];
LEDataOutputStream.writeLong(uuid.getMostSignificantBits(), buf, 0);
LEDataOutputStream.writeLong(uuid.getLeastSignificantBits(), buf, 8);
return buf;
}
}

View File

@@ -0,0 +1,8 @@
package org.bouncycastle.crypto;
/**
* all parameter classes implement this.
*/
public interface CipherParameters
{
}

View File

@@ -0,0 +1,30 @@
package org.bouncycastle.crypto;
/**
* this exception is thrown if a buffer that is meant to have output
* copied into it turns out to be too short, or if we've been given
* insufficient input. In general this exception will get thrown rather
* than an ArrayOutOfBounds exception.
*/
@SuppressWarnings("serial")
public class DataLengthException
extends RuntimeCryptoException
{
/**
* base constructor.
*/
public DataLengthException()
{
}
/**
* create a DataLengthException with the given message.
*
* @param message the message to be carried with the exception.
*/
public DataLengthException(
String message)
{
super(message);
}
}

View File

@@ -0,0 +1,28 @@
package org.bouncycastle.crypto;
/**
* this exception is thrown whenever a cipher requires a change of key, iv
* or similar after x amount of bytes enciphered
*/
@SuppressWarnings("serial")
public class MaxBytesExceededException
extends RuntimeCryptoException
{
/**
* base constructor.
*/
public MaxBytesExceededException()
{
}
/**
* create an with the given message.
*
* @param message the message to be carried with the exception.
*/
public MaxBytesExceededException(
String message)
{
super(message);
}
}

View File

@@ -0,0 +1,27 @@
package org.bouncycastle.crypto;
/**
* the foundation class for the exceptions thrown by the crypto packages.
*/
@SuppressWarnings("serial")
public class RuntimeCryptoException
extends RuntimeException
{
/**
* base constructor.
*/
public RuntimeCryptoException()
{
}
/**
* create a RuntimeCryptoException with the given message.
*
* @param message the message to be carried with the exception.
*/
public RuntimeCryptoException(
String message)
{
super(message);
}
}

View File

@@ -0,0 +1,53 @@
package org.bouncycastle.crypto;
/**
* the interface stream ciphers conform to.
*/
public interface StreamCipher
{
/**
* Initialise the cipher.
*
* @param forEncryption if true the cipher is initialised for
* encryption, if false for decryption.
* @param params the key and other data required by the cipher.
* @exception IllegalArgumentException if the params argument is
* inappropriate.
*/
public void init(boolean forEncryption, CipherParameters params)
throws IllegalArgumentException;
/**
* Return the name of the algorithm the cipher implements.
*
* @return the name of the algorithm the cipher implements.
*/
public String getAlgorithmName();
/**
* encrypt/decrypt a single byte returning the result.
*
* @param in the byte to be processed.
* @return the result of processing the input byte.
*/
public byte returnByte(byte in);
/**
* process a block of bytes from in putting the result into out.
*
* @param in the input byte array.
* @param inOff the offset into the in array where the data to be processed starts.
* @param len the number of bytes to be processed.
* @param out the output buffer the processed bytes go into.
* @param outOff the offset into the output byte array the processed data starts at.
* @exception DataLengthException if the output buffer is too small.
*/
public void processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
throws DataLengthException;
/**
* reset the cipher. This leaves it in the same state
* it was at after the last init (if there was one).
*/
public void reset();
}

View File

@@ -0,0 +1,377 @@
package org.bouncycastle.crypto.engines;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.MaxBytesExceededException;
import org.bouncycastle.crypto.StreamCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
/**
* Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005
*/
public class Salsa20Engine
implements StreamCipher
{
/** Constants */
private final static int stateSize = 16; // 16, 32 bit ints = 64 bytes
private final static byte[]
sigma = toByteArray("expand 32-byte k"),
tau = toByteArray("expand 16-byte k");
// No sure why the version in org.bouncycastle.util.Strings was throwing NoSuchMethodErrors
private static byte[] toByteArray(String string)
{
byte[] bytes = new byte[string.length()];
for (int i = 0; i != bytes.length; i++)
{
char ch = string.charAt(i);
bytes[i] = (byte)ch;
}
return bytes;
}
/*
* variables to hold the state of the engine
* during encryption and decryption
*/
private int index = 0;
private int[] engineState = new int[stateSize]; // state
private int[] x = new int[stateSize] ; // internal buffer
private byte[] keyStream = new byte[stateSize * 4], // expanded state, 64 bytes
workingKey = null,
workingIV = null;
private boolean initialised = false;
/*
* internal counter
*/
private int cW0, cW1, cW2;
/**
* initialise a Salsa20 cipher.
*
* @param forEncryption whether or not we are for encryption.
* @param params the parameters required to set up the cipher.
* @exception IllegalArgumentException if the params argument is
* inappropriate.
*/
public void init(
boolean forEncryption,
CipherParameters params)
{
/*
* Salsa20 encryption and decryption is completely
* symmetrical, so the 'forEncryption' is
* irrelevant. (Like 90% of stream ciphers)
*/
if (!(params instanceof ParametersWithIV))
{
throw new IllegalArgumentException("Salsa20 Init parameters must include an IV");
}
ParametersWithIV ivParams = (ParametersWithIV) params;
byte[] iv = ivParams.getIV();
if (iv == null || iv.length != 8)
{
throw new IllegalArgumentException("Salsa20 requires exactly 8 bytes of IV");
}
if (!(ivParams.getParameters() instanceof KeyParameter))
{
throw new IllegalArgumentException("Salsa20 Init parameters must include a key");
}
KeyParameter key = (KeyParameter) ivParams.getParameters();
workingKey = key.getKey();
workingIV = iv;
setKey(workingKey, workingIV);
}
public String getAlgorithmName()
{
return "Salsa20";
}
public byte returnByte(byte in)
{
if (limitExceeded())
{
throw new MaxBytesExceededException("2^70 byte limit per IV; Change IV");
}
if (index == 0)
{
salsa20WordToByte(engineState, keyStream);
engineState[8]++;
if (engineState[8] == 0)
{
engineState[9]++;
}
}
byte out = (byte)(keyStream[index]^in);
index = (index + 1) & 63;
return out;
}
public void processBytes(
byte[] in,
int inOff,
int len,
byte[] out,
int outOff)
{
if (!initialised)
{
throw new IllegalStateException(getAlgorithmName()+" not initialised");
}
if ((inOff + len) > in.length)
{
throw new DataLengthException("input buffer too short");
}
if ((outOff + len) > out.length)
{
throw new DataLengthException("output buffer too short");
}
if (limitExceeded(len))
{
throw new MaxBytesExceededException("2^70 byte limit per IV would be exceeded; Change IV");
}
for (int i = 0; i < len; i++)
{
if (index == 0)
{
salsa20WordToByte(engineState, keyStream);
engineState[8]++;
if (engineState[8] == 0)
{
engineState[9]++;
}
}
out[i+outOff] = (byte)(keyStream[index]^in[i+inOff]);
index = (index + 1) & 63;
}
}
public void reset()
{
setKey(workingKey, workingIV);
}
// Private implementation
private void setKey(byte[] keyBytes, byte[] ivBytes)
{
workingKey = keyBytes;
workingIV = ivBytes;
index = 0;
resetCounter();
int offset = 0;
byte[] constants;
// Key
engineState[1] = byteToIntLittle(workingKey, 0);
engineState[2] = byteToIntLittle(workingKey, 4);
engineState[3] = byteToIntLittle(workingKey, 8);
engineState[4] = byteToIntLittle(workingKey, 12);
if (workingKey.length == 32)
{
constants = sigma;
offset = 16;
}
else
{
constants = tau;
}
engineState[11] = byteToIntLittle(workingKey, offset);
engineState[12] = byteToIntLittle(workingKey, offset+4);
engineState[13] = byteToIntLittle(workingKey, offset+8);
engineState[14] = byteToIntLittle(workingKey, offset+12);
engineState[0 ] = byteToIntLittle(constants, 0);
engineState[5 ] = byteToIntLittle(constants, 4);
engineState[10] = byteToIntLittle(constants, 8);
engineState[15] = byteToIntLittle(constants, 12);
// IV
engineState[6] = byteToIntLittle(workingIV, 0);
engineState[7] = byteToIntLittle(workingIV, 4);
engineState[8] = engineState[9] = 0;
initialised = true;
}
/**
* Salsa20 function
*
* @param input input data
*
* @return keystream
*/
private void salsa20WordToByte(int[] input, byte[] output)
{
System.arraycopy(input, 0, x, 0, input.length);
for (int i = 0; i < 10; i++)
{
x[ 4] ^= rotl((x[ 0]+x[12]), 7);
x[ 8] ^= rotl((x[ 4]+x[ 0]), 9);
x[12] ^= rotl((x[ 8]+x[ 4]),13);
x[ 0] ^= rotl((x[12]+x[ 8]),18);
x[ 9] ^= rotl((x[ 5]+x[ 1]), 7);
x[13] ^= rotl((x[ 9]+x[ 5]), 9);
x[ 1] ^= rotl((x[13]+x[ 9]),13);
x[ 5] ^= rotl((x[ 1]+x[13]),18);
x[14] ^= rotl((x[10]+x[ 6]), 7);
x[ 2] ^= rotl((x[14]+x[10]), 9);
x[ 6] ^= rotl((x[ 2]+x[14]),13);
x[10] ^= rotl((x[ 6]+x[ 2]),18);
x[ 3] ^= rotl((x[15]+x[11]), 7);
x[ 7] ^= rotl((x[ 3]+x[15]), 9);
x[11] ^= rotl((x[ 7]+x[ 3]),13);
x[15] ^= rotl((x[11]+x[ 7]),18);
x[ 1] ^= rotl((x[ 0]+x[ 3]), 7);
x[ 2] ^= rotl((x[ 1]+x[ 0]), 9);
x[ 3] ^= rotl((x[ 2]+x[ 1]),13);
x[ 0] ^= rotl((x[ 3]+x[ 2]),18);
x[ 6] ^= rotl((x[ 5]+x[ 4]), 7);
x[ 7] ^= rotl((x[ 6]+x[ 5]), 9);
x[ 4] ^= rotl((x[ 7]+x[ 6]),13);
x[ 5] ^= rotl((x[ 4]+x[ 7]),18);
x[11] ^= rotl((x[10]+x[ 9]), 7);
x[ 8] ^= rotl((x[11]+x[10]), 9);
x[ 9] ^= rotl((x[ 8]+x[11]),13);
x[10] ^= rotl((x[ 9]+x[ 8]),18);
x[12] ^= rotl((x[15]+x[14]), 7);
x[13] ^= rotl((x[12]+x[15]), 9);
x[14] ^= rotl((x[13]+x[12]),13);
x[15] ^= rotl((x[14]+x[13]),18);
}
int offset = 0;
for (int i = 0; i < stateSize; i++)
{
intToByteLittle(x[i] + input[i], output, offset);
offset += 4;
}
for (int i = stateSize; i < x.length; i++)
{
intToByteLittle(x[i], output, offset);
offset += 4;
}
}
/**
* 32 bit word to 4 byte array in little endian order
*
* @param x value to 'unpack'
*
* @return value of x expressed as a byte[] array in little endian order
*/
private byte[] intToByteLittle(int x, byte[] out, int off)
{
out[off] = (byte)x;
out[off + 1] = (byte)(x >>> 8);
out[off + 2] = (byte)(x >>> 16);
out[off + 3] = (byte)(x >>> 24);
return out;
}
/**
* Rotate left
*
* @param x value to rotate
* @param y amount to rotate x
*
* @return rotated x
*/
private int rotl(int x, int y)
{
return (x << y) | (x >>> -y);
}
/**
* Pack byte[] array into an int in little endian order
*
* @param x byte array to 'pack'
* @param offset only x[offset]..x[offset+3] will be packed
*
* @return x[offset]..x[offset+3] 'packed' into an int in little-endian order
*/
private int byteToIntLittle(byte[] x, int offset)
{
return ((x[offset] & 255)) |
((x[offset + 1] & 255) << 8) |
((x[offset + 2] & 255) << 16) |
(x[offset + 3] << 24);
}
private void resetCounter()
{
cW0 = 0;
cW1 = 0;
cW2 = 0;
}
private boolean limitExceeded()
{
cW0++;
if (cW0 == 0)
{
cW1++;
if (cW1 == 0)
{
cW2++;
return (cW2 & 0x20) != 0; // 2^(32 + 32 + 6)
}
}
return false;
}
/*
* this relies on the fact len will always be positive.
*/
private boolean limitExceeded(int len)
{
if (cW0 >= 0)
{
cW0 += len;
}
else
{
cW0 += len;
if (cW0 >= 0)
{
cW1++;
if (cW1 == 0)
{
cW2++;
return (cW2 & 0x20) != 0; // 2^(32 + 32 + 6)
}
}
}
return false;
}
}

View File

@@ -0,0 +1,30 @@
package org.bouncycastle.crypto.params;
import org.bouncycastle.crypto.CipherParameters;
public class KeyParameter
implements CipherParameters
{
private byte[] key;
public KeyParameter(
byte[] key)
{
this(key, 0, key.length);
}
public KeyParameter(
byte[] key,
int keyOff,
int keyLen)
{
this.key = new byte[keyLen];
System.arraycopy(key, keyOff, this.key, 0, keyLen);
}
public byte[] getKey()
{
return key;
}
}

View File

@@ -0,0 +1,39 @@
package org.bouncycastle.crypto.params;
import org.bouncycastle.crypto.CipherParameters;
public class ParametersWithIV
implements CipherParameters
{
private byte[] iv;
private CipherParameters parameters;
public ParametersWithIV(
CipherParameters parameters,
byte[] iv)
{
this(parameters, iv, 0, iv.length);
}
public ParametersWithIV(
CipherParameters parameters,
byte[] iv,
int ivOff,
int ivLen)
{
this.iv = new byte[ivLen];
this.parameters = parameters;
System.arraycopy(iv, ivOff, this.iv, 0, ivLen);
}
public byte[] getIV()
{
return iv;
}
public CipherParameters getParameters()
{
return parameters;
}
}

View File

@@ -0,0 +1,247 @@
package org.bouncycastle.util;
import java.io.ByteArrayOutputStream;
import java.util.Vector;
public final class Strings
{
public static String fromUTF8ByteArray(byte[] bytes)
{
int i = 0;
int length = 0;
while (i < bytes.length)
{
length++;
if ((bytes[i] & 0xf0) == 0xf0)
{
// surrogate pair
length++;
i += 4;
}
else if ((bytes[i] & 0xe0) == 0xe0)
{
i += 3;
}
else if ((bytes[i] & 0xc0) == 0xc0)
{
i += 2;
}
else
{
i += 1;
}
}
char[] cs = new char[length];
i = 0;
length = 0;
while (i < bytes.length)
{
char ch;
if ((bytes[i] & 0xf0) == 0xf0)
{
int codePoint = ((bytes[i] & 0x03) << 18) | ((bytes[i+1] & 0x3F) << 12) | ((bytes[i+2] & 0x3F) << 6) | (bytes[i+3] & 0x3F);
int U = codePoint - 0x10000;
char W1 = (char)(0xD800 | (U >> 10));
char W2 = (char)(0xDC00 | (U & 0x3FF));
cs[length++] = W1;
ch = W2;
i += 4;
}
else if ((bytes[i] & 0xe0) == 0xe0)
{
ch = (char)(((bytes[i] & 0x0f) << 12)
| ((bytes[i + 1] & 0x3f) << 6) | (bytes[i + 2] & 0x3f));
i += 3;
}
else if ((bytes[i] & 0xd0) == 0xd0)
{
ch = (char)(((bytes[i] & 0x1f) << 6) | (bytes[i + 1] & 0x3f));
i += 2;
}
else if ((bytes[i] & 0xc0) == 0xc0)
{
ch = (char)(((bytes[i] & 0x1f) << 6) | (bytes[i + 1] & 0x3f));
i += 2;
}
else
{
ch = (char)(bytes[i] & 0xff);
i += 1;
}
cs[length++] = ch;
}
return new String(cs);
}
public static byte[] toUTF8ByteArray(String string)
{
return toUTF8ByteArray(string.toCharArray());
}
public static byte[] toUTF8ByteArray(char[] string)
{
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
char[] c = string;
int i = 0;
while (i < c.length)
{
char ch = c[i];
if (ch < 0x0080)
{
bOut.write(ch);
}
else if (ch < 0x0800)
{
bOut.write(0xc0 | (ch >> 6));
bOut.write(0x80 | (ch & 0x3f));
}
// surrogate pair
else if (ch >= 0xD800 && ch <= 0xDFFF)
{
// in error - can only happen, if the Java String class has a
// bug.
if (i + 1 >= c.length)
{
throw new IllegalStateException("invalid UTF-16 codepoint");
}
char W1 = ch;
ch = c[++i];
char W2 = ch;
// in error - can only happen, if the Java String class has a
// bug.
if (W1 > 0xDBFF)
{
throw new IllegalStateException("invalid UTF-16 codepoint");
}
int codePoint = (((W1 & 0x03FF) << 10) | (W2 & 0x03FF)) + 0x10000;
bOut.write(0xf0 | (codePoint >> 18));
bOut.write(0x80 | ((codePoint >> 12) & 0x3F));
bOut.write(0x80 | ((codePoint >> 6) & 0x3F));
bOut.write(0x80 | (codePoint & 0x3F));
}
else
{
bOut.write(0xe0 | (ch >> 12));
bOut.write(0x80 | ((ch >> 6) & 0x3F));
bOut.write(0x80 | (ch & 0x3F));
}
i++;
}
return bOut.toByteArray();
}
/**
* A locale independent version of toUpperCase.
*
* @param string input to be converted
* @return a US Ascii uppercase version
*/
public static String toUpperCase(String string)
{
boolean changed = false;
char[] chars = string.toCharArray();
for (int i = 0; i != chars.length; i++)
{
char ch = chars[i];
if ('a' <= ch && 'z' >= ch)
{
changed = true;
chars[i] = (char)(ch - 'a' + 'A');
}
}
if (changed)
{
return new String(chars);
}
return string;
}
/**
* A locale independent version of toLowerCase.
*
* @param string input to be converted
* @return a US ASCII lowercase version
*/
public static String toLowerCase(String string)
{
boolean changed = false;
char[] chars = string.toCharArray();
for (int i = 0; i != chars.length; i++)
{
char ch = chars[i];
if ('A' <= ch && 'Z' >= ch)
{
changed = true;
chars[i] = (char)(ch - 'A' + 'a');
}
}
if (changed)
{
return new String(chars);
}
return string;
}
public static byte[] toByteArray(String string)
{
byte[] bytes = new byte[string.length()];
for (int i = 0; i != bytes.length; i++)
{
char ch = string.charAt(i);
bytes[i] = (byte)ch;
}
return bytes;
}
@SuppressWarnings("unchecked")
public static String[] split(String input, char delimiter)
{
Vector v = new Vector();
boolean moreTokens = true;
String subString;
while (moreTokens)
{
int tokenLocation = input.indexOf(delimiter);
if (tokenLocation > 0)
{
subString = input.substring(0, tokenLocation);
v.addElement(subString);
input = input.substring(tokenLocation + 1);
}
else
{
moreTokens = false;
v.addElement(input);
}
}
String[] res = new String[v.size()];
for (int i = 0; i != res.length; i++)
{
res[i] = (String)v.elementAt(i);
}
return res;
}
}

View File

@@ -23,10 +23,10 @@ import static org.junit.Assert.assertArrayEquals;
import java.util.Calendar;
import java.util.Random;
import java.util.UUID;
import junit.framework.TestCase;
import com.keepassdroid.database.PwDate;
import com.keepassdroid.stream.LEDataInputStream;
import com.keepassdroid.stream.LEDataOutputStream;
@@ -180,4 +180,15 @@ public class TypesTest extends TestCase {
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE));
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND));
}
public void testUUID() {
Random rnd = new Random();
byte[] bUUID = new byte[16];
rnd.nextBytes(bUUID);
UUID uuid = Types.bytestoUUID(bUUID);
byte[] eUUID = Types.UUIDtoBytes(uuid);
assertArrayEquals("UUID match failed", bUUID, eUUID);
}
}

View File

@@ -25,6 +25,7 @@ import android.content.Context;
import android.test.AndroidTestCase;
import com.keepassdroid.Database;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDatabaseV3;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroup;
@@ -100,7 +101,7 @@ public class DeleteEntry extends AndroidTestCase {
}
private PwGroup getGroup(PwDatabaseV3 pm, String name) {
private PwGroup getGroup(PwDatabase pm, String name) {
Vector<PwGroup> groups = pm.getGroups();
for ( int i = 0; i < groups.size(); i++ ) {
PwGroup group = groups.get(i);