forked from I2P_Developers/i2p.i2p
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:
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
1173
router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
Normal file
1173
router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
Normal file
File diff suppressed because it is too large
Load Diff
68
router/java/src/net/i2p/router/transport/udp/PeerState2.java
Normal file
68
router/java/src/net/i2p/router/transport/udp/PeerState2.java
Normal 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; }
|
||||
|
||||
}
|
@ -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
|
||||
|
Reference in New Issue
Block a user