From cfdc2203fb70d547702aa5e7ab9f03f41176aad6 Mon Sep 17 00:00:00 2001 From: zzz <zzz@i2pmail.org> Date: Thu, 2 Jun 2022 08:43:50 -0400 Subject: [PATCH] SSU2: Relay WIP part 3 Put alice hash in relay intro as Bob Fix relay intro/response generation and signing Add token to relay response as Charlie Store relay nonce as Bob Handle relay response as Bob and forward to Alice Stub out handling relay response as Alice --- .../transport/udp/EstablishmentManager.java | 14 ++++ .../transport/udp/IntroductionManager.java | 70 +++++++++++++++++-- .../router/transport/udp/PacketBuilder2.java | 3 + .../i2p/router/transport/udp/SSU2Util.java | 61 +++++++++------- 4 files changed, 117 insertions(+), 31 deletions(-) diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index af3707ea1c..2a80676c7e 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -1296,6 +1296,8 @@ class EstablishmentManager { /** * We are Alice, we sent a RelayRequest to Bob and got a response back. + * + * SSU 1 only. */ void receiveRelayResponse(RemoteHostId bob, UDPPacketReader reader) { long nonce = reader.getRelayResponseReader().readNonce(); @@ -1353,6 +1355,18 @@ class EstablishmentManager { notifyActivity(); } + /** + * We are Alice, we sent a RelayRequest to Bob and got a RelayResponse back. + * + * SSU 2 only. + * + * @param data including token if code == 0 + * @since 0.9.55 + */ + void receiveRelayResponse(PeerState2 bob, long nonce, int code, byte[] data) { + // lookup nonce, determine who signed, validate sig, send SessionRequest if code == 0 + } + /** * Called from UDPReceiver. * Accelerate response to RelayResponse if we haven't sent it yet. diff --git a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java index e3d8147044..327e26e4b5 100644 --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java @@ -86,6 +86,8 @@ class IntroductionManager { private final Map<Long, PeerState> _outbound; /** map of relay tag to PeerState who have given us introduction tags */ private final Map<Long, PeerState> _inbound; + /** map of relay nonce to alice PeerState who requested it */ + private final Map<Long, PeerState2> _nonceToAlice; private final Set<InetAddress> _recentHolePunches; private long _lastHolePunchClean; @@ -117,6 +119,7 @@ class IntroductionManager { _builder2 = transport.getBuilder2(); _outbound = new ConcurrentHashMap<Long, PeerState>(MAX_OUTBOUND); _inbound = new ConcurrentHashMap<Long, PeerState>(MAX_INBOUND); + _nonceToAlice = (_builder2 != null) ? new ConcurrentHashMap<Long, PeerState2>(MAX_INBOUND) : null; _recentHolePunches = new HashSet<InetAddress>(16); ctx.statManager().createRateStat("udp.receiveRelayIntro", "How often we get a relayed request for us to talk to someone?", "udp", UDPTransport.RATES); ctx.statManager().createRateStat("udp.receiveRelayRequest", "How often we receive a good request to relay to someone else?", "udp", UDPTransport.RATES); @@ -669,7 +672,6 @@ class IntroductionManager { * @since 0.9.55 */ void receiveRelayRequest(PeerState2 alice, byte[] data) { - long tag = DataHelper.fromLong(data, 4, 4); long time = DataHelper.fromLong(data, 8, 4) * 1000; long now = _context.clock().now(); long skew = time - now; @@ -684,6 +686,8 @@ class IntroductionManager { _log.warn("Bad relay req version " + ver + " from " + alice); return; } + long nonce = DataHelper.fromLong(data, 0, 4); + long tag = DataHelper.fromLong(data, 4, 4); PeerState charlie = _outbound.get(Long.valueOf(tag)); RouterInfo aliceRI = null; int rcode; @@ -700,7 +704,15 @@ class IntroductionManager { SigningPublicKey spk = aliceRI.getIdentity().getSigningPublicKey(); if (SSU2Util.validateSig(_context, SSU2Util.RELAY_REQUEST_PROLOGUE, _context.routerHash(), charlie.getRemotePeer(), data, spk)) { - rcode = SSU2Util.RELAY_ACCEPT; + // save tag-to-alice mapping so we can forward the reply from charlie + PeerState2 old = _nonceToAlice.putIfAbsent(Long.valueOf(nonce), alice); + if (old != null && !old.equals(alice)) { + // dup tag + rcode = SSU2Util.RELAY_REJECT_BOB_UNSPEC; + } else { + rcode = SSU2Util.RELAY_ACCEPT; + } + // TODO add timer to remove from _nonceToAlice } else { if (_log.shouldWarn()) _log.warn("Signature failed relay intro\n" + aliceRI); @@ -721,17 +733,21 @@ class IntroductionManager { dbsm.setEntry(aliceRI); dbsm.setMessageExpiration(now + 10*1000); _transport.send(dbsm, charlie); - packet = _builder2.buildRelayIntro(data, (PeerState2) charlie); + // put alice hash in intro data + byte[] idata = new byte[1 + Hash.HASH_LENGTH + data.length]; + //idata[0] = 0; // flag + System.arraycopy(alice.getRemotePeer().getData(), 0, idata, 1, Hash.HASH_LENGTH); + System.arraycopy(data, 0, idata, 1 + Hash.HASH_LENGTH, data.length); + packet = _builder2.buildRelayIntro(idata, (PeerState2) charlie); } else { // send rejection to Alice SigningPrivateKey spk = _context.keyManager().getSigningPrivateKey(); - long nonce = DataHelper.fromLong(data, 0, 4); int iplen = data[13] & 0xff; int testPort = (int) DataHelper.fromLong(data, 14, 2); byte[] testIP = new byte[iplen - 2]; System.arraycopy(data, 16, testIP, 0, iplen - 2); data = SSU2Util.createRelayResponseData(_context, _context.routerHash(), rcode, - nonce, testIP, testPort, spk); + nonce, testIP, testPort, spk, 0); if (data == null) { if (_log.shouldWarn()) _log.warn("sig fail"); @@ -828,9 +844,17 @@ class IntroductionManager { // generate our signed data // we sign it even if rejecting, not required though + long token; + if (rcode == SSU2Util.RELAY_ACCEPT) { + RemoteHostId aliceID = new RemoteHostId(testIP, testPort); + EstablishmentManager.Token tok = _transport.getEstablisher().getInboundToken(aliceID); + token = tok.token; + } else { + token = 0; + } SigningPrivateKey spk = _context.keyManager().getSigningPrivateKey(); data = SSU2Util.createRelayResponseData(_context, bob.getRemotePeer(), rcode, - nonce, testIP, testPort, spk); + nonce, testIP, testPort, spk, token); if (data == null) { if (_log.shouldWarn()) _log.warn("sig fail"); @@ -864,6 +888,40 @@ class IntroductionManager { * @since 0.9.55 */ void receiveRelayResponse(PeerState2 peer, int status, byte[] data) { + long nonce = DataHelper.fromLong(data, 0, 4); + long time = DataHelper.fromLong(data, 4, 4) * 1000; + long now = _context.clock().now(); + long skew = time - now; + if (skew > MAX_SKEW || skew < 0 - MAX_SKEW) { + if (_log.shouldWarn()) + _log.warn("Too skewed for relay resp from " + peer); + return; + } + int ver = data[8] & 0xff; + if (ver != 2) { + if (_log.shouldWarn()) + _log.warn("Bad relay intro version " + ver + " from " + peer); + return; + } + // Look up nonce to determine if we are Alice or Bob + PeerState2 alice = _nonceToAlice.remove(Long.valueOf(nonce)); + if (alice != null) { + // We are Bob, send to Alice + // We don't check the signature here + byte[] idata = new byte[2 + data.length]; + //idata[0] = 0; // flag + idata[1] = (byte) status; + System.arraycopy(data, 0, idata, 2, data.length); + UDPPacket packet = _builder2.buildRelayResponse(idata, alice); + if (_log.shouldDebug()) + _log.debug("Send relay response " + " nonce " + nonce + " to " + alice); + _transport.send(packet); + } else { + // We are Alice, give to EstablishmentManager to check sig and process + if (_log.shouldDebug()) + _log.debug("Got relay response " + " nonce " + nonce + " from " + peer); + _transport.getEstablisher().receiveRelayResponse(peer, nonce, status, data); + } } /** diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java index d70f8ff9a6..158b6d79f2 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java @@ -723,6 +723,7 @@ class PacketBuilder2 { * From Alice to Bob. * In-session. * + * @param signedData flag + signed data * @return null on failure */ UDPPacket buildRelayRequest(byte[] signedData, PeerState2 bob) { @@ -737,6 +738,7 @@ class PacketBuilder2 { * From Bob to Charlie. * In-session. * + * @param signedData flag + alice hash + signed data * @return null on failure */ UDPPacket buildRelayIntro(byte[] signedData, PeerState2 charlie) { @@ -750,6 +752,7 @@ class PacketBuilder2 { * From Charlie to Bob or Bob to Alice. * In-session. * + * @param signedData flag + response code + signed data + optional token * @param state Alice or Bob * @return null on failure */ diff --git a/router/java/src/net/i2p/router/transport/udp/SSU2Util.java b/router/java/src/net/i2p/router/transport/udp/SSU2Util.java index 4f4878d55f..d98f6b8a30 100644 --- a/router/java/src/net/i2p/router/transport/udp/SSU2Util.java +++ b/router/java/src/net/i2p/router/transport/udp/SSU2Util.java @@ -227,22 +227,25 @@ final class SSU2Util { public static byte[] createRelayRequestData(I2PAppContext ctx, Hash h, Hash h2, long nonce, long tag, byte[] ip, int port, SigningPrivateKey spk) { - int datalen = 17 + ip.length; - byte[] data = new byte[datalen + spk.getType().getSigLen()]; - //data[0] = 0; // flag - DataHelper.toLong(data, 1, 4, nonce); - DataHelper.toLong(data, 5, 4, tag); - DataHelper.toLong(data, 9, 4, ctx.clock().now() / 1000); - data[13] = 2; // version - data[14] = (byte) (ip.length + 2); - DataHelper.toLong(data, 15, 2, port); - System.arraycopy(ip, 0, data, 17, ip.length); + int datalen = 16 + ip.length; + byte[] data = new byte[datalen]; + DataHelper.toLong(data, 0, 4, nonce); + DataHelper.toLong(data, 4, 4, tag); + DataHelper.toLong(data, 8, 4, ctx.clock().now() / 1000); + data[12] = 2; // version + data[13] = (byte) (ip.length + 2); + DataHelper.toLong(data, 14, 2, port); + System.arraycopy(ip, 0, data, 16, ip.length); Signature sig = sign(ctx, RELAY_REQUEST_PROLOGUE, h, h2, data, datalen, spk); if (sig == null) return null; + int len = 1 + datalen + spk.getType().getSigLen(); + byte[] rv = new byte[len]; + //rv[0] = 0; // flag + System.arraycopy(data, 0, rv, 1, data.length); byte[] s = sig.getData(); - System.arraycopy(s, 0, data, datalen, s.length); - return data; + System.arraycopy(s, 0, rv, 1 + datalen, s.length); + return rv; } /** @@ -250,28 +253,36 @@ final class SSU2Util { * * @param h Bob hash to be included in sig, not included in data * @param ip non-null + * @param token if nonzero, append it * @return null on failure * @since 0.9.55 */ public static byte[] createRelayResponseData(I2PAppContext ctx, Hash h, int code, long nonce, byte[] ip, int port, - SigningPrivateKey spk) { - int datalen = 14 + ip.length; - byte[] data = new byte[datalen + spk.getType().getSigLen()]; - //data[0] = 0; // flag - data[1] = (byte) code; - DataHelper.toLong(data, 2, 4, nonce); - DataHelper.toLong(data, 6, 4, ctx.clock().now() / 1000); - data[10] = 2; // version - data[11] = (byte) (ip.length + 2); - DataHelper.toLong(data, 12, 2, port); - System.arraycopy(ip, 0, data, 14, ip.length); + SigningPrivateKey spk, long token) { + int datalen = 12 + ip.length; + byte[] data = new byte[datalen]; + DataHelper.toLong(data, 0, 4, nonce); + DataHelper.toLong(data, 4, 4, ctx.clock().now() / 1000); + data[8] = 2; // version + data[9] = (byte) (ip.length + 2); + DataHelper.toLong(data, 10, 2, port); + System.arraycopy(ip, 0, data, 12, ip.length); Signature sig = sign(ctx, RELAY_RESPONSE_PROLOGUE, h, null, data, datalen, spk); if (sig == null) return null; + int len = 2 + datalen + spk.getType().getSigLen(); + if (token != 0) + len += 8; + byte[] rv = new byte[len]; + //rv[0] = 0; // flag + rv[1] = (byte) code; + System.arraycopy(data, 0, rv, 2, data.length); byte[] s = sig.getData(); - System.arraycopy(s, 0, data, datalen, s.length); - return data; + System.arraycopy(s, 0, rv, 2 + datalen, s.length); + if (token != 0) + DataHelper.toLong8(rv, 2 + datalen + s.length, token); + return rv; } /** -- GitLab