Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of github.com:CSEMike/OneSwarm

  • Loading branch information...
commit d744134f78bd8d62aac0ea8b76ce8c69bddd624f 2 parents b3d5143 + 0cd3d3f
@willscott willscott authored
Showing with 5,354 additions and 0 deletions.
  1. +74 −0 xmpp_publickey/build.xml
  2. BIN  xmpp_publickey/lib/derby.jar
  3. BIN  xmpp_publickey/lib/derbytools.jar
  4. BIN  xmpp_publickey/lib/mysql-connector-java-5.1.7-bin.jar
  5. BIN  xmpp_publickey/lib/smack.jar
  6. +28 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/CryptoHandler.java
  7. +37 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/FriendNetwork.java
  8. +176 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/PublicKeyClient.java
  9. +150 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/PublicKeyFriend.java
  10. +182 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/PublicKeyFriendBean.java
  11. +140 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/PublicKeyServer.java
  12. +122 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/Tools.java
  13. +113 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/ssl/client/PublicKeySSLClient.java
  14. +303 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/ssl/server/PublicKeySSLServer.java
  15. +157 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/ssl/server/SSLKeyManager.java
  16. +297 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/PersistentStorage.java
  17. +70 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/sql/DatabaseJob.java
  18. +444 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/sql/PersistentStorageSQL.java
  19. +125 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/sql/Queries.java
  20. +361 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/sql/QueryManager.java
  21. +76 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/sql/derby/PersistentStorageDerby.java
  22. +132 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/sql/derby/TablesDerby.java
  23. +66 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/sql/mysql/PersistentStorageMySQL.java
  24. +34 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/sql/mysql/QueryManagerMysql.java
  25. +131 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/sql/mysql/TablesMySQL.java
  26. +54 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/xmpp/XMPPNetwork.java
  27. +355 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/xmpp/client/PublicKeyXmppClient.java
  28. +220 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/xmpp/server/PublicKeyXmppServer.java
  29. +299 −0 xmpp_publickey/src/java/edu/washington/cs/publickey/xmpp/server/PublicKeyXmppServerProtocol.java
  30. +101 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/PublicKeyFriendTest.java
  31. +43 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/ssl/client/PublicKeySSLClientTest.java
  32. +106 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/ssl/server/PublicKeySSLServerTest.java
  33. +211 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/storage/sql/PersistentStorageSQLBigFatTest.java
  34. +237 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/storage/sql/PersistentStorageSQLTest.java
  35. +37 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/storage/sql/derby/PersistentStorageDerbyBigFatTest.java
  36. +55 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/storage/sql/derby/PersistentStorageDerbyTest.java
  37. +24 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/storage/sql/mysql/PersistentStorageMySQLBigFatTest.java
  38. +23 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/storage/sql/mysql/PersistentStorageMySQLTest.java
  39. +58 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/xmpp/XmppStuffTest.java
  40. +238 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/xmpp/client/PublicKeyCreator.java
  41. +75 −0 xmpp_publickey/test/java/edu/washington/cs/publickey/xmpp/client/PublicKeyXmppClientTest.java
View
74 xmpp_publickey/build.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<project name="publickey" default="all" basedir=".">
+ <taskdef resource="net/sf/antcontrib/antlib.xml" />
+
+ <description>
+ Publickey build file
+ </description>
+
+ <property name="smack.jar" value="lib/smack.jar" />
+ <property name="derby.jar" value="lib/derby.jar" />
+ <property name="derbytools.jar" value="lib/derbytools.jar" />
+ <property name="mysql.jar" value="lib/mysql-connector-java-5.1.7-bin.jar" />
+
+ <property name="bin.dir" value="bin.ant" />
+
+ <!-- set classpath -->
+ <path id="publickey.class.path">
+ <pathelement path="${derby.jar}" />
+ <pathelement path="${smack.jar}" />
+ <pathelement path="${derbytools.jar}" />
+ <pathelement path="${mysql.jar}" />
+ </path>
+
+ <target name="compile" description="compile the code">
+ <mkdir dir="${bin.dir}" />
+ <javac srcdir="src/java" destdir="${bin.dir}" nowarn="yes" source="1.5" target="1.5" includeAntRuntime="no" debug="true" debuglevel="lines,vars,source" fork="yes" memoryMaximumSize="256m">
+ <classpath refid="publickey.class.path" />
+ </javac>
+
+ </target>
+
+
+ <target name="all" depends="client-jar,server-jar" />
+
+ <target name="client-jar" depends="compile" description="Create the client jar">
+ <mkdir dir="build" />
+ <jar destfile="build/publickey-client.jar">
+ <fileset dir="${bin.dir}">
+ <include name="**/*.class" />
+ </fileset>
+ <!-- include the smack.jar file in the release jar -->
+ <!--<zipfileset excludes="META-INF/*.SF" src="${smack.jar}" />-->
+ </jar>
+
+ </target>
+
+ <target name="server-jar" depends="compile" description="Create the server jar">
+ <mkdir dir="build" />
+ <jar destfile="build/publickey-server.jar">
+ <manifest>
+ <attribute name="Built-By" value="${user.name}" />
+ <attribute name="Main-Class" value="edu.washington.cs.publickey.PublicKeyServer" />
+ <attribute name="Class-Path" value="." />
+ </manifest>
+ <fileset dir="${bin.dir}">
+ <include name="**/*.class" />
+ </fileset>
+ <!-- include the smack + derby.jar file in the release jar -->
+ <zipfileset excludes="META-INF/*.SF" src="${smack.jar}" />
+ <zipfileset excludes="META-INF/*.SF" src="${derby.jar}" />
+ <zipfileset excludes="META-INF/*.SF" src="${derbytools.jar}" />
+ <zipfileset excludes="META-INF/*.SF" src="${mysql.jar}" />
+ </jar>
+
+ </target>
+
+ <target name="clean">
+ <!-- Delete the bin directory tree -->
+ <delete failonerror="false">
+ <fileset dir="${bin.dir}" includes="**/*.class" />
+ <fileset dir="build" includes="**/*" />
+ </delete>
+ </target>
+</project>
View
BIN  xmpp_publickey/lib/derby.jar
Binary file not shown
View
BIN  xmpp_publickey/lib/derbytools.jar
Binary file not shown
View
BIN  xmpp_publickey/lib/mysql-connector-java-5.1.7-bin.jar
Binary file not shown
View
BIN  xmpp_publickey/lib/smack.jar
Binary file not shown
View
28 xmpp_publickey/src/java/edu/washington/cs/publickey/CryptoHandler.java
@@ -0,0 +1,28 @@
+/**
+ *
+ */
+package edu.washington.cs.publickey;
+
+import java.security.PublicKey;
+
+import javax.net.ssl.SSLContext;
+
+/**
+ * @author isdal
+ *
+ */
+public interface CryptoHandler {
+
+ /**
+ * Sign the date using a RSA public key
+ *
+ * @param data
+ * @return the signature
+ * @throws Exception
+ */
+ public byte[] sign(byte[] data) throws Exception;
+
+ public SSLContext getSSLContext() throws Exception;
+
+ public PublicKey getPublicKey();
+}
View
37 xmpp_publickey/src/java/edu/washington/cs/publickey/FriendNetwork.java
@@ -0,0 +1,37 @@
+/**
+ *
+ */
+package edu.washington.cs.publickey;
+
+/**
+ * @author isdal
+ *
+ */
+public enum FriendNetwork {
+ XMPP_GOOGLE(0, "XMPP (Google)");
+ private final static FriendNetwork[] val = { XMPP_GOOGLE };
+ private final int networkId;
+
+ private final String networkName;
+
+ private FriendNetwork(int networkId, String networkName) {
+ this.networkId = networkId;
+ this.networkName = networkName;
+ }
+
+ public int getNetworkId() {
+ return networkId;
+ }
+
+ public String getNetworkName() {
+ return networkName;
+ }
+
+ public static FriendNetwork getFromId(int id) {
+ if (id < val.length && id >= 0) {
+ return val[id];
+ }
+
+ return null;
+ }
+}
View
176 xmpp_publickey/src/java/edu/washington/cs/publickey/PublicKeyClient.java
@@ -0,0 +1,176 @@
+package edu.washington.cs.publickey;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
+
+public abstract class PublicKeyClient {
+ private final static String DEFAULT_EXISTING_FRIENDS = "friends.publickeyfriends";
+ public static boolean logToStdOut = false;
+
+ private static void log(String msg) {
+ if (logToStdOut) {
+ System.out.println(msg);
+ }
+ }
+
+ private final File existingFriendsFile;
+
+ protected List<PublicKeyFriend> knownFriends;
+ protected List<byte[]> knownKeys;
+
+ public PublicKeyClient(File existingFriendsFile, List<byte[]> knownKeys) {
+ this.existingFriendsFile = existingFriendsFile;
+ this.knownKeys = knownKeys;
+ this.loadKnownFriends();
+ }
+
+ protected void addKnownFriends(List<PublicKeyFriend> newFriends) throws IOException {
+ log("adding friends:" + newFriends.size());
+
+ HashMap<String, PublicKeyFriend> netUidHashToUid = new HashMap<String, PublicKeyFriend>();
+ HashMap<String, Boolean> knownPubKeys = new HashMap<String, Boolean>();
+ for (PublicKeyFriend f : knownFriends) {
+ String netUid = Base64.encode(f.getSourceNetworkUid());
+ netUidHashToUid.put(netUid, f);
+ if (f.getPublicKey() != null) {
+ String publicKey = Base64.encode(f.getPublicKey());
+ knownPubKeys.put(publicKey, true);
+ }
+ /*
+ * check if any of the known friends does not have any realname, in
+ * that case search for it
+ */
+ if (f.getRealName() == null) {
+ for (PublicKeyFriend newFriend : newFriends) {
+ if (Arrays.equals(newFriend.getSourceNetworkUid(), f.getSourceNetworkUid())) {
+ if (f.getRealName() == null) {
+ log("Updating friend, just got the realname");
+ f.setRealName(newFriend.getRealName());
+ }
+ }
+ }
+ }
+ }
+ // for each entry, check if we already know about that friend
+ // if not, add, if we do, update or ignore
+ for (PublicKeyFriend newFriend : newFriends) {
+ // users can either be added if they are the first user with a
+ // certain uid
+ String netUid = Base64.encode(newFriend.getSourceNetworkUid());
+ if (!netUidHashToUid.containsKey(netUid)) {
+ knownFriends.add(newFriend);
+ log("adding: " + newFriend);
+ } else if (newFriend.getPublicKey() != null) {
+ // or if we don't have that public key
+ String publicKey = Base64.encode(newFriend.getPublicKey());
+ if (!knownPubKeys.containsKey(publicKey)) {
+ // check if we need to add the real name
+ // we might have a dummy user in there
+ PublicKeyFriend existing = netUidHashToUid.get(netUid);
+ if (existing.getPublicKey() == null) {
+ log("updating: " + existing);
+ existing.setPublicKey(newFriend.getPublicKey());
+ existing.setPublicKeySha1(newFriend.getPublicKeySha1());
+ existing.setKeyNick(newFriend.getKeyNick());
+ log(existing.toString());
+ } else {
+ // the existing one already has a public key
+ // this must mean that this is the second+ for that user
+ newFriend.setRealName(existing.getRealName());
+ knownFriends.add(newFriend);
+ log("adding: " + newFriend);
+ }
+ } else {
+ if (newFriend.getRealName() == null) {
+ log("strange, new public key but no uid->realname mapping: " + newFriend);
+ }
+ }
+ }
+ }
+
+ File friendPath = existingFriendsFile;
+ if (friendPath == null) {
+ friendPath = new File(DEFAULT_EXISTING_FRIENDS);
+ }
+ /*
+ * create the parent directory if not exists
+ */
+ if (!friendPath.exists()) {
+ File parent = friendPath.getParentFile();
+ if (parent != null && !parent.exists()) {
+ parent.mkdirs();
+ }
+ }
+
+ String serialized = PublicKeyFriend.serialize(knownFriends.toArray(new PublicKeyFriend[knownFriends.size()]));
+ BufferedWriter out = new BufferedWriter(new FileWriter(friendPath));
+ out.write(serialized);
+ out.close();
+ }
+
+ public abstract void connect() throws Exception;
+
+ public abstract void disconnect() throws Exception;
+
+ public List<PublicKeyFriend> getFriends() {
+ return knownFriends;
+ }
+
+ protected List<byte[]> getKnownKeySha1s() {
+ List<byte[]> knownKeyList = new ArrayList<byte[]>();
+ for (byte[] key : knownKeys) {
+ try {
+ knownKeyList.add(Tools.getSha1(key));
+ } catch (NoSuchAlgorithmException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (UnsupportedEncodingException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ return knownKeyList;
+ }
+
+ private void loadKnownFriends() {
+ knownFriends = new ArrayList<PublicKeyFriend>();
+ File friendPath = existingFriendsFile;
+ if (friendPath == null) {
+ friendPath = new File(DEFAULT_EXISTING_FRIENDS);
+ }
+
+ StringBuilder b = new StringBuilder();
+ BufferedReader in;
+ try {
+ in = new BufferedReader(new FileReader(friendPath));
+ String line;
+ while ((line = in.readLine()) != null) {
+ b.append(line);
+ }
+ in.close();
+ knownFriends.addAll(Arrays.asList(PublicKeyFriend.deserialize(b.toString())));
+ } catch (FileNotFoundException e) {
+ log("existing friends file not found: " + friendPath);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ public abstract void updateFriends() throws Exception;
+
+}
View
150 xmpp_publickey/src/java/edu/washington/cs/publickey/PublicKeyFriend.java
@@ -0,0 +1,150 @@
+/**
+ *
+ */
+package edu.washington.cs.publickey;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.jivesoftware.smack.util.Base64;
+
+/**
+ * @author isdal
+ *
+ */
+public class PublicKeyFriend {
+ public static PublicKeyFriend[] deserialize(String testFriendSerialized) throws IOException {
+ PublicKeyFriendBean[] beans = PublicKeyFriendBean.deserialize(testFriendSerialized);
+ PublicKeyFriend[] fks = new PublicKeyFriend[beans.length];
+ for (int i = 0; i < fks.length; i++) {
+ fks[i] = new PublicKeyFriend(beans[i]);
+ }
+
+ return fks;
+ }
+
+ public static String serialize(PublicKeyFriend[] f) throws IOException {
+ PublicKeyFriendBean[] beans = new PublicKeyFriendBean[f.length];
+ for (int i = 0; i < beans.length; i++) {
+ beans[i] = f[i].getBean();
+ }
+ return PublicKeyFriendBean.serialize(beans);
+ }
+
+ private final PublicKeyFriendBean bean;
+
+ private transient int hashCode = 0;
+
+ public PublicKeyFriend() {
+ bean = new PublicKeyFriendBean();
+ }
+
+ public PublicKeyFriend(PublicKeyFriendBean bean) {
+ this.bean = bean;
+ }
+
+ PublicKeyFriendBean getBean() {
+ return bean;
+ }
+
+ public String getKeyNick() {
+ return bean.getKeyNick();
+ }
+
+ public byte[] getPublicKey() {
+ if (bean.getPublicKey() == null) {
+ return null;
+ }
+ return Base64.decode(bean.getPublicKey());
+ }
+
+ public byte[] getPublicKeySha1() {
+ return Base64.decode(bean.getPublicKeySha1());
+ }
+
+ public String getRealName() {
+ return bean.getRealName();
+ }
+
+ public FriendNetwork getSourceNetwork() {
+ return FriendNetwork.getFromId(bean.getSourceNetwork());
+ }
+
+ public byte[] getSourceNetworkUid() {
+ String sourceNetworkUid = bean.getSourceNetworkUid();
+ if (sourceNetworkUid == null) {
+ return null;
+ }
+ return Base64.decode(sourceNetworkUid);
+ }
+
+ public String serialize() throws IOException {
+ return bean.serialize();
+ }
+
+ public void setKeyNick(String keyNick) {
+ if (keyNick.length() > 255) {
+ throw new RuntimeException("max key nick length is 255");
+ }
+ bean.setKeyNick(keyNick);
+ }
+
+ public void setPublicKey(byte[] publicKey) {
+ bean.setPublicKey(Base64.encodeBytes(publicKey, Base64.DONT_BREAK_LINES));
+ }
+
+ public void setPublicKey(String publicKey) {
+ setPublicKey(Base64.decode(publicKey));
+ }
+
+ public void setPublicKeySha1(byte[] publicKeySha1) {
+ bean.setPublicKeySha1(Base64.encodeBytes(publicKeySha1, Base64.DONT_BREAK_LINES));
+ }
+
+ public void setRealName(String realName) {
+ bean.setRealName(realName);
+ }
+
+ public void setSourceNetwork(FriendNetwork f) {
+ bean.setSourceNetwork(f.getNetworkId());
+ }
+
+ public void setSourceNetworkUid(byte[] uid) {
+ if (uid == null || uid.length != 20) {
+ throw new RuntimeException("uid == null or uid.length != 20");
+ }
+ bean.setSourceNetworkUid(new String(Base64.encodeBytes(uid, Base64.DONT_BREAK_LINES)));
+ }
+
+ public String toString() {
+ return bean.toString();
+ }
+
+ public boolean equals(Object o) {
+ if (o.hashCode() == this.hashCode()) {
+ if (o instanceof PublicKeyFriend) {
+ PublicKeyFriend f = (PublicKeyFriend) o;
+ if (f.getPublicKey() != null && this.getPublicKey() != null) {
+ return Arrays.equals(f.getPublicKey(), this.getPublicKey());
+ } else if (f.getSourceNetworkUid() != null && this.getSourceNetworkUid() != null) {
+ return Arrays.equals(f.getSourceNetworkUid(), this.getSourceNetworkUid());
+ }
+
+ }
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ if (hashCode == 0) {
+ if (getPublicKey() != null) {
+ hashCode = Arrays.hashCode(getPublicKey());
+ } else if (getSourceNetworkUid() != null) {
+ hashCode = Arrays.hashCode(getSourceNetworkUid());
+ }
+ }
+
+ return hashCode;
+ }
+
+}
View
182 xmpp_publickey/src/java/edu/washington/cs/publickey/PublicKeyFriendBean.java
@@ -0,0 +1,182 @@
+/**
+ *
+ */
+package edu.washington.cs.publickey;
+
+import java.beans.XMLDecoder;
+import java.beans.XMLEncoder;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * @author isdal
+ *
+ */
+public class PublicKeyFriendBean {
+ public static boolean logToStdOut = false;
+
+ public static PublicKeyFriendBean[] deserialize(String raw) throws IOException {
+ log("'" + raw + "'");
+ ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader beanClassLoader = PublicKeyFriendBean.class.getClassLoader();
+ log("using cl " + oldClassLoader);
+ if (!oldClassLoader.equals(beanClassLoader)) {
+ log("setting class loader to: " + beanClassLoader);
+ Thread.currentThread().setContextClassLoader(beanClassLoader);
+ }
+ if (raw.length() == 0) {
+ return new PublicKeyFriendBean[0];
+ }
+ PublicKeyFriendBean[] res = null;
+ ByteArrayInputStream in = new ByteArrayInputStream(raw.getBytes("UTF-8"));
+ XMLDecoder d = new XMLDecoder(in);
+
+ Object o = d.readObject();
+ if (o instanceof PublicKeyFriendBean[]) {
+ res = ((PublicKeyFriendBean[]) o);
+ }
+ in.close();
+ if (!oldClassLoader.equals(beanClassLoader)) {
+ log("setting old classloader: " + oldClassLoader);
+ Thread.currentThread().setContextClassLoader(oldClassLoader);
+ }
+ return res;
+ }
+
+ private static void log(String msg) {
+ if (logToStdOut) {
+ System.out.println(msg);
+ }
+ }
+
+ public static String serialize(PublicKeyFriendBean[] f) throws IOException {
+ ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader beanClassLoader = PublicKeyFriendBean.class.getClassLoader();
+ log("using cl " + oldClassLoader);
+ if (!oldClassLoader.equals(beanClassLoader)) {
+ log("setting class loader to: " + beanClassLoader);
+ Thread.currentThread().setContextClassLoader(beanClassLoader);
+ }
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ XMLEncoder e = new XMLEncoder(out);
+ e.writeObject(f);
+ e.close();
+ out.close();
+ String ret = new String(out.toByteArray(), "UTF-8");
+ log("'" + ret + "'");
+ if (!oldClassLoader.equals(beanClassLoader)) {
+ log("setting old classloader: " + oldClassLoader);
+ Thread.currentThread().setContextClassLoader(oldClassLoader);
+ }
+ return ret;
+ }
+
+ private String keyNick;
+ private String publicKey;
+ private String publicKeySha1;
+
+ private String realName;
+
+ private int sourceNetwork;
+
+ private String sourceNetworkUid;
+
+ public boolean equals(Object o) {
+ if (o != null) {
+ if (o instanceof PublicKeyFriendBean) {
+ PublicKeyFriendBean c = (PublicKeyFriendBean) o;
+ if (!publicKey.equals(c.getPublicKey())) {
+ log("public key not same");
+ log("'" + publicKey + "'");
+ log("'" + c.getPublicKey() + "'");
+ return false;
+ }
+ if (sourceNetwork != c.getSourceNetwork()) {
+ log("source net not same");
+ return false;
+ }
+ if (!sourceNetworkUid.equals(c.getSourceNetworkUid())) {
+ log("net uid not same");
+ log("'" + sourceNetworkUid + "'");
+ log("'" + c.getSourceNetworkUid() + "'");
+ return false;
+ }
+ if (!keyNick.equals(c.getKeyNick())) {
+ log("key nick not same");
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public String getKeyNick() {
+ return keyNick;
+ }
+
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+ public String getPublicKeySha1() {
+ return publicKeySha1;
+ }
+
+ public String getRealName() {
+ return realName;
+ }
+
+ public int getSourceNetwork() {
+ return sourceNetwork;
+ }
+
+ public String getSourceNetworkUid() {
+ return sourceNetworkUid;
+ }
+
+ // public static PublicKeyFriendBean[] deserialize(String raw) throws
+ // IOException {
+ // return deserialize(raw, Thread.currentThread().getContextClassLoader());
+ // }
+
+ public String serialize() throws IOException {
+ return serialize(new PublicKeyFriendBean[] { this });
+ }
+
+ // public static String serialize(PublicKeyFriendBean[] f) throws
+ // IOException {
+ // return serialize(f, Thread.currentThread().getContextClassLoader());
+ // }
+
+ public void setKeyNick(String keyNick) {
+ this.keyNick = keyNick;
+ }
+
+ public void setPublicKey(String publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ public void setPublicKeySha1(String publicKeySha1) {
+ this.publicKeySha1 = publicKeySha1;
+ }
+
+ public void setRealName(String realName) {
+ this.realName = realName;
+ }
+
+ public void setSourceNetwork(int sourceNetwork) {
+ this.sourceNetwork = sourceNetwork;
+ }
+
+ public void setSourceNetworkUid(String sourceNetworkUid) {
+ this.sourceNetworkUid = sourceNetworkUid;
+ }
+
+ public String toString() {
+ return realName + " (" + keyNick + ") " + sourceNetwork + " " + sourceNetworkUid + " " + publicKey;
+ }
+}
View
140 xmpp_publickey/src/java/edu/washington/cs/publickey/PublicKeyServer.java
@@ -0,0 +1,140 @@
+/**
+ *
+ */
+package edu.washington.cs.publickey;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+import edu.washington.cs.publickey.ssl.server.PublicKeySSLServer;
+import edu.washington.cs.publickey.storage.PersistentStorage;
+import edu.washington.cs.publickey.storage.sql.derby.PersistentStorageDerby;
+import edu.washington.cs.publickey.storage.sql.mysql.PersistentStorageMySQL;
+import edu.washington.cs.publickey.xmpp.server.PublicKeyXmppServer;
+
+/**
+ * @author isdal
+ *
+ */
+public class PublicKeyServer {
+
+ private final static String key_db_type = "db_type";
+ private final static String key_xmpp = "xmpp";
+ private final static String key_ssl = "ssl";
+ private final static String key_ssl_passwd = "ssl_keystore_passwd";
+ private PersistentStorage storage;
+ private final List<PublicKeyXmppServer> publicKeyXmppServers = new LinkedList<PublicKeyXmppServer>();
+ private PublicKeySSLServer publicKeySSLServer;
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ if (args.length != 1) {
+ System.out.println("USAGE: PublicKeyXmppServer configFile");
+ System.exit(1);
+ }
+
+ try {
+ FileInputStream fis = new FileInputStream(args[0]);
+
+ Properties props = new Properties();
+
+ props.load(fis);
+ new PublicKeyServer(props);
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ public PublicKeyServer(Properties properties) {
+ String dbType = properties.getProperty(key_db_type);
+ try {
+ if ("DERBY".toLowerCase().equals(dbType.toLowerCase())) {
+ storage = new PersistentStorageDerby(properties);
+ } else if ("MYSQL".toLowerCase().equals(dbType.toLowerCase())) {
+ storage = new PersistentStorageMySQL(properties, false);
+ } else {
+ System.err.println("unknown storage type: " + dbType);
+ System.exit(1);
+ }
+ } catch (Exception e) {
+ System.err.println("error when initializing storate type:" + dbType);
+ e.printStackTrace();
+ System.exit(1);
+ }
+
+ String xmppEnabled = properties.getProperty(key_xmpp);
+ try {
+ if (xmppEnabled != null && xmppEnabled.equals("1")) {
+ String xmppUserNames = properties.getProperty(PublicKeyXmppServer.key_username);
+ String[] split = xmppUserNames.split(",");
+ for (String u : split) {
+ u = u.trim();
+ Properties localProperties = new Properties(properties);
+ localProperties.setProperty(PublicKeyXmppServer.key_username, u);
+ publicKeyXmppServers.add(new PublicKeyXmppServer(localProperties, storage));
+ Thread.sleep(10000);
+ }
+ }
+ } catch (InterruptedException e) {
+ }
+
+ String sslEnabled = properties.getProperty(key_ssl);
+ try {
+ if (sslEnabled != null && sslEnabled.equals("1")) {
+ char[] keyStorePasswd;
+ if (properties.containsKey(key_ssl_passwd)) {
+ keyStorePasswd = properties.getProperty(key_ssl_passwd).toCharArray();
+ } else {
+ System.out.println("Please supply the key store password");
+ BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+
+ keyStorePasswd = in.readLine().toCharArray();
+
+ }
+ publicKeySSLServer = new PublicKeySSLServer(properties, storage, keyStorePasswd);
+
+ }
+ } catch (Exception e) {
+ System.err.println("unable to create publickeyserver");
+ e.printStackTrace();
+ }
+
+ // add a shutdown hook to make sure that we close nicely
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ public void run() {
+ if (storage != null) {
+ storage.shutdown();
+ }
+ for (PublicKeyXmppServer publicKeyXmppServer : publicKeyXmppServers) {
+ publicKeyXmppServer.shutdown();
+ }
+ if (publicKeySSLServer != null) {
+ publicKeySSLServer.shutdown();
+ }
+ }
+ });
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+ try {
+ while (!in.readLine().equals("exit")) {
+ System.out.println("type exit to shut down the server");
+ }
+ } catch (Exception ex) {
+ // ex.printStackTrace();
+ }
+
+ }
+}
View
122 xmpp_publickey/src/java/edu/washington/cs/publickey/Tools.java
@@ -0,0 +1,122 @@
+package edu.washington.cs.publickey;
+
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.util.Base64;
+
+public class Tools {
+
+ public final static byte[] SHA1_SEED = Base64.decode("jRofQW+cWzw0ESusBZ4eO0Zm4ZuTWt1j" + "nqpIS5UDMAt3QPluOWenQUy2RZzvJJv1" + "ab5QyEqfFrEQVRcWEDun+mb9051wtHZD" + "gt0vMbVOWLdX/Ptd7C/aZUiIcgpWqaR6" + "ruv6s6ZO7xWXHP/ZU+HbWIUXg7eFCj4B" + "+WBtbm319fcH0A/kAeUsRFBUSOh3K/Qf" + "DvQpAPVG730acxWQTKbkNg4CmPi5iVmd" + "9ow0QwPntAMw0wMocP3DBWbuqM1hrQ4d" + "wWSef52A52PV4qTQjus9vc1T7Br8fDOG" + "3NK8CwzB1bgH72tuj13Go8CCNwHksLWm" + "6wJwEPbRLt4/NUXBIYdv9CbEmBMDc601" + "i7Y/9IKhIW01fAatjZwTM5aTsQ1qj+BZ" + "S9z384SQ1d6QAQN5Gz0GxXcaQJdGN7M3" + "rTmP4SMebOwGPR99MBG8zStNzcoVC/mk" + "JTjJFXYtqNWTtWMefMZCY8vBjk43Oslt");
+
+ public static class AcceptAllFilter implements PacketFilter {
+
+ public boolean accept(Packet packet) {
+ return true;
+ }
+ }
+
+ public final static String DEFAULT_PUBLIC_KEY_SERVER = "publickey.cs.washington.edu@gmail.com";
+
+ // sent in the client hello
+ public final static String PUBLICKEY_PAYLOAD_KEY__PublicKeyFriend = "PUBLICKEY_PAYLOAD_PUBLIC_KEY";
+
+ // sent the in server challenge
+ public final static String PUBLICKEY_PAYLOAD_NOUNCE__base64_byte_array = "PUBLICKEY_PAYLOAD_NOUNCE";
+
+ // sent in the client request
+ public final static String PUBLICKEY_PAYLOAD_SIGNATURE__String_Base64 = "PUBLICKEY_PAYLOAD_SIGNATURE";
+ public final static String PUBLICKEY_PAYLOAD_FRIENDS__MergeSha1_Base64 = "PUBLICKEY_PAYLOAD_FRIENDS";
+ public final static String PUBLICKEY_PAYLOAD_KNOWN_KEYS__MergeSha1_Base64 = "PUBLICKEY_PAYLOAD_KNOWN_KEYS";
+
+ // send in the server response
+ public final static String PUBLICKEY_PAYLOAD_FRIENDS_KEYS__PublicKeyFriend_array = "PUBLICKEY_PAYLOAD_FRIENDS_KEYS";
+
+ private static MessageDigest md;
+
+ static {
+ try {
+ md = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public static byte[] getSha1(String networkUid) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+ if (networkUid.contains("/")) {
+ String newId = networkUid.split("/")[0];
+ // System.out.println("converting: " + networkUid + "->" + newId);
+ networkUid = newId;
+ }
+ return getSha1(networkUid.getBytes());
+ }
+
+ public static byte[] getSha1(byte[] bytes) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+ md.update(SHA1_SEED);
+ md.update(bytes, 0, bytes.length);
+ byte[] sha1hash = md.digest();
+ md.reset();
+ return sha1hash;
+ }
+
+ public static List<byte[]> getListSha1(String mergedSha1s) {
+ byte[] merged = Base64.decode(mergedSha1s);
+ if (merged.length % 20 != 0) {
+ throw new RuntimeException("error, only 20 byte sha1 allowed: " + merged.length + " mod 20!=0");
+ }
+ List<byte[]> listOfSha1s = new LinkedList<byte[]>();
+ for (int i = 0; i < merged.length / 20; i++) {
+ byte[] sha1 = new byte[20];
+ System.arraycopy(merged, i * 20, sha1, 0, 20);
+ listOfSha1s.add(sha1);
+ }
+ return listOfSha1s;
+ }
+
+ public static String mergeSha1sAndBase64(List<byte[]> listOfSha1s) {
+ byte[] merged = new byte[listOfSha1s.size() * 20];
+ for (int i = 0; i < listOfSha1s.size(); i++) {
+ if (listOfSha1s.get(i).length != 20) {
+ throw new RuntimeException("only 20 byte sha1 hashes is allowed");
+ }
+ System.arraycopy(listOfSha1s.get(i), 0, merged, i * 20, 20);
+ }
+ return Base64.encodeBytes(merged, Base64.DONT_BREAK_LINES);
+ }
+
+ public static PublicKey keyForEncodedBytes(byte[] inBytes) throws InvalidKeySpecException {
+
+ X509EncodedKeySpec key_spec = new X509EncodedKeySpec(inBytes);
+ KeyFactory keyFactory = null;
+ try {
+ keyFactory = KeyFactory.getInstance("RSA");
+ } catch (NoSuchAlgorithmException e) {
+ System.err.println(e);
+ e.printStackTrace();
+ return null;
+ }
+
+ return keyFactory.generatePublic(key_spec);
+ }
+
+ public static boolean verifySignature(PublicKey key, byte[] nounce, byte[] signature) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {
+ Signature sig = Signature.getInstance("SHA1withRSA");
+ sig.initVerify(key);
+ sig.update(nounce);
+ return sig.verify(signature);
+ }
+}
View
113 xmpp_publickey/src/java/edu/washington/cs/publickey/ssl/client/PublicKeySSLClient.java
@@ -0,0 +1,113 @@
+package edu.washington.cs.publickey.ssl.client;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.UnknownHostException;
+import java.security.cert.Certificate;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+
+import javax.net.ssl.SSLSocket;
+
+import edu.washington.cs.publickey.CryptoHandler;
+import edu.washington.cs.publickey.PublicKeyClient;
+import edu.washington.cs.publickey.PublicKeyFriend;
+
+public class PublicKeySSLClient extends PublicKeyClient {
+ private SSLSocket socket;
+ private final String server;
+ private final int port;
+ private final CryptoHandler cryptoHandler;
+ private final byte[] expectedRemoteKey;
+
+ public PublicKeySSLClient(File existingFriendsFile, List<byte[]> knownKeys, String server, int port, byte[] expectedRemoteKey, CryptoHandler cryptoHandler) throws UnknownHostException, IOException, Exception {
+ super(existingFriendsFile, knownKeys);
+ this.server = server;
+ this.port = port;
+ this.cryptoHandler = cryptoHandler;
+ this.expectedRemoteKey = expectedRemoteKey;
+
+ }
+
+ @Override
+ public void connect() throws Exception {
+ if (socket != null) {
+ throw new Exception("Already connected");
+ }
+
+ socket = (SSLSocket) cryptoHandler.getSSLContext().getSocketFactory().createSocket(server, port);
+
+ // check the certificate
+ Certificate[] remoteCerts = socket.getSession().getPeerCertificates();
+ if (remoteCerts.length != 1) {
+ disconnect();
+ throw new Exception("Unable to get remote certificate");
+ }
+ //System.out.println("SSL Client: remote public key: " + Base64.encodeBytes(remoteCerts[0].getPublicKey().getEncoded(), Base64.DONT_BREAK_LINES));
+ if (!Arrays.equals(expectedRemoteKey, remoteCerts[0].getPublicKey().getEncoded())) {
+ disconnect();
+ throw new Exception("Remote server key verification failure! Potential remote security breach or man-in-the-middle attack!!!");
+ }
+ //System.out.println("SSLClient connected");
+ }
+
+ @Override
+ public void disconnect() throws Exception {
+ if (socket != null) {
+ SSLSocket localSocket = socket;
+ socket = null;
+ localSocket.close();
+ }
+ }
+
+ @Override
+ public void updateFriends() throws Exception {
+
+ DataOutputStream out = null;
+ BufferedReader in = null;
+ // ok, all seems fine
+ // open the output stream
+ try {
+ out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
+ /*
+ * the protocol is, write an int specifying the number of known
+ * friends then the sha1s of their public keys
+ */
+ List<byte[]> knownKeySha1s = super.getKnownKeySha1s();
+ out.writeInt(knownKeySha1s.size());
+ for (byte[] keySha : knownKeySha1s) {
+ if (keySha.length != 20) {
+ throw new Exception("Key sha1 length error, must be 20! (len=" + keySha.length + ")");
+ }
+ out.write(keySha);
+ }
+ out.flush();
+
+ // now read the response, this is gzipped xml
+ in = new BufferedReader(new InputStreamReader(new GZIPInputStream(socket.getInputStream())));
+ StringBuilder b = new StringBuilder();
+ String line;
+ while ((line = in.readLine()) != null) {
+ b.append(line + "\n");
+ //System.out.println(line);
+ }
+
+ PublicKeyFriend[] newFriends = PublicKeyFriend.deserialize(b.toString());
+ addKnownFriends(Arrays.asList(newFriends));
+
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ if (out != null) {
+ out.close();
+ }
+ disconnect();
+ }
+ }
+}
View
303 xmpp_publickey/src/java/edu/washington/cs/publickey/ssl/server/PublicKeySSLServer.java
@@ -0,0 +1,303 @@
+package edu.washington.cs.publickey.ssl.server;
+
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.zip.GZIPOutputStream;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+
+import edu.washington.cs.publickey.PublicKeyFriend;
+import edu.washington.cs.publickey.Tools;
+import edu.washington.cs.publickey.storage.PersistentStorage;
+
+public class PublicKeySSLServer {
+
+ static final String KEY_SSL_SERVER_KEYSTORE = "ssl_server_keystore";
+ final static String KEY_SSL_PORT = "ssl_server_port";
+
+ private static final int NUM_THREADS = 500;
+ private static final int MAX_DB_QUEUE = 20;
+
+ /*
+ * to protect against dos, only allow 2 connection attempts/second from the
+ * same ip and max 50 concurrent connections
+ */
+ private static final long MIN_MS_BETWEEN_CONNECT_ATTEMPTS_PER_IP = 500;
+ private static final Integer MAX_CONNECTION_PER_IP = 10;
+
+ private final PersistentStorage storage;
+ private final int serverPort;
+ private volatile boolean quit = false;
+ private final ExecutorService threadPool;
+ private SSLServerSocket serverSocket;
+
+ private HashMap<String, Integer> activeConnections = new HashMap<String, Integer>();
+ private HashMap<String, Long> lastConnectAttempt = new HashMap<String, Long>();
+
+ private volatile int queueLength = 0;
+
+ public PublicKeySSLServer(Properties props, PersistentStorage storage, char[] keystorePassword) throws IOException, KeyManagementException, NoSuchAlgorithmException, KeyStoreException, CertificateException, UnrecoverableKeyException, InterruptedException {
+ this.serverPort = Integer.parseInt((String) props.get(KEY_SSL_PORT));
+ this.storage = storage;
+ this.threadPool = Executors.newFixedThreadPool(NUM_THREADS);
+
+ File keyStoreFile = new File(props.getProperty(KEY_SSL_SERVER_KEYSTORE));
+
+ SSLKeyManager sslManager = new SSLKeyManager(keyStoreFile, keystorePassword);
+ serverSocket = sslManager.createServerSocket(serverPort);
+ serverSocket.setNeedClientAuth(true);
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ System.out.println("SSL server: listening on port " + serverPort);
+ while (!quit) {
+ try {
+ SSLSocket csocket = (SSLSocket) serverSocket.accept();
+
+ String remoteIp = csocket.getInetAddress().getHostAddress();
+ if (isConnectionAllowed(remoteIp)) {
+ queueLength++;
+ // System.out.println("connection from: " + remoteIp
+ // + " queue=" + queueLength);
+ initiatingConnection(remoteIp);
+ PublicKeySSLServerProtocol publicKeySSLServerProtocol = new PublicKeySSLServerProtocol(csocket);
+ threadPool.execute(publicKeySSLServerProtocol);
+ }
+ } catch (IOException e) {
+ if (e instanceof java.net.SocketException && e.getMessage().equals("Socket closed")) {
+ System.out.println("SSL Server: closing socket");
+ } else {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ });
+ t.setName("SSL Server accept thread");
+ t.start();
+ }
+
+ private void initiatingConnection(String remoteIp) {
+ synchronized (lastConnectAttempt) {
+ lastConnectAttempt.put(remoteIp, System.currentTimeMillis());
+ Integer active = activeConnections.get(remoteIp);
+ if (active == null) {
+ activeConnections.put(remoteIp, 1);
+ } else {
+ activeConnections.put(remoteIp, active + 1);
+ }
+ }
+ }
+
+ private void closingConnection(String remoteIp) {
+ synchronized (lastConnectAttempt) {
+ Integer active = activeConnections.get(remoteIp);
+ if (active <= 1) {
+ activeConnections.remove(remoteIp);
+ } else {
+ activeConnections.put(remoteIp, active - 1);
+ }
+ }
+ }
+
+ private boolean isConnectionAllowed(String remoteIp) {
+ synchronized (lastConnectAttempt) {
+ Long lastAttempt = lastConnectAttempt.get(remoteIp);
+
+ if (lastAttempt != null) {
+ long timeSince = System.currentTimeMillis() - lastAttempt;
+ if (timeSince < MIN_MS_BETWEEN_CONNECT_ATTEMPTS_PER_IP) {
+ System.err.println(new Date() + ": connection from '" + remoteIp + "' denied, " + " to high connect frequency (" + timeSince + "ms<" + MIN_MS_BETWEEN_CONNECT_ATTEMPTS_PER_IP + "ms)");
+ return false;
+ }
+ }
+ Integer numActiveConnections = activeConnections.get(remoteIp);
+ if (numActiveConnections != null && numActiveConnections > MAX_CONNECTION_PER_IP) {
+ System.err.println(new Date() + ": connection from '" + remoteIp + "' denied, " + " to many connections (" + numActiveConnections + ">" + MAX_CONNECTION_PER_IP + ")");
+ return false;
+ }
+ return true;
+ }
+ }
+
+ class PublicKeySSLServerProtocol implements Runnable {
+
+ private final SSLSocket socket;
+ private final String remoteIp;
+ long timeInclNet;
+
+ public PublicKeySSLServerProtocol(SSLSocket socket) {
+
+ this.socket = socket;
+ this.remoteIp = socket.getInetAddress().getHostAddress();
+ timeInclNet = System.currentTimeMillis();
+ }
+
+ public void run() {
+ try {
+ int dbQueueLength = storage.getDbQueueLength();
+ if (dbQueueLength > MAX_DB_QUEUE) {
+
+ long t = System.currentTimeMillis();
+
+ DataInputStream in = new DataInputStream(socket.getInputStream());
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(socket.getOutputStream())));
+
+ // read to not confuse the client
+ readIgnoreList(in);
+
+ out.write(PublicKeyFriend.serialize(new PublicKeyFriend[0]));
+ out.close();
+ in.close();
+ // log("dropping incoming connection, db_queue: " +
+ // dbQueueLength + " conn_queue: " + queueLength + " took: "
+ // + (System.currentTimeMillis() - t) + "ms");
+ } else {
+ Certificate[] remoteCerts;
+
+ remoteCerts = socket.getSession().getPeerCertificates();
+ if (remoteCerts.length > 0) {
+ byte[] remoteKey = remoteCerts[0].getPublicKey().getEncoded();
+ // log("accepting incoming connection, db_queue: " +
+ // dbQueueLength + " conn_queue: " + queueLength);
+ DataInputStream in = new DataInputStream(socket.getInputStream());
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(socket.getOutputStream())));
+
+ HashSet<Integer> keysToIgnore = readIgnoreList(in);
+
+ long timeExclNet = System.currentTimeMillis();
+ // ok, now we know what to ignore
+ // lets try to find new friends
+ PublicKeyFriend f = new PublicKeyFriend();
+ f.setPublicKey(remoteKey);
+ f.setPublicKeySha1(Tools.getSha1(remoteKey));
+ long dbStartTime = System.currentTimeMillis();
+ List<PublicKeyFriend> allFriends = storage.getFriendsUsingPublicKey(f);
+ long preLastSeenUpdate = System.currentTimeMillis();
+ storage.updateUserLastSeen(f);
+ long dbTime = System.currentTimeMillis() - dbStartTime;
+
+ long lastSeenOverhead = System.currentTimeMillis() - preLastSeenUpdate;
+ Map<Integer, PublicKeyFriend> newFriends = new HashMap<Integer, PublicKeyFriend>();
+
+ // add them all
+ for (PublicKeyFriend friend : allFriends) {
+ // ignore friends the user already knows of
+ int friendHash = Arrays.hashCode(friend.getPublicKeySha1());
+ if (!keysToIgnore.contains(friendHash)) {
+ // check if we already added this one
+ if (!newFriends.containsKey(friendHash)) {
+ newFriends.put(friendHash, friend);
+ }
+ }
+ }
+
+ List<PublicKeyFriend> friendsArray = new LinkedList<PublicKeyFriend>();
+ friendsArray.addAll(newFriends.values());
+
+ /*
+ * for debugging, just return an empty list
+ */
+ String serialized = PublicKeyFriend.serialize(new PublicKeyFriend[0]);
+ // String serialized =
+ // PublicKeyFriend.serialize(friendsArray.toArray(new
+ // PublicKeyFriend[newFriends.size()]));
+ timeExclNet = System.currentTimeMillis() - timeExclNet;
+ out.write(serialized);
+ out.close();
+ in.close();
+ timeInclNet = System.currentTimeMillis() - timeInclNet;
+ log("done, returned: " + friendsArray.size() + " ignored: " + keysToIgnore.size() + " time: (queries: " + timeExclNet + " lastSeen: " + lastSeenOverhead + " total: " + timeInclNet + " db=" + dbTime + ")");
+ }
+
+ }
+ } catch (java.io.EOFException e) {
+ System.err.println(remoteIp + ": " + "EOF to early");
+ } catch (SSLPeerUnverifiedException e) {
+ System.err.println(remoteIp + ": no cert, closing conn");
+ } catch (java.net.SocketException e) {
+ if ("Connection timed out".equals(e.getMessage())) {
+ System.err.println(remoteIp + ": other side timed out");
+ } else if ("Connection reset".equals(e.getMessage())) {
+ System.err.println(remoteIp + ": other side closed socket");
+ } else if ("Broken pipe".equals(e.getMessage())) {
+ System.err.println(remoteIp + ": broken pipe");
+ } else {
+ e.printStackTrace();
+ }
+ } catch (IOException e) {
+
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e1) {
+ }
+ }
+ closingConnection(remoteIp);
+ queueLength--;
+ }
+ }
+
+ private HashSet<Integer> readIgnoreList(DataInputStream in) throws IOException {
+ int numToIgnore = in.readInt();
+ if (numToIgnore > 100000) {
+ System.err.println("warning: user specified more that 100000 friends (!!!???), closing conn");
+ socket.close();
+ throw new IOException("user specified invalid data");
+ }
+
+ HashSet<Integer> keysToIgnore = new HashSet<Integer>();
+ byte[] pubKeySha = new byte[20];
+ for (int i = 0; i < numToIgnore; i++) {
+ // read 20 bytes
+ in.readFully(pubKeySha);
+ int hash = Arrays.hashCode(pubKeySha);
+ keysToIgnore.add(hash);
+ }
+ return keysToIgnore;
+ }
+
+ private void log(String msg) {
+ System.out.println(remoteIp + ": " + msg);
+ }
+ }
+
+ public void shutdown() {
+ quit = true;
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ threadPool.shutdown();
+ storage.shutdown();
+ }
+}
View
157 xmpp_publickey/src/java/edu/washington/cs/publickey/ssl/server/SSLKeyManager.java
@@ -0,0 +1,157 @@
+package edu.washington.cs.publickey.ssl.server;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.jivesoftware.smack.util.Base64;
+
+public class SSLKeyManager {
+
+ private static String KEY_ID = "PublicKey SSL Key";
+
+ public static String bytesToHex(byte[] data) {
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < data.length; i++) {
+ buf.append(byteToHex(data[i]));
+ }
+ return (buf.toString());
+ }
+
+ public static String byteToHex(byte data) {
+ StringBuffer buf = new StringBuffer();
+ buf.append(Integer.toHexString(0x0100 + (data & 0x00FF)).substring(1));
+ // buf.append(toHexChar((data >>> 4) & 0x0F));
+ // buf.append(toHexChar(data & 0x0F));
+ return buf.toString();
+ }
+
+ private final KeyStore keyStore;
+ private final File keyStoreFile;
+
+ private final char[] password;
+
+ private final SSLContext sslContext;
+
+ public SSLKeyManager(File keyStore, char[] password) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException, CertificateException, UnrecoverableKeyException, IOException, InterruptedException {
+ this.keyStoreFile = keyStore;
+ this.password = password;
+
+ this.keyStore = createKeyStore();
+ this.sslContext = createSSLContext();
+ }
+
+ private KeyStore createKeyStore() throws NoSuchAlgorithmException, IOException, InterruptedException, KeyStoreException, CertificateException, UnrecoverableKeyException, KeyManagementException {
+
+ if (keyStoreFile == null) {
+ throw new IOException("keystore file is null!");
+ }
+ // Check if we need to create the file/dirs
+ if (keyStoreFile.isDirectory()) {
+ throw new IOException("keystore file is a directory, " + keyStoreFile.getAbsolutePath());
+ } else if (!keyStoreFile.exists()) {
+ File parent = keyStoreFile.getParentFile();
+ if (!parent.exists()) {
+ boolean success = keyStoreFile.mkdirs();
+ if (!success) {
+ throw new IOException("unable to create directory: '" + parent.getAbsolutePath() + "'");
+ }
+ }
+ }
+
+ // Create keystore.
+ if (keyStoreFile.exists() == false) {
+ System.out.println("Generating new keystore...");
+ byte[] randomStuff = new byte[16];
+ SecureRandom securerandom = SecureRandom.getInstance("SHA1PRNG");
+ securerandom.nextBytes(randomStuff);
+ String stringName = bytesToHex(randomStuff);
+
+ String stringPassword = new String(password);
+ String[] cmd = new String[] { System.getProperty("java.home") + File.separator + "bin" + File.separator + "keytool", "-genkey", "-alias", KEY_ID, "-validity", "" + 100 * 365, "-keyalg", "RSA", "-keysize", "1024", "-dname", "CN=" + stringName, "-keystore", keyStoreFile.getPath(), "-keypass", stringPassword, "-storetype", "JKS", "-storepass", stringPassword };
+ Process process = Runtime.getRuntime().exec(cmd);
+ process.waitFor();
+ BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ String line;
+ while ((line = in.readLine()) != null) {
+ System.err.println(line);
+ }
+ BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+
+ while ((line = err.readLine()) != null) {
+ System.err.println(line);
+ }
+ if (process.exitValue() != 0) {
+ throw new IOException("error creating ssl key");
+ }
+ }
+ // Open keystore.
+ FileInputStream fileinputstream = new FileInputStream(keyStoreFile);
+ KeyStore keystore = KeyStore.getInstance("JKS");
+
+ keystore.load(fileinputstream, password);
+ String encodedPublicKey = Base64.encodeBytes(keystore.getCertificate(KEY_ID).getPublicKey().getEncoded(), Base64.DONT_BREAK_LINES);
+ System.out.println("public key=" + encodedPublicKey);
+ return keystore;
+ }
+
+ public SSLServerSocket createServerSocket(int serverPort) throws IOException {
+ SSLServerSocket serverSocket = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(serverPort);
+ return serverSocket;
+ }
+
+ public SSLSocket createClientSocket(String serverHost, int serverPort) throws IOException {
+ SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket(serverHost, serverPort);
+ socket.setNeedClientAuth(true);
+ return socket;
+ }
+
+ private SSLContext createSSLContext() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException, CertificateException, UnrecoverableKeyException, IOException, InterruptedException {
+ // init SSL stuff
+
+ KeyManagerFactory keymanagerfactory = KeyManagerFactory.getInstance("SunX509");
+ keymanagerfactory.init(keyStore, password);
+ KeyManager[] arkeymanager = keymanagerfactory.getKeyManagers();
+
+ // Create trust manager, we accept any certificates (it will be
+ // checked later)
+ TrustManager[] osTrustManager = new TrustManager[] { new AllTrustingManager() };
+
+ SSLContext sslcontext = SSLContext.getInstance("SSL");
+ sslcontext.init(arkeymanager, osTrustManager, null);
+
+ return sslcontext;
+ }
+
+ private static class AllTrustingManager implements X509TrustManager {
+
+ public void checkClientTrusted(X509Certificate[] certs, String authType) {
+
+ }
+
+ public void checkServerTrusted(X509Certificate[] certs, String authType) {
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return new java.security.cert.X509Certificate[0];
+ }
+ }
+}
View
297 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/PersistentStorage.java
@@ -0,0 +1,297 @@
+package edu.washington.cs.publickey.storage;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import edu.washington.cs.publickey.PublicKeyFriend;
+
+public abstract class PersistentStorage {
+
+ private final static boolean CACHE_ENABLED = true;
+ private final static int CACHE_SIZE = 100000;
+ private final static boolean CACHE_DEBUG = true;
+
+ private final Cache cache;
+
+ private static boolean ENABLE_LOGGING = false;
+
+ public PersistentStorage() {
+ if (CACHE_ENABLED) {
+ cache = new Cache(CACHE_SIZE);
+ } else {
+ cache = null;
+ }
+ }
+
+ public int addFriends(PublicKeyFriend user, PublicKeyFriend[] friends) throws Exception {
+ int friendsAdded = addFriendsImpl(user, friends);
+ if (friendsAdded > 0 && CACHE_ENABLED) {
+ long time = System.currentTimeMillis();
+ // expire the user from the cache
+ cache.remove(new PublicKeyHashKey(user.getPublicKeySha1()));
+
+ // expire all own keys
+ PublicKeyFriend[] ownKeys = getOwnPublicKeys(user);
+ cache.remove(ownKeys);
+
+ // get all friends and expire them as well
+ PublicKeyFriend[] dbFriends = getFriendPublicKeys(user);
+ cache.remove(dbFriends);
+ log("keeping cache up to date, overhead: " + (System.currentTimeMillis() - time) + "ms");
+ }
+ return friendsAdded;
+ }
+
+ private void log(String msg) {
+ if (ENABLE_LOGGING) {
+ System.out.println(msg);
+ }
+ }
+
+ protected abstract int addFriendsImpl(PublicKeyFriend user, PublicKeyFriend[] friends) throws Exception;
+
+ public void addPublicKey(PublicKeyFriend key) throws Exception {
+ boolean dbModified = addPublicKeyImpl(key);
+ if (dbModified && CACHE_ENABLED) {
+ long time = System.currentTimeMillis();
+ // expire the user from the cache
+ cache.remove(new PublicKeyHashKey(key.getPublicKeySha1()));
+
+ // get all friends and expire them as well
+ PublicKeyFriend[] dbFriends = getFriendPublicKeys(key);
+ cache.remove(dbFriends);
+
+ log("keeping cache up to date, overhead: " + (System.currentTimeMillis() - time) + "ms");
+ }
+ }
+
+ protected abstract boolean addPublicKeyImpl(PublicKeyFriend key) throws Exception;
+
+ public PublicKeyFriend[] getFriendPublicKeys(PublicKeyFriend friend) throws Exception {
+ return getFriendPublicKeysImpl(friend);
+ }
+
+ protected abstract PublicKeyFriend[] getFriendPublicKeysImpl(PublicKeyFriend friend) throws Exception;
+
+ public List<PublicKeyFriend> getFriendsUsingPublicKey(PublicKeyFriend f) throws Exception {
+ if (!CACHE_ENABLED) {
+ return getFriendsUsingPublicKeyImpl(f.getPublicKeySha1());
+ } else {
+ List<PublicKeyFriend> cachedEntries = cache.getFriendsUsingPublicKey(f);
+ if (CACHE_DEBUG) {
+ List<PublicKeyFriend> dbEntries = getFriendsUsingPublicKeyImpl(f.getPublicKeySha1());
+ boolean same = sameResult(dbEntries, cachedEntries);
+ if (!same) {
+ System.err.println("cache inconsistent, clearing");
+ cache.clear();
+ }
+ return dbEntries;
+ }
+ return cachedEntries;
+ }
+ }
+
+ private static boolean sameResult(List<PublicKeyFriend> dbEntries, List<PublicKeyFriend> cachedEntries) {
+ if (cachedEntries.size() != dbEntries.size()) {
+ System.err.println("Cache error!!!, cached=" + cachedEntries.size() + " db=" + dbEntries.size());
+ return false;
+ }
+ HashMap<PublicKeyHashKey, PublicKeyFriend> cacheMap = new HashMap<PublicKeyHashKey, PublicKeyFriend>();
+ HashMap<PublicKeyHashKey, PublicKeyFriend> dbMap = new HashMap<PublicKeyHashKey, PublicKeyFriend>();
+ for (PublicKeyFriend publicKeyFriend : cachedEntries) {
+ cacheMap.put(new PublicKeyHashKey(publicKeyFriend.getPublicKeySha1()), publicKeyFriend);
+ }
+ for (PublicKeyFriend dbFriend : dbEntries) {
+ PublicKeyHashKey dbKey = new PublicKeyHashKey(dbFriend.getPublicKeySha1());
+ dbMap.put(dbKey, dbFriend);
+
+ if (!cacheMap.containsKey(dbKey)) {
+ System.err.println("Cache error!!!, cache does not contain: " + dbFriend.getKeyNick());
+ return false;
+ }
+ }
+ for (PublicKeyFriend cacheFriend : cachedEntries) {
+ PublicKeyHashKey cacheKey = new PublicKeyHashKey(cacheFriend.getPublicKeySha1());
+ cacheMap.put(cacheKey, cacheFriend);
+ if (!dbMap.containsKey(cacheKey)) {
+ System.err.println("Cache error!!!, db does not contain: " + cacheFriend.getKeyNick());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected abstract List<PublicKeyFriend> getFriendsUsingPublicKeyImpl(final byte[] publickeysha1) throws Exception;
+
+ public PublicKeyFriend[] getOwnPublicKeys(PublicKeyFriend user) throws Exception {
+ return getOwnPublicKeysImpl(user);
+ }
+
+ protected abstract PublicKeyFriend[] getOwnPublicKeysImpl(PublicKeyFriend user) throws Exception;
+
+ public abstract void shutdown();
+
+ public abstract void updateUserLastSeen(PublicKeyFriend user) throws Exception;
+
+ public abstract int getDbQueueLength();
+
+ public abstract void deleteExpiredKeys();
+
+ class Cache {
+
+ private final CacheMap cache;
+
+ private long cacheHits;
+
+ private long totalLookups;
+
+ public Cache(final int maxEntries) {
+ cache = new CacheMap(maxEntries);
+
+ }
+
+ public void remove(PublicKeyFriend[] dbFriends) {
+ for (PublicKeyFriend f : dbFriends) {
+ remove(new PublicKeyHashKey(f.getPublicKeySha1()));
+ }
+ }
+
+ public void clear() {
+ System.err.println("clearing cache");
+ cache.clear();
+ }
+
+ public List<PublicKeyFriend> getFriendsUsingPublicKey(PublicKeyFriend f) throws Exception {
+ totalLookups++;
+ PublicKeyHashKey k = new PublicKeyHashKey(f.getPublicKeySha1());
+ List<PublicKeyFriend> v = cache.get(k);
+ if (v != null) {
+ cacheHits++;
+ System.out.println("Serving from $$, cache hit rate=" + Math.round(((100.0) * cacheHits) / totalLookups) + "%, size=" + cache.size());
+ return v;
+ } else {
+ List<PublicKeyFriend> dbValue = getFriendsUsingPublicKeyImpl(f.getPublicKeySha1());
+ cache.put(k, new PublicKeyCacheValue(dbValue));
+ System.out.println("Serving from db, cache hit rate=" + Math.round(((100.0) * cacheHits) / totalLookups) + "%, size=" + cache.size());
+ return dbValue;
+ }
+ }
+
+ public void remove(PublicKeyHashKey k) {
+ cache.remove(k);
+ }
+
+ }
+
+ private static class CacheMap {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+ private final Map<PublicKeyHashKey, PublicKeyCacheValue> cache = new LinkedHashMap<PublicKeyHashKey, PublicKeyCacheValue>() {
+ private static final long serialVersionUID = 1L;
+
+ protected boolean removeEldestEntry(Map.Entry<PublicKeyHashKey, PublicKeyCacheValue> eldest) {
+ boolean atCapacity = size() > maxEntries;
+ if (atCapacity) {
+ remove(eldest.getKey());
+ }
+ return atCapacity;
+ }
+ };
+ private final long maxEntries;
+
+ public CacheMap(int maxEntries) {
+ this.maxEntries = maxEntries;
+
+ }
+
+ public int size() {
+ return cache.size();
+ }
+
+ public void clear() {
+ cache.clear();
+ }
+
+ public synchronized List<PublicKeyFriend> get(PublicKeyHashKey key) {
+ PublicKeyCacheValue v = cache.get(key);
+ if (v != null) {
+ return v.cachedValue;
+ } else {
+ return null;
+ }
+
+ }
+
+ public synchronized void put(PublicKeyHashKey k, PublicKeyCacheValue v) {
+ cache.put(k, v);
+ }
+
+ public synchronized PublicKeyCacheValue remove(PublicKeyHashKey key) {
+ return cache.remove(key);
+ }
+
+ }
+
+ // private static class NetIDHashKey {
+ // final int hashcode;
+ // final byte[] netuidsha;
+ //
+ // public NetIDHashKey(byte[] netuidsha) {
+ // this.netuidsha = netuidsha;
+ // this.hashcode = Arrays.hashCode(netuidsha);
+ // }
+ //
+ // public boolean equals(Object o) {
+ // if (o instanceof NetIDHashKey) {
+ // NetIDHashKey c = (NetIDHashKey) o;
+ // if (Arrays.equals(c.netuidsha, netuidsha)) {
+ // return true;
+ // }
+ // }
+ // return false;
+ // }
+ //
+ // public int hashCode() {
+ // return hashcode;
+ // }
+ // }
+
+ private static class PublicKeyCacheValue {
+ List<PublicKeyFriend> cachedValue;
+ long lastSeen;
+
+ public PublicKeyCacheValue(List<PublicKeyFriend> v) {
+ this.cachedValue = v;
+ this.lastSeen = System.currentTimeMillis();
+ }
+ }
+
+ private static class PublicKeyHashKey {
+ final int hashcode;
+ final byte[] publickey;
+
+ public PublicKeyHashKey(byte[] publickeysha) {
+ this.publickey = publickeysha;
+ this.hashcode = Arrays.hashCode(publickey);
+ }
+
+ public boolean equals(Object o) {
+ if (o instanceof PublicKeyHashKey) {
+ PublicKeyHashKey c = (PublicKeyHashKey) o;
+ if (Arrays.equals(c.publickey, publickey)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return hashcode;
+ }
+ }
+}
View
70 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/sql/DatabaseJob.java
@@ -0,0 +1,70 @@
+package edu.washington.cs.publickey.storage.sql;
+
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.PriorityBlockingQueue;
+
+@SuppressWarnings("unchecked")
+abstract class DatabaseJob<T> implements Callable<T> ,Comparable<DatabaseJob>{
+
+ public final static int PRIO_INTERACTIVE = 10;
+ public final static int PRIO_LOW = 0;
+ private final int prio;
+ private final long startTime;
+
+ public DatabaseJob(int prio) {
+ this.prio = prio;
+ this.startTime = System.currentTimeMillis();
+ }
+
+ public int getPrio() {
+ return prio;
+ }
+
+ public long getTime() {
+ return this.startTime;
+ }
+
+ public int compareTo(DatabaseJob o) {
+ if (o.getPrio() > this.getPrio()) {
+ // o is better, return -1
+ return -1;
+ } else if (o.getPrio() < this.getPrio()) {
+ return 1;
+ } else {
+ if (o.getTime() < this.getTime()) {
+ return 1;
+ } else if (o.getTime() > this.getTime()) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ public String toString() {
+ return prio + " " + startTime;
+ }
+
+ public static void main(String[] args) throws InterruptedException {
+ Random r = new Random();
+ PriorityBlockingQueue<DatabaseJob<Object>> jobs = new PriorityBlockingQueue<DatabaseJob<Object>>();
+
+ for (int i = 0; i < 100; i++) {
+ Thread.sleep(2);
+ jobs.add(new DatabaseJob<Object>(r.nextInt(10)) {
+
+ public Object call() throws Exception {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ });
+ }
+
+ while (jobs.peek() != null) {
+ System.out.println(jobs.poll());
+ }
+
+ }
+}
View
444 xmpp_publickey/src/java/edu/washington/cs/publickey/storage/sql/PersistentStorageSQL.java
@@ -0,0 +1,444 @@
+package edu.washington.cs.publickey.storage.sql;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import edu.washington.cs.publickey.PublicKeyFriend;
+import edu.washington.cs.publickey.storage.PersistentStorage;
+
+public abstract class PersistentStorageSQL extends PersistentStorage {
+ private UserIdCache userIdCache = new UserIdCache(100000);
+ private static final boolean ENABLE_LOGGING = false;
+
+ private final boolean TRACE_QUERY_LATENCY = false;
+ public String framework = "embedded";
+ protected Connection conn;
+ protected QueryManager queryManager;
+ private HashMap<String, Long> startTime = new HashMap<String, Long>();
+ private final PriorityBlockingQueue<Runnable> dbManagerQueue = new PriorityBlockingQueue<Runnable>();
+
+ protected void startTrace(String function) {
+ if (TRACE_QUERY_LATENCY) {
+ // System.out.println("entered: " + function);
+ this.startTime.put(function, System.currentTimeMillis());
+ }
+ }
+
+ protected void endTrace(String function) {
+ if (TRACE_QUERY_LATENCY) {
+ Long time = startTime.get(function);
+ System.out.println("(" + (System.currentTimeMillis() - time) + "ms) completed: " + function);
+ }
+ }
+
+ private final ExecutorService dbManager = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, dbManagerQueue) {
+ public <T> Future<T> submit(Callable<T> task) {
+ if (task == null)
+ throw new NullPointerException();
+ FutureTask<T> ftask = new FutureAndComparable<T>(task);
+ execute(ftask);
+ return ftask;
+ }
+ };
+
+ public PersistentStorageSQL() {
+ super();
+ }
+
+ public synchronized void shutdown() {
+ startTrace("shutdown");
+ dbManager.shutdownNow();
+ try {
+ if (conn != null && !conn.isClosed()) {
+ /*
+ * We end the transaction and the connection.
+ */
+ conn.commit();
+ conn.close();
+ System.out.println("Committed transaction and closed connection");
+ }
+ } catch (SQLException e) {
+ System.out.println("got exception when closing conn:" + e.getMessage());
+ }
+
+ /*
+ * In embedded mode, an application should shut down Derby. If the
+ * application fails to shut down Derby explicitly, the Derby does not
+ * perform a checkpoint when the JVM shuts down, which means that the
+ * next connection will be slower. Explicitly shutting down Derby with
+ * the URL is preferred. This style of shutdown will always throw an
+ * "exception".
+ */
+ boolean gotSQLExc = false;
+
+ if (framework.equals("embedded")) {
+ try {
+ DriverManager.getConnection("jdbc:derby:;shutdown=true");
+ } catch (SQLException se) {
+ gotSQLExc = true;
+ }
+
+ if (!gotSQLExc) {
+ System.out.println("Database did not shut down normally");
+ } else {
+ System.out.println("Database shut down normally");
+ }
+ }
+ endTrace("shutdown");
+ }
+
+ protected void verifyDbDir(File dataBaseDir) throws Exception, IOException {
+ if (dataBaseDir.exists()) {
+ if (!dataBaseDir.isDirectory()) {
+ throw new Exception("specified db path is not a directory: '" + dataBaseDir.getCanonicalPath() + "'");
+ }
+ } else {
+ System.out.println("creating dir: " + dataBaseDir.getCanonicalPath());
+ dataBaseDir.mkdirs();
+ if (!dataBaseDir.isDirectory()) {
+ throw new Exception("unable to create db directory: '" + dataBaseDir.getCanonicalPath() + "'");
+ }
+ }
+ }
+
+ protected boolean addPublicKeyImpl(final PublicKeyFriend me) throws Exception {
+
+ Future<Boolean> f = dbManager.submit(new DatabaseJob<Boolean>(DatabaseJob.PRIO_INTERACTIVE) {
+ public Boolean call() throws Exception {
+ startTrace("addPublicKey");
+ boolean commitNeeded = false;
+ // get our user id (or create one if not exists)
+ Long userId = getUserIdAddIfNotExists(me);
+
+ String keyNick = queryManager.keyExists(userId, me.getPublicKeySha1());
+ log("existing keynick=" + keyNick);
+ log("sent keynick=" + me.getKeyNick());
+ if (keyNick == null) {
+ commitNeeded = true;
+ queryManager.insertOwnKey(userId, me.getKeyNick(), me.getPublicKey(), me.getPublicKeySha1());
+ } else if (!keyNick.equals(me.getKeyNick())) {
+ commitNeeded = true;
+ queryManager.updateOwnKey(userId, me.getKeyNick(), me.getPublicKeySha1());
+ } else {
+ // log("key already exists");
+ }
+
+ if (commitNeeded) {
+ // log("committing changes");
+ conn.commit();
+ }
+ endTrace("addPublicKey");
+ return commitNeeded;
+ }
+ });
+
+ return f.get();
+ }
+
+ protected PublicKeyFriend[] getFriendPublicKeysImpl(final PublicKeyFriend friend) throws Exception {
+
+ Future<PublicKeyFriend[]> f = dbManager.submit(new DatabaseJob<PublicKeyFriend[]>(DatabaseJob.PRIO_INTERACTIVE) {
+ public PublicKeyFriend[] call() throws Exception {
+ startTrace("getFriendsPublicKeys");
+ long user_id = getUserIdAddIfNotExists(friend);
+ List<PublicKeyFriend> mutualFriendsPublicKeys = queryManager.getMutualFriendsPublicKeys(user_id);
+
+ endTrace("getFriendsPublicKeys");
+ return mutualFriendsPublicKeys.toArray(new PublicKeyFriend[mutualFriendsPublicKeys.size()]);
+ }
+ });
+
+ return f.get();
+ }
+
+ protected PublicKeyFriend[] getOwnPublicKeysImpl(final PublicKeyFriend me) throws Exception {
+ startTrace("getOwnPublicKeys");
+ Future<PublicKeyFriend[]> f = dbManager.submit(new DatabaseJob<PublicKeyFriend[]>(DatabaseJob.PRIO_INTERACTIVE) {
+ public PublicKeyFriend[] call() throws Exception {
+ Long userId = userIdCache.getUserIdAddIfNotExists(me);
+ log("got userid=" + userId);
+ if (userId != null) {
+ PublicKeyFriend[] ownKeys = queryManager.ownKeys(me, userId);
+ endTrace("getOwnPublicKeys");
+ return ownKeys;
+ }
+ endTrace("getOwnPublicKeys");
+ return new PublicKeyFriend[0];
+ }
+ });
+ return f.get();
+ }
+
+ protected int addFriendsImpl(final PublicKeyFriend me, final PublicKeyFriend[] friends) throws Exception {
+
+ Future<Integer> f = dbManager.submit(new DatabaseJob<Integer>(DatabaseJob.PRIO_INTERACTIVE) {
+ public Integer call() throws Exception {
+ startTrace("addFriends");
+ boolean commitNeeded = false;
+ long user_id = getUserIdAddIfNotExists(me);
+
+ LinkedList<Long> allFriendIds = new LinkedList<Long>();
+
+ // ok, get the users that are in the db already
+ for (PublicKeyFriend friend : friends) {
+ // first, check if in db
+ allFriendIds.add(userIdCache.getUserIdAddIfNotExists(friend));
+ }
+
+ // get the users that we already have as friends
+ List<Long> existingFriendsList = queryManager.getFriendsOf(user_id);
+ HashSet<Long> existingFriends = new HashSet<Long>();
+ for (Long f : existingFriendsList) {
+ existingFriends.add(f);
+ }
+ log("existing friends: " + existingFriends.size());
+ // get the friends we need to add
+ List<Long> friendsToAdd = new ArrayList<Long>();
+ for (Long f_id : allFriendIds) {
+ if (!existingFriends.contains(f_id)) {
+ friendsToAdd.add(f_id);
+ }
+ }
+
+ log("adding friends: " + friendsToAdd.size());
+ // add them all to the friends table
+ if (friendsToAdd.size() > 0) {
+ commitNeeded = true;
+ queryManager.addFriends(user_id, friendsToAdd);
+ }
+
+ // and then, if we need to commit we should do that...
+ if (commitNeeded) {
+ conn.commit();
+ }
+ endTrace("addFriends");
+ return friendsToAdd.size();
+ }
+ });
+
+ return f.get();
+ }
+
+ private Long getUserIdAddIfNotExists(PublicKeyFriend publicKeyFriend) throws SQLException {
+ return userIdCache.getUserIdAddIfNotExists(publicKeyFriend);
+ }
+
+ private class UserIdCache {
+ private final int maxEntries;
+
+ public UserIdCache(int maxEntries) {
+ this.maxEntries = maxEntries;
+ }
+
+ long hits = 0;
+ long total = 0;
+ long added = 0;
+
+ private final Map<NetAndUIDWrapper, Long> cache = new LinkedHashMap<NetAndUIDWrapper, Long>() {
+ private static final long serialVersionUID = 1L;
+
+ protected boolean removeEldestEntry(Map.Entry<NetAndUIDWrapper, Long> eldest) {
+ boolean atCapacity = size() > maxEntries;
+ if (atCapacity) {
+ remove(eldest.getKey());
+ }
+ return atCapacity;
+ }
+ };
+
+ public Long getUserIdAddIfNotExists(PublicKeyFriend user) throws SQLException {
+ total++;
+ NetAndUIDWrapper wrapper = new NetAndUIDWrapper(user.getSourceNetworkUid(), user.getSourceNetwork().getNetworkId());
+ Long userId = cache.get(wrapper);
+ if (userId == null) {
+ userId = queryManager.getUserId(user.getSourceNetwork(), user.getSourceNetworkUid());
+ if (userId == null) {
+ added++;
+ ArrayList<PublicKeyFriend> l = new ArrayList<PublicKeyFriend>();
+ l.add(user);
+ ArrayList<Long> res = queryManager.addUsers(l);
+ if (res.size() != 1) {
+ throw new SQLException("strange, tried to add 1 user but added " + res.size());
+ }
+ userId = res.get(0);
+ // log("added new user, got userid=" + userId);
+ }
+ cache.put(wrapper, userId);
+ } else {
+ hits++;
+ }
+ if (total % 10000 == 0) {
+ System.out.println("total: " + total + " hitrate: " + ((100 * hits) / total) + "% added: " + added);
+ }
+ return userId;
+ }
+
+ private class NetAndUIDWrapper {
+ private final byte[] netUID;
+ private final int net;
+ private final int hashCode;
+
+ public NetAndUIDWrapper(byte[] netUID, int net) {
+ super();
+ this.netUID = netUID;
+ this.net = net;
+ this.hashCode = Arrays.hashCode(netUID);
+ }
+
+ public boolean equals(Object o) {
+ if (o instanceof NetAndUIDWrapper) {
+ NetAndUIDWrapper n = (NetAndUIDWrapper) o;
+ if (n.hashCode == hashCode) {
+ if (n.net == net) {
+ if (Arrays.equals(n.netUID, netUID)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return hashCode;
+ }
+
+ }
+ }
+
+ private void log(String msg) {
+ if (ENABLE_LOGGING) {
+ System.out.println(msg);
+ }
+ }
+
+ /**
+ * For unit testing, do not use
+ *
+ * @return The Querymanager in use
+ */
+ public QueryManager getQueryManager() {
+ return queryManager;
+ }
+
+ /**
+ * For unit testing, do not use
+ *
+ * @return the connection in use
+ */
+ public Connection getConnection() {
+ return conn;
+ }
+
+ protected List<PublicKeyFriend> getFriendsUsingPublicKeyImpl(final byte[] publickeysha1) throws Exception {
+ Future<List<PublicKeyFriend>> f = dbManager.submit(new DatabaseJob<List<PublicKeyFriend>>(DatabaseJob.PRIO_LOW) {
+ public List<PublicKeyFriend> call() throws Exception {
+ // step 1, get all user_id's associated with the public key
+ List<Long> userIds = queryManager.getUserIdsGivenPublicKey(publickeysha1);
+ // log("userIds found:" + userIds.size());
+
+ List<PublicKeyFriend> allFriends = new LinkedList<PublicKeyFriend>();
+ // step 2, get all public keys associated with the user_id's
+ // for (Long userId : userIds.keySet()) {
+ // PublicKeyFriend u = userIds.get(userId);
+ // PublicKeyFriend[] ownKeys = queryManager.ownKeys(u, userId);
+ // allFriends.addAll(Arrays.asList(ownKeys));
+ // }
+
+ // step 3, get all the friends
+ for (Long userId : userIds) {
+ List<PublicKeyFriend> mutualFriendsPublicKeys = queryManager.getMutualFriendsPublicKeys(userId);
+ allFriends.addAll(mutualFriendsPublicKeys);
+ }
+
+ return allFriends;
+ }
+ });
+
+ return f.get();
+ }
+
+ @Override
+ public void updateUserLastSeen(final PublicKeyFriend user) throws Exception {
+ Future<Void> f = dbManager.submit(new DatabaseJob<Void>(DatabaseJob.PRIO_INTERACTIVE) {
+ public Void call() throws Exception {
+ if (user.getSourceNetwork() != null && user.getSourceNetworkUid() != null && user.getPublicKeySha1() != null) {
+ queryManager.updateUserLastSeen(user.getSourceNetwork(), user.getSourceNetworkUid(), user.getPublicKeySha1());
+ } else if (user.getPublicKeySha1() != null) {
+ queryManager.updateUserLastSeen(user.getPublicKeySha1());
+ } else {
+ throw new Exception("Unable to update user, not enough info in publickeyfriend object");
+ }
+ return null;
+ }
+ });
+
+ f.get();
+ }
+
+ @Override
+ public int getDbQueueLength() {
+ return dbManagerQueue.size();
+ }
+
+ @Override
+ public void deleteExpiredKeys() {
+ try {
+ queryManager.deleteExpiredKeys();
+ } catch (SQLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ class FutureAndComparable<V> extends FutureTask<V> implements Comparable<FutureTask<V>> {
+
+ public FutureAndComparable(Callable<V> callable) {
+ super(callable);
+ if (callable instanceof DatabaseJob) {
+ this.job = (DatabaseJob<V>) callable;
+ } else {
+ job =