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 72e249ba4ed39999b67ba74be12ac82ae3cbd4cc..07c89f40e6f7e40e850ee9c9ec06faa3edf26c83 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -638,27 +638,21 @@ class EstablishmentManager { } if (isNew) { - /**** TODO - // Don't offer to relay to privileged ports. - // Only offer for an IPv4 session. - // TODO if already we have their RI, only offer if they need it (no 'C' cap) - // if extended options, only if they asked for it - if (state.isIntroductionRequested() && - state.getSentPort() >= 1024 && - _transport.canIntroduce(state.getSentIP().length == 16)) { - // ensure > 0 - long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE); - state.setSentRelayTag(tag); - } else { - // we got an IB even though we were firewalled, hidden, not high cap, etc. - } - ****/ if (_log.shouldInfo()) _log.info("Received NEW session/token request " + state); } else { if (_log.shouldDebug()) _log.debug("Receive DUP session/token request from: " + state); } + // call for both Session and Token request, why not + if (SSU2Util.ENABLE_RELAY && + state.isIntroductionRequested() && + state.getSentRelayTag() == 0 && // only set once + state.getSentPort() >= 1024 && + _transport.canIntroduce(state.getSentIP().length == 16)) { + long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE); + state.setSentRelayTag(tag); + } notifyActivity(); } @@ -1059,7 +1053,6 @@ class EstablishmentManager { state.getSentIP(), state.getSentPort(), remote.calculateHash(), false, state.getRTT()); peer.setCurrentCipherKey(state.getCipherKey()); peer.setCurrentMACKey(state.getMACKey()); - peer.setTheyRelayToUsAs(state.getReceivedRelayTag()); int mtu = state.getRemoteAddress().getMTU(); if (mtu > 0) peer.setHisMTU(mtu); @@ -1068,6 +1061,7 @@ class EstablishmentManager { // OES2 sets PS2 MTU peer = state2.getPeerState(); } + peer.setTheyRelayToUsAs(state.getReceivedRelayTag()); // 0 is the default //peer.setWeRelayToThemAs(0); diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java index a45099c6dee0594c9c0a1ed2d2da2c9f905247bd..805476e77d6efee9cdde5f22bf3167569a86525e 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java @@ -63,8 +63,8 @@ class InboundEstablishState { private final Queue<OutNetMessage> _queuedMessages; // count for backoff protected int _createdSentCount; - // default true - protected boolean _introductionRequested = true; + // default true for SSU 1, false for SSU 2 + protected boolean _introductionRequested; protected int _rtt; @@ -127,6 +127,7 @@ class InboundEstablishState { _establishBegin = ctx.clock().now(); _keyBuilder = dh; _queuedMessages = new LinkedBlockingQueue<OutNetMessage>(); + _introductionRequested = true; receiveSessionRequest(req); } diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java index b6d8b83b729141fe6f4fc02529fe80ccea285c53..3ad8298d1491e08a95d56b6db69a87d1aaff74fd 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java @@ -80,7 +80,6 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa _rcvHeaderEncryptKey1 = introKey; //_sendHeaderEncryptKey2 set below //_rcvHeaderEncryptKey2 set below - _introductionRequested = false; // todo int off = pkt.getOffset(); int len = pkt.getLength(); byte data[] = pkt.getData(); @@ -321,12 +320,43 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa } public void gotRelayTagRequest() { + if (!ENABLE_RELAY) + return; if (_log.shouldDebug()) _log.debug("Got relay tag request"); + _introductionRequested = true; } public void gotRelayTag(long tag) { - throw new IllegalStateException("Relay tag in Handshake"); + // shouldn't happen for inbound + } + + public void gotRelayRequest(byte[] data) { + if (!ENABLE_RELAY) + return; + if (_receivedConfirmedIdentity == null) + throw new IllegalStateException("RI must be first"); + } + + public void gotRelayResponse(int status, byte[] data) { + if (!ENABLE_RELAY) + return; + if (_receivedConfirmedIdentity == null) + throw new IllegalStateException("RI must be first"); + } + + public void gotRelayIntro(Hash aliceHash, byte[] data) { + if (!ENABLE_RELAY) + return; + if (_receivedConfirmedIdentity == null) + throw new IllegalStateException("RI must be first"); + } + + public void gotPeerTest(int msg, int status, Hash h, byte[] data) { + if (!ENABLE_PEER_TEST) + return; + if (_receivedConfirmedIdentity == null) + throw new IllegalStateException("RI must be first"); } public void gotToken(long token, long expires) { 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 d31c4ae2ce0eac1a5d5e203fc402c38b83370779..88e9c258c4236e4afe0c725b05661449b4543b4d 100644 --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java @@ -126,7 +126,7 @@ class IntroductionManager { public void add(PeerState peer) { if (peer == null) return; // Skip SSU2 until we have support for relay - if (peer.getVersion() != 1) + if (peer.getVersion() != 1 && !SSU2Util.ENABLE_RELAY) return; // let's not use an introducer on a privileged port, sounds like trouble if (!TransportUtil.isValidPort(peer.getRemotePort())) diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java index 29ef7b5d692231b10f2cac81b4dfd825b16ee5ea..3bd8011cbdef083def2eefe60a85869e48bc9a7d 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java @@ -37,7 +37,7 @@ class OutboundEstablishState { private byte _receivedY[]; protected byte _aliceIP[]; protected int _alicePort; - private long _receivedRelayTag; + protected long _receivedRelayTag; private long _receivedSignedOnTime; private SessionKey _sessionKey; private SessionKey _macKey; diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java index c2cae324e2470ec077325868c2167c87efb14b4f..6edd9176a8208b817b5337e6ea2adac579e9c58a 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java @@ -16,6 +16,7 @@ import net.i2p.crypto.HKDF; import net.i2p.data.Base64; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; +import net.i2p.data.Hash; import net.i2p.data.SessionKey; import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.router.RouterAddress; @@ -215,8 +216,27 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl } public void gotRelayTag(long tag) { + if (!ENABLE_RELAY) + return; if (_log.shouldDebug()) _log.debug("Got relay tag " + tag); + _receivedRelayTag = tag; + } + + public void gotRelayRequest(byte[] data) { + // won't be called, SSU2Payload will throw + } + + public void gotRelayResponse(int status, byte[] data) { + // won't be called, SSU2Payload will throw + } + + public void gotRelayIntro(Hash aliceHash, byte[] data) { + // won't be called, SSU2Payload will throw + } + + public void gotPeerTest(int msg, int status, Hash h, byte[] data) { + // won't be called, SSU2Payload will throw } public void gotToken(long token, long expires) { diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState2.java b/router/java/src/net/i2p/router/transport/udp/PeerState2.java index d7db35b360697bb2cc8e8e96ab1dd5b621d1fd39..d416810e4db181061d3c9a3d11c6b20e9f53634a 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState2.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState2.java @@ -386,9 +386,41 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback } public void gotRelayTagRequest() { + if (!ENABLE_RELAY) + return; } public void gotRelayTag(long tag) { + if (!ENABLE_RELAY) + return; + long old = getTheyRelayToUsAs(); + if (old != 0) { + if (_log.shouldWarn()) + _log.warn("Got new tag " + tag + " but had previous tag " + old + " on " + this); + return; + } + setTheyRelayToUsAs(tag); + _transport.getIntroManager().add(this); + } + + public void gotRelayRequest(byte[] data) { + if (!ENABLE_RELAY) + return; + } + + public void gotRelayResponse(int status, byte[] data) { + if (!ENABLE_RELAY) + return; + } + + public void gotRelayIntro(Hash aliceHash, byte[] data) { + if (!ENABLE_RELAY) + return; + } + + public void gotPeerTest(int msg, int status, Hash h, byte[] data) { + if (!ENABLE_PEER_TEST) + return; } public void gotToken(long token, long expires) { diff --git a/router/java/src/net/i2p/router/transport/udp/SSU2Payload.java b/router/java/src/net/i2p/router/transport/udp/SSU2Payload.java index 356710370c6710feb950ee4cb1ccfec85a577698..ef3c671bda5c74d8dc12f9f6ef70b1fe5cd952ff 100644 --- a/router/java/src/net/i2p/router/transport/udp/SSU2Payload.java +++ b/router/java/src/net/i2p/router/transport/udp/SSU2Payload.java @@ -8,6 +8,7 @@ import java.util.List; import net.i2p.I2PAppContext; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; +import net.i2p.data.Hash; import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.i2np.I2NPMessageException; import net.i2p.data.i2np.I2NPMessageImpl; @@ -94,6 +95,30 @@ class SSU2Payload { public void gotRelayTag(long tag); + /** + * @param data excludes flag, includes signature + */ + public void gotRelayRequest(byte[] data); + + /** + * @param status 0 = accept, 1-255 = reject + * @param data excludes flag, includes signature + */ + public void gotRelayResponse(int status, byte[] data); + + /** + * @param data excludes flag, includes signature + */ + public void gotRelayIntro(Hash aliceHash, byte[] data); + + /** + * @param msg 1-7 + * @param status 0 = accept, 1-255 = reject + * @param h Alice or Charlie hash + * @param data excludes flag, includes signature + */ + public void gotPeerTest(int msg, int status, Hash h, byte[] data); + public void gotToken(long token, long expires); /** @@ -114,6 +139,7 @@ class SSU2Payload { /** * Incoming payload. Calls the callback for each received block. * + * @param isHandshake true for Token Req, Retry, Sess Req, Sess Created; false for Sess Confirmed * @return number of blocks processed * @throws IOException on major errors * @throws DataFormatException on parsing of individual blocks @@ -256,6 +282,55 @@ class SSU2Payload { cb.gotRelayTag(tag); break; + case BLOCK_RELAYREQ: { + if (isHandshake) + throw new IOException("Illegal block in handshake: " + type); + if (len < 61) // 21 byte data w/ IPv4 + 40 byte DSA sig + throw new IOException("Bad length for RELAYREQ: " + len); + byte[] data = new byte[len - 1]; // skip flag + System.arraycopy(payload, i + 1, data, 0, len - 1); + cb.gotRelayRequest(data); + break; + } + + case BLOCK_RELAYRESP: { + if (isHandshake) + throw new IOException("Illegal block in handshake: " + type); + if (len < 62) // 22 byte data w/ IPv4 + 40 byte DSA sig + throw new IOException("Bad length for RELAYRESP: " + len); + int resp = payload[i + 1] & 0xff; // skip flag + byte[] data = new byte[len - 2]; + System.arraycopy(payload, i + 2, data, 0, len - 2); + cb.gotRelayResponse(resp, data); + break; + } + + case BLOCK_RELAYINTRO: { + if (isHandshake) + throw new IOException("Illegal block in handshake: " + type); + if (len < 93) // 32 byte hash + 21 byte data w/ IPv4 + 40 byte DSA sig + throw new IOException("Bad length for RELAYINTRO: " + len); + Hash h = Hash.create(payload, i + 1); // skip flag + byte[] data = new byte[len - (1 + Hash.HASH_LENGTH)]; // skip flag + System.arraycopy(payload, i + 1 + Hash.HASH_LENGTH, data, 0, data.length); + cb.gotRelayIntro(h, data); + break; + } + + case BLOCK_PEERTEST: { + if (isHandshake) + throw new IOException("Illegal block in handshake: " + type); + if (len < 92) // 32 byte hash + 20 byte data w/ IPv4 + 40 byte DSA sig + throw new IOException("Bad length for PEERTEST: " + len); + int mnum = payload[i] & 0xff; + int resp = payload[i + 1] & 0xff; + Hash h = Hash.create(payload, i + 3); // skip flag + byte[] data = new byte[len - (3 + Hash.HASH_LENGTH)]; + System.arraycopy(payload, i + 3 + Hash.HASH_LENGTH, data, 0, data.length); + cb.gotPeerTest(mnum, resp, h, data); + break; + } + case BLOCK_NEWTOKEN: if (len < 12) throw new IOException("Bad length for NEWTOKEN: " + len); @@ -644,6 +719,78 @@ class SSU2Payload { } } + public static class RelayRequestBlock extends Block { + private final byte[] d; + + public RelayRequestBlock(byte[] data) { + super(BLOCK_RELAYREQ); + d = data; + } + + public int getDataLength() { + return d.length; + } + + public int writeData(byte[] tgt, int off) { + System.arraycopy(d, 0, tgt, off, d.length); + return off + d.length; + } + } + + public static class RelayResponseBlock extends Block { + private final byte[] d; + + public RelayResponseBlock(byte[] data) { + super(BLOCK_RELAYRESP); + d = data; + } + + public int getDataLength() { + return d.length; + } + + public int writeData(byte[] tgt, int off) { + System.arraycopy(d, 0, tgt, off, d.length); + return off + d.length; + } + } + + public static class RelayIntroBlock extends Block { + private final byte[] d; + + public RelayIntroBlock(byte[] data) { + super(BLOCK_RELAYINTRO); + d = data; + } + + public int getDataLength() { + return d.length; + } + + public int writeData(byte[] tgt, int off) { + System.arraycopy(d, 0, tgt, off, d.length); + return off + d.length; + } + } + + public static class PeerTestBlock extends Block { + private final byte[] d; + + public PeerTestBlock(byte[] data) { + super(BLOCK_PEERTEST); + d = data; + } + + public int getDataLength() { + return d.length; + } + + public int writeData(byte[] tgt, int off) { + System.arraycopy(d, 0, tgt, off, d.length); + return off + d.length; + } + } + public static class NewTokenBlock extends Block { private final long t, e; 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 d8eacf585d3db0e0a5c25134c64c9245cdbac1e7..51c6e78eae8b9bd4587ee386a757dabf005a987c 100644 --- a/router/java/src/net/i2p/router/transport/udp/SSU2Util.java +++ b/router/java/src/net/i2p/router/transport/udp/SSU2Util.java @@ -3,6 +3,12 @@ package net.i2p.router.transport.udp; import net.i2p.I2PAppContext; import net.i2p.crypto.EncType; import net.i2p.crypto.HKDF; +import net.i2p.crypto.SigType; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.data.Signature; +import net.i2p.data.SigningPrivateKey; +import net.i2p.data.SigningPublicKey; /** * SSU2 Utils and constants @@ -12,6 +18,11 @@ import net.i2p.crypto.HKDF; final class SSU2Util { public static final int PROTOCOL_VERSION = 2; + // features + public static final boolean ENABLE_RELAY = false; + public static final boolean ENABLE_PEER_TEST = false; + public static final boolean ENABLE_PATH_CHALLENGE = false; + // lengths /** 32 */ public static final int KEY_LEN = EncType.ECIES_X25519.getPubkeyLen(); @@ -87,6 +98,12 @@ final class SSU2Util { public static final byte[] ZEROLEN = new byte[0]; public static final byte[] ZEROKEY = new byte[KEY_LEN]; + // relay and peer test + public static final byte[] RELAY_REQUEST_PROLOGUE = DataHelper.getASCII("RelayRequestData"); + public static final byte[] RELAY_RESPONSE_PROLOGUE = DataHelper.getASCII("RelayAgreementOK"); + public static final byte[] PEER_TEST_PROLOGUE = DataHelper.getASCII("PeerTestValidate"); + + private SSU2Util() {} /** @@ -98,4 +115,37 @@ final class SSU2Util { hkdf.calculate(key, ZEROLEN, info, rv); return rv; } + + /** + * Sign the relay or peer test data, using + * the prologue and hash as the initial data, + * and then the provided data. + * + * @return null on failure + */ + public static Signature sign(I2PAppContext ctx, byte[] prologue, Hash h, byte[] data, SigningPrivateKey spk) { + byte[] buf = new byte[prologue.length + Hash.HASH_LENGTH + data.length]; + System.arraycopy(prologue, 0, buf, 0, prologue.length); + System.arraycopy(h.getData(), 0, buf, prologue.length, Hash.HASH_LENGTH); + System.arraycopy(data, 0, buf, prologue.length + Hash.HASH_LENGTH, data.length); + return ctx.dsa().sign(buf, spk); + } + + /** + * Validate the signed relay or peer test data, using + * the prologue and hash as the initial data, + * and then the provided data which ends with a signature of the specified type. + */ + public static boolean validateSig(I2PAppContext ctx, byte[] prologue, Hash h, byte[] data, SigningPublicKey spk) { + SigType type = spk.getType(); + int siglen = type.getSigLen(); + byte[] buf = new byte[prologue.length + Hash.HASH_LENGTH + data.length - siglen]; + System.arraycopy(prologue, 0, buf, 0, prologue.length); + System.arraycopy(h.getData(), 0, buf, prologue.length, Hash.HASH_LENGTH); + System.arraycopy(data, 0, buf, prologue.length + Hash.HASH_LENGTH, data.length - siglen); + byte[] bsig = new byte[siglen]; + System.arraycopy(data, data.length - siglen, bsig, 0, siglen); + Signature sig = new Signature(type, bsig); + return ctx.dsa().verifySignature(sig, buf, spk); + } } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index e0f6c30af0138c15bcc67fd3c6f66f53bcdccf6d..2f07e7b31ff44db7adf348ce8466003b4eb64266 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -3433,6 +3433,20 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return _packetBuilder2; } + /** + * @since 0.9.54 + */ + IntroductionManager getIntroManager() { + return _introManager; + } + + /** + * @since 0.9.54 + */ + PeerTestManager getPeerTestManager() { + return _testManager; + } + /** * Does nothing * @deprecated as of 0.9.31