forked from I2P_Developers/i2p.i2p
SSU2: Fixes part 1
after initial testnet testing Use correct intro key for Session/Token request Fix state management in EstablishmentManager, OES2, IES2 Fix next send time during handshake Fix header decryption in PacketHandler Add additional packet checks in IES2 handling Remove expired IES immediately (SSU1 also) Failsafe sleep in EstablishmentManager on exception Remove dup requestSent() calls Don't release packet in PS2 Log tweaks and javadocs
This commit is contained in:
@@ -10,6 +10,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
@@ -382,7 +383,17 @@ class EstablishmentManager {
|
||||
}
|
||||
} else {
|
||||
// must have a valid session key
|
||||
byte[] keyBytes = addr.getIntroKey();
|
||||
byte[] keyBytes;
|
||||
int version = _transport.getSSUVersion(ra);
|
||||
if (version == 1) {
|
||||
keyBytes = addr.getIntroKey();
|
||||
} else {
|
||||
String siv = ra.getOption("i");
|
||||
if (siv != null)
|
||||
keyBytes = Base64.decode(siv);
|
||||
else
|
||||
keyBytes = null;
|
||||
}
|
||||
if (keyBytes == null) {
|
||||
_transport.markUnreachable(toHash);
|
||||
_transport.failed(msg, "Peer has no key, cannot establish");
|
||||
@@ -403,7 +414,6 @@ class EstablishmentManager {
|
||||
// don't ask if they are indirect
|
||||
boolean requestIntroduction = allowExtendedOptions && !isIndirect &&
|
||||
_transport.introducersMaybeRequired(TransportUtil.isIPv6(ra));
|
||||
int version = _transport.getSSUVersion(ra);
|
||||
if (version == 1) {
|
||||
state = new OutboundEstablishState(_context, maybeTo, to,
|
||||
toIdentity, allowExtendedOptions,
|
||||
@@ -751,7 +761,7 @@ class EstablishmentManager {
|
||||
*/
|
||||
void receiveRetry(OutboundEstablishState2 state, UDPPacket packet) {
|
||||
try {
|
||||
state.receiveSessionCreated(packet);
|
||||
state.receiveRetry(packet);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Corrupt Retry from: " + state, gse);
|
||||
@@ -760,7 +770,7 @@ class EstablishmentManager {
|
||||
}
|
||||
notifyActivity();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive retry from: " + state);
|
||||
_log.debug("Receive retry with token " + state.getToken() + " from: " + state);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1108,15 +1118,20 @@ class EstablishmentManager {
|
||||
public static final long MAX_TAG_VALUE = 0xFFFFFFFFl;
|
||||
|
||||
/**
|
||||
* This may be called more than once
|
||||
* This handles both initial send and retransmission of Session Created,
|
||||
* and, for SSU2, send of Retry.
|
||||
* Retry is never retransmnitted.
|
||||
*
|
||||
* This may be called more than once.
|
||||
*
|
||||
* Caller must synch on state.
|
||||
*/
|
||||
private void sendCreated(InboundEstablishState state) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send created to: " + state);
|
||||
|
||||
int version = state.getVersion();
|
||||
UDPPacket pkt;
|
||||
if (version == 1) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Send created to: " + state);
|
||||
try {
|
||||
state.generateSessionKey();
|
||||
} catch (DHSessionKeyBuilder.InvalidPublicParameterException ippe) {
|
||||
@@ -1130,13 +1145,27 @@ class EstablishmentManager {
|
||||
_transport.getExternalPort(state.getSentIP().length == 16),
|
||||
_transport.getIntroKey());
|
||||
} else {
|
||||
// if already sent, get from the state to retx
|
||||
InboundEstablishState2 state2 = (InboundEstablishState2) state;
|
||||
InboundEstablishState.InboundState istate = state2.getState();
|
||||
if (istate == IB_STATE_CREATED_SENT)
|
||||
if (istate == IB_STATE_CREATED_SENT) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Send created to: " + state);
|
||||
// if already sent, get from the state to retx
|
||||
pkt = state2.getRetransmitSessionCreatedPacket();
|
||||
else
|
||||
pkt = _builder2.buildSessionCreatedPacket((InboundEstablishState2) state);
|
||||
} else if (istate == IB_STATE_REQUEST_RECEIVED) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Send created to: " + state);
|
||||
pkt = _builder2.buildSessionCreatedPacket(state2);
|
||||
} else if (istate == IB_STATE_TOKEN_REQUEST_RECEIVED ||
|
||||
istate == IB_STATE_REQUEST_BAD_TOKEN_RECEIVED) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Send retry to: " + state);
|
||||
pkt = _builder2.buildRetryPacket(state2);
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Unhandled state " + istate + " on " + state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pkt == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -1152,23 +1181,44 @@ class EstablishmentManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller should probably synch on outboundState
|
||||
* This handles both initial send and retransmission of Session Request,
|
||||
* and, for SSU2, initial send and retransmission of Token Request.
|
||||
*
|
||||
* This may be called more than once.
|
||||
*
|
||||
* Caller must synch on state.
|
||||
*/
|
||||
private void sendRequest(OutboundEstablishState state) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send SessionRequest to: " + state);
|
||||
int version = state.getVersion();
|
||||
UDPPacket packet;
|
||||
if (version == 1) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Send Session Request to: " + state);
|
||||
packet = _builder.buildSessionRequestPacket(state);
|
||||
} else {
|
||||
// if already sent, get from the state to retx
|
||||
OutboundEstablishState2 state2 = (OutboundEstablishState2) state;
|
||||
OutboundEstablishState.OutboundState ostate = state2.getState();
|
||||
if (ostate == OB_STATE_REQUEST_SENT)
|
||||
if (ostate == OB_STATE_REQUEST_SENT ||
|
||||
ostate == OB_STATE_REQUEST_SENT_NEW_TOKEN) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Send Session Request to: " + state);
|
||||
// if already sent, get from the state to retx
|
||||
packet = state2.getRetransmitSessionRequestPacket();
|
||||
else
|
||||
} else if (ostate == OB_STATE_NEEDS_TOKEN ||
|
||||
ostate == OB_STATE_TOKEN_REQUEST_SENT) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Send Token Request to: " + state);
|
||||
packet = _builder2.buildTokenRequestPacket(state2);
|
||||
} else if (ostate == OB_STATE_UNKNOWN ||
|
||||
ostate == OB_STATE_RETRY_RECEIVED) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Send Session Request to: " + state);
|
||||
packet = _builder2.buildSessionRequestPacket(state2);
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Unhandled state " + ostate + " on " + state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (packet != null) {
|
||||
_transport.send(packet);
|
||||
@@ -1315,7 +1365,8 @@ class EstablishmentManager {
|
||||
* Note that while a SessionConfirmed could in theory be fragmented,
|
||||
* in practice a RouterIdentity is 387 bytes and a single fragment is 512 bytes max,
|
||||
* so it will never be fragmented.
|
||||
* Caller should probably synch on state.
|
||||
*
|
||||
* Caller must synch on state.
|
||||
*/
|
||||
private void sendConfirmation(OutboundEstablishState state) {
|
||||
boolean valid = state.validateSessionCreated();
|
||||
@@ -1379,9 +1430,13 @@ class EstablishmentManager {
|
||||
* ack to the SessionConfirmed - otherwise we haven't generated the keys.
|
||||
* Caller should probably synch on state.
|
||||
*
|
||||
* SSU1 only.
|
||||
*
|
||||
* @since 0.9.2
|
||||
*/
|
||||
private void sendDestroy(OutboundEstablishState state) {
|
||||
if (state.getVersion() > 1)
|
||||
return;
|
||||
UDPPacket packet = _builder.buildSessionDestroyPacket(state);
|
||||
if (packet != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -1397,9 +1452,13 @@ class EstablishmentManager {
|
||||
* Otherwise we haven't generated the keys.
|
||||
* Caller should probably synch on state.
|
||||
*
|
||||
* SSU1 only.
|
||||
*
|
||||
* @since 0.9.2
|
||||
*/
|
||||
private void sendDestroy(InboundEstablishState state) {
|
||||
if (state.getVersion() > 1)
|
||||
return;
|
||||
UDPPacket packet = _builder.buildSessionDestroyPacket(state);
|
||||
if (packet != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -1463,8 +1522,11 @@ class EstablishmentManager {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Processing for inbound: " + inboundState);
|
||||
synchronized (inboundState) {
|
||||
switch (inboundState.getState()) {
|
||||
InboundEstablishState.InboundState istate = inboundState.getState();
|
||||
switch (istate) {
|
||||
case IB_STATE_REQUEST_RECEIVED:
|
||||
case IB_STATE_TOKEN_REQUEST_RECEIVED: // SSU2
|
||||
case IB_STATE_REQUEST_BAD_TOKEN_RECEIVED: // SSU2
|
||||
if (expired)
|
||||
processExpired(inboundState);
|
||||
else
|
||||
@@ -1473,11 +1535,18 @@ class EstablishmentManager {
|
||||
|
||||
case IB_STATE_CREATED_SENT: // fallthrough
|
||||
case IB_STATE_CONFIRMED_PARTIALLY:
|
||||
case IB_STATE_RETRY_SENT: // SSU2
|
||||
if (expired) {
|
||||
sendDestroy(inboundState);
|
||||
processExpired(inboundState);
|
||||
} else if (inboundState.getNextSendTime() <= now) {
|
||||
sendCreated(inboundState);
|
||||
if (istate == IB_STATE_RETRY_SENT) {
|
||||
// Retry is never retransmitted
|
||||
inboundState.fail();
|
||||
processExpired(inboundState);
|
||||
} else {
|
||||
sendCreated(inboundState);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1510,6 +1579,12 @@ class EstablishmentManager {
|
||||
// Can't happen, always call receiveSessionRequest() before putting in map
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("hrm, state is unknown for " + inboundState);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Unhandled state on " + inboundState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1585,6 +1660,7 @@ class EstablishmentManager {
|
||||
switch (outboundState.getState()) {
|
||||
case OB_STATE_UNKNOWN: // fall thru
|
||||
case OB_STATE_INTRODUCED:
|
||||
case OB_STATE_NEEDS_TOKEN: // SSU2 only
|
||||
if (expired)
|
||||
processExpired(outboundState);
|
||||
else
|
||||
@@ -1592,6 +1668,9 @@ class EstablishmentManager {
|
||||
break;
|
||||
|
||||
case OB_STATE_REQUEST_SENT:
|
||||
case OB_STATE_TOKEN_REQUEST_SENT: // SSU2 only
|
||||
case OB_STATE_RETRY_RECEIVED: // SSU2 only
|
||||
case OB_STATE_REQUEST_SENT_NEW_TOKEN: // SSU2 only
|
||||
// no response yet (or it was invalid), lets retry
|
||||
long rtime = outboundState.getRequestSentTime();
|
||||
if (expired || (rtime > 0 && rtime + OB_MESSAGE_TIMEOUT <= now))
|
||||
@@ -1635,6 +1714,11 @@ class EstablishmentManager {
|
||||
case OB_STATE_VALIDATION_FAILED:
|
||||
processExpired(outboundState);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Unhandled state on " + outboundState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1695,6 +1779,7 @@ class EstablishmentManager {
|
||||
* @since 0.9.2
|
||||
*/
|
||||
private void processExpired(InboundEstablishState inboundState) {
|
||||
_inboundStates.remove(inboundState.getRemoteHostId());
|
||||
OutNetMessage msg;
|
||||
while ((msg = inboundState.getNextQueuedMessage()) != null) {
|
||||
_transport.failed(msg, "Expired during failed establish");
|
||||
@@ -1794,6 +1879,8 @@ class EstablishmentManager {
|
||||
doPass();
|
||||
} catch (RuntimeException re) {
|
||||
_log.log(Log.CRIT, "Error in the establisher", re);
|
||||
// don't loop too fast
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
_inboundStates.clear();
|
||||
|
||||
@@ -32,8 +32,8 @@ class InboundEstablishState {
|
||||
protected final Log _log;
|
||||
// SessionRequest message
|
||||
private byte _receivedX[];
|
||||
private byte _bobIP[];
|
||||
private final int _bobPort;
|
||||
protected byte _bobIP[];
|
||||
protected final int _bobPort;
|
||||
private final DHSessionKeyBuilder _keyBuilder;
|
||||
// SessionCreated message
|
||||
private byte _sentY[];
|
||||
|
||||
@@ -77,9 +77,6 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
//_sendHeaderEncryptKey2 set below
|
||||
//_rcvHeaderEncryptKey2 set below
|
||||
_introductionRequested = false; // todo
|
||||
//_bobIP = TODO
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Receive sessionRequest, BobIP = " + Addresses.toString(_bobIP));
|
||||
int off = pkt.getOffset();
|
||||
int len = pkt.getLength();
|
||||
byte data[] = pkt.getData();
|
||||
@@ -107,12 +104,18 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
} else if (type == SESSION_REQUEST_FLAG_BYTE &&
|
||||
(token == 0 ||
|
||||
(ENFORCE_TOKEN && !_transport.getEstablisher().isInboundTokenValid(_remoteHostId, token)))) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Invalid token " + token + " in session request from: " + _aliceSocketAddress);
|
||||
_currentState = InboundState.IB_STATE_REQUEST_BAD_TOKEN_RECEIVED;
|
||||
_sendHeaderEncryptKey2 = introKey;
|
||||
// Generate token for the retry.
|
||||
// We do NOT register it with the EstablishmentManager, it must be used immediately.
|
||||
do {
|
||||
token = ctx.random().nextLong();
|
||||
} while (token == 0);
|
||||
_token = token;
|
||||
// do NOT bother to init the handshake state and decrypt the payload
|
||||
_timeReceived = _establishBegin;
|
||||
} else {
|
||||
// fast MSB check for key < 2^255
|
||||
if ((data[off + LONG_HEADER_SIZE + KEY_LEN - 1] & 0x80) != 0)
|
||||
@@ -251,7 +254,11 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
}
|
||||
|
||||
public void gotAddress(byte[] ip, int port) {
|
||||
throw new IllegalStateException("Address in Handshake");
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Got Address: " + Addresses.toString(ip, port));
|
||||
_bobIP = ip;
|
||||
// final, see super
|
||||
//_bobPort = port;
|
||||
}
|
||||
|
||||
public void gotIntroKey(byte[] key) {
|
||||
@@ -356,8 +363,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
}
|
||||
_createdSentCount++;
|
||||
_nextSend = _lastSend + delay;
|
||||
if ( (_currentState == InboundState.IB_STATE_UNKNOWN) || (_currentState == InboundState.IB_STATE_REQUEST_RECEIVED) )
|
||||
_currentState = InboundState.IB_STATE_CREATED_SENT;
|
||||
_currentState = InboundState.IB_STATE_CREATED_SENT;
|
||||
}
|
||||
|
||||
|
||||
@@ -368,6 +374,9 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
throw new IllegalStateException("Bad state for Retry Sent: " + _currentState);
|
||||
_currentState = InboundState.IB_STATE_RETRY_SENT;
|
||||
_lastSend = _context.clock().now();
|
||||
// Won't really be transmitted, they have 3 sec to respond or
|
||||
// EstablishmentManager.handleInbound() will fail the connection
|
||||
_nextSend = _lastSend + RETRANSMIT_DELAY;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -539,17 +548,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
byte data[] = pkt.getData();
|
||||
int off = pkt.getOffset();
|
||||
System.arraycopy(_sessCrForReTX, 0, data, off, _sessCrForReTX.length);
|
||||
InetAddress to;
|
||||
try {
|
||||
to = InetAddress.getByAddress(_aliceIP);
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("How did we think this was a valid IP? " + _remoteHostId);
|
||||
packet.release();
|
||||
return null;
|
||||
}
|
||||
pkt.setAddress(to);
|
||||
pkt.setPort(_alicePort);
|
||||
pkt.setSocketAddress(_aliceSocketAddress);
|
||||
packet.setMessageType(PacketBuilder2.TYPE_CONF);
|
||||
packet.setPriority(PacketBuilder2.PRIORITY_HIGH);
|
||||
createdPacketSent();
|
||||
|
||||
@@ -35,8 +35,8 @@ class OutboundEstablishState {
|
||||
private DHSessionKeyBuilder _keyBuilder;
|
||||
// SessionCreated message
|
||||
private byte _receivedY[];
|
||||
private byte _aliceIP[];
|
||||
private int _alicePort;
|
||||
protected byte _aliceIP[];
|
||||
protected int _alicePort;
|
||||
private long _receivedRelayTag;
|
||||
private long _receivedSignedOnTime;
|
||||
private SessionKey _sessionKey;
|
||||
@@ -94,6 +94,11 @@ class OutboundEstablishState {
|
||||
/** SessionConfirmed failed validation */
|
||||
OB_STATE_VALIDATION_FAILED,
|
||||
|
||||
/**
|
||||
* SSU2: We don't have a token
|
||||
* @since 0.9.54
|
||||
*/
|
||||
OB_STATE_NEEDS_TOKEN,
|
||||
/**
|
||||
* SSU2: We have sent a token request
|
||||
* @since 0.9.54
|
||||
|
||||
@@ -123,6 +123,8 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
_routerAddress = ra;
|
||||
if (_token != 0)
|
||||
createNewState(ra);
|
||||
else
|
||||
_currentState = OutboundState.OB_STATE_NEEDS_TOKEN;
|
||||
|
||||
byte[] ik = introKey.getData();
|
||||
_sendHeaderEncryptKey1 = ik;
|
||||
@@ -196,7 +198,9 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
|
||||
public void gotAddress(byte[] ip, int port) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Got ADDRESS block: " + Addresses.toString(ip, port));
|
||||
_log.debug("Got Address: " + Addresses.toString(ip, port));
|
||||
_aliceIP = ip;
|
||||
_alicePort = port;
|
||||
}
|
||||
|
||||
public void gotIntroKey(byte[] key) {
|
||||
@@ -245,8 +249,15 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
// end payload callbacks
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
// SSU 1 unsupported things
|
||||
// SSU 1 overrides
|
||||
|
||||
@Override
|
||||
public synchronized boolean validateSessionCreated() {
|
||||
// All validation is in receiveSessionCreated()
|
||||
boolean rv = _currentState == OutboundState.OB_STATE_CREATED_RECEIVED ||
|
||||
_currentState == OutboundState.OB_STATE_CONFIRMED_COMPLETELY;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// SSU 2 things
|
||||
|
||||
@@ -268,6 +279,9 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; }
|
||||
public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; }
|
||||
public byte[] getSendHeaderEncryptKey2() { return _sendHeaderEncryptKey2; }
|
||||
/**
|
||||
* @return null before Session Request is sent (i.e. we sent a Token Request first)
|
||||
*/
|
||||
public byte[] getRcvHeaderEncryptKey2() { return _rcvHeaderEncryptKey2; }
|
||||
public byte[] getRcvRetryHeaderEncryptKey2() { return _rcvRetryHeaderEncryptKey2; }
|
||||
public InetSocketAddress getSentAddress() { return _bobSocketAddress; }
|
||||
@@ -278,9 +292,22 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
public synchronized void receiveRetry(UDPPacket packet) throws GeneralSecurityException {
|
||||
////// TODO state check
|
||||
DatagramPacket pkt = packet.getPacket();
|
||||
SocketAddress from = pkt.getSocketAddress();
|
||||
if (!from.equals(_bobSocketAddress))
|
||||
throw new GeneralSecurityException("Address mismatch: req: " + _bobSocketAddress + " conf: " + from);
|
||||
int off = pkt.getOffset();
|
||||
int len = pkt.getLength();
|
||||
byte data[] = pkt.getData();
|
||||
long rid = DataHelper.fromLong8(data, off);
|
||||
if (rid != _rcvConnID)
|
||||
throw new GeneralSecurityException("Conn ID mismatch: 1: " + _rcvConnID + " 2: " + rid);
|
||||
long sid = DataHelper.fromLong8(data, off + SRC_CONN_ID_OFFSET);
|
||||
if (sid != _sendConnID)
|
||||
throw new GeneralSecurityException("Conn ID mismatch: 1: " + _sendConnID + " 2: " + sid);
|
||||
long token = DataHelper.fromLong8(data, off + TOKEN_OFFSET);
|
||||
if (token == 0)
|
||||
throw new GeneralSecurityException("Bad token 0 in retry");
|
||||
_token = token;
|
||||
_timeReceived = 0;
|
||||
try {
|
||||
// decrypt in-place
|
||||
@@ -302,7 +329,8 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
if (skew > MAX_SKEW || skew < 0 - MAX_SKEW)
|
||||
throw new GeneralSecurityException("Skew exceeded in Session/Token Request: " + skew);
|
||||
createNewState(_routerAddress);
|
||||
////// TODO state change
|
||||
_currentState = OutboundState.OB_STATE_RETRY_RECEIVED;
|
||||
packetReceived();
|
||||
}
|
||||
|
||||
public synchronized void receiveSessionCreated(UDPPacket packet) throws GeneralSecurityException {
|
||||
@@ -320,6 +348,13 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
int off = pkt.getOffset();
|
||||
int len = pkt.getLength();
|
||||
byte data[] = pkt.getData();
|
||||
long rid = DataHelper.fromLong8(data, off);
|
||||
if (rid != _rcvConnID)
|
||||
throw new GeneralSecurityException("Conn ID mismatch: 1: " + _rcvConnID + " 2: " + rid);
|
||||
long sid = DataHelper.fromLong8(data, off + SRC_CONN_ID_OFFSET);
|
||||
if (sid != _sendConnID)
|
||||
throw new GeneralSecurityException("Conn ID mismatch: 1: " + _sendConnID + " 2: " + sid);
|
||||
|
||||
_handshakeState.mixHash(data, off, LONG_HEADER_SIZE);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State after mixHash 2: " + _handshakeState);
|
||||
@@ -344,11 +379,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
_sessReqForReTX = null;
|
||||
_sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessionConfirmed");
|
||||
|
||||
if (_currentState == OutboundState.OB_STATE_UNKNOWN ||
|
||||
_currentState == OutboundState.OB_STATE_REQUEST_SENT ||
|
||||
_currentState == OutboundState.OB_STATE_INTRODUCED ||
|
||||
_currentState == OutboundState.OB_STATE_PENDING_INTRO)
|
||||
_currentState = OutboundState.OB_STATE_CREATED_RECEIVED;
|
||||
_currentState = OutboundState.OB_STATE_CREATED_RECEIVED;
|
||||
|
||||
if (_requestSentCount == 1) {
|
||||
_rtt = (int) (_context.clock().now() - _requestSentTime);
|
||||
@@ -361,10 +392,10 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
* and save them for retransmission
|
||||
*/
|
||||
public synchronized void tokenRequestSent(DatagramPacket packet) {
|
||||
if (_currentState == OutboundState.OB_STATE_UNKNOWN)
|
||||
OutboundState old = _currentState;
|
||||
requestSent();
|
||||
if (old == OutboundState.OB_STATE_NEEDS_TOKEN)
|
||||
_currentState = OutboundState.OB_STATE_TOKEN_REQUEST_SENT;
|
||||
else if (_currentState == OutboundState.OB_STATE_RETRY_RECEIVED)
|
||||
_currentState = OutboundState.OB_STATE_REQUEST_SENT_NEW_TOKEN;
|
||||
// don't bother saving for retx, just make a new one every time
|
||||
}
|
||||
|
||||
@@ -383,7 +414,10 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
}
|
||||
if (_rcvHeaderEncryptKey2 == null)
|
||||
_rcvHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader");
|
||||
OutboundState old = _currentState;
|
||||
requestSent();
|
||||
if (old == OutboundState.OB_STATE_RETRY_RECEIVED)
|
||||
_currentState = OutboundState.OB_STATE_REQUEST_SENT_NEW_TOKEN;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -461,17 +495,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
byte data[] = pkt.getData();
|
||||
int off = pkt.getOffset();
|
||||
System.arraycopy(_sessReqForReTX, 0, data, off, _sessReqForReTX.length);
|
||||
InetAddress to;
|
||||
try {
|
||||
to = InetAddress.getByAddress(_bobIP);
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("How did we think this was a valid IP? " + _remoteHostId);
|
||||
packet.release();
|
||||
return null;
|
||||
}
|
||||
pkt.setAddress(to);
|
||||
pkt.setPort(_bobPort);
|
||||
pkt.setSocketAddress(_bobSocketAddress);
|
||||
packet.setMessageType(PacketBuilder2.TYPE_SREQ);
|
||||
packet.setPriority(PacketBuilder2.PRIORITY_HIGH);
|
||||
requestSent();
|
||||
|
||||
@@ -207,7 +207,8 @@ class PacketBuilder2 {
|
||||
off += sz;
|
||||
sizeWritten += sz;
|
||||
}
|
||||
Block block = getPadding(sizeWritten, peer.getMTU());
|
||||
// FIXME
|
||||
Block block = getPadding(sizeWritten, currentMTU);
|
||||
if (block != null) {
|
||||
blocks.add(block);
|
||||
int sz = block.getTotalLength();
|
||||
@@ -216,13 +217,13 @@ class PacketBuilder2 {
|
||||
}
|
||||
SSU2Payload.writePayload(data, SHORT_HEADER_SIZE, blocks);
|
||||
pkt.setLength(off);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Packet " + pktNum + " before encryption:\n" + HexDump.dump(data, 0, off));
|
||||
//if (_log.shouldDebug())
|
||||
// _log.debug("Packet " + pktNum + " before encryption:\n" + HexDump.dump(data, 0, off));
|
||||
|
||||
encryptDataPacket(packet, peer.getSendCipher(), pktNum, peer.getSendHeaderEncryptKey1(), peer.getSendHeaderEncryptKey2());
|
||||
setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort());
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Packet " + pktNum + " after encryption:\n" + HexDump.dump(data, 0, pkt.getLength()));
|
||||
//if (_log.shouldDebug())
|
||||
// _log.debug("Packet " + pktNum + " after encryption:\n" + HexDump.dump(data, 0, pkt.getLength()));
|
||||
|
||||
// FIXME ticket #2675
|
||||
// the packet could have been built before the current mtu got lowered, so
|
||||
@@ -294,7 +295,6 @@ class PacketBuilder2 {
|
||||
pkt.setLength(LONG_HEADER_SIZE);
|
||||
byte[] introKey = state.getSendHeaderEncryptKey1();
|
||||
encryptTokenRequest(packet, introKey, n, introKey, introKey);
|
||||
state.requestSent();
|
||||
pkt.setSocketAddress(state.getSentAddress());
|
||||
packet.setMessageType(TYPE_SREQ);
|
||||
packet.setPriority(PRIORITY_HIGH);
|
||||
@@ -316,7 +316,6 @@ class PacketBuilder2 {
|
||||
pkt.setLength(LONG_HEADER_SIZE);
|
||||
byte[] introKey = state.getSendHeaderEncryptKey1();
|
||||
encryptSessionRequest(packet, state.getHandshakeState(), introKey, introKey, state.needIntroduction());
|
||||
state.requestSent();
|
||||
pkt.setSocketAddress(state.getSentAddress());
|
||||
packet.setMessageType(TYPE_SREQ);
|
||||
packet.setPriority(PRIORITY_HIGH);
|
||||
@@ -451,7 +450,8 @@ class PacketBuilder2 {
|
||||
pkt.setLength(SHORT_HEADER_SIZE);
|
||||
SSU2Payload.RIBlock block = new SSU2Payload.RIBlock(ourInfo, 0, len,
|
||||
false, gzip, 0, numFragments);
|
||||
encryptSessionConfirmed(packet, state.getHandshakeState(), state.getMTU(),
|
||||
boolean isIPv6 = state.getSentIP().length == 16;
|
||||
encryptSessionConfirmed(packet, state.getHandshakeState(), state.getMTU(), isIPv6,
|
||||
state.getSendHeaderEncryptKey1(), state.getSendHeaderEncryptKey2(), block, state.getNextToken());
|
||||
pkt.setSocketAddress(state.getSentAddress());
|
||||
packet.setMessageType(TYPE_CONF);
|
||||
@@ -875,22 +875,24 @@ class PacketBuilder2 {
|
||||
* @param packet containing only 16 byte header
|
||||
*/
|
||||
private void encryptSessionConfirmed(UDPPacket packet, HandshakeState state, int mtu,
|
||||
byte[] hdrKey1, byte[] hdrKey2,
|
||||
boolean isIPv6, byte[] hdrKey1, byte[] hdrKey2,
|
||||
SSU2Payload.RIBlock riblock, long token) {
|
||||
DatagramPacket pkt = packet.getPacket();
|
||||
byte data[] = pkt.getData();
|
||||
int off = pkt.getOffset();
|
||||
mtu -= UDP_HEADER_SIZE;
|
||||
mtu -= isIPv6 ? IPV6_HEADER_SIZE : IP_HEADER_SIZE;
|
||||
try {
|
||||
List<Block> blocks = new ArrayList<Block>(3);
|
||||
int len = riblock.getTotalLength();
|
||||
blocks.add(riblock);
|
||||
if (token > 0) {
|
||||
// TODO only if room
|
||||
// only if room
|
||||
if (token > 0 && mtu - len >= 15) {
|
||||
Block block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + EstablishmentManager.IB_TOKEN_EXPIRATION);
|
||||
len += block.getTotalLength();
|
||||
blocks.add(block);
|
||||
}
|
||||
Block block = getPadding(len, mtu - 80);
|
||||
Block block = getPadding(len, mtu - (SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN + MAC_LEN)); // 80
|
||||
if (block != null) {
|
||||
len += block.getTotalLength();
|
||||
blocks.add(block);
|
||||
@@ -904,6 +906,8 @@ class PacketBuilder2 {
|
||||
_log.debug("State after mixHash 3: " + state);
|
||||
state.writeMessage(data, off + SHORT_HEADER_SIZE, data, off + SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN, len);
|
||||
pkt.setLength(pkt.getLength() + KEY_LEN + MAC_LEN + len + MAC_LEN);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Session confirmed packet length is: " + pkt.getLength());
|
||||
} catch (RuntimeException re) {
|
||||
if (!_log.shouldWarn())
|
||||
_log.error("Bad msg 3 out", re);
|
||||
|
||||
@@ -211,7 +211,7 @@ class PacketHandler {
|
||||
handlePacket(_reader, packet);
|
||||
} catch (RuntimeException e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Crazy error handling a packet: " + packet, e);
|
||||
_log.error("Internal error handling " + packet, e);
|
||||
}
|
||||
|
||||
// back to the cache with thee!
|
||||
@@ -779,6 +779,8 @@ class PacketHandler {
|
||||
*/
|
||||
private void receiveSSU2Packet(UDPPacket packet, PeerState2 state) {
|
||||
// header and body decryption is done by PeerState2
|
||||
// This bypasses InboundMessageStates completely.
|
||||
// All handling of fragments and acks is done in PeerState2.
|
||||
state.receivePacket(packet);
|
||||
}
|
||||
|
||||
@@ -830,8 +832,9 @@ class PacketHandler {
|
||||
// Session Request (after Retry) or Session Confirmed
|
||||
// or retransmitted Session Request or Token Rquest
|
||||
k2 = state.getRcvHeaderEncryptKey2();
|
||||
if (state.getState() == InboundEstablishState.InboundState.IB_STATE_RETRY_SENT) {
|
||||
// Session Request
|
||||
if (k2 == null) {
|
||||
// Session Request after Retry
|
||||
k2 = k1;
|
||||
header = SSU2Header.trialDecryptHandshakeHeader(packet, k1, k2);
|
||||
if (header == null ||
|
||||
header.getType() != SSU2Util.SESSION_REQUEST_FLAG_BYTE ||
|
||||
@@ -841,6 +844,13 @@ class PacketHandler {
|
||||
_log.warn("Failed decrypt Session Request after Retry: " + header);
|
||||
return false;
|
||||
}
|
||||
if (header.getSrcConnID() != state.getSendConnID()) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Bad Source Conn id " + header);
|
||||
// TODO could be a retransmitted Session Request,
|
||||
// tell establisher?
|
||||
return false;
|
||||
}
|
||||
type = SSU2Util.SESSION_REQUEST_FLAG_BYTE;
|
||||
} else {
|
||||
// Session Confirmed or retransmitted Session Request or Token Request
|
||||
@@ -862,13 +872,6 @@ class PacketHandler {
|
||||
_log.warn("Bad Dest Conn id " + header);
|
||||
return false;
|
||||
}
|
||||
if (header.getSrcConnID() != state.getSendConnID()) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Bad Source Conn id " + header);
|
||||
// TODO could be a retransmitted Session Request,
|
||||
// tell establisher?
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// all good
|
||||
@@ -912,15 +915,21 @@ class PacketHandler {
|
||||
// decrypt header
|
||||
byte[] k1 = state.getRcvHeaderEncryptKey1();
|
||||
byte[] k2 = state.getRcvHeaderEncryptKey2();
|
||||
SSU2Header.Header header = SSU2Header.trialDecryptHandshakeHeader(packet, k1, k2);
|
||||
if (header != null) {
|
||||
// dest conn ID decrypts the same for both Session Created
|
||||
// and Retry, so we can bail out now if it doesn't match
|
||||
if (header.getDestConnID() != state.getRcvConnID()) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Bad Dest Conn id " + header);
|
||||
return false;
|
||||
SSU2Header.Header header;
|
||||
if (k2 != null) {
|
||||
header = SSU2Header.trialDecryptHandshakeHeader(packet, k1, k2);
|
||||
if (header != null) {
|
||||
// dest conn ID decrypts the same for both Session Created
|
||||
// and Retry, so we can bail out now if it doesn't match
|
||||
if (header.getDestConnID() != state.getRcvConnID()) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Bad Dest Conn id " + header);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we have only sent a Token Request
|
||||
header = null;
|
||||
}
|
||||
int type;
|
||||
if (header == null ||
|
||||
|
||||
@@ -185,8 +185,8 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
|
||||
byte[] data = dpacket.getData();
|
||||
int off = dpacket.getOffset();
|
||||
int len = dpacket.getLength();
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Packet before header decryption:\n" + HexDump.dump(data, off, len));
|
||||
//if (_log.shouldDebug())
|
||||
// _log.debug("Packet before header decryption:\n" + HexDump.dump(data, off, len));
|
||||
try {
|
||||
if (len < MIN_DATA_LEN) {
|
||||
if (_log.shouldWarn())
|
||||
@@ -199,11 +199,6 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
|
||||
_log.warn("bad data header on " + this);
|
||||
return;
|
||||
}
|
||||
if (header.getDestConnID() != _rcvConnID) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("bad Dest Conn id " + header.getDestConnID() + " on " + this);
|
||||
return;
|
||||
}
|
||||
if (header.getType() != DATA_FLAG_BYTE) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("bad data pkt type " + (header.getType() & 0xff) + " on " + this);
|
||||
@@ -217,16 +212,21 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
|
||||
// we didn't know the session has disconnected yet.
|
||||
return;
|
||||
}
|
||||
if (header.getDestConnID() != _rcvConnID) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("bad Dest Conn id " + header.getDestConnID() + " on " + this);
|
||||
return;
|
||||
}
|
||||
long n = header.getPacketNumber();
|
||||
SSU2Header.acceptTrialDecrypt(packet, header);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Packet " + n + " after header decryption:\n" + HexDump.dump(data, off, len));
|
||||
//if (_log.shouldDebug())
|
||||
// _log.debug("Packet " + n + " after header decryption:\n" + HexDump.dump(data, off, len));
|
||||
synchronized (_rcvCha) {
|
||||
_rcvCha.setNonce(n);
|
||||
// decrypt in-place
|
||||
_rcvCha.decryptWithAd(header.data, data, off + SHORT_HEADER_SIZE, data, off + SHORT_HEADER_SIZE, len - SHORT_HEADER_SIZE);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Packet " + n + " after full decryption:\n" + HexDump.dump(data, off, len - MAC_LEN));
|
||||
//if (_log.shouldDebug())
|
||||
// _log.debug("Packet " + n + " after full decryption:\n" + HexDump.dump(data, off, len - MAC_LEN));
|
||||
if (_receivedMessages.set(n)) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dup pkt rcvd " + n + " on " + this);
|
||||
@@ -244,8 +244,6 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Bad encrypted packet:\n" + HexDump.dump(data, off, len), ioobe);
|
||||
} finally {
|
||||
packet.release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,10 +190,12 @@ final class SSU2Header {
|
||||
public String toString() {
|
||||
if (data.length >= SESSION_HEADER_SIZE) {
|
||||
return "Handshake header destID " + getDestConnID() + " pkt num " + getPacketNumber() + " type " + getType() +
|
||||
" version " + getVersion() + " netID " + getNetID() +
|
||||
" srcID " + getSrcConnID() + " token " + getToken() + " key " + Base64.encode(getEphemeralKey());
|
||||
}
|
||||
if (data.length >= LONG_HEADER_SIZE) {
|
||||
return "Long header destID " + getDestConnID() + " pkt num " + getPacketNumber() + " type " + getType() +
|
||||
" version " + getVersion() + " netID " + getNetID() +
|
||||
" srcID " + getSrcConnID() + " token " + getToken();
|
||||
}
|
||||
return "Short header destID " + getDestConnID() + " pkt num " + getPacketNumber() + " type " + getType();
|
||||
|
||||
Reference in New Issue
Block a user