Validate New Messages for Reblogging and Comments of Blog Posts

Also includes unit tests for the new message types.

Closes #591
This commit is contained in:
Torsten Grote
2016-08-09 19:39:57 -03:00
parent 84d4bf2205
commit caee7fe61b
7 changed files with 433 additions and 70 deletions

View File

@@ -29,16 +29,19 @@ public interface BlogConstants {
String BLOG_PUBLIC_KEY = "blogPublicKey"; String BLOG_PUBLIC_KEY = "blogPublicKey";
// Metadata keys // Metadata keys
String KEY_TYPE = "type";
String KEY_DESCRIPTION = "description"; String KEY_DESCRIPTION = "description";
String KEY_TITLE = "title"; String KEY_TITLE = "title";
String KEY_TIMESTAMP = "timestamp"; String KEY_TIMESTAMP = "timestamp";
String KEY_TIME_RECEIVED = "timeReceived"; String KEY_TIME_RECEIVED = "timeReceived";
String KEY_PARENT = "parent";
String KEY_AUTHOR_ID = "id"; String KEY_AUTHOR_ID = "id";
String KEY_AUTHOR_NAME = "name"; String KEY_AUTHOR_NAME = "name";
String KEY_PUBLIC_KEY = "publicKey"; String KEY_PUBLIC_KEY = "publicKey";
String KEY_AUTHOR = "author"; String KEY_AUTHOR = "author";
String KEY_CONTENT_TYPE = "contentType"; String KEY_CONTENT_TYPE = "contentType";
String KEY_READ = "read"; String KEY_READ = "read";
String KEY_COMMENT = "comment";
String KEY_ORIGINAL_MSG_ID = "originalMessageId";
String KEY_CURRENT_MSG_ID = "currentMessageId";
} }

View File

@@ -14,10 +14,10 @@ public class BlogPostHeader extends PostHeader {
private final long timeReceived; private final long timeReceived;
public BlogPostHeader(@Nullable String title, @NotNull MessageId id, public BlogPostHeader(@Nullable String title, @NotNull MessageId id,
@Nullable MessageId parentId, long timestamp, long timeReceived, long timestamp, long timeReceived, @NotNull Author author,
@NotNull Author author, @NotNull Status authorStatus, @NotNull Status authorStatus, @NotNull String contentType,
@NotNull String contentType, boolean read) { boolean read) {
super(id, parentId, timestamp, author, authorStatus, contentType, read); super(id, null, timestamp, author, authorStatus, contentType, read);
this.title = title; this.title = title;
this.timeReceived = timeReceived; this.timeReceived = timeReceived;

View File

@@ -0,0 +1,33 @@
package org.briarproject.api.blogs;
public enum MessageType {
POST(0),
COMMENT(1),
WRAPPED_POST(2),
WRAPPED_COMMENT(3);
int value;
MessageType(int value) {
this.value = value;
}
public static MessageType valueOf(int value) {
switch (value) {
case 0:
return POST;
case 1:
return COMMENT;
case 2:
return WRAPPED_POST;
case 3:
return WRAPPED_COMMENT;
default:
throw new IllegalArgumentException();
}
}
public int getInt() {
return value;
}
}

View File

@@ -52,7 +52,6 @@ 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_AUTHOR_NAME;
import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE; import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION; import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION;
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT;
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY; 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.BlogConstants.KEY_READ;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
@@ -240,7 +239,6 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
meta = new BdfDictionary(); meta = new BdfDictionary();
if (p.getTitle() != null) meta.put(KEY_TITLE, p.getTitle()); if (p.getTitle() != null) meta.put(KEY_TITLE, p.getTitle());
meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp()); meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
if (p.getParent() != null) meta.put(KEY_PARENT, p.getParent());
Author a = p.getAuthor(); Author a = p.getAuthor();
BdfDictionary authorMeta = new BdfDictionary(); BdfDictionary authorMeta = new BdfDictionary();
@@ -409,9 +407,6 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
String title = meta.getOptionalString(KEY_TITLE); String title = meta.getOptionalString(KEY_TITLE);
long timestamp = meta.getLong(KEY_TIMESTAMP); long timestamp = meta.getLong(KEY_TIMESTAMP);
long timeReceived = meta.getLong(KEY_TIME_RECEIVED, timestamp); long timeReceived = meta.getLong(KEY_TIME_RECEIVED, timestamp);
MessageId parentId = null;
if (meta.containsKey(KEY_PARENT))
parentId = new MessageId(meta.getRaw(KEY_PARENT));
BdfDictionary d = meta.getDictionary(KEY_AUTHOR); BdfDictionary d = meta.getDictionary(KEY_AUTHOR);
AuthorId authorId = new AuthorId(d.getRaw(KEY_AUTHOR_ID)); AuthorId authorId = new AuthorId(d.getRaw(KEY_AUTHOR_ID));
@@ -427,7 +422,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
String contentType = meta.getString(KEY_CONTENT_TYPE); String contentType = meta.getString(KEY_CONTENT_TYPE);
boolean read = meta.getBoolean(KEY_READ); boolean read = meta.getBoolean(KEY_READ);
return new BlogPostHeader(title, id, parentId, timestamp, timeReceived, return new BlogPostHeader(title, id, timestamp, timeReceived, author,
author, authorStatus, contentType, read); authorStatus, contentType, read);
} }
} }

View File

@@ -4,6 +4,7 @@ import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId; import org.briarproject.api.UniqueId;
import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogFactory; import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.blogs.MessageType;
import org.briarproject.api.clients.BdfMessageContext; import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoComponent;
@@ -16,8 +17,10 @@ import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.InvalidMessageException; import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageFactory;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.clients.BdfMessageValidator; import org.briarproject.clients.BdfMessageValidator;
@@ -29,13 +32,16 @@ import java.util.Collections;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR; 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_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME; 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_CONTENT_TYPE; import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT; import static org.briarproject.api.blogs.BlogConstants.KEY_CURRENT_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_PUBLIC_KEY;
import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE; import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE;
import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH; import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH;
@@ -44,14 +50,19 @@ import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH
class BlogPostValidator extends BdfMessageValidator { class BlogPostValidator extends BdfMessageValidator {
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final GroupFactory groupFactory;
private final MessageFactory messageFactory;
private final BlogFactory blogFactory; private final BlogFactory blogFactory;
BlogPostValidator(CryptoComponent crypto, BlogFactory blogFactory, BlogPostValidator(CryptoComponent crypto, GroupFactory groupFactory,
MessageFactory messageFactory, BlogFactory blogFactory,
ClientHelper clientHelper, MetadataEncoder metadataEncoder, ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock) { Clock clock) {
super(clientHelper, metadataEncoder, clock); super(clientHelper, metadataEncoder, clock);
this.crypto = crypto; this.crypto = crypto;
this.groupFactory = groupFactory;
this.messageFactory = messageFactory;
this.blogFactory = blogFactory; this.blogFactory = blogFactory;
} }
@@ -59,14 +70,51 @@ class BlogPostValidator extends BdfMessageValidator {
protected BdfMessageContext validateMessage(Message m, Group g, protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException { BdfList body) throws InvalidMessageException, FormatException {
BdfMessageContext c;
// TODO Remove! For Temporary Backwards Compatibility only!
if (body.get(0) instanceof BdfList) {
c = validatePost(m, g, body);
addMessageMetadata(c, m.getTimestamp());
return c;
}
int type = body.getLong(0).intValue();
body.removeElementAt(0);
switch (MessageType.valueOf(type)) {
case POST:
c = validatePost(m, g, body);
addMessageMetadata(c, m.getTimestamp());
break;
case COMMENT:
c = validateComment(m, g, body);
addMessageMetadata(c, m.getTimestamp());
break;
case WRAPPED_POST:
c = validateWrappedPost(m, g, body);
break;
case WRAPPED_COMMENT:
c = validateWrappedComment(m, g, body);
break;
default:
throw new InvalidMessageException("Unknown Message Type");
}
c.getDictionary().put(KEY_TYPE, type);
return c;
}
private BdfMessageContext validatePost(Message m, Group g, BdfList body)
throws InvalidMessageException, FormatException {
// Content, Signature // Content, Signature
checkSize(body, 2); checkSize(body, 2);
BdfList content = body.getList(0); BdfList content = body.getList(0);
// Content: Parent ID, content type, title (optional), post body, // Content: content type, title (optional), post body,
// attachments (optional) // attachments (optional)
checkSize(content, 5); checkSize(content, 5);
// Parent ID is optional // Parent ID is optional
// TODO remove when breaking backwards compatibility
byte[] parent = content.getOptionalRaw(0); byte[] parent = content.getOptionalRaw(0);
checkLength(parent, UniqueId.LENGTH); checkLength(parent, UniqueId.LENGTH);
// Content type // Content type
@@ -81,23 +129,166 @@ class BlogPostValidator extends BdfMessageValidator {
byte[] postBody = content.getRaw(3); byte[] postBody = content.getRaw(3);
checkLength(postBody, 0, MAX_BLOG_POST_BODY_LENGTH); checkLength(postBody, 0, MAX_BLOG_POST_BODY_LENGTH);
// Attachments // Attachments
BdfDictionary attachments = content.getOptionalDictionary(4); content.getOptionalDictionary(4);
// TODO handle attachments somehow
// Verify Signature
byte[] sig = body.getRaw(1);
checkLength(sig, 1, MAX_SIGNATURE_LENGTH);
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), content);
Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
Author a = b.getAuthor();
verifySignature(sig, a.getPublicKey(), signed);
// Return the metadata and dependencies
BdfDictionary meta = new BdfDictionary();
if (title != null) meta.put(KEY_TITLE, title);
meta.put(KEY_AUTHOR, authorToBdfDictionary(a));
meta.put(KEY_CONTENT_TYPE, contentType);
return new BdfMessageContext(meta, null);
}
private BdfMessageContext validateComment(Message m, Group g, BdfList body)
throws InvalidMessageException, FormatException {
// comment, parent_original_id, signature, parent_current_id
checkSize(body, 4);
// Comment
String comment = body.getOptionalString(0);
checkLength(comment, 0, MAX_BLOG_POST_BODY_LENGTH);
// parent_original_id
// The ID of a post or comment in this group or another group
byte[] originalIdBytes = body.getRaw(1);
checkLength(originalIdBytes, MessageId.LENGTH);
MessageId originalId = new MessageId(originalIdBytes);
// Signature // Signature
byte[] sig = body.getRaw(1); byte[] sig = body.getRaw(2);
checkLength(sig, 0, MAX_SIGNATURE_LENGTH); checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
// Verify the signature BdfList signed =
Author a; BdfList.of(g.getId(), m.getTimestamp(), comment, originalId);
Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
Author a = b.getAuthor();
verifySignature(sig, a.getPublicKey(), signed);
// parent_current_id
// The ID of a post, comment, wrapped post or wrapped comment in this
// group, which had the ID parent_original_id in the group
// where it was originally posted
byte[] currentIdBytes = body.getRaw(3);
checkLength(currentIdBytes, MessageId.LENGTH);
MessageId currentId = new MessageId(currentIdBytes);
// Return the metadata and dependencies
BdfDictionary meta = new BdfDictionary();
if (comment != null) meta.put(KEY_COMMENT, comment);
meta.put(KEY_ORIGINAL_MSG_ID, originalId);
meta.put(KEY_CURRENT_MSG_ID, currentId);
meta.put(KEY_AUTHOR, authorToBdfDictionary(a));
Collection<MessageId> dependencies = Collections.singleton(currentId);
return new BdfMessageContext(meta, dependencies);
}
private BdfMessageContext validateWrappedPost(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException {
// group descriptor, timestamp, content, signature
checkSize(body, 4);
// Group Descriptor
byte[] descriptor = body.getRaw(0);
// Timestamp of Wrapped Post
long wTimestamp = body.getLong(1);
// Content of Wrapped Post
BdfList content = body.getList(2);
// Signature of Wrapped Post
byte[] signature = body.getRaw(3);
checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
// Get and Validate the Wrapped Message
Group wGroup = groupFactory
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
BdfList wBodyList = BdfList.of(content, signature);
byte[] wBody = clientHelper.toByteArray(wBodyList);
Message wMessage =
messageFactory.createMessage(wGroup.getId(), wTimestamp, wBody);
BdfMessageContext c = validatePost(wMessage, wGroup, wBodyList);
// Return the metadata and dependencies
BdfDictionary meta = new BdfDictionary();
meta.put(KEY_TIMESTAMP, wTimestamp);
meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR));
meta.put(KEY_CONTENT_TYPE,
c.getDictionary().getString(KEY_CONTENT_TYPE));
return new BdfMessageContext(meta, null);
}
private BdfMessageContext validateWrappedComment(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException {
// group descriptor, timestamp, comment, parent_original_id, signature,
// parent_current_id
checkSize(body, 6);
// Group Descriptor
byte[] descriptor = body.getRaw(0);
// Timestamp of Wrapped Comment
long wTimestamp = body.getLong(1);
// Body of Wrapped Comment
String comment = body.getOptionalString(2);
// parent_original_id
// Taken from the original comment
byte[] originalIdBytes = body.getRaw(3);
checkLength(originalIdBytes, MessageId.LENGTH);
MessageId originalId = new MessageId(originalIdBytes);
// signature
// Taken from the original comment
byte[] signature = body.getRaw(4);
checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
// parent_current_id
// The ID of a post, comment, wrapped post or wrapped comment in this
// group, which had the ID parent_original_id in the group
// where it was originally posted
byte[] currentIdBytes = body.getRaw(5);
checkLength(currentIdBytes, MessageId.LENGTH);
MessageId currentId = new MessageId(currentIdBytes);
// Get and Validate the Wrapped Comment
Group wGroup = groupFactory
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
BdfList wBodyList = BdfList.of(comment, originalId, signature,
currentId);
byte[] wBody = clientHelper.toByteArray(wBodyList);
Message wMessage =
messageFactory.createMessage(wGroup.getId(), wTimestamp, wBody);
BdfMessageContext c = validateComment(wMessage, wGroup, wBodyList);
// Return the metadata and dependencies
Collection<MessageId> dependencies = Collections.singleton(currentId);
BdfDictionary meta = new BdfDictionary();
meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId());
meta.put(KEY_CURRENT_MSG_ID, currentId);
meta.put(KEY_TIMESTAMP, wTimestamp);
if (comment != null) meta.put(KEY_COMMENT, comment);
meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR));
return new BdfMessageContext(meta, dependencies);
}
private void verifySignature(byte[] sig, byte[] publicKey, BdfList signed)
throws InvalidMessageException {
try { try {
// Get the blog author
Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
a = b.getAuthor();
// Parse the public key // Parse the public key
KeyParser keyParser = crypto.getSignatureKeyParser(); KeyParser keyParser = crypto.getSignatureKeyParser();
PublicKey key = keyParser.parsePublicKey(a.getPublicKey()); PublicKey key = keyParser.parsePublicKey(publicKey);
// Serialise the data to be signed
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), content);
// Verify the signature // Verify the signature
Signature signature = crypto.getSignature(); Signature signature = crypto.getSignature();
signature.initVerify(key); signature.initVerify(key);
@@ -107,26 +298,23 @@ class BlogPostValidator extends BdfMessageValidator {
} }
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new InvalidMessageException("Invalid public key"); throw new InvalidMessageException("Invalid public key");
} catch (FormatException e) {
throw new InvalidMessageException(e);
} }
}
// Return the metadata and dependencies static BdfDictionary authorToBdfDictionary(Author a) {
BdfDictionary meta = new BdfDictionary(); return BdfDictionary.of(
Collection<MessageId> dependencies = null;
if (title != null) meta.put(KEY_TITLE, title);
BdfDictionary author = BdfDictionary.of(
new BdfEntry(KEY_AUTHOR_ID, a.getId()), new BdfEntry(KEY_AUTHOR_ID, a.getId()),
new BdfEntry(KEY_AUTHOR_NAME, a.getName()), new BdfEntry(KEY_AUTHOR_NAME, a.getName()),
new BdfEntry(KEY_PUBLIC_KEY, a.getPublicKey()) new BdfEntry(KEY_PUBLIC_KEY, a.getPublicKey())
); );
meta.put(KEY_AUTHOR, author);
meta.put(KEY_TIMESTAMP, m.getTimestamp());
meta.put(KEY_TIME_RECEIVED, clock.currentTimeMillis());
if (parent != null) {
meta.put(KEY_PARENT, parent);
dependencies = Collections.singletonList(new MessageId(parent));
}
meta.put(KEY_CONTENT_TYPE, contentType);
meta.put(KEY_READ, false);
return new BdfMessageContext(meta, dependencies);
} }
private void addMessageMetadata(BdfMessageContext c, long time) {
c.getDictionary().put(KEY_TIMESTAMP, time);
c.getDictionary().put(KEY_TIME_RECEIVED, clock.currentTimeMillis());
c.getDictionary().put(KEY_READ, false);
}
} }

View File

@@ -11,6 +11,7 @@ import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sync.GroupFactory; import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.MessageFactory;
import org.briarproject.api.sync.ValidationManager; import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
@@ -64,11 +65,13 @@ public class BlogsModule {
@Singleton @Singleton
BlogPostValidator provideBlogPostValidator( BlogPostValidator provideBlogPostValidator(
ValidationManager validationManager, CryptoComponent crypto, ValidationManager validationManager, CryptoComponent crypto,
GroupFactory groupFactory, MessageFactory messageFactory,
BlogFactory blogFactory, ClientHelper clientHelper, BlogFactory blogFactory, ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) { MetadataEncoder metadataEncoder, Clock clock) {
BlogPostValidator validator = new BlogPostValidator(crypto, BlogPostValidator validator = new BlogPostValidator(crypto,
blogFactory, clientHelper, metadataEncoder, clock); groupFactory, messageFactory, blogFactory, clientHelper,
metadataEncoder, clock);
validationManager.registerMessageValidator(CLIENT_ID, validator); validationManager.registerMessageValidator(CLIENT_ID, validator);
return validator; return validator;

View File

@@ -18,9 +18,11 @@ import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.InvalidMessageException; import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageFactory;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.system.SystemClock; import org.briarproject.system.SystemClock;
@@ -34,10 +36,17 @@ import java.security.GeneralSecurityException;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR; 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_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME; 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_CONTENT_TYPE; import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
import static org.briarproject.api.blogs.BlogConstants.KEY_CURRENT_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_PUBLIC_KEY;
import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
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.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@@ -47,11 +56,17 @@ public class BlogPostValidatorTest extends BriarTestCase {
private final Mockery context = new Mockery(); private final Mockery context = new Mockery();
private final Blog blog; private final Blog blog;
private final Author author; private final Author author;
private final BdfDictionary authorDict;
private final ClientId clientId;
private final byte[] descriptor;
private final Group group; private final Group group;
private final Message message; private final Message message;
private final BlogPostValidator validator; private final BlogPostValidator validator;
private final CryptoComponent cryptoComponent = private final CryptoComponent cryptoComponent =
context.mock(CryptoComponent.class); 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 BlogFactory blogFactory = context.mock(BlogFactory.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class); private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final Clock clock = new SystemClock(); private final Clock clock = new SystemClock();
@@ -61,12 +76,18 @@ public class BlogPostValidatorTest extends BriarTestCase {
public BlogPostValidatorTest() { public BlogPostValidatorTest() {
GroupId groupId = new GroupId(TestUtils.getRandomId()); GroupId groupId = new GroupId(TestUtils.getRandomId());
ClientId clientId = new ClientId(TestUtils.getRandomId()); clientId = BlogManagerImpl.CLIENT_ID;
byte[] descriptor = TestUtils.getRandomBytes(12); descriptor = TestUtils.getRandomBytes(42);
group = new Group(groupId, clientId, descriptor); group = new Group(groupId, clientId, descriptor);
AuthorId authorId = new AuthorId(TestUtils.getRandomBytes(AuthorId.LENGTH)); AuthorId authorId =
new AuthorId(TestUtils.getRandomBytes(AuthorId.LENGTH));
byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH); byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
author = new Author(authorId, "Author", publicKey); 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); blog = new Blog(group, "Test Blog", "", author);
MessageId messageId = new MessageId(TestUtils.getRandomId()); MessageId messageId = new MessageId(TestUtils.getRandomId());
@@ -75,29 +96,27 @@ public class BlogPostValidatorTest extends BriarTestCase {
message = new Message(messageId, group.getId(), timestamp, raw); message = new Message(messageId, group.getId(), timestamp, raw);
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class); MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
validator = new BlogPostValidator(cryptoComponent, blogFactory, validator = new BlogPostValidator(cryptoComponent, groupFactory,
clientHelper, metadataEncoder, clock); messageFactory, blogFactory, clientHelper, metadataEncoder,
clock);
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@Test @Test
public void testValidateProperBlogPost() public void testValidateProperBlogPost()
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
// Parent ID, content type, title (optional), post body, attachments // content type, title (optional), post body, attachments
BdfList content = BdfList.of(null, contentType, null, body, null); BdfList content = BdfList.of(null, contentType, null, body, null);
final byte[] sigBytes = TestUtils.getRandomBytes(42); final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList.of(content, sigBytes); BdfList m = BdfList.of(POST.getInt(), content, sigBytes);
expectCrypto(m, true); BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), content);
expectCrypto(signed, sigBytes, true);
final BdfDictionary result = final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary(); validator.validateMessage(message, group, m).getDictionary();
assertEquals(contentType, result.getString(KEY_CONTENT_TYPE)); assertEquals(contentType, result.getString(KEY_CONTENT_TYPE));
BdfDictionary authorDict = BdfDictionary.of(
new BdfEntry(KEY_AUTHOR_ID, author.getId()),
new BdfEntry(KEY_AUTHOR_NAME, author.getName()),
new BdfEntry(KEY_PUBLIC_KEY, author.getPublicKey())
);
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR)); assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
assertFalse(result.getBoolean(KEY_READ)); assertFalse(result.getBoolean(KEY_READ));
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -107,7 +126,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
public void testValidateBlogPostWithoutAttachments() public void testValidateBlogPostWithoutAttachments()
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
BdfList content = BdfList.of(null, contentType, null, body); BdfList content = BdfList.of(null, contentType, null, body);
BdfList m = BdfList.of(content, null); BdfList m = BdfList.of(POST.getInt(), content, null);
validator.validateMessage(message, group, m).getDictionary(); validator.validateMessage(message, group, m).getDictionary();
} }
@@ -116,7 +135,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
public void testValidateBlogPostWithoutSignature() public void testValidateBlogPostWithoutSignature()
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
BdfList content = BdfList.of(null, contentType, null, body, null); BdfList content = BdfList.of(null, contentType, null, body, null);
BdfList m = BdfList.of(content, null); BdfList m = BdfList.of(POST.getInt(), content, null);
validator.validateMessage(message, group, m).getDictionary(); validator.validateMessage(message, group, m).getDictionary();
} }
@@ -124,27 +143,149 @@ public class BlogPostValidatorTest extends BriarTestCase {
@Test(expected = InvalidMessageException.class) @Test(expected = InvalidMessageException.class)
public void testValidateBlogPostWithBadSignature() public void testValidateBlogPostWithBadSignature()
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
// Parent ID, content type, title (optional), post body, attachments // content type, title (optional), post body, attachments
BdfList content = BdfList.of(null, contentType, null, body, null); BdfList content = BdfList.of(null, contentType, null, body, null);
final byte[] sigBytes = TestUtils.getRandomBytes(42); final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList.of(content, sigBytes); BdfList m = BdfList.of(POST.getInt(), content, sigBytes);
expectCrypto(m, false); BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), content);
expectCrypto(signed, sigBytes, false);
validator.validateMessage(message, group, m).getDictionary(); validator.validateMessage(message, group, m).getDictionary();
} }
private void expectCrypto(BdfList m, final boolean pass) @Test
public void testValidateProperBlogComment()
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
// comment, parent_original_id, signature, parent_current_id
String comment = "This is a blog comment";
MessageId originalId = new MessageId(TestUtils.getRandomId());
byte[] currentId = TestUtils.getRandomId();
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList.of(COMMENT.getInt(), comment, originalId,
sigBytes, currentId);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), comment,
originalId);
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, result.getRaw(KEY_CURRENT_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());
byte[] currentId = TestUtils.getRandomId();
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList.of(COMMENT.getInt(), null, originalId, sigBytes,
currentId);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), null,
originalId);
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
BdfList content = BdfList.of(null, contentType, null, body, null);
final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m =
BdfList.of(WRAPPED_POST.getInt(), descriptor,
message.getTimestamp(), content, sigBytes);
BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), content);
expectCrypto(signed, sigBytes, true);
final BdfList originalList = BdfList.of(content, 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(contentType, result.getString(KEY_CONTENT_TYPE));
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());
final byte[] sigBytes = TestUtils.getRandomBytes(42);
MessageId currentId = new MessageId(TestUtils.getRandomId());
BdfList m = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor,
message.getTimestamp(), comment, originalId, sigBytes,
currentId);
BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(),
comment, originalId);
expectCrypto(signed, sigBytes, true);
final BdfList originalList = BdfList.of(comment, originalId, sigBytes,
currentId);
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_CURRENT_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 Signature signature = context.mock(Signature.class);
final KeyParser keyParser = context.mock(KeyParser.class); final KeyParser keyParser = context.mock(KeyParser.class);
final PublicKey publicKey = context.mock(PublicKey.class); final PublicKey publicKey = context.mock(PublicKey.class);
final BdfList content = m.getList(0);
final byte[] sigBytes = m.getRaw(1);
final BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), content);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(blogFactory).parseBlog(group, ""); oneOf(blogFactory).parseBlog(group, "");
will(returnValue(blog)); will(returnValue(blog));
@@ -156,9 +297,9 @@ public class BlogPostValidatorTest extends BriarTestCase {
will(returnValue(signature)); will(returnValue(signature));
oneOf(signature).initVerify(publicKey); oneOf(signature).initVerify(publicKey);
oneOf(clientHelper).toByteArray(signed); oneOf(clientHelper).toByteArray(signed);
will(returnValue(sigBytes)); will(returnValue(sig));
oneOf(signature).update(sigBytes); oneOf(signature).update(sig);
oneOf(signature).verify(sigBytes); oneOf(signature).verify(sig);
will(returnValue(pass)); will(returnValue(pass));
}}); }});
} }