diff --git a/core/java/src/net/i2p/client/impl/I2CPMessageProducer.java b/core/java/src/net/i2p/client/impl/I2CPMessageProducer.java index 545a36eb9afbb060dbd6f9846acb7d1d2d8aea88..4f59355eb16cc381a657c957c8cfbf572370b5be 100644 --- a/core/java/src/net/i2p/client/impl/I2CPMessageProducer.java +++ b/core/java/src/net/i2p/client/impl/I2CPMessageProducer.java @@ -17,6 +17,7 @@ import java.util.concurrent.locks.ReentrantLock; import net.i2p.I2PAppContext; import net.i2p.client.I2PSessionException; import net.i2p.client.SendMessageOptions; +import net.i2p.data.DatabaseEntry; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; import net.i2p.data.LeaseSet; @@ -28,6 +29,7 @@ import net.i2p.data.SigningPrivateKey; import net.i2p.data.i2cp.AbuseReason; import net.i2p.data.i2cp.AbuseSeverity; import net.i2p.data.i2cp.CreateLeaseSetMessage; +import net.i2p.data.i2cp.CreateLeaseSet2Message; import net.i2p.data.i2cp.CreateSessionMessage; import net.i2p.data.i2cp.DestroySessionMessage; import net.i2p.data.i2cp.MessageId; @@ -328,13 +330,20 @@ class I2CPMessageProducer { } /** - * Create a new signed leaseSet in response to a request to do so and send it - * to the router - * + * In response to a RequestLeaseSet Message from the router, send a + * CreateLeaseset Message back to the router. + * This method is misnamed, it does not create the LeaseSet, + * the caller does that. + * */ public void createLeaseSet(I2PSessionImpl session, LeaseSet leaseSet, SigningPrivateKey signingPriv, PrivateKey priv) throws I2PSessionException { - CreateLeaseSetMessage msg = new CreateLeaseSetMessage(); + CreateLeaseSetMessage msg; + int type = leaseSet.getType(); + if (type == DatabaseEntry.KEY_TYPE_LEASESET) + msg = new CreateLeaseSetMessage(); + else + msg = new CreateLeaseSet2Message(); msg.setLeaseSet(leaseSet); msg.setPrivateKey(priv); msg.setSigningPrivateKey(signingPriv); diff --git a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java index 83931b30ea34c9dfbe899f32b1c113b7dc34a0f7..d3f03f4cd922f9a9424eb1d70d26a3dee078663c 100644 --- a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java +++ b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java @@ -171,6 +171,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2 private final boolean _fastReceive; private volatile boolean _routerSupportsFastReceive; private volatile boolean _routerSupportsHostLookup; + private volatile boolean _routerSupportsLS2; protected static final int CACHE_MAX_SIZE = SystemVersion.isAndroid() ? 32 : 128; /** @@ -197,19 +198,25 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2 private static final long MAX_SEND_WAIT = 10*1000; private static final String MIN_FAST_VERSION = "0.9.4"; +////// TESTING, change to 38 before release + private static final String MIN_LS2_VERSION = "0.9.37"; /** @param routerVersion as rcvd in the SetDateMessage, may be null for very old routers */ void dateUpdated(String routerVersion) { - _routerSupportsFastReceive = _context.isRouterContext() || + boolean isrc = _context.isRouterContext(); + _routerSupportsFastReceive = isrc || (routerVersion != null && routerVersion.length() > 0 && VersionComparator.comp(routerVersion, MIN_FAST_VERSION) >= 0); - _routerSupportsHostLookup = _context.isRouterContext() || + _routerSupportsHostLookup = isrc || TEST_LOOKUP || (routerVersion != null && routerVersion.length() > 0 && VersionComparator.comp(routerVersion, MIN_HOST_LOOKUP_VERSION) >= 0); - _routerSupportsSubsessions = _context.isRouterContext() || + _routerSupportsSubsessions = isrc || (routerVersion != null && routerVersion.length() > 0 && VersionComparator.comp(routerVersion, MIN_SUBSESSION_VERSION) >= 0); + _routerSupportsLS2 = isrc || + (routerVersion != null && routerVersion.length() > 0 && + VersionComparator.comp(routerVersion, MIN_LS2_VERSION) >= 0); synchronized (_stateLock) { if (_state == State.OPENING) { changeState(State.GOTDATE); @@ -281,9 +288,11 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2 _privateKey = null; _signingPrivateKey = null; } - _routerSupportsFastReceive = _context.isRouterContext(); - _routerSupportsHostLookup = _context.isRouterContext(); - _routerSupportsSubsessions = _context.isRouterContext(); + boolean isrc = _context.isRouterContext(); + _routerSupportsFastReceive = isrc; + _routerSupportsHostLookup = isrc; + _routerSupportsSubsessions = isrc; + _routerSupportsLS2 = isrc; } /** @@ -509,6 +518,13 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2 return _fastReceive && _routerSupportsFastReceive; } + /** + * @since 0.9.38 + */ + public boolean supportsLS2() { + return _routerSupportsLS2; + } + void setLeaseSet(LeaseSet ls) { _leaseSet = ls; if (ls != null) { diff --git a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java index d88b12e759634ed15055e57532449acc0ac2bb87..82a7fb46dfa6483e09af887fd21a8c6d2069f65d 100644 --- a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java +++ b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java @@ -17,14 +17,19 @@ import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; import net.i2p.client.I2PSessionException; +import net.i2p.crypto.EncType; import net.i2p.crypto.KeyGenerator; +import net.i2p.crypto.KeyPair; import net.i2p.crypto.SigType; +import net.i2p.data.DatabaseEntry; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.Lease; +import net.i2p.data.Lease2; import net.i2p.data.LeaseSet; +import net.i2p.data.LeaseSet2; import net.i2p.data.PrivateKey; import net.i2p.data.PublicKey; import net.i2p.data.SessionKey; @@ -58,13 +63,38 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { _existingLeaseSets = new ConcurrentHashMap<Destination, LeaseInfo>(4); } + /** + * Do we send a LeaseSet or a LeaseSet2? + * @since 0.9.38 + */ + protected static boolean requiresLS2(I2PSessionImpl session) { + if (!session.supportsLS2()) + return false; + String s = session.getOptions().getProperty("crypto.encType"); + if (s != null) { + EncType type = EncType.parseEncType(s); + if (type != null && type != EncType.ELGAMAL_2048 && type.isAvailable()) + return true; + } + s = session.getOptions().getProperty("i2cp.leaseSetType"); + if (s != null) { + try { + int type = Integer.parseInt(s); + if (type != DatabaseEntry.KEY_TYPE_LEASESET) + return true; + } catch (NumberFormatException nfe) {} + } + return false; + } + public void handleMessage(I2CPMessage message, I2PSessionImpl session) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Handle message " + message); RequestLeaseSetMessage msg = (RequestLeaseSetMessage) message; - LeaseSet leaseSet = new LeaseSet(); + boolean isLS2 = requiresLS2(session); + LeaseSet leaseSet = isLS2 ? new LeaseSet2() : new LeaseSet(); for (int i = 0; i < msg.getEndpoints(); i++) { - Lease lease = new Lease(); + Lease lease = isLS2 ? new Lease2() : new Lease(); lease.setGateway(msg.getRouter(i)); lease.setTunnelId(msg.getTunnelId(i)); lease.setEndDate(msg.getEndDate()); @@ -147,7 +177,26 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { li = new LeaseInfo(privKey, dest); } } else { - li = new LeaseInfo(dest); + EncType type = EncType.ELGAMAL_2048; + String senc = session.getOptions().getProperty("crypto.encType"); + if (senc != null) { + EncType newtype = EncType.parseEncType(senc); + if (newtype != null) { + if (newtype.isAvailable()) { + type = newtype; + if (_log.shouldDebug()) + _log.debug("Using crypto type: " + type); + } else { + _log.error("Unsupported crypto.encType: " + newtype); + } + } else { + _log.error("Bad crypto.encType: " + senc); + } + } else { + if (_log.shouldDebug()) + _log.debug("Using default crypto type"); + } + li = new LeaseInfo(dest, type); if (_log.shouldLog(Log.DEBUG)) _log.debug("Creating new leaseInfo keys for " + dest + " without configured private keys"); } @@ -187,14 +236,18 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { // Workaround for unparsable serialized signing private key for revocation // Send him a dummy DSA_SHA1 private key since it's unused anyway // See CreateLeaseSetMessage.doReadMessage() + // For LS1 only SigningPrivateKey spk = li.getSigningPrivateKey(); - if (!_context.isRouterContext() && spk.getType() != SigType.DSA_SHA1) { + if (!_context.isRouterContext() && spk.getType() != SigType.DSA_SHA1 && + !(leaseSet instanceof LeaseSet2)) { byte[] dummy = new byte[SigningPrivateKey.KEYSIZE_BYTES]; _context.random().nextBytes(dummy); spk = new SigningPrivateKey(dummy); } session.getProducer().createLeaseSet(session, leaseSet, spk, li.getPrivateKey()); session.setLeaseSet(leaseSet); + if (_log.shouldDebug()) + _log.debug("Created and signed LeaseSet: " + leaseSet); } catch (DataFormatException dfe) { session.propogateError("Error signing the leaseSet", dfe); } catch (I2PSessionException ise) { @@ -220,8 +273,8 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { /** * New keys */ - public LeaseInfo(Destination dest) { - SimpleDataStructure encKeys[] = KeyGenerator.getInstance().generatePKIKeys(); + public LeaseInfo(Destination dest, EncType type) { + KeyPair encKeys = KeyGenerator.getInstance().generatePKIKeys(type); // must be same type as the Destination's signing key SimpleDataStructure signKeys[]; try { @@ -229,8 +282,8 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { } catch (GeneralSecurityException gse) { throw new IllegalStateException(gse); } - _pubKey = (PublicKey) encKeys[0]; - _privKey = (PrivateKey) encKeys[1]; + _pubKey = encKeys.getPublic(); + _privKey = encKeys.getPrivate(); _signingPubKey = (SigningPublicKey) signKeys[0]; _signingPrivKey = (SigningPrivateKey) signKeys[1]; } diff --git a/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java index 41cc9eb9319714061dc3a469faf5140d170bd48e..4fd7c12a59fbae1c3adb0c4dfcb8455cf21094c8 100644 --- a/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java +++ b/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java @@ -10,7 +10,10 @@ package net.i2p.client.impl; */ import net.i2p.I2PAppContext; +import net.i2p.data.Lease; +import net.i2p.data.Lease2; import net.i2p.data.LeaseSet; +import net.i2p.data.LeaseSet2; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.RequestVariableLeaseSetMessage; import net.i2p.util.Log; @@ -32,9 +35,21 @@ class RequestVariableLeaseSetMessageHandler extends RequestLeaseSetMessageHandle if (_log.shouldLog(Log.DEBUG)) _log.debug("Handle message " + message); RequestVariableLeaseSetMessage msg = (RequestVariableLeaseSetMessage) message; - LeaseSet leaseSet = new LeaseSet(); + boolean isLS2 = requiresLS2(session); + LeaseSet leaseSet = isLS2 ? new LeaseSet2() : new LeaseSet(); for (int i = 0; i < msg.getEndpoints(); i++) { - leaseSet.addLease(msg.getEndpoint(i)); + Lease lease; + if (isLS2) { + // convert Lease to Lease2 + Lease old = msg.getEndpoint(i); + lease = new Lease2(); + lease.setGateway(old.getGateway()); + lease.setTunnelId(old.getTunnelId()); + lease.setEndDate(old.getEndDate()); + } else { + lease = msg.getEndpoint(i); + } + leaseSet.addLease(lease); } signLeaseSet(leaseSet, session); } diff --git a/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java b/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java new file mode 100644 index 0000000000000000000000000000000000000000..fe54df66b345aac9670c352880dcd14cd5a39778 --- /dev/null +++ b/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java @@ -0,0 +1,109 @@ +package net.i2p.data.i2cp; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import net.i2p.crypto.EncType; +import net.i2p.crypto.SigType; +import net.i2p.data.DatabaseEntry; +import net.i2p.data.DataFormatException; +import net.i2p.data.LeaseSet; +import net.i2p.data.LeaseSet2; +import net.i2p.data.PrivateKey; +import net.i2p.data.SigningPrivateKey; + +/** + * Like CreateLeaseSetMessage, but supports both old + * and new LeaseSet types, including LS2, Meta, and Encrypted. + * + * For LS2: + * Same as CreateLeaseSetMessage, but has a netdb type before + * the LeaseSet. SigningPrivateKey and PrivateKey are + * serialized after the LeaseSet, not before, so we can + * infer the types from the LeaseSet. + * + * @since 0.9.38 + */ +public class CreateLeaseSet2Message extends CreateLeaseSetMessage { + public final static int MESSAGE_TYPE = 40; + + public CreateLeaseSet2Message() { + super(); + } + + @Override + protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException { + try { + _sessionId = new SessionId(); + _sessionId.readBytes(in); + int type = in.read(); + if (type == DatabaseEntry.KEY_TYPE_LEASESET) { + _leaseSet = new LeaseSet(); + } else if (type == DatabaseEntry.KEY_TYPE_LS2) { + _leaseSet = new LeaseSet2(); + } else if (type == -1) { + throw new EOFException("EOF reading LS type"); + } else { + throw new I2CPMessageException("Unsupported Leaseset type: " + type); + } + _leaseSet.readBytes(in); + // In CLSM this is the type of the dest, but revocation is unimplemented. + // In CLS2M this is the type of the signature (which may be different than the + // type of the dest if it's an offline signature) + // and is needed by the session tag manager. + SigType stype = _leaseSet.getSignature().getType(); + if (stype == null) + throw new I2CPMessageException("Unsupported sig type"); + _signingPrivateKey = new SigningPrivateKey(stype); + _signingPrivateKey.readBytes(in); + EncType etype = _leaseSet.getEncryptionKey().getType(); + if (etype == null) + throw new I2CPMessageException("Unsupported encryption type"); + _privateKey = new PrivateKey(etype); + _privateKey.readBytes(in); + } catch (DataFormatException dfe) { + throw new I2CPMessageException("Error reading the CreateLeaseSetMessage", dfe); + } + } + + @Override + protected byte[] doWriteMessage() throws I2CPMessageException, IOException { + if (_sessionId == null || _signingPrivateKey == null || _privateKey == null || _leaseSet == null) + throw new I2CPMessageException("Unable to write out the message as there is not enough data"); + int size = 4 // sessionId + + 1 // type + + _leaseSet.size() + + _signingPrivateKey.length() + + _privateKey.length(); + ByteArrayOutputStream os = new ByteArrayOutputStream(size); + try { + _sessionId.writeBytes(os); + os.write(_leaseSet.getType()); + _leaseSet.writeBytes(os); + _signingPrivateKey.writeBytes(os); + _privateKey.writeBytes(os); + } catch (DataFormatException dfe) { + throw new I2CPMessageException("Error writing out the message data", dfe); + } + return os.toByteArray(); + } + + @Override + public int getType() { + return MESSAGE_TYPE; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("[CreateLeaseSet2Message: "); + buf.append("\n\tLeaseSet: ").append(getLeaseSet()); + buf.append("\n\tSigningPrivateKey: ").append(getSigningPrivateKey()); + buf.append("\n\tPrivateKey: ").append(getPrivateKey()); + buf.append("\n\tSessionId: ").append(getSessionId()); + buf.append("]"); + return buf.toString(); + } +} diff --git a/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java b/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java index d7c55e53bd609aa9919696ea61b457fa6378c655..277e9cd91db911759aee3b1a5ad7f4480f8e273f 100644 --- a/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java +++ b/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java @@ -26,10 +26,10 @@ import net.i2p.data.SigningPrivateKey; */ public class CreateLeaseSetMessage extends I2CPMessageImpl { public final static int MESSAGE_TYPE = 4; - private SessionId _sessionId; - private LeaseSet _leaseSet; - private SigningPrivateKey _signingPrivateKey; - private PrivateKey _privateKey; + protected SessionId _sessionId; + protected LeaseSet _leaseSet; + protected SigningPrivateKey _signingPrivateKey; + protected PrivateKey _privateKey; public CreateLeaseSetMessage() { } diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java index 915757fc09e8d4860185091139e96898c1ea907d..5c5d76885276735f83fdd6701d8475dc5a294e67 100644 --- a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java +++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java @@ -112,6 +112,8 @@ public class I2CPMessageHandler { return new HostLookupMessage(); case HostReplyMessage.MESSAGE_TYPE: return new HostReplyMessage(); + case CreateLeaseSet2Message.MESSAGE_TYPE: + return new CreateLeaseSet2Message(); default: throw new I2CPMessageException("The type " + type + " is an unknown I2CP message"); } diff --git a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java index b6893b59c0922a9ca95b8dc32e5a701a99031615..76836e315843aaf22d1676ca6da7acff9edf7107 100644 --- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java +++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java @@ -20,6 +20,7 @@ import net.i2p.data.Payload; import net.i2p.data.PublicKey; import net.i2p.data.i2cp.BandwidthLimitsMessage; import net.i2p.data.i2cp.CreateLeaseSetMessage; +import net.i2p.data.i2cp.CreateLeaseSet2Message; import net.i2p.data.i2cp.CreateSessionMessage; import net.i2p.data.i2cp.DestLookupMessage; import net.i2p.data.i2cp.DestroySessionMessage; @@ -126,6 +127,7 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi handleReceiveEnd((ReceiveMessageEndMessage)message); break; case CreateLeaseSetMessage.MESSAGE_TYPE: + case CreateLeaseSet2Message.MESSAGE_TYPE: handleCreateLeaseSet((CreateLeaseSetMessage)message); break; case DestroySessionMessage.MESSAGE_TYPE: