mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Message parser and encoder.
This commit is contained in:
9
api/net/sf/briar/api/crypto/KeyParser.java
Normal file
9
api/net/sf/briar/api/crypto/KeyParser.java
Normal 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;
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import net.sf.briar.api.serial.Raw;
|
||||
|
||||
public interface Message extends Raw {
|
||||
|
||||
static final int MAX_SIZE = 1024 * 1023; // Not a typo
|
||||
|
||||
/** Returns the message's unique identifier. */
|
||||
MessageId getId();
|
||||
|
||||
|
||||
12
api/net/sf/briar/api/protocol/MessageEncoder.java
Normal file
12
api/net/sf/briar/api/protocol/MessageEncoder.java
Normal 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;
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
package net.sf.briar.api.protocol;
|
||||
|
||||
import java.security.SignatureException;
|
||||
|
||||
import net.sf.briar.api.serial.FormatException;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public interface MessageParser {
|
||||
|
||||
Message parseMessage(byte[] body) throws FormatException, SignatureException;
|
||||
Message parseMessage(byte[] raw) throws IOException, GeneralSecurityException;
|
||||
}
|
||||
|
||||
63
components/net/sf/briar/protocol/MessageEncoderImpl.java
Normal file
63
components/net/sf/briar/protocol/MessageEncoderImpl.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -12,16 +12,16 @@ public class MessageImpl implements Message {
|
||||
private final GroupId group;
|
||||
private final AuthorId author;
|
||||
private final long timestamp;
|
||||
private final byte[] body;
|
||||
private final byte[] raw;
|
||||
|
||||
public MessageImpl(MessageId id, MessageId parent, GroupId group,
|
||||
AuthorId author, long timestamp, byte[] body) {
|
||||
AuthorId author, long timestamp, byte[] raw) {
|
||||
this.id = id;
|
||||
this.parent = parent;
|
||||
this.group = group;
|
||||
this.author = author;
|
||||
this.timestamp = timestamp;
|
||||
this.body = body;
|
||||
this.raw = raw;
|
||||
}
|
||||
|
||||
public MessageId getId() {
|
||||
@@ -45,11 +45,11 @@ public class MessageImpl implements Message {
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return body.length;
|
||||
return raw.length;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return body;
|
||||
return raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
84
components/net/sf/briar/protocol/MessageParserImpl.java
Normal file
84
components/net/sf/briar/protocol/MessageParserImpl.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,23 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
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.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
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.BatchBuilder;
|
||||
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.HeaderBuilder;
|
||||
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.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;
|
||||
import net.sf.briar.api.serial.Writer;
|
||||
import net.sf.briar.api.serial.WriterFactory;
|
||||
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 Map<String, String> transports =
|
||||
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());
|
||||
private final AuthorId authorId = new AuthorId(TestUtils.getRandomId());
|
||||
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
|
||||
// FIXME: This test should not depend on impls in another component
|
||||
private final ReaderFactory rf = new ReaderFactoryImpl();
|
||||
private final WriterFactory wf = new WriterFactoryImpl();
|
||||
|
||||
private final KeyPair keyPair;
|
||||
private final Signature sig;
|
||||
private final MessageDigest digest;
|
||||
private final KeyParser keyParser;
|
||||
private final Message message;
|
||||
|
||||
public BundleReadWriteTest() throws NoSuchAlgorithmException {
|
||||
public BundleReadWriteTest() throws Exception {
|
||||
super();
|
||||
keyPair = KeyPairGenerator.getInstance(KEY_PAIR_ALGO).generateKeyPair();
|
||||
sig = Signature.getInstance(SIGNATURE_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);
|
||||
MessageEncoder messageEncoder = new MessageEncoderImpl(sig, digest, wf);
|
||||
message = messageEncoder.encodeMessage(MessageId.NONE, sub, nick,
|
||||
keyPair, messageBody.getBytes("UTF-8"));
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -108,7 +120,7 @@ public class BundleReadWriteTest extends TestCase {
|
||||
w.close();
|
||||
|
||||
assertTrue(bundle.exists());
|
||||
assertTrue(bundle.length() > messageBody.length);
|
||||
assertTrue(bundle.length() > message.getSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -116,13 +128,8 @@ public class BundleReadWriteTest extends TestCase {
|
||||
|
||||
testWriteBundle();
|
||||
|
||||
MessageParser messageParser = new MessageParser() {
|
||||
public Message parseMessage(byte[] body) throws FormatException,
|
||||
SignatureException {
|
||||
// FIXME: Really parse the message
|
||||
return message;
|
||||
}
|
||||
};
|
||||
MessageParser messageParser =
|
||||
new MessageParserImpl(keyParser, sig, digest, rf);
|
||||
Provider<HeaderBuilder> headerBuilderProvider =
|
||||
new Provider<HeaderBuilder>() {
|
||||
public HeaderBuilder get() {
|
||||
@@ -146,7 +153,16 @@ public class BundleReadWriteTest extends TestCase {
|
||||
assertEquals(subs, h.getSubscriptions());
|
||||
assertEquals(transports, h.getTransports());
|
||||
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());
|
||||
r.close();
|
||||
}
|
||||
@@ -158,19 +174,14 @@ public class BundleReadWriteTest extends TestCase {
|
||||
testWriteBundle();
|
||||
|
||||
RandomAccessFile f = new RandomAccessFile(bundle, "rw");
|
||||
f.seek(bundle.length() - 50);
|
||||
f.seek(bundle.length() - 150);
|
||||
byte b = f.readByte();
|
||||
f.seek(bundle.length() - 50);
|
||||
f.seek(bundle.length() - 150);
|
||||
f.writeByte(b + 1);
|
||||
f.close();
|
||||
|
||||
MessageParser messageParser = new MessageParser() {
|
||||
public Message parseMessage(byte[] body) throws FormatException,
|
||||
SignatureException {
|
||||
// FIXME: Really parse the message
|
||||
return message;
|
||||
}
|
||||
};
|
||||
MessageParser messageParser =
|
||||
new MessageParserImpl(keyParser, sig, digest, rf);
|
||||
Provider<HeaderBuilder> headerBuilderProvider =
|
||||
new Provider<HeaderBuilder>() {
|
||||
public HeaderBuilder get() {
|
||||
|
||||
Reference in New Issue
Block a user