ability to certify shared files

This commit is contained in:
Zlatin Balevsky
2019-11-04 15:22:24 +00:00
parent bf3502220f
commit 5d51b1c580
9 changed files with 276 additions and 0 deletions

View File

@@ -18,6 +18,8 @@ import com.muwire.core.download.UIDownloadCancelledEvent
import com.muwire.core.download.UIDownloadEvent import com.muwire.core.download.UIDownloadEvent
import com.muwire.core.download.UIDownloadPausedEvent import com.muwire.core.download.UIDownloadPausedEvent
import com.muwire.core.download.UIDownloadResumedEvent import com.muwire.core.download.UIDownloadResumedEvent
import com.muwire.core.filecert.CertificateManager
import com.muwire.core.filecert.UICreateCertificateEvent
import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.files.FileHashedEvent import com.muwire.core.files.FileHashedEvent
import com.muwire.core.files.FileHashingEvent import com.muwire.core.files.FileHashingEvent
@@ -99,6 +101,7 @@ public class Core {
final FileManager fileManager final FileManager fileManager
final UploadManager uploadManager final UploadManager uploadManager
final ContentManager contentManager final ContentManager contentManager
final CertificateManager certificateManager
private final Router router private final Router router
@@ -209,6 +212,10 @@ public class Core {
eventBus = new EventBus() eventBus = new EventBus()
log.info("initializing certificate manager")
certificateManager = new CertificateManager(eventBus, home, me, spk)
eventBus.register(UICreateCertificateEvent.class, certificateManager)
log.info("initializing trust service") log.info("initializing trust service")
File goodTrust = new File(home, "trusted") File goodTrust = new File(home, "trusted")
File badTrust = new File(home, "distrusted") File badTrust = new File(home, "distrusted")

View File

@@ -0,0 +1,119 @@
package com.muwire.core.filecert
import com.muwire.core.Constants
import com.muwire.core.InfoHash
import com.muwire.core.InvalidSignatureException
import com.muwire.core.Name
import com.muwire.core.Persona
import net.i2p.crypto.DSAEngine
import net.i2p.data.Signature
import net.i2p.data.SigningPrivateKey
import net.i2p.data.SigningPublicKey
class Certificate {
private final byte version
private final InfoHash infoHash
private final Name name
private final long timestamp
private final Persona issuer
private final byte[] sig
private volatile byte [] payload
Certificate(InputStream is) {
version = (byte) (is.read() & 0xFF)
if (version != Constants.FILE_CERT_VERSION)
throw new IOException("Unknown version $version")
DataInputStream dis = new DataInputStream(is)
timestamp = dis.readLong()
byte [] root = new byte[InfoHash.SIZE]
dis.readFully(root)
infoHash = new InfoHash(root)
name = new Name(dis)
issuer = new Persona(is)
sig = new byte[Constants.SIG_TYPE.getSigLen()]
dis.readFully(sig)
if (!verify(version, infoHash, name, timestamp, issuer, sig))
throw new InvalidSignatureException("certificate for $name.name from ${issuer.getHumanReadableName()} didn't verify")
}
Certificate(InfoHash infoHash, String name, long timestamp, Persona issuer, SigningPrivateKey spk) {
this.version = Constants.FILE_CERT_VERSION
this.infoHash = infoHash
this.name = new Name(name)
this.timestamp = timestamp
this.issuer = issuer
ByteArrayOutputStream baos = new ByteArrayOutputStream()
DataOutputStream daos = new DataOutputStream(baos)
daos.write(version)
daos.writeLong(timestamp)
daos.write(infoHash.getRoot())
this.name.write(daos)
issuer.write(daos)
daos.close()
byte[] payload = baos.toByteArray()
Signature signature = DSAEngine.getInstance().sign(payload, spk)
this.sig = signature.getData()
}
private static boolean verify(byte version, InfoHash infoHash, Name name, long timestamp, Persona issuer, byte[] sig) {
ByteArrayOutputStream baos = new ByteArrayOutputStream()
DataOutputStream daos = new DataOutputStream(baos)
daos.write(version)
daos.writeLong(timestamp)
daos.write(infoHash.getRoot())
name.write(daos)
issuer.write(daos)
daos.close()
byte [] payload = baos.toByteArray()
SigningPublicKey spk = issuer.destination.getSigningPublicKey()
Signature signature = new Signature(Constants.SIG_TYPE, sig)
DSAEngine.getInstance().verifySignature(signature, payload, spk)
}
public void write(OutputStream os) {
if (payload == null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream()
DataOutputStream daos = new DataOutputStream(baos)
daos.write(version)
daos.writeLong(timestamp)
daos.write(infoHash.getRoot())
name.write(daos)
issuer.write(daos)
daos.write(sig)
daos.close()
payload = baos.toByteArray()
}
os.write(payload)
}
@Override
public int hashCode() {
version.hashCode() ^ infoHash.hashCode() ^ timestamp.hashCode() ^ name.hashCode() ^ issuer.hashCode()
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Certificate))
return false
Certificate other = (Certificate)o
version == other.version &&
infoHash == other.infoHash &&
timestamp == other.timestamp &&
name == other.name &&
issuer == other.issuer
}
}

View File

@@ -0,0 +1,7 @@
package com.muwire.core.filecert
import com.muwire.core.Event
class CertificateCreatedEvent extends Event {
Certificate certificate
}

View File

@@ -0,0 +1,110 @@
package com.muwire.core.filecert
import java.util.concurrent.ConcurrentHashMap
import java.util.logging.Level
import com.muwire.core.EventBus
import com.muwire.core.InfoHash
import com.muwire.core.InvalidSignatureException
import com.muwire.core.Name
import com.muwire.core.Persona
import groovy.util.logging.Log
import net.i2p.data.Base64
import net.i2p.data.SigningPrivateKey
@Log
class CertificateManager {
private final EventBus eventBus
private final File certDir
private final Persona me
private final SigningPrivateKey spk
final Map<InfoHash, Set<Certificate>> byInfoHash = new ConcurrentHashMap()
final Map<Persona, Set<Certificate>> byIssuer = new ConcurrentHashMap()
CertificateManager(EventBus eventBus, File home, Persona me, SigningPrivateKey spk) {
this.eventBus = eventBus
this.me = me
this.spk = spk
this.certDir = new File(home, "filecerts")
if (!certDir.exists())
certDir.mkdirs()
else
loadCertificates()
}
private void loadCertificates() {
certDir.listFiles({ dir, name ->
name.endsWith("mwcert")
} as FilenameFilter).each { certFile ->
Certificate cert = null
try {
certFile.withInputStream {
cert = new Certificate(it)
}
} catch (IOException | InvalidSignatureException ignore) {
log.log(Level.WARNING, "Certificate failed to load from $certFile", ignore)
return
}
Set<Certificate> existing = byInfoHash.get(cert.infoHash)
if (existing == null) {
existing = new HashSet<>()
byInfoHash.put(cert.infoHash, existing)
}
existing.add(cert)
existing = byIssuer.get(cert.issuer)
if (existing == null) {
existing = new HashSet<>()
byIssuer.put(cert.issuer, existing)
}
existing.add(cert)
eventBus.publish(new CertificateCreatedEvent(certificate : cert))
}
}
void onUICreateCertificateEvent(UICreateCertificateEvent e) {
InfoHash infoHash = e.sharedFile.getInfoHash()
String name = e.sharedFile.getFile().getName()
long timestamp = System.currentTimeMillis()
Certificate cert = new Certificate(infoHash, name, timestamp, me, spk)
boolean added = true
Set<Certificate> existing = byInfoHash.get(cert.infoHash)
if (existing == null) {
existing = new HashSet<>()
byInfoHash.put(cert.infoHash, existing)
}
added &= existing.add(cert)
existing = byIssuer.get(cert.issuer)
if (existing == null) {
existing = new HashSet<>()
byIssuer.put(cert.issuer, existing)
}
added &= existing.add(cert)
if (added) {
String infoHashString = Base64.encode(infoHash.getRoot())
File certFile = new File(certDir, "${infoHashString}${name}.mwcert")
certFile.withOutputStream { cert.write(it) }
eventBus.publish(new CertificateCreatedEvent(certificate : cert))
}
}
boolean hasLocalCertificate(InfoHash infoHash) {
if (!byInfoHash.containsKey(infoHash))
return false
Set<Certificate> set = byInfoHash.get(infoHash)
for (Certificate cert : set) {
if (cert.issuer == me)
return true
}
return false
}
}

View File

@@ -0,0 +1,8 @@
package com.muwire.core.filecert
import com.muwire.core.Event
import com.muwire.core.SharedFile
class UICreateCertificateEvent extends Event {
SharedFile sharedFile
}

View File

@@ -4,6 +4,7 @@ import net.i2p.crypto.SigType;
public class Constants { public class Constants {
public static final byte PERSONA_VERSION = (byte)1; public static final byte PERSONA_VERSION = (byte)1;
public static final byte FILE_CERT_VERSION = (byte)1;
public static final SigType SIG_TYPE = SigType.EdDSA_SHA512_Ed25519; public static final SigType SIG_TYPE = SigType.EdDSA_SHA512_Ed25519;
public static final int MAX_HEADER_SIZE = 0x1 << 14; public static final int MAX_HEADER_SIZE = 0x1 << 14;

View File

@@ -28,6 +28,7 @@ import com.muwire.core.download.UIDownloadCancelledEvent
import com.muwire.core.download.UIDownloadEvent import com.muwire.core.download.UIDownloadEvent
import com.muwire.core.download.UIDownloadPausedEvent import com.muwire.core.download.UIDownloadPausedEvent
import com.muwire.core.download.UIDownloadResumedEvent import com.muwire.core.download.UIDownloadResumedEvent
import com.muwire.core.filecert.UICreateCertificateEvent
import com.muwire.core.files.DirectoryUnsharedEvent import com.muwire.core.files.DirectoryUnsharedEvent
import com.muwire.core.files.FileUnsharedEvent import com.muwire.core.files.FileUnsharedEvent
import com.muwire.core.files.UIPersistFilesEvent import com.muwire.core.files.UIPersistFilesEvent
@@ -338,6 +339,13 @@ class MainFrameController {
performSearch(it) performSearch(it)
} }
} }
@ControllerAction
void issueCertificate() {
view.selectedSharedFiles().each {
core.eventBus.publish(new UICreateCertificateEvent(sharedFile : it))
}
}
void saveMuWireSettings() { void saveMuWireSettings() {
core.saveMuSettings() core.saveMuSettings()

View File

@@ -27,6 +27,7 @@ import com.muwire.core.connection.DisconnectionEvent
import com.muwire.core.content.ContentControlEvent import com.muwire.core.content.ContentControlEvent
import com.muwire.core.download.DownloadStartedEvent import com.muwire.core.download.DownloadStartedEvent
import com.muwire.core.download.Downloader import com.muwire.core.download.Downloader
import com.muwire.core.filecert.CertificateCreatedEvent
import com.muwire.core.files.AllFilesLoadedEvent import com.muwire.core.files.AllFilesLoadedEvent
import com.muwire.core.files.DirectoryUnsharedEvent import com.muwire.core.files.DirectoryUnsharedEvent
import com.muwire.core.files.DirectoryWatchedEvent import com.muwire.core.files.DirectoryWatchedEvent
@@ -208,6 +209,7 @@ class MainFrameModel {
core.eventBus.register(UpdateDownloadedEvent.class, this) core.eventBus.register(UpdateDownloadedEvent.class, this)
core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this) core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this)
core.eventBus.register(SearchEvent.class, this) core.eventBus.register(SearchEvent.class, this)
core.eventBus.register(CertificateCreatedEvent.class, this)
core.muOptions.watchedKeywords.each { core.muOptions.watchedKeywords.each {
core.eventBus.publish(new ContentControlEvent(term : it, regex: false, add: true)) core.eventBus.publish(new ContentControlEvent(term : it, regex: false, add: true))
@@ -571,6 +573,12 @@ class MainFrameModel {
} }
} }
void onCertificateCreatedEvent(CertificateCreatedEvent e) {
runInsideUIAsync {
view.refreshSharedFiles()
}
}
private void insertIntoTree(SharedFile file) { private void insertIntoTree(SharedFile file) {
List<File> parents = new ArrayList<>() List<File> parents = new ArrayList<>()
File tmp = file.file.getParentFile() File tmp = file.file.getParentFile()

View File

@@ -269,6 +269,10 @@ class MainFrameView {
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()}) closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
closureColumn(header : "Size", preferredWidth : 50, type : Long, read : {row -> row.getCachedLength() }) closureColumn(header : "Size", preferredWidth : 50, type : Long, read : {row -> row.getCachedLength() })
closureColumn(header : "Comments", preferredWidth : 50, type : Boolean, read : {it.getComment() != null}) closureColumn(header : "Comments", preferredWidth : 50, type : Boolean, read : {it.getComment() != null})
closureColumn(header : "Certificates", preferredWidth : 50, type : Boolean, read : {
Core core = application.context.get("core")
core.certificateManager.hasLocalCertificate(it.getInfoHash())
})
closureColumn(header : "Search Hits", preferredWidth: 50, type : Integer, read : {it.getHits()}) closureColumn(header : "Search Hits", preferredWidth: 50, type : Integer, read : {it.getHits()})
closureColumn(header : "Downloaders", preferredWidth: 50, type : Integer, read : {it.getDownloaders().size()}) closureColumn(header : "Downloaders", preferredWidth: 50, type : Integer, read : {it.getDownloaders().size()})
} }
@@ -295,6 +299,7 @@ class MainFrameView {
panel { panel {
button(text : "Share files", actionPerformed : shareFiles) button(text : "Share files", actionPerformed : shareFiles)
button(text : "Add Comment", enabled : bind {model.addCommentButtonEnabled}, addCommentAction) button(text : "Add Comment", enabled : bind {model.addCommentButtonEnabled}, addCommentAction)
button(text : "Certify", enabled : bind {model.addCommentButtonEnabled}, issueCertificateAction)
} }
panel { panel {
panel { panel {
@@ -596,6 +601,9 @@ class MainFrameView {
JMenuItem commentSelectedFiles = new JMenuItem("Comment selected files") JMenuItem commentSelectedFiles = new JMenuItem("Comment selected files")
commentSelectedFiles.addActionListener({mvcGroup.controller.addComment()}) commentSelectedFiles.addActionListener({mvcGroup.controller.addComment()})
sharedFilesMenu.add(commentSelectedFiles) sharedFilesMenu.add(commentSelectedFiles)
JMenuItem certifySelectedFiles = new JMenuItem("Certify selected files")
certifySelectedFiles.addActionListener({mvcGroup.controller.issueCertificate()})
sharedFilesMenu.add(certifySelectedFiles)
def sharedFilesMouseListener = new MouseAdapter() { def sharedFilesMouseListener = new MouseAdapter() {
@Override @Override