mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 21:29:54 +01:00
Sign the message with the group's private key if the group is
restricted, and check the signature if it should be present.
This commit is contained in:
@@ -6,10 +6,21 @@ import java.security.PrivateKey;
|
|||||||
|
|
||||||
public interface MessageEncoder {
|
public interface MessageEncoder {
|
||||||
|
|
||||||
|
/** Encodes an anonymous to an unrestricted group. */
|
||||||
Message encodeMessage(MessageId parent, Group group, byte[] body)
|
Message encodeMessage(MessageId parent, Group group, byte[] body)
|
||||||
throws IOException;
|
throws IOException, GeneralSecurityException;
|
||||||
|
|
||||||
|
/** Encodes an anonymous message to a restricted group. */
|
||||||
|
Message encodeMessage(MessageId parent, Group group, PrivateKey groupKey,
|
||||||
|
byte[] body) throws IOException, GeneralSecurityException;
|
||||||
|
|
||||||
|
/** Encodes a pseudonymous message to an unrestricted group. */
|
||||||
Message encodeMessage(MessageId parent, Group group, Author author,
|
Message encodeMessage(MessageId parent, Group group, Author author,
|
||||||
PrivateKey privateKey, byte[] body) throws IOException,
|
PrivateKey authorKey, byte[] body) throws IOException,
|
||||||
GeneralSecurityException;
|
GeneralSecurityException;
|
||||||
|
|
||||||
|
/** Encode a pseudonymous message to a restricted group. */
|
||||||
|
Message encodeMessage(MessageId parent, Group group, PrivateKey groupKey,
|
||||||
|
Author author, PrivateKey authorKey, byte[] body)
|
||||||
|
throws IOException, GeneralSecurityException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,30 +34,31 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Message encodeMessage(MessageId parent, Group group, byte[] body)
|
public Message encodeMessage(MessageId parent, Group group, byte[] body)
|
||||||
throws IOException {
|
throws IOException, GeneralSecurityException {
|
||||||
long timestamp = System.currentTimeMillis();
|
return encodeMessage(parent, group, null, null, null, body);
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
}
|
||||||
Writer w = writerFactory.createWriter(out);
|
|
||||||
// Write the message
|
public Message encodeMessage(MessageId parent, Group group,
|
||||||
w.writeUserDefinedTag(Tags.MESSAGE);
|
PrivateKey groupKey, byte[] body) throws IOException,
|
||||||
parent.writeTo(w);
|
GeneralSecurityException {
|
||||||
group.writeTo(w);
|
return encodeMessage(parent, group, groupKey, null, null, body);
|
||||||
w.writeNull(); // No author
|
|
||||||
w.writeInt64(timestamp);
|
|
||||||
w.writeBytes(body);
|
|
||||||
w.writeNull(); // No author's signature
|
|
||||||
byte[] raw = out.toByteArray();
|
|
||||||
// The message ID is the hash of the entire message
|
|
||||||
messageDigest.reset();
|
|
||||||
messageDigest.update(raw);
|
|
||||||
MessageId id = new MessageId(messageDigest.digest());
|
|
||||||
return new MessageImpl(id, parent, group.getId(), AuthorId.NONE,
|
|
||||||
timestamp, raw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message encodeMessage(MessageId parent, Group group, Author author,
|
public Message encodeMessage(MessageId parent, Group group, Author author,
|
||||||
PrivateKey privateKey, byte[] body) throws IOException,
|
PrivateKey authorKey, byte[] body) throws IOException,
|
||||||
GeneralSecurityException {
|
GeneralSecurityException {
|
||||||
|
return encodeMessage(parent, group, null, author, authorKey, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message encodeMessage(MessageId parent, Group group,
|
||||||
|
PrivateKey groupKey, Author author, PrivateKey authorKey,
|
||||||
|
byte[] body) throws IOException, GeneralSecurityException {
|
||||||
|
|
||||||
|
if((author == null) != (authorKey == null))
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if((group.getPublicKey() == null) != (groupKey == null))
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
Writer w = writerFactory.createWriter(out);
|
Writer w = writerFactory.createWriter(out);
|
||||||
@@ -65,29 +66,32 @@ class MessageEncoderImpl implements MessageEncoder {
|
|||||||
w.writeUserDefinedTag(Tags.MESSAGE);
|
w.writeUserDefinedTag(Tags.MESSAGE);
|
||||||
parent.writeTo(w);
|
parent.writeTo(w);
|
||||||
group.writeTo(w);
|
group.writeTo(w);
|
||||||
author.writeTo(w);
|
if(author == null) w.writeNull();
|
||||||
|
else author.writeTo(w);
|
||||||
w.writeInt64(timestamp);
|
w.writeInt64(timestamp);
|
||||||
w.writeBytes(body);
|
w.writeBytes(body);
|
||||||
// Sign the message
|
// Sign the message with the author's private key, if there is one
|
||||||
byte[] signable = out.toByteArray();
|
if(authorKey == null) {
|
||||||
signature.initSign(privateKey);
|
w.writeNull();
|
||||||
signature.update(signable);
|
} else {
|
||||||
byte[] sig = signature.sign();
|
signature.initSign(authorKey);
|
||||||
signable = null;
|
signature.update(out.toByteArray());
|
||||||
// Write the signature
|
w.writeBytes(signature.sign());
|
||||||
w.writeBytes(sig);
|
}
|
||||||
|
// Sign the message with the group's private key, if there is one
|
||||||
|
if(groupKey == null) {
|
||||||
|
w.writeNull();
|
||||||
|
} else {
|
||||||
|
signature.initSign(groupKey);
|
||||||
|
signature.update(out.toByteArray());
|
||||||
|
w.writeBytes(signature.sign());
|
||||||
|
}
|
||||||
|
// Hash the message, including the signatures, to get the message ID
|
||||||
byte[] raw = out.toByteArray();
|
byte[] raw = out.toByteArray();
|
||||||
// The message ID is the hash of the entire message
|
|
||||||
messageDigest.reset();
|
messageDigest.reset();
|
||||||
messageDigest.update(raw);
|
messageDigest.update(raw);
|
||||||
MessageId id = new MessageId(messageDigest.digest());
|
MessageId id = new MessageId(messageDigest.digest());
|
||||||
// The author ID is the hash of the author object
|
AuthorId authorId = author == null ? AuthorId.NONE : author.getId();
|
||||||
out.reset();
|
|
||||||
w = writerFactory.createWriter(out);
|
|
||||||
author.writeTo(w);
|
|
||||||
messageDigest.reset();
|
|
||||||
messageDigest.update(out.toByteArray());
|
|
||||||
AuthorId authorId = new AuthorId(messageDigest.digest());
|
|
||||||
return new MessageImpl(id, parent, group.getId(), authorId, timestamp,
|
return new MessageImpl(id, parent, group.getId(), authorId, timestamp,
|
||||||
raw);
|
raw);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.security.GeneralSecurityException;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.SignatureException;
|
|
||||||
|
|
||||||
import net.sf.briar.api.crypto.CryptoComponent;
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.crypto.KeyParser;
|
import net.sf.briar.api.crypto.KeyParser;
|
||||||
@@ -64,12 +63,18 @@ class MessageReader implements ObjectReader<Message> {
|
|||||||
if(timestamp < 0L) throw new FormatException();
|
if(timestamp < 0L) throw new FormatException();
|
||||||
// Skip the message body
|
// Skip the message body
|
||||||
r.readBytes();
|
r.readBytes();
|
||||||
// Record the length of the signed data
|
// Record the length of the data covered by the author's signature
|
||||||
int messageLength = (int) counting.getCount();
|
int signedByAuthor = (int) counting.getCount();
|
||||||
// Read the author's signature, if there is one
|
// Read the author's signature, if there is one
|
||||||
byte[] authorSig = null;
|
byte[] authorSig = null;
|
||||||
if(author == null) r.readNull();
|
if(author == null) r.readNull();
|
||||||
else authorSig = r.readBytes();
|
else authorSig = r.readBytes();
|
||||||
|
// Record the length of the data covered by the group's signature
|
||||||
|
int signedByGroup = (int) counting.getCount();
|
||||||
|
// Read the group's signature, if there is one
|
||||||
|
byte[] groupSig = null;
|
||||||
|
if(group.getPublicKey() == null) r.readNull();
|
||||||
|
else groupSig = r.readBytes();
|
||||||
// That's all, folks
|
// That's all, folks
|
||||||
r.removeConsumer(counting);
|
r.removeConsumer(counting);
|
||||||
r.removeConsumer(copying);
|
r.removeConsumer(copying);
|
||||||
@@ -77,16 +82,26 @@ class MessageReader implements ObjectReader<Message> {
|
|||||||
// Verify the author's signature, if there is one
|
// Verify the author's signature, if there is one
|
||||||
if(author != null) {
|
if(author != null) {
|
||||||
try {
|
try {
|
||||||
PublicKey publicKey =
|
PublicKey k = keyParser.parsePublicKey(author.getPublicKey());
|
||||||
keyParser.parsePublicKey(author.getPublicKey());
|
signature.initVerify(k);
|
||||||
signature.initVerify(publicKey);
|
signature.update(raw, 0, signedByAuthor);
|
||||||
signature.update(raw, 0, messageLength);
|
if(!signature.verify(authorSig)) throw new FormatException();
|
||||||
if(!signature.verify(authorSig)) throw new SignatureException();
|
|
||||||
} catch(GeneralSecurityException e) {
|
} catch(GeneralSecurityException e) {
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Hash the message, including the signature, to get the message ID
|
// Verify the group's signature, if there is one
|
||||||
|
if(group.getPublicKey() != null) {
|
||||||
|
try {
|
||||||
|
PublicKey k = keyParser.parsePublicKey(group.getPublicKey());
|
||||||
|
signature.initVerify(k);
|
||||||
|
signature.update(raw, 0, signedByGroup);
|
||||||
|
if(!signature.verify(groupSig)) throw new FormatException();
|
||||||
|
} catch(GeneralSecurityException e) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Hash the message, including the signatures, to get the message ID
|
||||||
messageDigest.reset();
|
messageDigest.reset();
|
||||||
messageDigest.update(raw);
|
messageDigest.update(raw);
|
||||||
MessageId id = new MessageId(messageDigest.digest());
|
MessageId id = new MessageId(messageDigest.digest());
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import java.io.File;
|
|||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
@@ -50,8 +52,6 @@ public class FileReadWriteTest extends TestCase {
|
|||||||
private final File file = new File(testDir, "foo");
|
private final File file = new File(testDir, "foo");
|
||||||
|
|
||||||
private final BatchId ack = new BatchId(TestUtils.getRandomId());
|
private final BatchId ack = new BatchId(TestUtils.getRandomId());
|
||||||
private final String authorName = "Foo Bar";
|
|
||||||
private final String messageBody = "This is the message body! Wooooooo!";
|
|
||||||
private final long start = System.currentTimeMillis();
|
private final long start = System.currentTimeMillis();
|
||||||
|
|
||||||
private final ReaderFactory readerFactory;
|
private final ReaderFactory readerFactory;
|
||||||
@@ -59,8 +59,10 @@ public class FileReadWriteTest extends TestCase {
|
|||||||
private final PacketWriterFactory packetWriterFactory;
|
private final PacketWriterFactory packetWriterFactory;
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final Author author;
|
private final Author author;
|
||||||
private final Group group;
|
private final Group group, group1;
|
||||||
private final Message message;
|
private final Message message, message1, message2, message3;
|
||||||
|
private final String authorName = "Alice";
|
||||||
|
private final String messageBody = "Hello world";
|
||||||
|
|
||||||
public FileReadWriteTest() throws Exception {
|
public FileReadWriteTest() throws Exception {
|
||||||
super();
|
super();
|
||||||
@@ -73,18 +75,28 @@ public class FileReadWriteTest extends TestCase {
|
|||||||
crypto = i.getInstance(CryptoComponent.class);
|
crypto = i.getInstance(CryptoComponent.class);
|
||||||
assertEquals(crypto.getMessageDigest().getDigestLength(),
|
assertEquals(crypto.getMessageDigest().getDigestLength(),
|
||||||
UniqueId.LENGTH);
|
UniqueId.LENGTH);
|
||||||
// Create a group
|
// Create two groups: one restricted, one unrestricted
|
||||||
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
|
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
|
||||||
group = groupFactory.createGroup("Group name", null);
|
group = groupFactory.createGroup("Unrestricted group", null);
|
||||||
|
KeyPair groupKeyPair = crypto.generateKeyPair();
|
||||||
|
group1 = groupFactory.createGroup("Restricted group",
|
||||||
|
groupKeyPair.getPublic().getEncoded());
|
||||||
// Create an author
|
// Create an author
|
||||||
AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
|
AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
|
||||||
KeyPair keyPair = crypto.generateKeyPair();
|
KeyPair authorKeyPair = crypto.generateKeyPair();
|
||||||
author = authorFactory.createAuthor(authorName,
|
author = authorFactory.createAuthor(authorName,
|
||||||
keyPair.getPublic().getEncoded());
|
authorKeyPair.getPublic().getEncoded());
|
||||||
// Create and encode a test message, signed by the author
|
// Create two messages to each group: one anonymous, one pseudonymous
|
||||||
MessageEncoder messageEncoder = i.getInstance(MessageEncoder.class);
|
MessageEncoder messageEncoder = i.getInstance(MessageEncoder.class);
|
||||||
message = messageEncoder.encodeMessage(MessageId.NONE, group, author,
|
message = messageEncoder.encodeMessage(MessageId.NONE, group,
|
||||||
keyPair.getPrivate(), messageBody.getBytes("UTF-8"));
|
messageBody.getBytes("UTF-8"));
|
||||||
|
message1 = messageEncoder.encodeMessage(MessageId.NONE, group1,
|
||||||
|
groupKeyPair.getPrivate(), messageBody.getBytes("UTF-8"));
|
||||||
|
message2 = messageEncoder.encodeMessage(MessageId.NONE, group, author,
|
||||||
|
authorKeyPair.getPrivate(), messageBody.getBytes("UTF-8"));
|
||||||
|
message3 = messageEncoder.encodeMessage(MessageId.NONE, group1,
|
||||||
|
groupKeyPair.getPrivate(), author, authorKeyPair.getPrivate(),
|
||||||
|
messageBody.getBytes("UTF-8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -102,11 +114,17 @@ public class FileReadWriteTest extends TestCase {
|
|||||||
|
|
||||||
BatchWriter b = packetWriterFactory.createBatchWriter(out);
|
BatchWriter b = packetWriterFactory.createBatchWriter(out);
|
||||||
assertTrue(b.writeMessage(message.getBytes()));
|
assertTrue(b.writeMessage(message.getBytes()));
|
||||||
|
assertTrue(b.writeMessage(message1.getBytes()));
|
||||||
|
assertTrue(b.writeMessage(message2.getBytes()));
|
||||||
|
assertTrue(b.writeMessage(message3.getBytes()));
|
||||||
b.finish();
|
b.finish();
|
||||||
|
|
||||||
SubscriptionWriter s =
|
SubscriptionWriter s =
|
||||||
packetWriterFactory.createSubscriptionWriter(out);
|
packetWriterFactory.createSubscriptionWriter(out);
|
||||||
s.writeSubscriptions(Collections.singleton(group));
|
Collection<Group> subs = new ArrayList<Group>();
|
||||||
|
subs.add(group);
|
||||||
|
subs.add(group1);
|
||||||
|
s.writeSubscriptions(subs);
|
||||||
|
|
||||||
TransportWriter t = packetWriterFactory.createTransportWriter(out);
|
TransportWriter t = packetWriterFactory.createTransportWriter(out);
|
||||||
t.writeTransports(Collections.singletonMap("foo", "bar"));
|
t.writeTransports(Collections.singletonMap("foo", "bar"));
|
||||||
@@ -151,22 +169,23 @@ public class FileReadWriteTest extends TestCase {
|
|||||||
// Read the batch
|
// Read the batch
|
||||||
assertTrue(reader.hasUserDefined(Tags.BATCH));
|
assertTrue(reader.hasUserDefined(Tags.BATCH));
|
||||||
Batch b = reader.readUserDefined(Tags.BATCH, Batch.class);
|
Batch b = reader.readUserDefined(Tags.BATCH, Batch.class);
|
||||||
Iterator<Message> i = b.getMessages().iterator();
|
Collection<Message> messages = b.getMessages();
|
||||||
assertTrue(i.hasNext());
|
assertEquals(4, messages.size());
|
||||||
Message m = i.next();
|
Iterator<Message> i = messages.iterator();
|
||||||
assertEquals(message.getId(), m.getId());
|
checkMessageEquality(message, i.next());
|
||||||
assertEquals(message.getParent(), m.getParent());
|
checkMessageEquality(message1, i.next());
|
||||||
assertEquals(message.getGroup(), m.getGroup());
|
checkMessageEquality(message2, i.next());
|
||||||
assertEquals(message.getAuthor(), m.getAuthor());
|
checkMessageEquality(message3, i.next());
|
||||||
assertEquals(message.getTimestamp(), m.getTimestamp());
|
|
||||||
assertTrue(Arrays.equals(message.getBytes(), m.getBytes()));
|
|
||||||
assertFalse(i.hasNext());
|
|
||||||
|
|
||||||
// Read the subscriptions update
|
// Read the subscriptions update
|
||||||
assertTrue(reader.hasUserDefined(Tags.SUBSCRIPTIONS));
|
assertTrue(reader.hasUserDefined(Tags.SUBSCRIPTIONS));
|
||||||
Subscriptions s = reader.readUserDefined(Tags.SUBSCRIPTIONS,
|
Subscriptions s = reader.readUserDefined(Tags.SUBSCRIPTIONS,
|
||||||
Subscriptions.class);
|
Subscriptions.class);
|
||||||
assertEquals(Collections.singletonList(group), s.getSubscriptions());
|
Collection<Group> subs = s.getSubscriptions();
|
||||||
|
assertEquals(2, subs.size());
|
||||||
|
Iterator<Group> i1 = subs.iterator();
|
||||||
|
checkGroupEquality(group, i1.next());
|
||||||
|
checkGroupEquality(group1, i1.next());
|
||||||
assertTrue(s.getTimestamp() > start);
|
assertTrue(s.getTimestamp() > start);
|
||||||
assertTrue(s.getTimestamp() <= System.currentTimeMillis());
|
assertTrue(s.getTimestamp() <= System.currentTimeMillis());
|
||||||
|
|
||||||
@@ -185,4 +204,22 @@ public class FileReadWriteTest extends TestCase {
|
|||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
TestUtils.deleteTestDirectory(testDir);
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkMessageEquality(Message m1, Message m2) {
|
||||||
|
assertEquals(m1.getId(), m2.getId());
|
||||||
|
assertEquals(m1.getParent(), m2.getParent());
|
||||||
|
assertEquals(m1.getGroup(), m2.getGroup());
|
||||||
|
assertEquals(m1.getAuthor(), m2.getAuthor());
|
||||||
|
assertEquals(m1.getTimestamp(), m2.getTimestamp());
|
||||||
|
assertTrue(Arrays.equals(m1.getBytes(), m2.getBytes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkGroupEquality(Group g1, Group g2) {
|
||||||
|
assertEquals(g1.getId(), g2.getId());
|
||||||
|
assertEquals(g1.getName(), g2.getName());
|
||||||
|
byte[] k1 = g1.getPublicKey();
|
||||||
|
byte[] k2 = g2.getPublicKey();
|
||||||
|
if(k1 == null) assertNull(k2);
|
||||||
|
else assertTrue(Arrays.equals(k1, k2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user