ability to certify shared files
This commit is contained in:
@@ -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")
|
||||||
|
119
core/src/main/groovy/com/muwire/core/filecert/Certificate.groovy
Normal file
119
core/src/main/groovy/com/muwire/core/filecert/Certificate.groovy
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
package com.muwire.core.filecert
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
class CertificateCreatedEvent extends Event {
|
||||||
|
Certificate certificate
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
@@ -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;
|
||||||
|
@@ -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()
|
||||||
|
@@ -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()
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user