diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index 3ad564c51..65d1eca00 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -1247,7 +1247,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn header = getErrorPage("dnfp", ERR_DESTINATION_UNKNOWN); } else if(ahelperPresent) { header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN); - } else if(destination.length() == 60 && destination.toLowerCase(Locale.US).endsWith(".b32.i2p")) { + } else if(destination.length() >= 60 && destination.toLowerCase(Locale.US).endsWith(".b32.i2p")) { header = getErrorPage("nols", ERR_DESTINATION_UNKNOWN); extraMessage = _t("Destination lease set not found"); } else { diff --git a/core/java/src/net/i2p/data/BlindData.java b/core/java/src/net/i2p/data/BlindData.java index 0d3eed59c..3e613fd5b 100644 --- a/core/java/src/net/i2p/data/BlindData.java +++ b/core/java/src/net/i2p/data/BlindData.java @@ -25,6 +25,7 @@ public class BlindData { private long _routingKeyGenMod; /** + * @param secret may be null or zero-length * @throws IllegalArgumentException on various errors */ public BlindData(I2PAppContext ctx, Destination dest, SigType blindType, String secret) { @@ -33,6 +34,7 @@ public class BlindData { } /** + * @param secret may be null or zero-length * @throws IllegalArgumentException on various errors */ public BlindData(I2PAppContext ctx, SigningPublicKey spk, SigType blindType, String secret) { @@ -47,13 +49,27 @@ public class BlindData { } /** - * @return The blinded key for the current day + * @return The unblinded SPK, non-null + */ + public SigningPublicKey getUnblindedPubKey() { + return _clearSPK; + } + + /** + * @return The blinded key for the current day, non-null */ public synchronized SigningPublicKey getBlindedPubKey() { calculate(); return _blindSPK; } + /** + * @return The hash of the destination if known, or null + */ + public synchronized Hash getDestHash() { + return _dest != null ? _dest.getHash() : null; + } + /** * @return The hash of the blinded key for the current day */ @@ -127,4 +143,22 @@ public class BlindData { System.arraycopy(_blindSPK.getData(), 0, hashData, 2, _blindSPK.length()); _blindHash = _context.sha().calculateHash(hashData); } + + @Override + public synchronized String toString() { + calculate(); + StringBuilder buf = new StringBuilder(256); + buf.append("[BlindData: "); + buf.append("\n\tSigningPublicKey: ").append(_clearSPK); + buf.append("\n\tAlpha : ").append(_alpha); + buf.append("\n\tBlindedPublicKey: ").append(_blindSPK); + buf.append("\n\tBlinded Hash : ").append(_blindHash); + buf.append("\n\tSecret: ").append(_secret); + if (_dest != null) + buf.append("\n\tDestination: ").append(_dest); + if (_dest != null) + buf.append("\n\tDestination: unknown"); + buf.append(']'); + return buf.toString(); + } } diff --git a/core/java/src/net/i2p/data/EncryptedLeaseSet.java b/core/java/src/net/i2p/data/EncryptedLeaseSet.java index 2610edb8a..8b6ae045d 100644 --- a/core/java/src/net/i2p/data/EncryptedLeaseSet.java +++ b/core/java/src/net/i2p/data/EncryptedLeaseSet.java @@ -33,6 +33,8 @@ public class EncryptedLeaseSet extends LeaseSet2 { private LeaseSet2 _decryptedLS2; private Hash __calculatedHash; private SigningPrivateKey _alpha; + // to decrypt with if we don't have full dest + private SigningPublicKey _unblindedSPK; private String _secret; private final Log _log; @@ -120,11 +122,31 @@ public class EncryptedLeaseSet extends LeaseSet2 { _destination = dest; } SigningPublicKey spk = dest.getSigningPublicKey(); + setSigningKey(spk); + } + + /** + * Overridden to set the blinded key + * + * @param spk unblinded key non-null, must be EdDSA_SHA512_Ed25519 or RedDSA_SHA512_Ed25519 + * @throws IllegalStateException if already signed + * @throws IllegalArgumentException if not EdDSA + * @since 0.9.40 + */ + @Override + public void setSigningKey(SigningPublicKey spk) { + // TODO already-set checks SigType type = spk.getType(); if (type != SigType.EdDSA_SHA512_Ed25519 && type != SigType.RedDSA_SHA512_Ed25519) throw new IllegalArgumentException(); - SigningPublicKey bpk = blind(); + if (_unblindedSPK != null) { + if (!_unblindedSPK.equals(spk)) + throw new IllegalArgumentException("unblinded pubkey mismatch"); + } else { + _unblindedSPK = spk; + } + SigningPublicKey bpk = blind(spk); if (_signingKey == null) _signingKey = bpk; else if (!_signingKey.equals(bpk)) @@ -139,13 +161,12 @@ public class EncryptedLeaseSet extends LeaseSet2 { * * @since 0.9.39 */ - private SigningPublicKey blind() { - SigningPublicKey spk = _destination.getSigningPublicKey(); + private SigningPublicKey blind(SigningPublicKey spk) { I2PAppContext ctx = I2PAppContext.getGlobalContext(); if (_published <= 0) - _alpha = Blinding.generateAlpha(ctx, _destination.getSigningPublicKey(), _secret); + _alpha = Blinding.generateAlpha(ctx, spk, _secret); else - _alpha = Blinding.generateAlpha(ctx, _destination.getSigningPublicKey(), _secret, _published); + _alpha = Blinding.generateAlpha(ctx, spk, _secret, _published); SigningPublicKey rv = Blinding.blind(spk, _alpha); if (_log.shouldDebug()) _log.debug("Blind:" + @@ -476,14 +497,13 @@ public class EncryptedLeaseSet extends LeaseSet2 { * @since 0.9.39 */ private byte[] getSubcredential(I2PAppContext ctx) { - if (_destination == null) - throw new IllegalStateException("no known destination to decrypt with"); - SigningPublicKey destspk = _destination.getSigningPublicKey(); - int spklen = destspk.length(); + if (_unblindedSPK == null) + throw new IllegalStateException("no known SPK to decrypt with"); + int spklen = _unblindedSPK.length(); byte[] in = new byte[spklen + 4]; // SHA256("credential" || spk || sigtypein || sigtypeout) - System.arraycopy(destspk.getData(), 0, in, 0, spklen); - DataHelper.toLong(in, spklen, 2, destspk.getType().getCode()); + System.arraycopy(_unblindedSPK.getData(), 0, in, 0, spklen); + DataHelper.toLong(in, spklen, 2, _unblindedSPK.getType().getCode()); DataHelper.toLong(in, spklen + 2, 2, SigType.RedDSA_SHA512_Ed25519.getCode()); byte[] credential = hash(ctx, CREDENTIAL, in); byte[] spk = _signingKey.getData(); @@ -572,8 +592,9 @@ public class EncryptedLeaseSet extends LeaseSet2 { return false; } _log.info("ELS2 outer sig verify success"); - if (_destination == null) { - _log.warn("ELS2 no dest to decrypt with"); + if (_unblindedSPK == null) { + if (_log.shouldWarn()) + _log.warn("ELS2 no dest/SPK to decrypt with", new Exception("I did it")); return true; } try { diff --git a/history.txt b/history.txt index 1ba070f22..d11b96053 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,13 @@ +2019-03-27 zzz + * NetDB: Cache blinding data for lookups and decryption (proposal #123) + +2019-03-23 zzz + * Data: Preliminary work on new b32 format (proposal #149) + * SelfSignedGenerator: + - Fix generation with Ed25519ph keys (ticket #2465) + - Increase serial number from 63 to 71 bits + * SusiDNS: Add import feature (ticket #2447) + 2019-03-22 zzz * i2ptunnel: Escape {} in URLs (ticket #2130) diff --git a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java index 610859f1a..c2287207c 100644 --- a/router/java/src/net/i2p/router/NetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/NetworkDatabaseFacade.java @@ -13,10 +13,12 @@ import java.io.Writer; import java.util.Collections; import java.util.Set; +import net.i2p.data.BlindData; import net.i2p.data.DatabaseEntry; import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; +import net.i2p.data.SigningPublicKey; import net.i2p.data.router.RouterInfo; import net.i2p.router.networkdb.reseed.ReseedChecker; @@ -161,4 +163,19 @@ public abstract class NetworkDatabaseFacade implements Service { * @since 0.9.16 */ public boolean isNegativeCachedForever(Hash key) { return false; } + + /** + * @param spk unblinded key + * @return BlindData or null + * @since 0.9.40 + */ + public BlindData getBlindData(SigningPublicKey spk) { + return null; + } + + /** + * @param bd new BlindData to put in the cache + * @since 0.9.40 + */ + public void setBlindData(BlindData bd) {} } diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 87e5bffca..282c18b42 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 1; + public final static long BUILD = 2; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/client/LookupDestJob.java b/router/java/src/net/i2p/router/client/LookupDestJob.java index b6a1cd939..e03758ab9 100644 --- a/router/java/src/net/i2p/router/client/LookupDestJob.java +++ b/router/java/src/net/i2p/router/client/LookupDestJob.java @@ -9,9 +9,12 @@ import java.util.Locale; import net.i2p.crypto.Blinding; import net.i2p.data.Base32; import net.i2p.data.BlindData; +import net.i2p.data.DatabaseEntry; import net.i2p.data.Destination; +import net.i2p.data.EncryptedLeaseSet; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; +import net.i2p.data.SigningPublicKey; import net.i2p.data.i2cp.DestReplyMessage; import net.i2p.data.i2cp.HostReplyMessage; import net.i2p.data.i2cp.I2CPMessage; @@ -34,6 +37,7 @@ class LookupDestJob extends JobImpl { private final String _name; private final SessionId _sessID; private final Hash _fromLocalDest; + private final BlindData _blindData; private static final long DEFAULT_TIMEOUT = 15*1000; @@ -69,6 +73,7 @@ class LookupDestJob extends JobImpl { _timeout = timeout; _sessID = sessID; _fromLocalDest = fromLocalDest; + BlindData bd = null; if (name != null && name.length() >= 60) { // convert a b32 lookup to a hash lookup String nlc = name.toLowerCase(Locale.US); @@ -82,8 +87,16 @@ class LookupDestJob extends JobImpl { name = null; } else if (b.length >= 35) { // encrypted LS2 + // lookup the blinded hash try { - BlindData bd = Blinding.decode(context, b); + bd = Blinding.decode(context, b); + SigningPublicKey spk = bd.getUnblindedPubKey(); + BlindData bd2 = getContext().netDb().getBlindData(spk); + if (bd2 != null) { + bd = bd2; + } else { + getContext().netDb().setBlindData(bd); + } h = bd.getBlindedHash(); if (_log.shouldDebug()) _log.debug("Converting name lookup " + name + " to blinded " + h); @@ -99,6 +112,7 @@ class LookupDestJob extends JobImpl { } _hash = h; _name = name; + _blindData = bd; } public String getName() { return _name != null ? @@ -107,6 +121,15 @@ class LookupDestJob extends JobImpl { } public void runJob() { + if (_blindData != null) { + Destination d = _blindData.getDestination(); + if (d != null) { + if (_log.shouldDebug()) + _log.debug("Found cached b33 lookup " + _name + " to " + d); + returnDest(d); + return; + } + } if (_name != null) { // inline, ignore timeout Destination d = getContext().namingService().lookup(_name); @@ -121,6 +144,7 @@ class LookupDestJob extends JobImpl { } } else { DoneJob done = new DoneJob(getContext()); + // TODO tell router this is an encrypted lookup, skip 38 or earlier ffs? getContext().netDb().lookupDestination(_hash, done, _timeout, _fromLocalDest); } } @@ -132,6 +156,18 @@ class LookupDestJob extends JobImpl { public String getName() { return "LeaseSet Lookup Reply to Client"; } public void runJob() { Destination dest = getContext().netDb().lookupDestinationLocally(_hash); + if (dest == null && _blindData != null) { + // TODO store and lookup original hash instead + LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(_hash); + if (ls != null && ls.getType() == DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) { + // already decrypted + EncryptedLeaseSet encls = (EncryptedLeaseSet) ls; + LeaseSet decls = encls.getDecryptedLeaseSet(); + if (decls != null) { + dest = decls.getDestination(); + } + } + } if (dest != null) { if (_log.shouldDebug()) _log.debug("Found hash lookup " + _hash + " to " + dest); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/BlindCache.java b/router/java/src/net/i2p/router/networkdb/kademlia/BlindCache.java new file mode 100644 index 000000000..83f25714d --- /dev/null +++ b/router/java/src/net/i2p/router/networkdb/kademlia/BlindCache.java @@ -0,0 +1,218 @@ +package net.i2p.router.networkdb.kademlia; + +import java.util.concurrent.ConcurrentHashMap; + +import net.i2p.crypto.Blinding; +import net.i2p.crypto.SigType; +import net.i2p.data.BlindData; +import net.i2p.data.Destination; +import net.i2p.data.Hash; +import net.i2p.data.SigningPublicKey; +import net.i2p.router.RouterContext; + +/** + * Cache of blinding data. See proposal 123. + * + * @since 0.9.40 + */ +class BlindCache { + + private final RouterContext _context; + // unblinded key + private final ConcurrentHashMap _cache; + // blinded key + private final ConcurrentHashMap _reverseCache; + // dest hash + private final ConcurrentHashMap _hashCache; + + /** + * Caller MUST call startup() to load persistent cache from disk + */ + public BlindCache(RouterContext ctx) { + _context = ctx; + _cache = new ConcurrentHashMap(32); + _reverseCache = new ConcurrentHashMap(32); + _hashCache = new ConcurrentHashMap(32); + } + + /** + * May be restarted by calling startup() again. + */ + public synchronized void shutdown() { + store(); + _cache.clear(); + _reverseCache.clear(); + _hashCache.clear(); + } + + public synchronized void startup() { + load(); + } + + /** + * The hash to lookup for a dest. + * If known to be blinded, returns the current blinded hash. + * If not known to be blinded, returns the standard dest hash. + * + * @param dest may or may not be blinded + * @return the unblinded or blinded hash + */ + public Hash getHash(Destination dest) { + Hash rv = getBlindedHash(dest); + if (rv != null) + return rv; + return dest.getHash(); + } + + /** + * The hash to lookup for a dest hash. + * If known to be blinded, returns the current blinded hash. + * If not known to be blinded, returns h. + * + * @param dest may or may not be blinded + * @return the blinded hash or h + */ + public Hash getHash(Hash h) { + BlindData bd = _hashCache.get(h); + if (bd != null) + return bd.getBlindedHash(); + return h; + } + + /** + * The hash to lookup for a dest. + * If known to be blinded, returns the current blinded hash. + * If not known to be blinded, returns null. + * + * @param dest may or may not be blinded + * @return the blinded hash or null if not blinded + */ + public Hash getBlindedHash(Destination dest) { + BlindData bd = _cache.get(dest.getSigningPublicKey()); + if (bd != null) + return bd.getBlindedHash(); + return null; + } + + /** + * The hash to lookup for a SPK known to be blinded. + * Default blinded type assumed. + * Secret assumed null. + * + * @param spk known to be blinded + * @return the blinded hash + * @throws IllegalArgumentException on various errors + */ + public Hash getBlindedHash(SigningPublicKey spk) { + BlindData bd = _cache.get(spk); + if (bd == null) + bd = new BlindData(_context, spk, Blinding.getDefaultBlindedType(spk.getType()), null); + addToCache(bd); + return bd.getBlindedHash(); + } + + /** + * Mark a destination as known to be blinded + * + * @param dest known to be blinded + * @param blindedType null for default + * @param secret may be null + * @throws IllegalArgumentException on various errors + */ + public void setBlinded(Destination dest, SigType blindedType, String secret) { + SigningPublicKey spk = dest.getSigningPublicKey(); + BlindData bd = _cache.get(spk); + if (bd != null) { + bd.setDestination(dest); + } else { + if (blindedType == null) + blindedType = Blinding.getDefaultBlindedType(spk.getType()); + bd = new BlindData(_context, dest, blindedType, secret); + bd.setDestination(dest); + addToCache(bd); + } + } + + /** + * Add the destination to the cache entry. + * Must previously be in cache. + * + * @param dest known to be blinded + * @throws IllegalArgumentException on various errors + */ + public void setBlinded(Destination dest) { + SigningPublicKey spk = dest.getSigningPublicKey(); + BlindData bd = _cache.get(spk); + if (bd != null) { + bd.setDestination(dest); + _hashCache.putIfAbsent(dest.getHash(), bd); + } + } + + public void addToCache(BlindData bd) { + _cache.put(bd.getUnblindedPubKey(), bd); + _reverseCache.put(bd.getBlindedPubKey(), bd); + Destination dest = bd.getDestination(); + if (dest != null) + _hashCache.put(dest.getHash(), bd); + } + + /** + * The cached data or null + */ + public BlindData getData(Destination dest) { + BlindData rv = getData(dest.getSigningPublicKey()); + if (rv != null) { + Destination d = rv.getDestination(); + if (d == null) + rv.setDestination(dest); + else if (!dest.equals(d)) + rv = null; // mismatch ??? + } + return rv; + } + + /** + * The cached data or null + * + * @param spk the unblinded public key + */ + public BlindData getData(SigningPublicKey spk) { + SigType type = spk.getType(); + if (type != SigType.EdDSA_SHA512_Ed25519 && + type != SigType.RedDSA_SHA512_Ed25519) + return null; + return _cache.get(spk); + } + + /** + * The cached data or null + * + * @param spk the blinded public key + */ + public BlindData getReverseData(SigningPublicKey spk) { + SigType type = spk.getType(); + if (type != SigType.RedDSA_SHA512_Ed25519) + return null; + return _reverseCache.get(spk); + } + + /** + * Refresh all the data at midnight + * + */ + public void rollover() { + _reverseCache.clear(); + for (BlindData bd : _cache.values()) { + _reverseCache.put(bd.getBlindedPubKey(), bd); + } + } + + private void load() { + // TODO + } + + private void store() { + // TODO + } +} diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index 819f2a766..4141cf3b4 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -21,15 +21,18 @@ import java.util.Set; import net.i2p.crypto.SigAlgo; import net.i2p.crypto.SigType; +import net.i2p.data.BlindData; import net.i2p.data.Certificate; import net.i2p.data.DatabaseEntry; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; +import net.i2p.data.EncryptedLeaseSet; import net.i2p.data.Hash; import net.i2p.data.KeyCertificate; import net.i2p.data.LeaseSet; import net.i2p.data.LeaseSet2; +import net.i2p.data.SigningPublicKey; import net.i2p.data.i2np.DatabaseLookupMessage; import net.i2p.data.i2np.DatabaseStoreMessage; import net.i2p.data.router.RouterAddress; @@ -73,6 +76,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad private volatile long _lastRIPublishTime; private NegativeLookupCache _negativeCache; protected final int _networkID; + private final BlindCache _blindCache; /** * Map of Hash to RepublishLeaseSetJob for leases we'realready managing. @@ -171,6 +175,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad _publishingLeaseSets = new HashMap(8); _activeRequests = new HashMap(8); _reseedChecker = new ReseedChecker(context); + _blindCache = new BlindCache(context); context.statManager().createRateStat("netDb.lookupDeferred", "how many lookups are deferred?", "NetworkDatabase", new long[] { 60*60*1000 }); context.statManager().createRateStat("netDb.exploreKeySet", "how many keys are queued for exploration?", "NetworkDatabase", new long[] { 60*60*1000 }); context.statManager().createRateStat("netDb.negativeCache", "Aborted lookup, already cached", "NetworkDatabase", new long[] { 60*60*1000l }); @@ -246,7 +251,9 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad //_ds = null; _exploreKeys.clear(); // hope this doesn't cause an explosion, it shouldn't. // _exploreKeys = null; - _negativeCache.clear(); + if (_negativeCache != null) + _negativeCache.clear(); + _blindCache.shutdown(); } public synchronized void restart() { @@ -257,6 +264,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad } _ds.restart(); _exploreKeys.clear(); + _blindCache.startup(); _initialized = true; @@ -287,6 +295,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad // _exploreKeys = new HashSet(64); _dbDir = dbDir; _negativeCache = new NegativeLookupCache(_context); + _blindCache.startup(); createHandlers(); @@ -463,6 +472,27 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad return _kb.size(); } + /** + * @param spk unblinded key + * @return BlindData or null + * @since 0.9.40 + */ + @Override + public BlindData getBlindData(SigningPublicKey spk) { + return _blindCache.getData(spk); + } + + /** + * @param bd new BlindData to put in the cache + * @since 0.9.40 + */ + @Override + public void setBlindData(BlindData bd) { + if (_log.shouldWarn()) + _log.warn("Adding to blind cache: " + bd); + _blindCache.addToCache(bd); + } + /** * @return RouterInfo, LeaseSet, or null, validated * @since 0.8.3 @@ -476,10 +506,12 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad int type = rv.getType(); if (DatabaseEntry.isLeaseSet(type)) { LeaseSet ls = (LeaseSet)rv; - if (ls.isCurrent(Router.CLOCK_FUDGE_FACTOR)) + if (ls.isCurrent(Router.CLOCK_FUDGE_FACTOR)) { return rv; - else + } else { + key = _blindCache.getHash(key); fail(key); + } } else if (type == DatabaseEntry.KEY_TYPE_ROUTERINFO) { try { if (validate((RouterInfo)rv) == null) @@ -533,6 +565,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad } else { //if (_log.shouldLog(Log.DEBUG)) // _log.debug("leaseSet not found locally, running search"); + key = _blindCache.getHash(key); search(key, onFindJob, onFailedLookupJob, timeoutMs, true, fromLocalDest); } //if (_log.shouldLog(Log.DEBUG)) @@ -549,6 +582,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad */ public void lookupLeaseSetRemotely(Hash key, Hash fromLocalDest) { if (!_initialized) return; + key = _blindCache.getHash(key); search(key, null, null, 20*1000, true, fromLocalDest); } @@ -564,6 +598,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad if (ls.isCurrent(Router.CLOCK_FUDGE_FACTOR)) { return ls; } else { + key = _blindCache.getHash(key); fail(key); // this was an interesting key, so either refetch it or simply explore with it _exploreKeys.add(key); @@ -599,6 +634,7 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad _log.info("Negative cached, not searching dest: " + key); _context.jobQueue().addJob(onFinishedJob); } else { + key = _blindCache.getHash(key); search(key, onFinishedJob, onFinishedJob, timeoutMs, true, fromLocalDest); } } @@ -892,12 +928,47 @@ public abstract class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacad if (rv != null && !leaseSet.getDestination().equals(rv.getDestination())) throw new IllegalArgumentException("LS Hash collision"); + EncryptedLeaseSet encls = null; + if (leaseSet.getType() == DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) { + // set dest or key before validate() calls verifySignature() which + // will do the decryption + BlindData bd = _blindCache.getReverseData(leaseSet.getSigningKey()); + if (bd != null) { + if (_log.shouldWarn()) + _log.warn("Found blind data for encls: " + bd); + encls = (EncryptedLeaseSet) leaseSet; + Destination dest = bd.getDestination(); + if (dest != null) { + encls.setDestination(dest); + } else { + encls.setSigningKey(bd.getUnblindedPubKey()); + } + } else { + if (_log.shouldWarn()) + _log.warn("No blind data found for encls: " + encls); + } + } + + String err = validate(key, leaseSet); if (err != null) throw new IllegalArgumentException("Invalid store attempt - " + err); _ds.put(key, leaseSet); + if (encls != null) { + // we now have decrypted it, store it as well + LeaseSet decls = encls.getDecryptedLeaseSet(); + if (decls != null) { + if (_log.shouldWarn()) + _log.warn("Successfully decrypted encls: " + decls); + // recursion + Destination dest = decls.getDestination(); + store(dest.getHash(), decls); + _blindCache.setBlinded(dest); + } + } + // Iterate through the old failure / success count, copying over the old // values (if any tunnels overlap between leaseSets). no need to be // ueberthreadsafe fascists here, since these values are just heuristics