I2P Address: [http://git.idk.i2p]

Skip to content
Snippets Groups Projects
Verified Commit 6a54af39 authored by zzz's avatar zzz
Browse files

NTCP: Remove NTCP 1 suppport

Remove individual 1/2 enable config
Additional cleanup to follow
parent 82b7eea5
No related branches found
No related tags found
No related merge requests found
......@@ -94,12 +94,6 @@ public class TransportManager implements TransportEventListener {
private static final String PROP_JAVA_PROXY3 = "http.proxyHost";
private static final String PROP_JAVA_PROXY4 = "https.proxyHost";
/** default true */
private static final String PROP_NTCP1_ENABLE = "i2np.ntcp1.enable";
private static final boolean DEFAULT_NTCP1_ENABLE = false;
private static final String PROP_NTCP2_ENABLE = "i2np.ntcp2.enable";
private static final boolean DEFAULT_NTCP2_ENABLE = true;
private static final String PROP_ADVANCED = "routerconsole.advanced";
/** not forever, since they may update */
......@@ -125,10 +119,8 @@ public class TransportManager implements TransportEventListener {
_upnpManager = enableUPnP ? new UPnPManager(context, this) : null;
_upnpRefresher = enableUPnP ? new UPnPRefresher() : null;
_enableUDP = _context.getBooleanPropertyDefaultTrue(PROP_ENABLE_UDP);
_enableNTCP1 = isNTCPEnabled(context) &&
context.getProperty(PROP_NTCP1_ENABLE, DEFAULT_NTCP1_ENABLE);
boolean enableNTCP2 = isNTCPEnabled(context) &&
context.getProperty(PROP_NTCP2_ENABLE, DEFAULT_NTCP2_ENABLE);
_enableNTCP1 = false;
boolean enableNTCP2 = isNTCPEnabled(context);
_dhThread = (_enableUDP || enableNTCP2) ? new DHSessionKeyBuilder.PrecalcRunner(context) : null;
// always created, even if NTCP2 is not enabled, because ratchet needs it
_xdhThread = new X25519KeyFactory(context);
......@@ -268,10 +260,7 @@ public class TransportManager implements TransportEventListener {
initializeAddress(udp);
}
if (isNTCPEnabled(_context)) {
DHSessionKeyBuilder.PrecalcRunner dh = _enableNTCP1 ? _dhThread : null;
boolean enableNTCP2 = _context.getProperty(PROP_NTCP2_ENABLE, DEFAULT_NTCP2_ENABLE);
X25519KeyFactory xdh = enableNTCP2 ? _xdhThread : null;
Transport ntcp = new NTCPTransport(_context, dh, xdh);
Transport ntcp = new NTCPTransport(_context, null, _xdhThread);
addTransport(ntcp);
initializeAddress(ntcp);
if (udp != null) {
......@@ -292,9 +281,7 @@ public class TransportManager implements TransportEventListener {
}
public static boolean isNTCPEnabled(RouterContext ctx) {
return ctx.getBooleanPropertyDefaultTrue(PROP_ENABLE_NTCP) &&
(ctx.getProperty(PROP_NTCP1_ENABLE, DEFAULT_NTCP1_ENABLE) ||
ctx.getProperty(PROP_NTCP2_ENABLE, DEFAULT_NTCP2_ENABLE));
return ctx.getBooleanPropertyDefaultTrue(PROP_ENABLE_NTCP);
}
/**
......
......@@ -164,8 +164,8 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
_log.warn("Short buffer got " + remaining + " total now " + _received + " on " + this);
return;
}
if (remaining + _received < NTCP1_MSG1_SIZE ||
!_transport.isNTCP1Enabled()) {
//if (remaining + _received < NTCP1_MSG1_SIZE ||
// !_transport.isNTCP1Enabled()) {
// Less than 288 total received, assume NTCP2
// TODO can't change our mind later if we get more than 287
_con.setVersion(2);
......@@ -173,343 +173,8 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
receiveInboundNTCP2(src);
// releaseBufs() will return the unused DH
return;
}
}
int toGet = Math.min(remaining, XY_SIZE - _received);
src.get(_X, _received, toGet);
_received += toGet;
if (_received < XY_SIZE)
return;
changeState(State.IB_GOT_X);
_received = 0;
}
if (_state == State.IB_GOT_X && src.hasRemaining()) {
int toGet = Math.min(src.remaining(), HXY_SIZE - _received);
src.get(_hX_xor_bobIdentHash, _received, toGet);
_received += toGet;
if (_received < HXY_SIZE)
return;
changeState(State.IB_GOT_HX);
_received = 0;
}
if (_state == State.IB_GOT_HX) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"Enough data for a DH received");
// first verify that Alice knows who she is trying to talk with and that the X
// isn't corrupt
byte[] realXor = SimpleByteCache.acquire(HXY_SIZE);
_context.sha().calculateHash(_X, 0, XY_SIZE, realXor, 0);
xor32(_context.routerHash().getData(), realXor);
if (!DataHelper.eq(realXor, _hX_xor_bobIdentHash)) {
SimpleByteCache.release(realXor);
_context.statManager().addRateData("ntcp.invalidHXxorBIH", 1);
fail("Invalid hX_xor");
return;
}
SimpleByteCache.release(realXor);
if (!_transport.isHXHIValid(_hX_xor_bobIdentHash)) {
// blocklist source? but spoofed IPs could DoS us
_context.statManager().addRateData("ntcp.replayHXxorBIH", 1);
fail("Replay hX_xor");
return;
}
try {
// ok, they're actually trying to talk to us, and we got their (unauthenticated) X
_dh.setPeerPublicValue(_X);
_dh.getSessionKey(); // force the calc
System.arraycopy(_hX_xor_bobIdentHash, AES_SIZE, _prevEncrypted, 0, AES_SIZE);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"DH session key calculated (" + _dh.getSessionKey().toBase64() + ")");
// now prepare our response: Y+E(H(X+Y)+tsB+padding, sk, Y[239:255])
byte xy[] = new byte[XY_SIZE + XY_SIZE];
System.arraycopy(_X, 0, xy, 0, XY_SIZE);
System.arraycopy(_Y, 0, xy, XY_SIZE, XY_SIZE);
byte[] hxy = SimpleByteCache.acquire(HXY_SIZE);
_context.sha().calculateHash(xy, 0, XY_SIZE + XY_SIZE, hxy, 0);
// our (Bob's) timestamp in seconds
_tsB = (_context.clock().now() + 500) / 1000l;
byte toEncrypt[] = new byte[HXY_TSB_PAD_SIZE]; // 48
System.arraycopy(hxy, 0, toEncrypt, 0, HXY_SIZE);
byte tsB[] = DataHelper.toLong(4, _tsB);
System.arraycopy(tsB, 0, toEncrypt, HXY_SIZE, tsB.length);
_context.random().nextBytes(toEncrypt, HXY_SIZE + 4, 12);
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(prefix()+"h(x+y)="+Base64.encode(hxy));
_log.debug(prefix() + "tsb = " + _tsB);
_log.debug(prefix()+"unencrypted H(X+Y)+tsB+padding: " + Base64.encode(toEncrypt));
_log.debug(prefix()+"encryption iv= " + Base64.encode(_Y, XY_SIZE-AES_SIZE, AES_SIZE));
_log.debug(prefix()+"encryption key= " + _dh.getSessionKey().toBase64());
}
SimpleByteCache.release(hxy);
_context.aes().encrypt(toEncrypt, 0, _e_hXY_tsB, 0, _dh.getSessionKey(),
_Y, XY_SIZE-AES_SIZE, HXY_TSB_PAD_SIZE);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"encrypted H(X+Y)+tsB+padding: " + Base64.encode(_e_hXY_tsB));
byte write[] = new byte[XY_SIZE + HXY_TSB_PAD_SIZE];
System.arraycopy(_Y, 0, write, 0, XY_SIZE);
System.arraycopy(_e_hXY_tsB, 0, write, XY_SIZE, HXY_TSB_PAD_SIZE);
// ok, now that is prepared, we want to actually send it, so make sure we are up for writing
changeState(State.IB_SENT_Y);
_transport.getPumper().wantsWrite(_con, write);
if (!src.hasRemaining()) return;
} catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
_context.statManager().addRateData("ntcp.invalidDH", 1);
fail("Invalid X", e);
return;
} catch (IllegalStateException ise) {
// setPeerPublicValue()
fail("reused keys?", ise);
return;
}
}
// ok, we are onto the encrypted area, i.e. Message #3
while (STATES_MSG3.contains(_state) && src.hasRemaining()) {
// Collect a 16-byte block
if (_received < AES_SIZE && src.hasRemaining()) {
int toGet = Math.min(src.remaining(), AES_SIZE - _received);
src.get(_curEncrypted, _received, toGet);
_received += toGet;
if (_received < AES_SIZE) {
// no more bytes available in the buffer, and only a partial
// block was read, so we can't decrypt it.
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "end of available data with only a partial block read (" +
+ _received + ")");
return;
}
}
// Decrypt the 16-byte block
if (_received >= AES_SIZE) {
_context.aes().decrypt(_curEncrypted, 0, _curDecrypted, 0, _dh.getSessionKey(),
_prevEncrypted, 0, AES_SIZE);
byte swap[] = _prevEncrypted;
_prevEncrypted = _curEncrypted;
_curEncrypted = swap;
_received = 0;
if (_state == State.IB_SENT_Y) { // we are on the first decrypted block
int sz = (int)DataHelper.fromLong(_curDecrypted, 0, 2);
if (sz < MIN_RI_SIZE || sz > MAX_RI_SIZE) {
_context.statManager().addRateData("ntcp.invalidInboundSize", sz);
fail("size is invalid", new Exception("size is " + sz));
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "got the RI size: " + sz);
_aliceIdentSize = sz;
changeState(State.IB_GOT_RI_SIZE);
// We must defer the calculations for total size of the message until
// we get the full alice ident so
// we can determine how long the signature is.
// See below
}
try {
_sz_aliceIdent_tsA_padding_aliceSig.write(_curDecrypted);
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe);
}
if (_state == State.IB_GOT_RI_SIZE &&
_sz_aliceIdent_tsA_padding_aliceSig.size() >= 2 + _aliceIdentSize) {
// we have enough to get Alice's RI and determine the sig+padding length
readAliceRouterIdentity();
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "got the RI");
if (_aliceIdent == null) {
// readAliceRouterIdentity already called fail
return;
}
SigType type = _aliceIdent.getSigningPublicKey().getType();
if (type == null) {
fail("Unsupported sig type");
return;
}
changeState(State.IB_GOT_RI);
// handle variable signature size
_sz_aliceIdent_tsA_padding_aliceSigSize = 2 + _aliceIdentSize + 4 + type.getSigLen();
int rem = (_sz_aliceIdent_tsA_padding_aliceSigSize % AES_SIZE);
int padding = 0;
if (rem > 0)
padding = AES_SIZE-rem;
_sz_aliceIdent_tsA_padding_aliceSigSize += padding;
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "alice ident size decrypted as " + _aliceIdentSize +
", making the padding at " + padding + " and total size at " +
_sz_aliceIdent_tsA_padding_aliceSigSize);
}
if (_state == State.IB_GOT_RI &&
_sz_aliceIdent_tsA_padding_aliceSig.size() >= _sz_aliceIdent_tsA_padding_aliceSigSize) {
// we have the remainder of Message #3, i.e. the padding+signature
// Time to verify.
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "got the sig");
verifyInbound(src);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"verifying size (sz=" + _sz_aliceIdent_tsA_padding_aliceSig.size()
+ " expected=" + _sz_aliceIdent_tsA_padding_aliceSigSize
+ ' ' + _state
+ ')');
return;
}
}
}
// check for remaining data
if (STATES_DONE.contains(_state) && src.hasRemaining()) {
if (_log.shouldWarn())
_log.warn("Received unexpected " + src.remaining() + " on " + this, new Exception());
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"done with the data, not yet complete or corrupt");
}
/**
* We are Bob. We have received enough of message #3 from Alice
* to get Alice's RouterIdentity.
*
* _aliceIdentSize must be set.
* _sz_aliceIdent_tsA_padding_aliceSig must contain at least 2 + _aliceIdentSize bytes.
*
* Sets _aliceIdent so that we
* may determine the signature and padding sizes.
*
* After all of message #3 is received including the signature and
* padding, verifyIdentity() must be called.
*
* State must be IB_GOT_RI_SIZE.
* Caller must synch.
*
* @since 0.9.16 pulled out of verifyInbound()
*/
private void readAliceRouterIdentity() {
byte b[] = _sz_aliceIdent_tsA_padding_aliceSig.toByteArray();
try {
int sz = _aliceIdentSize;
if (sz < MIN_RI_SIZE || sz > MAX_RI_SIZE ||
sz > b.length-2) {
_context.statManager().addRateData("ntcp.invalidInboundSize", sz);
fail("size is invalid", new Exception("size is " + sz));
return;
//}
}
RouterIdentity alice = new RouterIdentity();
ByteArrayInputStream bais = new ByteArrayInputStream(b, 2, sz);
alice.readBytes(bais);
_aliceIdent = alice;
} catch (IOException ioe) {
_context.statManager().addRateData("ntcp.invalidInboundIOE", 1);
fail("Error verifying peer", ioe);
} catch (DataFormatException dfe) {
_context.statManager().addRateData("ntcp.invalidInboundDFE", 1);
fail("Error verifying peer", dfe);
}
}
/**
* We are Bob. Verify message #3 from Alice, then send message #4 to Alice.
* NTCP 1 only.
*
* _aliceIdentSize and _aliceIdent must be set.
* _sz_aliceIdent_tsA_padding_aliceSig must contain at least
* (2 + _aliceIdentSize + 4 + padding + sig) bytes.
*
* Sets _aliceIdent so that we
*
* readAliceRouterIdentity() must have been called previously
*
* Make sure the signatures are correct, and if they are, update the
* NIOConnection with the session key / peer ident / clock skew / iv.
* The NIOConnection itself is responsible for registering with the
* transport
*
* State must be IB_GOT_RI.
* This will always change the state to VERIFIED or CORRUPT.
* Caller must synch.
*
* @param buf possibly containing "extra" data for data phase
*/
private void verifyInbound(ByteBuffer buf) {
byte b[] = _sz_aliceIdent_tsA_padding_aliceSig.toByteArray();
try {
int sz = _aliceIdentSize;
// her timestamp from message #3
long tsA = DataHelper.fromLong(b, 2+sz, 4);
// _tsB is when we sent message #2
// Adjust backward by RTT/2
long now = _context.clock().now();
// rtt from sending #2 to receiving #3
long rtt = now - _con.getCreated();
_peerSkew = (now - (tsA * 1000) - (rtt / 2) + 500) / 1000;
ByteArrayStream baos = new ByteArrayStream(256 + 256 + 32 + 4 + 4);
baos.write(_X);
baos.write(_Y);
baos.write(_context.routerHash().getData());
baos.write(DataHelper.toLong(4, tsA));
baos.write(DataHelper.toLong(4, _tsB));
//baos.write(b, 2+sz+4, b.length-2-sz-4-Signature.SIGNATURE_BYTES);
byte toVerify[] = baos.toByteArray();
// handle variable signature size
SigType type = _aliceIdent.getSigningPublicKey().getType();
if (type == null) {
fail("unsupported sig type");
return;
}
byte s[] = new byte[type.getSigLen()];
System.arraycopy(b, b.length-s.length, s, 0, s.length);
Signature sig = new Signature(type, s);
boolean ok = _context.dsa().verifySignature(sig, toVerify, _aliceIdent.getSigningPublicKey());
Hash aliceHash = _aliceIdent.calculateHash();
if (ok) {
ok = verifyInbound(aliceHash);
}
if (ok) {
_con.setRemotePeer(_aliceIdent);
sendInboundConfirm(aliceHash, tsA);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"e_bobSig is " + _e_bobSig.length + " bytes long");
byte iv[] = _curEncrypted; // reuse buf
System.arraycopy(_e_bobSig, _e_bobSig.length-AES_SIZE, iv, 0, AES_SIZE);
// this does not copy the IV, do not release to cache
// We are Bob, she is Alice, clock skew is Alice-Bob
// skew in seconds
_con.finishInboundEstablishment(_dh.getSessionKey(), _peerSkew, iv, _prevEncrypted);
changeState(State.VERIFIED);
if (buf.hasRemaining()) {
// process "extra" data
// This is unlikely for inbound, as we must reply with message 4
if (_log.shouldWarn())
_log.warn("extra data " + buf.remaining() + " on " + this);
_con.recvEncryptedI2NP(buf);
}
releaseBufs(true);
if (_log.shouldLog(Log.INFO))
_log.info(prefix()+"Verified remote peer as " + aliceHash);
} else {
_context.statManager().addRateData("ntcp.invalidInboundSignature", 1);
// verifyInbound(aliceHash) called fail()
}
} catch (IOException ioe) {
_context.statManager().addRateData("ntcp.invalidInboundIOE", 1);
fail("Error verifying peer", ioe);
}
}
......@@ -607,45 +272,6 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
return rv;
}
/**
* We are Bob. Send message #4 to Alice.
*
* State must be VERIFIED.
* Caller must synch.
*
* @param h Alice's Hash
*/
private void sendInboundConfirm(Hash h, long tsA) {
// send Alice E(S(X+Y+Alice.identHash+tsA+tsB), sk, prev)
byte toSign[] = new byte[XY_SIZE + XY_SIZE + 32+4+4];
int off = 0;
System.arraycopy(_X, 0, toSign, off, XY_SIZE); off += XY_SIZE;
System.arraycopy(_Y, 0, toSign, off, XY_SIZE); off += XY_SIZE;
System.arraycopy(h.getData(), 0, toSign, off, 32); off += 32;
DataHelper.toLong(toSign, off, 4, tsA); off += 4;
DataHelper.toLong(toSign, off, 4, _tsB); off += 4;
// handle variable signature size
Signature sig = _context.dsa().sign(toSign, _context.keyManager().getSigningPrivateKey());
int siglen = sig.length();
int rem = siglen % AES_SIZE;
int padding;
if (rem > 0)
padding = AES_SIZE - rem;
else
padding = 0;
byte preSig[] = new byte[siglen + padding];
System.arraycopy(sig.getData(), 0, preSig, 0, siglen);
if (padding > 0)
_context.random().nextBytes(preSig, siglen, padding);
_e_bobSig = new byte[preSig.length];
_context.aes().encrypt(preSig, 0, _e_bobSig, 0, _dh.getSessionKey(), _e_hXY_tsB, HXY_TSB_PAD_SIZE - AES_SIZE, _e_bobSig.length);
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "Sending encrypted inbound confirmation");
_transport.getPumper().wantsWrite(_con, _e_bobSig);
}
//// NTCP2 below here
/**
......
package net.i2p.router.transport.ntcp;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.Arrays;
import net.i2p.crypto.SigType;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.Signature;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
import net.i2p.util.Log;
import net.i2p.util.SimpleByteCache;
/**
*
* NTCP 1 only. We are Alice.
*
* @since 0.9.35 pulled out of EstablishState
*/
class OutboundEstablishState extends EstablishBase {
public OutboundEstablishState(RouterContext ctx, NTCPTransport transport, NTCPConnection con) {
super(ctx, transport, con);
_state = State.OB_INIT;
ctx.sha().calculateHash(_X, 0, XY_SIZE, _hX_xor_bobIdentHash, 0);
xor32(con.getRemotePeer().calculateHash().getData(), _hX_xor_bobIdentHash);
// _prevEncrypted will be created later
}
/**
* Parse the contents of the buffer as part of the handshake.
*
* All data must be copied out of the buffer as Reader.processRead()
* will return it to the pool.
*
* If there are additional data in the buffer after the handshake is complete,
* the EstablishState is responsible for passing it to NTCPConnection.
*/
@Override
public synchronized void receive(ByteBuffer src) {
super.receive(src);
if (!src.hasRemaining())
return; // nothing to receive
receiveOutbound(src);
}
/**
* Get the NTCP version
* @return 1
* @since 0.9.35
*/
public int getVersion() { return 1; }
/**
* We are Alice, so receive these bytes as part of an outbound connection.
* This method receives messages 2 and 4, and sends message 3.
*
* All data must be copied out of the buffer as Reader.processRead()
* will return it to the pool.
*
* Caller must synch.
*
*/
private void receiveOutbound(ByteBuffer src) {
// recv Y+E(H(X+Y)+tsB, sk, Y[239:255])
// Read in Y, which is the first part of message #2
if (_state == State.OB_SENT_X && src.hasRemaining()) {
int toGet = Math.min(src.remaining(), XY_SIZE - _received);
src.get(_Y, _received, toGet);
_received += toGet;
if (_received < XY_SIZE)
return;
try {
_dh.setPeerPublicValue(_Y);
_dh.getSessionKey(); // force the calc
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"DH session key calculated (" + _dh.getSessionKey().toBase64() + ")");
changeState(State.OB_GOT_Y);
_received = 0;
} catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
_context.statManager().addRateData("ntcp.invalidDH", 1);
fail("Invalid X", e);
return;
} catch (IllegalStateException ise) {
// setPeerPublicValue()
fail("reused keys?", ise);
return;
}
}
// Read in the rest of message #2
if (_state == State.OB_GOT_Y && src.hasRemaining()) {
int toGet = Math.min(src.remaining(), HXY_TSB_PAD_SIZE - _received);
src.get(_e_hXY_tsB, _received, toGet);
_received += toGet;
if (_received < HXY_TSB_PAD_SIZE)
return;
if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix() + "received _e_hXY_tsB fully");
byte hXY_tsB[] = new byte[HXY_TSB_PAD_SIZE];
_context.aes().decrypt(_e_hXY_tsB, 0, hXY_tsB, 0, _dh.getSessionKey(), _Y, XY_SIZE-AES_SIZE, HXY_TSB_PAD_SIZE);
byte XY[] = new byte[XY_SIZE + XY_SIZE];
System.arraycopy(_X, 0, XY, 0, XY_SIZE);
System.arraycopy(_Y, 0, XY, XY_SIZE, XY_SIZE);
byte[] h = SimpleByteCache.acquire(HXY_SIZE);
_context.sha().calculateHash(XY, 0, XY_SIZE + XY_SIZE, h, 0);
if (!DataHelper.eq(h, 0, hXY_tsB, 0, HXY_SIZE)) {
SimpleByteCache.release(h);
_context.statManager().addRateData("ntcp.invalidHXY", 1);
fail("Invalid H(X+Y) - mitm attack attempted?");
return;
}
SimpleByteCache.release(h);
changeState(State.OB_GOT_HXY);
_received = 0;
// their (Bob's) timestamp in seconds
_tsB = DataHelper.fromLong(hXY_tsB, HXY_SIZE, 4);
long now = _context.clock().now();
// rtt from sending #1 to receiving #2
long rtt = now - _con.getCreated();
// our (Alice's) timestamp in seconds
_tsA = (now + 500) / 1000;
_peerSkew = (now - (_tsB * 1000) - (rtt / 2) + 500) / 1000;
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"h(X+Y) is correct, skew = " + _peerSkew);
// the skew is not authenticated yet, but it is certainly fatal to
// the establishment, so fail hard if appropriate
long diff = 1000*Math.abs(_peerSkew);
if (!_context.clock().getUpdatedSuccessfully()) {
// Adjust the clock one time in desperation
// We are Alice, he is Bob, adjust to match Bob
_context.clock().setOffset(1000 * (0 - _peerSkew), true);
_peerSkew = 0;
if (diff != 0)
_log.logAlways(Log.WARN, "NTP failure, NTCP adjusting clock by " + DataHelper.formatDuration(diff));
} else if (diff >= Router.CLOCK_FUDGE_FACTOR) {
_context.statManager().addRateData("ntcp.invalidOutboundSkew", diff);
_transport.markReachable(_con.getRemotePeer().calculateHash(), false);
// Only banlist if we know what time it is
_context.banlist().banlistRouter(DataHelper.formatDuration(diff),
_con.getRemotePeer().calculateHash(),
_x("Excessive clock skew: {0}"));
_transport.setLastBadSkew(_peerSkew);
fail("Clocks too skewed (" + diff + " ms)", null, true);
return;
} else if (_log.shouldLog(Log.DEBUG)) {
_log.debug(prefix()+"Clock skew: " + diff + " ms");
}
// now prepare and send our response
// send E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB), sk, hX_xor_Bob.identHash[16:31])
int sigSize = XY_SIZE + XY_SIZE + HXY_SIZE + 4+4;//+12;
byte preSign[] = new byte[sigSize];
System.arraycopy(_X, 0, preSign, 0, XY_SIZE);
System.arraycopy(_Y, 0, preSign, XY_SIZE, XY_SIZE);
System.arraycopy(_con.getRemotePeer().calculateHash().getData(), 0, preSign, XY_SIZE + XY_SIZE, HXY_SIZE);
DataHelper.toLong(preSign, XY_SIZE + XY_SIZE + HXY_SIZE, 4, _tsA);
DataHelper.toLong(preSign, XY_SIZE + XY_SIZE + HXY_SIZE + 4, 4, _tsB);
// hXY_tsB has 12 bytes of padding (size=48, tsB=4 + hXY=32)
Signature sig = _context.dsa().sign(preSign, _context.keyManager().getSigningPrivateKey());
byte ident[] = _context.router().getRouterInfo().getIdentity().toByteArray();
// handle variable signature size
int min = 2 + ident.length + 4 + sig.length();
int rem = min % AES_SIZE;
int padding = 0;
if (rem > 0)
padding = AES_SIZE - rem;
byte preEncrypt[] = new byte[min+padding];
DataHelper.toLong(preEncrypt, 0, 2, ident.length);
System.arraycopy(ident, 0, preEncrypt, 2, ident.length);
DataHelper.toLong(preEncrypt, 2+ident.length, 4, _tsA);
if (padding > 0)
_context.random().nextBytes(preEncrypt, 2 + ident.length + 4, padding);
System.arraycopy(sig.getData(), 0, preEncrypt, 2+ident.length+4+padding, sig.length());
_prevEncrypted = new byte[preEncrypt.length];
_context.aes().encrypt(preEncrypt, 0, _prevEncrypted, 0, _dh.getSessionKey(),
_hX_xor_bobIdentHash, _hX_xor_bobIdentHash.length-AES_SIZE, preEncrypt.length);
changeState(State.OB_SENT_RI);
_transport.getPumper().wantsWrite(_con, _prevEncrypted);
}
// Read in message #4
if (_state == State.OB_SENT_RI && src.hasRemaining()) {
// we are receiving their confirmation
// recv E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev)
int off = 0;
if (_e_bobSig == null) {
// handle variable signature size
int siglen = _con.getRemotePeer().getSigningPublicKey().getType().getSigLen();
int rem = siglen % AES_SIZE;
int padding;
if (rem > 0)
padding = AES_SIZE - rem;
else
padding = 0;
_e_bobSig = new byte[siglen + padding];
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "receiving E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " +
src.hasRemaining() + ")");
} else {
off = _received;
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "continuing to receive E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " +
src.hasRemaining() + " off=" + off + " recv=" + _received + ")");
}
while (_state == State.OB_SENT_RI && src.hasRemaining()) {
_e_bobSig[off++] = src.get();
_received++;
if (off >= _e_bobSig.length) {
changeState(State.OB_GOT_SIG);
byte bobSig[] = new byte[_e_bobSig.length];
_context.aes().decrypt(_e_bobSig, 0, bobSig, 0, _dh.getSessionKey(),
_e_hXY_tsB, HXY_TSB_PAD_SIZE - AES_SIZE, _e_bobSig.length);
// ignore the padding
// handle variable signature size
SigType type = _con.getRemotePeer().getSigningPublicKey().getType();
int siglen = type.getSigLen();
// we don't need to do this if no padding!
byte bobSigData[] = new byte[siglen];
System.arraycopy(bobSig, 0, bobSigData, 0, siglen);
Signature sig = new Signature(type, bobSigData);
byte toVerify[] = new byte[XY_SIZE + XY_SIZE + HXY_SIZE +4+4];
int voff = 0;
System.arraycopy(_X, 0, toVerify, voff, XY_SIZE); voff += XY_SIZE;
System.arraycopy(_Y, 0, toVerify, voff, XY_SIZE); voff += XY_SIZE;
System.arraycopy(_context.routerHash().getData(), 0, toVerify, voff, HXY_SIZE); voff += HXY_SIZE;
DataHelper.toLong(toVerify, voff, 4, _tsA); voff += 4;
DataHelper.toLong(toVerify, voff, 4, _tsB); voff += 4;
boolean ok = _context.dsa().verifySignature(sig, toVerify, _con.getRemotePeer().getSigningPublicKey());
if (!ok) {
_context.statManager().addRateData("ntcp.invalidSignature", 1);
fail("Signature was invalid - attempt to spoof " + _con.getRemotePeer().calculateHash().toBase64() + "?");
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "signature verified from Bob. done!");
byte nextWriteIV[] = SimpleByteCache.acquire(AES_SIZE);
System.arraycopy(_prevEncrypted, _prevEncrypted.length-AES_SIZE, nextWriteIV, 0, AES_SIZE);
// this does not copy the nextWriteIV, do not release to cache
// We are Alice, he is Bob, clock skew is Bob - Alice
// skew in seconds
_con.finishOutboundEstablishment(_dh.getSessionKey(), _peerSkew, nextWriteIV, _e_bobSig);
changeState(State.VERIFIED);
if (src.hasRemaining()) {
// process "extra" data
// This is fairly common for outbound, where Bob may send his updated RI
if (_log.shouldInfo())
_log.info("extra data " + src.remaining() + " on " + this);
_con.recvEncryptedI2NP(src);
}
releaseBufs(true);
// if socket gets closed this will be null - prevent NPE
InetAddress ia = _con.getChannel().socket().getInetAddress();
if (ia != null)
_transport.setIP(_con.getRemotePeer().calculateHash(), ia.getAddress());
}
return;
}
}
}
// check for remaining data
if ((_state == State.VERIFIED || _state == State.CORRUPT) && src.hasRemaining()) {
if (_log.shouldWarn())
_log.warn("Received unexpected " + src.remaining() + " on " + this, new Exception());
}
}
/**
* We are Alice.
* We are establishing an outbound connection, so prepare ourselves by
* queueing up the write of the first part of the handshake
* This method sends message #1 to Bob.
*
* @throws IllegalStateException
*/
@Override
public synchronized void prepareOutbound() {
if (_state == State.OB_INIT) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix() + "send X");
byte toWrite[] = new byte[XY_SIZE + _hX_xor_bobIdentHash.length];
System.arraycopy(_X, 0, toWrite, 0, XY_SIZE);
System.arraycopy(_hX_xor_bobIdentHash, 0, toWrite, XY_SIZE, _hX_xor_bobIdentHash.length);
changeState(State.OB_SENT_X);
_transport.getPumper().wantsWrite(_con, toWrite);
} else {
throw new IllegalStateException(prefix() + "unexpected prepareOutbound()");
}
}
/**
* Only call once. Caller must synch.
* @since 0.9.16
*/
@Override
protected void releaseBufs(boolean isVerified) {
super.releaseBufs(isVerified);
Arrays.fill(_Y, (byte) 0);
SimpleByteCache.release(_Y);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment