diff --git a/LICENSE b/LICENSE
index ce4c89cf7..0dbc5557d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -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.
+
diff --git a/src/biz/source_code/base64Coder/Base64Coder.java b/src/biz/source_code/base64Coder/Base64Coder.java
new file mode 100644
index 000000000..9054587d5
--- /dev/null
+++ b/src/biz/source_code/base64Coder/Base64Coder.java
@@ -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.
+*
+*
+* This class is used to encode and decode data in Base64 format as described in RFC 1521.
+*
+*
+* Project home page: www.source-code.biz/base64coder/java
+* Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
+* 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; isun.misc.BASE64Encoder.encodeBuffer(byte[]).
+* @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 in to be processed.
+* @param iLen Number of bytes to be processed in in, starting at iOff.
+* @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 in.
+* @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 in to be processed.
+* @param iLen Number of bytes to process in in, starting at iOff.
+* @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 sun.misc.BASE64Decoder.decodeBuffer(String).
+* @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 in to be processed.
+* @param iLen Number of characters to process in in, starting at iOff.
+* @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(cur));
+ entries.put(cur.getUUID(), new WeakReference(cur));
}
for (int i = 0; i < childGroups.size(); i++ ) {
diff --git a/src/com/keepassdroid/EntryActivity.java b/src/com/keepassdroid/EntryActivity.java
index 14c09a448..58fe5171c 100644
--- a/src/com/keepassdroid/EntryActivity.java
+++ b/src/com/keepassdroid/EntryActivity.java
@@ -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);
diff --git a/src/com/keepassdroid/EntryEditActivity.java b/src/com/keepassdroid/EntryEditActivity.java
index 8c27de2b4..d3f053fec 100644
--- a/src/com/keepassdroid/EntryEditActivity.java
+++ b/src/com/keepassdroid/EntryEditActivity.java
@@ -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);
diff --git a/src/com/keepassdroid/crypto/PwStreamCipherFactory.java b/src/com/keepassdroid/crypto/PwStreamCipherFactory.java
new file mode 100644
index 000000000..1284f28da
--- /dev/null
+++ b/src/com/keepassdroid/crypto/PwStreamCipherFactory.java
@@ -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 .
+ *
+ */
+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;
+ }
+}
diff --git a/src/com/keepassdroid/database/CrsAlgorithm.java b/src/com/keepassdroid/database/CrsAlgorithm.java
index caa4ef4a0..f6878473f 100644
--- a/src/com/keepassdroid/database/CrsAlgorithm.java
+++ b/src/com/keepassdroid/database/CrsAlgorithm.java
@@ -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;
+ }
+
}
diff --git a/src/com/keepassdroid/database/ITimeLogger.java b/src/com/keepassdroid/database/ITimeLogger.java
new file mode 100644
index 000000000..2b62c8611
--- /dev/null
+++ b/src/com/keepassdroid/database/ITimeLogger.java
@@ -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 .
+ *
+ */
+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);
+
+}
diff --git a/src/com/keepassdroid/database/PwCustomIcon.java b/src/com/keepassdroid/database/PwCustomIcon.java
new file mode 100644
index 000000000..ec38994a6
--- /dev/null
+++ b/src/com/keepassdroid/database/PwCustomIcon.java
@@ -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 .
+ *
+ */
+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;
+ }
+}
diff --git a/src/com/keepassdroid/database/PwDatabase.java b/src/com/keepassdroid/database/PwDatabase.java
index 755321ea2..57386e0db 100644
--- a/src/com/keepassdroid/database/PwDatabase.java
+++ b/src/com/keepassdroid/database/PwDatabase.java
@@ -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 {
diff --git a/src/com/keepassdroid/database/PwDatabaseV3.java b/src/com/keepassdroid/database/PwDatabaseV3.java
index 62a0000a2..c144e5d63 100644
--- a/src/com/keepassdroid/database/PwDatabaseV3.java
+++ b/src/com/keepassdroid/database/PwDatabaseV3.java
@@ -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);
}
diff --git a/src/com/keepassdroid/database/PwDatabaseV4.java b/src/com/keepassdroid/database/PwDatabaseV4.java
index 4aec808bb..d4f10683b 100644
--- a/src/com/keepassdroid/database/PwDatabaseV4.java
+++ b/src/com/keepassdroid/database/PwDatabaseV4.java
@@ -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 deletedObjects = new ArrayList();
+ public List customIcons;
+ public Map customData = new HashMap();
+
+ 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 groups = new Vector();
- 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
diff --git a/src/com/keepassdroid/database/PwDbHeaderV4.java b/src/com/keepassdroid/database/PwDbHeaderV4.java
index 230d8f1cf..94788174c 100644
--- a/src/com/keepassdroid/database/PwDbHeaderV4.java
+++ b/src/com/keepassdroid/database/PwDbHeaderV4.java
@@ -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.
diff --git a/src/com/keepassdroid/database/PwDefsV4.java b/src/com/keepassdroid/database/PwDefsV4.java
new file mode 100644
index 000000000..62907c44d
--- /dev/null
+++ b/src/com/keepassdroid/database/PwDefsV4.java
@@ -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 .
+ *
+ */
+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";
+
+}
diff --git a/src/com/keepassdroid/database/PwDeletedObject.java b/src/com/keepassdroid/database/PwDeletedObject.java
new file mode 100644
index 000000000..ea94b9322
--- /dev/null
+++ b/src/com/keepassdroid/database/PwDeletedObject.java
@@ -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 .
+ *
+ */
+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;
+ }
+}
diff --git a/src/com/keepassdroid/database/PwEntry.java b/src/com/keepassdroid/database/PwEntry.java
index b9898e41b..872c18f9e 100644
--- a/src/com/keepassdroid/database/PwEntry.java
+++ b/src/com/keepassdroid/database/PwEntry.java
@@ -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();
diff --git a/src/com/keepassdroid/database/PwEntryV3.java b/src/com/keepassdroid/database/PwEntryV3.java
index 123cad74d..265ea4065 100644
--- a/src/com/keepassdroid/database/PwEntryV3.java
+++ b/src/com/keepassdroid/database/PwEntryV3.java
@@ -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;
+ }
+
}
diff --git a/src/com/keepassdroid/database/PwEntryV4.java b/src/com/keepassdroid/database/PwEntryV4.java
index e11e0173b..3df2b4447 100644
--- a/src/com/keepassdroid/database/PwEntryV4.java
+++ b/src/com/keepassdroid/database/PwEntryV4.java
@@ -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 strings = new HashMap();
+ public Map binaries = new HashMap();
+ public UUID customIconUuid;
+ public String foregroundColor;
+ public String backgroupColor;
+ public String overrideURL;
+ public AutoType autoType = new AutoType();
+ public List history = new ArrayList();
+
+ 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 windowSeqPairs = new HashMap();
+
+ 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;
+ }
}
diff --git a/src/com/keepassdroid/database/PwGroup.java b/src/com/keepassdroid/database/PwGroup.java
index 5fe734c5f..bc53c801d 100644
--- a/src/com/keepassdroid/database/PwGroup.java
+++ b/src/com/keepassdroid/database/PwGroup.java
@@ -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();
}
diff --git a/src/com/keepassdroid/database/PwGroupV3.java b/src/com/keepassdroid/database/PwGroupV3.java
index 4356af204..fc5cf2883 100644
--- a/src/com/keepassdroid/database/PwGroupV3.java
+++ b/src/com/keepassdroid/database/PwGroupV3.java
@@ -118,4 +118,9 @@ public class PwGroupV3 extends PwGroup {
return name;
}
+ @Override
+ public Date getLastMod() {
+ return tLastMod.getJDate();
+ }
+
}
diff --git a/src/com/keepassdroid/database/PwGroupV4.java b/src/com/keepassdroid/database/PwGroupV4.java
index 009a55bbb..b6894bb43 100644
--- a/src/com/keepassdroid/database/PwGroupV4.java
+++ b/src/com/keepassdroid/database/PwGroupV4.java
@@ -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 listGroups = new ArrayList();
+ public List listEntries = new ArrayList();
+
+ 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;
}
diff --git a/src/com/keepassdroid/database/edit/AddEntry.java b/src/com/keepassdroid/database/edit/AddEntry.java
index e2e1f4572..6665b678f 100644
--- a/src/com/keepassdroid/database/edit/AddEntry.java
+++ b/src/com/keepassdroid/database/edit/AddEntry.java
@@ -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(parent));
// Add entry to global
- mDb.entries.put(Types.bytestoUUID(mEntry.uuid), new WeakReference(mEntry));
+ mDb.entries.put(mEntry.getUUID(), new WeakReference(mEntry));
if ( mDb.indexBuilt ) {
// Add entry to search index
diff --git a/src/com/keepassdroid/database/edit/AddEntryV3.java b/src/com/keepassdroid/database/edit/AddEntryV3.java
index fc7df94f4..8454c2e38 100644
--- a/src/com/keepassdroid/database/edit/AddEntryV3.java
+++ b/src/com/keepassdroid/database/edit/AddEntryV3.java
@@ -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
diff --git a/src/com/keepassdroid/database/load/ImporterV3.java b/src/com/keepassdroid/database/load/ImporterV3.java
index 05f2006de..b150c2990 100644
--- a/src/com/keepassdroid/database/load/ImporterV3.java
+++ b/src/com/keepassdroid/database/load/ImporterV3.java
@@ -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);
diff --git a/src/com/keepassdroid/database/load/ImporterV4.java b/src/com/keepassdroid/database/load/ImporterV4.java
index 11f3cdc21..79f919275 100644
--- a/src/com/keepassdroid/database/load/ImporterV4.java
+++ b/src/com/keepassdroid/database/load/ImporterV4.java
@@ -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 ctxGroups = new Stack();
+ 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;
+
+ }
}
diff --git a/src/com/keepassdroid/database/save/PwEntryOutputV3.java b/src/com/keepassdroid/database/save/PwEntryOutputV3.java
index 825d6d43e..0033f6652 100644
--- a/src/com/keepassdroid/database/save/PwEntryOutputV3.java
+++ b/src/com/keepassdroid/database/save/PwEntryOutputV3.java
@@ -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);
diff --git a/src/com/keepassdroid/search/SearchDbHelper.java b/src/com/keepassdroid/search/SearchDbHelper.java
index 8fff8f7e5..cfac80469 100644
--- a/src/com/keepassdroid/search/SearchDbHelper.java
+++ b/src/com/keepassdroid/search/SearchDbHelper.java
@@ -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();
diff --git a/src/com/keepassdroid/utils/Types.java b/src/com/keepassdroid/utils/Types.java
index df1b41b51..6904efba9 100644
--- a/src/com/keepassdroid/utils/Types.java
+++ b/src/com/keepassdroid/utils/Types.java
@@ -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;
+ }
+
}
diff --git a/src/org/bouncycastle/crypto/CipherParameters.java b/src/org/bouncycastle/crypto/CipherParameters.java
new file mode 100644
index 000000000..5be873047
--- /dev/null
+++ b/src/org/bouncycastle/crypto/CipherParameters.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.crypto;
+
+/**
+ * all parameter classes implement this.
+ */
+public interface CipherParameters
+{
+}
diff --git a/src/org/bouncycastle/crypto/DataLengthException.java b/src/org/bouncycastle/crypto/DataLengthException.java
new file mode 100644
index 000000000..29b37ae41
--- /dev/null
+++ b/src/org/bouncycastle/crypto/DataLengthException.java
@@ -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);
+ }
+}
diff --git a/src/org/bouncycastle/crypto/MaxBytesExceededException.java b/src/org/bouncycastle/crypto/MaxBytesExceededException.java
new file mode 100644
index 000000000..5747f27c5
--- /dev/null
+++ b/src/org/bouncycastle/crypto/MaxBytesExceededException.java
@@ -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);
+ }
+}
diff --git a/src/org/bouncycastle/crypto/RuntimeCryptoException.java b/src/org/bouncycastle/crypto/RuntimeCryptoException.java
new file mode 100644
index 000000000..8e782dee1
--- /dev/null
+++ b/src/org/bouncycastle/crypto/RuntimeCryptoException.java
@@ -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);
+ }
+}
diff --git a/src/org/bouncycastle/crypto/StreamCipher.java b/src/org/bouncycastle/crypto/StreamCipher.java
new file mode 100644
index 000000000..2a55d4f65
--- /dev/null
+++ b/src/org/bouncycastle/crypto/StreamCipher.java
@@ -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();
+}
diff --git a/src/org/bouncycastle/crypto/engines/Salsa20Engine.java b/src/org/bouncycastle/crypto/engines/Salsa20Engine.java
new file mode 100644
index 000000000..c51d120b9
--- /dev/null
+++ b/src/org/bouncycastle/crypto/engines/Salsa20Engine.java
@@ -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;
+ }
+}
diff --git a/src/org/bouncycastle/crypto/params/KeyParameter.java b/src/org/bouncycastle/crypto/params/KeyParameter.java
new file mode 100644
index 000000000..5c4fe0e0b
--- /dev/null
+++ b/src/org/bouncycastle/crypto/params/KeyParameter.java
@@ -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;
+ }
+}
diff --git a/src/org/bouncycastle/crypto/params/ParametersWithIV.java b/src/org/bouncycastle/crypto/params/ParametersWithIV.java
new file mode 100644
index 000000000..4a1e6e9a3
--- /dev/null
+++ b/src/org/bouncycastle/crypto/params/ParametersWithIV.java
@@ -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;
+ }
+}
diff --git a/src/org/bouncycastle/util/Strings.java b/src/org/bouncycastle/util/Strings.java
new file mode 100644
index 000000000..253e72261
--- /dev/null
+++ b/src/org/bouncycastle/util/Strings.java
@@ -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;
+ }
+}
diff --git a/tests/src/com/keepassdroid/tests/TypesTest.java b/tests/src/com/keepassdroid/tests/TypesTest.java
index 48ef3ef68..daf09e45a 100644
--- a/tests/src/com/keepassdroid/tests/TypesTest.java
+++ b/tests/src/com/keepassdroid/tests/TypesTest.java
@@ -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);
+ }
}
\ No newline at end of file
diff --git a/tests/src/com/keepassdroid/tests/database/DeleteEntry.java b/tests/src/com/keepassdroid/tests/database/DeleteEntry.java
index b47223779..0e97be845 100644
--- a/tests/src/com/keepassdroid/tests/database/DeleteEntry.java
+++ b/tests/src/com/keepassdroid/tests/database/DeleteEntry.java
@@ -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 groups = pm.getGroups();
for ( int i = 0; i < groups.size(); i++ ) {
PwGroup group = groups.get(i);