forked from I2P_Developers/i2p.i2p
I2CP: New Blinding Info message (proposal 123)
client- and router-side support new session lookupDest2() method return new b33 failure codes from lookup show b33 alternates in tools stub out support in HTTP client
This commit is contained in:
@ -24,12 +24,15 @@ import net.i2p.app.ClientApp;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.app.Outproxy;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.LookupResult;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.crypto.Blinding;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.BlindData;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
@ -1231,7 +1234,25 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
} else if (len >= 64) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("lookup b33 in-session " + destination);
|
||||
clientDest = sess.lookupDest(destination, 20*1000);
|
||||
try {
|
||||
BlindData bd = Blinding.decode(_context, destination);
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Resolved b33 " + bd);
|
||||
// TESTING
|
||||
//sess.sendBlindingInfo(bd, 24*60*60*1000);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Unable to resolve b33 " + destination, iae);
|
||||
// TODO new error page
|
||||
}
|
||||
LookupResult lresult = sess.lookupDest2(destination, 20*1000);
|
||||
clientDest = lresult.getDestination();
|
||||
int code = lresult.getResultCode();
|
||||
if (code != 0) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Unable to resolve b33 " + destination + " error code " + code);
|
||||
// TODO new form
|
||||
}
|
||||
} else {
|
||||
// 61-63 chars, this won't work
|
||||
clientDest = _context.namingService().lookup(destination);
|
||||
|
@ -14,6 +14,7 @@ import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.BlindData;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PrivateKey;
|
||||
@ -409,6 +410,17 @@ public interface I2PSession {
|
||||
*/
|
||||
public Destination lookupDest(String name, long maxWait) throws I2PSessionException;
|
||||
|
||||
/**
|
||||
* Ask the router to lookup a Destination by host name.
|
||||
* Blocking. See above for details.
|
||||
* Same as lookupDest() but with a failure code in the return value
|
||||
*
|
||||
* @param maxWait ms
|
||||
* @since 0.9.43
|
||||
* @return non-null
|
||||
*/
|
||||
public LookupResult lookupDest2(String name, long maxWait) throws I2PSessionException;
|
||||
|
||||
/**
|
||||
* Pass updated options to the router.
|
||||
* Does not remove properties previously present but missing from this options parameter.
|
||||
@ -425,6 +437,13 @@ public interface I2PSession {
|
||||
*/
|
||||
public int[] bandwidthLimits() throws I2PSessionException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param expiration ms from now, 0 means forever
|
||||
* @since 0.9.43
|
||||
*/
|
||||
public void sendBlindingInfo(BlindData bd, int expiration) throws I2PSessionException;
|
||||
|
||||
/**
|
||||
* Listen on specified protocol and port.
|
||||
*
|
||||
|
53
core/java/src/net/i2p/client/LookupResult.java
Normal file
53
core/java/src/net/i2p/client/LookupResult.java
Normal file
@ -0,0 +1,53 @@
|
||||
package net.i2p.client;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.i2cp.HostReplyMessage;
|
||||
|
||||
/**
|
||||
* The return value of I2PSession.lookupDest2()
|
||||
*
|
||||
* @since 0.9.43
|
||||
*/
|
||||
public interface LookupResult {
|
||||
|
||||
/** getDestination() will be non-null */
|
||||
public static final int RESULT_SUCCESS = HostReplyMessage.RESULT_SUCCESS;
|
||||
|
||||
/** general failure, probably a local hostname lookup failure, or a b32 lookup timeout */
|
||||
public static final int RESULT_FAILURE = HostReplyMessage.RESULT_FAILURE;
|
||||
|
||||
/**
|
||||
* b33 requires a lookup password but the router does not have it cached;
|
||||
* please supply in a blinding info message
|
||||
*/
|
||||
public static final int RESULT_SECRET_REQUIRED = HostReplyMessage.RESULT_SECRET_REQUIRED;
|
||||
|
||||
/**
|
||||
* b33 requires per-client auth private key but the router does not have it cached;
|
||||
* please supply in a blinding info message
|
||||
*/
|
||||
public static final int RESULT_KEY_REQUIRED = HostReplyMessage.RESULT_KEY_REQUIRED;
|
||||
|
||||
/**
|
||||
* b33 requires a lookup password and per-client auth private key but the router does not have them cached;
|
||||
* please supply in a blinding info message
|
||||
*/
|
||||
public static final int RESULT_SECRET_AND_KEY_REQUIRED = HostReplyMessage.RESULT_SECRET_AND_KEY_REQUIRED;
|
||||
|
||||
/**
|
||||
* b33 requires per-client auth private key, the router has a key, but decryption failed;
|
||||
* please supply a new key in a blinding info message
|
||||
*/
|
||||
public static final int RESULT_DECRYPTION_FAILURE = HostReplyMessage.RESULT_DECRYPTION_FAILURE;
|
||||
|
||||
/**
|
||||
* @return zero for success, nonzero for failure
|
||||
*/
|
||||
public int getResultCode();
|
||||
|
||||
/**
|
||||
* @return Destination on success, null on failure
|
||||
*/
|
||||
public Destination getDestination();
|
||||
|
||||
}
|
@ -32,7 +32,7 @@ class HostReplyMessageHandler extends HandlerImpl {
|
||||
if (d != null) {
|
||||
session.destReceived(id, d);
|
||||
} else {
|
||||
session.destLookupFailed(id);
|
||||
session.destLookupFailed(id, msg.getResultCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,9 +38,11 @@ import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.client.LookupResult;
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.BlindData;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
@ -50,6 +52,7 @@ import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.data.i2cp.BlindingInfoMessage;
|
||||
import net.i2p.data.i2cp.DestLookupMessage;
|
||||
import net.i2p.data.i2cp.DestReplyMessage;
|
||||
import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
|
||||
@ -198,6 +201,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
private volatile boolean _routerSupportsFastReceive;
|
||||
private volatile boolean _routerSupportsHostLookup;
|
||||
private volatile boolean _routerSupportsLS2;
|
||||
private volatile boolean _routerSupportsBlindingInfo;
|
||||
|
||||
protected static final int CACHE_MAX_SIZE = SystemVersion.isAndroid() ? 32 : 128;
|
||||
/**
|
||||
@ -206,7 +210,8 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
*/
|
||||
private static final Map<Object, Destination> _lookupCache = new LHMCache<Object, Destination>(CACHE_MAX_SIZE);
|
||||
private static final String MIN_HOST_LOOKUP_VERSION = "0.9.11";
|
||||
private static final boolean TEST_LOOKUP = false;
|
||||
// todo change to false for release
|
||||
private static final boolean TEST_BLINDINFO = true;
|
||||
|
||||
/** SSL interface (only) @since 0.8.3 */
|
||||
protected static final String PROP_ENABLE_SSL = "i2cp.SSL";
|
||||
@ -225,6 +230,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
|
||||
private static final String MIN_FAST_VERSION = "0.9.4";
|
||||
private static final String MIN_LS2_VERSION = "0.9.38";
|
||||
private static final String MIN_BLINDINFO_VERSION = "0.9.43";
|
||||
|
||||
/** @param routerVersion as rcvd in the SetDateMessage, may be null for very old routers */
|
||||
void dateUpdated(String routerVersion) {
|
||||
@ -233,7 +239,6 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
(routerVersion != null && routerVersion.length() > 0 &&
|
||||
VersionComparator.comp(routerVersion, MIN_FAST_VERSION) >= 0);
|
||||
_routerSupportsHostLookup = isrc ||
|
||||
TEST_LOOKUP ||
|
||||
(routerVersion != null && routerVersion.length() > 0 &&
|
||||
VersionComparator.comp(routerVersion, MIN_HOST_LOOKUP_VERSION) >= 0);
|
||||
_routerSupportsSubsessions = isrc ||
|
||||
@ -242,6 +247,10 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
_routerSupportsLS2 = isrc ||
|
||||
(routerVersion != null && routerVersion.length() > 0 &&
|
||||
VersionComparator.comp(routerVersion, MIN_LS2_VERSION) >= 0);
|
||||
_routerSupportsBlindingInfo = isrc ||
|
||||
TEST_BLINDINFO ||
|
||||
(routerVersion != null && routerVersion.length() > 0 &&
|
||||
VersionComparator.comp(routerVersion, MIN_BLINDINFO_VERSION) >= 0);
|
||||
synchronized (_stateLock) {
|
||||
if (_state == State.OPENING) {
|
||||
changeState(State.GOTDATE);
|
||||
@ -252,6 +261,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
public static final int LISTEN_PORT = I2PClient.DEFAULT_LISTEN_PORT;
|
||||
|
||||
private static final int BUF_SIZE = 32*1024;
|
||||
private static final SessionId DUMMY_SESSION = new SessionId(65535);
|
||||
|
||||
/**
|
||||
* for extension by SimpleSession (no dest)
|
||||
@ -1500,6 +1510,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
if (h.equals(w.hash)) {
|
||||
synchronized (w) {
|
||||
w.destination = d;
|
||||
w.code = LookupResult.RESULT_SUCCESS;
|
||||
w.notifyAll();
|
||||
}
|
||||
}
|
||||
@ -1515,6 +1526,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
for (LookupWaiter w : _pendingLookups) {
|
||||
if (h.equals(w.hash)) {
|
||||
synchronized (w) {
|
||||
w.code = LookupResult.RESULT_FAILURE;
|
||||
w.notifyAll();
|
||||
}
|
||||
}
|
||||
@ -1539,6 +1551,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
}
|
||||
synchronized (w) {
|
||||
w.destination = d;
|
||||
w.code = LookupResult.RESULT_SUCCESS;
|
||||
w.notifyAll();
|
||||
}
|
||||
}
|
||||
@ -1550,10 +1563,11 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
* on reception of HostReplyMessage
|
||||
* @since 0.9.11
|
||||
*/
|
||||
void destLookupFailed(long nonce) {
|
||||
void destLookupFailed(long nonce, int code) {
|
||||
for (LookupWaiter w : _pendingLookups) {
|
||||
if (nonce == w.nonce) {
|
||||
synchronized (w) {
|
||||
w.code = code;
|
||||
w.notifyAll();
|
||||
}
|
||||
}
|
||||
@ -1581,6 +1595,11 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
public final long nonce;
|
||||
/** the reply; synch on this */
|
||||
public Destination destination;
|
||||
/**
|
||||
* the return code; sync on this
|
||||
* @since 0.9.43
|
||||
*/
|
||||
public int code;
|
||||
|
||||
public LookupWaiter(Hash h) {
|
||||
this(h, -1);
|
||||
@ -1599,6 +1618,16 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
this.name = name;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
/** Dummy, completed
|
||||
* @since 0.9.43
|
||||
*/
|
||||
public LookupWaiter(Destination d) {
|
||||
hash = null;
|
||||
name = null;
|
||||
nonce = 0;
|
||||
destination = d;
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.9.20 */
|
||||
@ -1657,7 +1686,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
_log.info("Sending HostLookup for " + h);
|
||||
SessionId id = _sessionId;
|
||||
if (id == null)
|
||||
id = new SessionId(65535);
|
||||
id = DUMMY_SESSION;
|
||||
sendMessage_unchecked(new HostLookupMessage(id, h, nonce, maxWait));
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@ -1708,12 +1737,50 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
* @return null on failure
|
||||
*/
|
||||
public Destination lookupDest(String name, long maxWait) throws I2PSessionException {
|
||||
LookupWaiter waiter = x_lookupDest(name, maxWait);
|
||||
if (waiter == null)
|
||||
return null;
|
||||
synchronized(waiter) {
|
||||
return waiter.destination;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the router to lookup a Destination by host name.
|
||||
* Blocking. See above for details.
|
||||
* Same as lookupDest() but with a failure code in the return value
|
||||
*
|
||||
* @param maxWait ms
|
||||
* @since 0.9.43
|
||||
* @return non-null
|
||||
*/
|
||||
public LookupResult lookupDest2(String name, long maxWait) throws I2PSessionException {
|
||||
LookupWaiter waiter = x_lookupDest(name, maxWait);
|
||||
if (waiter == null)
|
||||
return new LkupResult(LookupResult.RESULT_FAILURE, null);
|
||||
synchronized(waiter) {
|
||||
int code = waiter.code;
|
||||
Destination d = waiter.destination;
|
||||
if (d == null && code == LookupResult.RESULT_SUCCESS)
|
||||
code = LookupResult.RESULT_FAILURE;
|
||||
return new LkupResult(code, d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the router to lookup a Destination by host name.
|
||||
* Blocking. See above for details.
|
||||
* @param maxWait ms
|
||||
* @since 0.9.11
|
||||
* @return null on failure
|
||||
*/
|
||||
private LookupWaiter x_lookupDest(String name, long maxWait) throws I2PSessionException {
|
||||
if (name.length() == 0)
|
||||
return null;
|
||||
// Shortcut for b64
|
||||
if (name.length() >= 516) {
|
||||
try {
|
||||
return new Destination(name);
|
||||
return new LookupWaiter(new Destination(name));
|
||||
} catch (DataFormatException dfe) {
|
||||
return null;
|
||||
}
|
||||
@ -1724,7 +1791,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
synchronized (_lookupCache) {
|
||||
Destination rv = _lookupCache.get(name);
|
||||
if (rv != null)
|
||||
return rv;
|
||||
return new LookupWaiter(rv);
|
||||
}
|
||||
if (isClosed()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@ -1734,7 +1801,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
if (!_routerSupportsHostLookup) {
|
||||
// do them a favor and convert to Hash lookup
|
||||
if (name.length() == 60 && name.toLowerCase(Locale.US).endsWith(".b32.i2p"))
|
||||
return lookupDest(Hash.create(Base32.decode(name.toLowerCase(Locale.US).substring(0, 52))), maxWait);
|
||||
return new LookupWaiter(lookupDest(Hash.create(Base32.decode(name.toLowerCase(Locale.US).substring(0, 52))), maxWait));
|
||||
// else unsupported
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Router does not support HostLookup for " + name);
|
||||
@ -1743,18 +1810,17 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
int nonce = _lookupID.incrementAndGet() & 0x7fffffff;
|
||||
LookupWaiter waiter = new LookupWaiter(name, nonce);
|
||||
_pendingLookups.offer(waiter);
|
||||
Destination rv = null;
|
||||
try {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending HostLookup for " + name);
|
||||
SessionId id = _sessionId;
|
||||
if (id == null)
|
||||
id = new SessionId(65535);
|
||||
id = DUMMY_SESSION;
|
||||
sendMessage_unchecked(new HostLookupMessage(id, name, nonce, maxWait));
|
||||
try {
|
||||
synchronized (waiter) {
|
||||
waiter.wait(maxWait);
|
||||
rv = waiter.destination;
|
||||
return waiter;
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
throw new I2PSessionException("Interrupted", ie);
|
||||
@ -1762,7 +1828,6 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
} finally {
|
||||
_pendingLookups.remove(waiter);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1793,6 +1858,22 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
|
||||
return _bwLimits;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param expiration ms from now, 0 means forever
|
||||
* @since 0.9.43
|
||||
*/
|
||||
public void sendBlindingInfo(BlindData bd, int expiration) throws I2PSessionException {
|
||||
if (!_routerSupportsBlindingInfo)
|
||||
throw new I2PSessionException("Router does not support BlindingInfo");
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Sending BlindingInfo");
|
||||
SessionId id = _sessionId;
|
||||
if (id == null)
|
||||
id = DUMMY_SESSION;
|
||||
sendMessage_unchecked(new BlindingInfoMessage(bd, id, expiration));
|
||||
}
|
||||
|
||||
protected void updateActivity() {
|
||||
_lastActivity = _context.clock().now();
|
||||
if (_isReduced) {
|
||||
|
31
core/java/src/net/i2p/client/impl/LkupResult.java
Normal file
31
core/java/src/net/i2p/client/impl/LkupResult.java
Normal file
@ -0,0 +1,31 @@
|
||||
package net.i2p.client.impl;
|
||||
|
||||
import net.i2p.client.LookupResult;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
* The return value of I2PSession.lookupDest2()
|
||||
*
|
||||
* @since 0.9.43
|
||||
*/
|
||||
public class LkupResult implements LookupResult {
|
||||
|
||||
private final int _code;
|
||||
private final Destination _dest;
|
||||
|
||||
LkupResult(int code, Destination dest) {
|
||||
_code = code;
|
||||
_dest = dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return zero for success, nonzero for failure
|
||||
*/
|
||||
public int getResultCode() { return _code; }
|
||||
|
||||
/**
|
||||
* @return Destination on success, null on failure
|
||||
*/
|
||||
public Destination getDestination() { return _dest; }
|
||||
|
||||
}
|
@ -263,8 +263,8 @@ class SubSession extends I2PSessionMuxedImpl {
|
||||
* on reception of HostReplyMessage
|
||||
*/
|
||||
@Override
|
||||
void destLookupFailed(long nonce) {
|
||||
_primary.destLookupFailed(nonce);
|
||||
void destLookupFailed(long nonce, int code) {
|
||||
_primary.destLookupFailed(nonce, code);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -199,6 +199,7 @@ public final class Blinding {
|
||||
* See proposal 149.
|
||||
*
|
||||
* @param address ending with ".b32.i2p"
|
||||
* @return BlindData structure, use getUnblindedPubKey() for the result
|
||||
* @throws IllegalArgumentException on bad inputs or unsupported SigTypes
|
||||
* @since 0.9.40
|
||||
*/
|
||||
|
@ -278,6 +278,7 @@ public class BlindData {
|
||||
buf.append("[BlindData: ");
|
||||
buf.append("\n\tSigningPublicKey: ").append(_clearSPK);
|
||||
buf.append("\n\tAlpha : ").append(_alpha);
|
||||
buf.append("\n\tAlpha valid for : ").append((new Date(_date)).toString());
|
||||
buf.append("\n\tBlindedPublicKey: ").append(_blindSPK);
|
||||
buf.append("\n\tBlinded Hash : ").append(_blindHash);
|
||||
if (_secret != null)
|
||||
@ -298,7 +299,12 @@ public class BlindData {
|
||||
else
|
||||
buf.append("\n\tDestination : unknown");
|
||||
buf.append("\n\tB32 : ").append(toBase32());
|
||||
buf.append("\n\tCreated : ").append((new Date(_date)).toString());
|
||||
if (!_authRequired)
|
||||
buf.append("\n\t + auth : ").append(Blinding.encode(_clearSPK, _secretRequired, true));
|
||||
if (!_secretRequired)
|
||||
buf.append("\n\t + secret : ").append(Blinding.encode(_clearSPK, true, _authRequired));
|
||||
if (!(_authRequired || _secretRequired))
|
||||
buf.append("\n\t + auth,secret : ").append(Blinding.encode(_clearSPK, true, true));
|
||||
buf.append(']');
|
||||
return buf.toString();
|
||||
}
|
||||
|
@ -845,7 +845,7 @@ public class PrivateKeyFile {
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder(128);
|
||||
s.append("Dest: ");
|
||||
s.append("Destination: ");
|
||||
s.append(this.dest != null ? this.dest.toBase64() : "null");
|
||||
s.append("\nB32 : ");
|
||||
s.append(this.dest != null ? this.dest.toBase32() : "null");
|
||||
@ -856,6 +856,9 @@ public class PrivateKeyFile {
|
||||
type == SigType.RedDSA_SHA512_Ed25519) {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
s.append("\nBlinded B32: ").append(Blinding.encode(spk));
|
||||
s.append("\n + auth key: ").append(Blinding.encode(spk, false, true));
|
||||
s.append("\n + password: ").append(Blinding.encode(spk, true, false));
|
||||
s.append("\n + auth/pw : ").append(Blinding.encode(spk, true, true));
|
||||
}
|
||||
}
|
||||
s.append("\nContains: ");
|
||||
|
370
core/java/src/net/i2p/data/i2cp/BlindingInfoMessage.java
Normal file
370
core/java/src/net/i2p/data/i2cp/BlindingInfoMessage.java
Normal file
@ -0,0 +1,370 @@
|
||||
package net.i2p.data.i2cp;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.BlindData;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
|
||||
/**
|
||||
* Advise the router that the endpoint is blinded.
|
||||
* Client to router. There is no reply.
|
||||
* Preliminary - subject to change -
|
||||
* See proposal 123.
|
||||
*
|
||||
* @see BlindData
|
||||
* @see net.i2p.crypto.Blinding
|
||||
*
|
||||
* @since 0.9.43; do not send to routers older than 0.9.43.
|
||||
*/
|
||||
public class BlindingInfoMessage extends I2CPMessageImpl {
|
||||
public final static int MESSAGE_TYPE = 42;
|
||||
|
||||
private SessionId _sessionId;
|
||||
private int _endpointType;
|
||||
private int _authType;
|
||||
private SigType _blindType;
|
||||
private long _expiration;
|
||||
private Hash _hash;
|
||||
private String _host;
|
||||
private Destination _dest;
|
||||
private SigningPublicKey _pubkey;
|
||||
private PrivateKey _privkey;
|
||||
private String _secret;
|
||||
private BlindData _blindData;
|
||||
|
||||
private static final int FLAG_AUTH = 0x0f;
|
||||
private static final int FLAG_SECRET = 0x10;
|
||||
|
||||
public static final int TYPE_HASH = 0;
|
||||
public static final int TYPE_HOST = 1;
|
||||
public static final int TYPE_DEST = 2;
|
||||
public static final int TYPE_KEY = 3;
|
||||
|
||||
public BlindingInfoMessage() {}
|
||||
|
||||
/**
|
||||
* @param expiration ms from now or 0 for forever
|
||||
*/
|
||||
public BlindingInfoMessage(BlindData bd,
|
||||
SessionId id, int expiration) {
|
||||
this(id, expiration, bd.getAuthType(), bd.getBlindedSigType(), bd.getAuthPrivKey(), bd.getSecret());
|
||||
Destination dest = bd.getDestination();
|
||||
if (dest != null) {
|
||||
_dest = dest;
|
||||
_hash = dest.calculateHash();
|
||||
_pubkey = dest.getSigningPublicKey();
|
||||
_endpointType = TYPE_DEST;
|
||||
} else if (bd.getUnblindedPubKey() != null) {
|
||||
_pubkey = bd.getUnblindedPubKey();
|
||||
_endpointType = TYPE_KEY;
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
_blindData = bd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authType 0 (none), 1 (DH), 3 (PSK)
|
||||
* @param expiration ms from now or 0 for forever
|
||||
* @param privKey null for auth none, non-null for DH/PSK
|
||||
* @param secret may be null
|
||||
*/
|
||||
public BlindingInfoMessage(Hash h,
|
||||
SessionId id, int expiration,
|
||||
int authType, SigType blindType,
|
||||
PrivateKey privKey, String secret) {
|
||||
this(id, expiration, authType, blindType, privKey, secret);
|
||||
if (h == null || h.getData() == null)
|
||||
throw new IllegalArgumentException();
|
||||
_hash = h;
|
||||
_endpointType = TYPE_HASH;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param h hostname
|
||||
* @param authType 0 (none), 1 (DH), 3 (PSK)
|
||||
* @param expiration ms from now or 0 for forever
|
||||
* @param privKey null for auth none, non-null for DH/PSK
|
||||
* @param secret may be null
|
||||
*/
|
||||
public BlindingInfoMessage(String h,
|
||||
SessionId id, int expiration,
|
||||
int authType, SigType blindType,
|
||||
PrivateKey privKey, String secret) {
|
||||
this(id, expiration, authType, blindType, privKey, secret);
|
||||
if (h == null)
|
||||
throw new IllegalArgumentException();
|
||||
_host = h;
|
||||
_endpointType = TYPE_HOST;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authType 0 (none), 1 (DH), 3 (PSK)
|
||||
* @param expiration ms from now or 0 for forever
|
||||
* @param privKey null for auth none, non-null for DH/PSK
|
||||
* @param secret may be null
|
||||
*/
|
||||
public BlindingInfoMessage(Destination d,
|
||||
SessionId id, int expiration,
|
||||
int authType, SigType blindType,
|
||||
PrivateKey privKey, String secret) {
|
||||
this(id, expiration, authType, blindType, privKey, secret);
|
||||
if (d == null || d.getSigningPublicKey() == null)
|
||||
throw new IllegalArgumentException();
|
||||
_dest = d;
|
||||
_hash = d.calculateHash();
|
||||
_pubkey = d.getSigningPublicKey();
|
||||
_endpointType = TYPE_DEST;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authType 0 (none), 1 (DH), 3 (PSK)
|
||||
* @param expiration ms from now or 0 for forever
|
||||
* @param privKey null for auth none, non-null for DH/PSK
|
||||
* @param secret may be null
|
||||
*/
|
||||
public BlindingInfoMessage(SigningPublicKey s,
|
||||
SessionId id, int expiration,
|
||||
int authType, SigType blindType,
|
||||
PrivateKey privKey, String secret) {
|
||||
this(id, expiration, authType, blindType, privKey, secret);
|
||||
if (s == null || s.getData() == null)
|
||||
throw new IllegalArgumentException();
|
||||
_pubkey = s;
|
||||
_endpointType = TYPE_KEY;
|
||||
}
|
||||
|
||||
private BlindingInfoMessage(SessionId id, int expiration, int authType, SigType blindType,
|
||||
PrivateKey privKey, String secret) {
|
||||
if (id == null || blindType == null)
|
||||
throw new IllegalArgumentException();
|
||||
if (authType != BlindData.AUTH_NONE && authType != BlindData.AUTH_DH &&
|
||||
authType != BlindData.AUTH_PSK)
|
||||
throw new IllegalArgumentException();
|
||||
if (authType == BlindData.AUTH_NONE && privKey != null)
|
||||
throw new IllegalArgumentException("no key required");
|
||||
if (authType != BlindData.AUTH_NONE && privKey == null)
|
||||
throw new IllegalArgumentException("key required");
|
||||
_sessionId = id;
|
||||
_authType = authType;
|
||||
_blindType = blindType;
|
||||
if (expiration > 0)
|
||||
_expiration = expiration + I2PAppContext.getGlobalContext().clock().now();
|
||||
_privkey = privKey;
|
||||
_secret = secret;
|
||||
}
|
||||
|
||||
public SessionId getSessionId() {
|
||||
return _sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SessionId for this message.
|
||||
*/
|
||||
@Override
|
||||
public SessionId sessionId() {
|
||||
return _sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ms 1 to 2**32 - 1
|
||||
*/
|
||||
public long getTimeout() {
|
||||
return _expiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 (none), 1 (DH), 3 (PSK)
|
||||
*/
|
||||
public int getAuthType() {
|
||||
return _authType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 (hash) or 1 (host) or 2 (dest) or 3 (key)
|
||||
*/
|
||||
public int getEndpointType() {
|
||||
return _endpointType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return only valid if endpoint type == 0 or 2
|
||||
*/
|
||||
public Hash getHash() {
|
||||
return _hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return only valid if endpoint type == 1
|
||||
*/
|
||||
public String getHostname() {
|
||||
return _host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return only valid if endpoint type == 2
|
||||
*/
|
||||
public String getDestination() {
|
||||
return _host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return only valid if endpoint type == 2 or 3
|
||||
*/
|
||||
public SigningPublicKey getSigningPublicKey() {
|
||||
return _pubkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return private key or null
|
||||
*/
|
||||
public PrivateKey getPrivateKey() {
|
||||
return _privkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return secret or null
|
||||
*/
|
||||
public String getSecret() {
|
||||
return _secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return blind data or null if not enough info
|
||||
*/
|
||||
public BlindData getBlindData() {
|
||||
if (_blindData != null)
|
||||
return _blindData;
|
||||
if (_endpointType == TYPE_DEST)
|
||||
_blindData = new BlindData(I2PAppContext.getGlobalContext(), _dest, _blindType, _secret, _authType, _privkey);
|
||||
else if (_endpointType == TYPE_KEY)
|
||||
_blindData = new BlindData(I2PAppContext.getGlobalContext(), _pubkey, _blindType, _secret, _authType, _privkey);
|
||||
return _blindData;
|
||||
}
|
||||
|
||||
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
|
||||
try {
|
||||
_sessionId = new SessionId();
|
||||
_sessionId.readBytes(in);
|
||||
int flags = in.read();
|
||||
_authType = flags & FLAG_AUTH;
|
||||
boolean hasSecret = (flags & FLAG_SECRET) != 0;
|
||||
_endpointType = in.read();
|
||||
int bt = (int) DataHelper.readLong(in, 2);
|
||||
_blindType = SigType.getByCode(bt);
|
||||
if (_blindType == null)
|
||||
throw new I2CPMessageException("unsupported sig type " + bt);
|
||||
_expiration = DataHelper.readLong(in, 4);
|
||||
if (_endpointType == TYPE_HASH) {
|
||||
_hash = Hash.create(in);
|
||||
} else if (_endpointType == TYPE_HOST) {
|
||||
_host = DataHelper.readString(in);
|
||||
if (_host.length() == 0)
|
||||
throw new I2CPMessageException("bad host");
|
||||
} else if (_endpointType == TYPE_DEST) {
|
||||
_dest = Destination.create(in);
|
||||
} else if (_endpointType == TYPE_KEY) {
|
||||
int st = (int) DataHelper.readLong(in, 2);
|
||||
SigType sigt = SigType.getByCode(st);
|
||||
if (sigt == null)
|
||||
throw new I2CPMessageException("unsupported sig type " + st);
|
||||
int len = sigt.getPubkeyLen();
|
||||
byte[] key = new byte[len];
|
||||
DataHelper.read(in, key);
|
||||
_pubkey = new SigningPublicKey(sigt, key);
|
||||
} else {
|
||||
throw new I2CPMessageException("bad type");
|
||||
}
|
||||
if (_authType == BlindData.AUTH_DH || _authType == BlindData.AUTH_PSK) {
|
||||
byte[] key = new byte[32];
|
||||
DataHelper.read(in, key);
|
||||
_privkey = new PrivateKey(EncType.ECIES_X25519, key);
|
||||
} else if (_authType != BlindData.AUTH_NONE) {
|
||||
throw new I2CPMessageException("bad auth type " + _authType);
|
||||
}
|
||||
if (hasSecret)
|
||||
_secret = DataHelper.readString(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2CPMessageException("bad data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
|
||||
int len;
|
||||
if (_endpointType == TYPE_HASH) {
|
||||
if (_hash == null)
|
||||
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
|
||||
len = 11 + Hash.HASH_LENGTH;
|
||||
} else if (_endpointType == TYPE_HOST) {
|
||||
if (_host == null)
|
||||
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
|
||||
len = 12 + _host.length();
|
||||
} else {
|
||||
throw new I2CPMessageException("bad type");
|
||||
}
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(len);
|
||||
try {
|
||||
_sessionId.writeBytes(os);
|
||||
byte flags = (byte) _authType;
|
||||
if (_secret != null)
|
||||
flags |= FLAG_SECRET;
|
||||
os.write(flags);
|
||||
os.write((byte) _endpointType);
|
||||
DataHelper.writeLong(os, 2, _blindType.getCode());
|
||||
DataHelper.writeLong(os, 4, _expiration);
|
||||
if (_endpointType == TYPE_HASH) {
|
||||
_hash.writeBytes(os);
|
||||
} else if (_endpointType == TYPE_HOST) {
|
||||
DataHelper.writeString(os, _host);
|
||||
} else if (_endpointType == TYPE_DEST) {
|
||||
_dest.writeBytes(os);
|
||||
} else {
|
||||
DataHelper.writeLong(os, 2, _privkey.getType().getCode());
|
||||
os.write(_privkey.getData());
|
||||
DataHelper.writeString(os, _host);
|
||||
}
|
||||
if (_secret != null)
|
||||
DataHelper.writeString(os, _secret);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2CPMessageException("bad data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return MESSAGE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("[BlindingInfoMessage: ");
|
||||
buf.append("\n\tSession: ").append(_sessionId);
|
||||
buf.append("\n\tTimeout: ").append(_expiration);
|
||||
if (_endpointType == TYPE_HASH)
|
||||
buf.append("\n\tHash: ").append(_hash.toBase32());
|
||||
else if (_endpointType == TYPE_HOST)
|
||||
buf.append("\n\tHost: ").append(_host);
|
||||
else if (_endpointType == TYPE_DEST)
|
||||
buf.append("\n\tDest: ").append(_dest);
|
||||
else if (_endpointType == TYPE_KEY)
|
||||
buf.append("\n\tKey: ").append(_pubkey);
|
||||
if (_privkey != null)
|
||||
buf.append("\n\tPriv Key: ").append(_privkey);
|
||||
if (_secret != null)
|
||||
buf.append("\n\tSecret: ").append(_secret);
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
2019-09-10 zzz
|
||||
* I2CP: New Blinding Info message (proposal 123)
|
||||
|
||||
2019-09-08 zzz
|
||||
* Transport:
|
||||
- Don't automatically transition from firewalled
|
||||
|
@ -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 = 5;
|
||||
public final static long BUILD = 6;
|
||||
|
||||
/** for example "-test" */
|
||||
public final static String EXTRA = "";
|
||||
|
@ -15,6 +15,7 @@ import net.i2p.CoreVersion;
|
||||
import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.BlindData;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
@ -25,7 +26,9 @@ import net.i2p.data.LeaseSet2;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.data.i2cp.BandwidthLimitsMessage;
|
||||
import net.i2p.data.i2cp.BlindingInfoMessage;
|
||||
import net.i2p.data.i2cp.CreateLeaseSetMessage;
|
||||
import net.i2p.data.i2cp.CreateLeaseSet2Message;
|
||||
import net.i2p.data.i2cp.CreateSessionMessage;
|
||||
@ -152,6 +155,9 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
case GetBandwidthLimitsMessage.MESSAGE_TYPE:
|
||||
handleGetBWLimits((GetBandwidthLimitsMessage)message);
|
||||
break;
|
||||
case BlindingInfoMessage.MESSAGE_TYPE:
|
||||
handleBlindingInfo((BlindingInfoMessage)message);
|
||||
break;
|
||||
default:
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unhandled I2CP type received: " + message.getType());
|
||||
@ -774,6 +780,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
* Divide router limit by 1.75 for overhead.
|
||||
* This could someday give a different answer to each client.
|
||||
* But it's not enforced anywhere.
|
||||
*
|
||||
* protected for unit test override
|
||||
*/
|
||||
protected void handleGetBWLimits(GetBandwidthLimitsMessage message) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@ -789,4 +797,41 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @since 0.9.43
|
||||
*/
|
||||
private void handleBlindingInfo(BlindingInfoMessage message) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Got Blinding info");
|
||||
BlindData bd = message.getBlindData();
|
||||
SigningPublicKey spk = bd.getUnblindedPubKey();
|
||||
if (spk == null || bd == null) {
|
||||
// hash or hostname lookup? don't support for now
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Unsupported BlindingInfo type: " + message);
|
||||
return;
|
||||
}
|
||||
BlindData obd = _context.netDb().getBlindData(spk);
|
||||
if (obd == null) {
|
||||
_context.netDb().setBlindData(bd);
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("New: " + bd);
|
||||
} else {
|
||||
// update if changed
|
||||
PrivateKey okey = obd.getAuthPrivKey();
|
||||
PrivateKey nkey = bd.getAuthPrivKey();
|
||||
String osec = obd.getSecret();
|
||||
String nsec = bd.getSecret();
|
||||
if ((nkey != null && !nkey.equals(okey)) ||
|
||||
(nsec != null && !nsec.equals(osec))) {
|
||||
_context.netDb().setBlindData(bd);
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Updated: " + bd);
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("No change: " + obd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ class LookupDestJob extends JobImpl {
|
||||
SigningPublicKey spk = bd.getUnblindedPubKey();
|
||||
BlindData bd2 = getContext().netDb().getBlindData(spk);
|
||||
if (bd2 != null) {
|
||||
// BlindData from database may have privkey or secret
|
||||
bd = bd2;
|
||||
} else {
|
||||
getContext().netDb().setBlindData(bd);
|
||||
@ -132,6 +133,20 @@ class LookupDestJob extends JobImpl {
|
||||
returnDest(d);
|
||||
return;
|
||||
}
|
||||
boolean fail1 = _blindData.getAuthRequired() && _blindData.getAuthPrivKey() == null;
|
||||
boolean fail2 = _blindData.getSecretRequired() && _blindData.getSecret() == null;
|
||||
if (fail1 || fail2) {
|
||||
int code;
|
||||
if (fail1 && fail2)
|
||||
code = HostReplyMessage.RESULT_SECRET_AND_KEY_REQUIRED;
|
||||
else if (fail1)
|
||||
code = HostReplyMessage.RESULT_KEY_REQUIRED;
|
||||
else
|
||||
code = HostReplyMessage.RESULT_SECRET_REQUIRED;
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Failed b33 lookup " + _name + " with code " + code);
|
||||
returnFail(code);
|
||||
}
|
||||
}
|
||||
if (_name != null) {
|
||||
// inline, ignore timeout
|
||||
@ -151,7 +166,7 @@ class LookupDestJob extends JobImpl {
|
||||
getContext().netDb().lookupDestination(_hash, done, _timeout, _fromLocalDest);
|
||||
} else {
|
||||
// blinding decode fail
|
||||
returnFail();
|
||||
returnFail(HostReplyMessage.RESULT_DECRYPTION_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,13 +213,22 @@ class LookupDestJob extends JobImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the failed hash so the client can correlate replies with requests
|
||||
* Return the request ID or failed hash so the client can correlate replies with requests
|
||||
* @since 0.8.3
|
||||
*/
|
||||
private void returnFail() {
|
||||
returnFail(HostReplyMessage.RESULT_FAILURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the request ID or failed hash so the client can correlate replies with requests
|
||||
* @param code failure code, greater than zero, only used for HostReplyMessage
|
||||
* @since 0.9.43
|
||||
*/
|
||||
private void returnFail(int code) {
|
||||
I2CPMessage msg;
|
||||
if (_reqID >= 0)
|
||||
msg = new HostReplyMessage(_sessID, HostReplyMessage.RESULT_FAILURE, _reqID);
|
||||
msg = new HostReplyMessage(_sessID, code, _reqID);
|
||||
else if (_hash != null)
|
||||
msg = new DestReplyMessage(_hash);
|
||||
else
|
||||
|
Reference in New Issue
Block a user