package net.i2p.client.impl;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.EOFException;
import java.security.GeneralSecurityException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSessionException;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SigType;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Lease;
import net.i2p.data.LeaseSet;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.data.SimpleDataStructure;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.RequestLeaseSetMessage;
import net.i2p.util.Log;
/**
* Handle I2CP RequestLeaseSetMessage from the router by granting all leases,
* using the specified expiration time for each lease.
*
* @author jrandom
*/
class RequestLeaseSetMessageHandler extends HandlerImpl {
private final Map<Destination, LeaseInfo> _existingLeaseSets;
public RequestLeaseSetMessageHandler(I2PAppContext context) {
this(context, RequestLeaseSetMessage.MESSAGE_TYPE);
}
/**
* For extension
* @since 0.9.7
*/
protected RequestLeaseSetMessageHandler(I2PAppContext context, int messageType) {
super(context, messageType);
// not clear why there would ever be more than one
_existingLeaseSets = new ConcurrentHashMap<Destination, LeaseInfo>(4);
}
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();
for (int i = 0; i < msg.getEndpoints(); i++) {
Lease lease = new Lease();
lease.setGateway(msg.getRouter(i));
lease.setTunnelId(msg.getTunnelId(i));
lease.setEndDate(msg.getEndDate());
//lease.setStartDate(msg.getStartDate());
leaseSet.addLease(lease);
}
signLeaseSet(leaseSet, session);
}
/**
* Finish creating and signing the new LeaseSet
* @since 0.9.7
*/
protected synchronized void signLeaseSet(LeaseSet leaseSet, I2PSessionImpl session) {
Destination dest = session.getMyDestination();
// also, if this session is connected to multiple routers, include other leases here
leaseSet.setDestination(dest);
// reuse the old keys for the client
LeaseInfo li = _existingLeaseSets.get(dest);
if (li == null) {
// [enctype:]b64 of private key
String spk = session.getOptions().getProperty("i2cp.leaseSetPrivateKey");
// [sigtype:]b64 of private key
String sspk = session.getOptions().getProperty("i2cp.leaseSetSigningPrivateKey");
PrivateKey privKey = null;
SigningPrivateKey signingPrivKey = null;
if (spk != null && sspk != null) {
boolean useOldKeys = true;
int colon = sspk.indexOf(':');
SigType type = dest.getSigType();
if (colon > 0) {
String stype = sspk.substring(0, colon);
SigType t = SigType.parseSigType(stype);
if (t == type)
sspk = sspk.substring(colon + 1);
else
useOldKeys = false;
}
colon = spk.indexOf(':');
// just ignore for now, no other types supported
if (colon >= 0)
spk = spk.substring(colon + 1);
if (useOldKeys) {
try {
signingPrivKey = new SigningPrivateKey(type);
signingPrivKey.fromBase64(sspk);
} catch (DataFormatException iae) {
useOldKeys = false;
signingPrivKey = null;
}
}
if (useOldKeys) {
try {
privKey = new PrivateKey();
privKey.fromBase64(spk);
} catch (DataFormatException iae) {
privKey = null;
}
}
}
if (privKey == null && !_existingLeaseSets.isEmpty()) {
// look for keypair from another dest using same pubkey
PublicKey pk = dest.getPublicKey();
for (Map.Entry<Destination, LeaseInfo> e : _existingLeaseSets.entrySet()) {
if (pk.equals(e.getKey().getPublicKey())) {
privKey = e.getValue().getPrivateKey();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Creating new leaseInfo keys for " + dest + " with private key from " + e.getKey());
break;
}
}
}
if (privKey != null) {
if (signingPrivKey != null) {
li = new LeaseInfo(privKey, signingPrivKey);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Creating new leaseInfo keys for " + dest + " WITH configured private keys");
} else {
li = new LeaseInfo(privKey, dest);
}
} else {
li = new LeaseInfo(dest);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Creating new leaseInfo keys for " + dest + " without configured private keys");
}
_existingLeaseSets.put(dest, li);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Caching the old leaseInfo keys for "
+ dest);
}
leaseSet.setEncryptionKey(li.getPublicKey());
leaseSet.setSigningKey(li.getSigningPublicKey());
boolean encrypt = Boolean.parseBoolean(session.getOptions().getProperty("i2cp.encryptLeaseSet"));
String sk = session.getOptions().getProperty("i2cp.leaseSetKey");
if (encrypt && sk != null) {
SessionKey key = new SessionKey();
try {
key.fromBase64(sk);
leaseSet.encrypt(key);
_context.keyRing().put(session.getMyDestination().calculateHash(), key);
} catch (DataFormatException dfe) {
_log.error("Bad leaseset key: " + sk);
}
}
try {
leaseSet.sign(session.getPrivateKey());
// 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()
SigningPrivateKey spk = li.getSigningPrivateKey();
if (!_context.isRouterContext() && spk.getType() != SigType.DSA_SHA1) {
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);
} catch (DataFormatException dfe) {
session.propogateError("Error signing the leaseSet", dfe);
} catch (I2PSessionException ise) {
if (session.isClosed()) {
// race, closed while signing leaseset
// EOFExceptions are logged at WARN level (see I2PSessionImpl.propogateError())
// so the user won't see this
EOFException eof = new EOFException("Session closed while signing leaseset");
eof.initCause(ise);
session.propogateError("Session closed while signing leaseset", eof);
} else {
session.propogateError("Error sending the signed leaseSet", ise);
}
}
}
private static class LeaseInfo {
private final PublicKey _pubKey;
private final PrivateKey _privKey;
private final SigningPublicKey _signingPubKey;
private final SigningPrivateKey _signingPrivKey;
/**
* New keys
*/
public LeaseInfo(Destination dest) {
SimpleDataStructure encKeys[] = KeyGenerator.getInstance().generatePKIKeys();
// must be same type as the Destination's signing key
SimpleDataStructure signKeys[];
try {
signKeys = KeyGenerator.getInstance().generateSigningKeys(dest.getSigningPublicKey().getType());
} catch (GeneralSecurityException gse) {
throw new IllegalStateException(gse);
}
_pubKey = (PublicKey) encKeys[0];
_privKey = (PrivateKey) encKeys[1];
_signingPubKey = (SigningPublicKey) signKeys[0];
_signingPrivKey = (SigningPrivateKey) signKeys[1];
}
/**
* Existing keys
* @since 0.9.18
*/
public LeaseInfo(PrivateKey privKey, SigningPrivateKey signingPrivKey) {
_pubKey = KeyGenerator.getPublicKey(privKey);
_privKey = privKey;
_signingPubKey = KeyGenerator.getSigningPublicKey(signingPrivKey);
_signingPrivKey = signingPrivKey;
}
/**
* Existing crypto key, new signing key
* @since 0.9.21
*/
public LeaseInfo(PrivateKey privKey, Destination dest) {
SimpleDataStructure signKeys[];
try {
signKeys = KeyGenerator.getInstance().generateSigningKeys(dest.getSigningPublicKey().getType());
} catch (GeneralSecurityException gse) {
throw new IllegalStateException(gse);
}
_pubKey = KeyGenerator.getPublicKey(privKey);
_privKey = privKey;
_signingPubKey = (SigningPublicKey) signKeys[0];
_signingPrivKey = (SigningPrivateKey) signKeys[1];
}
public PublicKey getPublicKey() {
return _pubKey;
}
public PrivateKey getPrivateKey() {
return _privKey;
}
public SigningPublicKey getSigningPublicKey() {
return _signingPubKey;
}
public SigningPrivateKey getSigningPrivateKey() {
return _signingPrivKey;
}
@Override
public int hashCode() {
return DataHelper.hashCode(_pubKey) + 7 * DataHelper.hashCode(_privKey) + 7 * 7
* DataHelper.hashCode(_signingPubKey) + 7 * 7 * 7 * DataHelper.hashCode(_signingPrivKey);
}
@Override
public boolean equals(Object obj) {
if ((obj == null) || !(obj instanceof LeaseInfo)) return false;
LeaseInfo li = (LeaseInfo) obj;
return DataHelper.eq(_pubKey, li.getPublicKey()) && DataHelper.eq(_privKey, li.getPrivateKey())
&& DataHelper.eq(_signingPubKey, li.getSigningPublicKey())
&& DataHelper.eq(_signingPrivKey, li.getSigningPrivateKey());
}
}
}