add extended signature in queries to prevent replay attacks

This commit is contained in:
Zlatin Balevsky
2019-11-09 16:39:16 +00:00
parent afaadf65a4
commit f4a2864942
6 changed files with 62 additions and 6 deletions

View File

@@ -153,6 +153,10 @@ abstract class Connection implements Closeable {
query.originator = e.originator.toBase64() query.originator = e.originator.toBase64()
if (e.sig != null) if (e.sig != null)
query.sig = Base64.encode(e.sig) query.sig = Base64.encode(e.sig)
if (e.queryTime > 0)
query.queryTime = e.queryTime
if (e.sig2 != null)
query.sig2 = Base64.encode(e.sig2)
messages.put(query) messages.put(query)
} }
@@ -249,6 +253,32 @@ abstract class Connection implements Closeable {
log.info("query signature verified") log.info("query signature verified")
} else } else
log.info("no signature in query") log.info("no signature in query")
// TODO: make this mandatory at some point
byte[] sig2 = null
long queryTime = 0
if (search.sig2 != null) {
if (search.queryTime == null) {
log.info("extended signature but no timestamp")
return
}
sig2 = Base64.decode(search.sig2)
queryTime = search.queryTime
byte [] payload = (search.uuid + String.valueOf(queryTime)).getBytes(StandardCharsets.US_ASCII)
def spk = originator.destination.getSigningPublicKey()
def signature = new Signature(Constants.SIG_TYPE, sig2)
if (!DSAEngine.getInstance().verifySignature(signature, payload, spk)) {
log.info("extended signature didn't match uuid and timestamp")
return
} else {
log.info("extended query signature verified")
if (queryTime < System.currentTimeMillis() - Constants.MAX_QUERY_AGE) {
log.info("query too old")
return
}
}
} else
log.info("no extended signature in query")
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords, SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
searchHash : infohash, searchHash : infohash,
@@ -262,7 +292,9 @@ abstract class Connection implements Closeable {
originator : originator, originator : originator,
receivedOn : endpoint.destination, receivedOn : endpoint.destination,
firstHop : search.firstHop, firstHop : search.firstHop,
sig : sig ) sig : sig,
queryTime : queryTime,
sig2 : sig2 )
eventBus.publish(event) eventBus.publish(event)
} }

View File

@@ -13,6 +13,8 @@ class QueryEvent extends Event {
Persona originator Persona originator
Destination receivedOn Destination receivedOn
byte[] sig byte[] sig
long queryTime
byte[] sig2
String toString() { String toString() {
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" + "searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +

View File

@@ -13,6 +13,7 @@ import com.muwire.core.files.FileSharedEvent
import com.muwire.core.search.QueryEvent import com.muwire.core.search.QueryEvent
import com.muwire.core.search.SearchEvent import com.muwire.core.search.SearchEvent
import com.muwire.core.search.UIResultBatchEvent import com.muwire.core.search.UIResultBatchEvent
import com.muwire.core.util.DataUtil
import groovy.json.JsonOutput import groovy.json.JsonOutput
import groovy.json.JsonSlurper import groovy.json.JsonSlurper
@@ -176,9 +177,12 @@ class UpdateClient {
signer = payload.signer signer = payload.signer
log.info("starting search for new version hash $payload.infoHash") log.info("starting search for new version hash $payload.infoHash")
Signature sig = DSAEngine.getInstance().sign(updateInfoHash.getRoot(), spk) Signature sig = DSAEngine.getInstance().sign(updateInfoHash.getRoot(), spk)
def searchEvent = new SearchEvent(searchHash : updateInfoHash.getRoot(), uuid : UUID.randomUUID(), oobInfohash : true, persona : me) UUID uuid = UUID.randomUUID()
long timestamp = System.currentTimeMillis()
byte [] sig2 = DataUtil.signUUID(uuid, timestamp, spk)
def searchEvent = new SearchEvent(searchHash : updateInfoHash.getRoot(), uuid : uuid, oobInfohash : true, persona : me)
def queryEvent = new QueryEvent(searchEvent : searchEvent, firstHop : true, replyTo : me.destination, def queryEvent = new QueryEvent(searchEvent : searchEvent, firstHop : true, replyTo : me.destination,
receivedOn : me.destination, originator : me, sig : sig.data) receivedOn : me.destination, originator : me, sig : sig.data, queryTime : timestamp, sig2 : sig2)
eventBus.publish(queryEvent) eventBus.publish(queryEvent)
} }
} }

View File

@@ -13,4 +13,6 @@ public class Constants {
public static final int MAX_RESULTS = 0x1 << 16; public static final int MAX_RESULTS = 0x1 << 16;
public static final int MAX_COMMENT_LENGTH = 0x1 << 15; public static final int MAX_COMMENT_LENGTH = 0x1 << 15;
public static final long MAX_QUERY_AGE = 5 * 60 * 1000L;
} }

View File

@@ -15,11 +15,15 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.muwire.core.Constants; import com.muwire.core.Constants;
import net.i2p.crypto.DSAEngine;
import net.i2p.data.Base64; import net.i2p.data.Base64;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.util.ConcurrentHashSet; import net.i2p.util.ConcurrentHashSet;
public class DataUtil { public class DataUtil {
@@ -203,4 +207,10 @@ public class DataUtil {
.collect(Collectors.joining(",")); .collect(Collectors.joining(","));
props.setProperty(property, encoded); props.setProperty(property, encoded);
} }
public static byte[] signUUID(UUID uuid, long timestamp, SigningPrivateKey spk) {
byte [] payload = (uuid.toString() + String.valueOf(timestamp)).getBytes(StandardCharsets.US_ASCII);
Signature sig = DSAEngine.getInstance().sign(payload, spk);
return sig.getData();
}
} }

View File

@@ -7,9 +7,11 @@ import griffon.core.mvc.MVCGroup
import griffon.core.mvc.MVCGroupConfiguration import griffon.core.mvc.MVCGroupConfiguration
import griffon.inject.MVCMember import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor import griffon.metadata.ArtifactProviderFor
import groovy.json.StringEscapeUtils
import net.i2p.crypto.DSAEngine import net.i2p.crypto.DSAEngine
import net.i2p.data.Base64 import net.i2p.data.Base64
import net.i2p.data.Signature import net.i2p.data.Signature
import net.i2p.data.SigningPrivateKey
import java.awt.Desktop import java.awt.Desktop
import java.awt.event.ActionEvent import java.awt.event.ActionEvent
@@ -43,6 +45,7 @@ import com.muwire.core.trust.TrustLevel
import com.muwire.core.trust.TrustSubscriptionEvent import com.muwire.core.trust.TrustSubscriptionEvent
import com.muwire.core.upload.HashListUploader import com.muwire.core.upload.HashListUploader
import com.muwire.core.upload.Uploader import com.muwire.core.upload.Uploader
import com.muwire.core.util.DataUtil
@ArtifactProviderFor(GriffonController) @ArtifactProviderFor(GriffonController)
class MainFrameController { class MainFrameController {
@@ -121,9 +124,10 @@ class MainFrameController {
Signature sig = DSAEngine.getInstance().sign(payload, core.spk) Signature sig = DSAEngine.getInstance().sign(payload, core.spk)
long timestamp = System.currentTimeMillis()
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop, core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop,
replyTo: core.me.destination, receivedOn: core.me.destination, replyTo: core.me.destination, receivedOn: core.me.destination,
originator : core.me, sig : sig.data)) originator : core.me, sig : sig.data, queryTime : timestamp, sig2 : DataUtil.signUUID(uuid, timestamp, core.spk)))
} }
@@ -140,14 +144,16 @@ class MainFrameController {
byte [] infoHashBytes = Base64.decode(infoHash) byte [] infoHashBytes = Base64.decode(infoHash)
Signature sig = DSAEngine.getInstance().sign(infoHashBytes, core.spk) Signature sig = DSAEngine.getInstance().sign(infoHashBytes, core.spk)
long timestamp = System.currentTimeMillis()
byte [] sig2 = DataUtil.signUUID(uuid, timestamp, core.spk)
def searchEvent = new SearchEvent(searchHash : Base64.decode(infoHash), uuid:uuid, def searchEvent = new SearchEvent(searchHash : Base64.decode(infoHash), uuid:uuid,
oobInfohash: true, persona : core.me) oobInfohash: true, persona : core.me)
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : true, core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : true,
replyTo: core.me.destination, receivedOn: core.me.destination, replyTo: core.me.destination, receivedOn: core.me.destination,
originator : core.me, sig : sig.data)) originator : core.me, sig : sig.data, queryTime : timestamp, sig2 : sig2))
} }
private int selectedDownload() { private int selectedDownload() {
def downloadsTable = builder.getVariable("downloads-table") def downloadsTable = builder.getVariable("downloads-table")
def selected = downloadsTable.getSelectedRow() def selected = downloadsTable.getSelectedRow()