add extended signature in queries to prevent replay attacks
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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()}" +
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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()
|
||||||
|
Reference in New Issue
Block a user