Files
briar/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java
Torsten Grote 3dd3a18694 Add support for comments and reblogging to Blog Client
Comments and reblogs need to depend on the post they refer to.
Since message dependencies are limited to one group,
the post and also the comments need to be wrapped
when commented on or reblogged to another blog.

For this reason, in addition to comments, two new wrapping message types
are introduced. They retain all data of the original messages and allow
for reconstruction and signature verification.

This commit breaks backwards compatibility with old blog posts.
It removes the content type, title and parent ID from the post
message structure.
2016-08-29 13:37:20 -03:00

301 lines
12 KiB
Java

package org.briarproject.blogs;
import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.api.FormatException;
import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PublicKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageFactory;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.system.Clock;
import org.briarproject.system.SystemClock;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.io.IOException;
import java.security.GeneralSecurityException;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT;
import static org.briarproject.api.blogs.BlogConstants.KEY_WRAPPED_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
import static org.briarproject.api.blogs.MessageType.COMMENT;
import static org.briarproject.api.blogs.MessageType.POST;
import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
import static org.briarproject.api.blogs.MessageType.WRAPPED_POST;
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class BlogPostValidatorTest extends BriarTestCase {
private final Mockery context = new Mockery();
private final Blog blog;
private final Author author;
private final BdfDictionary authorDict;
private final ClientId clientId;
private final byte[] descriptor;
private final Group group;
private final Message message;
private final BlogPostValidator validator;
private final CryptoComponent cryptoComponent =
context.mock(CryptoComponent.class);
private final GroupFactory groupFactory = context.mock(GroupFactory.class);
private final MessageFactory messageFactory =
context.mock(MessageFactory.class);
private final BlogFactory blogFactory = context.mock(BlogFactory.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final Clock clock = new SystemClock();
private final String body = TestUtils.getRandomString(42);
private final String contentType = "text/plain";
public BlogPostValidatorTest() {
GroupId groupId = new GroupId(TestUtils.getRandomId());
clientId = BlogManagerImpl.CLIENT_ID;
descriptor = TestUtils.getRandomBytes(42);
group = new Group(groupId, clientId, descriptor);
AuthorId authorId =
new AuthorId(TestUtils.getRandomBytes(AuthorId.LENGTH));
byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
author = new Author(authorId, "Author", publicKey);
authorDict = BdfDictionary.of(
new BdfEntry(KEY_AUTHOR_ID, author.getId()),
new BdfEntry(KEY_AUTHOR_NAME, author.getName()),
new BdfEntry(KEY_PUBLIC_KEY, author.getPublicKey())
);
blog = new Blog(group, "Test Blog", "", author);
MessageId messageId = new MessageId(TestUtils.getRandomId());
long timestamp = System.currentTimeMillis();
byte[] raw = TestUtils.getRandomBytes(123);
message = new Message(messageId, group.getId(), timestamp, raw);
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
validator = new BlogPostValidator(cryptoComponent, groupFactory,
messageFactory, blogFactory, clientHelper, metadataEncoder,
clock);
context.assertIsSatisfied();
}
@Test
public void testValidateProperBlogPost()
throws IOException, GeneralSecurityException {
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList.of(POST.getInt(), body, sigBytes);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), body);
expectCrypto(signed, sigBytes, true);
final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary();
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
assertFalse(result.getBoolean(KEY_READ));
context.assertIsSatisfied();
}
@Test(expected = FormatException.class)
public void testValidateBlogPostWithoutAttachments()
throws IOException, GeneralSecurityException {
BdfList content = BdfList.of(null, contentType, null, body);
BdfList m = BdfList.of(POST.getInt(), content, null);
validator.validateMessage(message, group, m).getDictionary();
}
@Test(expected = FormatException.class)
public void testValidateBlogPostWithoutSignature()
throws IOException, GeneralSecurityException {
BdfList content = BdfList.of(null, contentType, null, body, null);
BdfList m = BdfList.of(POST.getInt(), content, null);
validator.validateMessage(message, group, m).getDictionary();
}
@Test(expected = InvalidMessageException.class)
public void testValidateBlogPostWithBadSignature()
throws IOException, GeneralSecurityException {
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList.of(POST.getInt(), body, sigBytes);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), body);
expectCrypto(signed, sigBytes, false);
validator.validateMessage(message, group, m).getDictionary();
}
@Test
public void testValidateProperBlogComment()
throws IOException, GeneralSecurityException {
// comment, parent_original_id, parent_id, signature
String comment = "This is a blog comment";
MessageId originalId = new MessageId(TestUtils.getRandomId());
MessageId currentId = new MessageId(TestUtils.getRandomId());
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m =
BdfList.of(COMMENT.getInt(), comment, originalId, currentId,
sigBytes);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), comment,
originalId, currentId);
expectCrypto(signed, sigBytes, true);
final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary();
assertEquals(comment, result.getString(KEY_COMMENT));
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
assertEquals(originalId.getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID));
assertEquals(currentId.getBytes(), result.getRaw(KEY_WRAPPED_MSG_ID));
assertFalse(result.getBoolean(KEY_READ));
context.assertIsSatisfied();
}
@Test
public void testValidateProperEmptyBlogComment()
throws IOException, GeneralSecurityException {
// comment, parent_original_id, signature, parent_current_id
MessageId originalId = new MessageId(TestUtils.getRandomId());
MessageId currentId = new MessageId(TestUtils.getRandomId());
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m =
BdfList.of(COMMENT.getInt(), null, originalId, currentId,
sigBytes);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), null,
originalId, currentId);
expectCrypto(signed, sigBytes, true);
final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary();
assertFalse(result.containsKey(KEY_COMMENT));
context.assertIsSatisfied();
}
@Test
public void testValidateProperWrappedPost()
throws IOException, GeneralSecurityException {
// group descriptor, timestamp, content, signature
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m =
BdfList.of(WRAPPED_POST.getInt(), descriptor,
message.getTimestamp(), body, sigBytes);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), body);
expectCrypto(signed, sigBytes, true);
final BdfList originalList = BdfList.of(POST.getInt(), body, sigBytes);
final byte[] originalBody = TestUtils.getRandomBytes(42);
context.checking(new Expectations() {{
oneOf(groupFactory).createGroup(clientId, descriptor);
will(returnValue(blog.getGroup()));
oneOf(clientHelper).toByteArray(originalList);
will(returnValue(originalBody));
oneOf(messageFactory)
.createMessage(group.getId(), message.getTimestamp(),
originalBody);
will(returnValue(message));
}});
final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary();
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
context.assertIsSatisfied();
}
@Test
public void testValidateProperWrappedComment()
throws IOException, GeneralSecurityException {
// group descriptor, timestamp, comment, parent_original_id, signature,
// parent_current_id
String comment = "This is another comment";
MessageId originalId = new MessageId(TestUtils.getRandomId());
MessageId oldId = new MessageId(TestUtils.getRandomId());
final byte[] sigBytes = TestUtils.getRandomBytes(42);
MessageId currentId = new MessageId(TestUtils.getRandomId());
BdfList m = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor,
message.getTimestamp(), comment, originalId, oldId, sigBytes,
currentId);
BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(),
comment, originalId, oldId);
expectCrypto(signed, sigBytes, true);
final BdfList originalList = BdfList.of(COMMENT.getInt(), comment,
originalId, oldId, sigBytes);
final byte[] originalBody = TestUtils.getRandomBytes(42);
context.checking(new Expectations() {{
oneOf(groupFactory).createGroup(clientId, descriptor);
will(returnValue(blog.getGroup()));
oneOf(clientHelper).toByteArray(originalList);
will(returnValue(originalBody));
oneOf(messageFactory)
.createMessage(group.getId(), message.getTimestamp(),
originalBody);
will(returnValue(message));
}});
final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary();
assertEquals(comment, result.getString(KEY_COMMENT));
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
assertEquals(
message.getId().getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID));
assertEquals(currentId.getBytes(), result.getRaw(KEY_WRAPPED_MSG_ID));
context.assertIsSatisfied();
}
private void expectCrypto(final BdfList signed, final byte[] sig,
final boolean pass) throws IOException, GeneralSecurityException {
final Signature signature = context.mock(Signature.class);
final KeyParser keyParser = context.mock(KeyParser.class);
final PublicKey publicKey = context.mock(PublicKey.class);
context.checking(new Expectations() {{
oneOf(blogFactory).parseBlog(group, "");
will(returnValue(blog));
oneOf(cryptoComponent).getSignatureKeyParser();
will(returnValue(keyParser));
oneOf(keyParser).parsePublicKey(blog.getAuthor().getPublicKey());
will(returnValue(publicKey));
oneOf(cryptoComponent).getSignature();
will(returnValue(signature));
oneOf(signature).initVerify(publicKey);
oneOf(clientHelper).toByteArray(signed);
will(returnValue(sig));
oneOf(signature).update(sig);
oneOf(signature).verify(sig);
will(returnValue(pass));
}});
}
}