From 7cbb43ab75fb2326d04fc1f5eb309e0ea186dc92 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Sat, 2 Mar 2019 19:53:16 +0000 Subject: [PATCH] I2CP: Encrypted LS2 handling fixes, log tweaks (WIP) Add number of privkeys field to CreateLeaseSet2 message Check all privkeys, not just the first, on router side --- .../impl/RequestLeaseSetMessageHandler.java | 3 +- .../src/net/i2p/data/EncryptedLeaseSet.java | 45 ++++++-- core/java/src/net/i2p/data/LeaseSet2.java | 14 +++ .../i2p/data/i2cp/CreateLeaseSet2Message.java | 40 +++++-- .../client/ClientMessageEventListener.java | 101 ++++++++++++++---- 5 files changed, 159 insertions(+), 44 deletions(-) diff --git a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java index bebc2cf277..008837a1b2 100644 --- a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java +++ b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java @@ -268,8 +268,9 @@ class RequestLeaseSetMessageHandler extends HandlerImpl { } } else { leaseSet.setEncryptionKey(li.getPublicKey()); + // revocation key + leaseSet.setSigningKey(li.getSigningPublicKey()); } - leaseSet.setSigningKey(li.getSigningPublicKey()); // SubSession options aren't updated via the gui, so use the primary options Properties opts; if (session instanceof SubSession) diff --git a/core/java/src/net/i2p/data/EncryptedLeaseSet.java b/core/java/src/net/i2p/data/EncryptedLeaseSet.java index b96ba2ccc4..eb88cd86d8 100644 --- a/core/java/src/net/i2p/data/EncryptedLeaseSet.java +++ b/core/java/src/net/i2p/data/EncryptedLeaseSet.java @@ -5,6 +5,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.List; import net.i2p.I2PAppContext; import net.i2p.crypto.Blinding; @@ -68,7 +69,6 @@ public class EncryptedLeaseSet extends LeaseSet2 { */ @Override public int getLeaseCount() { - // TODO attempt decryption return _decryptedLS2 != null ? _decryptedLS2.getLeaseCount() : 0; } @@ -77,10 +77,19 @@ public class EncryptedLeaseSet extends LeaseSet2 { */ @Override public Lease getLease(int index) { - // TODO attempt decryption return _decryptedLS2 != null ? _decryptedLS2.getLease(index) : null; } + /** + * @return null if not decrypted. + * @since 0.9.39 + */ + public List<PublicKey> getEncryptionKeys() { + if (_decryptedLS2 != null) + return _decryptedLS2.getEncryptionKeys(); + return super.getEncryptionKeys(); + } + /** * Overridden to set the blinded key * @@ -105,7 +114,9 @@ public class EncryptedLeaseSet extends LeaseSet2 { if (_signingKey == null) _signingKey = bpk; else if (!_signingKey.equals(bpk)) - throw new IllegalArgumentException("blinded pubkey mismatch"); + throw new IllegalArgumentException("blinded pubkey mismatch:" + + "\nas received: " + _signingKey + + "\nas calculated: " + bpk); } /** @@ -121,7 +132,13 @@ public class EncryptedLeaseSet extends LeaseSet2 { _alpha = Blinding.generateAlpha(ctx, _destination, null); else _alpha = Blinding.generateAlpha(ctx, _destination, null, _published); - return Blinding.blind(spk, _alpha); + SigningPublicKey rv = Blinding.blind(spk, _alpha); + if (_log.shouldDebug()) + _log.debug("Blind:" + + "\norig: " + spk + + "\nalpha: " + _alpha + + "\nblinded: " + rv); + return rv; } /** @@ -348,6 +365,10 @@ public class EncryptedLeaseSet extends LeaseSet2 { plaintext = ciphertext; ciphertext = new byte[SALT_LEN + plaintext.length]; System.arraycopy(salt, 0, ciphertext, 0, SALT_LEN); + if (_log.shouldDebug()) { + _log.debug("Encrypt: chacha20 key:\n" + net.i2p.util.HexDump.dump(key)); + _log.debug("Encrypt: chacha20 IV:\n" + net.i2p.util.HexDump.dump(iv)); + } ChaCha20.encrypt(key, iv, plaintext, 0, ciphertext, SALT_LEN, plaintext.length); if (_log.shouldDebug()) _log.debug("Encrypt: outer ciphertext:\n" + net.i2p.util.HexDump.dump(ciphertext)); @@ -377,6 +398,10 @@ public class EncryptedLeaseSet extends LeaseSet2 { byte[] plaintext = new byte[ciphertext.length - SALT_LEN]; // first 32 bytes of ciphertext are the salt hkdf.calculate(ciphertext, input, ELS2L1K, key, iv, 0); + if (_log.shouldDebug()) { + _log.debug("Decrypt: chacha20 key:\n" + net.i2p.util.HexDump.dump(key)); + _log.debug("Decrypt: chacha20 IV:\n" + net.i2p.util.HexDump.dump(iv)); + } ChaCha20.decrypt(key, iv, ciphertext, SALT_LEN, plaintext, 0, plaintext.length); if (_log.shouldDebug()) { _log.debug("Decrypt: outer ciphertext:\n" + net.i2p.util.HexDump.dump(ciphertext)); @@ -477,8 +502,8 @@ public class EncryptedLeaseSet extends LeaseSet2 { _flags = saveFlags; if (_log.shouldDebug()) { _log.debug("Sign inner with key: " + key.getType() + ' ' + key.toBase64()); - _log.debug("Corresponding pubkey: " + key.toPublic().toBase64()); - _log.debug("Sign inner: " + _signature.getType() + ' ' + _signature.toBase64()); + _log.debug("Corresponding pubkey: " + key.toPublic()); + _log.debug("Inner sig: " + _signature.getType() + ' ' + _signature.toBase64()); } encrypt(null); SigningPrivateKey bkey = Blinding.blind(key, _alpha); @@ -498,8 +523,8 @@ public class EncryptedLeaseSet extends LeaseSet2 { throw new DataFormatException("Signature failed with " + key.getType() + " key"); if (_log.shouldDebug()) { _log.debug("Sign outer with key: " + bkey.getType() + ' ' + bkey.toBase64()); - _log.debug("Corresponding pubkey: " + bkey.toPublic().toBase64()); - _log.debug("Sign outer: " + _signature.getType() + ' ' + _signature.toBase64()); + _log.debug("Corresponding pubkey: " + bkey.toPublic()); + _log.debug("Outer sig: " + _signature.getType() + ' ' + _signature.toBase64()); } } @@ -514,7 +539,7 @@ public class EncryptedLeaseSet extends LeaseSet2 { public boolean verifySignature() { if (_log.shouldDebug()) { _log.debug("Sig verify outer with key: " + _signingKey.getType() + ' ' + _signingKey.toBase64()); - _log.debug("Sig verify outer: " + _signature.getType() + ' ' + _signature.toBase64()); + _log.debug("Outer sig: " + _signature.getType() + ' ' + _signature.toBase64()); } if (!super.verifySignature()) { _log.warn("ELS2 outer sig verify fail"); @@ -537,7 +562,7 @@ public class EncryptedLeaseSet extends LeaseSet2 { if (_log.shouldDebug()) { _log.debug("Decrypted inner LS2:\n" + _decryptedLS2); _log.debug("Sig verify inner with key: " + _decryptedLS2.getDestination().getSigningPublicKey().getType() + ' ' + _decryptedLS2.getDestination().getSigningPublicKey().toBase64()); - _log.debug("Sig verify inner: " + _decryptedLS2.getSignature().getType() + ' ' + _decryptedLS2.getSignature().toBase64()); + _log.debug("Inner sig: " + _decryptedLS2.getSignature().getType() + ' ' + _decryptedLS2.getSignature().toBase64()); } boolean rv = _decryptedLS2.verifySignature(); if (!rv) diff --git a/core/java/src/net/i2p/data/LeaseSet2.java b/core/java/src/net/i2p/data/LeaseSet2.java index 208c76496b..a91253b173 100644 --- a/core/java/src/net/i2p/data/LeaseSet2.java +++ b/core/java/src/net/i2p/data/LeaseSet2.java @@ -16,6 +16,7 @@ import net.i2p.crypto.EncType; import net.i2p.crypto.SigAlgo; import net.i2p.crypto.SigType; import net.i2p.util.Clock; +import net.i2p.util.Log; import net.i2p.util.OrderedProperties; /** @@ -256,6 +257,19 @@ public class LeaseSet2 extends LeaseSet { return KEY_TYPE_LS2; } + /** + * The revocation key. Overridden to do nothing, + * as we're using the _signingKey field for the blinded key in Enc LS2. + * + * @since 0.9.39 + */ + @Override + public void setSigningKey(SigningPublicKey key) { + Log log = I2PAppContext.getGlobalContext().logManager().getLog(LeaseSet2.class); + if (log.shouldWarn()) + log.warn("Don't set revocation key in ls2", new Exception("I did it")); + } + /** without sig! */ @Override protected byte[] getBytes() { diff --git a/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java b/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java index df5a8584c6..194701656b 100644 --- a/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java +++ b/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java @@ -115,18 +115,32 @@ public class CreateLeaseSet2Message extends CreateLeaseSetMessage { type == DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) { LeaseSet2 ls2 = (LeaseSet2) _leaseSet; // get one PrivateKey for each PublicKey - // TODO decrypt an encrypted LS so we can get the keys List<PublicKey> pks = ls2.getEncryptionKeys(); - if (pks == null) - throw new I2CPMessageException("TODO decrypt"); - for (PublicKey pk : pks) { - EncType etype = pk.getType(); - if (etype == null) - throw new I2CPMessageException("Unsupported encryption type"); + int numkeys = in.read(); + // pks is null for encrypted LS2 + if (pks != null && numkeys != pks.size()) + throw new I2CPMessageException("Wrong number of privkeys"); + for (int i = 0; i < numkeys; i++) { int encType = (int) DataHelper.readLong(in, 2); int encLen = (int) DataHelper.readLong(in, 2); - if (encType != etype.getCode() || encLen != etype.getPrivkeyLen()) - throw new I2CPMessageException("Enc type mismatch"); + EncType etype; + if (pks != null) { + // standard LS2 + etype = pks.get(i).getType(); + if (etype == null) + throw new I2CPMessageException("Unsupported encryption type: " + encType); + if (encType != etype.getCode()) + throw new I2CPMessageException("Enc type mismatch"); + if (encLen != etype.getPrivkeyLen()) + throw new I2CPMessageException("Enc type bad length"); + } else { + // encrypted LS2 + etype = EncType.getByCode(encType); + if (etype == null) + throw new I2CPMessageException("Unsupported encryption type: " + encType); + if (encLen != etype.getPrivkeyLen()) + throw new I2CPMessageException("Enc type bad length"); + } PrivateKey priv = new PrivateKey(etype); priv.readBytes(in); addPrivateKey(priv); @@ -164,7 +178,9 @@ public class CreateLeaseSet2Message extends CreateLeaseSetMessage { os.write(_leaseSet.getType()); _leaseSet.writeBytes(os); if (type != DatabaseEntry.KEY_TYPE_META_LS2) { - for (PrivateKey pk : getPrivateKeys()) { + List<PrivateKey> pks = getPrivateKeys(); + os.write(pks.size()); + for (PrivateKey pk : pks) { EncType etype = pk.getType(); DataHelper.writeLong(os, 2, etype.getCode()); DataHelper.writeLong(os, 2, pk.length()); @@ -187,7 +203,9 @@ public class CreateLeaseSet2Message extends CreateLeaseSetMessage { StringBuilder buf = new StringBuilder(); buf.append("[CreateLeaseSet2Message: "); buf.append("\n\tLeaseSet: ").append(_leaseSet); - if (_leaseSet.getType() != DatabaseEntry.KEY_TYPE_META_LS2) { + int type = _leaseSet.getType(); + if (type != DatabaseEntry.KEY_TYPE_META_LS2 && + type != DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) { for (PrivateKey pk : getPrivateKeys()) { buf.append("\n\tPrivateKey: ").append(pk); } diff --git a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java index 57c1e090b5..48c57bbd70 100644 --- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java +++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java @@ -19,7 +19,9 @@ import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; +import net.i2p.data.LeaseSet2; import net.i2p.data.Payload; +import net.i2p.data.PrivateKey; import net.i2p.data.PublicKey; import net.i2p.data.i2cp.BandwidthLimitsMessage; import net.i2p.data.i2cp.CreateLeaseSetMessage; @@ -290,7 +292,14 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi } } String lsType = props.getProperty("i2cp.leaseSetType"); - if ("7".equals(lsType)) { + if ("5".equals(lsType)) { + SigType stype = dest.getSigningPublicKey().getType(); + if (stype != SigType.EdDSA_SHA512_Ed25519 && + stype != SigType.RedDSA_SHA512_Ed25519) { + _runner.disconnectClient("Invalid sig type for encrypted LS"); + return; + } + } else if ("7".equals(lsType)) { // Prevent tunnel builds for Meta LS // more TODO props.setProperty("inbound.length", "0"); @@ -512,7 +521,9 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi return; } int type = ls.getType(); - if (type != DatabaseEntry.KEY_TYPE_META_LS2 && message.getPrivateKey() == null) { + if (type != DatabaseEntry.KEY_TYPE_META_LS2 && + type != DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2 && + message.getPrivateKey() == null) { if (_log.shouldLog(Log.ERROR)) _log.error("Null private keys: " + message); _runner.disconnectClient("Invalid CreateLeaseSetMessage - null private keys"); @@ -536,12 +547,31 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi return; } Destination dest = cfg.getDestination(); - Destination ndest = ls.getDestination(); - if (!dest.equals(ndest)) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Different destination in LS"); - _runner.disconnectClient("Different destination in LS"); - return; + if (type == DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) { + // so we can decrypt it + try { + ls.setDestination(dest); + } catch (RuntimeException re) { + if (_log.shouldError()) + _log.error("Error decrypting leaseset from client", re); + _runner.disconnectClient(re.toString()); + return; + } + // we have to do this before checking encryption keys below + if (!ls.verifySignature()) { + if (_log.shouldError()) + _log.error("Error decrypting leaseset from client"); + _runner.disconnectClient("Error decrypting leaseset from client"); + return; + } + } else { + Destination ndest = ls.getDestination(); + if (!dest.equals(ndest)) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Different destination in LS"); + _runner.disconnectClient("Different destination in LS"); + return; + } } if (type != DatabaseEntry.KEY_TYPE_META_LS2) { LeaseSetKeys keys = _context.keyManager().getKeys(dest); @@ -550,20 +580,47 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi // Verify and register crypto keys if new or if changed // Private crypto key should never change, and if it does, // one of the checks below will fail - PublicKey pk; - try { - pk = message.getPrivateKey().toPublic(); - } catch (IllegalArgumentException iae) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Bad private key in LS"); - _runner.disconnectClient("Bad private key in LS"); - return; - } - if (!pk.equals(ls.getEncryptionKey())) { - if (_log.shouldLog(Log.ERROR)) - _log.error("Private/public crypto key mismatch in LS"); - _runner.disconnectClient("Private/public crypto key mismatch in LS"); - return; + if (type == DatabaseEntry.KEY_TYPE_LEASESET) { + // LS1 + PublicKey pk; + try { + pk = message.getPrivateKey().toPublic(); + } catch (IllegalArgumentException iae) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Bad private key in LS"); + _runner.disconnectClient("Bad private key in LS"); + return; + } + if (!pk.equals(ls.getEncryptionKey())) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Private/public crypto key mismatch in LS"); + _runner.disconnectClient("Private/public crypto key mismatch in LS"); + return; + } + } else { + // LS2 + LeaseSet2 ls2 = (LeaseSet2) ls; + CreateLeaseSet2Message msg2 = (CreateLeaseSet2Message) message; + List<PublicKey> eks = ls2.getEncryptionKeys(); + List<PrivateKey> pks = msg2.getPrivateKeys(); + for (int i = 0; i < eks.size(); i++) { + PublicKey ek = eks.get(i); + PublicKey pk; + try { + pk = pks.get(i).toPublic(); + } catch (IllegalArgumentException iae) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Bad private key in LS: " + i); + _runner.disconnectClient("Bad private key in LS: " + i); + return; + } + if (!pk.equals(ek)) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Private/public crypto key mismatch in LS for key: " + i); + _runner.disconnectClient("Private/public crypto key mismatch in LS for key: " + i); + return; + } + } } // just register new SPK, don't verify, unused _context.keyManager().registerKeys(dest, message.getSigningPrivateKey(), message.getPrivateKey()); -- GitLab