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);
config.setProperty(p, Base64.encode(rk));
}
// As of 0.9.18, add persistent leaseset keys if not present
// but only if we know the sigtype
p = OPT + "i2cp.leaseSetSigningPrivateKey";
if (_dest != null && !config.containsKey(p)) {
try {
SigType type = _dest.getSigType();
SimpleDataStructure keys[] = KeyGenerator.getInstance().generateSigningKeys(type);
config.setProperty(p, type.name() + ':' + keys[1].toBase64());
} catch (GeneralSecurityException gse) {
// so much for that
String senc = config.getProperty(OPT + "i2cp.leaseSetEncType", "0");
String slstyp = config.getProperty(OPT + "i2cp.leaseSetType", "0");
if (senc.equals("0") && slstyp.equals("0")) {
// only for LS1
p = OPT + "i2cp.leaseSetSigningPrivateKey";
if (!config.containsKey(p)) {
SigType type;
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
// multiple types as of 0.9.46, add missing ones
p = OPT + "i2cp.leaseSetPrivateKey";
@ -860,7 +875,6 @@ public class TunnelConfig {
// normalize it first to make the code below easier
if (skeys != null && skeys.length() > 0 && !skeys.contains(":"))
config.setProperty(p, "ELGAMAL_2048:" + skeys);
String senc = config.getProperty(OPT + "i2cp.leaseSetEncType", "0");
String[] senca = DataHelper.split(senc, ",");
// for each configured enc type, generate a key if we don't have it
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.Map;
import java.util.Properties;
import java.util.Set;
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.
private static final String[] CLIENT_SIDE_OPTIONS = new String[] {
"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",
I2PClient.PROP_ENABLE_SSL, I2PClient.PROP_TCP_HOST, I2PClient.PROP_TCP_PORT,
// long and shouldn't be passed through
@ -105,12 +107,28 @@ class I2CPMessageProducer {
* @return a new copy, may be modified
* @since 0.9.38
*/
private static Properties getRouterOptions(I2PSessionImpl session) {
private Properties getRouterOptions(I2PSessionImpl session) {
Properties props = new Properties();
props.putAll(session.getOptions());
for (int i = 0; i < CLIENT_SIDE_OPTIONS.length; 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;
}

View File

@ -516,18 +516,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
continue;
}
String val = options.getProperty(key);
// 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)
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);
}
rv.setProperty(key, val);
}
return rv;
}

View File

@ -186,35 +186,69 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
// reuse the old keys for the client
LeaseInfo li = _existingLeaseSets.get(dest);
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
String spk = session.getOptions().getProperty(PROP_LS_PK);
// [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);
SigningPrivateKey signingPrivKey = null;
if (spk != null && sspk != null) {
if (spk != null && (isLS2 || 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;
}
if (useOldKeys) {
try {
signingPrivKey = new SigningPrivateKey(type);
signingPrivKey.fromBase64(sspk);
} catch (DataFormatException dfe) {
useOldKeys = false;
signingPrivKey = null;
if (!isLS2) {
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;
}
if (useOldKeys) {
try {
signingPrivKey = new SigningPrivateKey(type);
signingPrivKey.fromBase64(sspk);
} catch (DataFormatException dfe) {
useOldKeys = false;
signingPrivKey = null;
}
}
}
if (useOldKeys) {
parsePrivateKeys(spk, privKeys);
parsePrivateKeys(spk, privKeys, types);
}
}
if (privKeys.isEmpty() && !_existingLeaseSets.isEmpty()) {
@ -223,8 +257,8 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
for (Map.Entry<Destination, LeaseInfo> e : _existingLeaseSets.entrySet()) {
if (pk.equals(e.getKey().getPublicKey())) {
privKeys.addAll(e.getValue().getPrivateKeys());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Creating new leaseInfo keys for " + dest + " with private key from " + e.getKey());
if (_log.shouldInfo())
_log.info("Creating leaseInfo for " + dest.toBase32() + " with private key from " + e.getKey().toBase32());
break;
}
}
@ -232,53 +266,27 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
if (!privKeys.isEmpty()) {
if (signingPrivKey != null) {
li = new LeaseInfo(privKeys, signingPrivKey);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Creating new leaseInfo keys for " + dest + " WITH configured private keys");
if (_log.shouldInfo())
_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 {
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 {
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);
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");
li = new LeaseInfo(dest, types, isLS2);
if (_log.shouldInfo())
_log.info("Creating leaseInfo for " + dest.toBase32() + " without configured private keys");
}
_existingLeaseSets.put(dest, li);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Caching the old leaseInfo keys for "
+ dest);
+ dest.toBase32());
}
if (isLS2) {
@ -438,7 +446,7 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
* @param privKeys out parameter
* @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, ",");
for (String spk : spks) {
int colon = spk.indexOf(':');
@ -446,14 +454,17 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
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 (allowedTypes.contains(type)) {
try {
PrivateKey privKey = new PrivateKey(type);
privKey.fromBase64(spk.substring(colon + 1));
privKeys.add(privKey);
} catch (DataFormatException dfe) {
_log.error("Bad private key: " + spk, dfe);
}
} else {
if (_log.shouldDebug())
_log.debug("Using crypto type: " + type);
} catch (DataFormatException dfe) {
_log.error("Bad private key: " + spk, dfe);
_log.debug("Ignoring private key with unconfigured crypto type: " + type);
}
} else {
_log.error("Unsupported crypto type: " + type);
@ -462,12 +473,18 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
_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);
EncType type = EncType.ELGAMAL_2048;
if (allowedTypes.contains(type)) {
try {
PrivateKey privKey = new PrivateKey();
privKey.fromBase64(spk);
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 {
_log.error("Empty crypto type");
@ -488,7 +505,7 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
* New keys
* @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) {
Collections.sort(types, Collections.reverseOrder());
}
@ -499,19 +516,24 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
_pubKeys.add(encKeys.getPublic());
_privKeys.add(encKeys.getPrivate());
}
// 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);
if (isLS2) {
_signingPubKey = null;
_signingPrivKey = null;
} else {
// 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);
}
_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
* @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
* @since 0.9.21
*/
@ -549,6 +571,24 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
_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 */
public PublicKey getPublicKey() {
return _pubKeys.get(0);
@ -569,10 +609,12 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
return _privKeys;
}
/** @return null for LS2 */
public SigningPublicKey getSigningPublicKey() {
return _signingPubKey;
}
/** @return null for LS2 */
public SigningPrivateKey getSigningPrivateKey() {
return _signingPrivKey;
}