SSU2: Add token support to EstablishmentManager

This commit is contained in:
zzz
2022-02-27 05:26:04 -05:00
parent 759f6968f6
commit 2b93dbbf48
7 changed files with 148 additions and 18 deletions

View File

@@ -29,6 +29,7 @@ import net.i2p.router.util.DecayingHashSet;
import net.i2p.router.util.DecayingBloomFilter;
import net.i2p.util.Addresses;
import net.i2p.util.I2PThread;
import net.i2p.util.LHMCache;
import net.i2p.util.Log;
import net.i2p.util.VersionComparator;
@@ -45,6 +46,12 @@ class EstablishmentManager {
private final PacketBuilder _builder;
private final int _networkID;
// SSU 2
private final PacketBuilder2 _builder2;
private final boolean _enableSSU2;
private final Map<Hash, Token> _outboundTokens;
private final Map<RemoteHostId, Token> _inboundTokens;
/** map of RemoteHostId to InboundEstablishState */
private final ConcurrentHashMap<RemoteHostId, InboundEstablishState> _inboundStates;
@@ -138,6 +145,10 @@ class EstablishmentManager {
private static final String VERSION_ALLOW_EXTENDED_OPTIONS = "0.9.24";
private static final String PROP_DISABLE_EXT_OPTS = "i2np.udp.disableExtendedOptions";
// SSU 2
private static final int MAX_TOKENS = 512;
public static final long IB_TOKEN_EXPIRATION = 60*60*1000L;
public EstablishmentManager(RouterContext ctx, UDPTransport transport) {
_context = ctx;
@@ -145,12 +156,22 @@ class EstablishmentManager {
_networkID = ctx.router().getNetworkID();
_transport = transport;
_builder = transport.getBuilder();
_builder2 = transport.getBuilder2();
_enableSSU2 = _builder2 != null;
_inboundStates = new ConcurrentHashMap<RemoteHostId, InboundEstablishState>();
_outboundStates = new ConcurrentHashMap<RemoteHostId, OutboundEstablishState>();
_queuedOutbound = new ConcurrentHashMap<RemoteHostId, List<OutNetMessage>>();
_liveIntroductions = new ConcurrentHashMap<Long, OutboundEstablishState>();
_outboundByClaimedAddress = new ConcurrentHashMap<RemoteHostId, OutboundEstablishState>();
_outboundByHash = new ConcurrentHashMap<Hash, OutboundEstablishState>();
if (_enableSSU2) {
_inboundTokens = new LHMCache<RemoteHostId, Token>(MAX_TOKENS);
_outboundTokens = new LHMCache<Hash, Token>(MAX_TOKENS);
} else {
_inboundTokens = null;
_outboundTokens = null;
}
_activityLock = new Object();
_replayFilter = new DecayingHashSet(ctx, 10*60*1000, 8, "SSU-DH-X");
DEFAULT_MAX_CONCURRENT_ESTABLISH = Math.max(DEFAULT_LOW_MAX_CONCURRENT_ESTABLISH,
@@ -1428,6 +1449,86 @@ class EstablishmentManager {
}
}
//// SSU 2 ////
/**
* Remember a token that can be used later to connect to the peer
*
* @param token nonzero
* @since 0.9.54
*/
public void addOutboundToken(Hash peer, long token, long expires) {
if (expires < _context.clock().now())
return;
Token tok = new Token(token, expires);
synchronized(_outboundTokens) {
_outboundTokens.put(peer, tok);
}
}
/**
* Get a token to connect to the peer
*
* @return 0 if none available
* @since 0.9.54
*/
public long getOutboundToken(Hash peer) {
Token tok;
synchronized(_outboundTokens) {
tok = _outboundTokens.remove(peer);
}
if (tok == null)
return 0;
if (tok.expires < _context.clock().now())
return 0;
return tok.token;
}
/**
* Remember a token that can be used later for the peer to connect to us
*
* @param token nonzero
* @since 0.9.54
*/
public void addInboundToken(RemoteHostId peer, long token) {
long expires = _context.clock().now() + IB_TOKEN_EXPIRATION;
Token tok = new Token(token, expires);
synchronized(_inboundTokens) {
_inboundTokens.put(peer, tok);
}
}
/**
* Is the token from this peer valid?
*
* @return valid
* @since 0.9.54
*/
public boolean isInboundTokenValid(RemoteHostId peer, long token) {
if (token == 0)
return false;
Token tok;
synchronized(_inboundTokens) {
tok = _inboundTokens.get(peer);
if (tok == null)
return false;
if (tok.token != token)
return false;
_inboundTokens.remove(peer);
}
return tok.expires >= _context.clock().now();
}
private static class Token {
public final long token, expires;
public Token(long tok, long exp) {
token = tok; expires = exp;
}
}
//// End SSU 2 ////
/**
* Driving thread, processing up to one step for an inbound peer and up to
* one step for an outbound peer. This is prodded whenever any peer's state

View File

@@ -50,15 +50,15 @@ class InboundEstablishState {
private byte _receivedSignature[];
private boolean _verificationAttempted;
// sig not verified
private RouterIdentity _receivedUnconfirmedIdentity;
protected RouterIdentity _receivedUnconfirmedIdentity;
// identical to uncomfirmed, but sig now verified
private RouterIdentity _receivedConfirmedIdentity;
protected RouterIdentity _receivedConfirmedIdentity;
// general status
private final long _establishBegin;
//private long _lastReceive;
protected long _lastSend;
protected long _nextSend;
private final RemoteHostId _remoteHostId;
protected final RemoteHostId _remoteHostId;
protected InboundState _currentState;
private final Queue<OutNetMessage> _queuedMessages;
// count for backoff

View File

@@ -32,17 +32,21 @@ import net.i2p.util.Log;
* @since 0.9.54
*/
class InboundEstablishState2 extends InboundEstablishState implements SSU2Payload.PayloadCallback {
private final UDPTransport _transport;
private final InetSocketAddress _aliceSocketAddress;
private final long _rcvConnID;
private final long _sendConnID;
private final long _token;
private final long _nextToken;
private final HandshakeState _handshakeState;
private byte[] _sendHeaderEncryptKey1;
private final byte[] _rcvHeaderEncryptKey1;
private byte[] _sendHeaderEncryptKey2;
private byte[] _rcvHeaderEncryptKey2;
// testing
private static final boolean ENFORCE_TOKEN = false;
/**
* @param localPort Must be our external port, otherwise the signature of the
* SessionCreated message will be bad if the external port != the internal port.
@@ -51,6 +55,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
public InboundEstablishState2(RouterContext ctx, UDPTransport transport,
UDPPacket packet) throws GeneralSecurityException {
super(ctx, (InetSocketAddress) packet.getPacket().getSocketAddress());
_transport = transport;
DatagramPacket pkt = packet.getPacket();
_aliceSocketAddress = (InetSocketAddress) pkt.getSocketAddress();
_handshakeState = new HandshakeState(HandshakeState.PATTERN_ID_XK_SSU2, HandshakeState.RESPONDER, transport.getXDHFactory());
@@ -68,9 +73,6 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
int off = pkt.getOffset();
int len = pkt.getLength();
byte data[] = pkt.getData();
// fast MSB check for key < 2^255
if ((data[off + 32 + 32 - 1] & 0x80) != 0)
throw new GeneralSecurityException("Bad PK msg 1");
_rcvConnID = DataHelper.fromLong8(data, off);
_sendConnID = DataHelper.fromLong8(data, off + 16);
if (_rcvConnID == _sendConnID)
@@ -85,7 +87,9 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
token = ctx.random().nextLong();
} while (token == 0);
_token = token;
} else if (type == 0 && token == 0) { // || token not valid
} else if (type == 0 &&
(token == 0 ||
(ENFORCE_TOKEN && !_transport.getEstablisher().isInboundTokenValid(_remoteHostId, token)))) {
_currentState = InboundState.IB_STATE_REQUEST_BAD_TOKEN_RECEIVED;
_sendHeaderEncryptKey2 = introKey;
do {
@@ -93,6 +97,9 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
} while (token == 0);
_token = token;
} else {
// fast MSB check for key < 2^255
if ((data[off + 32 + 32 - 1] & 0x80) != 0)
throw new GeneralSecurityException("Bad PK msg 1");
// probably don't need again
_token = token;
_handshakeState.start();
@@ -116,7 +123,6 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
_sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader");
_currentState = InboundState.IB_STATE_REQUEST_RECEIVED;
}
_nextToken = ctx.random().nextLong();
packetReceived();
}
@@ -149,6 +155,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
System.out.println("Got RI block: " + ri);
if (isHandshake)
throw new DataFormatException("RI in Sess Req");
_receivedUnconfirmedIdentity = ri.getIdentity();
List<RouterAddress> addrs = ri.getTargetAddresses("SSU", "SSU2");
RouterAddress ra = null;
for (RouterAddress addr : addrs) {
@@ -179,6 +186,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
if (!"2".equals(ra.getOption("v")))
throw new DataFormatException("bad SSU2 v");
_receivedConfirmedIdentity = _receivedUnconfirmedIdentity;
_sendHeaderEncryptKey1 = ik;
//_sendHeaderEncryptKey2 calculated below
@@ -207,19 +215,25 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
}
public void gotToken(long token, long expires) {
System.out.println("Got NEW TOKEN block " + token + " expires " + DataHelper.formatTime(expires));
if (_receivedConfirmedIdentity == null)
throw new IllegalStateException("RI must be first");
_transport.getEstablisher().addOutboundToken(_receivedConfirmedIdentity.calculateHash(), token, expires);
}
public void gotI2NP(I2NPMessage msg) {
System.out.println("Got I2NP block: " + msg);
if (getState() != InboundState.IB_STATE_CREATED_SENT)
throw new IllegalStateException("I2NP in Sess Req");
if (_receivedConfirmedIdentity == null)
throw new IllegalStateException("RI must be first");
}
public void gotFragment(byte[] data, int off, int len, long messageID, int frag, boolean isLast) throws DataFormatException {
System.out.println("Got FRAGMENT block: " + messageID);
if (getState() != InboundState.IB_STATE_CREATED_SENT)
throw new IllegalStateException("I2NP in Sess Req");
if (_receivedConfirmedIdentity == null)
throw new IllegalStateException("RI must be first");
}
public void gotACK(long ackThru, int acks, byte[] ranges) {
@@ -244,7 +258,15 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
public long getSendConnID() { return _sendConnID; }
public long getRcvConnID() { return _rcvConnID; }
public long getToken() { return _token; }
public long getNextToken() { return _nextToken; }
public long getNextToken() {
// generate on the fly, this will only be called once
long token;
do {
token = _context.random().nextLong();
} while (token == 0);
_transport.getEstablisher().addInboundToken(_remoteHostId, token);
return token;
}
public HandshakeState getHandshakeState() { return _handshakeState; }
public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; }
public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; }

View File

@@ -55,7 +55,7 @@ class OutboundEstablishState {
private long _nextSend;
protected RemoteHostId _remoteHostId;
private final RemoteHostId _claimedAddress;
private final RouterIdentity _remotePeer;
protected final RouterIdentity _remotePeer;
private final boolean _allowExtendedOptions;
private final boolean _needIntroduction;
private final SessionKey _introKey;

View File

@@ -37,7 +37,6 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
private final long _sendConnID;
private final long _rcvConnID;
private long _token;
private final long _nextToken;
private HandshakeState _handshakeState;
private final byte[] _sendHeaderEncryptKey1;
private final byte[] _rcvHeaderEncryptKey1;
@@ -96,7 +95,6 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
} while (_token == 0);
}
_rcvConnID = rcid;
_nextToken = ctx.random().nextLong();
byte[] ik = introKey.getData();
_sendHeaderEncryptKey1 = ik;
_rcvHeaderEncryptKey1 = ik;
@@ -173,7 +171,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
}
public void gotToken(long token, long expires) {
System.out.println("Got NEW TOKEN block " + token + " expires " + DataHelper.formatTime(expires));
_transport.getEstablisher().addOutboundToken(_remotePeer.calculateHash(), token, expires);
}
public void gotI2NP(I2NPMessage msg) {
@@ -208,7 +206,15 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
public long getSendConnID() { return _sendConnID; }
public long getRcvConnID() { return _rcvConnID; }
public long getToken() { return _token; }
public long getNextToken() { return _nextToken; }
public long getNextToken() {
// generate on the fly, this will only be called once
long token;
do {
token = _context.random().nextLong();
} while (token == 0);
_transport.getEstablisher().addInboundToken(_remoteHostId, token);
return token;
}
public HandshakeState getHandshakeState() { return _handshakeState; }
public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; }
public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; }

View File

@@ -795,7 +795,7 @@ class PacketBuilder2 {
blocks.add(block);
}
if (token > 0) {
block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + 6*24*60*60*1000L);
block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + EstablishmentManager.IB_TOKEN_EXPIRATION);
len += block.getTotalLength();
blocks.add(block);
}
@@ -923,7 +923,7 @@ class PacketBuilder2 {
blocks.add(riblock);
if (token > 0) {
// TODO only if room
Block block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + 6*24*60*60*1000L);
Block block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + EstablishmentManager.IB_TOKEN_EXPIRATION);
len += block.getTotalLength();
blocks.add(block);
}

View File

@@ -223,6 +223,7 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
}
public void gotToken(long token, long expires) {
_transport.getEstablisher().addOutboundToken(_remotePeer, token, expires);
}
public void gotI2NP(I2NPMessage msg) {