Added a new version of the BB Server that allows for search of messages containing multiple tags and/or signatures.

Added tests for both Server types.
Bulletin-Board-Client-phase_1
Arbel Deutsch Peled 2015-12-08 08:17:30 +02:00
parent 679d18f4a2
commit 0b847dcaf4
4 changed files with 552 additions and 86 deletions

View File

@ -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<MessageFilter>{
@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<MessageFilter> filters = new ArrayList<MessageFilter>(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();
}
}

View File

@ -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();
}
}

View File

@ -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<BulletinBoardMessage> 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<BulletinBoardMessage> 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<BulletinBoardMessage> 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());
}
}
}

View File

@ -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();
}
}