diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index c852b359a7d962e64397ad1b06608847f9cd2ed5..5237ea4ac214bb1bf00ebbfac23d9200765ffca1 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -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); } /** diff --git a/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java index 88e1cd068c4ce10481036ef0dc4af98a7035b957..8e47a6bf7151da6da53c7120eb6b1e39dfdc6e44 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java @@ -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 /** diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java index 0c4d4a80c0738fc0e0e1b9621a7d323799182bcf..79867850c552e413a926856fbd2c8847440e4d39 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java @@ -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 /** diff --git a/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java deleted file mode 100644 index c40573803b1bbf2415563a261b223da46b34c795..0000000000000000000000000000000000000000 --- a/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java +++ /dev/null @@ -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); - } -}