From b9b6d760e080107f40f5525623212cd58e9703b7 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Fri, 30 Oct 2020 13:57:28 +0000 Subject: [PATCH] Objects necessary for collections --- .../core/collections/FileCollection.groovy | 117 ++++++++++++++++++ .../collections/FileCollectionItem.groovy | 77 ++++++++++++ .../InvalidCollectionException.groovy | 26 ++++ .../main/java/com/muwire/core/Constants.java | 2 + 4 files changed, 222 insertions(+) create mode 100644 core/src/main/groovy/com/muwire/core/collections/FileCollection.groovy create mode 100644 core/src/main/groovy/com/muwire/core/collections/FileCollectionItem.groovy create mode 100644 core/src/main/groovy/com/muwire/core/collections/InvalidCollectionException.groovy diff --git a/core/src/main/groovy/com/muwire/core/collections/FileCollection.groovy b/core/src/main/groovy/com/muwire/core/collections/FileCollection.groovy new file mode 100644 index 00000000..01f4ac3c --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/collections/FileCollection.groovy @@ -0,0 +1,117 @@ +package com.muwire.core.collections + +import com.muwire.core.Constants +import com.muwire.core.InfoHash +import com.muwire.core.Name +import com.muwire.core.Persona +import com.muwire.core.util.DataUtil + +import net.i2p.crypto.DSAEngine +import net.i2p.data.Signature +import net.i2p.data.SigningPrivateKey + +class FileCollection { + + private final long timestamp + private final Persona author + private final String comment + private final byte[] sig + + private volatile byte[] payload + + private final Set files = new LinkedHashSet<>() + + FileCollection(long timestamp, Persona author, String comment, Set files, + SigningPrivateKey spk) { + this.timestamp = timestamp; + this.author = author; + this.comment = comment + this.files = files + + byte [] signablePayload = signablePayload() + Signature signature = DSAEngine.getInstance().sign(signablePayload, spk) + this.sig = signature.getData() + } + + FileCollection(InputStream is) { + DataInputStream dis = new DataInputStream(is) + byte version = (byte) dis.read() + if (version != Constants.COLLECTION_VERSION) + throw new InvalidCollectionException("unsupported version $version") + + int numFiles = dis.readUnsignedShort() + + author = new Persona(dis) + timestamp = dis.readLong() + def commentName = new Name(dis) + comment = commentName.name + + numFiles.times { + files.add(new FileCollectionItem(dis)) + } + + sig = new byte[Constants.SIG_TYPE.getSigLen()] + dis.readFully(sig) + + if (!verify()) + throw new InvalidCollectionException("invalid signature") + } + + private boolean verify() { + def baos = new ByteArrayOutputStream() + def daos = new DataOutputStream(baos) + + daos.writeByte(Constants.COLLECTION_VERSION) + daos.writeShort(files.size()) + author.write(daos) + daos.writeLong(timestamp) + def commentName = new Name(comment) + commentName.write(daos) + files.each { + it.write(daos) + } + + daos.close() + byte [] payload = baos.toByteArray() + + def spk = author.destination.getSigningPublicKey() + def signature = new Signature(spk.getType(), sig) + DSAEngine.getInstance().verifySignature(signature, payload, spk) + } + + private byte[] signablePayload() { + + def baos = new ByteArrayOutputStream() + def daos = new DataOutputStream(baos) + + daos.writeByte(Constants.COLLECTION_VERSION) + daos.writeShort(files.size()) + author.write(daos) + daos.writeLong(timestamp) + def commentName = new Name(comment) + commentName.write(daos) + files.each { + it.write(daos) + } + + daos.close() + baos.toByteArray() + } + + public void write(OutputStream os) { + if (payload == null) { + def baos = new ByteArrayOutputStream() + def daos = new DataOutputStream(baos) + + daos.write(signablePayload()) + daos.write(sig) + + daos.close() + payload = baos.toByteArray() + } + os.write(payload) + } + + + +} diff --git a/core/src/main/groovy/com/muwire/core/collections/FileCollectionItem.groovy b/core/src/main/groovy/com/muwire/core/collections/FileCollectionItem.groovy new file mode 100644 index 00000000..6b7ac745 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/collections/FileCollectionItem.groovy @@ -0,0 +1,77 @@ +package com.muwire.core.collections + +import com.muwire.core.Constants +import com.muwire.core.InfoHash +import com.muwire.core.Name + +class FileCollectionItem { + + private final InfoHash infoHash + private final String comment + private final File file + private volatile byte[] payload + private final List pathElements = new ArrayList<>() + + public FileCollectionItem(InfoHash infoHash, String comment, List pathElements) { + this.infoHash = infoHash + this.comment = comment + this.pathElements = pathElements + + File f = null + pathElements.each { + if (f == null) { + f = new File(it) + return + } + f = new File(f, it) + } + this.file = f + } + + public FileCollectionItem(InputStream is) { + DataInputStream dis = new DataInputStream(is) + byte version = dis.readByte() + if (version != Constants.COLLECTION_ENTRY_VERSION) + throw new InvalidCollectionException("unsupported entry version $version") + + byte [] hash = new byte[32] + dis.readFully(hash) + infoHash = new InfoHash(hash) + + int nPathElements = dis.readUnsignedByte() + File f = null + nPathElements.times { + def element = new Name(dis) + pathElements.add(element.name) + if (f == null) { + f = new File(element.name) + return + } + f = new File(f, element.name) + } + file = f + + def commentName = new Name(dis) + comment = commentName.name + } + + void write(OutputStream os) { + if (payload == null) { + def baos = new ByteArrayOutputStream() + def daos = new DataOutputStream(baos) + + daos.writeByte(Constants.COLLECTION_ENTRY_VERSION) + daos.write(infoHash.getRoot()) + daos.writeByte((byte) pathElements.size()) + pathElements.each { + def name = new Name(it) + name.write(daos) + } + def commentName = new Name(comment) + commentName.write(daos) + daos.close() + payload = baos.toByteArray() + } + os.write(payload) + } +} diff --git a/core/src/main/groovy/com/muwire/core/collections/InvalidCollectionException.groovy b/core/src/main/groovy/com/muwire/core/collections/InvalidCollectionException.groovy new file mode 100644 index 00000000..d6b517eb --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/collections/InvalidCollectionException.groovy @@ -0,0 +1,26 @@ +package com.muwire.core.collections + +class InvalidCollectionException extends Exception { + + public InvalidCollectionException() { + super(); + } + + public InvalidCollectionException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public InvalidCollectionException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidCollectionException(String message) { + super(message); + } + + public InvalidCollectionException(Throwable cause) { + super(cause); + } + +} diff --git a/core/src/main/java/com/muwire/core/Constants.java b/core/src/main/java/com/muwire/core/Constants.java index 900040fd..1b6a9981 100644 --- a/core/src/main/java/com/muwire/core/Constants.java +++ b/core/src/main/java/com/muwire/core/Constants.java @@ -8,6 +8,8 @@ public class Constants { public static final int MAX_NICKNAME_LENGTH = 30; public static final byte FILE_CERT_VERSION = (byte)2; public static final int CHAT_VERSION = 1; + public static final byte COLLECTION_VERSION = (byte)1; + public static final byte COLLECTION_ENTRY_VERSION = (byte)1; public static final SigType SIG_TYPE = SigType.EdDSA_SHA512_Ed25519;