diff --git a/core/java/src/net/i2p/crypto/ElGamalEngine.java b/core/java/src/net/i2p/crypto/ElGamalEngine.java index 67dadf0f80..70a11df7d0 100644 --- a/core/java/src/net/i2p/crypto/ElGamalEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalEngine.java @@ -119,6 +119,9 @@ public final class ElGamalEngine { if ((data == null) || (data.length > ELG_CLEARTEXT_LENGTH)) throw new IllegalArgumentException("Data to encrypt must be <= 222 bytes"); if (publicKey == null) throw new IllegalArgumentException("Null public key specified"); + EncType type = publicKey.getType(); + if (type != EncType.ELGAMAL_2048) + throw new IllegalArgumentException("Bad public key type " + type); long start = _context.clock().now(); @@ -193,6 +196,9 @@ public final class ElGamalEngine { * @return unencrypted data or null on failure */ public byte[] decrypt(byte encrypted[], PrivateKey privateKey) { + EncType type = privateKey.getType(); + if (type != EncType.ELGAMAL_2048) + throw new IllegalArgumentException("Bad private key type " + type); if ((encrypted == null) || (encrypted.length != ELG_ENCRYPTED_LENGTH)) throw new IllegalArgumentException("Data to decrypt must be exactly ELG_ENCRYPTED_LENGTH bytes"); long start = _context.clock().now(); diff --git a/core/java/src/net/i2p/crypto/EncType.java b/core/java/src/net/i2p/crypto/EncType.java index d24558e08e..b1f82d0780 100644 --- a/core/java/src/net/i2p/crypto/EncType.java +++ b/core/java/src/net/i2p/crypto/EncType.java @@ -11,7 +11,7 @@ import net.i2p.data.Hash; import net.i2p.data.SimpleDataStructure; /** - * PRELIMINARY - unused - subject to change + * PRELIMINARY - subject to change * * Defines the properties for various encryption types * that I2P supports or may someday support. @@ -33,18 +33,21 @@ public enum EncType { /** * Used by i2pd. Not yet supported by Java I2P. * Pubkey 64 bytes; privkey 32 bytes. + * See proposal 145. */ EC_P256(1, 64, 32, EncAlgo.EC, "EC/None/NoPadding", ECConstants.P256_SPEC, "0.9.38"), /** * Reserved, not used by anybody. * Pubkey 96 bytes; privkey 48 bytes. + * See proposal 145. */ EC_P384(2, 96, 48, EncAlgo.EC, "EC/None/NoPadding", ECConstants.P384_SPEC, "0.9.38"), /** * Reserved, not used by anybody. * Pubkey 132 bytes; privkey 66 bytes. + * See proposal 145. */ EC_P521(3, 132, 66, EncAlgo.EC, "EC/None/NoPadding", ECConstants.P521_SPEC, "0.9.38"), @@ -56,8 +59,6 @@ public enum EncType { ECIES_X25519(4, 32, 32, EncAlgo.ECIES, "EC/None/NoPadding", X25519_SPEC, "0.9.38"); - - private final int code, pubkeyLen, privkeyLen; private final EncAlgo base; private final String algoName, since; @@ -71,6 +72,8 @@ public enum EncType { */ EncType(int cod, int pubLen, int privLen, EncAlgo baseAlgo, String transformation, AlgorithmParameterSpec pSpec, String supportedSince) { + if (pubLen > 256) + throw new IllegalArgumentException("fixup PublicKey for longer keys"); code = cod; pubkeyLen = pubLen; privkeyLen = privLen; diff --git a/core/java/src/net/i2p/data/Destination.java b/core/java/src/net/i2p/data/Destination.java index 866bf91658..693c5c3d20 100644 --- a/core/java/src/net/i2p/data/Destination.java +++ b/core/java/src/net/i2p/data/Destination.java @@ -60,8 +60,12 @@ public class Destination extends KeysAndCert { byte[] padding; if (c.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { // convert SPK to new SPK and padding + // EncTypes 1-3 allowed in Destinations, see proposal 145 KeyCertificate kcert = c.toKeyCertificate(); - padding = sk.getPadding(kcert); + byte[] pad1 = pk.getPadding(kcert); + byte[] pad2 = sk.getPadding(kcert); + padding = combinePadding(pad1, pad2); + pk = pk.toTypedKey(kcert); sk = sk.toTypedKey(kcert); c = kcert; } else { @@ -70,7 +74,8 @@ public class Destination extends KeysAndCert { Destination rv; synchronized(_cache) { rv = _cache.get(sk); - if (rv != null && rv.getPublicKey().equals(pk) && rv.getCertificate().equals(c)) { + if (rv != null && rv.getPublicKey().equals(pk) && rv.getCertificate().equals(c) && + DataHelper.eq(rv.getPadding(), padding)) { //if (STATS) // I2PAppContext.getGlobalContext().statManager().addRateData("DestCache", 1); return rv; @@ -97,6 +102,11 @@ public class Destination extends KeysAndCert { * @since 0.9.9 */ private Destination(PublicKey pk, SigningPublicKey sk, Certificate c, byte[] padding) { + if (padding != null) { + int sz = pk.length() + sk.length() + padding.length; + if (sz != 384) + throw new IllegalArgumentException("bad total length " + sz); + } _publicKey = pk; _signingKey = sk; _certificate = c; diff --git a/core/java/src/net/i2p/data/KeyCertificate.java b/core/java/src/net/i2p/data/KeyCertificate.java index 7c2b490073..113f57fbf2 100644 --- a/core/java/src/net/i2p/data/KeyCertificate.java +++ b/core/java/src/net/i2p/data/KeyCertificate.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import net.i2p.crypto.EncType; import net.i2p.crypto.SigType; /** @@ -147,6 +148,14 @@ public class KeyCertificate extends Certificate { return SigType.getByCode(getSigTypeCode()); } + /** + * @return null if unset or unknown + * @since 0.9.42 + */ + public EncType getEncType() { + return EncType.getByCode(getCryptoTypeCode()); + } + /** * Signing Key extra data, if any, is first in the array. * Crypto Key extra data, if any, is second in the array, diff --git a/core/java/src/net/i2p/data/KeysAndCert.java b/core/java/src/net/i2p/data/KeysAndCert.java index 431cb8fe48..d68880fde6 100644 --- a/core/java/src/net/i2p/data/KeysAndCert.java +++ b/core/java/src/net/i2p/data/KeysAndCert.java @@ -15,6 +15,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; +import net.i2p.crypto.EncType; import net.i2p.crypto.SHA256Generator; import net.i2p.crypto.SigType; @@ -68,6 +69,22 @@ public class KeysAndCert extends DataStructureImpl { return SigType.DSA_SHA1; } + /** + * @return null if not set or unknown + * @since 0.9.42 + */ + public EncType getEncType() { + if (_certificate == null) + return null; + if (_certificate.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { + try { + KeyCertificate kcert = _certificate.toKeyCertificate(); + return kcert.getEncType(); + } catch (DataFormatException dfe) {} + } + return EncType.ELGAMAL_2048; + } + public PublicKey getPublicKey() { return _publicKey; } @@ -117,28 +134,48 @@ public class KeysAndCert extends DataStructureImpl { public void readBytes(InputStream in) throws DataFormatException, IOException { if (_publicKey != null || _signingKey != null || _certificate != null) throw new IllegalStateException(); - _publicKey = PublicKey.create(in); + PublicKey pk = PublicKey.create(in); SigningPublicKey spk = SigningPublicKey.create(in); - Certificate cert = Certificate.create(in); + Certificate cert = Certificate.create(in); if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { - // convert SPK to new SPK and padding + // convert PK and SPK to new PK and SPK and padding KeyCertificate kcert = cert.toKeyCertificate(); + _publicKey = pk.toTypedKey(kcert); _signingKey = spk.toTypedKey(kcert); - _padding = spk.getPadding(kcert); + byte[] pad1 = pk.getPadding(kcert); + byte[] pad2 = spk.getPadding(kcert); + _padding = combinePadding(pad1, pad2); _certificate = kcert; } else { + _publicKey = pk; _signingKey = spk; _certificate = cert; } } + /** + * @return null if both are null + * @since 0.9.42 + */ + protected static byte[] combinePadding(byte[] pad1, byte[] pad2) { + if (pad1 == null) + return pad2; + if (pad2 == null) + return pad1; + byte[] rv = new byte[pad1.length + pad2.length]; + System.arraycopy(pad1, 0, rv, 0, pad1.length); + System.arraycopy(pad2, 0, rv, pad1.length, pad2.length); + return rv; + } + public void writeBytes(OutputStream out) throws DataFormatException, IOException { if ((_certificate == null) || (_publicKey == null) || (_signingKey == null)) throw new DataFormatException("Not enough data to format the router identity"); _publicKey.writeBytes(out); if (_padding != null) out.write(_padding); - else if (_signingKey.length() < SigningPublicKey.KEYSIZE_BYTES) + else if (_signingKey.length() < SigningPublicKey.KEYSIZE_BYTES || + _publicKey.length() < PublicKey.KEYSIZE_BYTES) throw new DataFormatException("No padding set"); _signingKey.writeTruncatedBytes(out); _certificate.writeBytes(out); diff --git a/core/java/src/net/i2p/data/PublicKey.java b/core/java/src/net/i2p/data/PublicKey.java index 2e182cb120..44f4180da6 100644 --- a/core/java/src/net/i2p/data/PublicKey.java +++ b/core/java/src/net/i2p/data/PublicKey.java @@ -11,6 +11,7 @@ package net.i2p.data; import java.io.InputStream; import java.io.IOException; +import java.util.Arrays; import net.i2p.crypto.EncType; @@ -20,6 +21,7 @@ import net.i2p.crypto.EncType; * exponent, not the primes, which are constant and defined in the crypto spec. * * As of release 0.9.38, keys of arbitrary length and type are supported. + * Note: Support for keys longer than 256 bytes unimplemented. * See EncType. * * @author jrandom @@ -134,6 +136,63 @@ public class PublicKey extends SimpleDataStructure { return _unknownTypeCode; } + /** + * Up-convert this from an untyped (type 0) PK to a typed PK based on the Key Cert given. + * The type of the returned key will be null if the kcert sigtype is null. + * + * @throws IllegalArgumentException if this is already typed to a different type + * @since 0.9.42 + */ + public PublicKey toTypedKey(KeyCertificate kcert) { + if (_data == null) + throw new IllegalStateException(); + EncType newType = kcert.getEncType(); + if (_type == newType) + return this; + if (_type != EncType.ELGAMAL_2048) + throw new IllegalArgumentException("Cannot convert " + _type + " to " + newType); + // unknown type, keep the 256 bytes of data + if (newType == null) + return new PublicKey(null, _data); + int newLen = newType.getPubkeyLen(); + if (newLen == KEYSIZE_BYTES) + return new PublicKey(newType, _data); + byte[] newData = new byte[newLen]; + if (newLen < KEYSIZE_BYTES) { + // LEFT justified, padding at end + System.arraycopy(_data, 0, newData, 0, newLen); + } else { + // full 256 bytes + fragment in kcert + throw new IllegalArgumentException("TODO"); + } + return new PublicKey(newType, newData); + } + + /** + * Get the portion of this (type 0) PK that is really padding based on the Key Cert type given, + * if any + * + * @return trailing padding length > 0 or null if no padding or type is unknown + * @throws IllegalArgumentException if this is already typed to a different type + * @since 0.9.42 + */ + public byte[] getPadding(KeyCertificate kcert) { + if (_data == null) + throw new IllegalStateException(); + EncType newType = kcert.getEncType(); + if (_type == newType || newType == null) + return null; + if (_type != EncType.ELGAMAL_2048) + throw new IllegalStateException("Cannot convert " + _type + " to " + newType); + int newLen = newType.getPubkeyLen(); + if (newLen >= KEYSIZE_BYTES) + return null; + int padLen = KEYSIZE_BYTES - newLen; + byte[] pad = new byte[padLen]; + System.arraycopy(_data, _data.length - padLen, pad, 0, padLen); + return pad; + } + /** * @since 0.9.17 */ @@ -160,4 +219,23 @@ public class PublicKey extends SimpleDataStructure { buf.append(']'); return buf.toString(); } + + /** + * @since 0.9.42 + */ + @Override + public int hashCode() { + return DataHelper.hashCode(_type) ^ super.hashCode(); + } + + /** + * @since 0.9.42 + */ + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || !(obj instanceof PublicKey)) return false; + PublicKey s = (PublicKey) obj; + return _type == s._type && Arrays.equals(_data, s._data); + } } diff --git a/router/java/src/net/i2p/router/crypto/ElGamalAESEngine.java b/router/java/src/net/i2p/router/crypto/ElGamalAESEngine.java index 067ac3de87..8f0b61adc7 100644 --- a/router/java/src/net/i2p/router/crypto/ElGamalAESEngine.java +++ b/router/java/src/net/i2p/router/crypto/ElGamalAESEngine.java @@ -17,6 +17,7 @@ import java.util.Set; import net.i2p.I2PAppContext; import net.i2p.crypto.AESEngine; +import net.i2p.crypto.EncType; import net.i2p.crypto.SessionKeyManager; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; @@ -97,6 +98,8 @@ public final class ElGamalAESEngine { _log.error("Data is less than the minimum size (" + data.length + " < " + MIN_ENCRYPTED_SIZE + ")"); return null; } + if (targetPrivateKey.getType() != EncType.ELGAMAL_2048) + return null; byte tag[] = new byte[32]; System.arraycopy(data, 0, tag, 0, 32); @@ -399,7 +402,8 @@ public final class ElGamalAESEngine { * no less than the paddedSize parameter, but may be more. This method uses the * ElGamal+AES algorithm in the data structure spec. * - * @param target public key to which the data should be encrypted. + * @param target public key to which the data should be encrypted, must be ELGAMAL_2048. + * May be null if key and currentTag are non-null. * @param key session key to use during encryption * @param tagsForDelivery session tags to be associated with the key (or newKey if specified), or null; * 200 max enforced at receiver @@ -407,11 +411,17 @@ public final class ElGamalAESEngine { * @param newKey key to be delivered to the target, with which the tagsForDelivery should be associated, or null * @param paddedSize minimum size in bytes of the body after padding it (if less than the * body's real size, no bytes are appended but the body is not truncated) + * @throws IllegalArgumentException on bad target EncType * * Unused externally, only called by below (i.e. newKey is always null) */ public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, SessionTag currentTag, SessionKey newKey, long paddedSize) { + if (target != null) { + EncType type = target.getType(); + if (type != EncType.ELGAMAL_2048) + throw new IllegalArgumentException("Bad public key type " + type); + } if (currentTag == null) { if (_log.shouldLog(Log.INFO)) _log.info("Current tag is null, encrypting as new session"); @@ -420,8 +430,9 @@ public final class ElGamalAESEngine { } //if (_log.shouldLog(Log.INFO)) // _log.info("Current tag is NOT null, encrypting as existing session"); + // target unused, using key and tag only _context.statManager().updateFrequency("crypto.elGamalAES.encryptExistingSession"); - byte rv[] = encryptExistingSession(data, target, key, tagsForDelivery, currentTag, newKey, paddedSize); + byte rv[] = encryptExistingSession(data, key, tagsForDelivery, currentTag, newKey, paddedSize); if (_log.shouldLog(Log.DEBUG)) _log.debug("Existing session encrypted with tag: " + currentTag.toString() + ": " + rv.length + " bytes and key: " + key.toBase64() /* + ": " + Base64.encode(rv, 0, 64) */); return rv; @@ -447,13 +458,15 @@ public final class ElGamalAESEngine { * or a 514-byte ElGamal block and several 32-byte session tags for a new session. * So the returned encrypted data will be at least 32 bytes larger than paddedSize. * - * @param target public key to which the data should be encrypted. + * @param target public key to which the data should be encrypted, must be ELGAMAL_2048. + * May be null if key and currentTag are non-null. * @param key session key to use during encryption * @param tagsForDelivery session tags to be associated with the key or null; * 200 max enforced at receiver * @param currentTag sessionTag to use, or null if it should use ElG (i.e. new session) * @param paddedSize minimum size in bytes of the body after padding it (if less than the * body's real size, no bytes are appended but the body is not truncated) + * @throws IllegalArgumentException on bad target EncType * */ public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, @@ -468,6 +481,7 @@ public final class ElGamalAESEngine { * * @param tagsForDelivery session tags to be associated with the key or null; * 200 max enforced at receiver + * @throws IllegalArgumentException on bad target EncType * @deprecated unused */ public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, long paddedSize) { @@ -479,6 +493,7 @@ public final class ElGamalAESEngine { * No new session key * No current tag (encrypt as new session) * + * @throws IllegalArgumentException on bad target EncType * @deprecated unused */ public byte[] encrypt(byte data[], PublicKey target, SessionKey key, long paddedSize) { @@ -573,11 +588,10 @@ public final class ElGamalAESEngine { * - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0 * * - * @param target unused, this is AES encrypt only using the session key and tag * @param tagsForDelivery session tags to be associated with the key or null; * 200 max enforced at receiver */ - private byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, + private byte[] encryptExistingSession(byte data[], SessionKey key, Set tagsForDelivery, SessionTag currentTag, SessionKey newKey, long paddedSize) { //_log.debug("Encrypting to an EXISTING session"); byte rawTag[] = currentTag.getData(); diff --git a/router/java/src/net/i2p/router/crypto/TransientSessionKeyManager.java b/router/java/src/net/i2p/router/crypto/TransientSessionKeyManager.java index 37deb99eb0..f0f7ddb62e 100644 --- a/router/java/src/net/i2p/router/crypto/TransientSessionKeyManager.java +++ b/router/java/src/net/i2p/router/crypto/TransientSessionKeyManager.java @@ -25,6 +25,7 @@ import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; import net.i2p.I2PAppContext; +import net.i2p.crypto.EncType; import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.TagSetHandle; import net.i2p.data.DataHelper; @@ -283,6 +284,8 @@ public class TransientSessionKeyManager extends SessionKeyManager { * Retrieve the session key currently associated with encryption to the target. * Generates a new session and session key if not previously exising. * + * @param target public key to which the data should be encrypted, must be ELGAMAL_2048. + * @throws IllegalArgumentException on bad target EncType * @return non-null * @since 0.9 */ @@ -310,6 +313,9 @@ public class TransientSessionKeyManager extends SessionKeyManager { * * Racy if called after getCurrentKey() to check for a current session; * use getCurrentOrNewKey() in that case. + * + * @param target public key to which the data should be encrypted, must be ELGAMAL_2048. + * @throws IllegalArgumentException on bad target EncType */ @Override public void createSession(PublicKey target, SessionKey key) { @@ -322,6 +328,9 @@ public class TransientSessionKeyManager extends SessionKeyManager { * */ private OutboundSession createAndReturnSession(PublicKey target, SessionKey key) { + EncType type = target.getType(); + if (type != EncType.ELGAMAL_2048) + throw new IllegalArgumentException("Bad public key type " + type); if (_log.shouldLog(Log.INFO)) _log.info("New OB session, sesskey: " + key + " target: " + toString(target)); OutboundSession sess = new OutboundSession(_context, _log, target, key); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java index 0433d61a99..88fe43f176 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java @@ -10,6 +10,7 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import net.i2p.crypto.EncType; import net.i2p.crypto.SigType; import net.i2p.data.Base64; import net.i2p.data.DataHelper; @@ -425,6 +426,13 @@ public class IterativeSearchJob extends FloodSearchJob { // request encrypted reply // now covered by version check above, which is more recent //if (DatabaseLookupMessage.supportsEncryptedReplies(ri)) { + EncType type = ri.getIdentity().getPublicKey().getType(); + if (type != EncType.ELGAMAL_2048) { + failed(peer, false); + if (_log.shouldLog(Log.WARN)) + _log.warn(getJobId() + ": Can't do encrypted lookup to " + peer + " with EncType " + type); + return; + } if (true) { MessageWrapper.OneTimeSession sess; if (isClientReplyTunnel) diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java b/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java index e6e5889f9e..bd544b5e8c 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java @@ -3,6 +3,7 @@ package net.i2p.router.networkdb.kademlia; import java.util.HashSet; import java.util.Set; +import net.i2p.crypto.EncType; import net.i2p.crypto.SessionKeyManager; import net.i2p.crypto.TagSetHandle; import net.i2p.data.Certificate; @@ -40,9 +41,14 @@ public class MessageWrapper { * * @param from must be a local client with a session key manager, * or null to use the router's session key manager + * @param to must be ELGAMAL_2048 EncType * @return null on encrypt failure */ static WrappedMessage wrap(RouterContext ctx, I2NPMessage m, Hash from, RouterInfo to) { + PublicKey sentTo = to.getIdentity().getPublicKey(); + if (sentTo.getType() != EncType.ELGAMAL_2048) + return null; + PayloadGarlicConfig payload = new PayloadGarlicConfig(Certificate.NULL_CERT, ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE), m.getMessageExpiration(), @@ -63,7 +69,6 @@ public class MessageWrapper { if (msg == null) return null; TagSetHandle tsh = null; - PublicKey sentTo = to.getIdentity().getPublicKey(); if (!sentTags.isEmpty()) tsh = skm.tagsDelivered(sentTo, sentKey, sentTags); //if (_log.shouldLog(Log.DEBUG)) @@ -118,10 +123,15 @@ public class MessageWrapper { * to hide the contents from the OBEP. * Forces ElGamal. * + * @param to must be ELGAMAL_2048 EncType * @return null on encrypt failure * @since 0.9.5 */ static GarlicMessage wrap(RouterContext ctx, I2NPMessage m, RouterInfo to) { + PublicKey key = to.getIdentity().getPublicKey(); + if (key.getType() != EncType.ELGAMAL_2048) + return null; + PayloadGarlicConfig payload = new PayloadGarlicConfig(Certificate.NULL_CERT, ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE), m.getMessageExpiration(), @@ -129,7 +139,6 @@ public class MessageWrapper { payload.setRecipient(to); SessionKey sentKey = ctx.keyGenerator().generateSessionKey(); - PublicKey key = to.getIdentity().getPublicKey(); GarlicMessage msg = GarlicMessageBuilder.buildMessage(ctx, payload, null, null, key, sentKey, null); return msg; diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java index 23fdcefb9f..b9eda93c6e 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java @@ -12,6 +12,7 @@ import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; +import net.i2p.crypto.EncType; import net.i2p.crypto.SHA256Generator; import net.i2p.crypto.SigType; import net.i2p.data.DataFormatException; @@ -469,6 +470,9 @@ public abstract class TunnelPeerSelector extends ConnectChecker { maxLen++; if (cap.length() <= maxLen) return true; + if (peer.getIdentity().getPublicKey().getType() != EncType.ELGAMAL_2048) + return true; + // otherwise, it contains flags we aren't trying to focus on, // so don't exclude it based on published capacity