forked from I2P_Developers/i2p.i2p
NTCP: Remove NTCP 1 suppport
Remove individual 1/2 enable config Additional cleanup to follow
This commit is contained in:
@@ -94,12 +94,6 @@ public class TransportManager implements TransportEventListener {
|
||||
private static final String PROP_JAVA_PROXY3 = "http.proxyHost";
|
||||
private static final String PROP_JAVA_PROXY4 = "https.proxyHost";
|
||||
|
||||
/** default true */
|
||||
private static final String PROP_NTCP1_ENABLE = "i2np.ntcp1.enable";
|
||||
private static final boolean DEFAULT_NTCP1_ENABLE = false;
|
||||
private static final String PROP_NTCP2_ENABLE = "i2np.ntcp2.enable";
|
||||
private static final boolean DEFAULT_NTCP2_ENABLE = true;
|
||||
|
||||
private static final String PROP_ADVANCED = "routerconsole.advanced";
|
||||
|
||||
/** not forever, since they may update */
|
||||
@@ -125,10 +119,8 @@ public class TransportManager implements TransportEventListener {
|
||||
_upnpManager = enableUPnP ? new UPnPManager(context, this) : null;
|
||||
_upnpRefresher = enableUPnP ? new UPnPRefresher() : null;
|
||||
_enableUDP = _context.getBooleanPropertyDefaultTrue(PROP_ENABLE_UDP);
|
||||
_enableNTCP1 = isNTCPEnabled(context) &&
|
||||
context.getProperty(PROP_NTCP1_ENABLE, DEFAULT_NTCP1_ENABLE);
|
||||
boolean enableNTCP2 = isNTCPEnabled(context) &&
|
||||
context.getProperty(PROP_NTCP2_ENABLE, DEFAULT_NTCP2_ENABLE);
|
||||
_enableNTCP1 = false;
|
||||
boolean enableNTCP2 = isNTCPEnabled(context);
|
||||
_dhThread = (_enableUDP || enableNTCP2) ? new DHSessionKeyBuilder.PrecalcRunner(context) : null;
|
||||
// always created, even if NTCP2 is not enabled, because ratchet needs it
|
||||
_xdhThread = new X25519KeyFactory(context);
|
||||
@@ -268,10 +260,7 @@ public class TransportManager implements TransportEventListener {
|
||||
initializeAddress(udp);
|
||||
}
|
||||
if (isNTCPEnabled(_context)) {
|
||||
DHSessionKeyBuilder.PrecalcRunner dh = _enableNTCP1 ? _dhThread : null;
|
||||
boolean enableNTCP2 = _context.getProperty(PROP_NTCP2_ENABLE, DEFAULT_NTCP2_ENABLE);
|
||||
X25519KeyFactory xdh = enableNTCP2 ? _xdhThread : null;
|
||||
Transport ntcp = new NTCPTransport(_context, dh, xdh);
|
||||
Transport ntcp = new NTCPTransport(_context, null, _xdhThread);
|
||||
addTransport(ntcp);
|
||||
initializeAddress(ntcp);
|
||||
if (udp != null) {
|
||||
@@ -292,9 +281,7 @@ public class TransportManager implements TransportEventListener {
|
||||
}
|
||||
|
||||
public static boolean isNTCPEnabled(RouterContext ctx) {
|
||||
return ctx.getBooleanPropertyDefaultTrue(PROP_ENABLE_NTCP) &&
|
||||
(ctx.getProperty(PROP_NTCP1_ENABLE, DEFAULT_NTCP1_ENABLE) ||
|
||||
ctx.getProperty(PROP_NTCP2_ENABLE, DEFAULT_NTCP2_ENABLE));
|
||||
return ctx.getBooleanPropertyDefaultTrue(PROP_ENABLE_NTCP);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -164,8 +164,8 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
|
||||
_log.warn("Short buffer got " + remaining + " total now " + _received + " on " + this);
|
||||
return;
|
||||
}
|
||||
if (remaining + _received < NTCP1_MSG1_SIZE ||
|
||||
!_transport.isNTCP1Enabled()) {
|
||||
//if (remaining + _received < NTCP1_MSG1_SIZE ||
|
||||
// !_transport.isNTCP1Enabled()) {
|
||||
// Less than 288 total received, assume NTCP2
|
||||
// TODO can't change our mind later if we get more than 287
|
||||
_con.setVersion(2);
|
||||
@@ -173,343 +173,8 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
|
||||
receiveInboundNTCP2(src);
|
||||
// releaseBufs() will return the unused DH
|
||||
return;
|
||||
}
|
||||
//}
|
||||
}
|
||||
int toGet = Math.min(remaining, XY_SIZE - _received);
|
||||
src.get(_X, _received, toGet);
|
||||
_received += toGet;
|
||||
if (_received < XY_SIZE)
|
||||
return;
|
||||
changeState(State.IB_GOT_X);
|
||||
_received = 0;
|
||||
}
|
||||
|
||||
if (_state == State.IB_GOT_X && src.hasRemaining()) {
|
||||
int toGet = Math.min(src.remaining(), HXY_SIZE - _received);
|
||||
src.get(_hX_xor_bobIdentHash, _received, toGet);
|
||||
_received += toGet;
|
||||
if (_received < HXY_SIZE)
|
||||
return;
|
||||
changeState(State.IB_GOT_HX);
|
||||
_received = 0;
|
||||
}
|
||||
|
||||
if (_state == State.IB_GOT_HX) {
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"Enough data for a DH received");
|
||||
|
||||
// first verify that Alice knows who she is trying to talk with and that the X
|
||||
// isn't corrupt
|
||||
byte[] realXor = SimpleByteCache.acquire(HXY_SIZE);
|
||||
_context.sha().calculateHash(_X, 0, XY_SIZE, realXor, 0);
|
||||
xor32(_context.routerHash().getData(), realXor);
|
||||
if (!DataHelper.eq(realXor, _hX_xor_bobIdentHash)) {
|
||||
SimpleByteCache.release(realXor);
|
||||
_context.statManager().addRateData("ntcp.invalidHXxorBIH", 1);
|
||||
fail("Invalid hX_xor");
|
||||
return;
|
||||
}
|
||||
SimpleByteCache.release(realXor);
|
||||
if (!_transport.isHXHIValid(_hX_xor_bobIdentHash)) {
|
||||
// blocklist source? but spoofed IPs could DoS us
|
||||
_context.statManager().addRateData("ntcp.replayHXxorBIH", 1);
|
||||
fail("Replay hX_xor");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// ok, they're actually trying to talk to us, and we got their (unauthenticated) X
|
||||
_dh.setPeerPublicValue(_X);
|
||||
_dh.getSessionKey(); // force the calc
|
||||
System.arraycopy(_hX_xor_bobIdentHash, AES_SIZE, _prevEncrypted, 0, AES_SIZE);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"DH session key calculated (" + _dh.getSessionKey().toBase64() + ")");
|
||||
|
||||
// now prepare our response: Y+E(H(X+Y)+tsB+padding, sk, Y[239:255])
|
||||
byte xy[] = new byte[XY_SIZE + XY_SIZE];
|
||||
System.arraycopy(_X, 0, xy, 0, XY_SIZE);
|
||||
System.arraycopy(_Y, 0, xy, XY_SIZE, XY_SIZE);
|
||||
byte[] hxy = SimpleByteCache.acquire(HXY_SIZE);
|
||||
_context.sha().calculateHash(xy, 0, XY_SIZE + XY_SIZE, hxy, 0);
|
||||
// our (Bob's) timestamp in seconds
|
||||
_tsB = (_context.clock().now() + 500) / 1000l;
|
||||
byte toEncrypt[] = new byte[HXY_TSB_PAD_SIZE]; // 48
|
||||
System.arraycopy(hxy, 0, toEncrypt, 0, HXY_SIZE);
|
||||
byte tsB[] = DataHelper.toLong(4, _tsB);
|
||||
System.arraycopy(tsB, 0, toEncrypt, HXY_SIZE, tsB.length);
|
||||
_context.random().nextBytes(toEncrypt, HXY_SIZE + 4, 12);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(prefix()+"h(x+y)="+Base64.encode(hxy));
|
||||
_log.debug(prefix() + "tsb = " + _tsB);
|
||||
_log.debug(prefix()+"unencrypted H(X+Y)+tsB+padding: " + Base64.encode(toEncrypt));
|
||||
_log.debug(prefix()+"encryption iv= " + Base64.encode(_Y, XY_SIZE-AES_SIZE, AES_SIZE));
|
||||
_log.debug(prefix()+"encryption key= " + _dh.getSessionKey().toBase64());
|
||||
}
|
||||
SimpleByteCache.release(hxy);
|
||||
_context.aes().encrypt(toEncrypt, 0, _e_hXY_tsB, 0, _dh.getSessionKey(),
|
||||
_Y, XY_SIZE-AES_SIZE, HXY_TSB_PAD_SIZE);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"encrypted H(X+Y)+tsB+padding: " + Base64.encode(_e_hXY_tsB));
|
||||
byte write[] = new byte[XY_SIZE + HXY_TSB_PAD_SIZE];
|
||||
System.arraycopy(_Y, 0, write, 0, XY_SIZE);
|
||||
System.arraycopy(_e_hXY_tsB, 0, write, XY_SIZE, HXY_TSB_PAD_SIZE);
|
||||
|
||||
// ok, now that is prepared, we want to actually send it, so make sure we are up for writing
|
||||
changeState(State.IB_SENT_Y);
|
||||
_transport.getPumper().wantsWrite(_con, write);
|
||||
if (!src.hasRemaining()) return;
|
||||
} catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
|
||||
_context.statManager().addRateData("ntcp.invalidDH", 1);
|
||||
fail("Invalid X", e);
|
||||
return;
|
||||
} catch (IllegalStateException ise) {
|
||||
// setPeerPublicValue()
|
||||
fail("reused keys?", ise);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ok, we are onto the encrypted area, i.e. Message #3
|
||||
while (STATES_MSG3.contains(_state) && src.hasRemaining()) {
|
||||
|
||||
// Collect a 16-byte block
|
||||
if (_received < AES_SIZE && src.hasRemaining()) {
|
||||
int toGet = Math.min(src.remaining(), AES_SIZE - _received);
|
||||
src.get(_curEncrypted, _received, toGet);
|
||||
_received += toGet;
|
||||
if (_received < AES_SIZE) {
|
||||
// no more bytes available in the buffer, and only a partial
|
||||
// block was read, so we can't decrypt it.
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "end of available data with only a partial block read (" +
|
||||
+ _received + ")");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Decrypt the 16-byte block
|
||||
if (_received >= AES_SIZE) {
|
||||
_context.aes().decrypt(_curEncrypted, 0, _curDecrypted, 0, _dh.getSessionKey(),
|
||||
_prevEncrypted, 0, AES_SIZE);
|
||||
|
||||
byte swap[] = _prevEncrypted;
|
||||
_prevEncrypted = _curEncrypted;
|
||||
_curEncrypted = swap;
|
||||
_received = 0;
|
||||
|
||||
if (_state == State.IB_SENT_Y) { // we are on the first decrypted block
|
||||
int sz = (int)DataHelper.fromLong(_curDecrypted, 0, 2);
|
||||
if (sz < MIN_RI_SIZE || sz > MAX_RI_SIZE) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundSize", sz);
|
||||
fail("size is invalid", new Exception("size is " + sz));
|
||||
return;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "got the RI size: " + sz);
|
||||
_aliceIdentSize = sz;
|
||||
changeState(State.IB_GOT_RI_SIZE);
|
||||
|
||||
// We must defer the calculations for total size of the message until
|
||||
// we get the full alice ident so
|
||||
// we can determine how long the signature is.
|
||||
// See below
|
||||
|
||||
}
|
||||
try {
|
||||
_sz_aliceIdent_tsA_padding_aliceSig.write(_curDecrypted);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe);
|
||||
}
|
||||
|
||||
if (_state == State.IB_GOT_RI_SIZE &&
|
||||
_sz_aliceIdent_tsA_padding_aliceSig.size() >= 2 + _aliceIdentSize) {
|
||||
// we have enough to get Alice's RI and determine the sig+padding length
|
||||
readAliceRouterIdentity();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "got the RI");
|
||||
if (_aliceIdent == null) {
|
||||
// readAliceRouterIdentity already called fail
|
||||
return;
|
||||
}
|
||||
SigType type = _aliceIdent.getSigningPublicKey().getType();
|
||||
if (type == null) {
|
||||
fail("Unsupported sig type");
|
||||
return;
|
||||
}
|
||||
changeState(State.IB_GOT_RI);
|
||||
// handle variable signature size
|
||||
_sz_aliceIdent_tsA_padding_aliceSigSize = 2 + _aliceIdentSize + 4 + type.getSigLen();
|
||||
int rem = (_sz_aliceIdent_tsA_padding_aliceSigSize % AES_SIZE);
|
||||
int padding = 0;
|
||||
if (rem > 0)
|
||||
padding = AES_SIZE-rem;
|
||||
_sz_aliceIdent_tsA_padding_aliceSigSize += padding;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "alice ident size decrypted as " + _aliceIdentSize +
|
||||
", making the padding at " + padding + " and total size at " +
|
||||
_sz_aliceIdent_tsA_padding_aliceSigSize);
|
||||
}
|
||||
|
||||
if (_state == State.IB_GOT_RI &&
|
||||
_sz_aliceIdent_tsA_padding_aliceSig.size() >= _sz_aliceIdent_tsA_padding_aliceSigSize) {
|
||||
// we have the remainder of Message #3, i.e. the padding+signature
|
||||
// Time to verify.
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "got the sig");
|
||||
verifyInbound(src);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"verifying size (sz=" + _sz_aliceIdent_tsA_padding_aliceSig.size()
|
||||
+ " expected=" + _sz_aliceIdent_tsA_padding_aliceSigSize
|
||||
+ ' ' + _state
|
||||
+ ')');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for remaining data
|
||||
if (STATES_DONE.contains(_state) && src.hasRemaining()) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Received unexpected " + src.remaining() + " on " + this, new Exception());
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"done with the data, not yet complete or corrupt");
|
||||
}
|
||||
|
||||
/**
|
||||
* We are Bob. We have received enough of message #3 from Alice
|
||||
* to get Alice's RouterIdentity.
|
||||
*
|
||||
* _aliceIdentSize must be set.
|
||||
* _sz_aliceIdent_tsA_padding_aliceSig must contain at least 2 + _aliceIdentSize bytes.
|
||||
*
|
||||
* Sets _aliceIdent so that we
|
||||
* may determine the signature and padding sizes.
|
||||
*
|
||||
* After all of message #3 is received including the signature and
|
||||
* padding, verifyIdentity() must be called.
|
||||
*
|
||||
* State must be IB_GOT_RI_SIZE.
|
||||
* Caller must synch.
|
||||
*
|
||||
* @since 0.9.16 pulled out of verifyInbound()
|
||||
*/
|
||||
private void readAliceRouterIdentity() {
|
||||
byte b[] = _sz_aliceIdent_tsA_padding_aliceSig.toByteArray();
|
||||
|
||||
try {
|
||||
int sz = _aliceIdentSize;
|
||||
if (sz < MIN_RI_SIZE || sz > MAX_RI_SIZE ||
|
||||
sz > b.length-2) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundSize", sz);
|
||||
fail("size is invalid", new Exception("size is " + sz));
|
||||
return;
|
||||
}
|
||||
RouterIdentity alice = new RouterIdentity();
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(b, 2, sz);
|
||||
alice.readBytes(bais);
|
||||
_aliceIdent = alice;
|
||||
} catch (IOException ioe) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundIOE", 1);
|
||||
fail("Error verifying peer", ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundDFE", 1);
|
||||
fail("Error verifying peer", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We are Bob. Verify message #3 from Alice, then send message #4 to Alice.
|
||||
* NTCP 1 only.
|
||||
*
|
||||
* _aliceIdentSize and _aliceIdent must be set.
|
||||
* _sz_aliceIdent_tsA_padding_aliceSig must contain at least
|
||||
* (2 + _aliceIdentSize + 4 + padding + sig) bytes.
|
||||
*
|
||||
* Sets _aliceIdent so that we
|
||||
*
|
||||
* readAliceRouterIdentity() must have been called previously
|
||||
*
|
||||
* Make sure the signatures are correct, and if they are, update the
|
||||
* NIOConnection with the session key / peer ident / clock skew / iv.
|
||||
* The NIOConnection itself is responsible for registering with the
|
||||
* transport
|
||||
*
|
||||
* State must be IB_GOT_RI.
|
||||
* This will always change the state to VERIFIED or CORRUPT.
|
||||
* Caller must synch.
|
||||
*
|
||||
* @param buf possibly containing "extra" data for data phase
|
||||
*/
|
||||
private void verifyInbound(ByteBuffer buf) {
|
||||
byte b[] = _sz_aliceIdent_tsA_padding_aliceSig.toByteArray();
|
||||
try {
|
||||
int sz = _aliceIdentSize;
|
||||
// her timestamp from message #3
|
||||
long tsA = DataHelper.fromLong(b, 2+sz, 4);
|
||||
// _tsB is when we sent message #2
|
||||
// Adjust backward by RTT/2
|
||||
long now = _context.clock().now();
|
||||
// rtt from sending #2 to receiving #3
|
||||
long rtt = now - _con.getCreated();
|
||||
_peerSkew = (now - (tsA * 1000) - (rtt / 2) + 500) / 1000;
|
||||
|
||||
ByteArrayStream baos = new ByteArrayStream(256 + 256 + 32 + 4 + 4);
|
||||
baos.write(_X);
|
||||
baos.write(_Y);
|
||||
baos.write(_context.routerHash().getData());
|
||||
baos.write(DataHelper.toLong(4, tsA));
|
||||
baos.write(DataHelper.toLong(4, _tsB));
|
||||
//baos.write(b, 2+sz+4, b.length-2-sz-4-Signature.SIGNATURE_BYTES);
|
||||
|
||||
byte toVerify[] = baos.toByteArray();
|
||||
|
||||
// handle variable signature size
|
||||
SigType type = _aliceIdent.getSigningPublicKey().getType();
|
||||
if (type == null) {
|
||||
fail("unsupported sig type");
|
||||
return;
|
||||
}
|
||||
byte s[] = new byte[type.getSigLen()];
|
||||
System.arraycopy(b, b.length-s.length, s, 0, s.length);
|
||||
Signature sig = new Signature(type, s);
|
||||
boolean ok = _context.dsa().verifySignature(sig, toVerify, _aliceIdent.getSigningPublicKey());
|
||||
Hash aliceHash = _aliceIdent.calculateHash();
|
||||
if (ok) {
|
||||
ok = verifyInbound(aliceHash);
|
||||
}
|
||||
if (ok) {
|
||||
_con.setRemotePeer(_aliceIdent);
|
||||
sendInboundConfirm(aliceHash, tsA);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"e_bobSig is " + _e_bobSig.length + " bytes long");
|
||||
byte iv[] = _curEncrypted; // reuse buf
|
||||
System.arraycopy(_e_bobSig, _e_bobSig.length-AES_SIZE, iv, 0, AES_SIZE);
|
||||
// this does not copy the IV, do not release to cache
|
||||
// We are Bob, she is Alice, clock skew is Alice-Bob
|
||||
// skew in seconds
|
||||
_con.finishInboundEstablishment(_dh.getSessionKey(), _peerSkew, iv, _prevEncrypted);
|
||||
changeState(State.VERIFIED);
|
||||
if (buf.hasRemaining()) {
|
||||
// process "extra" data
|
||||
// This is unlikely for inbound, as we must reply with message 4
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("extra data " + buf.remaining() + " on " + this);
|
||||
_con.recvEncryptedI2NP(buf);
|
||||
}
|
||||
releaseBufs(true);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(prefix()+"Verified remote peer as " + aliceHash);
|
||||
} else {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundSignature", 1);
|
||||
// verifyInbound(aliceHash) called fail()
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundIOE", 1);
|
||||
fail("Error verifying peer", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,45 +272,6 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* We are Bob. Send message #4 to Alice.
|
||||
*
|
||||
* State must be VERIFIED.
|
||||
* Caller must synch.
|
||||
*
|
||||
* @param h Alice's Hash
|
||||
*/
|
||||
private void sendInboundConfirm(Hash h, long tsA) {
|
||||
// send Alice E(S(X+Y+Alice.identHash+tsA+tsB), sk, prev)
|
||||
byte toSign[] = new byte[XY_SIZE + XY_SIZE + 32+4+4];
|
||||
int off = 0;
|
||||
System.arraycopy(_X, 0, toSign, off, XY_SIZE); off += XY_SIZE;
|
||||
System.arraycopy(_Y, 0, toSign, off, XY_SIZE); off += XY_SIZE;
|
||||
System.arraycopy(h.getData(), 0, toSign, off, 32); off += 32;
|
||||
DataHelper.toLong(toSign, off, 4, tsA); off += 4;
|
||||
DataHelper.toLong(toSign, off, 4, _tsB); off += 4;
|
||||
|
||||
// handle variable signature size
|
||||
Signature sig = _context.dsa().sign(toSign, _context.keyManager().getSigningPrivateKey());
|
||||
int siglen = sig.length();
|
||||
int rem = siglen % AES_SIZE;
|
||||
int padding;
|
||||
if (rem > 0)
|
||||
padding = AES_SIZE - rem;
|
||||
else
|
||||
padding = 0;
|
||||
byte preSig[] = new byte[siglen + padding];
|
||||
System.arraycopy(sig.getData(), 0, preSig, 0, siglen);
|
||||
if (padding > 0)
|
||||
_context.random().nextBytes(preSig, siglen, padding);
|
||||
_e_bobSig = new byte[preSig.length];
|
||||
_context.aes().encrypt(preSig, 0, _e_bobSig, 0, _dh.getSessionKey(), _e_hXY_tsB, HXY_TSB_PAD_SIZE - AES_SIZE, _e_bobSig.length);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "Sending encrypted inbound confirmation");
|
||||
_transport.getPumper().wantsWrite(_con, _e_bobSig);
|
||||
}
|
||||
|
||||
//// NTCP2 below here
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,26 +58,6 @@ import net.i2p.util.VersionComparator;
|
||||
*
|
||||
* Public only for UI peers page. Not a public API, not for external use.
|
||||
*
|
||||
* The NTCP transport sends individual I2NP messages AES/256/CBC encrypted with
|
||||
* a simple checksum. The unencrypted message is encoded as follows:
|
||||
*<pre>
|
||||
* +-------+-------+--//--+---//----+-------+-------+-------+-------+
|
||||
* | sizeof(data) | data | padding | adler checksum of sz+data+pad |
|
||||
* +-------+-------+--//--+---//----+-------+-------+-------+-------+
|
||||
*</pre>
|
||||
* That message is then encrypted with the DH/2048 negotiated session key
|
||||
* (station to station authenticated per the EstablishState class) using the
|
||||
* last 16 bytes of the previous encrypted message as the IV.
|
||||
*
|
||||
* One special case is a metadata message where the sizeof(data) is 0. In
|
||||
* that case, the unencrypted message is encoded as:
|
||||
*<pre>
|
||||
* +-------+-------+-------+-------+-------+-------+-------+-------+
|
||||
* | 0 | timestamp in seconds | uninterpreted
|
||||
* +-------+-------+-------+-------+-------+-------+-------+-------+
|
||||
* uninterpreted | adler checksum of sz+data+pad |
|
||||
* +-------+-------+-------+-------+-------+-------+-------+-------+
|
||||
*</pre>
|
||||
*
|
||||
*/
|
||||
public class NTCPConnection implements Closeable {
|
||||
@@ -249,8 +229,9 @@ public class NTCPConnection implements Closeable {
|
||||
this(ctx, transport, remAddr, false);
|
||||
_remotePeer = remotePeer;
|
||||
_version = version;
|
||||
if (version == 1) {
|
||||
_establishState = new OutboundEstablishState(ctx, transport, this);
|
||||
if (version != 2) {
|
||||
throw new IllegalArgumentException("bad version " + version);
|
||||
//_establishState = new OutboundEstablishState(ctx, transport, this);
|
||||
} else {
|
||||
try {
|
||||
_establishState = new OutboundNTCP2State(ctx, transport, this);
|
||||
@@ -329,58 +310,6 @@ public class NTCPConnection implements Closeable {
|
||||
*/
|
||||
public void setRemotePeer(RouterIdentity ident) { _remotePeer = ident; }
|
||||
|
||||
/**
|
||||
* We are Bob. NTCP1 only.
|
||||
*
|
||||
* Caller MUST call recvEncryptedI2NP() after, for any remaining bytes in receive buffer
|
||||
*
|
||||
* @param clockSkew OUR clock minus ALICE's clock in seconds (may be negative, obviously, but |val| should
|
||||
* be under 1 minute)
|
||||
* @param prevWriteEnd exactly 16 bytes, not copied, do not corrupt, the write AES IV
|
||||
* @param prevReadEnd 16 or more bytes, last 16 bytes copied as the read AES IV
|
||||
*/
|
||||
void finishInboundEstablishment(SessionKey key, long clockSkew, byte prevWriteEnd[], byte prevReadEnd[]) {
|
||||
NTCPConnection toClose = locked_finishInboundEstablishment(key, clockSkew, prevWriteEnd, prevReadEnd);
|
||||
if (toClose != null) {
|
||||
int drained = toClose.drainOutboundTo(_outbound);
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Old connection closed: " + toClose + " replaced by " + this + "; drained " + drained);
|
||||
_context.statManager().addRateData("ntcp.inboundEstablishedDuplicate", toClose.getUptime());
|
||||
toClose.close();
|
||||
}
|
||||
enqueueInfoMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* We are Bob. NTCP1 only.
|
||||
*
|
||||
* @param clockSkew OUR clock minus ALICE's clock in seconds (may be negative, obviously, but |val| should
|
||||
* be under 1 minute)
|
||||
* @param prevWriteEnd exactly 16 bytes, not copied, do not corrupt, the write AES IV
|
||||
* @param prevReadEnd 16 or more bytes, last 16 bytes copied as the read AES IV
|
||||
* @return old conn to be closed by caller, or null
|
||||
*/
|
||||
private synchronized NTCPConnection locked_finishInboundEstablishment(
|
||||
SessionKey key, long clockSkew, byte prevWriteEnd[], byte prevReadEnd[]) {
|
||||
if (_establishState == EstablishBase.VERIFIED) {
|
||||
IllegalStateException ise = new IllegalStateException("Already finished on " + this);
|
||||
_log.error("Already finished", ise);
|
||||
throw ise;
|
||||
}
|
||||
byte[] prevReadBlock = new byte[BLOCK_SIZE];
|
||||
System.arraycopy(prevReadEnd, prevReadEnd.length - BLOCK_SIZE, prevReadBlock, 0, BLOCK_SIZE);
|
||||
_curReadState = new NTCP1ReadState(prevReadBlock);
|
||||
_sessionKey = key;
|
||||
_clockSkew = clockSkew;
|
||||
_prevWriteEnd = prevWriteEnd;
|
||||
_establishedOn = _context.clock().now();
|
||||
NTCPConnection rv = _transport.inboundEstablished(this);
|
||||
_nextMetaTime = _establishedOn + (META_FREQUENCY / 2) + _context.random().nextInt(META_FREQUENCY);
|
||||
_nextInfoTime = _establishedOn + (INFO_FREQUENCY / 2) + _context.random().nextInt(INFO_FREQUENCY);
|
||||
_establishState = EstablishBase.VERIFIED;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* A positive number means our clock is ahead of theirs.
|
||||
* @return seconds
|
||||
@@ -658,10 +587,7 @@ public class NTCPConnection implements Closeable {
|
||||
* con is established, so we know what the version is.
|
||||
*/
|
||||
void enqueueInfoMessage() {
|
||||
if (_version == 1) {
|
||||
enqueueInfoMessageNTCP1();
|
||||
// may change to 2 for inbound
|
||||
} else if (_isInbound) {
|
||||
if (_isInbound) {
|
||||
// TODO or if outbound and it's not right at the beginning
|
||||
// TODO flood
|
||||
sendOurRouterInfo(false);
|
||||
@@ -669,56 +595,6 @@ public class NTCPConnection implements Closeable {
|
||||
// don't need to send for NTCP 2 outbound, it's in msg 3
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject a DatabaseStoreMessage with our RouterInfo. NTCP 1 only.
|
||||
*/
|
||||
private void enqueueInfoMessageNTCP1() {
|
||||
int priority = INFO_PRIORITY;
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("SENDING INFO message pri. " + priority + ": " + toString());
|
||||
DatabaseStoreMessage dsm = new DatabaseStoreMessage(_context);
|
||||
dsm.setEntry(_context.router().getRouterInfo());
|
||||
// We are injecting directly, so we can use a null target.
|
||||
OutNetMessage infoMsg = new OutNetMessage(_context, dsm, _context.clock().now()+10*1000, priority, null);
|
||||
infoMsg.beginSend();
|
||||
send(infoMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* We are Alice. NTCP1 only.
|
||||
*
|
||||
* Caller MUST call recvEncryptedI2NP() after, for any remaining bytes in receive buffer
|
||||
*
|
||||
* @param clockSkew OUR clock minus BOB's clock in seconds (may be negative, obviously, but |val| should
|
||||
* be under 1 minute)
|
||||
* @param prevWriteEnd exactly 16 bytes, not copied, do not corrupt
|
||||
* @param prevReadEnd 16 or more bytes, last 16 bytes copied
|
||||
*/
|
||||
synchronized void finishOutboundEstablishment(SessionKey key, long clockSkew, byte prevWriteEnd[], byte prevReadEnd[]) {
|
||||
if (_establishState == EstablishBase.VERIFIED) {
|
||||
IllegalStateException ise = new IllegalStateException("Already finished on " + this);
|
||||
_log.error("Already finished", ise);
|
||||
throw ise;
|
||||
}
|
||||
byte[] prevReadBlock = new byte[BLOCK_SIZE];
|
||||
System.arraycopy(prevReadEnd, prevReadEnd.length - BLOCK_SIZE, prevReadBlock, 0, BLOCK_SIZE);
|
||||
_curReadState = new NTCP1ReadState(prevReadBlock);
|
||||
_sessionKey = key;
|
||||
_clockSkew = clockSkew;
|
||||
_prevWriteEnd = prevWriteEnd;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("outbound established (key=" + key + " skew=" + clockSkew +
|
||||
" prevWriteEnd: " + Base64.encode(prevWriteEnd) + " prevReadBlock: " + Base64.encode(prevReadBlock));
|
||||
|
||||
_establishedOn = _context.clock().now();
|
||||
_establishState = EstablishBase.VERIFIED;
|
||||
_transport.markReachable(getRemotePeer().calculateHash(), false);
|
||||
_nextMetaTime = _establishedOn + (META_FREQUENCY / 2) + _context.random().nextInt(META_FREQUENCY);
|
||||
_nextInfoTime = _establishedOn + (INFO_FREQUENCY / 2) + _context.random().nextInt(INFO_FREQUENCY);
|
||||
if (!_outbound.isEmpty())
|
||||
_transport.getWriter().wantsWrite(this, "outbound established");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the next I2NP message for transmission. This should be run from
|
||||
* the Writer thread pool. NTCP 1 or 2.
|
||||
@@ -738,98 +614,7 @@ public class NTCPConnection implements Closeable {
|
||||
if (!isEstablished()) {
|
||||
return;
|
||||
}
|
||||
if (_version == 1)
|
||||
prepareNextWriteFast(prep);
|
||||
else
|
||||
prepareNextWriteNTCP2(prep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the next I2NP message for transmission. This should be run from
|
||||
* the Writer thread pool. NTCP 1 only.
|
||||
*
|
||||
* Caller must synchronize.
|
||||
* @param buf a PrepBuffer to use as scratch space
|
||||
*
|
||||
*/
|
||||
private void prepareNextWriteFast(PrepBuffer buf) {
|
||||
long now = _context.clock().now();
|
||||
if (_nextMetaTime <= now) {
|
||||
sendMeta();
|
||||
_nextMetaTime = now + (META_FREQUENCY / 2) + _context.random().nextInt(META_FREQUENCY / 2);
|
||||
}
|
||||
|
||||
OutNetMessage msg;
|
||||
synchronized (_currentOutbound) {
|
||||
if (!_currentOutbound.isEmpty()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("attempt for multiple outbound messages with " + _currentOutbound.size() + " already waiting and " + _outbound.size() + " queued");
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
msg = _outbound.poll();
|
||||
if (msg == null)
|
||||
return;
|
||||
if (msg.getExpiration() >= now)
|
||||
break;
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping message expired on queue: " + msg + " on " + this);
|
||||
_transport.afterSend(msg, false, false, msg.getLifetime());
|
||||
}
|
||||
_currentOutbound.add(msg);
|
||||
}
|
||||
|
||||
bufferedPrepare(msg, buf);
|
||||
_context.aes().encrypt(buf.unencrypted, 0, buf.encrypted, 0, _sessionKey, _prevWriteEnd, 0, buf.unencryptedLength);
|
||||
System.arraycopy(buf.encrypted, buf.encrypted.length-16, _prevWriteEnd, 0, _prevWriteEnd.length);
|
||||
_transport.getPumper().wantsWrite(this, buf.encrypted);
|
||||
|
||||
// for every 6-12 hours that we are connected to a peer, send them
|
||||
// our updated netDb info (they may not accept it and instead query
|
||||
// the floodfill netDb servers, but they may...)
|
||||
if (_nextInfoTime <= now) {
|
||||
// perhaps this should check to see if we are bw throttled, etc?
|
||||
enqueueInfoMessage();
|
||||
_nextInfoTime = now + (INFO_FREQUENCY / 2) + _context.random().nextInt(INFO_FREQUENCY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the message/checksum/padding/etc for transmission, but leave off
|
||||
* the encryption. This should be called from a Writer thread
|
||||
*
|
||||
* @param msg message to send
|
||||
* @param buf PrepBuffer to use as scratch space
|
||||
*/
|
||||
private void bufferedPrepare(OutNetMessage msg, PrepBuffer buf) {
|
||||
I2NPMessage m = msg.getMessage();
|
||||
// 2 offset for size
|
||||
int sz = m.toByteArray(buf.unencrypted, 2) - 2;
|
||||
int min = 2 + sz + 4;
|
||||
int rem = min % 16;
|
||||
int padding = 0;
|
||||
if (rem > 0)
|
||||
padding = 16 - rem;
|
||||
buf.unencryptedLength = min+padding;
|
||||
DataHelper.toLong(buf.unencrypted, 0, 2, sz);
|
||||
if (padding > 0) {
|
||||
_context.random().nextBytes(buf.unencrypted, 2+sz, padding);
|
||||
}
|
||||
|
||||
buf.crc.update(buf.unencrypted, 0, buf.unencryptedLength-4);
|
||||
|
||||
long val = buf.crc.getValue();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Outbound message " + _messagesWritten + " has crc " + val
|
||||
+ " sz=" +sz + " rem=" + rem + " padding=" + padding);
|
||||
|
||||
DataHelper.toLong(buf.unencrypted, buf.unencryptedLength-4, 4, val);
|
||||
// TODO object churn
|
||||
// 1) store the length only
|
||||
// 2) in prepareNextWriteFast(), pull a byte buffer off a queue and encrypt to that
|
||||
// 3) change EventPumper.wantsWrite() to take a ByteBuffer arg
|
||||
// 4) in EventPumper.processWrite(), release the byte buffer
|
||||
buf.encrypted = new byte[buf.unencryptedLength];
|
||||
prepareNextWriteNTCP2(prep);
|
||||
}
|
||||
|
||||
static class PrepBuffer {
|
||||
@@ -1386,39 +1171,6 @@ public class NTCPConnection implements Closeable {
|
||||
_curReadState.receive(buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* One special case is a metadata message where the sizeof(data) is 0. In
|
||||
* that case, the unencrypted message is encoded as:
|
||||
*
|
||||
* <pre>
|
||||
* +-------+-------+-------+-------+-------+-------+-------+-------+
|
||||
* | 0 | timestamp in seconds | uninterpreted
|
||||
* +-------+-------+-------+-------+-------+-------+-------+-------+
|
||||
* uninterpreted | adler checksum of sz+data+pad |
|
||||
* +-------+-------+-------+-------+-------+-------+-------+-------+
|
||||
* </pre>
|
||||
*
|
||||
* Caller must synch
|
||||
*
|
||||
* @param unencrypted 16 bytes starting at off
|
||||
* @param off the offset
|
||||
*/
|
||||
private void readMeta(byte unencrypted[], int off) {
|
||||
Adler32 crc = new Adler32();
|
||||
crc.update(unencrypted, off, META_SIZE - 4);
|
||||
long expected = crc.getValue();
|
||||
long read = DataHelper.fromLong(unencrypted, off + META_SIZE - 4, 4);
|
||||
if (read != expected) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("I2NP metadata message had a bad CRC value");
|
||||
_context.statManager().addRateData("ntcp.corruptMetaCRC", 1);
|
||||
close();
|
||||
return;
|
||||
}
|
||||
long ts = DataHelper.fromLong(unencrypted, off + 2, 4);
|
||||
receiveTimestamp(ts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a received timestamp, NTCP 1 or 2.
|
||||
* Caller must synch
|
||||
@@ -1443,37 +1195,6 @@ public class NTCPConnection implements Closeable {
|
||||
_clockSkew = newSkew;
|
||||
}
|
||||
|
||||
/**
|
||||
* One special case is a metadata message where the sizeof(data) is 0. In
|
||||
* that case, the unencrypted message is encoded as:
|
||||
*
|
||||
*<pre>
|
||||
* +-------+-------+-------+-------+-------+-------+-------+-------+
|
||||
* | 0 | timestamp in seconds | uninterpreted
|
||||
* +-------+-------+-------+-------+-------+-------+-------+-------+
|
||||
* uninterpreted | adler checksum of sz+data+pad |
|
||||
* +-------+-------+-------+-------+-------+-------+-------+-------+
|
||||
*</pre>
|
||||
*
|
||||
* Caller must synchronize.
|
||||
*/
|
||||
private void sendMeta() {
|
||||
byte[] data = new byte[META_SIZE];
|
||||
DataHelper.toLong(data, 0, 2, 0);
|
||||
DataHelper.toLong(data, 2, 4, (_context.clock().now() + 500) / 1000);
|
||||
_context.random().nextBytes(data, 6, 6);
|
||||
Adler32 crc = new Adler32();
|
||||
crc.update(data, 0, META_SIZE - 4);
|
||||
DataHelper.toLong(data, META_SIZE - 4, 4, crc.getValue());
|
||||
_context.aes().encrypt(data, 0, data, 0, _sessionKey, _prevWriteEnd, 0, META_SIZE);
|
||||
System.arraycopy(data, META_SIZE - 16, _prevWriteEnd, 0, _prevWriteEnd.length);
|
||||
// perhaps this should skip the bw limiter to reduce clock skew issues?
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending NTCP metadata");
|
||||
_sendingMeta = true;
|
||||
_transport.getPumper().wantsWrite(this, data);
|
||||
}
|
||||
|
||||
private static final int MAX_HANDLERS = 4;
|
||||
|
||||
/**
|
||||
@@ -1514,319 +1235,6 @@ public class NTCPConnection implements Closeable {
|
||||
public int getFramesReceived();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the unencrypted message (16 bytes at a time).
|
||||
* verify the checksum, and pass it on to
|
||||
* an I2NPMessageHandler. The unencrypted message is encoded as follows:
|
||||
*
|
||||
*<pre>
|
||||
* +-------+-------+--//--+---//----+-------+-------+-------+-------+
|
||||
* | sizeof(data) | data | padding | adler checksum of sz+data+pad |
|
||||
* +-------+-------+--//--+---//----+-------+-------+-------+-------+
|
||||
*</pre>
|
||||
*
|
||||
* sizeof(data)+data+pad+crc.
|
||||
*
|
||||
* perhaps to reduce the per-con memory footprint, we can acquire/release
|
||||
* the ReadState._data and ._bais when _size is > 0, so there are only
|
||||
* J 16KB buffers for the cons actually transmitting, instead of one per
|
||||
* con (including idle ones)
|
||||
*
|
||||
* Call all methods from synchronized parent method.
|
||||
*
|
||||
*/
|
||||
private class NTCP1ReadState implements ReadState {
|
||||
private int _size;
|
||||
private ByteArray _dataBuf;
|
||||
private int _nextWrite;
|
||||
private final Adler32 _crc;
|
||||
private long _stateBegin;
|
||||
private int _blocks;
|
||||
/** encrypted block of the current I2NP message being read */
|
||||
private byte _curReadBlock[];
|
||||
/** next byte to which data should be placed in the _curReadBlock */
|
||||
private int _curReadBlockIndex;
|
||||
private final byte _decryptBlockBuf[];
|
||||
/** last AES block of the encrypted I2NP message (to serve as the next block's IV) */
|
||||
private byte _prevReadBlock[];
|
||||
|
||||
/**
|
||||
* @param prevReadBlock 16 bytes AES IV
|
||||
*/
|
||||
public NTCP1ReadState(byte[] prevReadBlock) {
|
||||
_crc = new Adler32();
|
||||
_prevReadBlock = prevReadBlock;
|
||||
_curReadBlock = new byte[BLOCK_SIZE];
|
||||
_decryptBlockBuf = new byte[BLOCK_SIZE];
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
_size = -1;
|
||||
_nextWrite = 0;
|
||||
_stateBegin = -1;
|
||||
_blocks = -1;
|
||||
_crc.reset();
|
||||
if (_dataBuf != null)
|
||||
releaseReadBuf(_dataBuf);
|
||||
_dataBuf = null;
|
||||
_curReadBlockIndex = 0;
|
||||
}
|
||||
|
||||
/** @since 0.9.36 */
|
||||
public void destroy() {
|
||||
if (_dataBuf != null) {
|
||||
releaseReadBuf(_dataBuf);
|
||||
_dataBuf = null;
|
||||
}
|
||||
// TODO zero things out
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection must be established!
|
||||
*
|
||||
* The contents of the buffer include some fraction of one or more
|
||||
* encrypted and encoded I2NP messages. individual i2np messages are
|
||||
* encoded as "sizeof(data)+data+pad+crc", and those are encrypted
|
||||
* with the session key and the last 16 bytes of the previous encrypted
|
||||
* i2np message.
|
||||
*
|
||||
* The NTCP connection now owns the buffer
|
||||
* BUT it must copy out the data
|
||||
* as reader will call EventPumper.releaseBuf().
|
||||
*
|
||||
* @since 0.9.36 moved from parent class
|
||||
*/
|
||||
public void receive(ByteBuffer buf) {
|
||||
// hasArray() is false for direct buffers, at least on my system...
|
||||
if (_curReadBlockIndex == 0 && buf.hasArray()) {
|
||||
// fast way
|
||||
int tot = buf.remaining();
|
||||
if (tot >= 32 && tot % 16 == 0) {
|
||||
recvEncryptedFast(buf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (buf.hasRemaining() && !_closed.get()) {
|
||||
int want = Math.min(buf.remaining(), BLOCK_SIZE - _curReadBlockIndex);
|
||||
if (want > 0) {
|
||||
buf.get(_curReadBlock, _curReadBlockIndex, want);
|
||||
_curReadBlockIndex += want;
|
||||
}
|
||||
if (_curReadBlockIndex >= BLOCK_SIZE) {
|
||||
// cbc
|
||||
_context.aes().decryptBlock(_curReadBlock, 0, _sessionKey, _decryptBlockBuf, 0);
|
||||
xor16(_prevReadBlock, _decryptBlockBuf);
|
||||
boolean ok = recvUnencryptedI2NP();
|
||||
if (!ok) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Read buffer " + System.identityHashCode(buf) + " contained corrupt data, IV was: " + Base64.encode(_decryptBlockBuf));
|
||||
_context.statManager().addRateData("ntcp.corruptDecryptedI2NP", 1);
|
||||
return;
|
||||
}
|
||||
byte swap[] = _prevReadBlock;
|
||||
_prevReadBlock = _curReadBlock;
|
||||
_curReadBlock = swap;
|
||||
_curReadBlockIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt directly out of the ByteBuffer instead of copying the bytes
|
||||
* 16 at a time to the _curReadBlock / _prevReadBlock flip buffers.
|
||||
*
|
||||
* More efficient but can only be used if buf.hasArray == true AND
|
||||
* _curReadBlockIndex must be 0 and buf.getRemaining() % 16 must be 0
|
||||
* and buf.getRemaining() must be >= 16.
|
||||
* All this is true for most incoming buffers.
|
||||
* In theory this could be fixed up to handle the other cases too but that's hard.
|
||||
* Caller must synchronize!
|
||||
*
|
||||
* @since 0.8.12, moved from parent class in 0.9.36
|
||||
*/
|
||||
private void recvEncryptedFast(ByteBuffer buf) {
|
||||
byte[] array = buf.array();
|
||||
int pos = buf.arrayOffset() + buf.position();
|
||||
int end = pos + buf.remaining();
|
||||
|
||||
// Copy to _curReadBlock for next IV...
|
||||
System.arraycopy(array, end - BLOCK_SIZE, _curReadBlock, 0, BLOCK_SIZE);
|
||||
// call aes().decrypt() to decrypt all at once, in place
|
||||
// decrypt() will offload to the JVM/OS for larger sizes
|
||||
_context.aes().decrypt(array, pos, array, pos, _sessionKey, _prevReadBlock, buf.remaining());
|
||||
|
||||
for ( ; pos < end; pos += BLOCK_SIZE) {
|
||||
boolean ok = receiveBlock(array, pos);
|
||||
if (!ok) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Read buffer " + System.identityHashCode(buf) + " contained corrupt data");
|
||||
_context.statManager().addRateData("ntcp.corruptDecryptedI2NP", 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// ...and flip to _prevReadBlock for next time
|
||||
byte swap[] = _prevReadBlock;
|
||||
_prevReadBlock = _curReadBlock;
|
||||
_curReadBlock = swap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the next 16 bytes of cleartext to the read state.
|
||||
* _decryptBlockBuf contains another cleartext block of I2NP to parse.
|
||||
* Caller must synchronize!
|
||||
*
|
||||
* @return success
|
||||
* @since 0.9.36 moved from parent class
|
||||
*/
|
||||
private boolean recvUnencryptedI2NP() {
|
||||
return receiveBlock(_decryptBlockBuf, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller must synchronize
|
||||
* @param buf 16 bytes starting at off
|
||||
* @param off offset
|
||||
* @return success, only false on initial block with invalid size
|
||||
*/
|
||||
private boolean receiveBlock(byte buf[], int off) {
|
||||
if (_size == -1) {
|
||||
return receiveInitial(buf, off);
|
||||
} else {
|
||||
receiveSubsequent(buf, off);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller must synchronize
|
||||
*
|
||||
* @param buf 16 bytes starting at off
|
||||
* @param off offset
|
||||
* @return success
|
||||
*/
|
||||
private boolean receiveInitial(byte buf[], int off) {
|
||||
_size = (int)DataHelper.fromLong(buf, off, 2);
|
||||
if (_size > BUFFER_SIZE) {
|
||||
// this is typically an AES decryption error, not actually a large I2NP message
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("I2NP message too big - size: " + _size + " Closing " + NTCPConnection.this.toString(), new Exception());
|
||||
_context.statManager().addRateData("ntcp.corruptTooLargeI2NP", _size);
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
if (_size == 0) {
|
||||
readMeta(buf, off);
|
||||
init();
|
||||
} else {
|
||||
_stateBegin = _context.clock().now();
|
||||
_dataBuf = acquireReadBuf();
|
||||
System.arraycopy(buf, off + 2, _dataBuf.getData(), 0, BLOCK_SIZE - 2);
|
||||
_nextWrite += BLOCK_SIZE - 2;
|
||||
_crc.update(buf, off, BLOCK_SIZE);
|
||||
_blocks++;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("new I2NP message with size: " + _size + " for message " + _messagesRead);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller must synchronize
|
||||
*
|
||||
* @param buf 16 bytes starting at off
|
||||
* @param off offset
|
||||
*/
|
||||
private void receiveSubsequent(byte buf[], int off) {
|
||||
_blocks++;
|
||||
int remaining = _size - _nextWrite;
|
||||
int blockUsed = Math.min(BLOCK_SIZE, remaining);
|
||||
if (remaining > 0) {
|
||||
System.arraycopy(buf, off, _dataBuf.getData(), _nextWrite, blockUsed);
|
||||
_nextWrite += blockUsed;
|
||||
remaining -= blockUsed;
|
||||
}
|
||||
if ( (remaining <= 0) && (BLOCK_SIZE - blockUsed < 4) ) {
|
||||
// we've received all the data but not the 4-byte checksum
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("crc wraparound required on block " + _blocks + " in message " + _messagesRead);
|
||||
_crc.update(buf, off, BLOCK_SIZE);
|
||||
return;
|
||||
} else if (remaining <= 0) {
|
||||
receiveLastBlock(buf, off);
|
||||
} else {
|
||||
_crc.update(buf, off, BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks the checksum in buf only.
|
||||
* All previous data, including that in buf, must have been copied to _dataBuf.
|
||||
* Note that the checksum does not cover the padding.
|
||||
* Caller must synchronize.
|
||||
*
|
||||
* @param buf 16 bytes starting at off
|
||||
* @param off offset of the 16-byte block (NOT of the checksum only)
|
||||
*/
|
||||
private void receiveLastBlock(byte buf[], int off) {
|
||||
// on the last block
|
||||
long expectedCrc = DataHelper.fromLong(buf, off + BLOCK_SIZE - 4, 4);
|
||||
_crc.update(buf, off, BLOCK_SIZE - 4);
|
||||
long val = _crc.getValue();
|
||||
if (val == expectedCrc) {
|
||||
try {
|
||||
I2NPMessageHandler h = acquireHandler(_context);
|
||||
|
||||
// Don't do readMessage(InputStream). I2NPMessageImpl.readBytes() copies the data
|
||||
// from a stream to a temp buffer.
|
||||
// We could extend BAIS to adjust the protected count variable to _size
|
||||
// so that readBytes() doesn't read too far, but it could still read too far.
|
||||
// So use the new handler method that limits the size.
|
||||
h.readMessage(_dataBuf.getData(), 0, _size);
|
||||
I2NPMessage read = h.lastRead();
|
||||
long timeToRecv = _context.clock().now() - _stateBegin;
|
||||
releaseHandler(h);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("I2NP message " + _messagesRead + "/" + (read != null ? read.getUniqueId() : 0)
|
||||
+ " received after " + timeToRecv + " with " + _size +"/"+ (_blocks*16) + " bytes on " + NTCPConnection.this.toString());
|
||||
_context.statManager().addRateData("ntcp.receiveTime", timeToRecv);
|
||||
_context.statManager().addRateData("ntcp.receiveSize", _size);
|
||||
|
||||
// FIXME move end of try block here.
|
||||
// On the input side, move releaseHandler() and init() to a finally block.
|
||||
|
||||
if (read != null) {
|
||||
_transport.messageReceived(read, _remotePeer, null, timeToRecv, _size);
|
||||
_lastReceiveTime = _context.clock().now();
|
||||
_messagesRead.incrementAndGet();
|
||||
}
|
||||
} catch (I2NPMessageException ime) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Error parsing I2NP message on " + NTCPConnection.this +
|
||||
"\nDUMP:\n" + HexDump.dump(_dataBuf.getData(), 0, _size),
|
||||
ime);
|
||||
}
|
||||
_context.statManager().addRateData("ntcp.corruptI2NPIME", 1);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("CRC incorrect for message " + _messagesRead + " (calc=" + val + " expected=" + expectedCrc +
|
||||
") size=" + _size + " blocks=" + _blocks + " on: " + NTCPConnection.this);
|
||||
_context.statManager().addRateData("ntcp.corruptI2NPCRC", 1);
|
||||
}
|
||||
// get it ready for the next I2NP message
|
||||
init();
|
||||
}
|
||||
|
||||
/*
|
||||
* Dummy.
|
||||
* @return 0 always
|
||||
* @since 0.9.36
|
||||
*/
|
||||
public int getFramesReceived() { return 0; }
|
||||
}
|
||||
|
||||
//// NTCP2 below here
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,317 +0,0 @@
|
||||
package net.i2p.router.transport.ntcp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleByteCache;
|
||||
|
||||
/**
|
||||
*
|
||||
* NTCP 1 only. We are Alice.
|
||||
*
|
||||
* @since 0.9.35 pulled out of EstablishState
|
||||
*/
|
||||
class OutboundEstablishState extends EstablishBase {
|
||||
|
||||
public OutboundEstablishState(RouterContext ctx, NTCPTransport transport, NTCPConnection con) {
|
||||
super(ctx, transport, con);
|
||||
_state = State.OB_INIT;
|
||||
ctx.sha().calculateHash(_X, 0, XY_SIZE, _hX_xor_bobIdentHash, 0);
|
||||
xor32(con.getRemotePeer().calculateHash().getData(), _hX_xor_bobIdentHash);
|
||||
// _prevEncrypted will be created later
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the contents of the buffer as part of the handshake.
|
||||
*
|
||||
* All data must be copied out of the buffer as Reader.processRead()
|
||||
* will return it to the pool.
|
||||
*
|
||||
* If there are additional data in the buffer after the handshake is complete,
|
||||
* the EstablishState is responsible for passing it to NTCPConnection.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void receive(ByteBuffer src) {
|
||||
super.receive(src);
|
||||
if (!src.hasRemaining())
|
||||
return; // nothing to receive
|
||||
receiveOutbound(src);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NTCP version
|
||||
* @return 1
|
||||
* @since 0.9.35
|
||||
*/
|
||||
public int getVersion() { return 1; }
|
||||
|
||||
/**
|
||||
* We are Alice, so receive these bytes as part of an outbound connection.
|
||||
* This method receives messages 2 and 4, and sends message 3.
|
||||
*
|
||||
* All data must be copied out of the buffer as Reader.processRead()
|
||||
* will return it to the pool.
|
||||
*
|
||||
* Caller must synch.
|
||||
*
|
||||
*/
|
||||
private void receiveOutbound(ByteBuffer src) {
|
||||
|
||||
// recv Y+E(H(X+Y)+tsB, sk, Y[239:255])
|
||||
// Read in Y, which is the first part of message #2
|
||||
if (_state == State.OB_SENT_X && src.hasRemaining()) {
|
||||
int toGet = Math.min(src.remaining(), XY_SIZE - _received);
|
||||
src.get(_Y, _received, toGet);
|
||||
_received += toGet;
|
||||
if (_received < XY_SIZE)
|
||||
return;
|
||||
|
||||
try {
|
||||
_dh.setPeerPublicValue(_Y);
|
||||
_dh.getSessionKey(); // force the calc
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"DH session key calculated (" + _dh.getSessionKey().toBase64() + ")");
|
||||
changeState(State.OB_GOT_Y);
|
||||
_received = 0;
|
||||
} catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
|
||||
_context.statManager().addRateData("ntcp.invalidDH", 1);
|
||||
fail("Invalid X", e);
|
||||
return;
|
||||
} catch (IllegalStateException ise) {
|
||||
// setPeerPublicValue()
|
||||
fail("reused keys?", ise);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Read in the rest of message #2
|
||||
if (_state == State.OB_GOT_Y && src.hasRemaining()) {
|
||||
int toGet = Math.min(src.remaining(), HXY_TSB_PAD_SIZE - _received);
|
||||
src.get(_e_hXY_tsB, _received, toGet);
|
||||
_received += toGet;
|
||||
if (_received < HXY_TSB_PAD_SIZE)
|
||||
return;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix() + "received _e_hXY_tsB fully");
|
||||
byte hXY_tsB[] = new byte[HXY_TSB_PAD_SIZE];
|
||||
_context.aes().decrypt(_e_hXY_tsB, 0, hXY_tsB, 0, _dh.getSessionKey(), _Y, XY_SIZE-AES_SIZE, HXY_TSB_PAD_SIZE);
|
||||
byte XY[] = new byte[XY_SIZE + XY_SIZE];
|
||||
System.arraycopy(_X, 0, XY, 0, XY_SIZE);
|
||||
System.arraycopy(_Y, 0, XY, XY_SIZE, XY_SIZE);
|
||||
byte[] h = SimpleByteCache.acquire(HXY_SIZE);
|
||||
_context.sha().calculateHash(XY, 0, XY_SIZE + XY_SIZE, h, 0);
|
||||
if (!DataHelper.eq(h, 0, hXY_tsB, 0, HXY_SIZE)) {
|
||||
SimpleByteCache.release(h);
|
||||
_context.statManager().addRateData("ntcp.invalidHXY", 1);
|
||||
fail("Invalid H(X+Y) - mitm attack attempted?");
|
||||
return;
|
||||
}
|
||||
SimpleByteCache.release(h);
|
||||
changeState(State.OB_GOT_HXY);
|
||||
_received = 0;
|
||||
// their (Bob's) timestamp in seconds
|
||||
_tsB = DataHelper.fromLong(hXY_tsB, HXY_SIZE, 4);
|
||||
long now = _context.clock().now();
|
||||
// rtt from sending #1 to receiving #2
|
||||
long rtt = now - _con.getCreated();
|
||||
// our (Alice's) timestamp in seconds
|
||||
_tsA = (now + 500) / 1000;
|
||||
_peerSkew = (now - (_tsB * 1000) - (rtt / 2) + 500) / 1000;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"h(X+Y) is correct, skew = " + _peerSkew);
|
||||
|
||||
// the skew is not authenticated yet, but it is certainly fatal to
|
||||
// the establishment, so fail hard if appropriate
|
||||
long diff = 1000*Math.abs(_peerSkew);
|
||||
if (!_context.clock().getUpdatedSuccessfully()) {
|
||||
// Adjust the clock one time in desperation
|
||||
// We are Alice, he is Bob, adjust to match Bob
|
||||
_context.clock().setOffset(1000 * (0 - _peerSkew), true);
|
||||
_peerSkew = 0;
|
||||
if (diff != 0)
|
||||
_log.logAlways(Log.WARN, "NTP failure, NTCP adjusting clock by " + DataHelper.formatDuration(diff));
|
||||
} else if (diff >= Router.CLOCK_FUDGE_FACTOR) {
|
||||
_context.statManager().addRateData("ntcp.invalidOutboundSkew", diff);
|
||||
_transport.markReachable(_con.getRemotePeer().calculateHash(), false);
|
||||
// Only banlist if we know what time it is
|
||||
_context.banlist().banlistRouter(DataHelper.formatDuration(diff),
|
||||
_con.getRemotePeer().calculateHash(),
|
||||
_x("Excessive clock skew: {0}"));
|
||||
_transport.setLastBadSkew(_peerSkew);
|
||||
fail("Clocks too skewed (" + diff + " ms)", null, true);
|
||||
return;
|
||||
} else if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(prefix()+"Clock skew: " + diff + " ms");
|
||||
}
|
||||
|
||||
// now prepare and send our response
|
||||
// send E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB), sk, hX_xor_Bob.identHash[16:31])
|
||||
int sigSize = XY_SIZE + XY_SIZE + HXY_SIZE + 4+4;//+12;
|
||||
byte preSign[] = new byte[sigSize];
|
||||
System.arraycopy(_X, 0, preSign, 0, XY_SIZE);
|
||||
System.arraycopy(_Y, 0, preSign, XY_SIZE, XY_SIZE);
|
||||
System.arraycopy(_con.getRemotePeer().calculateHash().getData(), 0, preSign, XY_SIZE + XY_SIZE, HXY_SIZE);
|
||||
DataHelper.toLong(preSign, XY_SIZE + XY_SIZE + HXY_SIZE, 4, _tsA);
|
||||
DataHelper.toLong(preSign, XY_SIZE + XY_SIZE + HXY_SIZE + 4, 4, _tsB);
|
||||
// hXY_tsB has 12 bytes of padding (size=48, tsB=4 + hXY=32)
|
||||
Signature sig = _context.dsa().sign(preSign, _context.keyManager().getSigningPrivateKey());
|
||||
|
||||
byte ident[] = _context.router().getRouterInfo().getIdentity().toByteArray();
|
||||
// handle variable signature size
|
||||
int min = 2 + ident.length + 4 + sig.length();
|
||||
int rem = min % AES_SIZE;
|
||||
int padding = 0;
|
||||
if (rem > 0)
|
||||
padding = AES_SIZE - rem;
|
||||
byte preEncrypt[] = new byte[min+padding];
|
||||
DataHelper.toLong(preEncrypt, 0, 2, ident.length);
|
||||
System.arraycopy(ident, 0, preEncrypt, 2, ident.length);
|
||||
DataHelper.toLong(preEncrypt, 2+ident.length, 4, _tsA);
|
||||
if (padding > 0)
|
||||
_context.random().nextBytes(preEncrypt, 2 + ident.length + 4, padding);
|
||||
System.arraycopy(sig.getData(), 0, preEncrypt, 2+ident.length+4+padding, sig.length());
|
||||
|
||||
_prevEncrypted = new byte[preEncrypt.length];
|
||||
_context.aes().encrypt(preEncrypt, 0, _prevEncrypted, 0, _dh.getSessionKey(),
|
||||
_hX_xor_bobIdentHash, _hX_xor_bobIdentHash.length-AES_SIZE, preEncrypt.length);
|
||||
|
||||
changeState(State.OB_SENT_RI);
|
||||
_transport.getPumper().wantsWrite(_con, _prevEncrypted);
|
||||
}
|
||||
|
||||
// Read in message #4
|
||||
if (_state == State.OB_SENT_RI && src.hasRemaining()) {
|
||||
// we are receiving their confirmation
|
||||
|
||||
// recv E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev)
|
||||
int off = 0;
|
||||
if (_e_bobSig == null) {
|
||||
// handle variable signature size
|
||||
int siglen = _con.getRemotePeer().getSigningPublicKey().getType().getSigLen();
|
||||
int rem = siglen % AES_SIZE;
|
||||
int padding;
|
||||
if (rem > 0)
|
||||
padding = AES_SIZE - rem;
|
||||
else
|
||||
padding = 0;
|
||||
_e_bobSig = new byte[siglen + padding];
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "receiving E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " +
|
||||
src.hasRemaining() + ")");
|
||||
} else {
|
||||
off = _received;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "continuing to receive E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " +
|
||||
src.hasRemaining() + " off=" + off + " recv=" + _received + ")");
|
||||
}
|
||||
while (_state == State.OB_SENT_RI && src.hasRemaining()) {
|
||||
_e_bobSig[off++] = src.get();
|
||||
_received++;
|
||||
|
||||
if (off >= _e_bobSig.length) {
|
||||
changeState(State.OB_GOT_SIG);
|
||||
byte bobSig[] = new byte[_e_bobSig.length];
|
||||
_context.aes().decrypt(_e_bobSig, 0, bobSig, 0, _dh.getSessionKey(),
|
||||
_e_hXY_tsB, HXY_TSB_PAD_SIZE - AES_SIZE, _e_bobSig.length);
|
||||
// ignore the padding
|
||||
// handle variable signature size
|
||||
SigType type = _con.getRemotePeer().getSigningPublicKey().getType();
|
||||
int siglen = type.getSigLen();
|
||||
// we don't need to do this if no padding!
|
||||
byte bobSigData[] = new byte[siglen];
|
||||
System.arraycopy(bobSig, 0, bobSigData, 0, siglen);
|
||||
Signature sig = new Signature(type, bobSigData);
|
||||
|
||||
byte toVerify[] = new byte[XY_SIZE + XY_SIZE + HXY_SIZE +4+4];
|
||||
int voff = 0;
|
||||
System.arraycopy(_X, 0, toVerify, voff, XY_SIZE); voff += XY_SIZE;
|
||||
System.arraycopy(_Y, 0, toVerify, voff, XY_SIZE); voff += XY_SIZE;
|
||||
System.arraycopy(_context.routerHash().getData(), 0, toVerify, voff, HXY_SIZE); voff += HXY_SIZE;
|
||||
DataHelper.toLong(toVerify, voff, 4, _tsA); voff += 4;
|
||||
DataHelper.toLong(toVerify, voff, 4, _tsB); voff += 4;
|
||||
|
||||
boolean ok = _context.dsa().verifySignature(sig, toVerify, _con.getRemotePeer().getSigningPublicKey());
|
||||
if (!ok) {
|
||||
_context.statManager().addRateData("ntcp.invalidSignature", 1);
|
||||
fail("Signature was invalid - attempt to spoof " + _con.getRemotePeer().calculateHash().toBase64() + "?");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "signature verified from Bob. done!");
|
||||
byte nextWriteIV[] = SimpleByteCache.acquire(AES_SIZE);
|
||||
System.arraycopy(_prevEncrypted, _prevEncrypted.length-AES_SIZE, nextWriteIV, 0, AES_SIZE);
|
||||
// this does not copy the nextWriteIV, do not release to cache
|
||||
// We are Alice, he is Bob, clock skew is Bob - Alice
|
||||
// skew in seconds
|
||||
_con.finishOutboundEstablishment(_dh.getSessionKey(), _peerSkew, nextWriteIV, _e_bobSig);
|
||||
changeState(State.VERIFIED);
|
||||
if (src.hasRemaining()) {
|
||||
// process "extra" data
|
||||
// This is fairly common for outbound, where Bob may send his updated RI
|
||||
if (_log.shouldInfo())
|
||||
_log.info("extra data " + src.remaining() + " on " + this);
|
||||
_con.recvEncryptedI2NP(src);
|
||||
}
|
||||
releaseBufs(true);
|
||||
// if socket gets closed this will be null - prevent NPE
|
||||
InetAddress ia = _con.getChannel().socket().getInetAddress();
|
||||
if (ia != null)
|
||||
_transport.setIP(_con.getRemotePeer().calculateHash(), ia.getAddress());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for remaining data
|
||||
if ((_state == State.VERIFIED || _state == State.CORRUPT) && src.hasRemaining()) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Received unexpected " + src.remaining() + " on " + this, new Exception());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We are Alice.
|
||||
* We are establishing an outbound connection, so prepare ourselves by
|
||||
* queueing up the write of the first part of the handshake
|
||||
* This method sends message #1 to Bob.
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
@Override
|
||||
public synchronized void prepareOutbound() {
|
||||
if (_state == State.OB_INIT) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "send X");
|
||||
byte toWrite[] = new byte[XY_SIZE + _hX_xor_bobIdentHash.length];
|
||||
System.arraycopy(_X, 0, toWrite, 0, XY_SIZE);
|
||||
System.arraycopy(_hX_xor_bobIdentHash, 0, toWrite, XY_SIZE, _hX_xor_bobIdentHash.length);
|
||||
changeState(State.OB_SENT_X);
|
||||
_transport.getPumper().wantsWrite(_con, toWrite);
|
||||
} else {
|
||||
throw new IllegalStateException(prefix() + "unexpected prepareOutbound()");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only call once. Caller must synch.
|
||||
* @since 0.9.16
|
||||
*/
|
||||
@Override
|
||||
protected void releaseBufs(boolean isVerified) {
|
||||
super.releaseBufs(isVerified);
|
||||
Arrays.fill(_Y, (byte) 0);
|
||||
SimpleByteCache.release(_Y);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user