diff --git a/history.txt b/history.txt index 3f86ddf9313d23a4f4a78d6062f8cc40f8674025..a80a560810f63178f6e72529baf685d72dd2eaaf 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,10 @@ +2018-06-02 zzz + * Console: Sort tunnels within pools by expiration (ticket #2232) + * NTCP: Refactor EstablishState in prep for NTCP2 + +2018-06-01 zzz + * SusiDNS: Fix deleting notes (ticket #1433) + 2018-05-31 zzz * Console: - Fix CSS preventing ordered lists (ticket #2075) diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index e1f87e077ded8c1235896b49923811522eadbbfe..8ab24107c98197555eefc67083d312bbe5d8ab93 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 16; + public final static long BUILD = 17; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishBase.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishBase.java new file mode 100644 index 0000000000000000000000000000000000000000..492385e72d227899a31b0767a4a4979c2cc27b85 --- /dev/null +++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishBase.java @@ -0,0 +1,376 @@ +package net.i2p.router.transport.ntcp; + +import java.nio.ByteBuffer; + +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; + +/** + * Handle the 4-phase establishment, which is as follows: + * + * <pre> + * + * Alice contacts Bob + * ========================================================= + * + * Message 1 (Session Request): + * X+(H(X) xor Bob.identHash)-----------------------------> + * + * Message 2 (Session Created): + * <----------------------------------------Y+E(H(X+Y)+tsB, sk, Y[239:255]) + * + * Message 3 (Session Confirm A): + * E(sz+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB), sk, hX_xor_Bob.identHash[16:31])---> + * + * Message 4 (Session Confirm B): + * <----------------------E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) + * + * Key: + * + * X, Y: 256 byte DH keys + * H(): 32 byte SHA256 Hash + * E(data, session key, IV): AES256 Encrypt + * S(): 40 byte DSA Signature + * tsA, tsB: timestamps (4 bytes, seconds since epoch) + * sk: 32 byte Session key + * sz: 2 byte size of Alice identity to follow + * + * </pre> + * + * + * Alternately, when Bob receives a connection, it could be a + * check connection (perhaps prompted by Bob asking for someone + * to verify his listener). check connections are formatted per + * isCheckInfo() + * NOTE: Check info is unused. + * + */ +abstract class EstablishBase implements EstablishState { + + public static final VerifiedEstablishState VERIFIED = new VerifiedEstablishState(); + public static final FailedEstablishState FAILED = new FailedEstablishState(); + + protected final RouterContext _context; + protected final Log _log; + + // bob receives (and alice sends) + protected final byte _X[]; + protected final byte _hX_xor_bobIdentHash[]; + // alice receives (and bob sends) + protected final byte _Y[]; + protected final byte _e_hXY_tsB[]; + /** Bob's timestamp in seconds, this is in message #2, *before* _tsA */ + protected transient long _tsB; + /** Alice's timestamp in seconds, this is in message #3, *after* _tsB + * Only saved for outbound. For inbound, see verifyInbound(). + */ + protected transient long _tsA; + /** + * OUR clock minus HIS clock, in seconds + * + * Inbound: tsB - tsA - rtt/2 + * Outbound: tsA - tsB - rtt/2 + */ + protected transient long _peerSkew; + protected transient byte _e_bobSig[]; + + /** previously received encrypted block (or the IV) */ + protected byte _prevEncrypted[]; + /** decryption buffer */ + protected final byte _curDecrypted[]; + + /** bytes received so far */ + protected int _received; + private byte _extra[]; + + protected final DHSessionKeyBuilder _dh; + + protected final NTCPTransport _transport; + protected final NTCPConnection _con; + /** error causing the corruption */ + private String _err; + /** exception causing the error */ + private Exception _e; + private boolean _failedBySkew; + + protected static final int MIN_RI_SIZE = 387; + protected static final int MAX_RI_SIZE = 3072; + + protected static final int AES_SIZE = 16; + protected static final int XY_SIZE = 256; + protected static final int HXY_SIZE = 32; //Hash.HASH_LENGTH; + protected static final int HXY_TSB_PAD_SIZE = HXY_SIZE + 4 + 12; // 48 + + protected static final Object _stateLock = new Object(); + protected State _state; + + protected enum State { + OB_INIT, + /** sent 1 */ + OB_SENT_X, + /** sent 1, got 2 partial */ + OB_GOT_Y, + /** sent 1, got 2 */ + OB_GOT_HXY, + /** sent 1, got 2, sent 3 */ + OB_SENT_RI, + /** sent 1, got 2, sent 3, got 4 */ + OB_GOT_SIG, + + IB_INIT, + /** got 1 partial */ + IB_GOT_X, + /** got 1 */ + IB_GOT_HX, + /** got 1, sent 2 */ + IB_SENT_Y, + /** got 1, sent 2, got partial 3 */ + IB_GOT_RI_SIZE, + /** got 1, sent 2, got 3 */ + IB_GOT_RI, + + /** OB: got and verified 4; IB: got and verified 3 and sent 4 */ + VERIFIED, + CORRUPT + } + + private EstablishBase() { + _context = null; + _log = null; + _X = null; + _Y = null; + _hX_xor_bobIdentHash = null; + _curDecrypted = null; + _dh = null; + _transport = null; + _con = null; + _e_hXY_tsB = null; + } + + protected EstablishBase(RouterContext ctx, NTCPTransport transport, NTCPConnection con) { + _context = ctx; + _log = ctx.logManager().getLog(getClass()); + _transport = transport; + _con = con; + _dh = _transport.getDHBuilder(); + _hX_xor_bobIdentHash = SimpleByteCache.acquire(HXY_SIZE); + if (_con.isInbound()) { + _X = SimpleByteCache.acquire(XY_SIZE); + _Y = _dh.getMyPublicValueBytes(); + } else { + _X = _dh.getMyPublicValueBytes(); + _Y = SimpleByteCache.acquire(XY_SIZE); + } + + _e_hXY_tsB = new byte[HXY_TSB_PAD_SIZE]; + _curDecrypted = SimpleByteCache.acquire(AES_SIZE); + } + + /** @since 0.9.16 */ + protected void changeState(State state) { + synchronized (_stateLock) { + _state = state; + } + } + + /** + * parse the contents of the buffer as part of the handshake. if the + * handshake is completed and there is more data remaining, the data are + * copieed out so that the next read will be the (still encrypted) remaining + * data (available from getExtraBytes) + * + * All data must be copied out of the buffer as Reader.processRead() + * will return it to the pool. + */ + public synchronized void receive(ByteBuffer src) { + synchronized(_stateLock) { + if (_state == State.VERIFIED || _state == State.CORRUPT) + throw new IllegalStateException(prefix() + "received unexpected data on " + _con); + } + if (_log.shouldLog(Log.DEBUG)) + _log.debug(prefix() + "Receiving: " + src.remaining() + " Received: " + _received); + } + + /** + * Does nothing. Outbound (Alice) must override. + * We are establishing an outbound connection, so prepare ourselves by + * queueing up the write of the first part of the handshake + */ + public void prepareOutbound() {} + + /** + * Was this connection failed because of clock skew? + */ + public synchronized boolean getFailedBySkew() { return _failedBySkew; } + + /** did the handshake fail for some reason? */ + public boolean isCorrupt() { + synchronized(_stateLock) { + return _state == State.CORRUPT; + } + } + + /** + * If synchronized on this, fails with + * deadlocks from all over via CSFI.isEstablished(). + * Also CSFI.getFramedAveragePeerClockSkew(). + * + * @return is the handshake complete and valid? + */ + public boolean isComplete() { + synchronized(_stateLock) { + return _state == State.VERIFIED; + } + } + + /** Anything left over in the byte buffer after verification is extra + * + * All data must be copied out of the buffer as Reader.processRead() + * will return it to the pool. + * + * State must be VERIFIED. + * Caller must synch. + */ + protected void prepareExtra(ByteBuffer buf) { + int remaining = buf.remaining(); + if (remaining > 0) { + _extra = new byte[remaining]; + buf.get(_extra); + _received += remaining; + } + if (_log.shouldLog(Log.DEBUG)) + _log.debug(prefix() + "prepare extra " + remaining + " (total received: " + _received + ")"); + } + + /** + * if complete, this will contain any bytes received as part of the + * handshake that were after the actual handshake. This may return null. + */ + public synchronized byte[] getExtraBytes() { return _extra; } + + /** + * Release resources on timeout. + * @param e may be null + * @since 0.9.16 + */ + public synchronized void close(String reason, Exception e) { + fail(reason, e); + } + + /** Caller must synch. */ + protected void fail(String reason) { fail(reason, null); } + + /** Caller must synch. */ + protected void fail(String reason, Exception e) { fail(reason, e, false); } + + /** Caller must synch. */ + protected void fail(String reason, Exception e, boolean bySkew) { + synchronized(_stateLock) { + if (_state == State.CORRUPT || _state == State.VERIFIED) + return; + changeState(State.CORRUPT); + } + _failedBySkew = bySkew; + _err = reason; + _e = e; + if (_log.shouldLog(Log.WARN)) + _log.warn(prefix()+"Failed to establish: " + _err, e); + releaseBufs(false); + } + + /** + * Only call once. Caller must synch. + * @since 0.9.16 + */ + protected void releaseBufs(boolean isVerified) { + // null or longer for OB + if (_prevEncrypted != null && _prevEncrypted.length == AES_SIZE) + SimpleByteCache.release(_prevEncrypted); + SimpleByteCache.release(_curDecrypted); + SimpleByteCache.release(_hX_xor_bobIdentHash); + if (_dh.getPeerPublicValue() == null) + _transport.returnUnused(_dh); + } + + public synchronized String getError() { return _err; } + + public synchronized Exception getException() { return _e; } + + /** + * XOR a into b. Modifies b. a is unmodified. + * @param a 32 bytes + * @param b 32 bytes + * @since 0.9.12 + */ + protected static void xor32(byte[] a, byte[] b) { + for (int i = 0; i < 32; i++) { + b[i] ^= a[i]; + } + } + + protected String prefix() { return toString(); } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + if (_con.isInbound()) + buf.append("IBES "); + else + buf.append("OBES "); + buf.append(System.identityHashCode(this)); + buf.append(' ').append(_state); + if (_con.isEstablished()) buf.append(" established"); + buf.append(": "); + return buf.toString(); + } + + /** + * @since 0.9.8 + */ + private static class VerifiedEstablishState extends EstablishBase { + + public VerifiedEstablishState() { + super(); + _state = State.VERIFIED; + } + + @Override public void prepareOutbound() { + Log log =RouterContext.getCurrentContext().logManager().getLog(VerifiedEstablishState.class); + log.warn("prepareOutbound() on verified state, doing nothing!"); + } + + @Override public String toString() { return "VerifiedEstablishState: ";} + } + + /** + * @since 0.9.16 + */ + private static class FailedEstablishState extends EstablishBase { + + public FailedEstablishState() { + super(); + _state = State.CORRUPT; + } + + @Override public void prepareOutbound() { + Log log =RouterContext.getCurrentContext().logManager().getLog(VerifiedEstablishState.class); + log.warn("prepareOutbound() on verified state, doing nothing!"); + } + + @Override public String toString() { return "FailedEstablishState: ";} + } + + /** + * Mark a string for extraction by xgettext and translation. + * Use this only in static initializers. + * It does not translate! + * @return s + */ + protected static final String _x(String s) { + return s; + } + +} diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java index ff43d294769f54cb2f68850be8ce4129c6635971..2a40f69c1780a204592f58c48a08b7a656219a87 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java @@ -1,215 +1,13 @@ package net.i2p.router.transport.ntcp; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.nio.ByteBuffer; -import net.i2p.I2PAppContext; -import net.i2p.crypto.SigType; -import net.i2p.data.Base64; -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; - /** - * Handle the 4-phase establishment, which is as follows: - * - * <pre> - * - * Alice contacts Bob - * ========================================================= - * - * Message 1 (Session Request): - * X+(H(X) xor Bob.identHash)-----------------------------> - * - * Message 2 (Session Created): - * <----------------------------------------Y+E(H(X+Y)+tsB, sk, Y[239:255]) - * - * Message 3 (Session Confirm A): - * E(sz+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB), sk, hX_xor_Bob.identHash[16:31])---> - * - * Message 4 (Session Confirm B): - * <----------------------E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) - * - * Key: - * - * X, Y: 256 byte DH keys - * H(): 32 byte SHA256 Hash - * E(data, session key, IV): AES256 Encrypt - * S(): 40 byte DSA Signature - * tsA, tsB: timestamps (4 bytes, seconds since epoch) - * sk: 32 byte Session key - * sz: 2 byte size of Alice identity to follow - * - * </pre> - * - * - * Alternately, when Bob receives a connection, it could be a - * check connection (perhaps prompted by Bob asking for someone - * to verify his listener). check connections are formatted per - * isCheckInfo() - * NOTE: Check info is unused. + * Handle the establishment * */ -class EstablishState { - - public static final VerifiedEstablishState VERIFIED = new VerifiedEstablishState(); - public static final FailedEstablishState FAILED = new FailedEstablishState(); - - private final RouterContext _context; - private final Log _log; - - // bob receives (and alice sends) - private final byte _X[]; - private final byte _hX_xor_bobIdentHash[]; - private int _aliceIdentSize; - private RouterIdentity _aliceIdent; - /** contains the decrypted aliceIndexSize + aliceIdent + tsA + padding + aliceSig */ - private ByteArrayOutputStream _sz_aliceIdent_tsA_padding_aliceSig; - /** how long we expect _sz_aliceIdent_tsA_padding_aliceSig to be when its full */ - private int _sz_aliceIdent_tsA_padding_aliceSigSize; - // alice receives (and bob sends) - private final byte _Y[]; - private final byte _e_hXY_tsB[]; - /** Bob's timestamp in seconds, this is in message #2, *before* _tsA */ - private transient long _tsB; - /** Alice's timestamp in seconds, this is in message #3, *after* _tsB - * Only saved for outbound. For inbound, see verifyInbound(). - */ - private transient long _tsA; - /** - * OUR clock minus HIS clock, in seconds - * - * Inbound: tsB - tsA - rtt/2 - * Outbound: tsA - tsB - rtt/2 - */ - private transient long _peerSkew; - private transient byte _e_bobSig[]; - - /** previously received encrypted block (or the IV) */ - private byte _prevEncrypted[]; - /** current encrypted block we are reading (IB only) or an IV buf used at the end for OB */ - private byte _curEncrypted[]; - /** - * next index in _curEncrypted to write to (equals _curEncrypted length if the block is - * ready to decrypt) - */ - private int _curEncryptedOffset; - /** decryption buffer */ - private final byte _curDecrypted[]; - - /** bytes received so far */ - private int _received; - private byte _extra[]; - - private final DHSessionKeyBuilder _dh; - - private final NTCPTransport _transport; - private final NTCPConnection _con; - /** error causing the corruption */ - private String _err; - /** exception causing the error */ - private Exception _e; - private boolean _failedBySkew; +interface EstablishState { - private static final int MIN_RI_SIZE = 387; - private static final int MAX_RI_SIZE = 3072; - - private static final int AES_SIZE = 16; - private static final int XY_SIZE = 256; - private static final int HXY_SIZE = 32; //Hash.HASH_LENGTH; - private static final int HXY_TSB_PAD_SIZE = HXY_SIZE + 4 + 12; // 48 - - private static final Object _stateLock = new Object(); - protected State _state; - - private enum State { - OB_INIT, - /** sent 1 */ - OB_SENT_X, - /** sent 1, got 2 partial */ - OB_GOT_Y, - /** sent 1, got 2 */ - OB_GOT_HXY, - /** sent 1, got 2, sent 3 */ - OB_SENT_RI, - /** sent 1, got 2, sent 3, got 4 */ - OB_GOT_SIG, - - IB_INIT, - /** got 1 partial */ - IB_GOT_X, - /** got 1 */ - IB_GOT_HX, - /** got 1, sent 2 */ - IB_SENT_Y, - /** got 1, sent 2, got partial 3 */ - IB_GOT_RI_SIZE, - /** got 1, sent 2, got 3 */ - IB_GOT_RI, - - /** OB: got and verified 4; IB: got and verified 3 and sent 4 */ - VERIFIED, - CORRUPT - } - - private EstablishState() { - _context = null; - _log = null; - _X = null; - _Y = null; - _hX_xor_bobIdentHash = null; - _curDecrypted = null; - _dh = null; - _transport = null; - _con = null; - _e_hXY_tsB = null; - } - - public EstablishState(RouterContext ctx, NTCPTransport transport, NTCPConnection con) { - _context = ctx; - _log = ctx.logManager().getLog(getClass()); - _transport = transport; - _con = con; - _dh = _transport.getDHBuilder(); - _hX_xor_bobIdentHash = SimpleByteCache.acquire(HXY_SIZE); - if (_con.isInbound()) { - _X = SimpleByteCache.acquire(XY_SIZE); - _Y = _dh.getMyPublicValueBytes(); - _sz_aliceIdent_tsA_padding_aliceSig = new ByteArrayOutputStream(512); - _prevEncrypted = SimpleByteCache.acquire(AES_SIZE); - _state = State.IB_INIT; - } else { - _X = _dh.getMyPublicValueBytes(); - _Y = SimpleByteCache.acquire(XY_SIZE); - ctx.sha().calculateHash(_X, 0, XY_SIZE, _hX_xor_bobIdentHash, 0); - xor32(con.getRemotePeer().calculateHash().getData(), _hX_xor_bobIdentHash); - // _prevEncrypted will be created later - _state = State.OB_INIT; - } - - _e_hXY_tsB = new byte[HXY_TSB_PAD_SIZE]; - _curEncrypted = SimpleByteCache.acquire(AES_SIZE); - _curDecrypted = SimpleByteCache.acquire(AES_SIZE); - } - - /** @since 0.9.16 */ - private void changeState(State state) { - synchronized (_stateLock) { - _state = state; - } - } - /** * parse the contents of the buffer as part of the handshake. if the * handshake is completed and there is more data remaining, the data are @@ -219,442 +17,22 @@ class EstablishState { * All data must be copied out of the buffer as Reader.processRead() * will return it to the pool. */ - public synchronized void receive(ByteBuffer src) { - synchronized(_stateLock) { - if (_state == State.VERIFIED || _state == State.CORRUPT) - throw new IllegalStateException(prefix() + "received unexpected data on " + _con); - } - if (!src.hasRemaining()) - return; // nothing to receive - - if (_log.shouldLog(Log.DEBUG)) - _log.debug(prefix() + "Receiving: " + src.remaining() + " Received: " + _received); - if (_con.isInbound()) - receiveInbound(src); - else - receiveOutbound(src); - } - - /** - * Was this connection failed because of clock skew? - */ - public synchronized boolean getFailedBySkew() { return _failedBySkew; } + public void receive(ByteBuffer src); /** - * we are Bob, so receive these bytes as part of an inbound connection - * This method receives messages 1 and 3, and sends messages 2 and 4. - * - * All data must be copied out of the buffer as Reader.processRead() - * will return it to the pool. - * - * Caller must synch. - * - * FIXME none of the _state comparisons use _stateLock, but whole thing - * is synchronized, should be OK. See isComplete() + * Does nothing. Outbound (Alice) must override. + * We are establishing an outbound connection, so prepare ourselves by + * queueing up the write of the first part of the handshake */ - private void receiveInbound(ByteBuffer src) { - while (_state == State.IB_INIT && src.hasRemaining()) { - byte c = src.get(); - _X[_received++] = c; - if (_received >= XY_SIZE) - changeState(State.IB_GOT_X); - } - while (_state == State.IB_GOT_X && src.hasRemaining()) { - int i = _received - XY_SIZE; - _received++; - byte c = src.get(); - _hX_xor_bobIdentHash[i] = c; - if (i >= HXY_SIZE - 1) - changeState(State.IB_GOT_HX); - } - - 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; - } - - } - - // ok, we are onto the encrypted area, i.e. Message #3 - while ((_state == State.IB_SENT_Y || - _state == State.IB_GOT_RI_SIZE || - _state == State.IB_GOT_RI) && src.hasRemaining()) { - - // Collect a 16-byte block - while (_curEncryptedOffset < AES_SIZE && src.hasRemaining()) { - _curEncrypted[_curEncryptedOffset++] = src.get(); - _received++; - } - // Decrypt the 16-byte block - if (_curEncryptedOffset >= AES_SIZE) { - _context.aes().decrypt(_curEncrypted, 0, _curDecrypted, 0, _dh.getSessionKey(), - _prevEncrypted, 0, AES_SIZE); - - byte swap[] = _prevEncrypted; - _prevEncrypted = _curEncrypted; - _curEncrypted = swap; - _curEncryptedOffset = 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(); - if (_state == State.VERIFIED && src.hasRemaining()) - prepareExtra(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 - + " extra=" + (_extra != null ? _extra.length : 0) + ")"); - return; - } - } else { - // 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 (" + - _curEncryptedOffset + ", " + _received + ")"); - } - } - - if (_log.shouldLog(Log.DEBUG)) - _log.debug(prefix()+"done with the data, not yet complete or corrupt"); - } + public void prepareOutbound(); /** - * 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. - * - * FIXME none of the _state comparisons use _stateLock, but whole thing - * is synchronized, should be OK. See isComplete() + * Was this connection failed because of clock skew? */ - 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 - while (_state == State.OB_SENT_X && src.hasRemaining()) { - byte c = src.get(); - _Y[_received++] = c; - if (_received >= XY_SIZE) { - 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); - } catch (DHSessionKeyBuilder.InvalidPublicParameterException e) { - _context.statManager().addRateData("ntcp.invalidDH", 1); - fail("Invalid X", e); - return; - } - } - } - - // Read in Y, which is the first part of message #2 - // Read in the rest of message #2 - while (_state == State.OB_GOT_Y && src.hasRemaining()) { - int i = _received-XY_SIZE; - _received++; - byte c = src.get(); - _e_hXY_tsB[i] = c; - if (i+1 >= HXY_TSB_PAD_SIZE) { - 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); - // 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 - XY_SIZE - HXY_TSB_PAD_SIZE; - 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(); - 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!"); - prepareExtra(src); - byte nextWriteIV[] = _curEncrypted; // reuse buf - 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 - _con.finishOutboundEstablishment(_dh.getSessionKey(), _peerSkew, nextWriteIV, _e_bobSig); // skew in seconds - 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()); - changeState(State.VERIFIED); - } - return; - } - } - } - } + public boolean getFailedBySkew(); /** did the handshake fail for some reason? */ - public boolean isCorrupt() { - synchronized(_stateLock) { - return _state == State.CORRUPT; - } - } + public boolean isCorrupt(); /** * If synchronized on this, fails with @@ -663,554 +41,22 @@ class EstablishState { * * @return is the handshake complete and valid? */ - public boolean isComplete() { - synchronized(_stateLock) { - return _state == State.VERIFIED; - } - } - - /** - * 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. - */ - public synchronized void prepareOutbound() { - boolean shouldSend; - synchronized(_stateLock) { - shouldSend = _state == State.OB_INIT; - } - if (shouldSend) { - 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 { - if (_log.shouldLog(Log.WARN)) - _log.warn(prefix() + "unexpected prepareOutbound()"); - } - } - - /** - * 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. - * - * _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. - * Caller must synch. - */ - private void verifyInbound() { - 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; - - ByteArrayOutputStream baos = new ByteArrayOutputStream(768); - 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()); - if (ok) { - // get inet-addr - InetAddress addr = this._con.getChannel().socket().getInetAddress(); - byte[] ip = (addr == null) ? null : addr.getAddress(); - if (_context.banlist().isBanlistedForever(_aliceIdent.calculateHash())) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Dropping inbound connection from permanently banlisted peer: " + _aliceIdent.calculateHash()); - // So next time we will not accept the con from this IP, - // rather than doing the whole handshake - if(ip != null) - _context.blocklist().add(ip); - fail("Peer is banlisted forever: " + _aliceIdent.calculateHash()); - return; - } - if(ip != null) - _transport.setIP(_aliceIdent.calculateHash(), ip); - if (_log.shouldLog(Log.DEBUG)) - _log.debug(prefix() + "verification successful for " + _con); - - long diff = 1000*Math.abs(_peerSkew); - if (!_context.clock().getUpdatedSuccessfully()) { - // Adjust the clock one time in desperation - // This isn't very likely, outbound will do it first - // We are Bob, she is Alice, adjust to match Alice - _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.invalidInboundSkew", diff); - _transport.markReachable(_aliceIdent.calculateHash(), true); - // Only banlist if we know what time it is - _context.banlist().banlistRouter(DataHelper.formatDuration(diff), - _aliceIdent.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"); - } - - _con.setRemotePeer(_aliceIdent); - sendInboundConfirm(_aliceIdent, 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 - _con.finishInboundEstablishment(_dh.getSessionKey(), _peerSkew, iv, _prevEncrypted); // skew in seconds - releaseBufs(true); - if (_log.shouldLog(Log.INFO)) - _log.info(prefix()+"Verified remote peer as " + _aliceIdent.calculateHash()); - changeState(State.VERIFIED); - } else { - _context.statManager().addRateData("ntcp.invalidInboundSignature", 1); - fail("Peer verification failed - spoof of " + _aliceIdent.calculateHash() + "?"); - } - } catch (IOException ioe) { - _context.statManager().addRateData("ntcp.invalidInboundIOE", 1); - fail("Error verifying peer", ioe); - } - } - - /** - * We are Bob. Send message #4 to Alice. - * - * State must be VERIFIED. - * Caller must synch. - */ - private void sendInboundConfirm(RouterIdentity alice, 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; - Hash h = alice.calculateHash(); - 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); - } - - /** Anything left over in the byte buffer after verification is extra - * - * All data must be copied out of the buffer as Reader.processRead() - * will return it to the pool. - * - * State must be VERIFIED. - * Caller must synch. - */ - private void prepareExtra(ByteBuffer buf) { - int remaining = buf.remaining(); - if (remaining > 0) { - _extra = new byte[remaining]; - buf.get(_extra); - _received += remaining; - } - if (_log.shouldLog(Log.DEBUG)) - _log.debug(prefix() + "prepare extra " + remaining + " (total received: " + _received + ")"); - } + public boolean isComplete(); /** * if complete, this will contain any bytes received as part of the * handshake that were after the actual handshake. This may return null. */ - public synchronized byte[] getExtraBytes() { return _extra; } + public byte[] getExtraBytes(); /** * Release resources on timeout. * @param e may be null * @since 0.9.16 */ - public synchronized void close(String reason, Exception e) { - fail(reason, e); - } + public void close(String reason, Exception e); - /** Caller must synch. */ - private void fail(String reason) { fail(reason, null); } - - /** Caller must synch. */ - private void fail(String reason, Exception e) { fail(reason, e, false); } - - /** Caller must synch. */ - private void fail(String reason, Exception e, boolean bySkew) { - synchronized(_stateLock) { - if (_state == State.CORRUPT || _state == State.VERIFIED) - return; - changeState(State.CORRUPT); - } - _failedBySkew = bySkew; - _err = reason; - _e = e; - if (_log.shouldLog(Log.WARN)) - _log.warn(prefix()+"Failed to establish: " + _err, e); - releaseBufs(false); - } - - /** - * Only call once. Caller must synch. - * @since 0.9.16 - */ - private void releaseBufs(boolean isVerified) { - // null or longer for OB - if (_prevEncrypted != null && _prevEncrypted.length == AES_SIZE) - SimpleByteCache.release(_prevEncrypted); - // Do not release _curEncrypted if verified, it is passed to - // NTCPConnection to use as the IV - if (!isVerified) - SimpleByteCache.release(_curEncrypted); - SimpleByteCache.release(_curDecrypted); - SimpleByteCache.release(_hX_xor_bobIdentHash); - if (_dh.getPeerPublicValue() == null) - _transport.returnUnused(_dh); - if (_con.isInbound()) - SimpleByteCache.release(_X); - else - SimpleByteCache.release(_Y); - } - - public synchronized String getError() { return _err; } - - public synchronized Exception getException() { return _e; } - - /** - * XOR a into b. Modifies b. a is unmodified. - * @param a 32 bytes - * @param b 32 bytes - * @since 0.9.12 - */ - private static void xor32(byte[] a, byte[] b) { - for (int i = 0; i < 32; i++) { - b[i] ^= a[i]; - } - } - - private String prefix() { return toString(); } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(64); - if (_con.isInbound()) - buf.append("IBES "); - else - buf.append("OBES "); - buf.append(System.identityHashCode(this)); - buf.append(' ').append(_state); - if (_con.isEstablished()) buf.append(" established"); - buf.append(": "); - return buf.toString(); - } - - /** - * @since 0.9.8 - */ - private static class VerifiedEstablishState extends EstablishState { - - public VerifiedEstablishState() { - super(); - _state = State.VERIFIED; - } - - @Override public void prepareOutbound() { - Log log =RouterContext.getCurrentContext().logManager().getLog(VerifiedEstablishState.class); - log.warn("prepareOutbound() on verified state, doing nothing!"); - } - - @Override public String toString() { return "VerifiedEstablishState: ";} - } - - /** - * @since 0.9.16 - */ - private static class FailedEstablishState extends EstablishState { - - public FailedEstablishState() { - super(); - _state = State.CORRUPT; - } - - @Override public void prepareOutbound() { - Log log =RouterContext.getCurrentContext().logManager().getLog(VerifiedEstablishState.class); - log.warn("prepareOutbound() on verified state, doing nothing!"); - } - - @Override public String toString() { return "FailedEstablishState: ";} - } - -/******* - public static void main(String args[]) { - if (args.length == 3) { - checkHost(args); - return; - } - I2PAppContext ctx = I2PAppContext.getGlobalContext(); - try { - java.net.Socket s = new java.net.Socket("localhost", 9094); - OutputStream out = s.getOutputStream(); - DHSessionKeyBuilder dh = new DHSessionKeyBuilder(); - byte X[] = dh.getMyPublicValueBytes(); - - // SEND X+(H(X) xor Bob.identHash)-----------------------------> - out.write(X); - System.out.println("sent X =" + Base64.encode(X)); - byte bih[] = Base64.decode("HuRdDx9t-RaZfYkYvacRwP~6s9mvbdkYzIMrpUCsZIo="); - System.out.println("bih = " + Base64.encode(bih)); - Hash hx = ctx.sha().calculateHash(X); - System.out.println("hx = " + Base64.encode(hx.getData())); - byte hx_xor_bih[] = DataHelper.xor(bih, hx.getData()); - System.out.println("xor = " + Base64.encode(hx_xor_bih)); - out.write(hx_xor_bih); - out.flush(); - // DONE SENDING X+(H(X) xor Bob.identHash)-----------------------------> - - // NOW READ Y+E(H(X+Y)+tsB+padding, sk, Y[239:255]) - InputStream in = s.getInputStream(); - byte toRead[] = new byte[256+(32+4+12)]; - int read = 0; - while (read < toRead.length) { - int r = in.read(toRead, read, toRead.length-read); - if (r == -1) - throw new EOFException("eof at read=" + read); - read += r; - } - byte Y[] = new byte[256]; - System.arraycopy(toRead, 0, Y, 0, Y.length); - dh.setPeerPublicValue(Y); - byte decrypted[] = new byte[(32+4+12)]; - ctx.aes().decrypt(toRead, Y.length, decrypted, 0, dh.getSessionKey(), Y, Y.length-16, decrypted.length); - //display y, encrypted, decrypted, hx+y, tsb, padding - //unencrypted H(X+Y)+tsB+padding: bSJIv1ynFw9MhIqbObOpCqeZxtFvKEx-ilcsZQ31zYNEnVXyHCZagLbdQYRmd1oq - System.out.println("dh session key: " + dh.getSessionKey().toBase64()); - System.out.println("decryption iv: " + Base64.encode(Y, Y.length-16, 16)); - System.out.println("Y = " + Base64.encode(Y)); - byte xy[] = new byte[512]; - System.arraycopy(X, 0, xy, 0, X.length); - System.arraycopy(Y, 0, xy, X.length, Y.length); - System.out.println("h(x+y): " + ctx.sha().calculateHash(xy).toBase64()); - System.out.println("encrypted H(X+Y)+tsB+padding: " + Base64.encode(toRead, Y.length, toRead.length-Y.length)); - System.out.println("unencrypted H(X+Y)+tsB+padding: " + Base64.encode(decrypted)); - long tsB = DataHelper.fromLong(decrypted, 32, 4); - - //try { Thread.sleep(40*1000); } catch (InterruptedException ie) {} - - RouterIdentity alice = new RouterIdentity(); - Object k[] = ctx.keyGenerator().generatePKIKeypair(); - PublicKey pub = (PublicKey)k[0]; - PrivateKey priv = (PrivateKey)k[1]; - k = ctx.keyGenerator().generateSigningKeypair(); - SigningPublicKey spub = (SigningPublicKey)k[0]; - SigningPrivateKey spriv = (SigningPrivateKey)k[1]; - alice.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null)); - alice.setPublicKey(pub); - alice.setSigningPublicKey(spub); - - // SEND E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB+padding), sk, hX_xor_Bob.identHash[16:31])---> - - ByteArrayOutputStream baos = new ByteArrayOutputStream(512); - byte aliceb[] = alice.toByteArray(); - long tsA = ctx.clock().now()/1000l; - baos.write(DataHelper.toLong(2, aliceb.length)); - baos.write(aliceb); - baos.write(DataHelper.toLong(4, tsA)); - - int base = baos.size() + Signature.SIGNATURE_BYTES; - int rem = base % 16; - int padding = 0; - if (rem > 0) - padding = 16 - rem; - byte pad[] = new byte[padding]; - ctx.random().nextBytes(pad); - baos.write(pad); - base += padding; - - ByteArrayOutputStream sbaos = new ByteArrayOutputStream(512); - sbaos.write(X); - sbaos.write(Y); - sbaos.write(bih); - sbaos.write(DataHelper.toLong(4, tsA)); - sbaos.write(DataHelper.toLong(4, tsB)); - //sbaos.write(pad); - Signature sig = ctx.dsa().sign(sbaos.toByteArray(), spriv); - baos.write(sig.toByteArray()); - - byte unencrypted[] = baos.toByteArray(); - byte toWrite[] = new byte[unencrypted.length]; - System.out.println("unencrypted.length = " + unencrypted.length + " alice.size = " + aliceb.length + " padding = " + padding + " base = " + base); - ctx.aes().encrypt(unencrypted, 0, toWrite, 0, dh.getSessionKey(), hx_xor_bih, 16, unencrypted.length); - - out.write(toWrite); - out.flush(); - - System.out.println("unencrypted: " + Base64.encode(unencrypted)); - System.out.println("encrypted: " + Base64.encode(toWrite)); - System.out.println("Local peer: " + alice.calculateHash().toBase64()); - - // now check bob's signature - - SigningPublicKey bobPubKey = null; - try { - RouterInfo info = new RouterInfo(); - info.readBytes(new FileInputStream("/home/jrandom/routers/router1/netDb/routerInfo-HuRdDx9t-RaZfYkYvacRwP~6s9mvbdkYzIMrpUCsZIo=.dat")); - bobPubKey = info.getIdentity().getSigningPublicKey(); - } catch (Exception e) { - e.printStackTrace(); - return; - } - - System.out.println("Reading in bob's sig"); - - byte bobRead[] = new byte[48]; - read = 0; - while (read < bobRead.length) { - int r = in.read(bobRead, read, bobRead.length-read); - if (r == -1) - throw new EOFException("eof at read=" + read); - read += r; - } - - // bob should have sent E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) - byte preSig[] = new byte[Signature.SIGNATURE_BYTES+8]; - ctx.aes().decrypt(bobRead, 0, preSig, 0, dh.getSessionKey(), toRead, toRead.length-16, preSig.length); - byte bobSigData[] = new byte[Signature.SIGNATURE_BYTES]; - System.arraycopy(preSig, 0, bobSigData, 0, Signature.SIGNATURE_BYTES); // ignore the padding - System.out.println("Bob's sig: " + Base64.encode(bobSigData)); - - byte signed[] = new byte[256+256+32+4+4]; - int off = 0; - System.arraycopy(X, 0, signed, off, 256); off += 256; - System.arraycopy(Y, 0, signed, off, 256); off += 256; - Hash h = alice.calculateHash(); - System.arraycopy(h.getData(), 0, signed, off, 32); off += 32; - DataHelper.toLong(signed, off, 4, tsA); off += 4; - DataHelper.toLong(signed, off, 4, tsB); off += 4; - - Signature bobSig = new Signature(bobSigData); - boolean ok = ctx.dsa().verifySignature(bobSig, signed, bobPubKey); - - System.out.println("bob's sig matches? " + ok); - - try { Thread.sleep(5*1000); } catch (InterruptedException ie) {} - byte fakeI2NPbuf[] = new byte[128]; - ctx.random().nextBytes(fakeI2NPbuf); - out.write(fakeI2NPbuf); - out.flush(); - - try { Thread.sleep(30*1000); } catch (InterruptedException ie) {} - s.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } -*******/ - - /** - * Mark a string for extraction by xgettext and translation. - * Use this only in static initializers. - * It does not translate! - * @return s - */ - private static final String _x(String s) { - return s; - } + public String getError(); + public Exception getException(); } diff --git a/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java new file mode 100644 index 0000000000000000000000000000000000000000..bd899b43d8bacce79ffb3183c5e064b6cf5dbea0 --- /dev/null +++ b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java @@ -0,0 +1,486 @@ +package net.i2p.router.transport.ntcp; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.nio.ByteBuffer; + +import net.i2p.crypto.SigType; +import net.i2p.data.Base64; +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; + +/** + * + * We are Bob + * + */ +class InboundEstablishState extends EstablishBase { + + /** + * next index in _curEncrypted to write to (equals _curEncrypted length if the block is + * ready to decrypt) + */ + private int _curEncryptedOffset; + + /** current encrypted block we are reading (IB only) or an IV buf used at the end for OB */ + private byte _curEncrypted[]; + + private int _aliceIdentSize; + private RouterIdentity _aliceIdent; + + /** contains the decrypted aliceIndexSize + aliceIdent + tsA + padding + aliceSig */ + private ByteArrayOutputStream _sz_aliceIdent_tsA_padding_aliceSig; + + /** how long we expect _sz_aliceIdent_tsA_padding_aliceSig to be when its full */ + private int _sz_aliceIdent_tsA_padding_aliceSigSize; + + public InboundEstablishState(RouterContext ctx, NTCPTransport transport, NTCPConnection con) { + super(ctx, transport, con); + _state = State.IB_INIT; + _sz_aliceIdent_tsA_padding_aliceSig = new ByteArrayOutputStream(512); + _prevEncrypted = SimpleByteCache.acquire(AES_SIZE); + _curEncrypted = SimpleByteCache.acquire(AES_SIZE); + } + + /** + * parse the contents of the buffer as part of the handshake. if the + * handshake is completed and there is more data remaining, the data are + * copieed out so that the next read will be the (still encrypted) remaining + * data (available from getExtraBytes) + * + * All data must be copied out of the buffer as Reader.processRead() + * will return it to the pool. + */ + @Override + public synchronized void receive(ByteBuffer src) { + super.receive(src); + if (!src.hasRemaining()) + return; // nothing to receive + receiveInbound(src); + } + + /** + * we are Bob, so receive these bytes as part of an inbound connection + * This method receives messages 1 and 3, and sends messages 2 and 4. + * + * All data must be copied out of the buffer as Reader.processRead() + * will return it to the pool. + * + * Caller must synch. + * + * FIXME none of the _state comparisons use _stateLock, but whole thing + * is synchronized, should be OK. See isComplete() + */ + private void receiveInbound(ByteBuffer src) { + while (_state == State.IB_INIT && src.hasRemaining()) { + byte c = src.get(); + _X[_received++] = c; + if (_received >= XY_SIZE) + changeState(State.IB_GOT_X); + } + while (_state == State.IB_GOT_X && src.hasRemaining()) { + int i = _received - XY_SIZE; + _received++; + byte c = src.get(); + _hX_xor_bobIdentHash[i] = c; + if (i >= HXY_SIZE - 1) + changeState(State.IB_GOT_HX); + } + + 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; + } + + } + + // ok, we are onto the encrypted area, i.e. Message #3 + while ((_state == State.IB_SENT_Y || + _state == State.IB_GOT_RI_SIZE || + _state == State.IB_GOT_RI) && src.hasRemaining()) { + + // Collect a 16-byte block + while (_curEncryptedOffset < AES_SIZE && src.hasRemaining()) { + _curEncrypted[_curEncryptedOffset++] = src.get(); + _received++; + } + // Decrypt the 16-byte block + if (_curEncryptedOffset >= AES_SIZE) { + _context.aes().decrypt(_curEncrypted, 0, _curDecrypted, 0, _dh.getSessionKey(), + _prevEncrypted, 0, AES_SIZE); + + byte swap[] = _prevEncrypted; + _prevEncrypted = _curEncrypted; + _curEncrypted = swap; + _curEncryptedOffset = 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(); + if (_state == State.VERIFIED && src.hasRemaining()) + prepareExtra(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; + } + } else { + // 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 (" + + _curEncryptedOffset + ", " + _received + ")"); + } + } + + 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. + * + * _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. + * Caller must synch. + */ + private void verifyInbound() { + 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; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(768); + 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()); + if (ok) { + // get inet-addr + InetAddress addr = this._con.getChannel().socket().getInetAddress(); + byte[] ip = (addr == null) ? null : addr.getAddress(); + if (_context.banlist().isBanlistedForever(_aliceIdent.calculateHash())) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Dropping inbound connection from permanently banlisted peer: " + _aliceIdent.calculateHash()); + // So next time we will not accept the con from this IP, + // rather than doing the whole handshake + if(ip != null) + _context.blocklist().add(ip); + fail("Peer is banlisted forever: " + _aliceIdent.calculateHash()); + return; + } + if(ip != null) + _transport.setIP(_aliceIdent.calculateHash(), ip); + if (_log.shouldLog(Log.DEBUG)) + _log.debug(prefix() + "verification successful for " + _con); + + long diff = 1000*Math.abs(_peerSkew); + if (!_context.clock().getUpdatedSuccessfully()) { + // Adjust the clock one time in desperation + // This isn't very likely, outbound will do it first + // We are Bob, she is Alice, adjust to match Alice + _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.invalidInboundSkew", diff); + _transport.markReachable(_aliceIdent.calculateHash(), true); + // Only banlist if we know what time it is + _context.banlist().banlistRouter(DataHelper.formatDuration(diff), + _aliceIdent.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"); + } + + _con.setRemotePeer(_aliceIdent); + sendInboundConfirm(_aliceIdent, 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 + _con.finishInboundEstablishment(_dh.getSessionKey(), _peerSkew, iv, _prevEncrypted); // skew in seconds + releaseBufs(true); + if (_log.shouldLog(Log.INFO)) + _log.info(prefix()+"Verified remote peer as " + _aliceIdent.calculateHash()); + changeState(State.VERIFIED); + } else { + _context.statManager().addRateData("ntcp.invalidInboundSignature", 1); + fail("Peer verification failed - spoof of " + _aliceIdent.calculateHash() + "?"); + } + } catch (IOException ioe) { + _context.statManager().addRateData("ntcp.invalidInboundIOE", 1); + fail("Error verifying peer", ioe); + } + } + + /** + * We are Bob. Send message #4 to Alice. + * + * State must be VERIFIED. + * Caller must synch. + */ + private void sendInboundConfirm(RouterIdentity alice, 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; + Hash h = alice.calculateHash(); + 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); + } + + /** + * Only call once. Caller must synch. + * @since 0.9.16 + */ + @Override + protected void releaseBufs(boolean isVerified) { + super.releaseBufs(isVerified); + // Do not release _curEncrypted if verified, it is passed to + // NTCPConnection to use as the IV + if (!isVerified) + SimpleByteCache.release(_curEncrypted); + SimpleByteCache.release(_X); + } +} 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 5c0f3992bf595df0eb04c7e661cca05b05659351..65fa325534241f0de69add1445073f3e860a1e39 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java @@ -190,7 +190,7 @@ public class NTCPConnection implements Closeable { _isInbound = true; _decryptBlockBuf = new byte[BLOCK_SIZE]; _curReadState = new ReadState(); - _establishState = new EstablishState(ctx, transport, this); + _establishState = new InboundEstablishState(ctx, transport, this); _conKey = key; _conKey.attach(this); _inboundListener = new InboundListener(); @@ -216,7 +216,7 @@ public class NTCPConnection implements Closeable { //_outbound = new CoDelPriorityBlockingQueue(ctx, "NTCP-Connection", 32); _outbound = new PriBlockingQueue<OutNetMessage>(ctx, "NTCP-Connection", 32); _isInbound = false; - _establishState = new EstablishState(ctx, transport, this); + _establishState = new OutboundEstablishState(ctx, transport, this); _decryptBlockBuf = new byte[BLOCK_SIZE]; _curReadState = new ReadState(); _inboundListener = new InboundListener(); @@ -309,7 +309,7 @@ public class NTCPConnection implements Closeable { 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 = EstablishState.VERIFIED; + _establishState = EstablishBase.VERIFIED; return rv; } @@ -427,7 +427,7 @@ public class NTCPConnection implements Closeable { private synchronized NTCPConnection locked_close(boolean allowRequeue) { if (_chan != null) try { _chan.close(); } catch (IOException ioe) { } if (_conKey != null) _conKey.cancel(); - _establishState = EstablishState.FAILED; + _establishState = EstablishBase.FAILED; NTCPConnection old = _transport.removeCon(this); _transport.getReader().connectionClosed(this); _transport.getWriter().connectionClosed(this); @@ -532,7 +532,7 @@ public class NTCPConnection implements Closeable { _log.debug("Outbound established, prevWriteEnd: " + Base64.encode(prevWriteEnd) + " prevReadEnd: " + Base64.encode(prevReadEnd)); _establishedOn = _context.clock().now(); - _establishState = EstablishState.VERIFIED; + _establishState = EstablishBase.VERIFIED; _transport.markReachable(getRemotePeer().calculateHash(), false); boolean msgs = !_outbound.isEmpty(); _nextMetaTime = _establishedOn + (META_FREQUENCY / 2) + _context.random().nextInt(META_FREQUENCY); diff --git a/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java new file mode 100644 index 0000000000000000000000000000000000000000..12decc559908d170ba13e9eeb6242a6dcdf5ef3f --- /dev/null +++ b/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java @@ -0,0 +1,290 @@ +package net.i2p.router.transport.ntcp; + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.ByteBuffer; + +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; + +/** + * + * We are Alice + * + */ +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. if the + * handshake is completed and there is more data remaining, the data are + * copieed out so that the next read will be the (still encrypted) remaining + * data (available from getExtraBytes) + * + * All data must be copied out of the buffer as Reader.processRead() + * will return it to the pool. + */ + @Override + public synchronized void receive(ByteBuffer src) { + super.receive(src); + if (!src.hasRemaining()) + return; // nothing to receive + receiveOutbound(src); + } + + /** + * 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. + * + * FIXME none of the _state comparisons use _stateLock, but whole thing + * is synchronized, should be OK. See isComplete() + */ + 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 + while (_state == State.OB_SENT_X && src.hasRemaining()) { + byte c = src.get(); + _Y[_received++] = c; + if (_received >= XY_SIZE) { + 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); + } catch (DHSessionKeyBuilder.InvalidPublicParameterException e) { + _context.statManager().addRateData("ntcp.invalidDH", 1); + fail("Invalid X", e); + return; + } + } + } + + // Read in Y, which is the first part of message #2 + // Read in the rest of message #2 + while (_state == State.OB_GOT_Y && src.hasRemaining()) { + int i = _received-XY_SIZE; + _received++; + byte c = src.get(); + _e_hXY_tsB[i] = c; + if (i+1 >= HXY_TSB_PAD_SIZE) { + 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); + // 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 - XY_SIZE - HXY_TSB_PAD_SIZE; + 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(); + 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!"); + prepareExtra(src); + 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 + _con.finishOutboundEstablishment(_dh.getSessionKey(), _peerSkew, nextWriteIV, _e_bobSig); // skew in seconds + 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()); + changeState(State.VERIFIED); + } + return; + } + } + } + } + + /** + * 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. + */ + public synchronized void prepareOutbound() { + boolean shouldSend; + synchronized(_stateLock) { + shouldSend = _state == State.OB_INIT; + } + if (shouldSend) { + 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 { + if (_log.shouldLog(Log.WARN)) + _log.warn(prefix() + "unexpected prepareOutbound()"); + } + } + + /** + * Only call once. Caller must synch. + * @since 0.9.16 + */ + @Override + protected void releaseBufs(boolean isVerified) { + super.releaseBufs(isVerified); + SimpleByteCache.release(_Y); + } +}