NTCP: Remove NTCP 1 suppport

Remove individual 1/2 enable config
Additional cleanup to follow
This commit is contained in:
zzz
2021-03-08 09:05:32 -05:00
parent 82b7eea5f0
commit 6a54af399d
4 changed files with 12 additions and 1308 deletions

View File

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

View File

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

View File

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

View File

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