SSU: Add SSU2 class extensions and packet builder

Pass XDH key builder to UDPTransport
Add SSU2 static keygen when enabled
WIP, not hooked in
This commit is contained in:
zzz
2022-02-24 06:13:28 -05:00
parent b8407a261e
commit f4be99ecd0
6 changed files with 2063 additions and 2 deletions

View File

@ -83,6 +83,10 @@ public class TransportManager implements TransportEventListener {
/** default true */
public final static String PROP_ENABLE_UDP = "i2np.udp.enable";
/**
* @since 0.9.54
*/
public final static String PROP_ENABLE_SSU2 = "i2np.ssu2.enable";
/** default true */
public final static String PROP_ENABLE_NTCP = "i2np.ntcp.enable";
/** default true */
@ -255,7 +259,8 @@ public class TransportManager implements TransportEventListener {
private void configTransports() {
Transport udp = null;
if (_enableUDP) {
udp = new UDPTransport(_context, _dhThread);
X25519KeyFactory xdh = _context.getBooleanProperty(PROP_ENABLE_SSU2) ? _xdhThread : null;
udp = new UDPTransport(_context, _dhThread, _xdhThread);
addTransport(udp);
initializeAddress(udp);
}

View File

@ -0,0 +1,398 @@
package net.i2p.router.transport.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.GeneralSecurityException;
import java.util.List;
import com.southernstorm.noise.protocol.CipherState;
import com.southernstorm.noise.protocol.CipherStatePair;
import com.southernstorm.noise.protocol.HandshakeState;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.util.Addresses;
import net.i2p.util.Log;
/**
* Data for a new connection being established, where the remote peer has
* initiated the connection with us. In other words, they are Alice and
* we are Bob.
*
* SSU2 only.
*
* @since 0.9.54
*/
class InboundEstablishState2 extends InboundEstablishState implements SSU2Payload.PayloadCallback {
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;
/**
* @param localPort Must be our external port, otherwise the signature of the
* SessionCreated message will be bad if the external port != the internal port.
* @param packet with all header encryption removed
*/
public InboundEstablishState2(RouterContext ctx, UDPTransport transport,
UDPPacket packet) throws GeneralSecurityException {
super(ctx, (InetSocketAddress) packet.getPacket().getSocketAddress());
DatagramPacket pkt = packet.getPacket();
_aliceSocketAddress = (InetSocketAddress) pkt.getSocketAddress();
_handshakeState = new HandshakeState(HandshakeState.PATTERN_ID_XK_SSU2, HandshakeState.RESPONDER, transport.getXDHFactory());
_handshakeState.getLocalKeyPair().setKeys(transport.getSSU2StaticPrivKey(), 0,
transport.getSSU2StaticPubKey(), 0);
byte[] introKey = transport.getSSU2StaticIntroKey();
_sendHeaderEncryptKey1 = introKey;
_rcvHeaderEncryptKey1 = introKey;
//_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();
// 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)
throw new GeneralSecurityException("Identical Conn IDs");
int type = data[off + 12] & 0xff;
long token = DataHelper.fromLong8(data, off + 24);
if (type == 10) {
_currentState = InboundState.IB_STATE_TOKEN_REQUEST_RECEIVED;
// TODO decrypt chacha?
_sendHeaderEncryptKey2 = introKey;
do {
token = ctx.random().nextLong();
} while (token == 0);
_token = token;
} else if (type == 0 && token == 0) { // || token not valid
_currentState = InboundState.IB_STATE_REQUEST_BAD_TOKEN_RECEIVED;
_sendHeaderEncryptKey2 = introKey;
do {
token = ctx.random().nextLong();
} while (token == 0);
_token = token;
} else {
// probably don't need again
_token = token;
_handshakeState.start();
if (_log.shouldDebug())
_log.debug("State after start: " + _handshakeState);
_handshakeState.mixHash(data, off, 32);
if (_log.shouldDebug())
_log.debug("State after mixHash 1: " + _handshakeState);
byte[] payload = new byte[len - 80]; // 32 hdr, 32 eph. key, 16 MAC
try {
_handshakeState.readMessage(data, off + 32, len - 32, payload, 0);
} catch (GeneralSecurityException gse) {
if (_log.shouldDebug())
_log.debug("Session request error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse);
throw gse;
}
if (_log.shouldDebug())
_log.debug("State after sess req: " + _handshakeState);
processPayload(payload, payload.length, true);
_sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader");
_currentState = InboundState.IB_STATE_REQUEST_RECEIVED;
}
_nextToken = ctx.random().nextLong();
packetReceived();
}
@Override
public int getVersion() { return 2; }
private void processPayload(byte[] payload, int length, boolean isHandshake) throws GeneralSecurityException {
try {
int blocks = SSU2Payload.processPayload(_context, this, payload, 0, length, isHandshake);
System.out.println("Processed " + blocks + " blocks");
} catch (Exception e) {
_log.error("IES2 payload error\n" + net.i2p.util.HexDump.dump(payload, 0, length));
throw new GeneralSecurityException("IES2 payload error", e);
}
}
/////////////////////////////////////////////////////////
// begin payload callbacks
/////////////////////////////////////////////////////////
public void gotDateTime(long time) {
System.out.println("Got DATE block: " + DataHelper.formatTime(time));
}
public void gotOptions(byte[] options, boolean isHandshake) {
System.out.println("Got OPTIONS block");
}
public void gotRI(RouterInfo ri, boolean isHandshake, boolean flood) throws DataFormatException {
System.out.println("Got RI block: " + ri);
if (isHandshake)
throw new DataFormatException("RI in Sess Req");
List<RouterAddress> addrs = ri.getTargetAddresses("SSU", "SSU2");
RouterAddress ra = null;
for (RouterAddress addr : addrs) {
// skip NTCP w/o "s"
if (addrs.size() > 1 && addr.getTransportStyle().equals("SSU") && addr.getOption("s") == null)
continue;
ra = addr;
break;
}
if (ra == null)
throw new DataFormatException("no SSU2 addr");
String siv = ra.getOption("i");
if (siv == null)
throw new DataFormatException("no SSU2 IKey");
byte[] ik = Base64.decode(siv);
if (ik == null)
throw new DataFormatException("bad SSU2 IKey");
if (ik.length != 32)
throw new DataFormatException("bad SSU2 IKey len");
String ss = ra.getOption("s");
if (ss == null)
throw new DataFormatException("no SSU2 S");
byte[] s = Base64.decode(ss);
if (s == null)
throw new DataFormatException("bad SSU2 S");
if (s.length != 32)
throw new DataFormatException("bad SSU2 S len");
if (!"2".equals(ra.getOption("v")))
throw new DataFormatException("bad SSU2 v");
_sendHeaderEncryptKey1 = ik;
//_sendHeaderEncryptKey2 calculated below
}
public void gotRIFragment(byte[] data, boolean isHandshake, boolean flood, boolean isGzipped, int frag, int totalFrags) {
System.out.println("Got RI fragment " + frag + " of " + totalFrags);
if (isHandshake)
throw new IllegalStateException("RI in Sess Req");
}
public void gotAddress(byte[] ip, int port) {
System.out.println("Got ADDRESS block: " + Addresses.toString(ip, port));
throw new IllegalStateException("Address in Handshake");
}
public void gotIntroKey(byte[] key) {
System.out.println("Got Intro key: " + Base64.encode(key));
}
public void gotRelayTagRequest() {
System.out.println("Got relay tag request");
}
public void gotRelayTag(long tag) {
System.out.println("Got relay tag " + tag);
throw new IllegalStateException("Relay tag in Handshake");
}
public void gotToken(long token, long expires) {
System.out.println("Got NEW TOKEN block " + token + " expires " + DataHelper.formatTime(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");
}
public void gotFragment(byte[] data, long messageID, int type, long expires, 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");
}
public void gotACK(long ackThru, int acks, byte[] ranges) {
System.out.println("Got ACK block: " + ackThru);
throw new IllegalStateException("ACK in Handshake");
}
public void gotTermination(int reason, long count) {
System.out.println("Got TERMINATION block, reason: " + reason + " count: " + count);
throw new IllegalStateException("Termination in Handshake");
}
public void gotUnknown(int type, int len) {
System.out.println("Got UNKNOWN block, type: " + type + " len: " + len);
}
public void gotPadding(int paddingLength, int frameLength) {
System.out.println("Got PADDING block, len: " + paddingLength + " in frame len: " + frameLength);
}
/////////////////////////////////////////////////////////
// end payload callbacks
/////////////////////////////////////////////////////////
public long getSendConnID() { return _sendConnID; }
public long getRcvConnID() { return _rcvConnID; }
public long getToken() { return _token; }
public long getNextToken() { return _nextToken; }
public HandshakeState getHandshakeState() { return _handshakeState; }
public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; }
public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; }
public byte[] getSendHeaderEncryptKey2() { return _sendHeaderEncryptKey2; }
public byte[] getRcvHeaderEncryptKey2() { return _rcvHeaderEncryptKey2; }
public InetSocketAddress getSentAddress() { return _aliceSocketAddress; }
@Override
public synchronized void createdPacketSent() {
/// todo state check
if (_rcvHeaderEncryptKey2 == null)
_rcvHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessionConfirmed");
_lastSend = _context.clock().now();
long delay;
if (_createdSentCount == 0) {
delay = RETRANSMIT_DELAY;
} else {
delay = Math.min(RETRANSMIT_DELAY << _createdSentCount, MAX_DELAY);
}
_createdSentCount++;
_nextSend = _lastSend + delay;
if ( (_currentState == InboundState.IB_STATE_UNKNOWN) || (_currentState == InboundState.IB_STATE_REQUEST_RECEIVED) )
_currentState = InboundState.IB_STATE_CREATED_SENT;
}
/** note that we just sent a Retry packet */
public synchronized void retryPacketSent() {
if (_currentState != InboundState.IB_STATE_REQUEST_BAD_TOKEN_RECEIVED &&
_currentState != InboundState.IB_STATE_TOKEN_REQUEST_RECEIVED)
throw new IllegalStateException("Bad state for Retry Sent: " + _currentState);
_currentState = InboundState.IB_STATE_RETRY_SENT;
}
/**
*
*/
public synchronized void receiveSessionRequestAfterRetry(UDPPacket packet) throws GeneralSecurityException {
if (_currentState != InboundState.IB_STATE_RETRY_SENT)
throw new GeneralSecurityException("Bad state for Session Request after Retry: " + _currentState);
DatagramPacket pkt = packet.getPacket();
SocketAddress from = pkt.getSocketAddress();
if (!from.equals(_aliceSocketAddress))
throw new GeneralSecurityException("Address mismatch: req: " + _aliceSocketAddress + " 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 + 16);
if (sid != _sendConnID)
throw new GeneralSecurityException("Conn ID mismatch: 1: " + _sendConnID + " 2: " + sid);
long token = DataHelper.fromLong8(data, off + 24);
if (token != _token)
throw new GeneralSecurityException("Token mismatch: 1: " + _token + " 2: " + token);
_handshakeState.start();
_handshakeState.mixHash(data, off, 32);
if (_log.shouldDebug())
_log.debug("State after mixHash 1: " + _handshakeState);
byte[] payload = new byte[len - 80]; // 16 hdr, 32 static key, 16 MAC, 16 MAC
try {
_handshakeState.readMessage(data, off + 32, len - 32, payload, 0);
} catch (GeneralSecurityException gse) {
if (_log.shouldDebug())
_log.debug("Session Request error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse);
throw gse;
}
if (_log.shouldDebug())
_log.debug("State after sess req: " + _handshakeState);
processPayload(payload, payload.length, true);
_sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader");
_currentState = InboundState.IB_STATE_REQUEST_RECEIVED;
if (_createdSentCount == 1) {
_rtt = (int) ( _context.clock().now() - _lastSend );
}
packetReceived();
}
/**
*
*
*
*/
public synchronized void receiveSessionConfirmed(UDPPacket packet) throws GeneralSecurityException {
if (_currentState != InboundState.IB_STATE_CREATED_SENT)
throw new GeneralSecurityException("Bad state for Session Confirmed: " + _currentState);
DatagramPacket pkt = packet.getPacket();
SocketAddress from = pkt.getSocketAddress();
if (!from.equals(_aliceSocketAddress))
throw new GeneralSecurityException("Address mismatch: req: " + _aliceSocketAddress + " 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: req: " + _rcvConnID + " conf: " + rid);
_handshakeState.mixHash(data, off, 16);
if (_log.shouldDebug())
_log.debug("State after mixHash 3: " + _handshakeState);
byte[] payload = new byte[len - 80]; // 16 hdr, 32 static key, 16 MAC, 16 MAC
try {
_handshakeState.readMessage(data, off + 16, len - 16, payload, 0);
} catch (GeneralSecurityException gse) {
if (_log.shouldDebug())
_log.debug("Session Confirmed error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse);
throw gse;
}
if (_log.shouldDebug())
_log.debug("State after sess conf: " + _handshakeState);
processPayload(payload, payload.length, false);
// TODO split, calculate keys
// TODO fix state
if ( (_currentState == InboundState.IB_STATE_UNKNOWN) ||
(_currentState == InboundState.IB_STATE_REQUEST_RECEIVED) ||
(_currentState == InboundState.IB_STATE_CREATED_SENT) ) {
if (confirmedFullyReceived())
_currentState = InboundState.IB_STATE_CONFIRMED_COMPLETELY;
else
_currentState = InboundState.IB_STATE_CONFIRMED_PARTIALLY;
}
if (_createdSentCount == 1) {
_rtt = (int) ( _context.clock().now() - _lastSend );
}
packetReceived();
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder(128);
buf.append("IES2 ");
buf.append(Addresses.toString(_aliceIP, _alicePort));
buf.append(" RelayTag: ").append(_sentRelayTag);
buf.append(' ').append(_currentState);
return buf.toString();
}
}

View File

@ -0,0 +1,288 @@
package net.i2p.router.transport.udp;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import com.southernstorm.noise.protocol.CipherState;
import com.southernstorm.noise.protocol.CipherStatePair;
import com.southernstorm.noise.protocol.HandshakeState;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.util.Addresses;
import net.i2p.util.Log;
/**
* Data for a new connection being established, where we initiated the
* connection with a remote peer. In other words, we are Alice and
* they are Bob.
*
* SSU2 only.
*
* @since 0.9.54
*/
class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payload.PayloadCallback {
private InetSocketAddress _bobSocketAddress;
private final UDPTransport _transport;
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;
private byte[] _sendHeaderEncryptKey2;
private byte[] _rcvHeaderEncryptKey2;
private final byte[] _rcvRetryHeaderEncryptKey2;
private int _mtu;
private static final boolean SET_TOKEN = false;
/**
* @param claimedAddress an IP/port based RemoteHostId, or null if unknown
* @param remoteHostId non-null, == claimedAddress if direct, or a hash-based one if indirect
* @param remotePeer must have supported sig type
* @param needIntroduction should we ask Bob to be an introducer for us?
ignored unless allowExtendedOptions is true
* @param introKey Bob's introduction key, as published in the netdb
* @param addr non-null
*/
public OutboundEstablishState2(RouterContext ctx, UDPTransport transport, RemoteHostId claimedAddress,
RemoteHostId remoteHostId, int mtu,
RouterIdentity remotePeer, byte[] publicKey,
boolean needIntroduction,
SessionKey introKey, UDPAddress addr) {
super(ctx, claimedAddress, remoteHostId, remotePeer, needIntroduction, introKey, addr);
_transport = transport;
if (claimedAddress != null) {
try {
_bobSocketAddress = new InetSocketAddress(InetAddress.getByAddress(_bobIP), _bobPort);
} catch (UnknownHostException uhe) {
throw new IllegalArgumentException("bad IP", uhe);
}
_mtu = mtu;
} else {
_mtu = PeerState.MIN_IPV6_MTU;
}
if (addr.getIntroducerCount() > 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("new outbound establish to " + remotePeer.calculateHash() + ", with address: " + addr);
_currentState = OutboundState.OB_STATE_PENDING_INTRO;
} else {
_currentState = OutboundState.OB_STATE_UNKNOWN;
}
// SSU2
createNewState(publicKey);
_sendConnID = ctx.random().nextLong();
// rcid == scid is not allowed
long rcid;
do {
rcid = ctx.random().nextLong();
} while (_sendConnID == rcid);
if (SET_TOKEN) {
do {
_token = ctx.random().nextLong();
} while (_token == 0);
}
_rcvConnID = rcid;
_nextToken = ctx.random().nextLong();
byte[] ik = introKey.getData();
_sendHeaderEncryptKey1 = ik;
_rcvHeaderEncryptKey1 = ik;
_sendHeaderEncryptKey2 = ik;
//_rcvHeaderEncryptKey2 will be set after the Session Request message is created
_rcvRetryHeaderEncryptKey2 = ik;
}
private void createNewState(byte[] publicKey) {
try {
_handshakeState = new HandshakeState(HandshakeState.PATTERN_ID_XK_SSU2, HandshakeState.INITIATOR, _transport.getXDHFactory());
} catch (GeneralSecurityException gse) {
throw new IllegalStateException("bad proto", gse);
}
_handshakeState.getRemotePublicKey().setPublicKey(publicKey, 0);
_handshakeState.getLocalKeyPair().setKeys(_transport.getSSU2StaticPrivKey(), 0,
_transport.getSSU2StaticPubKey(), 0);
}
public synchronized void restart(long token) {
_token = token;
HandshakeState old = _handshakeState;
byte[] pub = new byte[32];
old.getRemotePublicKey().getPublicKey(pub, 0);
createNewState(pub);
old.destroy();
//_rcvHeaderEncryptKey2 will be set after the Session Request message is created
_rcvHeaderEncryptKey2 = null;
}
private void processPayload(byte[] payload, int length, boolean isHandshake) throws GeneralSecurityException {
try {
int blocks = SSU2Payload.processPayload(_context, this, payload, 0, length, isHandshake);
System.out.println("Processed " + blocks + " blocks");
} catch (Exception e) {
throw new GeneralSecurityException("Session Created payload error", e);
}
}
/////////////////////////////////////////////////////////
// begin payload callbacks
/////////////////////////////////////////////////////////
public void gotDateTime(long time) {
System.out.println("Got DATE block: " + DataHelper.formatTime(time));
}
public void gotOptions(byte[] options, boolean isHandshake) {
System.out.println("Got OPTIONS block");
}
public void gotRI(RouterInfo ri, boolean isHandshake, boolean flood) throws DataFormatException {
System.out.println("Got RI block: " + ri);
throw new DataFormatException("RI in Sess Created");
}
public void gotRIFragment(byte[] data, boolean isHandshake, boolean flood, boolean isGzipped, int frag, int totalFrags) {
System.out.println("Got RI fragment " + frag + " of " + totalFrags);
throw new IllegalStateException("RI in Sess Created");
}
public void gotAddress(byte[] ip, int port) {
System.out.println("Got ADDRESS block: " + Addresses.toString(ip, port));
}
public void gotIntroKey(byte[] key) {
System.out.println("Got Intro key: " + Base64.encode(key));
}
public void gotRelayTagRequest() {
System.out.println("Got relay tag request");
throw new IllegalStateException("Relay tag req in Sess Created");
}
public void gotRelayTag(long tag) {
System.out.println("Got relay tag " + tag);
}
public void gotToken(long token, long expires) {
System.out.println("Got NEW TOKEN block " + token + " expires " + DataHelper.formatTime(expires));
}
public void gotI2NP(I2NPMessage msg) {
System.out.println("Got I2NP block: " + msg);
throw new IllegalStateException("I2NP in Sess Created");
}
public void gotFragment(byte[] data, long messageID, int type, long expires, int frag, boolean isLast) throws DataFormatException {
System.out.println("Got FRAGMENT block: " + messageID);
throw new IllegalStateException("I2NP in Sess Created");
}
public void gotACK(long ackThru, int acks, byte[] ranges) {
System.out.println("Got ACK block: " + ackThru);
throw new IllegalStateException("ACK in Sess Created");
}
public void gotTermination(int reason, long count) {
System.out.println("Got TERMINATION block, reason: " + reason + " count: " + count);
throw new IllegalStateException("Termination in Sess Created");
}
public void gotUnknown(int type, int len) {
System.out.println("Got UNKNOWN block, type: " + type + " len: " + len);
}
public void gotPadding(int paddingLength, int frameLength) {
System.out.println("Got PADDING block, len: " + paddingLength + " in frame len: " + frameLength);
}
/////////////////////////////////////////////////////////
// end payload callbacks
/////////////////////////////////////////////////////////
public long getSendConnID() { return _sendConnID; }
public long getRcvConnID() { return _rcvConnID; }
public long getToken() { return _token; }
public long getNextToken() { return _nextToken; }
public HandshakeState getHandshakeState() { return _handshakeState; }
public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; }
public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; }
public byte[] getSendHeaderEncryptKey2() { return _sendHeaderEncryptKey2; }
public byte[] getRcvHeaderEncryptKey2() { return _rcvHeaderEncryptKey2; }
public byte[] getRcvRetryHeaderEncryptKey2() { return _rcvRetryHeaderEncryptKey2; }
public InetSocketAddress getSentAddress() { return _bobSocketAddress; }
/** what is the largest packet we can send to the peer? */
public int getMTU() { return _mtu; }
public synchronized void receiveSessionCreated(UDPPacket packet) throws GeneralSecurityException {
////// todo fix state check
if (_currentState == OutboundState.OB_STATE_VALIDATION_FAILED) {
if (_log.shouldLog(Log.WARN))
_log.warn("Session created already failed");
return;
}
DatagramPacket pkt = packet.getPacket();
SocketAddress from = pkt.getSocketAddress();
if (!from.equals(_bobSocketAddress))
throw new GeneralSecurityException("Address mismatch: req: " + _bobSocketAddress + " created: " + from);
int off = pkt.getOffset();
int len = pkt.getLength();
byte data[] = pkt.getData();
_handshakeState.mixHash(data, off, 32);
if (_log.shouldDebug())
_log.debug("State after mixHash 2: " + _handshakeState);
byte[] payload = new byte[len - 80]; // 32 hdr, 32 eph. key, 16 MAC
try {
_handshakeState.readMessage(data, off + 32, len - 32, payload, 0);
} catch (GeneralSecurityException gse) {
if (_log.shouldDebug())
_log.debug("Session create error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse);
throw gse;
}
if (_log.shouldDebug())
_log.debug("State after sess cr: " + _handshakeState);
processPayload(payload, payload.length, true);
_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;
if (_requestSentCount == 1) {
_rtt = (int) (_context.clock().now() - _requestSentTime);
}
packetReceived();
}
/**
* note that we just sent the SessionRequest packet
*/
@Override
public synchronized void requestSent() {
/// TODO store pkt for retx
if (_rcvHeaderEncryptKey2 == null)
_rcvHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader");
super.requestSent();
}
@Override
public String toString() {
return "OES2 " + _remoteHostId + ' ' + _currentState;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,68 @@
package net.i2p.router.transport.udp;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
/**
* Contain all of the state about a UDP connection to a peer.
* This is instantiated only after a connection is fully established.
*
* Public only for UI peers page. Not a public API, not for external use.
*
* SSU2 only.
*
* @since 0.9.54
*/
public class PeerState2 extends PeerState {
private final long _sendConnID;
private final long _rcvConnID;
private final AtomicInteger _packetNumber = new AtomicInteger();
private final byte[] _sendEncryptKey;
private final byte[] _rcvEncryptKey;
private final byte[] _sendHeaderEncryptKey1;
private final byte[] _rcvHeaderEncryptKey1;
private final byte[] _sendHeaderEncryptKey2;
private final byte[] _rcvHeaderEncryptKey2;
private final SSU2Bitfield _receivedMessages;
public static final int MIN_MTU = 1280;
/**
* @param rtt from the EstablishState, or 0 if not available
*/
public PeerState2(RouterContext ctx, UDPTransport transport,
InetSocketAddress remoteAddress, Hash remotePeer, boolean isInbound, int rtt,
byte[] sendKey, byte[] rcvKey, long sendID, long rcvID,
byte[] sendHdrKey1, byte[] sendHdrKey2, byte[] rcvHdrKey2) {
super(ctx, transport, remoteAddress, remotePeer, isInbound, rtt);
_sendConnID = sendID;
_rcvConnID = rcvID;
_sendEncryptKey = sendKey;
_rcvEncryptKey = rcvKey;
_sendHeaderEncryptKey1 = sendHdrKey1;
_rcvHeaderEncryptKey1 = transport.getSSU2StaticIntroKey();
_sendHeaderEncryptKey2 = sendHdrKey2;
_rcvHeaderEncryptKey2 = rcvHdrKey2;
_receivedMessages = new SSU2Bitfield(256, 0);
}
// SSU2
long getNextPacketNumber() { return _packetNumber.incrementAndGet(); }
public long getSendConnID() { return _sendConnID; }
public long getRcvConnID() { return _rcvConnID; }
public byte[] getSendEncryptKey() { return _sendEncryptKey; }
public byte[] getRcvEncryptKey() { return _rcvEncryptKey; }
public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; }
public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; }
public byte[] getSendHeaderEncryptKey2() { return _sendHeaderEncryptKey2; }
public byte[] getRcvHeaderEncryptKey2() { return _rcvHeaderEncryptKey2; }
public SSU2Bitfield getReceivedMessages() { return _receivedMessages; }
}

View File

@ -21,7 +21,9 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import net.i2p.CoreVersion;
import net.i2p.crypto.EncType;
import net.i2p.crypto.HMACGenerator;
import net.i2p.crypto.KeyPair;
import net.i2p.crypto.SigType;
import net.i2p.data.Base64;
import net.i2p.data.DatabaseEntry;
@ -30,6 +32,7 @@ import net.i2p.data.Hash;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.PrivateKey;
import net.i2p.data.SessionKey;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.I2NPMessage;
@ -46,6 +49,7 @@ import net.i2p.router.transport.TransportImpl;
import net.i2p.router.transport.TransportUtil;
import static net.i2p.router.transport.TransportUtil.IPv6Config.*;
import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
import net.i2p.router.transport.crypto.X25519KeyFactory;
import static net.i2p.router.transport.udp.PeerTestState.Role.*;
import net.i2p.router.util.EventLog;
import net.i2p.router.util.RandomIterator;
@ -132,6 +136,22 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
private RouterAddress _currentOurV4Address;
private RouterAddress _currentOurV6Address;
// SSU2
private final boolean _enableSSU1;
private final boolean _enableSSU2;
private final PacketBuilder2 _packetBuilder2;
private final X25519KeyFactory _xdhFactory;
private final byte[] _ssu2StaticPubKey;
private final byte[] _ssu2StaticPrivKey;
private final byte[] _ssu2StaticIntroKey;
private final String _ssu2B64StaticPubKey;
private final String _ssu2B64StaticIntroKey;
/** b64 static private key */
public static final String PROP_SSU2_SP = "i2np.ssu2.sp";
/** b64 static IV */
public static final String PROP_SSU2_IKEY = "i2np.ssu2.ikey";
private static final long MIN_DOWNTIME_TO_REKEY_HIDDEN = 24*60*60*1000L;
private static final int DROPLIST_PERIOD = 10*60*1000;
public static final String STYLE = "SSU";
public static final String PROP_INTERNAL_PORT = "i2np.udp.internalPort";
@ -295,10 +315,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
Status.IPV4_DISABLED_IPV6_OK);
public UDPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh) {
/**
* @param xdh non-null to enable SSU2
*/
public UDPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh, X25519KeyFactory xdh) {
super(ctx);
_networkID = ctx.router().getNetworkID();
_dhFactory = dh;
_xdhFactory = xdh;
_log = ctx.logManager().getLog(UDPTransport.class);
_peersByIdent = new ConcurrentHashMap<Hash, PeerState>(128);
_peersByRemoteHost = new ConcurrentHashMap<RemoteHostId, PeerState>(128);
@ -322,6 +346,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
}
_packetBuilder = new PacketBuilder(_context, this);
_packetBuilder2 = (xdh != null) ? new PacketBuilder2(_context, this) : null;
_fragments = new OutboundMessageFragments(_context, this, _activeThrottle);
_inboundFragments = new InboundMessageFragments(_context, _fragments, this);
//if (SHOULD_FLOOD_PEERS)
@ -363,6 +388,63 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
//_context.statManager().createRateStat("udp.packetAuthTimeSlow", "How long it takes to encrypt and MAC a packet for sending (when its slow)", "udp", RATES);
_context.simpleTimer2().addPeriodicEvent(new PingIntroducers(), MIN_EXPIRE_TIMEOUT * 3 / 4);
// SSU2 key and IV generation if required
_enableSSU1 = dh != null;
_enableSSU2 = xdh != null;
byte[] ikey = null;
String b64Ikey = null;
if (_enableSSU2) {
byte[] priv = null;
boolean shouldSave = false;
String s = null;
// try to determine if we've been down for 30 days or more
long minDowntime = _context.router().isHidden() ? MIN_DOWNTIME_TO_REKEY_HIDDEN : MIN_DOWNTIME_TO_REKEY;
boolean shouldRekey = _context.getEstimatedDowntime() >= minDowntime;
if (!shouldRekey) {
s = ctx.getProperty(PROP_SSU2_SP);
if (s != null) {
priv = Base64.decode(s);
}
}
if (priv == null || priv.length != SSU2Util.KEY_LEN) {
KeyPair keys = xdh.getKeys();
_ssu2StaticPrivKey = keys.getPrivate().getData();
_ssu2StaticPubKey = keys.getPublic().getData();
shouldSave = true;
} else {
_ssu2StaticPrivKey = priv;
_ssu2StaticPubKey = (new PrivateKey(EncType.ECIES_X25519, priv)).toPublic().getData();
}
if (!shouldSave) {
s = ctx.getProperty(PROP_SSU2_IKEY);
if (s != null) {
ikey = Base64.decode(s);
b64Ikey = s;
}
}
if (ikey == null || ikey.length != SSU2Util.INTRO_KEY_LEN) {
ikey = new byte[SSU2Util.INTRO_KEY_LEN];
do {
ctx.random().nextBytes(ikey);
} while (DataHelper.eq(ikey, 0, SSU2Util.ZEROKEY, 0, SSU2Util.INTRO_KEY_LEN));
shouldSave = true;
}
if (shouldSave) {
Map<String, String> changes = new HashMap<String, String>(2);
String b64Priv = Base64.encode(_ssu2StaticPrivKey);
b64Ikey = Base64.encode(ikey);
changes.put(PROP_SSU2_SP, b64Priv);
changes.put(PROP_SSU2_IKEY, b64Ikey);
ctx.router().saveConfig(changes, null);
}
} else {
_ssu2StaticPrivKey = null;
_ssu2StaticPubKey = null;
}
_ssu2StaticIntroKey = ikey;
_ssu2B64StaticIntroKey = b64Ikey;
_ssu2B64StaticPubKey = (_ssu2StaticPubKey != null) ? Base64.encode(_ssu2StaticPubKey) : null;
}
/**
@ -372,6 +454,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
OutboundMessageFragments getOMF() {
return _fragments;
}
/**
* Pick a port if not previously configured, so that TransportManager may
@ -779,6 +862,36 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
*/
SessionKey getIntroKey() { return _introKey; }
/**
* The static Intro key
*
* @return null if not configured for SSU2
* @since 0.9.54
*/
byte[] getSSU2StaticIntroKey() {
return _ssu2StaticIntroKey;
}
/**
* The static pub key
*
* @return null if not configured for SSU2
* @since 0.9.54
*/
byte[] getSSU2StaticPubKey() {
return _ssu2StaticPubKey;
}
/**
* The static priv key
*
* @return null if not configured for SSU2
* @since 0.9.54
*/
byte[] getSSU2StaticPrivKey() {
return _ssu2StaticPrivKey;
}
/**
* Published or requested port
*/
@ -3205,6 +3318,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
DHSessionKeyBuilder.Factory getDHFactory() {
return _dhFactory;
}
/**
* @return null if not configured for SSU2
* @since 0.9.54
*/
X25519KeyFactory getXDHFactory() {
return _xdhFactory;
}
/**
* @return the SSU HMAC
@ -3222,6 +3343,14 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
return _packetBuilder;
}
/**
* @return null if not configured for SSU2
* @since 0.9.54
*/
PacketBuilder2 getBuilder2() {
return _packetBuilder2;
}
/**
* Does nothing
* @deprecated as of 0.9.31