diff --git a/cli-lanterna/src/main/groovy/com/muwire/clilanterna/SearchModel.groovy b/cli-lanterna/src/main/groovy/com/muwire/clilanterna/SearchModel.groovy index c99c3d28..9ec69b50 100644 --- a/cli-lanterna/src/main/groovy/com/muwire/clilanterna/SearchModel.groovy +++ b/cli-lanterna/src/main/groovy/com/muwire/clilanterna/SearchModel.groovy @@ -8,7 +8,11 @@ import com.muwire.core.search.SearchEvent import com.muwire.core.search.UIResultBatchEvent import com.muwire.core.search.UIResultEvent +import net.i2p.crypto.DSAEngine import net.i2p.data.Base64 +import net.i2p.data.Signature + +import java.nio.charset.StandardCharsets import com.googlecode.lanterna.gui2.TextGUIThread import com.googlecode.lanterna.gui2.table.TableModel @@ -40,20 +44,27 @@ class SearchModel { } def searchEvent + byte [] payload if (hashSearch) { searchEvent = new SearchEvent(searchHash : root, uuid : UUID.randomUUID(), oobInfohash : true, compressedResults : true) + payload = root } else { def replaced = query.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ") def terms = replaced.split(" ") def nonEmpty = [] terms.each { if (it.length() > 0) nonEmpty << it } + payload = String.join(" ", nonEmpty).getBytes(StandardCharsets.UTF_8) searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : UUID.randomUUID(), oobInfohash: true, searchComments : core.muOptions.searchComments, compressedResults : true) } + boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop + + Signature sig = DSAEngine.getInstance().sign(payload, core.spk) + core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop, replyTo: core.me.destination, receivedOn: core.me.destination, - originator : core.me)) + originator : core.me, sig: sig.data)) } void unregister() { diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index 06addcc6..5b650628 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -103,6 +103,8 @@ public class Core { private final Router router final AtomicBoolean shutdown = new AtomicBoolean() + + final SigningPrivateKey spk public Core(MuWireSettings props, File home, String myVersion) { this.home = home @@ -180,7 +182,7 @@ public class Core { i2pSession = socketManager.getSession() def destination = new Destination() - def spk = new SigningPrivateKey(Constants.SIG_TYPE) + spk = new SigningPrivateKey(Constants.SIG_TYPE) keyDat.withInputStream { destination.readBytes(it) def privateKey = new PrivateKey() diff --git a/core/src/main/groovy/com/muwire/core/connection/Connection.groovy b/core/src/main/groovy/com/muwire/core/connection/Connection.groovy index 8af9b51d..a380f780 100644 --- a/core/src/main/groovy/com/muwire/core/connection/Connection.groovy +++ b/core/src/main/groovy/com/muwire/core/connection/Connection.groovy @@ -1,5 +1,6 @@ package com.muwire.core.connection +import java.nio.charset.StandardCharsets import java.util.concurrent.BlockingQueue import java.util.concurrent.CountDownLatch import java.util.concurrent.ExecutorService @@ -10,6 +11,7 @@ import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.logging.Level +import com.muwire.core.Constants import com.muwire.core.EventBus import com.muwire.core.MuWireSettings import com.muwire.core.Persona @@ -21,8 +23,10 @@ import com.muwire.core.trust.TrustLevel import com.muwire.core.trust.TrustService import groovy.util.logging.Log +import net.i2p.crypto.DSAEngine import net.i2p.data.Base64 import net.i2p.data.Destination +import net.i2p.data.Signature @Log abstract class Connection implements Closeable { @@ -147,6 +151,8 @@ abstract class Connection implements Closeable { query.replyTo = e.replyTo.toBase64() if (e.originator != null) query.originator = e.originator.toBase64() + if (e.sig != null) + query.sig = Base64.encode(e.sig) messages.put(query) } @@ -225,6 +231,24 @@ abstract class Connection implements Closeable { boolean compressedResults = false if (search.compressedResults != null) compressedResults = search.compressedResults + byte[] sig = null + // TODO: make this mandatory at some point + if (search.sig != null) { + sig = Base64.decode(search.sig) + byte [] payload + if (infohash != null) + payload = infohash + else + payload = String.join(" ",search.keywords).getBytes(StandardCharsets.UTF_8) + def spk = originator.destination.getSigningPublicKey() + def signature = new Signature(Constants.SIG_TYPE, sig) + if (!DSAEngine.getInstance().verifySig(signature, payload, spk)) { + log.info("signature didn't match keywords") + return + } else + log.info("query signature verified") + } else + log.info("no signature in query") SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords, searchHash : infohash, @@ -236,7 +260,8 @@ abstract class Connection implements Closeable { replyTo : replyTo, originator : originator, receivedOn : endpoint.destination, - firstHop : search.firstHop ) + firstHop : search.firstHop, + sig : sig ) eventBus.publish(event) } diff --git a/core/src/main/groovy/com/muwire/core/search/QueryEvent.groovy b/core/src/main/groovy/com/muwire/core/search/QueryEvent.groovy index 7cd3f631..bb753bb5 100644 --- a/core/src/main/groovy/com/muwire/core/search/QueryEvent.groovy +++ b/core/src/main/groovy/com/muwire/core/search/QueryEvent.groovy @@ -12,6 +12,7 @@ class QueryEvent extends Event { Destination replyTo Persona originator Destination receivedOn + byte[] sig String toString() { "searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" + diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 5dd8da2f..72302353 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -7,7 +7,11 @@ import griffon.core.mvc.MVCGroup import griffon.core.mvc.MVCGroupConfiguration import griffon.inject.MVCMember import griffon.metadata.ArtifactProviderFor +import net.i2p.crypto.DSAEngine import net.i2p.data.Base64 +import net.i2p.data.Signature + +import java.nio.charset.StandardCharsets import javax.annotation.Nonnull import javax.inject.Inject @@ -77,21 +81,27 @@ class MainFrameController { } def searchEvent + byte [] payload if (hashSearch) { searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true, compressedResults : true) + payload = root } else { // this can be improved a lot def replaced = search.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ") def terms = replaced.split(" ") def nonEmpty = [] terms.each { if (it.length() > 0) nonEmpty << it } + payload = String.join(" ",nonEmpty).getBytes(StandardCharsets.UTF_8) searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true, searchComments : core.muOptions.searchComments, compressedResults : true) } boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop + + Signature sig = DSAEngine.getInstance().sign(payload, core.spk) + core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop, replyTo: core.me.destination, receivedOn: core.me.destination, - originator : core.me)) + originator : core.me, sig : sig.data)) } void search(String infoHash, String tabTitle) {