I2CP: Fix issues with persisted leaseset private keys

- Don't generate revocation key for LS2
- Fix generation of persistent revocation key for LS1
- Fix persistent private keys without persistent revocation key
- Don't put unconfigured private keys in leaseset
- Don't strip i2cp.leaseSetPrivateKey from config before it's used
This commit is contained in:
zzz
2020-06-06 14:36:01 +00:00
parent 326178ad47
commit cd77461fba
4 changed files with 171 additions and 108 deletions

View File

@ -841,18 +841,33 @@ public class TunnelConfig {
_context.random().nextBytes(rk); _context.random().nextBytes(rk);
config.setProperty(p, Base64.encode(rk)); config.setProperty(p, Base64.encode(rk));
} }
// As of 0.9.18, add persistent leaseset keys if not present // As of 0.9.18, add persistent leaseset keys if not present
// but only if we know the sigtype // but only if we know the sigtype
p = OPT + "i2cp.leaseSetSigningPrivateKey"; String senc = config.getProperty(OPT + "i2cp.leaseSetEncType", "0");
if (_dest != null && !config.containsKey(p)) { String slstyp = config.getProperty(OPT + "i2cp.leaseSetType", "0");
try { if (senc.equals("0") && slstyp.equals("0")) {
SigType type = _dest.getSigType(); // only for LS1
SimpleDataStructure keys[] = KeyGenerator.getInstance().generateSigningKeys(type); p = OPT + "i2cp.leaseSetSigningPrivateKey";
config.setProperty(p, type.name() + ':' + keys[1].toBase64()); if (!config.containsKey(p)) {
} catch (GeneralSecurityException gse) { SigType type;
// so much for that if (_dest != null) {
type = _dest.getSigType();
} else {
String ssigtyp = config.getProperty(OPT + "i2cp.destination.sigType", "0");
type = SigType.parseSigType(ssigtyp);
}
if (type != null) {
try {
SimpleDataStructure keys[] = KeyGenerator.getInstance().generateSigningKeys(type);
config.setProperty(p, type.name() + ':' + keys[1].toBase64());
} catch (GeneralSecurityException gse) {
// so much for that
}
}
} }
} }
// persistent LS encryption keys // persistent LS encryption keys
// multiple types as of 0.9.46, add missing ones // multiple types as of 0.9.46, add missing ones
p = OPT + "i2cp.leaseSetPrivateKey"; p = OPT + "i2cp.leaseSetPrivateKey";
@ -860,7 +875,6 @@ public class TunnelConfig {
// normalize it first to make the code below easier // normalize it first to make the code below easier
if (skeys != null && skeys.length() > 0 && !skeys.contains(":")) if (skeys != null && skeys.length() > 0 && !skeys.contains(":"))
config.setProperty(p, "ELGAMAL_2048:" + skeys); config.setProperty(p, "ELGAMAL_2048:" + skeys);
String senc = config.getProperty(OPT + "i2cp.leaseSetEncType", "0");
String[] senca = DataHelper.split(senc, ","); String[] senca = DataHelper.split(senc, ",");
// for each configured enc type, generate a key if we don't have it // for each configured enc type, generate a key if we don't have it
for (int i = 0; i < senca.length; i++) { for (int i = 0; i < senca.length; i++) {

View File

@ -9,7 +9,9 @@ package net.i2p.client.impl;
* *
*/ */
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -64,7 +66,7 @@ class I2CPMessageProducer {
// Note that some are listed for both client and server side, don't include those below. // Note that some are listed for both client and server side, don't include those below.
private static final String[] CLIENT_SIDE_OPTIONS = new String[] { private static final String[] CLIENT_SIDE_OPTIONS = new String[] {
"i2cp.closeIdleTime", "i2cp.closeOnIdle", "i2cp.encryptLeaseSet", "i2cp.closeIdleTime", "i2cp.closeOnIdle", "i2cp.encryptLeaseSet",
"i2cp.gzip", "i2cp.leaseSetKey", "i2cp.leaseSetPrivateKey", I2PClient.PROP_GZIP, "i2cp.leaseSetKey", "i2cp.leaseSetPrivateKey",
"i2cp.leaseSetSigningPrivateKey", "i2cp.reduceIdleTime", "i2cp.reduceOnIdle", "i2cp.leaseSetSigningPrivateKey", "i2cp.reduceIdleTime", "i2cp.reduceOnIdle",
I2PClient.PROP_ENABLE_SSL, I2PClient.PROP_TCP_HOST, I2PClient.PROP_TCP_PORT, I2PClient.PROP_ENABLE_SSL, I2PClient.PROP_TCP_HOST, I2PClient.PROP_TCP_PORT,
// long and shouldn't be passed through // long and shouldn't be passed through
@ -105,12 +107,28 @@ class I2CPMessageProducer {
* @return a new copy, may be modified * @return a new copy, may be modified
* @since 0.9.38 * @since 0.9.38
*/ */
private static Properties getRouterOptions(I2PSessionImpl session) { private Properties getRouterOptions(I2PSessionImpl session) {
Properties props = new Properties(); Properties props = new Properties();
props.putAll(session.getOptions()); props.putAll(session.getOptions());
for (int i = 0; i < CLIENT_SIDE_OPTIONS.length; i++) { for (int i = 0; i < CLIENT_SIDE_OPTIONS.length; i++) {
props.remove(CLIENT_SIDE_OPTIONS[i]); props.remove(CLIENT_SIDE_OPTIONS[i]);
} }
for (Iterator<Map.Entry<Object, Object>> iter = props.entrySet().iterator(); iter.hasNext(); ) {
// Long strings MUST be removed, even in router context,
// as the session config properties must be serialized to be signed.
// fixme, bytes could still be over 255 (unlikely)
Map.Entry<Object, Object> e = iter.next();
String key = (String) e.getKey();
String val = (String) e.getValue();
if (key.length() > 255 || val.length() > 255) {
if (_log.shouldLog(Log.WARN))
_log.warn("Not passing on property ["
+ key
+ "] in the session config, key or value is too long (max = 255): "
+ val);
iter.remove();
}
}
return props; return props;
} }

View File

@ -516,18 +516,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
continue; continue;
} }
String val = options.getProperty(key); String val = options.getProperty(key);
// Long strings MUST be removed, even in router context, rv.setProperty(key, val);
// as the session config properties must be serialized to be signed.
// fixme, bytes could still be over 255 (unlikely)
if (key.length() > 255 || val.length() > 255) {
if (_log.shouldLog(Log.WARN))
_log.warn("Not passing on property ["
+ key
+ "] in the session config, key or value is too long (max = 255): "
+ val);
} else {
rv.setProperty(key, val);
}
} }
return rv; return rv;
} }

View File

@ -186,35 +186,69 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
// reuse the old keys for the client // reuse the old keys for the client
LeaseInfo li = _existingLeaseSets.get(dest); LeaseInfo li = _existingLeaseSets.get(dest);
if (li == null) { if (li == null) {
List<EncType> types = new ArrayList<EncType>(2);
String senc = session.getOptions().getProperty(PROP_LS_ENCTYPE);
if (senc != null) {
if (!PREFER_NEW_ENC && senc.equals("4,0"))
senc = "0,4";
else if (PREFER_NEW_ENC && senc.equals("0,4"))
senc = "4,0";
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);
} else {
_log.error("Unsupported crypto type: " + newtype);
}
} else {
_log.error("Unsupported crypto type: " + sencaa);
}
}
}
if (types.isEmpty()) {
//if (_log.shouldDebug())
// _log.debug("Using default crypto type");
types.add(EncType.ELGAMAL_2048);
}
// [enctype:]b64,... of private keys // [enctype:]b64,... of private keys
String spk = session.getOptions().getProperty(PROP_LS_PK); String spk = session.getOptions().getProperty(PROP_LS_PK);
// [sigtype:]b64 of private key // [sigtype:]b64 of private key
String sspk = session.getOptions().getProperty(PROP_LS_SPK); // only for LS1
String sspk = isLS2 ? null : session.getOptions().getProperty(PROP_LS_SPK);
List<PrivateKey> privKeys = new ArrayList<PrivateKey>(2); List<PrivateKey> privKeys = new ArrayList<PrivateKey>(2);
SigningPrivateKey signingPrivKey = null; SigningPrivateKey signingPrivKey = null;
if (spk != null && sspk != null) { if (spk != null && (isLS2 || sspk != null)) {
boolean useOldKeys = true; boolean useOldKeys = true;
int colon = sspk.indexOf(':'); if (!isLS2) {
SigType type = dest.getSigType(); int colon = sspk.indexOf(':');
if (colon > 0) { SigType type = dest.getSigType();
String stype = sspk.substring(0, colon); if (colon > 0) {
SigType t = SigType.parseSigType(stype); String stype = sspk.substring(0, colon);
if (t == type) SigType t = SigType.parseSigType(stype);
sspk = sspk.substring(colon + 1); if (t == type)
else sspk = sspk.substring(colon + 1);
useOldKeys = false; else
} useOldKeys = false;
if (useOldKeys) { }
try { if (useOldKeys) {
signingPrivKey = new SigningPrivateKey(type); try {
signingPrivKey.fromBase64(sspk); signingPrivKey = new SigningPrivateKey(type);
} catch (DataFormatException dfe) { signingPrivKey.fromBase64(sspk);
useOldKeys = false; } catch (DataFormatException dfe) {
signingPrivKey = null; useOldKeys = false;
signingPrivKey = null;
}
} }
} }
if (useOldKeys) { if (useOldKeys) {
parsePrivateKeys(spk, privKeys); parsePrivateKeys(spk, privKeys, types);
} }
} }
if (privKeys.isEmpty() && !_existingLeaseSets.isEmpty()) { if (privKeys.isEmpty() && !_existingLeaseSets.isEmpty()) {
@ -223,8 +257,8 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
for (Map.Entry<Destination, LeaseInfo> e : _existingLeaseSets.entrySet()) { for (Map.Entry<Destination, LeaseInfo> e : _existingLeaseSets.entrySet()) {
if (pk.equals(e.getKey().getPublicKey())) { if (pk.equals(e.getKey().getPublicKey())) {
privKeys.addAll(e.getValue().getPrivateKeys()); privKeys.addAll(e.getValue().getPrivateKeys());
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldInfo())
_log.debug("Creating new leaseInfo keys for " + dest + " with private key from " + e.getKey()); _log.info("Creating leaseInfo for " + dest.toBase32() + " with private key from " + e.getKey().toBase32());
break; break;
} }
} }
@ -232,53 +266,27 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
if (!privKeys.isEmpty()) { if (!privKeys.isEmpty()) {
if (signingPrivKey != null) { if (signingPrivKey != null) {
li = new LeaseInfo(privKeys, signingPrivKey); li = new LeaseInfo(privKeys, signingPrivKey);
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldInfo())
_log.debug("Creating new leaseInfo keys for " + dest + " WITH configured private keys"); _log.info("Creating leaseInfo for " + dest.toBase32() + " LS1 WITH configured private keys");
} else if (isLS2) {
li = new LeaseInfo(privKeys);
if (_log.shouldInfo())
_log.info("Creating leaseInfo for " + dest.toBase32() + " LS2 WITH configured private keys");
} else { } else {
li = new LeaseInfo(privKeys, dest); li = new LeaseInfo(privKeys, dest);
if (_log.shouldInfo())
_log.info("Creating leaseInfo for " + dest.toBase32() + " LS1 WITH configured private keys and new revocation key");
} }
} else { } else {
List<EncType> types = new ArrayList<EncType>(2); li = new LeaseInfo(dest, types, isLS2);
String senc = session.getOptions().getProperty(PROP_LS_ENCTYPE); if (_log.shouldInfo())
if (senc != null) { _log.info("Creating leaseInfo for " + dest.toBase32() + " without configured private keys");
if (!PREFER_NEW_ENC && senc.equals("4,0"))
senc = "0,4";
else if (PREFER_NEW_ENC && senc.equals("0,4"))
senc = "4,0";
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 type: " + sencaa);
}
}
}
if (types.isEmpty()) {
if (_log.shouldDebug())
_log.debug("Using default crypto type");
types.add(EncType.ELGAMAL_2048);
}
li = new LeaseInfo(dest, types);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Creating new leaseInfo keys for " + dest + " without configured private keys");
} }
_existingLeaseSets.put(dest, li); _existingLeaseSets.put(dest, li);
} else { } else {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Caching the old leaseInfo keys for " _log.debug("Caching the old leaseInfo keys for "
+ dest); + dest.toBase32());
} }
if (isLS2) { if (isLS2) {
@ -438,7 +446,7 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
* @param privKeys out parameter * @param privKeys out parameter
* @since 0.9.39 * @since 0.9.39
*/ */
private void parsePrivateKeys(String spkl, List<PrivateKey> privKeys) { private void parsePrivateKeys(String spkl, List<PrivateKey> privKeys, List<EncType> allowedTypes) {
String[] spks = DataHelper.split(spkl, ","); String[] spks = DataHelper.split(spkl, ",");
for (String spk : spks) { for (String spk : spks) {
int colon = spk.indexOf(':'); int colon = spk.indexOf(':');
@ -446,14 +454,17 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
EncType type = EncType.parseEncType(spk.substring(0, colon)); EncType type = EncType.parseEncType(spk.substring(0, colon));
if (type != null) { if (type != null) {
if (type.isAvailable()) { if (type.isAvailable()) {
try { if (allowedTypes.contains(type)) {
PrivateKey privKey = new PrivateKey(type); try {
privKey.fromBase64(spk.substring(colon + 1)); PrivateKey privKey = new PrivateKey(type);
privKeys.add(privKey); privKey.fromBase64(spk.substring(colon + 1));
privKeys.add(privKey);
} catch (DataFormatException dfe) {
_log.error("Bad private key: " + spk, dfe);
}
} else {
if (_log.shouldDebug()) if (_log.shouldDebug())
_log.debug("Using crypto type: " + type); _log.debug("Ignoring private key with unconfigured crypto type: " + type);
} catch (DataFormatException dfe) {
_log.error("Bad private key: " + spk, dfe);
} }
} else { } else {
_log.error("Unsupported crypto type: " + type); _log.error("Unsupported crypto type: " + type);
@ -462,12 +473,18 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
_log.error("Unsupported crypto type: " + spk); _log.error("Unsupported crypto type: " + spk);
} }
} else if (colon < 0) { } else if (colon < 0) {
try { EncType type = EncType.ELGAMAL_2048;
PrivateKey privKey = new PrivateKey(); if (allowedTypes.contains(type)) {
privKey.fromBase64(spk); try {
privKeys.add(privKey); PrivateKey privKey = new PrivateKey();
} catch (DataFormatException dfe) { privKey.fromBase64(spk);
_log.error("Bad private key: " + spk, dfe); privKeys.add(privKey);
} catch (DataFormatException dfe) {
_log.error("Bad private key: " + spk, dfe);
}
} else {
if (_log.shouldDebug())
_log.debug("Ignoring private key with unconfigured crypto type: " + type);
} }
} else { } else {
_log.error("Empty crypto type"); _log.error("Empty crypto type");
@ -488,7 +505,7 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
* New keys * New keys
* @param types must be available * @param types must be available
*/ */
public LeaseInfo(Destination dest, List<EncType> types) { public LeaseInfo(Destination dest, List<EncType> types, boolean isLS2) {
if (types.size() > 1 && PREFER_NEW_ENC) { if (types.size() > 1 && PREFER_NEW_ENC) {
Collections.sort(types, Collections.reverseOrder()); Collections.sort(types, Collections.reverseOrder());
} }
@ -499,19 +516,24 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
_pubKeys.add(encKeys.getPublic()); _pubKeys.add(encKeys.getPublic());
_privKeys.add(encKeys.getPrivate()); _privKeys.add(encKeys.getPrivate());
} }
// must be same type as the Destination's signing key if (isLS2) {
SimpleDataStructure signKeys[]; _signingPubKey = null;
try { _signingPrivKey = null;
signKeys = KeyGenerator.getInstance().generateSigningKeys(dest.getSigningPublicKey().getType()); } else {
} catch (GeneralSecurityException gse) { // must be same type as the Destination's signing key
throw new IllegalStateException(gse); SimpleDataStructure signKeys[];
try {
signKeys = KeyGenerator.getInstance().generateSigningKeys(dest.getSigningPublicKey().getType());
} catch (GeneralSecurityException gse) {
throw new IllegalStateException(gse);
}
_signingPubKey = (SigningPublicKey) signKeys[0];
_signingPrivKey = (SigningPrivateKey) signKeys[1];
} }
_signingPubKey = (SigningPublicKey) signKeys[0];
_signingPrivKey = (SigningPrivateKey) signKeys[1];
} }
/** /**
* Existing keys * Existing keys, LS1 only
* @param privKeys all EncTypes must be available * @param privKeys all EncTypes must be available
* @since 0.9.18 * @since 0.9.18
*/ */
@ -529,7 +551,7 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
} }
/** /**
* Existing crypto keys, new signing key * Existing crypto keys, new signing key, LS1 only
* @param privKeys all EncTypes must be available * @param privKeys all EncTypes must be available
* @since 0.9.21 * @since 0.9.21
*/ */
@ -549,6 +571,24 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
_signingPrivKey = (SigningPrivateKey) signKeys[1]; _signingPrivKey = (SigningPrivateKey) signKeys[1];
} }
/**
* Existing keys, LS2 only
* @param privKeys all EncTypes must be available
* @since 0.9.47
*/
public LeaseInfo(List<PrivateKey> privKeys) {
if (privKeys.size() > 1) {
Collections.sort(privKeys, new PrivKeyComparator());
}
_privKeys = privKeys;
_pubKeys = new ArrayList<PublicKey>(privKeys.size());
for (PrivateKey privKey : privKeys) {
_pubKeys.add(KeyGenerator.getPublicKey(privKey));
}
_signingPubKey = null;
_signingPrivKey = null;
}
/** @return the first one if more than one */ /** @return the first one if more than one */
public PublicKey getPublicKey() { public PublicKey getPublicKey() {
return _pubKeys.get(0); return _pubKeys.get(0);
@ -569,10 +609,12 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
return _privKeys; return _privKeys;
} }
/** @return null for LS2 */
public SigningPublicKey getSigningPublicKey() { public SigningPublicKey getSigningPublicKey() {
return _signingPubKey; return _signingPubKey;
} }
/** @return null for LS2 */
public SigningPrivateKey getSigningPrivateKey() { public SigningPrivateKey getSigningPrivateKey() {
return _signingPrivKey; return _signingPrivKey;
} }