SSU2: Stubs for relay and peer test (phase 2)

This commit is contained in:
zzz
2022-03-13 12:42:13 -04:00
parent 2f63762c80
commit 049456493f
10 changed files with 310 additions and 22 deletions

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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()))

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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