I2CP: Add preliminary support for LS2 (proposal 123)

This commit is contained in:
zzz
2018-12-01 11:40:10 +00:00
parent 922515dfe4
commit 79440f84eb
8 changed files with 230 additions and 24 deletions

View File

@ -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);

View File

@ -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) {

View File

@ -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];
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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() {
}

View File

@ -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");
}