From d95d81aea77fc5f4dd6e8bb80e05ec8fa8cdcee9 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 1 Jan 2019 17:14:37 +0000 Subject: [PATCH] Data: Add support for LS2 multiple encryption keys (proposal 123) --- core/java/src/net/i2p/data/LeaseSet2.java | 113 +++++++++++++++++----- 1 file changed, 89 insertions(+), 24 deletions(-) diff --git a/core/java/src/net/i2p/data/LeaseSet2.java b/core/java/src/net/i2p/data/LeaseSet2.java index d170ee4622..6daec7ff14 100644 --- a/core/java/src/net/i2p/data/LeaseSet2.java +++ b/core/java/src/net/i2p/data/LeaseSet2.java @@ -4,6 +4,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Properties; import net.i2p.I2PAppContext; @@ -35,9 +39,12 @@ public class LeaseSet2 extends LeaseSet { protected Properties _options; // only used for unknown types; else use _encryptionKey.getType() private int _encType; + // only used if more than one key, otherwise null + private List _encryptionKeys; private static final int FLAG_OFFLINE_KEYS = 1; private static final int FLAG_UNPUBLISHED = 2; + private static final int MAX_KEYS = 8; public LeaseSet2() { super(); @@ -59,6 +66,36 @@ public class LeaseSet2 extends LeaseSet { return _options.getProperty(opt); } + /** + * Add an encryption key. + */ + public void addEncryptionKey(PublicKey key) { + if (_encryptionKey == null) { + setEncryptionKey(key); + } else { + if (_encryptionKeys == null) { + _encryptionKeys = new ArrayList(4); + _encryptionKeys.add(_encryptionKey); + } else { + if (_encryptionKeys.size() >= MAX_KEYS) + throw new IllegalStateException(); + } + _encryptionKeys.add(key); + } + } + + /** + * This returns all the keys. getEncryptionKey() returns the first one. + * @return not a copy, do not modify, null if none + */ + public List getEncryptionKeys() { + if (_encryptionKeys != null) + return _encryptionKeys; + if (_encryptionKey != null) + return Collections.singletonList(_encryptionKey); + return null; + } + /** * Configure a set of options or statistics that the router can expose. * Makes a copy. @@ -193,18 +230,27 @@ public class LeaseSet2 extends LeaseSet { readHeader(in); // LS2 part _options = DataHelper.readProperties(in); - _encType = (int) DataHelper.readLong(in, 2); - int encLen = (int) DataHelper.readLong(in, 2); - // TODO - if (_encType == 0) { - _encryptionKey = PublicKey.create(in); - } else { - EncType type = EncType.getByCode(_encType); - // type will be null if unknown - byte[] encKey = new byte[encLen]; - DataHelper.read(in, encKey); - // this will throw IAE if type is non-null and length is wrong - _encryptionKey = new PublicKey(type, encKey); + int numKeys = in.read(); + if (numKeys <= 0 || numKeys > MAX_KEYS) + throw new DataFormatException("Bad key count: " + numKeys); + if (numKeys > 1) + _encryptionKeys = new ArrayList(numKeys); + for (int i = 0; i < numKeys; i++) { + _encType = (int) DataHelper.readLong(in, 2); + int encLen = (int) DataHelper.readLong(in, 2); + // TODO + if (_encType == 0) { + _encryptionKey = PublicKey.create(in); + } else { + EncType type = EncType.getByCode(_encType); + // type will be null if unknown + byte[] encKey = new byte[encLen]; + DataHelper.read(in, encKey); + // this will throw IAE if type is non-null and length is wrong + _encryptionKey = new PublicKey(type, encKey); + } + if (numKeys > 1) + _encryptionKeys.add(_encryptionKey); } int numLeases = in.read(); if (numLeases > MAX_LEASES) @@ -246,14 +292,18 @@ public class LeaseSet2 extends LeaseSet { } else { DataHelper.writeLong(out, 2, 0); } - EncType type = _encryptionKey.getType(); - if (type != null) { - DataHelper.writeLong(out, 2, type.getCode()); - } else { - DataHelper.writeLong(out, 2, _encType); + List keys = getEncryptionKeys(); + out.write((byte) keys.size()); + for (PublicKey key : keys) { + EncType type = key.getType(); + if (type != null) { + DataHelper.writeLong(out, 2, type.getCode()); + } else { + DataHelper.writeLong(out, 2, _encType); + } + DataHelper.writeLong(out, 2, key.length()); + key.writeBytes(out); } - DataHelper.writeLong(out, 2, _encryptionKey.length()); - _encryptionKey.writeBytes(out); out.write((byte) _leases.size()); for (Lease lease : _leases) { lease.writeBytes(out); @@ -433,13 +483,25 @@ public class LeaseSet2 extends LeaseSet { StringBuilder buf = new StringBuilder(128); buf.append("[LeaseSet2: "); buf.append("\n\tDestination: ").append(_destination); - buf.append("\n\tEncryptionKey: ").append(_encryptionKey); + List keys = getEncryptionKeys(); + 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)); + } if (isOffline()) { buf.append("\n\tTransient Key: ").append(_transientSigningPublicKey); buf.append("\n\tTransient Expires: ").append(new java.util.Date(_transientExpires)); buf.append("\n\tOffline Signature: ").append(_offlineSignature); } buf.append("\n\tOptions: ").append((_options != null) ? _options.size() : 0); + if (_options != null && !_options.isEmpty()) { + for (Map.Entry e : _options.entrySet()) { + String key = (String) e.getKey(); + String val = (String) e.getValue(); + buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]"); + } + } buf.append("\n\tSignature: ").append(_signature); buf.append("\n\tPublished: ").append(new java.util.Date(_published)); buf.append("\n\tExpires: ").append(new java.util.Date(_expires)); @@ -465,10 +527,10 @@ public class LeaseSet2 extends LeaseSet { PrivateKeyFile pkf = new PrivateKeyFile(f); pkf.createIfAbsent(SigType.EdDSA_SHA512_Ed25519); System.out.println("Online test"); - java.io.File f2 = new java.io.File("online-leaseset.dat"); + java.io.File f2 = new java.io.File("online-ls2.dat"); test(pkf, f2, false); System.out.println("Offline test"); - f2 = new java.io.File("offline-leaseset.dat"); + f2 = new java.io.File("offline-ls2.dat"); test(pkf, f2, true); } @@ -494,10 +556,13 @@ public class LeaseSet2 extends LeaseSet { ls2.setDestination(pkf.getDestination()); SimpleDataStructure encKeys[] = net.i2p.crypto.KeyGenerator.getInstance().generatePKIKeys(); PublicKey pubKey = (PublicKey) encKeys[0]; - ls2.setEncryptionKey(pubKey); + ls2.addEncryptionKey(pubKey); + net.i2p.crypto.KeyPair encKeys2 = net.i2p.crypto.KeyGenerator.getInstance().generatePKIKeys(net.i2p.crypto.EncType.ECIES_X25519); + pubKey = encKeys2.getPublic(); + ls2.addEncryptionKey(pubKey); SigningPrivateKey spk = pkf.getSigningPrivKey(); if (offline) { - now += 100000; + now += 365*24*60*60*1000L; SimpleDataStructure transKeys[] = net.i2p.crypto.KeyGenerator.getInstance().generateSigningKeys(SigType.EdDSA_SHA512_Ed25519); SigningPublicKey transientPub = (SigningPublicKey) transKeys[0]; SigningPrivateKey transientPriv = (SigningPrivateKey) transKeys[1];