From 0edcb31d64a08f1d59f0becb1ac6689d3b99f129 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Sat, 23 Jul 2011 01:29:18 +0100
Subject: [PATCH] Store group details in the database. Some tests are still
 failing...

---
 .../sf/briar/api/db/DatabaseComponent.java    |   5 +-
 api/net/sf/briar/api/protocol/Group.java      |   5 +-
 .../sf/briar/api/protocol/GroupFactory.java   |   6 +
 .../api/protocol/SubscriptionWriter.java      |   3 +-
 .../sf/briar/api/protocol/Subscriptions.java  |   3 +-
 .../net/sf/briar/crypto/CryptoModule.java     |  23 ++++
 .../net/sf/briar/crypto/KeyParserImpl.java    |  26 ++++
 components/net/sf/briar/db/Database.java      |   9 +-
 components/net/sf/briar/db/H2Database.java    |   6 +-
 components/net/sf/briar/db/JdbcDatabase.java  |  82 ++++++++----
 .../db/ReadWriteLockDatabaseComponent.java    |  12 +-
 .../db/SynchronizedDatabaseComponent.java     |  12 +-
 .../sf/briar/protocol/GroupFactoryImpl.java   |  38 ++++++
 .../net/sf/briar/protocol/GroupImpl.java      |  42 +++++++
 .../net/sf/briar/protocol/ProtocolModule.java |   4 +
 .../sf/briar/db/DatabaseComponentTest.java    |   8 +-
 test/net/sf/briar/db/H2DatabaseTest.java      | 119 +++++++++---------
 .../sf/briar/protocol/BatchReaderTest.java    |   5 +-
 test/net/sf/briar/protocol/ConsumersTest.java |  13 +-
 .../sf/briar/protocol/FileReadWriteTest.java  |  35 ++----
 .../SigningDigestingOutputStreamTest.java     |  13 +-
 21 files changed, 322 insertions(+), 147 deletions(-)
 create mode 100644 api/net/sf/briar/api/protocol/GroupFactory.java
 create mode 100644 components/net/sf/briar/crypto/CryptoModule.java
 create mode 100644 components/net/sf/briar/crypto/KeyParserImpl.java
 create mode 100644 components/net/sf/briar/protocol/GroupFactoryImpl.java
 create mode 100644 components/net/sf/briar/protocol/GroupImpl.java

diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java
index b4a81b2990..b3e08351b7 100644
--- a/api/net/sf/briar/api/db/DatabaseComponent.java
+++ b/api/net/sf/briar/api/db/DatabaseComponent.java
@@ -11,6 +11,7 @@ import net.sf.briar.api.protocol.AckWriter;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
 import net.sf.briar.api.protocol.BatchWriter;
+import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.SubscriptionWriter;
@@ -81,7 +82,7 @@ public interface DatabaseComponent {
 	Rating getRating(AuthorId a) throws DbException;
 
 	/** Returns the set of groups to which the user subscribes. */
-	Collection<GroupId> getSubscriptions() throws DbException;
+	Collection<Group> getSubscriptions() throws DbException;
 
 	/** Returns the local transport details. */
 	Map<String, String> getTransports() throws DbException;
@@ -108,7 +109,7 @@ public interface DatabaseComponent {
 	void setRating(AuthorId a, Rating r) throws DbException;
 
 	/** Subscribes to the given group. */
-	void subscribe(GroupId g) throws DbException;
+	void subscribe(Group g) throws DbException;
 
 	/**
 	 * Unsubscribes from the given group. Any messages belonging to the group
diff --git a/api/net/sf/briar/api/protocol/Group.java b/api/net/sf/briar/api/protocol/Group.java
index c62d203d02..43de430292 100644
--- a/api/net/sf/briar/api/protocol/Group.java
+++ b/api/net/sf/briar/api/protocol/Group.java
@@ -5,7 +5,10 @@ import java.security.PublicKey;
 /** A group to which users may subscribe. */
 public interface Group {
 
-	/** Returns the name of the group. */
+	/** Returns the group's unique identifier. */
+	GroupId getId();
+
+	/** Returns the group's name. */
 	String getName();
 
 	/**
diff --git a/api/net/sf/briar/api/protocol/GroupFactory.java b/api/net/sf/briar/api/protocol/GroupFactory.java
new file mode 100644
index 0000000000..044fca5975
--- /dev/null
+++ b/api/net/sf/briar/api/protocol/GroupFactory.java
@@ -0,0 +1,6 @@
+package net.sf.briar.api.protocol;
+
+public interface GroupFactory {
+
+	Group createGroup(GroupId id, String name, byte[] salt, byte[] publicKey);
+}
diff --git a/api/net/sf/briar/api/protocol/SubscriptionWriter.java b/api/net/sf/briar/api/protocol/SubscriptionWriter.java
index 7d47400015..0cddd424bf 100644
--- a/api/net/sf/briar/api/protocol/SubscriptionWriter.java
+++ b/api/net/sf/briar/api/protocol/SubscriptionWriter.java
@@ -5,7 +5,6 @@ import java.io.IOException;
 /** An interface for creating a subscription update. */
 public interface SubscriptionWriter {
 
-	// FIXME: This should work with groups, not IDs
 	/** Sets the contents of the update. */
-	void setSubscriptions(Iterable<GroupId> subs) throws IOException;
+	void setSubscriptions(Iterable<Group> subs) throws IOException;
 }
diff --git a/api/net/sf/briar/api/protocol/Subscriptions.java b/api/net/sf/briar/api/protocol/Subscriptions.java
index f25c7c44cf..ce9fc892c0 100644
--- a/api/net/sf/briar/api/protocol/Subscriptions.java
+++ b/api/net/sf/briar/api/protocol/Subscriptions.java
@@ -5,9 +5,8 @@ import java.util.Collection;
 /** A packet updating the sender's subscriptions. */
 public interface Subscriptions {
 
-	// FIXME: This should work with groups, not IDs
 	/** Returns the subscriptions contained in the update. */
-	Collection<GroupId> getSubscriptions();
+	Collection<Group> getSubscriptions();
 
 	/**
 	 * Returns the update's timestamp. Updates that are older than the newest
diff --git a/components/net/sf/briar/crypto/CryptoModule.java b/components/net/sf/briar/crypto/CryptoModule.java
new file mode 100644
index 0000000000..93ee654ddd
--- /dev/null
+++ b/components/net/sf/briar/crypto/CryptoModule.java
@@ -0,0 +1,23 @@
+package net.sf.briar.crypto;
+
+import java.security.NoSuchAlgorithmException;
+
+import net.sf.briar.api.crypto.KeyParser;
+
+import com.google.inject.AbstractModule;
+
+public class CryptoModule extends AbstractModule {
+
+	public static final String DIGEST_ALGO = "SHA-256";
+	public static final String KEY_PAIR_ALGO = "RSA";
+	public static final String SIGNATURE_ALGO = "SHA256withRSA";
+
+	@Override
+	protected void configure() {
+		try {
+			bind(KeyParser.class).toInstance(new KeyParserImpl(KEY_PAIR_ALGO));
+		} catch(NoSuchAlgorithmException e) {
+			throw new RuntimeException(e);
+		}
+	}
+}
diff --git a/components/net/sf/briar/crypto/KeyParserImpl.java b/components/net/sf/briar/crypto/KeyParserImpl.java
new file mode 100644
index 0000000000..d5b52e0b0a
--- /dev/null
+++ b/components/net/sf/briar/crypto/KeyParserImpl.java
@@ -0,0 +1,26 @@
+package net.sf.briar.crypto;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+import net.sf.briar.api.crypto.KeyParser;
+
+public class KeyParserImpl implements KeyParser {
+
+	private final KeyFactory keyFactory;
+
+	KeyParserImpl(String algorithm) throws NoSuchAlgorithmException {
+		keyFactory = KeyFactory.getInstance(algorithm);
+	}
+
+	public PublicKey parsePublicKey(byte[] encodedKey)
+	throws InvalidKeySpecException {
+		EncodedKeySpec e = new X509EncodedKeySpec(encodedKey);
+		return keyFactory.generatePublic(e);
+	}
+
+}
diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java
index ddc1b5f438..c408fe1580 100644
--- a/components/net/sf/briar/db/Database.java
+++ b/components/net/sf/briar/db/Database.java
@@ -9,6 +9,7 @@ import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.BatchId;
+import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
@@ -105,7 +106,7 @@ interface Database<T> {
 	 * <p>
 	 * Locking: subscriptions write.
 	 */
-	void addSubscription(T txn, GroupId g) throws DbException;
+	void addSubscription(T txn, Group g) throws DbException;
 
 	/**
 	 * Returns true iff the database contains the given contact.
@@ -235,14 +236,14 @@ interface Database<T> {
 	 * <p>
 	 * Locking: subscriptions read.
 	 */
-	Collection<GroupId> getSubscriptions(T txn) throws DbException;
+	Collection<Group> getSubscriptions(T txn) throws DbException;
 
 	/**
 	 * Returns the groups to which the given contact subscribes.
 	 * <p>
 	 * Locking: contacts read, subscriptions read.
 	 */
-	Collection<GroupId> getSubscriptions(T txn, ContactId c) throws DbException;
+	Collection<Group> getSubscriptions(T txn, ContactId c) throws DbException;
 
 	/**
 	 * Returns the local transport details.
@@ -335,7 +336,7 @@ interface Database<T> {
 	 * <p>
 	 * Locking: contacts write, subscriptions write.
 	 */
-	void setSubscriptions(T txn, ContactId c, Collection<GroupId> subs,
+	void setSubscriptions(T txn, ContactId c, Collection<Group> subs,
 			long timestamp) throws DbException;
 
 	/**
diff --git a/components/net/sf/briar/db/H2Database.java b/components/net/sf/briar/db/H2Database.java
index 2ddb2f7899..d2902e7e6b 100644
--- a/components/net/sf/briar/db/H2Database.java
+++ b/components/net/sf/briar/db/H2Database.java
@@ -13,6 +13,7 @@ import java.util.logging.Logger;
 import net.sf.briar.api.crypto.Password;
 import net.sf.briar.api.db.DatabasePassword;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.protocol.GroupFactory;
 
 import org.apache.commons.io.FileSystemUtils;
 
@@ -30,8 +31,9 @@ class H2Database extends JdbcDatabase {
 	private final long maxSize;
 
 	@Inject
-	H2Database(File dir, @DatabasePassword Password password, long maxSize) {
-		super("BINARY(32)", "BIGINT");
+	H2Database(File dir, @DatabasePassword Password password, long maxSize,
+			GroupFactory groupFactory) {
+		super(groupFactory, "BINARY(32)", "BIGINT", "BINARY");
 		home = new File(dir, "db");
 		this.password = password;
 		url = "jdbc:h2:split:" + home.getPath()
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index 23a2693b2b..c48bad47b9 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -2,6 +2,7 @@ package net.sf.briar.db;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.security.PublicKey;
 import java.sql.Blob;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
@@ -23,6 +24,8 @@ import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.BatchId;
+import net.sf.briar.api.protocol.Group;
+import net.sf.briar.api.protocol.GroupFactory;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
@@ -37,6 +40,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String CREATE_LOCAL_SUBSCRIPTIONS =
 		"CREATE TABLE localSubscriptions"
 		+ " (groupId HASH NOT NULL,"
+		+ " name VARCHAR NOT NULL,"
+		+ " salt BINARY,"
+		+ " publicKey BINARY,"
 		+ " PRIMARY KEY (groupId))";
 
 	private static final String CREATE_MESSAGES =
@@ -84,6 +90,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 		"CREATE TABLE contactSubscriptions"
 		+ " (contactId INT NOT NULL,"
 		+ " groupId HASH NOT NULL,"
+		+ " name VARCHAR NOT NULL,"
+		+ " salt BINARY,"
+		+ " publicKey BINARY,"
 		+ " PRIMARY KEY (contactId, groupId),"
 		+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
 		+ " ON DELETE CASCADE)";
@@ -157,7 +166,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		Logger.getLogger(JdbcDatabase.class.getName());
 
 	// Different database libraries use different names for certain types
-	private final String hashType, timestampType;
+	private final String hashType, timestampType, binaryType;
+	private final GroupFactory groupFactory;
 	private final LinkedList<Connection> connections =
 		new LinkedList<Connection>(); // Locking: self
 
@@ -166,9 +176,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	protected abstract Connection createConnection() throws SQLException;
 
-	JdbcDatabase(String hashType, String timestampType) {
+	JdbcDatabase(GroupFactory groupFactory, String hashType,
+			String timestampType, String binaryType) {
+		this.groupFactory = groupFactory;
 		this.hashType = hashType;
 		this.timestampType = timestampType;
+		this.binaryType = binaryType;
 	}
 
 	protected void open(boolean resume, File dir, String driverClass)
@@ -255,7 +268,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	private String insertTypeNames(String s) {
 		s = s.replaceAll("HASH", hashType);
-		return s.replaceAll("TIMESTAMP", timestampType);
+		s = s.replaceAll("TIMESTAMP", timestampType);
+		s = s.replaceAll("BINARY", binaryType);
+		return s;
 	}
 
 	private void tryToClose(Connection c) {
@@ -512,12 +527,18 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void addSubscription(Connection txn, GroupId g) throws DbException {
+	public void addSubscription(Connection txn, Group g) throws DbException {
 		PreparedStatement ps = null;
 		try {
-			String sql = "INSERT INTO localSubscriptions (groupId) VALUES (?)";
+			String sql = "INSERT INTO localSubscriptions"
+				+ " (groupId, name, salt, publicKey)"
+				+ " VALUES (?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, g.getBytes());
+			ps.setBytes(1, g.getId().getBytes());
+			ps.setString(2, g.getName());
+			ps.setBytes(3, g.getSalt());
+			PublicKey k = g.getPublicKey();
+			ps.setBytes(4, k == null ? null : k.getEncoded());
 			int rowsAffected = ps.executeUpdate();
 			assert rowsAffected == 1;
 			ps.close();
@@ -964,19 +985,26 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Collection<GroupId> getSubscriptions(Connection txn)
+	public Collection<Group> getSubscriptions(Connection txn)
 	throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT groupId FROM localSubscriptions";
+			String sql = "SELECT (groupId, name, salt, publicKey)"
+				+ " FROM localSubscriptions";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
-			Collection<GroupId> ids = new ArrayList<GroupId>();
-			while(rs.next()) ids.add(new GroupId(rs.getBytes(1)));
+			Collection<Group> subs = new ArrayList<Group>();
+			while(rs.next()) {
+				GroupId id = new GroupId(rs.getBytes(1));
+				String name = rs.getString(2);
+				byte[] salt = rs.getBytes(3);
+				byte[] publicKey = rs.getBytes(4);
+				subs.add(groupFactory.createGroup(id, name, salt, publicKey));
+			}
 			rs.close();
 			ps.close();
-			return ids;
+			return subs;
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -985,21 +1013,28 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Collection<GroupId> getSubscriptions(Connection txn, ContactId c)
+	public Collection<Group> getSubscriptions(Connection txn, ContactId c)
 	throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT groupId FROM contactSubscriptions"
+			String sql = "SELECT (groupId, name, salt, publicKey)"
+				+ " FROM contactSubscriptions"
 				+ " WHERE contactId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
 			rs = ps.executeQuery();
-			Collection<GroupId> ids = new ArrayList<GroupId>();
-			while(rs.next()) ids.add(new GroupId(rs.getBytes(1)));
+			Collection<Group> subs = new ArrayList<Group>();
+			while(rs.next()) {
+				GroupId id = new GroupId(rs.getBytes(1));
+				String name = rs.getString(2);
+				byte[] salt = rs.getBytes(3);
+				byte[] publicKey = rs.getBytes(4);
+				subs.add(groupFactory.createGroup(id, name, salt, publicKey));
+			}
 			rs.close();
 			ps.close();
-			return ids;
+			return subs;
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -1329,7 +1364,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public void setSubscriptions(Connection txn, ContactId c,
-			Collection<GroupId> subs, long timestamp) throws DbException {
+			Collection<Group> subs, long timestamp) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
@@ -1354,12 +1389,17 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps.executeUpdate();
 			ps.close();
 			// Store the new subscriptions
-			sql = "INSERT INTO contactSubscriptions (contactId, groupId)"
-				+ " VALUES (?, ?)";
+			sql = "INSERT INTO contactSubscriptions"
+				+ "(contactId, groupId, name, salt, publicKey)"
+				+ " VALUES (?, ?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			for(GroupId g : subs) {
-				ps.setBytes(2, g.getBytes());
+			for(Group g : subs) {
+				ps.setBytes(2, g.getId().getBytes());
+				ps.setString(3, g.getName());
+				ps.setBytes(4, g.getSalt());
+				PublicKey k = g.getPublicKey();
+				ps.setBytes(5, k == null ? null : k.getEncoded());
 				ps.addBatch();
 			}
 			int[] rowsAffectedArray = ps.executeBatch();
diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
index c0bc073925..0d8ae61524 100644
--- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
+++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
@@ -21,6 +21,7 @@ import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
 import net.sf.briar.api.protocol.BatchId;
 import net.sf.briar.api.protocol.BatchWriter;
+import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
@@ -347,8 +348,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 			try {
 				Txn txn = db.startTransaction();
 				try {
-					// FIXME: This should deal in Groups, not GroupIds
-					Collection<GroupId> subs = db.getSubscriptions(txn);
+					Collection<Group> subs = db.getSubscriptions(txn);
 					s.setSubscriptions(subs);
 					if(LOG.isLoggable(Level.FINE))
 						LOG.fine("Added " + subs.size() + " subscriptions");
@@ -431,12 +431,12 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
-	public Collection<GroupId> getSubscriptions() throws DbException {
+	public Collection<Group> getSubscriptions() throws DbException {
 		subscriptionLock.readLock().lock();
 		try {
 			Txn txn = db.startTransaction();
 			try {
-				Collection<GroupId> subs = db.getSubscriptions(txn);
+				Collection<Group> subs = db.getSubscriptions(txn);
 				db.commitTransaction(txn);
 				return subs;
 			} catch(DbException e) {
@@ -575,7 +575,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 			try {
 				Txn txn = db.startTransaction();
 				try {
-					Collection<GroupId> subs = s.getSubscriptions();
+					Collection<Group> subs = s.getSubscriptions();
 					db.setSubscriptions(txn, c, subs, s.getTimestamp());
 					if(LOG.isLoggable(Level.FINE))
 						LOG.fine("Received " + subs.size() + " subscriptions");
@@ -678,7 +678,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
-	public void subscribe(GroupId g) throws DbException {
+	public void subscribe(Group g) throws DbException {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g);
 		subscriptionLock.writeLock().lock();
 		try {
diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
index 60f99f6568..1de25c0bf4 100644
--- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
+++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
@@ -20,6 +20,7 @@ import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
 import net.sf.briar.api.protocol.BatchId;
 import net.sf.briar.api.protocol.BatchWriter;
+import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
@@ -252,8 +253,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 			synchronized(subscriptionLock) {
 				Txn txn = db.startTransaction();
 				try {
-					// FIXME: This should deal in Groups, not GroupIds
-					Collection<GroupId> subs = db.getSubscriptions(txn);
+					Collection<Group> subs = db.getSubscriptions(txn);
 					s.setSubscriptions(subs);
 					if(LOG.isLoggable(Level.FINE))
 						LOG.fine("Added " + subs.size() + " subscriptions");
@@ -320,11 +320,11 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
-	public Collection<GroupId> getSubscriptions() throws DbException {
+	public Collection<Group> getSubscriptions() throws DbException {
 		synchronized(subscriptionLock) {
 			Txn txn = db.startTransaction();
 			try {
-				Collection<GroupId> subs = db.getSubscriptions(txn);
+				Collection<Group> subs = db.getSubscriptions(txn);
 				db.commitTransaction(txn);
 				return subs;
 			} catch(DbException e) {
@@ -429,7 +429,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 			synchronized(subscriptionLock) {
 				Txn txn = db.startTransaction();
 				try {
-					Collection<GroupId> subs = s.getSubscriptions();
+					Collection<Group> subs = s.getSubscriptions();
 					db.setSubscriptions(txn, c, subs, s.getTimestamp());
 					if(LOG.isLoggable(Level.FINE))
 						LOG.fine("Received " + subs.size() + " subscriptions");
@@ -504,7 +504,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
-	public void subscribe(GroupId g) throws DbException {
+	public void subscribe(Group g) throws DbException {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g);
 		synchronized(subscriptionLock) {
 			Txn txn = db.startTransaction();
diff --git a/components/net/sf/briar/protocol/GroupFactoryImpl.java b/components/net/sf/briar/protocol/GroupFactoryImpl.java
new file mode 100644
index 0000000000..e98a0398b9
--- /dev/null
+++ b/components/net/sf/briar/protocol/GroupFactoryImpl.java
@@ -0,0 +1,38 @@
+package net.sf.briar.protocol;
+
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+
+import net.sf.briar.api.crypto.KeyParser;
+import net.sf.briar.api.protocol.Group;
+import net.sf.briar.api.protocol.GroupFactory;
+import net.sf.briar.api.protocol.GroupId;
+
+import com.google.inject.Inject;
+
+class GroupFactoryImpl implements GroupFactory {
+
+	private final KeyParser keyParser;
+
+	@Inject
+	GroupFactoryImpl(KeyParser keyParser) {
+		this.keyParser = keyParser;
+	}
+
+	public Group createGroup(GroupId id, String name, byte[] salt,
+			byte[] publicKey) {
+		if(salt == null && publicKey == null)
+			throw new IllegalArgumentException();
+		if(salt != null && publicKey != null)
+			throw new IllegalArgumentException();
+		PublicKey key = null;
+		if(publicKey != null) {
+			try {
+				key = keyParser.parsePublicKey(publicKey);
+			} catch (InvalidKeySpecException e) {
+				throw new IllegalArgumentException(e);
+			}
+		}
+		return new GroupImpl(id, name, salt, key);
+	}
+}
diff --git a/components/net/sf/briar/protocol/GroupImpl.java b/components/net/sf/briar/protocol/GroupImpl.java
new file mode 100644
index 0000000000..2d62fa3620
--- /dev/null
+++ b/components/net/sf/briar/protocol/GroupImpl.java
@@ -0,0 +1,42 @@
+package net.sf.briar.protocol;
+
+import java.security.PublicKey;
+
+import net.sf.briar.api.protocol.Group;
+import net.sf.briar.api.protocol.GroupId;
+
+public class GroupImpl implements Group {
+
+	private final GroupId id;
+	private final String name;
+	private final byte[] salt;
+	private final PublicKey publicKey;
+
+	GroupImpl(GroupId id, String name, byte[] salt, PublicKey publicKey) {
+		assert salt == null || publicKey == null;
+		this.id = id;
+		this.name = name;
+		this.salt = salt;
+		this.publicKey = publicKey;
+	}
+
+	public GroupId getId() {
+		return id;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public boolean isRestricted() {
+		return salt == null;
+	}
+
+	public byte[] getSalt() {
+		return salt;
+	}
+
+	public PublicKey getPublicKey() {
+		return publicKey;
+	}
+}
diff --git a/components/net/sf/briar/protocol/ProtocolModule.java b/components/net/sf/briar/protocol/ProtocolModule.java
index 9652d0bf5a..402d6af9ac 100644
--- a/components/net/sf/briar/protocol/ProtocolModule.java
+++ b/components/net/sf/briar/protocol/ProtocolModule.java
@@ -1,11 +1,15 @@
 package net.sf.briar.protocol;
 
+import net.sf.briar.api.protocol.GroupFactory;
+
 import com.google.inject.AbstractModule;
 
 public class ProtocolModule extends AbstractModule {
 
 	@Override
 	protected void configure() {
+		bind(AckFactory.class).to(AckFactoryImpl.class);
 		bind(BatchFactory.class).to(BatchFactoryImpl.class);
+		bind(GroupFactory.class).to(GroupFactoryImpl.class);
 	}
 }
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index e2aaba9208..385baf084a 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -17,6 +17,7 @@ import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.AckWriter;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.BatchId;
+import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
@@ -72,6 +73,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final Group group = context.mock(Group.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
 			will(returnValue(txn));
@@ -93,8 +95,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(true));
 			oneOf(database).getTransports(txn, contactId);
 			will(returnValue(transports));
-			// subscribe(groupId)
-			oneOf(database).addSubscription(txn, groupId);
+			// subscribe(group)
+			oneOf(database).addSubscription(txn, group);
 			// getSubscriptions()
 			oneOf(database).getSubscriptions(txn);
 			will(returnValue(subs));
@@ -113,7 +115,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		assertEquals(contactId, db.addContact(transports));
 		assertEquals(contacts, db.getContacts());
 		assertEquals(transports, db.getTransports(contactId));
-		db.subscribe(groupId);
+		db.subscribe(group);
 		assertEquals(subs, db.getSubscriptions());
 		db.unsubscribe(groupId);
 		db.removeContact(contactId);
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index 782c92b21f..72a6fe8f0a 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -21,17 +21,22 @@ import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.BatchId;
+import net.sf.briar.api.protocol.Group;
+import net.sf.briar.api.protocol.GroupFactory;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.crypto.CryptoModule;
+import net.sf.briar.protocol.ProtocolModule;
 
 import org.apache.commons.io.FileSystemUtils;
-import org.jmock.Expectations;
-import org.jmock.Mockery;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
 public class H2DatabaseTest extends TestCase {
 
 	private static final int ONE_MEGABYTE = 1024 * 1024;
@@ -40,7 +45,10 @@ public class H2DatabaseTest extends TestCase {
 	private final File testDir = TestUtils.getTestDirectory();
 	// The password has the format <file password> <space> <user password>
 	private final String passwordString = "foo bar";
+	private final Password password = new TestPassword();
 	private final Random random = new Random();
+	private final GroupFactory groupFactory;
+
 	private final AuthorId authorId;
 	private final BatchId batchId;
 	private final ContactId contactId;
@@ -50,9 +58,13 @@ public class H2DatabaseTest extends TestCase {
 	private final int size;
 	private final byte[] raw;
 	private final Message message;
+	private final Group group;
 
-	public H2DatabaseTest() {
+	public H2DatabaseTest() throws Exception {
 		super();
+		Injector i = Guice.createInjector(new ProtocolModule(),
+				new CryptoModule());
+		groupFactory = i.getInstance(GroupFactory.class);
 		authorId = new AuthorId(TestUtils.getRandomId());
 		batchId = new BatchId(TestUtils.getRandomId());
 		contactId = new ContactId(1);
@@ -64,6 +76,8 @@ public class H2DatabaseTest extends TestCase {
 		random.nextBytes(raw);
 		message = new TestMessage(messageId, MessageId.NONE, groupId, authorId,
 				timestamp, raw);
+		group = groupFactory.createGroup(groupId, "Group name",
+				TestUtils.getRandomId(), null);
 	}
 
 	@Before
@@ -81,7 +95,7 @@ public class H2DatabaseTest extends TestCase {
 		assertEquals(contactId, db.addContact(txn, transports));
 		assertTrue(db.containsContact(txn, contactId));
 		assertFalse(db.containsSubscription(txn, groupId));
-		db.addSubscription(txn, groupId);
+		db.addSubscription(txn, group);
 		assertTrue(db.containsSubscription(txn, groupId));
 		assertFalse(db.containsMessage(txn, messageId));
 		db.addMessage(txn, message);
@@ -158,7 +172,7 @@ public class H2DatabaseTest extends TestCase {
 		db.setRating(txn, authorId, Rating.GOOD);
 		// Check that the rating was stored
 		assertEquals(Rating.GOOD, db.getRating(txn, authorId));
-		
+
 		db.commitTransaction(txn);
 		db.close();
 	}
@@ -169,7 +183,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Subscribe to a group and store a message
-		db.addSubscription(txn, groupId);
+		db.addSubscription(txn, group);
 		db.addMessage(txn, message);
 
 		// Unsubscribing from the group should delete the message
@@ -188,8 +202,8 @@ public class H2DatabaseTest extends TestCase {
 
 		// Add a contact, subscribe to a group and store a message
 		assertEquals(contactId, db.addContact(txn, null));
-		db.addSubscription(txn, groupId);
-		db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
+		db.addSubscription(txn, group);
+		db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
 		db.addMessage(txn, message);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
 
@@ -221,8 +235,8 @@ public class H2DatabaseTest extends TestCase {
 
 		// Add a contact, subscribe to a group and store a message
 		assertEquals(contactId, db.addContact(txn, null));
-		db.addSubscription(txn, groupId);
-		db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
+		db.addSubscription(txn, group);
+		db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
 		db.addMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
 
@@ -258,7 +272,7 @@ public class H2DatabaseTest extends TestCase {
 
 		// Add a contact, subscribe to a group and store a message
 		assertEquals(contactId, db.addContact(txn, null));
-		db.addSubscription(txn, groupId);
+		db.addSubscription(txn, group);
 		db.addMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -269,13 +283,13 @@ public class H2DatabaseTest extends TestCase {
 		assertFalse(it.hasNext());
 
 		// The contact subscribing should make the message sendable
-		db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
+		db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
 		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertTrue(it.hasNext());
 		assertEquals(messageId, it.next());
 
 		// The contact unsubscribing should make the message unsendable
-		db.setSubscriptions(txn, contactId, Collections.<GroupId>emptySet(), 2);
+		db.setSubscriptions(txn, contactId, Collections.<Group>emptySet(), 2);
 		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertFalse(it.hasNext());
 
@@ -290,8 +304,8 @@ public class H2DatabaseTest extends TestCase {
 
 		// Add a contact, subscribe to a group and store a message
 		assertEquals(contactId, db.addContact(txn, null));
-		db.addSubscription(txn, groupId);
-		db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
+		db.addSubscription(txn, group);
+		db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
 		db.addMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -346,8 +360,8 @@ public class H2DatabaseTest extends TestCase {
 
 		// Add a contact, subscribe to a group and store a message
 		assertEquals(contactId, db.addContact(txn, null));
-		db.addSubscription(txn, groupId);
-		db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
+		db.addSubscription(txn, group);
+		db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
 		db.addMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -382,8 +396,8 @@ public class H2DatabaseTest extends TestCase {
 
 		// Add a contact, subscribe to a group and store a message
 		assertEquals(contactId, db.addContact(txn, null));
-		db.addSubscription(txn, groupId);
-		db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
+		db.addSubscription(txn, group);
+		db.setSubscriptions(txn, contactId, Collections.singleton(group), 1);
 		db.addMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -493,7 +507,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Subscribe to a group and store two messages
-		db.addSubscription(txn, groupId);
+		db.addSubscription(txn, group);
 		db.addMessage(txn, message);
 		db.addMessage(txn, message1);
 
@@ -518,6 +532,8 @@ public class H2DatabaseTest extends TestCase {
 		MessageId childId2 = new MessageId(TestUtils.getRandomId());
 		MessageId childId3 = new MessageId(TestUtils.getRandomId());
 		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
+		Group group1 = groupFactory.createGroup(groupId1, "Another group name",
+				TestUtils.getRandomId(), null);
 		Message child1 = new TestMessage(childId1, messageId, groupId,
 				authorId, timestamp, raw);
 		Message child2 = new TestMessage(childId2, messageId, groupId,
@@ -529,8 +545,8 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Subscribe to the groups and store the messages
-		db.addSubscription(txn, groupId);
-		db.addSubscription(txn, groupId1);
+		db.addSubscription(txn, group);
+		db.addSubscription(txn, group1);
 		db.addMessage(txn, message);
 		db.addMessage(txn, child1);
 		db.addMessage(txn, child2);
@@ -560,7 +576,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Subscribe to a group and store two messages
-		db.addSubscription(txn, groupId);
+		db.addSubscription(txn, group);
 		db.addMessage(txn, message);
 		db.addMessage(txn, message1);
 
@@ -598,7 +614,7 @@ public class H2DatabaseTest extends TestCase {
 		assertTrue(free > 0);
 		// Storing a message should reduce the free space
 		Connection txn = db.startTransaction();
-		db.addSubscription(txn, groupId);
+		db.addSubscription(txn, group);
 		db.addMessage(txn, message1);
 		db.commitTransaction(txn);
 		assertTrue(db.getFreeSpace() < free);
@@ -740,6 +756,9 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testUpdateSubscriptions() throws DbException {
+		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
+		Group group1 = groupFactory.createGroup(groupId1, "Another group name",
+				TestUtils.getRandomId(), null);
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
@@ -747,19 +766,13 @@ public class H2DatabaseTest extends TestCase {
 		Map<String, String> transports = Collections.emptyMap();
 		assertEquals(contactId, db.addContact(txn, transports));
 		// Add some subscriptions
-		Collection<GroupId> subs = new HashSet<GroupId>();
-		subs.add(new GroupId(TestUtils.getRandomId()));
-		subs.add(new GroupId(TestUtils.getRandomId()));
+		Collection<Group> subs = Collections.singletonList(group);
 		db.setSubscriptions(txn, contactId, subs, 1);
-		assertEquals(subs,
-				new HashSet<GroupId>(db.getSubscriptions(txn, contactId)));
+		assertEquals(subs, db.getSubscriptions(txn, contactId));
 		// Update the subscriptions
-		Collection<GroupId> subs1 = new HashSet<GroupId>();
-		subs1.add(new GroupId(TestUtils.getRandomId()));
-		subs1.add(new GroupId(TestUtils.getRandomId()));
+		Collection<Group> subs1 = Collections.singletonList(group1);
 		db.setSubscriptions(txn, contactId, subs1, 2);
-		assertEquals(subs1,
-				new HashSet<GroupId>(db.getSubscriptions(txn, contactId)));
+		assertEquals(subs1, db.getSubscriptions(txn, contactId));
 
 		db.commitTransaction(txn);
 		db.close();
@@ -768,6 +781,9 @@ public class H2DatabaseTest extends TestCase {
 	@Test
 	public void testSubscriptionsNotUpdatedIfTimestampIsOld()
 	throws DbException {
+		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
+		Group group1 = groupFactory.createGroup(groupId1, "Another group name",
+				TestUtils.getRandomId(), null);
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
@@ -775,39 +791,23 @@ public class H2DatabaseTest extends TestCase {
 		Map<String, String> transports = Collections.emptyMap();
 		assertEquals(contactId, db.addContact(txn, transports));
 		// Add some subscriptions
-		Collection<GroupId> subs = new HashSet<GroupId>();
-		subs.add(new GroupId(TestUtils.getRandomId()));
-		subs.add(new GroupId(TestUtils.getRandomId()));
+		Collection<Group> subs = Collections.singletonList(group);
 		db.setSubscriptions(txn, contactId, subs, 2);
-		assertEquals(subs,
-				new HashSet<GroupId>(db.getSubscriptions(txn, contactId)));
+		assertEquals(subs, db.getSubscriptions(txn, contactId));
 		// Try to update the subscriptions using a timestamp of 1
-		Collection<GroupId> subs1 = new HashSet<GroupId>();
-		subs1.add(new GroupId(TestUtils.getRandomId()));
-		subs1.add(new GroupId(TestUtils.getRandomId()));
+		Collection<Group> subs1 = Collections.singletonList(group1);
 		db.setSubscriptions(txn, contactId, subs1, 1);
 		// The old subscriptions should still be there
-		assertEquals(subs,
-				new HashSet<GroupId>(db.getSubscriptions(txn, contactId)));
+		assertEquals(subs, db.getSubscriptions(txn, contactId));
 
 		db.commitTransaction(txn);
 		db.close();
 	}
 
 	private Database<Connection> open(boolean resume) throws DbException {
-		final char[] passwordArray = passwordString.toCharArray();
-		Mockery context = new Mockery();
-		final Password password = context.mock(Password.class);
-		context.checking(new Expectations() {{
-			oneOf(password).getPassword();
-			will(returnValue(passwordArray));
-		}});
-		Database<Connection> db = new H2Database(testDir, password, MAX_SIZE);
+		Database<Connection> db = new H2Database(testDir, password, MAX_SIZE,
+				groupFactory);
 		db.open(resume);
-		context.assertIsSatisfied();
-		// The password array should be cleared after use
-		assertTrue(Arrays.equals(new char[passwordString.length()],
-				passwordArray));
 		return db;
 	}
 
@@ -815,4 +815,11 @@ public class H2DatabaseTest extends TestCase {
 	public void tearDown() {
 		TestUtils.deleteTestDirectory(testDir);
 	}
+
+	private class TestPassword implements Password {
+
+		public char[] getPassword() {
+			return passwordString.toCharArray();
+		}
+	}
 }
diff --git a/test/net/sf/briar/protocol/BatchReaderTest.java b/test/net/sf/briar/protocol/BatchReaderTest.java
index b82d9d253c..c7f18b0e57 100644
--- a/test/net/sf/briar/protocol/BatchReaderTest.java
+++ b/test/net/sf/briar/protocol/BatchReaderTest.java
@@ -17,6 +17,7 @@ import net.sf.briar.api.serial.Reader;
 import net.sf.briar.api.serial.ReaderFactory;
 import net.sf.briar.api.serial.Writer;
 import net.sf.briar.api.serial.WriterFactory;
+import net.sf.briar.crypto.CryptoModule;
 import net.sf.briar.serial.SerialModule;
 
 import org.jmock.Expectations;
@@ -29,8 +30,6 @@ import com.google.inject.Injector;
 
 public class BatchReaderTest extends TestCase {
 
-	private static final String DIGEST_ALGO = "SHA-256";
-
 	private final ReaderFactory readerFactory;
 	private final WriterFactory writerFactory;
 	private final MessageDigest messageDigest;
@@ -42,7 +41,7 @@ public class BatchReaderTest extends TestCase {
 		Injector i = Guice.createInjector(new SerialModule());
 		readerFactory = i.getInstance(ReaderFactory.class);
 		writerFactory = i.getInstance(WriterFactory.class);
-		messageDigest = MessageDigest.getInstance(DIGEST_ALGO);
+		messageDigest = MessageDigest.getInstance(CryptoModule.DIGEST_ALGO);
 		context = new Mockery();
 		message = context.mock(Message.class);
 	}
diff --git a/test/net/sf/briar/protocol/ConsumersTest.java b/test/net/sf/briar/protocol/ConsumersTest.java
index fdfba1c5fa..01af2ba690 100644
--- a/test/net/sf/briar/protocol/ConsumersTest.java
+++ b/test/net/sf/briar/protocol/ConsumersTest.java
@@ -9,19 +9,18 @@ import java.util.Random;
 
 import junit.framework.TestCase;
 import net.sf.briar.api.serial.FormatException;
+import net.sf.briar.crypto.CryptoModule;
 
 import org.junit.Test;
 
 public class ConsumersTest extends TestCase {
 
-	private static final String SIGNATURE_ALGO = "SHA256withRSA";
-	private static final String KEY_PAIR_ALGO = "RSA";
-	private static final String DIGEST_ALGO = "SHA-256";
-
 	@Test
 	public void testSigningConsumer() throws Exception {
-		Signature s = Signature.getInstance(SIGNATURE_ALGO);
-		KeyPair k = KeyPairGenerator.getInstance(KEY_PAIR_ALGO).genKeyPair();
+		Signature s = Signature.getInstance(CryptoModule.SIGNATURE_ALGO);
+		KeyPairGenerator gen =
+			KeyPairGenerator.getInstance(CryptoModule.KEY_PAIR_ALGO);
+		KeyPair k = gen.genKeyPair();
 		byte[] data = new byte[1234];
 		// Generate some random data and sign it
 		new Random().nextBytes(data);
@@ -40,7 +39,7 @@ public class ConsumersTest extends TestCase {
 
 	@Test
 	public void testDigestingConsumer() throws Exception {
-		MessageDigest m = MessageDigest.getInstance(DIGEST_ALGO);
+		MessageDigest m = MessageDigest.getInstance(CryptoModule.DIGEST_ALGO);
 		byte[] data = new byte[1234];
 		// Generate some random data and digest it
 		new Random().nextBytes(data);
diff --git a/test/net/sf/briar/protocol/FileReadWriteTest.java b/test/net/sf/briar/protocol/FileReadWriteTest.java
index a4a97cd9db..3d6b9c4b88 100644
--- a/test/net/sf/briar/protocol/FileReadWriteTest.java
+++ b/test/net/sf/briar/protocol/FileReadWriteTest.java
@@ -3,15 +3,10 @@ package net.sf.briar.protocol;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.MessageDigest;
-import java.security.PublicKey;
 import java.security.Signature;
-import java.security.spec.EncodedKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.X509EncodedKeySpec;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
@@ -33,6 +28,7 @@ import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.serial.Reader;
 import net.sf.briar.api.serial.ReaderFactory;
 import net.sf.briar.api.serial.WriterFactory;
+import net.sf.briar.crypto.CryptoModule;
 import net.sf.briar.serial.SerialModule;
 
 import org.junit.After;
@@ -44,10 +40,6 @@ import com.google.inject.Injector;
 
 public class FileReadWriteTest extends TestCase {
 
-	private static final String SIGNATURE_ALGO = "SHA256withRSA";
-	private static final String KEY_PAIR_ALGO = "RSA";
-	private static final String DIGEST_ALGO = "SHA-256";
-
 	private final File testDir = TestUtils.getTestDirectory();
 	private final File file = new File(testDir, "foo");
 
@@ -65,29 +57,22 @@ public class FileReadWriteTest extends TestCase {
 
 	public FileReadWriteTest() throws Exception {
 		super();
-		// Inject the reader and writer factories, since they belong to
-		// a different component
-		Injector i = Guice.createInjector(new SerialModule());
+		Injector i = Guice.createInjector(new SerialModule(),
+				new CryptoModule());
 		readerFactory = i.getInstance(ReaderFactory.class);
 		writerFactory = i.getInstance(WriterFactory.class);
-		signature = Signature.getInstance(SIGNATURE_ALGO);
-		messageDigest = MessageDigest.getInstance(DIGEST_ALGO);
-		batchDigest = MessageDigest.getInstance(DIGEST_ALGO);
-		final KeyFactory keyFactory = KeyFactory.getInstance(KEY_PAIR_ALGO);
-		keyParser = new KeyParser() {
-			public PublicKey parsePublicKey(byte[] encodedKey)
-			throws InvalidKeySpecException {
-				EncodedKeySpec e = new X509EncodedKeySpec(encodedKey);
-				return keyFactory.generatePublic(e);
-			}
-		};
+		keyParser = i.getInstance(KeyParser.class);
+		signature = Signature.getInstance(CryptoModule.SIGNATURE_ALGO);
+		messageDigest = MessageDigest.getInstance(CryptoModule.DIGEST_ALGO);
+		batchDigest = MessageDigest.getInstance(CryptoModule.DIGEST_ALGO);
 		assertEquals(messageDigest.getDigestLength(), UniqueId.LENGTH);
 		assertEquals(batchDigest.getDigestLength(), UniqueId.LENGTH);
 		// Create and encode a test message
 		MessageEncoder messageEncoder = new MessageEncoderImpl(signature,
 				messageDigest, writerFactory);
-		KeyPair keyPair =
-			KeyPairGenerator.getInstance(KEY_PAIR_ALGO).generateKeyPair();
+		KeyPairGenerator gen =
+			KeyPairGenerator.getInstance(CryptoModule.KEY_PAIR_ALGO);
+		KeyPair keyPair = gen.generateKeyPair();
 		message = messageEncoder.encodeMessage(MessageId.NONE, sub, nick,
 				keyPair, messageBody.getBytes("UTF-8"));
 	}
diff --git a/test/net/sf/briar/protocol/SigningDigestingOutputStreamTest.java b/test/net/sf/briar/protocol/SigningDigestingOutputStreamTest.java
index 7a04769564..f86f62114c 100644
--- a/test/net/sf/briar/protocol/SigningDigestingOutputStreamTest.java
+++ b/test/net/sf/briar/protocol/SigningDigestingOutputStreamTest.java
@@ -10,25 +10,24 @@ import java.util.Arrays;
 import java.util.Random;
 
 import junit.framework.TestCase;
+import net.sf.briar.crypto.CryptoModule;
 
 import org.junit.Before;
 import org.junit.Test;
 
 public class SigningDigestingOutputStreamTest extends TestCase {
 
-	private static final String SIGNATURE_ALGO = "SHA256withRSA";
-	private static final String KEY_PAIR_ALGO = "RSA";
-	private static final String DIGEST_ALGO = "SHA-256";
-
 	private KeyPair keyPair = null;
 	private Signature sig = null;
 	private MessageDigest dig = null;
 
 	@Before
 	public void setUp() throws Exception {
-		keyPair = KeyPairGenerator.getInstance(KEY_PAIR_ALGO).generateKeyPair();
-		sig = Signature.getInstance(SIGNATURE_ALGO);
-		dig = MessageDigest.getInstance(DIGEST_ALGO);
+		KeyPairGenerator gen =
+			KeyPairGenerator.getInstance(CryptoModule.KEY_PAIR_ALGO);
+		keyPair = gen.generateKeyPair();
+		sig = Signature.getInstance(CryptoModule.SIGNATURE_ALGO);
+		dig = MessageDigest.getInstance(CryptoModule.DIGEST_ALGO);
 	}
 
 	@Test
-- 
GitLab