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:
zzz
2022-03-06 06:15:23 -05:00
parent 9457271ce6
commit a6f61d2bf6
9 changed files with 235 additions and 107 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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