Message parser and encoder.

This commit is contained in:
akwizgran
2011-07-12 18:41:08 +01:00
parent 2af6f19476
commit 22a67cc0d2
8 changed files with 220 additions and 40 deletions

View File

@@ -0,0 +1,9 @@
package net.sf.briar.api.crypto;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
public interface KeyParser {
PublicKey parsePublicKey(byte[] encodedKey) throws GeneralSecurityException;
}

View File

@@ -4,6 +4,8 @@ import net.sf.briar.api.serial.Raw;
public interface Message extends Raw { public interface Message extends Raw {
static final int MAX_SIZE = 1024 * 1023; // Not a typo
/** Returns the message's unique identifier. */ /** Returns the message's unique identifier. */
MessageId getId(); MessageId getId();

View File

@@ -0,0 +1,12 @@
package net.sf.briar.api.protocol;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
public interface MessageEncoder {
Message encodeMessage(MessageId parent, GroupId group, String nick,
KeyPair keyPair, byte[] body) throws IOException,
GeneralSecurityException;
}

View File

@@ -1,10 +1,9 @@
package net.sf.briar.api.protocol; package net.sf.briar.api.protocol;
import java.security.SignatureException; import java.io.IOException;
import java.security.GeneralSecurityException;
import net.sf.briar.api.serial.FormatException;
public interface MessageParser { public interface MessageParser {
Message parseMessage(byte[] body) throws FormatException, SignatureException; Message parseMessage(byte[] raw) throws IOException, GeneralSecurityException;
} }

View File

@@ -0,0 +1,63 @@
package net.sf.briar.protocol;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.Signature;
import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.GroupId;
import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageEncoder;
import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
class MessageEncoderImpl implements MessageEncoder {
private final Signature signature;
private final MessageDigest messageDigest;
private final WriterFactory writerFactory;
MessageEncoderImpl(Signature signature, MessageDigest messageDigest,
WriterFactory writerFactory) {
this.signature = signature;
this.messageDigest = messageDigest;
this.writerFactory = writerFactory;
}
public Message encodeMessage(MessageId parent, GroupId group, String nick,
KeyPair keyPair, byte[] body) throws IOException,
GeneralSecurityException {
long timestamp = System.currentTimeMillis();
byte[] encodedKey = keyPair.getPublic().getEncoded();
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeRaw(parent);
w.writeRaw(group);
w.writeInt64(timestamp);
w.writeUtf8(nick);
w.writeRaw(encodedKey);
w.writeRaw(body);
byte[] signable = out.toByteArray();
signature.initSign(keyPair.getPrivate());
signature.update(signable);
byte[] sig = signature.sign();
w.writeRaw(sig);
byte[] raw = out.toByteArray();
w.close();
// The message ID is the hash of the entire message
messageDigest.reset();
messageDigest.update(raw);
MessageId id = new MessageId(messageDigest.digest());
// The author ID is the hash of the author's nick and public key
messageDigest.reset();
messageDigest.update(nick.getBytes("UTF-8"));
messageDigest.update((byte) 0); // Null separator
messageDigest.update(encodedKey);
AuthorId author = new AuthorId(messageDigest.digest());
return new MessageImpl(id, parent, group, author, timestamp, raw);
}
}

View File

@@ -12,16 +12,16 @@ public class MessageImpl implements Message {
private final GroupId group; private final GroupId group;
private final AuthorId author; private final AuthorId author;
private final long timestamp; private final long timestamp;
private final byte[] body; private final byte[] raw;
public MessageImpl(MessageId id, MessageId parent, GroupId group, public MessageImpl(MessageId id, MessageId parent, GroupId group,
AuthorId author, long timestamp, byte[] body) { AuthorId author, long timestamp, byte[] raw) {
this.id = id; this.id = id;
this.parent = parent; this.parent = parent;
this.group = group; this.group = group;
this.author = author; this.author = author;
this.timestamp = timestamp; this.timestamp = timestamp;
this.body = body; this.raw = raw;
} }
public MessageId getId() { public MessageId getId() {
@@ -45,11 +45,11 @@ public class MessageImpl implements Message {
} }
public int getSize() { public int getSize() {
return body.length; return raw.length;
} }
public byte[] getBytes() { public byte[] getBytes() {
return body; return raw;
} }
@Override @Override

View File

@@ -0,0 +1,84 @@
package net.sf.briar.protocol;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.protocol.AuthorId;
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.api.protocol.MessageParser;
import net.sf.briar.api.protocol.UniqueId;
import net.sf.briar.api.serial.FormatException;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
class MessageParserImpl implements MessageParser {
private final KeyParser keyParser;
private final Signature signature;
private final MessageDigest messageDigest;
private final ReaderFactory readerFactory;
MessageParserImpl(KeyParser keyParser, Signature signature,
MessageDigest messageDigest, ReaderFactory readerFactory) {
this.keyParser = keyParser;
this.signature = signature;
this.messageDigest = messageDigest;
this.readerFactory = readerFactory;
}
public Message parseMessage(byte[] raw) throws IOException,
GeneralSecurityException {
if(raw.length > Message.MAX_SIZE) throw new FormatException();
ByteArrayInputStream in = new ByteArrayInputStream(raw);
Reader r = readerFactory.createReader(in);
// Read the parent message ID
byte[] idBytes = r.readRaw();
if(idBytes.length != UniqueId.LENGTH) throw new FormatException();
MessageId parent = new MessageId(idBytes);
// Read the group ID
idBytes = r.readRaw();
if(idBytes.length != UniqueId.LENGTH) throw new FormatException();
GroupId group = new GroupId(idBytes);
// Read the timestamp
long timestamp = r.readInt64();
// Hash the author's nick and public key to get the author ID
String nick = r.readUtf8();
byte[] encodedKey = r.readRaw();
PublicKey publicKey = keyParser.parsePublicKey(encodedKey);
messageDigest.reset();
messageDigest.update(nick.getBytes("UTF-8"));
messageDigest.update((byte) 0); // Null separator
messageDigest.update(encodedKey);
AuthorId author = new AuthorId(messageDigest.digest());
// Skip the message body
r.readRaw();
// Read the signature and work out how long the signed message is
byte[] sig = r.readRaw();
int length = raw.length - sig.length - bytesToEncode(sig.length);
// Verify the signature
signature.initVerify(publicKey);
signature.update(raw, 0, length);
if(!signature.verify(sig)) throw new SignatureException();
// Hash the message, including the signature, to get the message ID
messageDigest.reset();
messageDigest.update(raw);
MessageId id = new MessageId(messageDigest.digest());
return new MessageImpl(id, parent, group, author, timestamp, raw);
}
// FIXME: Work out a better way of doing this
private int bytesToEncode(int i) {
if(i >= 0 && i <= Byte.MAX_VALUE) return 2;
if(i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) return 3;
if(i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) return 4;
return 6;
}
}

View File

@@ -5,19 +5,23 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.PublicKey;
import java.security.Signature; import java.security.Signature;
import java.security.SignatureException; import java.security.spec.EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import junit.framework.TestCase; import junit.framework.TestCase;
import net.sf.briar.TestUtils; import net.sf.briar.TestUtils;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.BatchBuilder; import net.sf.briar.api.protocol.BatchBuilder;
import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.BatchId;
@@ -27,11 +31,12 @@ import net.sf.briar.api.protocol.GroupId;
import net.sf.briar.api.protocol.Header; import net.sf.briar.api.protocol.Header;
import net.sf.briar.api.protocol.HeaderBuilder; import net.sf.briar.api.protocol.HeaderBuilder;
import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageEncoder;
import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.api.protocol.MessageParser; import net.sf.briar.api.protocol.MessageParser;
import net.sf.briar.api.protocol.UniqueId; import net.sf.briar.api.protocol.UniqueId;
import net.sf.briar.api.serial.FormatException;
import net.sf.briar.api.serial.Reader; 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.Writer;
import net.sf.briar.api.serial.WriterFactory; import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.serial.ReaderFactoryImpl; import net.sf.briar.serial.ReaderFactoryImpl;
@@ -59,27 +64,34 @@ public class BundleReadWriteTest extends TestCase {
private final Set<GroupId> subs = Collections.singleton(sub); private final Set<GroupId> subs = Collections.singleton(sub);
private final Map<String, String> transports = private final Map<String, String> transports =
Collections.singletonMap("foo", "bar"); Collections.singletonMap("foo", "bar");
private final String nick = "Foo Bar";
private final String messageBody = "This is the message body! Wooooooo!";
private final MessageId messageId = new MessageId(TestUtils.getRandomId()); // FIXME: This test should not depend on impls in another component
private final AuthorId authorId = new AuthorId(TestUtils.getRandomId()); private final ReaderFactory rf = new ReaderFactoryImpl();
private final long timestamp = System.currentTimeMillis();
private final byte[] messageBody = new byte[123];
private final Message message = new MessageImpl(messageId, MessageId.NONE,
sub, authorId, timestamp, messageBody);
// FIXME: This test should not depend on an impl in another component
private final WriterFactory wf = new WriterFactoryImpl(); private final WriterFactory wf = new WriterFactoryImpl();
private final KeyPair keyPair; private final KeyPair keyPair;
private final Signature sig; private final Signature sig;
private final MessageDigest digest; private final MessageDigest digest;
private final KeyParser keyParser;
private final Message message;
public BundleReadWriteTest() throws NoSuchAlgorithmException { public BundleReadWriteTest() throws Exception {
super(); super();
keyPair = KeyPairGenerator.getInstance(KEY_PAIR_ALGO).generateKeyPair(); keyPair = KeyPairGenerator.getInstance(KEY_PAIR_ALGO).generateKeyPair();
sig = Signature.getInstance(SIGNATURE_ALGO); sig = Signature.getInstance(SIGNATURE_ALGO);
digest = MessageDigest.getInstance(DIGEST_ALGO); digest = MessageDigest.getInstance(DIGEST_ALGO);
keyParser = new KeyParser() {
public PublicKey parsePublicKey(byte[] encodedKey) throws GeneralSecurityException {
EncodedKeySpec e = new X509EncodedKeySpec(encodedKey);
return KeyFactory.getInstance(KEY_PAIR_ALGO).generatePublic(e);
}
};
assertEquals(digest.getDigestLength(), UniqueId.LENGTH); assertEquals(digest.getDigestLength(), UniqueId.LENGTH);
MessageEncoder messageEncoder = new MessageEncoderImpl(sig, digest, wf);
message = messageEncoder.encodeMessage(MessageId.NONE, sub, nick,
keyPair, messageBody.getBytes("UTF-8"));
} }
@Before @Before
@@ -108,7 +120,7 @@ public class BundleReadWriteTest extends TestCase {
w.close(); w.close();
assertTrue(bundle.exists()); assertTrue(bundle.exists());
assertTrue(bundle.length() > messageBody.length); assertTrue(bundle.length() > message.getSize());
} }
@Test @Test
@@ -116,13 +128,8 @@ public class BundleReadWriteTest extends TestCase {
testWriteBundle(); testWriteBundle();
MessageParser messageParser = new MessageParser() { MessageParser messageParser =
public Message parseMessage(byte[] body) throws FormatException, new MessageParserImpl(keyParser, sig, digest, rf);
SignatureException {
// FIXME: Really parse the message
return message;
}
};
Provider<HeaderBuilder> headerBuilderProvider = Provider<HeaderBuilder> headerBuilderProvider =
new Provider<HeaderBuilder>() { new Provider<HeaderBuilder>() {
public HeaderBuilder get() { public HeaderBuilder get() {
@@ -146,7 +153,16 @@ public class BundleReadWriteTest extends TestCase {
assertEquals(subs, h.getSubscriptions()); assertEquals(subs, h.getSubscriptions());
assertEquals(transports, h.getTransports()); assertEquals(transports, h.getTransports());
Batch b = r.getNextBatch(); Batch b = r.getNextBatch();
assertEquals(Collections.singletonList(message), b.getMessages()); Iterator<Message> i = b.getMessages().iterator();
assertTrue(i.hasNext());
Message m = i.next();
assertEquals(message.getId(), m.getId());
assertEquals(message.getParent(), m.getParent());
assertEquals(message.getGroup(), m.getGroup());
assertEquals(message.getAuthor(), m.getAuthor());
assertEquals(message.getTimestamp(), m.getTimestamp());
assertTrue(Arrays.equals(message.getBytes(), m.getBytes()));
assertFalse(i.hasNext());
assertNull(r.getNextBatch()); assertNull(r.getNextBatch());
r.close(); r.close();
} }
@@ -158,19 +174,14 @@ public class BundleReadWriteTest extends TestCase {
testWriteBundle(); testWriteBundle();
RandomAccessFile f = new RandomAccessFile(bundle, "rw"); RandomAccessFile f = new RandomAccessFile(bundle, "rw");
f.seek(bundle.length() - 50); f.seek(bundle.length() - 150);
byte b = f.readByte(); byte b = f.readByte();
f.seek(bundle.length() - 50); f.seek(bundle.length() - 150);
f.writeByte(b + 1); f.writeByte(b + 1);
f.close(); f.close();
MessageParser messageParser = new MessageParser() { MessageParser messageParser =
public Message parseMessage(byte[] body) throws FormatException, new MessageParserImpl(keyParser, sig, digest, rf);
SignatureException {
// FIXME: Really parse the message
return message;
}
};
Provider<HeaderBuilder> headerBuilderProvider = Provider<HeaderBuilder> headerBuilderProvider =
new Provider<HeaderBuilder>() { new Provider<HeaderBuilder>() {
public HeaderBuilder get() { public HeaderBuilder get() {