diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java index 4f4d205b0e..74f4351632 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/NetDbRenderer.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import net.i2p.crypto.EncType; import net.i2p.crypto.SigType; import net.i2p.data.DatabaseEntry; import net.i2p.data.DataHelper; @@ -33,6 +34,7 @@ import net.i2p.data.Hash; import net.i2p.data.Lease; import net.i2p.data.LeaseSet; import net.i2p.data.LeaseSet2; +import net.i2p.data.PublicKey; import net.i2p.data.router.RouterAddress; import net.i2p.data.router.RouterInfo; import net.i2p.router.RouterContext; @@ -533,8 +535,24 @@ class NetDbRenderer { buf.append("\n"); //buf.append(dest.toBase32()).append("
"); buf.append("Signature type: ").append(dest.getSigningPublicKey().getType()); - if (type != DatabaseEntry.KEY_TYPE_META_LS2) - buf.append("  Encryption Key: ").append(ls.getEncryptionKey().toBase64().substring(0, 20)).append("…"); + if (type == DatabaseEntry.KEY_TYPE_LEASESET) { + buf.append("\nEncryption Key: ELGAMAL_2048 ") + .append(ls.getEncryptionKey().toBase64().substring(0, 20)) + .append("…"); + } else if (type == DatabaseEntry.KEY_TYPE_LS2) { + LeaseSet2 ls2 = (LeaseSet2) ls; + for (PublicKey pk : ls2.getEncryptionKeys()) { + buf.append("\nEncryption Key: "); + EncType etype = pk.getType(); + if (etype != null) + buf.append(etype); + else + buf.append("Unsupported type ").append(pk.getUnknownTypeCode()); + buf.append(' ') + .append(pk.toBase64().substring(0, 20)) + .append("…"); + } + } buf.append("\n"); buf.append("Routing Key: ").append(ls.getRoutingKey().toBase64()); buf.append(""); diff --git a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java index 8b3bf1b3f1..9755d7f314 100644 --- a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java +++ b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java @@ -11,6 +11,10 @@ package net.i2p.client.impl; import java.io.EOFException; import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; @@ -90,8 +94,7 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { return true; String s = session.getOptions().getProperty(PROP_LS_ENCTYPE); if (s != null) { - EncType type = EncType.parseEncType(s); - if (type != null && type != EncType.ELGAMAL_2048 && type.isAvailable()) + if (!s.equals("0") && !s.equals("ELGAMAL_2048")) return true; } s = session.getOptions().getProperty(PROP_LS_TYPE); @@ -150,14 +153,14 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { //lease.setStartDate(msg.getStartDate()); leaseSet.addLease(lease); } - signLeaseSet(leaseSet, session); + signLeaseSet(leaseSet, isLS2, session); } /** * Finish creating and signing the new LeaseSet * @since 0.9.7 */ - protected synchronized void signLeaseSet(LeaseSet leaseSet, I2PSessionImpl session) { + protected synchronized void signLeaseSet(LeaseSet leaseSet, boolean isLS2, I2PSessionImpl session) { Destination dest = session.getMyDestination(); // also, if this session is connected to multiple routers, include other leases here leaseSet.setDestination(dest); @@ -165,11 +168,11 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { // reuse the old keys for the client LeaseInfo li = _existingLeaseSets.get(dest); if (li == null) { - // [enctype:]b64 of private key + // [enctype:]b64,... of private keys String spk = session.getOptions().getProperty(PROP_LS_PK); // [sigtype:]b64 of private key String sspk = session.getOptions().getProperty(PROP_LS_SPK); - PrivateKey privKey = null; + List privKeys = new ArrayList(2); SigningPrivateKey signingPrivKey = null; if (spk != null && sspk != null) { boolean useOldKeys = true; @@ -183,69 +186,69 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { 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) { + } catch (DataFormatException dfe) { useOldKeys = false; signingPrivKey = null; } } if (useOldKeys) { - try { - privKey = new PrivateKey(); - privKey.fromBase64(spk); - } catch (DataFormatException iae) { - privKey = null; - } + parsePrivateKeys(spk, privKeys); } } - if (privKey == null && !_existingLeaseSets.isEmpty()) { - // look for keypair from another dest using same pubkey + if (privKeys.isEmpty() && !_existingLeaseSets.isEmpty()) { + // look for private keys from another dest using same pubkey PublicKey pk = dest.getPublicKey(); for (Map.Entry e : _existingLeaseSets.entrySet()) { if (pk.equals(e.getKey().getPublicKey())) { - privKey = e.getValue().getPrivateKey(); + privKeys.addAll(e.getValue().getPrivateKeys()); if (_log.shouldLog(Log.DEBUG)) _log.debug("Creating new leaseInfo keys for " + dest + " with private key from " + e.getKey()); break; } } } - if (privKey != null) { + if (!privKeys.isEmpty()) { if (signingPrivKey != null) { - li = new LeaseInfo(privKey, signingPrivKey); + li = new LeaseInfo(privKeys, signingPrivKey); if (_log.shouldLog(Log.DEBUG)) _log.debug("Creating new leaseInfo keys for " + dest + " WITH configured private keys"); } else { - li = new LeaseInfo(privKey, dest); + li = new LeaseInfo(privKeys, dest); } } else { - EncType type = EncType.ELGAMAL_2048; + List types = new ArrayList(2); String senc = session.getOptions().getProperty(PROP_LS_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); + String[] senca = DataHelper.split(senc, ","); + for (String sencaa : senca) { + EncType newtype = EncType.parseEncType(sencaa); + if (newtype != null) { + if (types.contains(newtype)) { + _log.error("Duplicate crypto type: " + newtype); + continue; + } + if (newtype.isAvailable()) { + types.add(newtype); + if (_log.shouldDebug()) + _log.debug("Using crypto type: " + newtype); + } else { + _log.error("Unsupported crypto type: " + newtype); + } } else { - _log.error("Unsupported crypto.encType: " + newtype); + _log.error("Unsupported crypto type: " + sencaa); } - } else { - _log.error("Bad crypto.encType: " + senc); } - } else { + } + if (types.isEmpty()) { if (_log.shouldDebug()) _log.debug("Using default crypto type"); + types.add(EncType.ELGAMAL_2048); } - li = new LeaseInfo(dest, type); + li = new LeaseInfo(dest, types); if (_log.shouldLog(Log.DEBUG)) _log.debug("Creating new leaseInfo keys for " + dest + " without configured private keys"); } @@ -256,8 +259,16 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { + dest); } - if (_ls2Type != DatabaseEntry.KEY_TYPE_META_LS2) + if (isLS2) { + LeaseSet2 ls2 = (LeaseSet2) leaseSet; + if (_ls2Type != DatabaseEntry.KEY_TYPE_META_LS2) { + for (PublicKey key : li.getPublicKeys()) { + ls2.addEncryptionKey(key); + } + } + } else { leaseSet.setEncryptionKey(li.getPublicKey()); + } leaseSet.setSigningKey(li.getSigningPublicKey()); // SubSession options aren't updated via the gui, so use the primary options Properties opts; @@ -325,17 +336,72 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { } } + /** + * @param spk non-null [type:]b64[,[type:]b64]... + * @param privKeys out parameter + * @since 0.9.39 + */ + private void parsePrivateKeys(String spkl, List privKeys) { + String[] spks = DataHelper.split(spkl, ","); + for (String spk : spks) { + int colon = spk.indexOf(':'); + if (colon > 0) { + EncType type = EncType.parseEncType(spk.substring(0, colon)); + if (type != null) { + if (type.isAvailable()) { + try { + PrivateKey privKey = new PrivateKey(type); + privKey.fromBase64(spk.substring(colon + 1)); + privKeys.add(privKey); + if (_log.shouldDebug()) + _log.debug("Using crypto type: " + type); + } catch (DataFormatException dfe) { + _log.error("Bad private key: " + spk, dfe); + } + } else { + _log.error("Unsupported crypto type: " + type); + } + } else { + _log.error("Unsupported crypto type: " + spk); + } + } else if (colon < 0) { + try { + PrivateKey privKey = new PrivateKey(); + privKey.fromBase64(spk); + privKeys.add(privKey); + } catch (DataFormatException dfe) { + _log.error("Bad private key: " + spk, dfe); + } + } else { + _log.error("Empty crypto type"); + } + } + } + + /** + * Multiple encryption keys supported, as of 0.9.39, for LS2 + */ private static class LeaseInfo { - private final PublicKey _pubKey; - private final PrivateKey _privKey; + private final List _pubKeys; + private final List _privKeys; private final SigningPublicKey _signingPubKey; private final SigningPrivateKey _signingPrivKey; /** * New keys + * @param types must be available */ - public LeaseInfo(Destination dest, EncType type) { - KeyPair encKeys = KeyGenerator.getInstance().generatePKIKeys(type); + public LeaseInfo(Destination dest, List types) { + if (types.size() > 1) { + Collections.sort(types, Collections.reverseOrder()); + } + _privKeys = new ArrayList(types.size()); + _pubKeys = new ArrayList(types.size()); + for (EncType type : types) { + KeyPair encKeys = KeyGenerator.getInstance().generatePKIKeys(type); + _pubKeys.add(encKeys.getPublic()); + _privKeys.add(encKeys.getPrivate()); + } // must be same type as the Destination's signing key SimpleDataStructure signKeys[]; try { @@ -343,46 +409,67 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { } catch (GeneralSecurityException gse) { throw new IllegalStateException(gse); } - _pubKey = encKeys.getPublic(); - _privKey = encKeys.getPrivate(); _signingPubKey = (SigningPublicKey) signKeys[0]; _signingPrivKey = (SigningPrivateKey) signKeys[1]; } /** * Existing keys + * @param privKeys all EncTypes must be available * @since 0.9.18 */ - public LeaseInfo(PrivateKey privKey, SigningPrivateKey signingPrivKey) { - _pubKey = KeyGenerator.getPublicKey(privKey); - _privKey = privKey; + public LeaseInfo(List privKeys, SigningPrivateKey signingPrivKey) { + if (privKeys.size() > 1) { + Collections.sort(privKeys, new PrivKeyComparator()); + } + _privKeys = privKeys; + _pubKeys = new ArrayList(privKeys.size()); + for (PrivateKey privKey : privKeys) { + _pubKeys.add(KeyGenerator.getPublicKey(privKey)); + } _signingPubKey = KeyGenerator.getSigningPublicKey(signingPrivKey); _signingPrivKey = signingPrivKey; } /** - * Existing crypto key, new signing key + * Existing crypto keys, new signing key + * @param privKeys all EncTypes must be available * @since 0.9.21 */ - public LeaseInfo(PrivateKey privKey, Destination dest) { + public LeaseInfo(List privKeys, 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; + _privKeys = privKeys; + _pubKeys = new ArrayList(privKeys.size()); + for (PrivateKey privKey : privKeys) { + _pubKeys.add(KeyGenerator.getPublicKey(privKey)); + } _signingPubKey = (SigningPublicKey) signKeys[0]; _signingPrivKey = (SigningPrivateKey) signKeys[1]; } + /** @return the first one if more than one */ public PublicKey getPublicKey() { - return _pubKey; + return _pubKeys.get(0); } + /** @return the first one if more than one */ public PrivateKey getPrivateKey() { - return _privKey; + return _privKeys.get(0); + } + + /** @since 0.9.39 */ + public List getPublicKeys() { + return _pubKeys; + } + + /** @since 0.9.39 */ + public List getPrivateKeys() { + return _privKeys; } public SigningPublicKey getSigningPublicKey() { @@ -392,20 +479,15 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { 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()); + + /** + * Reverse order by enc type + * @since 0.9.39 + */ + private static class PrivKeyComparator implements Comparator { + public int compare(PrivateKey l, PrivateKey r) { + return r.getType().compareTo(l.getType()); + } } } } diff --git a/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java index 05eabce9f2..7290570f6f 100644 --- a/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java +++ b/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java @@ -77,6 +77,6 @@ class RequestVariableLeaseSetMessageHandler extends RequestLeaseSetMessageHandle } leaseSet.addLease(lease); } - signLeaseSet(leaseSet, session); + signLeaseSet(leaseSet, isLS2, session); } } diff --git a/core/java/src/net/i2p/data/LeaseSet2.java b/core/java/src/net/i2p/data/LeaseSet2.java index da0acb73b2..ad323bbd12 100644 --- a/core/java/src/net/i2p/data/LeaseSet2.java +++ b/core/java/src/net/i2p/data/LeaseSet2.java @@ -555,7 +555,7 @@ public class LeaseSet2 extends LeaseSet { int sz = keys.size(); buf.append("\n\tEncryption Keys: ").append(sz); for (int i = 0; i < sz; i++) { - buf.append("\n\tEncryptionKey ").append(i).append(": ").append(keys.get(i)); + buf.append("\n\tEncryption Key ").append(i).append(": ").append(keys.get(i)); } if (isOffline()) { buf.append("\n\tTransient Key: ").append(_transientSigningPublicKey); diff --git a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java index 12e73e6c41..19783123d0 100644 --- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java +++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Properties; import net.i2p.CoreVersion; +import net.i2p.crypto.EncType; import net.i2p.crypto.SigType; import net.i2p.data.DatabaseEntry; import net.i2p.data.DataHelper; @@ -268,6 +269,26 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi } } props.putAll(inProps); + String senc = props.getProperty("i2cp.leaseSetEncType"); + if (senc != null) { + String[] senca = DataHelper.split(senc, ","); + for (String sencaa : senca) { + EncType type = EncType.parseEncType(sencaa); + if (type != null) { + if (!type.isAvailable()) { + String msg = "Unsupported crypto type: " + type; + _log.error(msg); + _runner.disconnectClient(msg); + return; + } + } else { + String msg = "Unsupported crypto type: " + sencaa; + _log.error(msg); + _runner.disconnectClient(msg); + return; + } + } + } if ("7".equals(props.getProperty("i2cp.leaseSetType"))) { // Prevent tunnel builds for Meta LS // more TODO