diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/EnhancedSQLiteBulletinBoardServer.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/EnhancedSQLiteBulletinBoardServer.java new file mode 100644 index 0000000..6baac36 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/sqlserver/EnhancedSQLiteBulletinBoardServer.java @@ -0,0 +1,190 @@ +package meerkat.bulletinboard.sqlserver; + +import com.google.protobuf.InvalidProtocolBufferException; +import meerkat.comm.CommunicationException; +import meerkat.protobuf.BulletinBoardAPI; +import meerkat.protobuf.BulletinBoardAPI.*; +import meerkat.protobuf.Crypto.*; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Created by Arbel Deutsch Peled on 07-Dec-15. + * This server version allows for reading of messages with several tags and/or signatures + * The superclass only allows one constraint per type. + */ +public class EnhancedSQLiteBulletinBoardServer extends SQLiteBulletinBoardServer { + + /** + * This class implements a comparator for the MessageFilter class + * The comparison is done solely by comparing the type of the filter + * This is used to sort the filters by type + */ + public class FilterTypeComparator implements Comparator{ + + @Override + public int compare(MessageFilter filter1, MessageFilter filter2) { + return filter1.getTypeValue() - filter2.getTypeValue(); + } + } + + @Override + public BulletinBoardMessageList readMessages(MessageFilterList filterList) throws CommunicationException { + + PreparedStatement pstmt; + ResultSet messages, signatures; + + long entryNum; + BulletinBoardMessageList.Builder resultListBuilder = BulletinBoardMessageList.newBuilder(); + BulletinBoardMessage.Builder messageBuilder; + + String sql; + String sqlSuffix = ""; + + List filters = new ArrayList(filterList.getFilterList()); + int i; + + boolean tagsRequired = false; + boolean signaturesRequired = false; + + boolean isFirstFilter = true; + + Collections.sort(filters, new FilterTypeComparator()); + + // Check if Tag/Signature tables are required for filtering purposes. + + sql = "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"; + + // Add conditions. + + if (!filters.isEmpty()){ + sql += " WHERE"; + + for (MessageFilter filter : filters){ + + if (filter.getType().getNumber() != FilterType.MAX_MESSAGES_VALUE){ + if (isFirstFilter){ + isFirstFilter = false; + } else{ + sql += " AND"; + } + } + + switch (filter.getType().getNumber()){ + case FilterType.EXACT_ENTRY_VALUE: + sql += " MsgTable.EntryNum = ?"; + break; + case FilterType.MAX_ENTRY_VALUE: + sql += " MsgTable.EntryNum <= ?"; + break; + case FilterType.MAX_MESSAGES_VALUE: + sqlSuffix += " LIMIT = ?"; + break; + case FilterType.MSG_ID_VALUE: + sql += " MsgTable.MsgId = ?"; + break; + case FilterType.SIGNER_ID_VALUE: + sql += " EXISTS (SELECT 1 FROM SignatureTable" + + " WHERE SignatureTable.SignerId = ? AND SignatureTable.EntryNum = MsgTable.EntryNum)"; + break; + case FilterType.TAG_VALUE: + sql += " EXISTS (SELECT 1 FROM TagTable" + + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" + + " WHERE TagTable.Tag = ? AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; + break; + } + } + + sql += sqlSuffix; + } + + // Make query. + + try { + pstmt = connection.prepareStatement(sql); + + // Specify values for filters. + + i = 1; + for (MessageFilter filter : filters){ + + switch (filter.getType().getNumber()){ + + case FilterType.EXACT_ENTRY_VALUE: // Go through. + case FilterType.MAX_ENTRY_VALUE: + pstmt.setLong(i, filter.getEntry()); + i++; + break; + + case FilterType.MSG_ID_VALUE: // Go through. + case FilterType.SIGNER_ID_VALUE: + pstmt.setBytes(i, filter.getId().toByteArray()); + i++; + break; + + case FilterType.TAG_VALUE: + pstmt.setString(i, filter.getTag()); + i++; + break; + + // The max-messages condition is applied as a suffix. Therefore, it is treated differently. + case FilterType.MAX_MESSAGES_VALUE: + pstmt.setLong(filters.size(), filter.getMaxMessages()); + i++; + break; + + } + } + + // Run query. + + messages = pstmt.executeQuery(); + + // Compile list of messages. + + sql = "SELECT Signature FROM SignatureTable WHERE EntryNum = ?"; + pstmt = connection.prepareStatement(sql); + + while (messages.next()){ + + // Get entry number and retrieve signatures. + + entryNum = messages.getLong(1); + pstmt.setLong(1, entryNum); + signatures = pstmt.executeQuery(); + + // Create message and append signatures. + + messageBuilder = BulletinBoardMessage.newBuilder() + .setEntryNum(entryNum) + .setMsg(UnsignedBulletinBoardMessage.parseFrom(messages.getBytes(2))); + + while (signatures.next()){ + messageBuilder.addSig(Signature.parseFrom(signatures.getBytes(1))); + } + + // Finalize message and add to message list. + + resultListBuilder.addMessage(messageBuilder.build()); + + } + + pstmt.close(); + + } catch (SQLException e){ + throw new CommunicationException("Error reading messages from DB: " + e.getMessage()); + } catch (InvalidProtocolBufferException e) { + throw new CommunicationException("Invalid data from DB: " + e.getMessage()); + } + + //Combine results and return. + + return resultListBuilder.build(); + } +} diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/EnhancedSQLiteBulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/EnhancedSQLiteBulletinBoardServerTest.java new file mode 100644 index 0000000..8d643e4 --- /dev/null +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/EnhancedSQLiteBulletinBoardServerTest.java @@ -0,0 +1,54 @@ +package meerkat.bulletinboard; + +import meerkat.bulletinboard.sqlserver.EnhancedSQLiteBulletinBoardServer; +import meerkat.comm.CommunicationException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.fail; + +/** + * Created by Arbel Deutsch Peled on 07-Dec-15. + */ +public class EnhancedSQLiteBulletinBoardServerTest{ + + private String testFilename = "EnhancedSQLiteDBTest.db"; + + private GenericBulletinBoardServerTest serverTest; + + @Before + public void init(){ + + File old = new File(testFilename); + old.delete(); + + BulletinBoardServer bulletinBoardServer = new EnhancedSQLiteBulletinBoardServer(); + try { + bulletinBoardServer.init(testFilename); + + } catch (CommunicationException e) { + System.err.println("Failed to initialize server " + e.getMessage()); + fail("Failed to initialize server " + e.getMessage()); + return; + } + + serverTest = new GenericBulletinBoardServerTest(); + serverTest.init(bulletinBoardServer); + } + + @Test + public void bulkTest() { + System.err.println("Testing Enhanced SQLite Server"); + serverTest.testInsert(); + serverTest.testSimpleTagAndSignature(); + } + + @After + public void close() { + serverTest.close(); + } + +} diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java index fb26df8..eca9273 100644 --- a/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/GenericBulletinBoardServerTest.java @@ -26,12 +26,14 @@ import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; -public abstract class GenericBulletinBoardServerTest { +public class GenericBulletinBoardServerTest { + protected BulletinBoardServer bulletinBoardServer; private ECDSASignature signers[]; + private ByteString[] signerIDs; private Random random; - + private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12"; private static String KEYFILE_EXAMPLE3 = "/certs/enduser-certs/user3-key-with-password-shh.p12"; @@ -40,10 +42,9 @@ public abstract class GenericBulletinBoardServerTest { public static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt"; public static String CERT3_PEM_EXAMPLE = "/certs/enduser-certs/user3.crt"; - + /** - * - * @param cls + * @param bulletinBoardServer is an initialized server. * @throws InstantiationException * @throws IllegalAccessException * @throws CertificateException @@ -53,19 +54,23 @@ public abstract class GenericBulletinBoardServerTest { * @throws UnrecoverableKeyException * @throws CommunicationException */ - public void init(String meerkatDB) throws InstantiationException, IllegalAccessException, CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, CommunicationException{ + public void init(BulletinBoardServer bulletinBoardServer) { - bulletinBoardServer.init(meerkatDB); + this.bulletinBoardServer = bulletinBoardServer; signers = new ECDSASignature[2]; + signerIDs = new ByteString[signers.length]; signers[0] = new ECDSASignature(); signers[1] = new ECDSASignature(); InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE); char[] password = KEYFILE_PASSWORD1.toCharArray(); - - KeyStore.Builder keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password); - signers[0].loadSigningCertificate(keyStoreBuilder); + + KeyStore.Builder keyStoreBuilder = null; + try { + keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password); + + signers[0].loadSigningCertificate(keyStoreBuilder); signers[0].loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE)); @@ -76,6 +81,40 @@ public abstract class GenericBulletinBoardServerTest { signers[1].loadSigningCertificate(keyStoreBuilder); signers[1].loadVerificationCertificates(getClass().getResourceAsStream(CERT3_PEM_EXAMPLE)); + + } catch (IOException e) { + System.err.println("Failed reading from signature file " + e.getMessage()); + fail("Failed reading from signature file " + e.getMessage()); + } catch (CertificateException e) { + System.err.println("Failed reading certificate " + e.getMessage()); + fail("Failed reading certificate " + e.getMessage()); + } catch (KeyStoreException e) { + System.err.println("Failed reading keystore " + e.getMessage()); + fail("Failed reading keystore " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + System.err.println("Couldn't find signing algorithm " + e.getMessage()); + fail("Couldn't find signing algorithm " + e.getMessage()); + } catch (UnrecoverableKeyException e) { + System.err.println("Couldn't find signing key " + e.getMessage()); + fail("Couldn't find signing key " + e.getMessage()); + } + + // Get signer IDs + // TODO: remove this after creation of getSignerID method + + UnsignedBulletinBoardMessage msg = UnsignedBulletinBoardMessage.newBuilder().build(); + + for (int i = 0 ; i < signers.length ; i++) { + + try { + signers[i].updateContent(UnsignedBulletinBoardMessage.newBuilder().build()); + signerIDs[i] = signers[i].sign().getSignerId(); + } catch (SignatureException e) { + System.err.println("Error signing message" + e.getMessage()); + fail("Error signing message" + e.getMessage()); + return; + } + } random = new Random(0); // We use insecure randomness in tests for repeatability } @@ -87,96 +126,134 @@ public abstract class GenericBulletinBoardServerTest { private String randomString(){ return new BigInteger(130, random).toString(32); } - - public void bulkTest() throws CommunicationException, SignatureException, InvalidKeyException, CertificateException, IOException{ - - final int TAG_NUM = 5; // Number of tags. - final int MESSAGE_NUM = 32; // Number of messages (2^TAG_NUM). + + private final int TAG_NUM = 5; // Number of tags. + private final int MESSAGE_NUM = 32; // Number of messages (2^TAG_NUM). + + + private String[] tags; + private byte[][] data; + + /** + * Tests writing of several messages with multiple tags and signatures. + * @throws CommunicationException + * @throws SignatureException + * @throws InvalidKeyException + * @throws CertificateException + * @throws IOException + */ + public void testInsert() { + final int BYTES_PER_MESSAGE_DATA = 50; // Message size. - - String[] tags = new String[TAG_NUM]; - byte[][] data = new byte[MESSAGE_NUM][BYTES_PER_MESSAGE_DATA]; - + + tags = new String[TAG_NUM]; + data = new byte[MESSAGE_NUM][BYTES_PER_MESSAGE_DATA]; + UnsignedBulletinBoardMessage.Builder unsignedMsgBuilder; BulletinBoardMessage.Builder msgBuilder; - - int i,j; - + + int i, j; + // Generate random data. - - for (i = 1 ; i <= MESSAGE_NUM ; i++){ - for (j = 0 ; j < BYTES_PER_MESSAGE_DATA ; j++){ - data[i-1][j] = randomByte(); + + for (i = 1; i <= MESSAGE_NUM; i++) { + for (j = 0; j < BYTES_PER_MESSAGE_DATA; j++) { + data[i - 1][j] = randomByte(); } } - - for (i = 0 ; i < TAG_NUM ; i++){ + + for (i = 0; i < TAG_NUM; i++) { tags[i] = randomString(); } - + // Build messages. - - for (i = 1 ; i <= MESSAGE_NUM ; i++){ + + for (i = 1; i <= MESSAGE_NUM; i++) { unsignedMsgBuilder = UnsignedBulletinBoardMessage.newBuilder() - .setData(ByteString.copyFrom(data[i-1])); - + .setData(ByteString.copyFrom(data[i - 1])); + // Add tags based on bit-representation of message number. - + int copyI = i; - for (j = 0 ; j < TAG_NUM ; j++){ - if (copyI % 2 == 1){ + for (j = 0; j < TAG_NUM; j++) { + if (copyI % 2 == 1) { unsignedMsgBuilder.addTag(tags[j]); } - + copyI >>>= 1; } - + // Build message. - + msgBuilder = BulletinBoardMessage.newBuilder() .setMsg(unsignedMsgBuilder.build()); - + // Add signatures. - - if (i % 2 == 1){ - signers[0].updateContent(msgBuilder.getMsg()); - msgBuilder.addSig(signers[0].sign()); - - if (i % 4 == 1){ - signers[1].updateContent(msgBuilder.getMsg()); - msgBuilder.addSig(signers[1].sign()); - } - } - - // Post message. - - bulletinBoardServer.postMessage(msgBuilder.build()); + + try { + + if (i % 2 == 1) { + signers[0].updateContent(msgBuilder.getMsg()); + msgBuilder.addSig(signers[0].sign()); + + if (i % 4 == 1) { + signers[1].updateContent(msgBuilder.getMsg()); + msgBuilder.addSig(signers[1].sign()); + } + } + + } catch (SignatureException e) { + fail(e.getMessage()); + } + + // Post message. + + try { + bulletinBoardServer.postMessage(msgBuilder.build()); + } catch (CommunicationException e) { + fail(e.getMessage()); + } } - + } + + /** + * Tests retrieval of messages written in {@Link #testInsert()} + * Only queries using one tag filter + */ + public void testSimpleTagAndSignature(){ + + List messages; + // Check tag mechanism - for (i = 0 ; i < TAG_NUM ; i++){ + for (int i = 0 ; i < TAG_NUM ; i++){ // Retrieve messages having tag i - - List messages = - bulletinBoardServer.readMessages( - MessageFilterList.newBuilder() - .addFilter(MessageFilter.newBuilder() - .setType(FilterType.TAG) - .setTag(tags[i]) + + try { + + messages = bulletinBoardServer.readMessages( + MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.TAG) + .setTag(tags[i]) + .build() + ) .build() - ) - .build() ) .getMessageList(); - + + } catch (CommunicationException e) { + fail(e.getMessage()); + return; + } + // Assert that the number of retrieved messages is correct. assertThat(messages.size(), is(MESSAGE_NUM / 2)); // Assert the identity of the messages. - + for (BulletinBoardMessage msg : messages){ // Assert serial number and raw data. @@ -185,34 +262,112 @@ public abstract class GenericBulletinBoardServerTest { assertThat(msg.getMsg().getData().toByteArray(), is(data[(int) msg.getEntryNum() - 1])); // Assert signatures. - - if (msg.getEntryNum() % 2 == 1){ - signers[0].initVerify(msg.getSig(0)); - signers[0].updateContent(msg.getMsg()); - assertTrue("Signature did not verify!", signers[0].verify()); - - if (msg.getEntryNum() % 4 == 1){ - signers[1].initVerify(msg.getSig(1)); - signers[1].updateContent(msg.getMsg()); - assertTrue("Signature did not verify!", signers[1].verify()); - - assertThat(msg.getSigCount(), is(2)); - } - else{ - assertThat(msg.getSigCount(), is(1)); - } - } - else{ - assertThat(msg.getSigCount(), is(0)); + + try { + + if (msg.getEntryNum() % 2 == 1) { + signers[0].initVerify(msg.getSig(0)); + signers[0].updateContent(msg.getMsg()); + assertTrue("Signature did not verify!", signers[0].verify()); + + if (msg.getEntryNum() % 4 == 1) { + signers[1].initVerify(msg.getSig(1)); + signers[1].updateContent(msg.getMsg()); + assertTrue("Signature did not verify!", signers[1].verify()); + + assertThat(msg.getSigCount(), is(2)); + } else { + assertThat(msg.getSigCount(), is(1)); + } + } else { + assertThat(msg.getSigCount(), is(0)); + } + } catch (Exception e) { + fail(e.getMessage()); } } } } + + /** + * Tests retrieval of messages written in {@Link #testInsert()} using multiple tags/signature filters. + */ + public void testEnhancedTagsAndSignatures(){ + + List messages; + MessageFilterList.Builder filterListBuilder = MessageFilterList.newBuilder(); + + int expectedMsgCount = MESSAGE_NUM; + + // Check multiple tag filters. + + for (int i = 0 ; i < TAG_NUM ; i++) { + + filterListBuilder.addFilter( + MessageFilter.newBuilder() + .setType(FilterType.TAG) + .setTag(tags[i]) + .build() + ); + + try { + messages = bulletinBoardServer.readMessages(filterListBuilder.build()).getMessageList(); + } catch (CommunicationException e) { + System.err.println("Failed retrieving multi-tag messages from DB: " + e.getMessage()); + fail("Failed retrieving multi-tag messages from DB: " + e.getMessage()); + return; + } + + expectedMsgCount /= 2; + + assertThat(messages.size(), is(expectedMsgCount)); + + for (BulletinBoardMessage msg : messages) { + for (int j = 0 ; j < i ; j++) { + assertThat((msg.getEntryNum() >>> j) % 2, is((long) 1)); + } + } + } + + // Check multiple signature filters. + + filterListBuilder = MessageFilterList.newBuilder() + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.SIGNER_ID) + .setId(signerIDs[0]) + .build()) + .addFilter(MessageFilter.newBuilder() + .setType(FilterType.SIGNER_ID) + .setId(signerIDs[1]) + .build() + ); + + try { + messages = bulletinBoardServer.readMessages(filterListBuilder.build()).getMessageList(); + } catch (CommunicationException e) { + System.err.println("Failed retrieving multi-signature message from DB: " + e.getMessage()); + fail("Failed retrieving multi-signature message from DB: " + e.getMessage()); + return; + } + + assertThat(messages.size(), is(MESSAGE_NUM / 4)); + + for (BulletinBoardMessage message : messages) { + assertThat(message.getEntryNum() % 4, is((long) 1)); + } + + } public void close(){ signers[0].clearSigningKey(); signers[1].clearSigningKey(); + try { + bulletinBoardServer.close(); + } catch (CommunicationException e) { + System.err.println("Error closing server " + e.getMessage()); + fail("Error closing server " + e.getMessage()); + } } } diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteBulletinBoardServerTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteBulletinBoardServerTest.java new file mode 100644 index 0000000..1981ea3 --- /dev/null +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/SQLiteBulletinBoardServerTest.java @@ -0,0 +1,67 @@ +package meerkat.bulletinboard; + +import meerkat.bulletinboard.sqlserver.SQLiteBulletinBoardServer; +import meerkat.comm.CommunicationException; +import meerkat.protobuf.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.security.*; +import java.security.cert.CertificateException; + +import static org.junit.Assert.fail; + +/** + * Created by Arbel Deutsch Peled on 07-Dec-15. + */ +public class SQLiteBulletinBoardServerTest{ + + private String testFilename = "SQLiteDBTest.db"; + + private GenericBulletinBoardServerTest serverTest; + + @Before + public void init(){ + + File old = new File(testFilename); + old.delete(); + + BulletinBoardServer bulletinBoardServer = new SQLiteBulletinBoardServer(); + try { + bulletinBoardServer.init(testFilename); + + } catch (CommunicationException e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + return; + } + + serverTest = new GenericBulletinBoardServerTest(); + try { + serverTest.init(bulletinBoardServer); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + } + + @Test + public void bulkTest() { + try { + serverTest.testInsert(); + serverTest.testSimpleTagAndSignature(); + } catch (Exception e) { + System.err.println(e.getMessage()); + fail(e.getMessage()); + } + } + + @After + public void close() { + serverTest.close(); + } + +}