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. * */ 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; private static final int MIN_RI_SIZE = 387; private static final int MAX_RI_SIZE = 2048; 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 * 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 (!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; } /** * 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 (_log.shouldLog(Log.DEBUG)) _log.debug("recv x" + (int)c + " received=" + _received); //if (_received >= _X.length) { // if (isCheckInfo(_context, _context.routerHash(), _X)) { // _context.statManager().addRateData("ntcp.inboundCheckConnection", 1); // fail("Incoming connection was a check connection"); // return; // } //} 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 (_log.shouldLog(Log.DEBUG)) _log.debug("recv bih" + (int)c + " received=" + _received); 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 (_log.shouldLog(Log.DEBUG)) { //_log.debug(prefix()+"_X = " + Base64.encode(_X)); // _log.debug(prefix()+"hx = " + Base64.encode(hX.getData())); // _log.debug(prefix()+"bih=" + Base64.encode(_context.routerHash().getData())); // _log.debug(prefix()+"xor=" + Base64.encode(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); //DataHelper.toLong(toEncrypt, hxy.getData().length, 4, _tsB); _context.random().nextBytes(toEncrypt, HXY_SIZE + 4, 12); if (_log.shouldLog(Log.DEBUG)) { //_log.debug(prefix()+"Y="+Base64.encode(_Y)); //_log.debug(prefix()+"x+y="+Base64.encode(xy)); _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()) { //if (_log.shouldLog(Log.DEBUG)) // _log.debug(prefix()+"Encrypted bytes available (" + 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); //if (_log.shouldLog(Log.DEBUG)) // _log.debug(prefix() + "full block read and decrypted: "); 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 (_log.shouldLog(Log.DEBUG)) // _log.debug(prefix()+"subsequent block decrypted (" + _sz_aliceIdent_tsA_padding_aliceSig.size() + ")"); 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"); } /** * 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 (_log.shouldLog(Log.DEBUG)) _log.debug("recv x" + (int)c + " received=" + _received); 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 (_log.shouldLog(Log.DEBUG)) // _log.debug(prefix() + "recv _e_hXY_tsB " + (int)c + " received=" + _received); 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 (_log.shouldLog(Log.DEBUG)) // _log.debug(prefix() + "h(XY)=" + h.toBase64()); 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) //System.arraycopy(hXY_tsB, hXY_tsB.length-12, preSign, _X.length+_Y.length+Hash.HASH_LENGTH+4+4, 12); //byte sigPad[] = new byte[padSig]; //_context.random().nextBytes(sigPad); //System.arraycopy(sigPad, 0, preSign, _X.length+_Y.length+Hash.HASH_LENGTH+4+4, padSig); Signature sig = _context.dsa().sign(preSign, _context.keyManager().getSigningPrivateKey()); //if (_log.shouldLog(Log.DEBUG)) { // _log.debug(prefix()+"signing " + Base64.encode(preSign)); //} 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); //if (_log.shouldLog(Log.DEBUG)) { //_log.debug(prefix() + "unencrypted response to Bob: " + Base64.encode(preEncrypt)); //_log.debug(prefix() + "encrypted response to Bob: " + Base64.encode(_prevEncrypted)); //} // send 'er off (when the bw limiter says, etc) 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()) { //if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix()+"recv bobSig received=" + _received); _e_bobSig[off++] = src.get(); _received++; if (off >= _e_bobSig.length) { changeState(State.OB_GOT_SIG); //if (_log.shouldLog(Log.DEBUG)) // _log.debug(prefix() + "received E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev): " + Base64.encode(_e_bobSig)); 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; } } } } /** 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; } } /** * 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(); //if (_log.shouldLog(Log.DEBUG)) // _log.debug(prefix()+"decrypted sz(etc) data: " + Base64.encode(b)); 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(); //if (_log.shouldLog(Log.DEBUG)) { // _log.debug(prefix()+"checking " + Base64.encode(toVerify, 0, AES_SIZE)); // //_log.debug(prefix()+"check pad " + Base64.encode(b, 2+sz+4, 12)); //} // 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 + ")"); } /** * 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. */ 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(); } /** * a check info connection will receive 256 bytes containing: * - 32 bytes of uninterpreted, ignored data * - 1 byte size * - that many bytes making up the local router's IP address (as reached by the remote side) * - 2 byte port number that the local router was reached on * - 4 byte i2p network time as known by the remote side (seconds since the epoch) * - uninterpreted padding data, up to byte 223 * - xor of the local router's identity hash and the SHA256 of bytes 32 through bytes 223 * * @return should always be false since nobody ever sends a check info message * */ /***** private static boolean isCheckInfo(I2PAppContext ctx, Hash us, byte first256[]) { Log log = ctx.logManager().getLog(EstablishState.class); int off = 32; // ignore the first 32 bytes byte[] xor = SimpleByteCache.acquire(Hash.HASH_LENGTH); ctx.sha().calculateHash(first256, off, first256.length-32-off, xor, 0); xor32(us.getData(), xor); //if (log.shouldLog(Log.DEBUG)) // log.debug("check hash: " + h.toBase64() + " xor: " + Base64.encode(xor)); if (DataHelper.eq(xor, 0, first256, first256.length-32, 32)) { SimpleByteCache.release(xor); // ok, data is as expected // parse our IP/port/etc out of the first256 int ipSize = (int)DataHelper.fromLong(first256, off, 1); off++; byte ip[] = new byte[ipSize]; System.arraycopy(first256, off, ip, 0, ipSize); try { InetAddress ourIP = InetAddress.getByAddress(ip); off += ipSize; int port = (int)DataHelper.fromLong(first256, off, 2); off += 2; long now = DataHelper.fromLong(first256, off, 4); off += 4; long skewSeconds = (ctx.clock().now()/1000)-now; if (log.shouldLog(Log.INFO)) log.info("Check info received: our IP: " + ourIP + " our port: " + port + " skew: " + skewSeconds + " s"); } catch (UnknownHostException uhe) { // ipSize is invalid if (log.shouldLog(Log.WARN)) log.warn("Invalid IP received on check connection (size: " + ipSize + ")"); } return true; } else { SimpleByteCache.release(xor); if (log.shouldLog(Log.DEBUG)) log.debug("Not a checkInfo connection"); return false; } } *****/ /** * @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: ";} } /** @deprecated unused */ /********* public static void checkHost(String args[]) { if (args.length != 3) { System.err.println("Usage: EstablishState ipOrHostname portNum peerHashBase64"); return; } try { I2PAppContext ctx = I2PAppContext.getGlobalContext(); String host = args[0]; int port = Integer.parseInt(args[1]); byte peer[] = Base64.decode(args[2]); Socket s = new Socket(host, port); OutputStream out = s.getOutputStream(); byte toSend[] = new byte[256]; ctx.random().nextBytes(toSend); int off = 32; byte ip[] = s.getInetAddress().getAddress(); DataHelper.toLong(toSend, off, 1, ip.length); off++; System.arraycopy(ip, 0, toSend, off, ip.length); off += ip.length; DataHelper.toLong(toSend, off, 2, port); off += 2; long now = ctx.clock().now()/1000; DataHelper.toLong(toSend, off, 4, now); off += 4; Hash h = ctx.sha().calculateHash(toSend, 32, toSend.length-32-32); DataHelper.xor(peer, 0, h.getData(), 0, toSend, toSend.length-32, peer.length); System.out.println("check hash: " + h.toBase64()); out.write(toSend); out.flush(); try { Thread.sleep(1000); } catch (InterruptedException ie) {} s.close(); } catch (Exception e) { e.printStackTrace(); } } *******/ /******* 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; } }