Author: nextgens
Date: 2007-09-29 19:28:19 +0000 (Sat, 29 Sep 2007)
New Revision: 15395
Modified:
trunk/freenet/src/freenet/crypt/DiffieHellman.java
trunk/freenet/src/freenet/crypt/DiffieHellmanContext.java
trunk/freenet/src/freenet/crypt/PCFBMode.java
trunk/freenet/src/freenet/node/FNPPacketMangler.java
trunk/freenet/src/freenet/node/PeerNode.java
Log:
Merge the jfk branch to trunk
This branch has been the responsibility of kryptos for his SoC work... I did
help him to finish it.
*IT WILL BREAK BACKWARD COMPATIBILITY UNTIL negType IS CHANGED*
by updating now you acknowledge that you've been warned ;)
Modified: trunk/freenet/src/freenet/crypt/DiffieHellman.java
===================================================================
--- trunk/freenet/src/freenet/crypt/DiffieHellman.java 2007-09-29 19:22:11 UTC
(rev 15394)
+++ trunk/freenet/src/freenet/crypt/DiffieHellman.java 2007-09-29 19:28:19 UTC
(rev 15395)
@@ -110,6 +110,18 @@
}
return new DiffieHellmanContext(params[0], params[1], group);
}
+ /**
+ * Create a DiffieHellmanLightContext.
+ */
+ public static DiffieHellmanLightContext generateLightContext() {
+ long time1 = System.currentTimeMillis();
+ NativeBigInteger[] params = getParams();
+ long time2 = System.currentTimeMillis();
+ if((time2 - time1) > 300) {
+ Logger.error(null,
"DiffieHellman.generateLightContext(): time2 is more than 300ms after time1
("+(time2 - time1)+ ')');
+ }
+ return new DiffieHellmanLightContext(params[0], params[1]);
+ }
public static NativeBigInteger[] getParams() {
synchronized (precalcBuffer) {
Modified: trunk/freenet/src/freenet/crypt/DiffieHellmanContext.java
===================================================================
--- trunk/freenet/src/freenet/crypt/DiffieHellmanContext.java 2007-09-29
19:22:11 UTC (rev 15394)
+++ trunk/freenet/src/freenet/crypt/DiffieHellmanContext.java 2007-09-29
19:28:19 UTC (rev 15395)
@@ -63,7 +63,7 @@
Logger.minor(this, "Key="+HexUtil.bytesToHex(key));
return key;
}
-
+
public synchronized void setOtherSideExponential(NativeBigInteger a) {
lastUsedTime = System.currentTimeMillis();
if(peerExponential != null) {
Modified: trunk/freenet/src/freenet/crypt/PCFBMode.java
===================================================================
--- trunk/freenet/src/freenet/crypt/PCFBMode.java 2007-09-29 19:22:11 UTC
(rev 15394)
+++ trunk/freenet/src/freenet/crypt/PCFBMode.java 2007-09-29 19:28:19 UTC
(rev 15395)
@@ -111,7 +111,7 @@
}
/**
- * NOTE: As a sideeffect, this will decrypt the data in the array.
+ * NOTE: As a side effect, this will decrypt the data in the array.
*/
//public synchronized byte[] blockDecipher(byte[] buf, int off, int len) {
public byte[] blockDecipher(byte[] buf, int off, int len) {
Modified: trunk/freenet/src/freenet/node/FNPPacketMangler.java
===================================================================
--- trunk/freenet/src/freenet/node/FNPPacketMangler.java 2007-09-29
19:22:11 UTC (rev 15394)
+++ trunk/freenet/src/freenet/node/FNPPacketMangler.java 2007-09-29
19:28:19 UTC (rev 15395)
@@ -1,3 +1,4 @@
+
/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
@@ -3,25 +4,47 @@
package freenet.node;
+import freenet.io.comm.SocketHandler;
+
import java.security.MessageDigest;
import java.util.Arrays;
-
import net.i2p.util.NativeBigInteger;
-
import freenet.crypt.BlockCipher;
+import freenet.crypt.DSA;
+import freenet.crypt.DSAGroup;
import freenet.crypt.DSASignature;
import freenet.crypt.DiffieHellman;
import freenet.crypt.DiffieHellmanContext;
+import freenet.crypt.DiffieHellmanLightContext;
import freenet.crypt.EntropySource;
+import freenet.crypt.Global;
+import freenet.crypt.HMAC;
import freenet.crypt.PCFBMode;
import freenet.crypt.SHA256;
-import freenet.io.comm.*;
+import freenet.crypt.UnsupportedCipherException;
+import freenet.crypt.ciphers.Rijndael;
+import freenet.io.comm.AsyncMessageCallback;
+import freenet.io.comm.FreenetInetAddress;
+import freenet.io.comm.IncomingPacketFilter;
+import freenet.io.comm.Message;
+import freenet.io.comm.MessageCore;
+import freenet.io.comm.NotConnectedException;
import freenet.io.comm.Peer.LocalAddressException;
import freenet.support.Fields;
+import freenet.io.comm.PacketSocketHandler;
+import freenet.io.comm.Peer;
+import freenet.io.comm.PeerContext;
import freenet.support.HexUtil;
import freenet.support.Logger;
import freenet.support.StringArray;
import freenet.support.TimeUtil;
import freenet.support.WouldBlockException;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.util.HashMap;
+
/**
* @author amphibian
@@ -36,1560 +59,2349 @@
public class FNPPacketMangler implements OutgoingPacketMangler,
IncomingPacketFilter {
private static boolean logMINOR;
- final Node node;
- final NodeCrypto crypto;
- final MessageCore usm;
- final PacketSocketHandler sock;
- final EntropySource fnpTimingSource;
- final EntropySource myPacketDataSource;
- private static final int MAX_PACKETS_IN_FLIGHT = 256;
- private static final int RANDOM_BYTES_LENGTH = 12;
- private static final int HASH_LENGTH = 32;
- /** Minimum headers overhead */
+ final Node node;
+ final NodeCrypto crypto;
+ final MessageCore usm;
+ final PacketSocketHandler sock;
+ final EntropySource fnpTimingSource;
+ final EntropySource myPacketDataSource;
+ /*
+ * Objects cached during JFK message exchange: JFK(3,4) with
authenticator as key
+ * The messages are cached in hashmaps because the message retrieval
from the cache
+ * can be performed in constant time( given the key)
+ * Usage of a linkedList could prove to be much slower due to the
allocation time
+ * for each node in the list.
+ */
+
+ private final HashMap authenticatorCache;
+
+ /** We renew it every 30mins (the spec. says "once a while") - access
is synchronized! */
+ private DiffieHellmanLightContext currentDHContext = null;
+ private long currentDHContextLifetime = 0;
+
+ protected static final int NONCE_SIZE = 8;
+ /**
+ * How big can the authenticator get before we flush it ?
+ * roughly n*(sizeof(message3|message4) + H(authenticator))
+ *
+ * We push to it until we reach the cap where we rekey
+ */
+ private static final int AUTHENTICATOR_CACHE_SIZE = 50;
+ private static final int MAX_PACKETS_IN_FLIGHT = 256;
+ private static final int RANDOM_BYTES_LENGTH = 12;
+ private static final int HASH_LENGTH = SHA256.getDigestLength();
+ /** The size of the key used to authenticate the hmac */
+ private static final int TRANSIENT_KEY_SIZE = HASH_LENGTH;
+ /** The key used to authenticate the hmac */
+ private final byte[] transientKey = new byte[TRANSIENT_KEY_SIZE];
+ /** Minimum headers overhead */
private static final int HEADERS_LENGTH_MINIMUM =
4 + // sequence number
- RANDOM_BYTES_LENGTH + // random junk
- 1 + // version
- 1 + // assume seqno != -1; otherwise would be 4
- 4 + // other side's seqno
- 1 + // number of acks
- 0 + // assume no acks
- 1 + // number of resend reqs
- 0 + // assume no resend requests
- 1 + // number of ack requests
- 0 + // assume no ack requests
- 1 + // no forgotten packets
- HASH_LENGTH + // hash
- 1; // number of messages
+ RANDOM_BYTES_LENGTH + // random junk
+ 1 + // version
+ 1 + // assume seqno != -1; otherwise would be 4
+ 4 + // other side's seqno
+ 1 + // number of acks
+ 0 + // assume no acks
+ 1 + // number of resend reqs
+ 0 + // assume no resend requests
+ 1 + // number of ack requests
+ 0 + // assume no ack requests
+ 1 + // no forgotten packets
+ HASH_LENGTH + // hash
+ 1; // number of messages
/** Headers overhead if there is one message and no acks. */
static public final int HEADERS_LENGTH_ONE_MESSAGE =
HEADERS_LENGTH_MINIMUM + 2; // 2 bytes = length of message.
rest is the same.
-
+
final int fullHeadersLengthMinimum;
final int fullHeadersLengthOneMessage;
-
- public FNPPacketMangler(Node node, NodeCrypto crypt, PacketSocketHandler
sock) {
- this.node = node;
- this.crypto = crypt;
- this.usm = node.usm;
- this.sock = sock;
- fnpTimingSource = new EntropySource();
- myPacketDataSource = new EntropySource();
- fullHeadersLengthMinimum = HEADERS_LENGTH_MINIMUM +
sock.getHeadersLength();
- fullHeadersLengthOneMessage = HEADERS_LENGTH_ONE_MESSAGE +
sock.getHeadersLength();
+
+
+ public FNPPacketMangler(Node node, NodeCrypto crypt,
PacketSocketHandler sock) {
+ this.node = node;
+ this.crypto = crypt;
+ this.usm = node.usm;
+ this.sock = sock;
+ fnpTimingSource = new EntropySource();
+ myPacketDataSource = new EntropySource();
+ authenticatorCache = new HashMap();
+
+ fullHeadersLengthMinimum = HEADERS_LENGTH_MINIMUM +
sock.getHeadersLength();
+ fullHeadersLengthOneMessage = HEADERS_LENGTH_ONE_MESSAGE +
sock.getHeadersLength();
logMINOR = Logger.shouldLog(Logger.MINOR, this);
- }
+ resetTransientKey();
+ }
- /**
- * Packet format:
- *
- * E_session_ecb(
- * 4 bytes: sequence number XOR first 4 bytes of node identity
- * 12 bytes: first 12 bytes of H(data)
- * )
- * E_session_ecb(
- * 16 bytes: bytes 12-28 of H(data)
- * ) XOR previous ciphertext XOR previous plaintext
- * 4 bytes: bytes 28-32 of H(data) XOR bytes 0-4 of H(data)
- * E_session_pcfb(data) // IV = first 32 bytes of packet
- *
- */
-
/**
+ * Packet format:
+ *
+ * E_session_ecb(
+ * 4 bytes: sequence number XOR first 4 bytes of node identity
+ * 12 bytes: first 12 bytes of H(data)
+ * )
+ * E_session_ecb(
+ * 16 bytes: bytes 12-28 of H(data)
+ * ) XOR previous ciphertext XOR previous plaintext
+ * 4 bytes: bytes 28-32 of H(data) XOR bytes 0-4 of H(data)
+ * E_session_pcfb(data) // IV = first 32 bytes of packet
+ *
+ */
+
+ /**
* Decrypt and authenticate packet.
* Then feed it to USM.checkFilters.
* Packets generated should have a PeerNode on them.
* Note that the buffer can be modified by this method.
*/
- public void process(byte[] buf, int offset, int length, Peer peer) {
- node.random.acceptTimerEntropy(fnpTimingSource, 0.25);
+ public void process(byte[] buf, int offset, int length, Peer peer) {
+ node.random.acceptTimerEntropy(fnpTimingSource, 0.25);
logMINOR = Logger.shouldLog(Logger.MINOR, this);
if(logMINOR) Logger.minor(this, "Packet length "+length+" from
"+peer);
- /**
- * Look up the Peer.
- * If we know it, check the packet with that key.
- * Otherwise try all of them (on the theory that nodes
- * occasionally change their IP addresses).
- */
- PeerNode opn = node.peers.getByPeer(peer);
- if(opn != null && opn.getOutgoingMangler() != this) {
- Logger.error(this, "Apparently contacted by "+opn+") on "+this);
- opn = null;
- }
- PeerNode pn;
-
- if(opn != null) {
- if(logMINOR) Logger.minor(this, "Trying exact match");
- if(length > HEADERS_LENGTH_MINIMUM) {
- if(tryProcess(buf, offset, length,
opn.getCurrentKeyTracker())) return;
- // Try with old key
- if(tryProcess(buf, offset, length,
opn.getPreviousKeyTracker())) return;
- // Try with unverified key
- if(tryProcess(buf, offset, length,
opn.getUnverifiedKeyTracker())) return;
- }
- if(length > Node.SYMMETRIC_KEY_LENGTH /* iv */ + HASH_LENGTH + 2
&& !node.isStopping()) {
- // Might be an auth packet
- if(tryProcessAuth(buf, offset, length, opn, peer, false))
return;
- }
- }
- PeerNode[] peers = crypto.getPeerNodes();
- // Existing connection, changed IP address?
- if(length > HASH_LENGTH + RANDOM_BYTES_LENGTH + 4 + 6) {
- for(int i=0;i<peers.length;i++) {
- pn = peers[i];
- if(pn == opn) continue;
- if(tryProcess(buf, offset, length, pn.getCurrentKeyTracker()))
{
- // IP address change
- pn.changedIP(peer);
- return;
- }
- if(tryProcess(buf, offset, length,
pn.getPreviousKeyTracker())) {
- // IP address change
- pn.changedIP(peer);
- return;
- }
- if(tryProcess(buf, offset, length,
pn.getUnverifiedKeyTracker())) {
- // IP address change
- pn.changedIP(peer);
- return;
- }
- }
- }
- if(node.isStopping()) return;
- // Disconnected node connecting on a new IP address?
- if(length > Node.SYMMETRIC_KEY_LENGTH /* iv */ + HASH_LENGTH + 2) {
- for(int i=0;i<peers.length;i++) {
- pn = peers[i];
- if(pn == opn) continue;
- if(tryProcessAuth(buf, offset, length, pn, peer, false))
return;
- }
- }
- OpennetManager opennet = node.getOpennet();
- if(opennet != null) {
- // Try old opennet connections.
- if(opennet.wantPeer(null, false)) {
- // We want a peer.
- // Try old connections.
- PeerNode[] oldPeers = opennet.getOldPeers();
- for(int i=0;i<oldPeers.length;i++) {
- if(tryProcessAuth(buf, offset, length,
oldPeers[i], peer, true)) return;
- }
- }
- }
- Logger.normal(this,"Unmatchable packet from "+peer);
- }
+ /**
+ * Look up the Peer.
+ * If we know it, check the packet with that key.
+ * Otherwise try all of them (on the theory that nodes
+ * occasionally change their IP addresses).
+ */
+ PeerNode opn = node.peers.getByPeer(peer);
+ if(opn != null && opn.getOutgoingMangler() != this) {
+ Logger.error(this, "Apparently contacted by "+opn+") on
"+this);
+ opn = null;
+ }
+ PeerNode pn;
- /**
- * Is this a negotiation packet? If so, process it.
- * @param buf The buffer to read bytes from
- * @param offset The offset at which to start reading
- * @param length The number of bytes to read
- * @param opn The PeerNode we think is responsible
- * @param peer The Peer to send a reply to
- * @return True if we handled a negotiation packet, false otherwise.
- */
- private boolean tryProcessAuth(byte[] buf, int offset, int length,
PeerNode opn, Peer peer, boolean oldOpennetPeer) {
- BlockCipher authKey = opn.incomingSetupCipher;
- if(logMINOR) Logger.minor(this, "Decrypt key:
"+HexUtil.bytesToHex(opn.incomingSetupKey)+" for "+peer+" : "+opn+" in
tryProcessAuth");
- // Does the packet match IV E( H(data) data ) ?
- PCFBMode pcfb = PCFBMode.create(authKey);
- int ivLength = pcfb.lengthIV();
- MessageDigest md = SHA256.getMessageDigest();
- int digestLength = HASH_LENGTH;
- if(length < digestLength + ivLength + 4) {
- if(logMINOR) Logger.minor(this, "Too short: "+length+" should be
at least "+(digestLength + ivLength + 4));
- SHA256.returnMessageDigest(md);
- return false;
- }
- // IV at the beginning
- pcfb.reset(buf, offset);
- // Then the hash, then the data
- // => Data starts at ivLength + digestLength
- // Decrypt the hash
- byte[] hash = new byte[digestLength];
- System.arraycopy(buf, offset+ivLength, hash, 0, digestLength);
- pcfb.blockDecipher(hash, 0, hash.length);
-
- int dataStart = ivLength + digestLength + offset+2;
-
- int byte1 = ((pcfb.decipher(buf[dataStart-2])) & 0xff);
- int byte2 = ((pcfb.decipher(buf[dataStart-1])) & 0xff);
- int dataLength = (byte1 << 8) + byte2;
- if(logMINOR) Logger.minor(this, "Data length: "+dataLength+" (1 =
"+byte1+" 2 = "+byte2+ ')');
- if(dataLength > length - (ivLength+hash.length+2)) {
- if(logMINOR) Logger.minor(this, "Invalid data length
"+dataLength+" ("+(length - (ivLength+hash.length+2))+") in tryProcessAuth");
- SHA256.returnMessageDigest(md);
- return false;
- }
- // Decrypt the data
- byte[] payload = new byte[dataLength];
- System.arraycopy(buf, dataStart, payload, 0, dataLength);
- pcfb.blockDecipher(payload, 0, payload.length);
-
- md.update(payload);
- byte[] realHash = md.digest();
- SHA256.returnMessageDigest(md); md = null;
-
- if(Arrays.equals(realHash, hash)) {
- // Got one
- processDecryptedAuth(payload, opn, peer, oldOpennetPeer);
- opn.reportIncomingBytes(length);
- return true;
- } else {
- if(logMINOR) Logger.minor(this, "Incorrect hash in tryProcessAuth
for "+peer+" (length="+dataLength+"): \nreal
hash="+HexUtil.bytesToHex(realHash)+"\n bad hash="+HexUtil.bytesToHex(hash));
- return false;
- }
- }
+ if(opn != null) {
+ if(logMINOR) Logger.minor(this, "Trying exact match");
+ if(length > HEADERS_LENGTH_MINIMUM) {
+ if(tryProcess(buf, offset, length,
opn.getCurrentKeyTracker())) return;
+ // Try with old key
+ if(tryProcess(buf, offset, length,
opn.getPreviousKeyTracker())) return;
+ // Try with unverified key
+ if(tryProcess(buf, offset, length,
opn.getUnverifiedKeyTracker())) return;
+ }
+ if(length > Node.SYMMETRIC_KEY_LENGTH /* iv */ +
HASH_LENGTH + 2 && !node.isStopping()) {
+ // Might be an auth packet
+ if(tryProcessAuth(buf, offset, length, opn,
peer, false)) return;
+ }
+ }
+ PeerNode[] peers = crypto.getPeerNodes();
+ // Existing connection, changed IP address?
+ if(length > HASH_LENGTH + RANDOM_BYTES_LENGTH + 4 + 6) {
+ for(int i=0;i<peers.length;i++) {
+ pn = peers[i];
+ if(pn == opn) continue;
+ if(tryProcess(buf, offset, length,
pn.getCurrentKeyTracker())) {
+ // IP address change
+ pn.changedIP(peer);
+ return;
+ }
+ if(tryProcess(buf, offset, length,
pn.getPreviousKeyTracker())) {
+ // IP address change
+ pn.changedIP(peer);
+ return;
+ }
+ if(tryProcess(buf, offset, length,
pn.getUnverifiedKeyTracker())) {
+ // IP address change
+ pn.changedIP(peer);
+ return;
+ }
+ }
+ }
+ if(node.isStopping()) return;
+ // Disconnected node connecting on a new IP address?
+ if(length > Node.SYMMETRIC_KEY_LENGTH /* iv */ + HASH_LENGTH +
2) {
+ for(int i=0;i<peers.length;i++) {
+ pn = peers[i];
+ if(pn == opn) continue;
+ if(tryProcessAuth(buf, offset, length, pn,
peer,false)) return;
+ }
+ }
+ OpennetManager opennet = node.getOpennet();
+ if(opennet != null) {
+ // Try old opennet connections.
+ if(opennet.wantPeer(null, false)) {
+ // We want a peer.
+ // Try old connections.
+ PeerNode[] oldPeers = opennet.getOldPeers();
+ for(int i=0;i<oldPeers.length;i++) {
+ if(tryProcessAuth(buf, offset, length,
oldPeers[i], peer, true)) return;
+ }
+ }
+ }
+ Logger.normal(this,"Unmatchable packet from "+peer);
+ }
- /**
- * Process a decrypted, authenticated auth packet.
- * @param payload The packet payload, after it has been decrypted.
- */
- private void processDecryptedAuth(byte[] payload, PeerNode pn, Peer
replyTo, boolean oldOpennetPeer) {
- if(logMINOR) Logger.minor(this, "Processing decrypted auth packet from
"+replyTo+" for "+pn);
- if(pn.isDisabled()) {
- if(logMINOR) Logger.minor(this, "Won't connect to a disabled
peer ("+pn+ ')');
- return; // We don't connect to disabled peers
- }
-
- long now = System.currentTimeMillis();
- int delta = (int) (now - pn.lastSentPacketTime());
-
- int negType = payload[1];
- int packetType = payload[2];
- int version = payload[0];
-
- if(logMINOR) Logger.minor(this, "Received auth packet for
"+pn.getPeer()+" (phase="+packetType+", v="+version+", nt="+negType+") (last
packet sent "+TimeUtil.formatTime(delta, 2, true)+" ago) from "+replyTo+"");
-
- /* Format:
- * 1 byte - version number (1)
- * 1 byte - negotiation type (0 = simple DH, will not be supported
when implement JFKi || 1 = StS)
- * 1 byte - packet type (0-3)
- */
- if(version != 1) {
- Logger.error(this, "Decrypted auth packet but invalid version:
"+version);
- return;
- }
+ /**
+ * Is this a negotiation packet? If so, process it.
+ * @param buf The buffer to read bytes from
+ * @param offset The offset at which to start reading
+ * @param length The number of bytes to read
+ * @param opn The PeerNode we think is responsible
+ * @param peer The Peer to send a reply to
+ * @return True if we handled a negotiation packet, false otherwise.
+ */
+ private boolean tryProcessAuth(byte[] buf, int offset, int length,
PeerNode opn, Peer peer, boolean oldOpennetPeer) {
+ BlockCipher authKey = opn.incomingSetupCipher;
+ if(logMINOR) Logger.minor(this, "Decrypt key:
"+HexUtil.bytesToHex(opn.incomingSetupKey)+" for "+peer+" : "+opn+" in
tryProcessAuth");
+ // Does the packet match IV E( H(data) data ) ?
+ PCFBMode pcfb = PCFBMode.create(authKey);
+ int ivLength = pcfb.lengthIV();
+ MessageDigest md = SHA256.getMessageDigest();
+ int digestLength = HASH_LENGTH;
+ if(length < digestLength + ivLength + 4) {
+ if(logMINOR) Logger.minor(this, "Too short: "+length+"
should be at least "+(digestLength + ivLength + 4));
+ SHA256.returnMessageDigest(md);
+ return false;
+ }
+ // IV at the beginning
+ pcfb.reset(buf, offset);
+ // Then the hash, then the data
+ // => Data starts at ivLength + digestLength
+ // Decrypt the hash
+ byte[] hash = new byte[digestLength];
+ System.arraycopy(buf, offset+ivLength, hash, 0, digestLength);
+ pcfb.blockDecipher(hash, 0, hash.length);
- if(negType == 0) {
- Logger.error(this, "Old ephemeral Diffie-Hellman (negType 0)
not supported.");
- return;
- }else if (negType == 1) {
- // Four stage Diffie-Hellman. 0 = ephemeral, 1 = payload stages
are signed (not quite STS)
- // FIXME reduce to 3 stages and implement STS properly (we have
a separate validation mechanism in PeerNode)
- // AFAICS this (with negType=1) is equivalent in security to
STS; it expands the second phase into a second and a fourth phase.
- // A -> B g^x
- // B -> A g^y
- // A -> B E^k ( ... )
- // B -> A E^k ( ... )
-
- if((packetType < 0) || (packetType > 3)) {
- Logger.error(this, "Decrypted auth packet but unknown
packet type "+packetType+" from "+replyTo+" possibly from "+pn);
- return;
- }
-
- // We keep one DiffieHellmanContext per node ONLY
- /*
- * Now, to the real meat
- * Alice, Bob share a base, g, and a modulus, p
- * Alice generates a random number r, and: 1: Alice -> Bob:
a=g^r
- * Bob receives this and generates his own random number, s,
and: 2: Bob -> Alice: b=g^s
- * Alice receives this, calculates K = b^r, and: 3: Alice ->
Bob: E_K ( H(data) data )
- * where data = [ Alice's startup number ]
- * Bob does exactly the same as Alice for packet 4.
- *
- * At this point we are done.
- */
- if(packetType == 0) {
- // We are Bob
- // We need to:
- // - Record Alice's a
- // - Generate our own s and b
- // - Send a type 1 packet back to Alice containing this
+ int dataStart = ivLength + digestLength + offset+2;
- DiffieHellmanContext ctx =
- processDHZeroOrOne(0, payload, pn);
- if(ctx == null) return;
- // Send reply
- sendFirstHalfDHPacket(1, negType,
ctx.getOurExponential(), pn, replyTo);
- // Send a type 1, they will reply with a type 2
- } else if(packetType == 1) {
- // We are Alice
- DiffieHellmanContext ctx =
- processDHZeroOrOne(1, payload, pn);
- if(ctx == null) return;
- sendSignedDHCompletion(2, ctx.getCipher(), pn,
replyTo, ctx);
- // Send a type 2
- } else if(packetType == 2) {
- // We are Bob
- // Receiving a completion packet
- // Verify the packet, then complete
- // Format: IV E_K ( H(data) data )
- // Where data = [ long: bob's startup number ]
- processSignedDHTwoOrThree(2, payload, pn, replyTo,
true, oldOpennetPeer);
- } else if(packetType == 3) {
- // We are Alice
- processSignedDHTwoOrThree(3, payload, pn, replyTo,
false, oldOpennetPeer);
- }
- }else {
- Logger.error(this, "Decrypted auth packet but unknown negotiation
type "+negType+" from "+replyTo+" possibly from "+pn);
- return;
- }
- }
+ int byte1 = ((pcfb.decipher(buf[dataStart-2])) & 0xff);
+ int byte2 = ((pcfb.decipher(buf[dataStart-1])) & 0xff);
+ int dataLength = (byte1 << 8) + byte2;
+ if(logMINOR) Logger.minor(this, "Data length: "+dataLength+" (1
= "+byte1+" 2 = "+byte2+ ')');
+ if(dataLength > length - (ivLength+hash.length+2)) {
+ if(logMINOR) Logger.minor(this, "Invalid data length
"+dataLength+" ("+(length - (ivLength+hash.length+2))+") in tryProcessAuth");
+ SHA256.returnMessageDigest(md);
+ return false;
+ }
+ // Decrypt the data
+ byte[] payload = new byte[dataLength];
+ System.arraycopy(buf, dataStart, payload, 0, dataLength);
+ pcfb.blockDecipher(payload, 0, payload.length);
- /**
- * Send a signed DH completion message.
- * Format:
- * IV
- * Signature on { My exponential, his exponential, data }
- * Data
- * @param phase The packet phase number. Either 2 or 3.
- * @param cipher The negotiated cipher.
- * @param pn The PeerNode which we are talking to.
- * @param replyTo The Peer to which to send the packet (not necessarily
the same
- * as the one on pn as the IP may have changed).
- */
- private void sendSignedDHCompletion(int phase, BlockCipher cipher,
PeerNode pn, Peer replyTo, DiffieHellmanContext ctx) {
- PCFBMode pcfb = PCFBMode.create(cipher);
- byte[] iv = new byte[pcfb.lengthIV()];
-
- byte[] myRef = crypto.myCompressedSetupRef();
- byte[] data = new byte[myRef.length + 8];
- System.arraycopy(Fields.longToBytes(node.bootID), 0, data, 0, 8);
- System.arraycopy(myRef, 0, data, 8, myRef.length);
-
- byte[] myExp = ctx.getOurExponential().toByteArray();
- byte[] hisExp = ctx.getHisExponential().toByteArray();
-
- MessageDigest md = SHA256.getMessageDigest();
- md.update(myExp);
- md.update(hisExp);
- md.update(data);
- byte[] hash = md.digest();
-
- DSASignature sig = crypto.sign(hash);
-
- byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
- byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
-
- Logger.minor(this, "Sending DH completion: "+pn+" hash
"+HexUtil.bytesToHex(hash)+" r="+HexUtil.bytesToHex(sig.getR().toByteArray())+"
s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
-
- int outputLength = iv.length + data.length + r.length + s.length + 2;
-
- byte[] output = new byte[outputLength];
-
- System.arraycopy(iv, 0, output, 0, iv.length);
- int count = iv.length;
- if(r.length > 255 || s.length > 255)
- throw new IllegalStateException("R or S is too long:
r.length="+r.length+" s.length="+s.length);
- output[count++] = (byte) r.length;
- System.arraycopy(r, 0, output, count, r.length);
- count += r.length;
- output[count++] = (byte) s.length;
- System.arraycopy(s, 0, output, count, s.length);
- count += s.length;
- System.arraycopy(data, 0, output, count, data.length);
-
- pcfb.blockEncipher(output, 0, output.length);
-
- sendAuthPacket(1, 1, phase, output, pn, replyTo);
- }
+ md.update(payload);
+ byte[] realHash = md.digest();
+ SHA256.returnMessageDigest(md); md = null;
- /**
- * Send a first-half (phase 0 or 1) DH negotiation packet to the node.
- * @param phase The phase of the message to be sent (0 or 1).
- * @param negType The negotiation type.
- * @param integer Our exponential
- * @param replyTo The peer to reply to
- */
- private void sendFirstHalfDHPacket(int phase, int negType,
NativeBigInteger integer, PeerNode pn, Peer replyTo) {
- long time1 = System.currentTimeMillis();
- if(logMINOR) Logger.minor(this, "Sending ("+phase+")
"+integer.toHexString()+" to "+pn.getPeer());
- byte[] data = integer.toByteArray();
- int targetLength = DiffieHellman.modulusLengthInBytes();
- if(data.length != targetLength) {
- byte[] newData = new byte[targetLength];
- if((data.length == targetLength+1) && (data[0] == 0)) {
- // Sign bit
- System.arraycopy(data, 1, newData, 0, targetLength);
- } else if(data.length < targetLength) {
- System.arraycopy(data, 0, newData, targetLength-data.length,
data.length);
- } else {
- throw new IllegalStateException("Too long!");
- }
- data = newData;
- }
- if(logMINOR) Logger.minor(this, "Processed:
"+HexUtil.bytesToHex(data));
- long time2 = System.currentTimeMillis();
- if((time2 - time1) > 200) {
- Logger.error(this, "sendFirstHalfDHPacket: time2 is more than 200ms
after time1 ("+(time2 - time1)+") working on "+replyTo+" of
"+pn.userToString());
- }
- sendAuthPacket(1, negType, phase, data, pn, replyTo);
- long time3 = System.currentTimeMillis();
- if((time3 - time2) > 500) {
- Logger.error(this, "sendFirstHalfDHPacket:sendAuthPacket() time3 is
more than half a second after time2 ("+(time3 - time2)+") working on
"+replyTo+" of "+pn.userToString());
- }
- if((time3 - time1) > 500) {
- Logger.error(this, "sendFirstHalfDHPacket: time3 is more than half a
second after time1 ("+(time3 - time1)+") working on "+replyTo+" of
"+pn.userToString());
- }
- }
+ if(Arrays.equals(realHash, hash)) {
+ // Got one
+ processDecryptedAuth(payload, opn, peer,
oldOpennetPeer);
+ opn.reportIncomingBytes(length);
+ return true;
+ } else {
+ if(logMINOR) Logger.minor(this, "Incorrect hash in
tryProcessAuth for "+peer+" (length="+dataLength+"): \nreal
hash="+HexUtil.bytesToHex(realHash)+"\n bad hash="+HexUtil.bytesToHex(hash));
+ return false;
+ }
+ }
- /**
- * Send an auth packet.
- */
- private void sendAuthPacket(int version, int negType, int phase, byte[]
data, PeerNode pn, Peer replyTo) {
- long now = System.currentTimeMillis();
- long delta = now - pn.lastSentPacketTime();
- byte[] output = new byte[data.length+3];
- output[0] = (byte) version;
- output[1] = (byte) negType;
- output[2] = (byte) phase;
- System.arraycopy(data, 0, output, 3, data.length);
- if(logMINOR) Logger.minor(this, "Sending auth packet for
"+pn.getPeer()+" (phase="+phase+", ver="+version+", nt="+negType+") (last
packet sent "+TimeUtil.formatTime(delta, 2, true)+" ago) to "+replyTo+"
data.length="+data.length);
- sendAuthPacket(output, pn, replyTo);
- }
+ /**
+ * Process a decrypted, authenticated auth packet.
+ * @param payload The packet payload, after it has been decrypted.
+ */
+ private void processDecryptedAuth(byte[] payload, PeerNode pn, Peer
replyTo, boolean oldOpennetPeer) {
+ if(logMINOR) Logger.minor(this, "Processing decrypted auth
packet from "+replyTo+" for "+pn);
+ if(pn.isDisabled()) {
+ if(logMINOR) Logger.minor(this, "Won't connect to a
disabled peer ("+pn+ ')');
+ return; // We don't connect to disabled peers
+ }
- /**
- * Send an auth packet (we have constructed the payload, now hash it, pad
it, encrypt it).
- */
- private void sendAuthPacket(byte[] output, PeerNode pn, Peer replyTo) {
- int length = output.length;
- if(length > sock.getMaxPacketSize()) {
- throw new IllegalStateException("Cannot send auth packet: too
long: "+length);
- }
- BlockCipher cipher = pn.outgoingSetupCipher;
- if(logMINOR) Logger.minor(this, "Outgoing cipher:
"+HexUtil.bytesToHex(pn.outgoingSetupKey));
- PCFBMode pcfb = PCFBMode.create(cipher);
- int paddingLength = node.fastWeakRandom.nextInt(100);
- byte[] iv = new byte[pcfb.lengthIV()];
- node.random.nextBytes(iv);
- byte[] hash = SHA256.digest(output);
- if(logMINOR) Logger.minor(this, "Data hash:
"+HexUtil.bytesToHex(hash));
- byte[] data = new byte[iv.length + hash.length + 2 /* length */ +
output.length + paddingLength];
- pcfb.reset(iv);
- System.arraycopy(iv, 0, data, 0, iv.length);
- pcfb.blockEncipher(hash, 0, hash.length);
- System.arraycopy(hash, 0, data, iv.length, hash.length);
- if(logMINOR) Logger.minor(this, "Payload length: "+length);
- data[hash.length+iv.length] = (byte) pcfb.encipher((byte)(length>>8));
- data[hash.length+iv.length+1] = (byte) pcfb.encipher((byte)length);
- pcfb.blockEncipher(output, 0, output.length);
- System.arraycopy(output, 0, data, hash.length+iv.length+2,
output.length);
- byte[] random = new byte[paddingLength];
- node.fastWeakRandom.nextBytes(random);
- System.arraycopy(random, 0, data,
hash.length+iv.length+2+output.length, random.length);
- try {
- sendPacket(data, replyTo, pn, 0);
+ long now = System.currentTimeMillis();
+ int delta = (int) (now - pn.lastSentPacketTime());
+
+ int negType = payload[1];
+ int packetType = payload[2];
+ int version = payload[0];
+
+ if(logMINOR) Logger.minor(this, "Received auth packet for
"+pn.getPeer()+" (phase="+packetType+", v="+version+", nt="+negType+") (last
packet sent "+TimeUtil.formatTime(delta, 2, true)+" ago) from "+replyTo+"");
+
+ /* Format:
+ * 1 byte - version number (1)
+ * 1 byte - negotiation type (0 = simple DH, will not be
supported when implement JFKi || 1 = StS)
+ * 1 byte - packet type (0-3)
+ */
+ if(version != 1) {
+ Logger.error(this, "Decrypted auth packet but invalid
version: "+version);
+ return;
+ }
+
+ if(negType == 0) {
+ Logger.error(this, "Old ephemeral Diffie-Hellman
(negType 0) not supported.");
+ return;
+ }else if (negType == 1) {
+ // Four stage Diffie-Hellman. 0 = ephemeral, 1 =
payload stages are signed (not quite STS)
+ // FIXME reduce to 3 stages and implement STS properly
(we have a separate validation mechanism in PeerNode)
+ // AFAICS this (with negType=1) is equivalent in
security to STS; it expands the second phase into a second and a fourth phase.
+ // A -> B g^x
+ // B -> A g^y
+ // A -> B E^k ( ... )
+ // B -> A E^k ( ... )
+
+ if((packetType < 0) || (packetType > 3)) {
+ Logger.error(this, "Decrypted auth packet but
unknown packet type "+packetType+" from "+replyTo+" possibly from "+pn);
+ return;
+ }
+
+ // We keep one DiffieHellmanContext per node ONLY
+ /*
+ * Now, to the real meat
+ * Alice, Bob share a base, g, and a modulus, p
+ * Alice generates a random number r, and: 1: Alice ->
Bob: a=g^r
+ * Bob receives this and generates his own random
number, s, and: 2: Bob -> Alice: b=g^s
+ * Alice receives this, calculates K = b^r, and: 3:
Alice -> Bob: E_K ( H(data) data )
+ * where data = [ Alice's startup number ]
+ * Bob does exactly the same as Alice for packet 4.
+ *
+ * At this point we are done.
+ */
+ if(packetType == 0) {
+ // We are Bob
+ // We need to:
+ // - Record Alice's a
+ // - Generate our own s and b
+ // - Send a type 1 packet back to Alice
containing this
+
+ DiffieHellmanContext ctx =
+ processDHZeroOrOne(0, payload, pn);
+ if(ctx == null) return;
+ // Send reply
+ sendFirstHalfDHPacket(1, negType,
ctx.getOurExponential(), pn, replyTo);
+ // Send a type 1, they will reply with a type 2
+ } else if(packetType == 1) {
+ // We are Alice
+ DiffieHellmanContext ctx =
+ processDHZeroOrOne(1, payload, pn);
+ if(ctx == null) return;
+ sendSignedDHCompletion(2, ctx.getCipher(), pn,
replyTo, ctx);
+ // Send a type 2
+ } else if(packetType == 2) {
+ // We are Bob
+ // Receiving a completion packet
+ // Verify the packet, then complete
+ // Format: IV E_K ( H(data) data )
+ // Where data = [ long: bob's startup number ]
+ processSignedDHTwoOrThree(2, payload, pn,
replyTo, true, oldOpennetPeer);
+ } else if(packetType == 3) {
+ // We are Alice
+ processSignedDHTwoOrThree(3, payload, pn,
replyTo, false, oldOpennetPeer);
+ }
+ }
+ else if (negType==2){
+ /*
+ * We implement Just Fast Keying key management
protocol with active identity protection
+ * for the initiator and no identity protection for the
responder
+ * M1:
+ * This is a straightforward DiffieHellman exponential.
+ * The Initiator Nonce serves two purposes;it allows
the initiator to use the same
+ * exponentials during different sessions while
ensuring that the resulting session
+ * key will be different,can be used to differentiate
between parallel sessions
+ * M2:
+ * Responder replies with a signed copy of his own
exponential, a random nonce and
+ * an authenticator which provides sufficient defense
against forgeries,replays
+ * We slightly deviate JFK here;we do not send any
public key information as specified in the JFK docs
+ * M3:
+ * Initiator echoes the data sent by the responder
including the authenticator.
+ * This helps the responder verify the authenticity of
the returned data.
+ * M4:
+ * Encrypted message of the signature on both nonces,
both exponentials using the same keys as in the previous message
+ */
+ if(packetType<0 || packetType>3){
+ Logger.error(this,"Unknown PacketType" +
packetType + "from" + replyTo + "from" +pn);
+ return ;
+ }
+ else if(packetType==0){
+ /*
+ * Initiator- This is a straightforward
DiffieHellman exponential.
+ * The Initiator Nonce serves two purposes;it
allows the initiator to use the same
+ * exponentials during different sessions while
ensuring that the resulting
+ * session key will be different,can be used to
differentiate between
+ * parallel sessions
+ */
+ processJFKMessage1(payload,pn,replyTo);
+
+ }
+ else if(packetType==1){
+ /*
+ * Responder replies with a signed copy of his
own exponential, a random
+ * nonce and an authenticator calculated from a
transient hash key private
+ * to the responder.
+ */
+ processJFKMessage2(payload,pn,replyTo);
+ }
+ else if(packetType==2){
+ /*
+ * Initiator echoes the data sent by the
responder.These messages are
+ * cached by the Responder.Receiving a
duplicate message simply causes
+ * the responder to Re-transmit the
corresponding message4
+ */
+ processJFKMessage3(payload, pn, replyTo,
oldOpennetPeer);
+ }
+ else if(packetType==3){
+ /*
+ * Encrypted message of the signature on both
nonces, both exponentials
+ * using the same keys as in the previous
message.
+ * The signature is non-message recovering
+ */
+ processJFKMessage4(payload, pn, replyTo,
oldOpennetPeer);
+ }
+ }
+ else {
+ Logger.error(this, "Decrypted auth packet but unknown
negotiation type "+negType+" from "+replyTo+" possibly from "+pn);
+ return;
+ }
+ }
+
+ /*
+ * Initiator Method:Message1
+ * Process Message1
+ * Send the Initiator nonce and DiffieHellman Exponential
+ * @param The packet phase number
+ * @param The peerNode we are talking to
+ * @param The peer to which we need to send the packet
+ *
+ * format :
+ * Ni
+ * g^i
+ * IDr'
+ */
+ private void processJFKMessage1(byte[] payload,PeerNode pn,Peer replyTo)
+ {
+ long t1=System.currentTimeMillis();
+ if(logMINOR) Logger.minor(this, "Got a JFK(1) message,
processing it - "+pn);
+ // FIXME: follow the spec and send IDr' ?
+ if(payload.length < NONCE_SIZE +
DiffieHellman.modulusLengthInBytes() + 3) {
+ Logger.error(this, "Packet too short from "+pn+":
"+payload.length+" after decryption in JFK(1), should be "+(NONCE_SIZE +
DiffieHellman.modulusLengthInBytes()));
+ return;
+ }
+ int offset=3;
+ // get Ni
+ byte[] nonceInitiator = new byte[NONCE_SIZE];
+ System.arraycopy(payload, offset, nonceInitiator, 0,
NONCE_SIZE);
+ offset += NONCE_SIZE;
+
+ // get g^i
+ byte[] hisExponential = new
byte[DiffieHellman.modulusLengthInBytes()];
+ System.arraycopy(payload, offset, hisExponential, 0,
DiffieHellman.modulusLengthInBytes());
+ NativeBigInteger _hisExponential = new
NativeBigInteger(1,hisExponential);
+ if(logMINOR) Logger.minor(this, "his exponential from message1
length="+DiffieHellman.modulusLengthInBytes() +" value=" +
_hisExponential.toHexString());
+ if(_hisExponential.compareTo(NativeBigInteger.ONE) > 0) {
+ sendJFKMessage2(nonceInitiator, pn, replyTo);
+ }else
+ Logger.error(this, "We can't accept the exponential
"+pn+" sent us; it's smaller than 1!!");
+
+ long t2=System.currentTimeMillis();
+ if((t2-t1)>500)
+ Logger.error(this,"Message1 timeout error:Sending
packet for"+pn.getPeer());
+ }
+
+ /*
+ * format:
+ * Ni,g^i
+ * NB: we don't send IDr as we know to who we are talking to (darknet)
+ */
+ private void sendJFKMessage1(PeerNode pn, Peer replyTo) {
+ if(logMINOR) Logger.minor(this, "Sending a JFK(1) message to
"+pn);
+ DiffieHellmanLightContext dhContext =
getLightDiffieHellmanContext(pn);
+ int offset = 0;
+ byte[] myExponential =
stripBigIntegerToNetworkFormat(dhContext.myExponential);
+ byte[] myNonce = new byte[NONCE_SIZE];
+ node.random.nextBytes(myNonce);
+
+ byte[] message1 = new
byte[NONCE_SIZE+DiffieHellman.modulusLengthInBytes()];
+
+ System.arraycopy(myNonce, 0, message1, offset, NONCE_SIZE);
+ offset += NONCE_SIZE;
+ System.arraycopy(myExponential, 0, message1, offset,
DiffieHellman.modulusLengthInBytes());
+
+ sendAuthPacket(1,2,0,message1,pn,replyTo);
+ }
+
+ /*
+ * format:
+ * Ni,Nr,g^r
+ * Signature[g^r,grpInfo(r)] - R, S
+ * Hashed JFKAuthenticator
+ * NB: we don't send IDr nor groupinfo as we know them (darknet)
+ */
+ private void sendJFKMessage2(byte[] nonceInitator, PeerNode pn, Peer
replyTo) {
+ if(logMINOR) Logger.minor(this, "Sending a JFK(2) message to
"+pn);
+ DiffieHellmanLightContext dhContext =
getLightDiffieHellmanContext(pn);
+ // g^r
+ byte[] myExponential =
stripBigIntegerToNetworkFormat(dhContext.myExponential);
+ // Nr
+ byte[] myNonce = new byte[NONCE_SIZE];
+ node.random.nextBytes(myNonce);
+ byte[] r =
dhContext.signature.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+ byte[] s =
dhContext.signature.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+ HMAC hash = new HMAC(SHA256.getInstance());
+ byte[] authenticator =
hash.mac(getTransientKey(),assembleJFKAuthenticator(myExponential, myNonce,
nonceInitator, replyTo.getAddress().getAddress()), HASH_LENGTH);
+ if(logMINOR) Logger.minor(this, "We are using the following
HMAC : " + HexUtil.bytesToHex(authenticator));
+
+ byte[] message2 = new
byte[NONCE_SIZE*2+DiffieHellman.modulusLengthInBytes()+
+ Node.SIGNATURE_PARAMETER_LENGTH*2+
+ HASH_LENGTH];
+
+ int offset = 0;
+ System.arraycopy(nonceInitator, 0, message2, offset,
NONCE_SIZE);
+ offset += NONCE_SIZE;
+ System.arraycopy(myNonce, 0, message2, offset, NONCE_SIZE);
+ offset += NONCE_SIZE;
+ System.arraycopy(myExponential, 0, message2, offset,
DiffieHellman.modulusLengthInBytes());
+ offset += DiffieHellman.modulusLengthInBytes();
+
+ System.arraycopy(r, 0, message2, offset,
Node.SIGNATURE_PARAMETER_LENGTH);
+ offset += Node.SIGNATURE_PARAMETER_LENGTH;
+ System.arraycopy(s, 0, message2, offset,
Node.SIGNATURE_PARAMETER_LENGTH);
+ offset += Node.SIGNATURE_PARAMETER_LENGTH;
+
+ System.arraycopy(authenticator, 0, message2, offset,
HASH_LENGTH);
+
+ sendAuthPacket(1,2,1,message2,pn,replyTo);
+ }
+
+ /*
+ * Assemble what will be the jfk-Authenticator :
+ * computed over the Responder exponentials and the Nonces and
+ * used by the responder to verify that the round-trip has been done
+ *
+ */
+ private byte[] assembleJFKAuthenticator(byte[] gR, byte[] nR, byte[]
nI, byte[] address) {
+ byte[] authData=new
byte[gR.length+nR.length+nI.length+address.length];
+ int offset = 0;
+
+ System.arraycopy(gR,0,authData,offset,gR.length);
+ offset += gR.length;
+ System.arraycopy(nR,0,authData,offset,nR.length);
+ offset += nR.length;
+ System.arraycopy(nI,0,authData,offset,nI.length);
+ offset += nI.length;
+ System.arraycopy(address, 0, authData, offset, address.length);
+
+ return authData;
+ }
+
+ /*
+ * Responder Method:Message2
+ * Process Message2: Must involve only minimal work for the responder
since at that point
+ * no round trip has yet occured with the initiator. Thus, he must not
be allowed to perform
+ * expensive calculations. In message2, his cost will be a single
authentication operation
+ * the cost is invocations of 2 cryptographic hash functions and
computation of a random nonce.
+ * Send the Initiator nonce,Responder nonce and DiffieHellman
Exponential of the responder
+ * in the clear.
+ * Send a signed copy of his own exponential
+ * Send an authenticator which is a hash of Ni,Nr,g^r calculated over
the transient key HKr
+ * Format of JFK(2) as specified above
+ *
+ * @param Payload
+ * @param The peer to which we need to send the packet
+ * @param The peerNode we are talking to
+ */
+
+ private void processJFKMessage2(byte[] payload,PeerNode pn,Peer replyTo)
+ {
+ long t1=System.currentTimeMillis();
+ if(logMINOR) Logger.minor(this, "Got a JFK(2) message,
processing it - "+pn);
+ // FIXME: follow the spec and send IDr' ?
+ int expectedLength = NONCE_SIZE*2 +
DiffieHellman.modulusLengthInBytes() + HASH_LENGTH*2;
+ if(payload.length < expectedLength + 3) {
+ Logger.error(this, "Packet too short from "+pn+":
"+payload.length+" after decryption in JFK(2), should be "+(expectedLength +
3));
+ return;
+ }
+
+ int inputOffset=3;
+ byte[] nonceInitiator = new byte[NONCE_SIZE];
+ System.arraycopy(payload, inputOffset, nonceInitiator, 0,
NONCE_SIZE);
+ inputOffset += NONCE_SIZE;
+ byte[] nonceResponder = new byte[NONCE_SIZE];
+ System.arraycopy(payload, inputOffset, nonceResponder, 0,
NONCE_SIZE);
+ inputOffset += NONCE_SIZE;
+
+ byte[] hisExponential = new
byte[DiffieHellman.modulusLengthInBytes()];
+ System.arraycopy(payload, inputOffset, hisExponential, 0,
DiffieHellman.modulusLengthInBytes());
+ inputOffset += DiffieHellman.modulusLengthInBytes();
+ NativeBigInteger _hisExponential = new
NativeBigInteger(1,hisExponential);
+
+ byte[] r = new byte[Node.SIGNATURE_PARAMETER_LENGTH];
+ System.arraycopy(payload, inputOffset, r, 0,
Node.SIGNATURE_PARAMETER_LENGTH);
+ inputOffset += Node.SIGNATURE_PARAMETER_LENGTH;
+ byte[] s = new byte[Node.SIGNATURE_PARAMETER_LENGTH];
+ System.arraycopy(payload, inputOffset, s, 0,
Node.SIGNATURE_PARAMETER_LENGTH);
+ inputOffset += Node.SIGNATURE_PARAMETER_LENGTH;
+
+ byte[] authenticator = new byte[HASH_LENGTH];
+ System.arraycopy(payload, inputOffset, authenticator, 0,
HASH_LENGTH);
+ inputOffset += HASH_LENGTH;
+
+ // Check try to find the authenticator in the cache.
+ // If authenticator is already present, indicates
duplicate/replayed message2
+ // Now simply transmit the corresponding message3
+ Object message3 = null;
+ synchronized (authenticatorCache) {
+ message3 = authenticatorCache.get(authenticator);
+ }
+ if(message3 != null) {
+ Logger.normal(this, "We replayed a message from the
cache (shouldn't happen often) -"+pn);
+ try{
+ sendAuthPacket(1, 2, 3, getBytes(message3), pn,
replyTo);
+ }catch(IOException e){
+ Logger.error(this,"Error getting bytes... wtf ?
"+e.getMessage(), e);
+ }
+ return;
+ }
+
+ // sanity check
+ if(_hisExponential.compareTo(NativeBigInteger.ONE) < 1) {
+ Logger.error(this, "We can't accept the exponential
"+pn+" sent us; it's smaller than 1!!");
+ return;
+ }
+
+ // Verify the DSA signature
+ DSASignature remoteSignature = new DSASignature(new
NativeBigInteger(1,r), new NativeBigInteger(1,s));
+ // At that point we don't know if it's "him"; let's check it out
+ byte[] locallyExpectedExponentials =
assembleDHParams(_hisExponential, pn.peerCryptoGroup);
+
+ if(!DSA.verify(pn.peerPubKey, remoteSignature, new
NativeBigInteger(1, SHA256.digest(locallyExpectedExponentials)), false)) {
+ Logger.error(this, "The signature verification has
failed in JFK(2)!! "+pn);
+ return;
+ }
+
+ sendJFKMessage3(1, 2, 3, nonceInitiator, nonceResponder,
hisExponential, authenticator, pn, replyTo);
+
+ long t2=System.currentTimeMillis();
+ if((t2-t1)>500)
+ Logger.error(this,"Message1 timeout error:Sending
packet for"+pn.getPeer());
+ }
+
+ /*
+ * Initiator Method:Message3
+ * Process Message3
+ * Send the Initiator nonce,Responder nonce and DiffieHellman
Exponential of the responder
+ * and initiator in the clear.(unVerifiedData)
+ * Send the authenticator which allows the responder to verify that a
roundtrip occured
+ * Compute the signature of the unVerifiedData and encrypt it using a
shared key
+ * which is derived from DHExponentials and the nonces; add a HMAC to
protect it
+ *
+ * @param Payload
+ * @param The peer to which we need to send the packet
+ * @param The peerNode we are talking to
+ * @return byte Message3
+ */
+ private void processJFKMessage3(byte[] payload, PeerNode pn,Peer
replyTo, boolean oldOpennetPeer)
+ {
+ final long t1 = System.currentTimeMillis();
+ if(logMINOR) Logger.minor(this, "Got a JFK(3) message,
processing it - "+pn);
+ BlockCipher c = null;
+ try { c = new Rijndael(256, 256); } catch
(UnsupportedCipherException e) {}
+ int inputOffset=3;
+
+ final int expectedLength = NONCE_SIZE*2 +
DiffieHellman.modulusLengthInBytes()*2 +
+
HASH_LENGTH + // authenticator
+
HASH_LENGTH + // HMAC of the cyphertext
+
(c.getBlockSize() >> 3) + // IV
+
HASH_LENGTH + // it's at least a signature
+ 8;
// a bootid
+ if(payload.length < expectedLength + 3) {
+ Logger.error(this, "Packet too short from "+pn+":
"+payload.length+" after decryption in JFK(3), should be "+(expectedLength +
3));
+ return;
+ }
+
+ // Ni
+ byte[] nonceInitiator = new byte[NONCE_SIZE];
+ System.arraycopy(payload, inputOffset, nonceInitiator, 0,
NONCE_SIZE);
+ inputOffset += NONCE_SIZE;
+ // Nr
+ byte[] nonceResponder = new byte[NONCE_SIZE];
+ System.arraycopy(payload, inputOffset, nonceResponder, 0,
NONCE_SIZE);
+ inputOffset += NONCE_SIZE;
+ // g^i
+ byte[] initiatorExponential = new
byte[DiffieHellman.modulusLengthInBytes()];
+ System.arraycopy(payload, inputOffset, initiatorExponential, 0,
DiffieHellman.modulusLengthInBytes());
+ inputOffset += DiffieHellman.modulusLengthInBytes();
+ // g^r
+ byte[] responderExponential = new
byte[DiffieHellman.modulusLengthInBytes()];
+ System.arraycopy(payload, inputOffset, responderExponential, 0,
DiffieHellman.modulusLengthInBytes());
+ inputOffset += DiffieHellman.modulusLengthInBytes();
+
+ byte[] authenticator = new byte[HASH_LENGTH];
+ System.arraycopy(payload, inputOffset, authenticator, 0,
HASH_LENGTH);
+ inputOffset += HASH_LENGTH;
+
+ // FIXME: check the cache before or after the hmac verification
?
+ // is it cheaper to wait for the lock on authenticatorCache or
to verify the hmac ?
+ HMAC mac = new HMAC(SHA256.getInstance());
+ if(!mac.verify(getTransientKey(),
assembleJFKAuthenticator(responderExponential, nonceResponder, nonceInitiator,
replyTo.getAddress().getAddress()) , authenticator)) {
+ Logger.error(this, "The HMAC doesn't match; let's
discard the packet (either we rekeyed or we are victim of forgery)");
+ return;
+ }
+ // Check try to find the authenticator in the cache.
+ // If authenticator is already present, indicates
duplicate/replayed message3
+ // Now simply transmit the corresponding message4
+ Object message4 = null;
+ synchronized (authenticatorCache) {
+ message4 = authenticatorCache.get(authenticator);
+ }
+ if(message4 != null) {
+ Logger.normal(this, "We replayed a message from the
cache (shouldn't happen often) - "+pn);
+ try{
+ sendAuthPacket(1, 2, 3, getBytes(message4), pn,
replyTo);
+ }catch(IOException e){
+ Logger.error(this,"Error getting bytes... wtf ?
"+e.getMessage(), e);
+ }
+ return;
+ }
+
+ // some sanity checks
+ NativeBigInteger _hisExponential = new NativeBigInteger(1,
initiatorExponential);
+ if(_hisExponential.compareTo(NativeBigInteger.ONE) < 1) {
+ Logger.error(this, "We can't accept the exponential
"+pn+" sent us; it's smaller than 1!!");
+ return;
+ }
+ NativeBigInteger _ourExponential = new NativeBigInteger(1,
responderExponential);
+ if(_ourExponential.compareTo(NativeBigInteger.ONE) < 1) {
+ Logger.error(this, "We can't accept the exponential
"+pn+" sent us; it's smaller than 1!! (our exponential?!?)");
+ return;
+ }
+
+ byte[] hmac = new byte[HASH_LENGTH];
+ System.arraycopy(payload, inputOffset, hmac, 0, HASH_LENGTH);
+ inputOffset += HASH_LENGTH;
+
+ DiffieHellmanLightContext dhContext =
getLightDiffieHellmanContext(pn);
+ BigInteger computedExponential =
dhContext.getHMACKey(_hisExponential, Global.DHgroupA);
+ byte[] Ks = computeJFKSharedKey(computedExponential,
nonceInitiator, nonceResponder, "0");
+ byte[] Ke = computeJFKSharedKey(computedExponential,
nonceInitiator, nonceResponder, "1");
+ byte[] Ka = computeJFKSharedKey(computedExponential,
nonceInitiator, nonceResponder, "2");
+ c.initialize(Ke);
+ final PCFBMode pk = PCFBMode.create(c);
+ int ivLength = pk.lengthIV();
+ int decypheredPayloadOffset = 0;
+ // We compute the HMAC of ("I"+cyphertext) : the cyphertext
includes the IV!
+ byte[] prefix = null;
+ try { prefix = "I".getBytes("UTF-8"); } catch
(UnsupportedEncodingException e) {}
+ byte[] decypheredPayload = new byte[prefix.length +
payload.length - inputOffset];
+ System.arraycopy(prefix, 0, decypheredPayload,
decypheredPayloadOffset, prefix.length);
+ decypheredPayloadOffset += prefix.length;
+ System.arraycopy(payload, inputOffset, decypheredPayload,
decypheredPayloadOffset, decypheredPayload.length-decypheredPayloadOffset);
+ if(!mac.verify(Ka, decypheredPayload, hmac)) {
+ Logger.error(this, "The digest-HMAC doesn't match;
let's discard the packet JFK(3) - "+pn);
+ return;
+ }
+
+ // Get the IV
+ pk.reset(decypheredPayload, decypheredPayloadOffset);
+ decypheredPayloadOffset += ivLength;
+ // Decrypt the payload
+ pk.blockDecipher(decypheredPayload, decypheredPayloadOffset,
decypheredPayload.length-decypheredPayloadOffset);
+ /*
+ * DecipheredData Format:
+ * Signature-r,s
+ */
+ byte[] r = new byte[Node.SIGNATURE_PARAMETER_LENGTH];
+ System.arraycopy(decypheredPayload, decypheredPayloadOffset, r,
0, Node.SIGNATURE_PARAMETER_LENGTH);
+ decypheredPayloadOffset += Node.SIGNATURE_PARAMETER_LENGTH;
+ byte[] s = new byte[Node.SIGNATURE_PARAMETER_LENGTH];
+ System.arraycopy(decypheredPayload, decypheredPayloadOffset, s,
0, Node.SIGNATURE_PARAMETER_LENGTH);
+ decypheredPayloadOffset += Node.SIGNATURE_PARAMETER_LENGTH;
+ byte[] data = new byte[decypheredPayload.length -
decypheredPayloadOffset];
+ System.arraycopy(decypheredPayload, decypheredPayloadOffset,
data, 0, decypheredPayload.length - decypheredPayloadOffset);
+ long bootID = Fields.bytesToLong(data);
+
+
+ // verify the signature
+ DSASignature remoteSignature = new DSASignature(new
NativeBigInteger(1,r), new NativeBigInteger(1,s));
+ if(!DSA.verify(pn.peerPubKey, remoteSignature, new
NativeBigInteger(1, SHA256.digest(assembleDHParams(nonceInitiator,
nonceResponder, _hisExponential, _ourExponential, crypto.myIdentity))), false))
{
+ Logger.error(this, "The signature verification has
failed!! JFK(3) - "+pn);
+ return;
+ }
+
+ // Send reply
+ sendJFKMessage4(1, 2, 3, nonceInitiator,
nonceResponder,initiatorExponential, responderExponential, c, Ke, Ka,
authenticator, pn, replyTo);
+ c.initialize(Ks);
+
+ // Promote if necessary
+ boolean dontWant = false;
+ if(oldOpennetPeer) {
+ OpennetManager opennet = node.getOpennet();
+ if(opennet == null) {
+ Logger.normal(this, "Dumping incoming
old-opennet peer as opennet just turned off: "+pn+".");
+ return;
+ }
+ if(!opennet.wantPeer(pn, true)) {
+ Logger.normal(this, "No longer want peer "+pn+"
- dumping it after connecting");
+ dontWant = true;
+ }
+ // wantPeer will call node.peers.addPeer(), we don't
have to.
+ }
+
+ if(pn.completedHandshake(bootID, data, 8, data.length-8, c, Ks,
replyTo, true)) {
+ if(dontWant)
+ node.peers.disconnect(pn, true, false);
+ else
+ pn.maybeSendInitialMessages();
+ } else {
+ Logger.error(this, "Handshake failure! with "+pn);
+ }
+
+ final long t2=System.currentTimeMillis();
+ if((t2-t1)>500)
+ Logger.error(this,"Message3 timeout error:Sending
packet for"+pn.getPeer());
+ }
+
+ /*
+ * Responder Method:Message4
+ * Process Message4
+ *
+ * @param Payload
+ * @param The peerNode we are talking to
+ * @param replyTo the Peer we are replying to
+ */
+ private void processJFKMessage4(byte[] payload, PeerNode pn, Peer
replyTo, boolean oldOpennetPeer)
+ {
+ final long t1 = System.currentTimeMillis();
+ if(logMINOR) Logger.minor(this, "Got a JFK(4) message,
processing it - "+pn);
+ BlockCipher c = null;
+ try { c = new Rijndael(256, 256); } catch
(UnsupportedCipherException e) {}
+ int inputOffset=3;
+
+ final int expectedLength = HASH_LENGTH + // HMAC of the
cyphertext
+
(c.getBlockSize() >> 3) + // IV
+
HASH_LENGTH + // the signature
+ 8
// the bootid; there should be the noderef too
+ ;
+ if(payload.length < expectedLength + 3) {
+ Logger.error(this, "Packet too short from "+pn+":
"+payload.length+" after decryption in JFK(4), should be "+(expectedLength +
3));
+ return;
+ }
+ byte[] jfkBuffer = pn.getJFKBuffer();
+ // FIXME: is that needed ?
+ if(jfkBuffer == null) {
+ Logger.normal(this, "We have already handled this
message... might be a replay or a bug - "+pn);
+ return;
+ }
+
+ byte[] hmac = new byte[HASH_LENGTH];
+ System.arraycopy(payload, inputOffset, hmac, 0, HASH_LENGTH);
+ inputOffset += HASH_LENGTH;
+
+ //FIXME: do we need to "c.initialize(Ke);" ? I don't think so
but I'm not sure - nextgens
+ c.initialize(pn.jfkKe);
+ final PCFBMode pk = PCFBMode.create(c);
+ int ivLength = pk.lengthIV();
+ int decypheredPayloadOffset = 0;
+ // We compute the HMAC of ("I"+cyphertext) : the cyphertext
includes the IV!
+ byte[] prefix = null;
+ try { prefix = "R".getBytes("UTF-8"); } catch
(UnsupportedEncodingException e) {}
+ byte[] decypheredPayload = new byte[prefix.length +
(payload.length-inputOffset)];
+ System.arraycopy(prefix, 0, decypheredPayload,
decypheredPayloadOffset, prefix.length);
+ decypheredPayloadOffset += prefix.length;
+ System.arraycopy(payload, inputOffset, decypheredPayload,
decypheredPayloadOffset, payload.length-inputOffset);
+ HMAC mac = new HMAC(SHA256.getInstance());
+ if(!mac.verify(pn.jfkKa, decypheredPayload, hmac)) {
+ Logger.error(this, "The digest-HMAC doesn't match;
let's discard the packet");
+ return;
+ }
+
+ // Get the IV
+ pk.reset(decypheredPayload, decypheredPayloadOffset);
+ decypheredPayloadOffset += ivLength;
+ // Decrypt the payload
+ pk.blockDecipher(decypheredPayload, decypheredPayloadOffset,
decypheredPayload.length - decypheredPayloadOffset);
+ /*
+ * DecipheredData Format:
+ * Signature-r,s
+ * bootID, znoderef
+ */
+ byte[] r = new byte[Node.SIGNATURE_PARAMETER_LENGTH];
+ System.arraycopy(decypheredPayload, decypheredPayloadOffset, r,
0, Node.SIGNATURE_PARAMETER_LENGTH);
+ decypheredPayloadOffset += Node.SIGNATURE_PARAMETER_LENGTH;
+ byte[] s = new byte[Node.SIGNATURE_PARAMETER_LENGTH];
+ System.arraycopy(decypheredPayload, decypheredPayloadOffset, s,
0, Node.SIGNATURE_PARAMETER_LENGTH);
+ decypheredPayloadOffset += Node.SIGNATURE_PARAMETER_LENGTH;
+ byte[] data = new byte[decypheredPayload.length -
decypheredPayloadOffset];
+ System.arraycopy(decypheredPayload, decypheredPayloadOffset,
data, 0, decypheredPayload.length - decypheredPayloadOffset);
+ long bootID = Fields.bytesToLong(data);
+
+ // verify the signature
+ DSASignature remoteSignature = new DSASignature(new
NativeBigInteger(1,r), new NativeBigInteger(1,s));
+ byte[] locallyGeneratedText = new byte[NONCE_SIZE * 2 +
DiffieHellman.modulusLengthInBytes() * 2 + crypto.myIdentity.length];
+ int bufferOffset = NONCE_SIZE * 2 +
DiffieHellman.modulusLengthInBytes()*2;
+ System.arraycopy(jfkBuffer, 0, locallyGeneratedText, 0,
bufferOffset);
+ System.arraycopy(crypto.myIdentity, 0, locallyGeneratedText,
bufferOffset, crypto.myIdentity.length);
+ if(!DSA.verify(pn.peerPubKey, remoteSignature, new
NativeBigInteger(1, SHA256.digest(locallyGeneratedText)), false)) {
+ Logger.error(this, "The signature verification has
failed!! JFK(4) -"+pn);
+ return;
+ }
+
+ // Promote if necessary
+ boolean dontWant = false;
+ if(oldOpennetPeer) {
+ OpennetManager opennet = node.getOpennet();
+ if(opennet == null) {
+ Logger.normal(this, "Dumping incoming
old-opennet peer as opennet just turned off: "+pn+".");
+ return;
+ }
+ if(!opennet.wantPeer(pn, true)) {
+ Logger.normal(this, "No longer want peer "+pn+"
- dumping it after connecting");
+ dontWant = true;
+ }
+ // wantPeer will call node.peers.addPeer(), we don't
have to.
+ }
+
+ // We change the key
+ c.initialize(pn.jfkKs);
+ if(pn.completedHandshake(bootID, data, 8, data.length - 8, c,
pn.jfkKs, replyTo, false)) {
+ if(dontWant)
+ node.peers.disconnect(pn, true, false);
+ else
+ pn.maybeSendInitialMessages();
+ } else {
+ Logger.error(this, "Handshake failed!");
+ }
+
+ // cleanup
+ pn.setJFKBuffer(null);
+ pn.jfkKa = null;
+ pn.jfkKe = null;
+ pn.jfkKs = null;
+
+ final long t2=System.currentTimeMillis();
+ if((t2-t1)>500)
+ Logger.error(this,"Message3 timeout error:Sending
packet for"+pn.getPeer());
+ }
+
+ /*
+ * Convert Object to byteArray
+ */
+ private byte[] getBytes(Object o) throws IOException
+ {
+ ByteArrayOutputStream bs = new ByteArrayOutputStream();
+ ObjectOutputStream os = new ObjectOutputStream(bs);
+ os.writeObject(o);
+ os.flush();
+ os.close();
+ bs.close();
+ byte [] output = bs.toByteArray();
+ return output;
+ }
+
+ /*
+ * Format:
+ * Ni
+ * Nr
+ * g^i
+ * g^r
+ * Authenticator
+ * HMAC(cyphertext)
+ * IV + E[S[Ni,Nr,g^i,g^r], bootid, znoderef]
+ */
+
+ private void sendJFKMessage3(int version,int negType,int phase,byte[]
nonceInitiator,byte[] nonceResponder,byte[] hisExponential, byte[]
authenticator, PeerNode pn, Peer replyTo)
+ {
+ if(logMINOR) Logger.minor(this, "Sending a JFK(3) message to
"+pn);
+ BlockCipher c = null;
+ try { c = new Rijndael(256, 256); } catch
(UnsupportedCipherException e) {}
+ DiffieHellmanLightContext dhContext =
getLightDiffieHellmanContext(pn);
+ byte[] ourExponential =
stripBigIntegerToNetworkFormat(dhContext.myExponential);
+ byte[] myRef = crypto.myCompressedSetupRef();
+ byte[] data = new byte[8 + myRef.length];
+ System.arraycopy(Fields.longToBytes(node.bootID), 0, data, 0,
8);
+ System.arraycopy(myRef, 0, data, 8, myRef.length);
+ byte[] message3 = new byte[NONCE_SIZE*2 + // nI, nR
+
DiffieHellman.modulusLengthInBytes()*2 + // g^i, g^r
+ HASH_LENGTH + // authenticator
+ HASH_LENGTH + // HMAC(cyphertext)
+ (c.getBlockSize() >> 3) + // IV
+ Node.SIGNATURE_PARAMETER_LENGTH * 2
+ // Signature (R,S)
+ data.length]; // The bootid+noderef
+ int offset = 0;
+ // Ni
+ System.arraycopy(nonceInitiator, 0, message3, offset,
NONCE_SIZE);
+ offset += NONCE_SIZE;
+ // Nr
+ System.arraycopy(nonceResponder, 0, message3, offset,
NONCE_SIZE);
+ offset += NONCE_SIZE;
+ // g^i
+ System.arraycopy(ourExponential, 0,message3, offset,
ourExponential.length);
+ offset += ourExponential.length;
+ // g^r
+ System.arraycopy(hisExponential, 0,message3, offset,
hisExponential.length);
+ offset += hisExponential.length;
+
+ // Authenticator
+ System.arraycopy(authenticator, 0, message3, offset,
HASH_LENGTH);
+ offset += HASH_LENGTH;
+ /*
+ * Digital Signature of the message with the private key
belonging to the initiator/responder
+ * It is assumed to be non-message recovering
+ */
+ NativeBigInteger _ourExponential = new
NativeBigInteger(1,ourExponential);
+ NativeBigInteger _hisExponential = new
NativeBigInteger(1,hisExponential);
+ // save parameters so that we can verify message4
+ byte[] toSign = assembleDHParams(nonceInitiator,
nonceResponder, _ourExponential, _hisExponential, pn.identity);
+ pn.setJFKBuffer(toSign);
+ DSASignature localSignature =
crypto.sign(SHA256.digest(toSign));
+ byte[] r =
localSignature.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+ byte[] s =
localSignature.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+
+ BigInteger computedExponential =
dhContext.getHMACKey(_hisExponential, Global.DHgroupA);
+ pn.jfkKs = computeJFKSharedKey(computedExponential,
nonceInitiator, nonceResponder, "0");
+ pn.jfkKe = computeJFKSharedKey(computedExponential,
nonceInitiator, nonceResponder, "1");
+ pn.jfkKa = computeJFKSharedKey(computedExponential,
nonceInitiator, nonceResponder, "2");
+ c.initialize(pn.jfkKe);
+ PCFBMode pcfb = PCFBMode.create(c);
+ int ivLength = pcfb.lengthIV();
+ byte[] iv = new byte[ivLength];
+ node.random.nextBytes(iv);
+ pcfb.reset(iv);
+ int cleartextOffset = 0;
+ byte[] prefix = null;
+ try { prefix = "I".getBytes("UTF-8"); } catch
(UnsupportedEncodingException e) {}
+
+ byte[] cleartext = new byte[prefix.length + ivLength +
Node.SIGNATURE_PARAMETER_LENGTH * 2 + data.length];
+ System.arraycopy(prefix, 0, cleartext, cleartextOffset,
prefix.length);
+ cleartextOffset += prefix.length;
+ System.arraycopy(iv, 0, cleartext, cleartextOffset, ivLength);
+ cleartextOffset += ivLength;
+ System.arraycopy(r, 0, cleartext, cleartextOffset,
Node.SIGNATURE_PARAMETER_LENGTH);
+ cleartextOffset += Node.SIGNATURE_PARAMETER_LENGTH;
+ System.arraycopy(s, 0, cleartext, cleartextOffset,
Node.SIGNATURE_PARAMETER_LENGTH);
+ cleartextOffset += Node.SIGNATURE_PARAMETER_LENGTH;
+ System.arraycopy(data, 0, cleartext, cleartextOffset,
data.length);
+ cleartextOffset += data.length;
+
+ int cleartextToEncypherOffset = prefix.length + ivLength;
+ pcfb.blockEncipher(cleartext, cleartextToEncypherOffset,
cleartext.length-cleartextToEncypherOffset);
+
+ // We compute the HMAC of (prefix + cyphertext) Includes the IV!
+ HMAC mac = new HMAC(SHA256.getInstance());
+ byte[] hmac = mac.mac(pn.jfkKa, cleartext, HASH_LENGTH);
+
+ // copy stuffs back to the message
+ System.arraycopy(hmac, 0, message3, offset, HASH_LENGTH);
+ offset += HASH_LENGTH;
+ System.arraycopy(iv, 0, message3, offset, ivLength);
+ offset += ivLength;
+ System.arraycopy(cleartext, cleartextToEncypherOffset,
message3, offset, cleartext.length-cleartextToEncypherOffset);
+
+ // cache the message
+ synchronized (authenticatorCache) {
+ if(authenticatorCache.size() > AUTHENTICATOR_CACHE_SIZE)
+ resetTransientKey();
+ else
+ authenticatorCache.put(authenticator,message3);
+ }
+ sendAuthPacket(1, 2, 2, message3, pn, replyTo);
+ }
+
+
+ /*
+ * Format:
+ * E[S[Ni,Nr,g^i,g^r,idI],bootID,znoderef]
+ */
+ private void sendJFKMessage4(int version,int negType,int phase,byte[]
nonceInitiator,byte[] nonceResponder,byte[] initiatorExponential,byte[]
responderExponential, BlockCipher c, byte[] Ke, byte[] Ka, byte[]
authenticator, PeerNode pn, Peer replyTo)
+ {
+ if(logMINOR)
+ Logger.minor(this, "Sending a JFK(4) message to "+pn);
+ NativeBigInteger _responderExponential = new
NativeBigInteger(1,responderExponential);
+ NativeBigInteger _initiatorExponential = new
NativeBigInteger(1,initiatorExponential);
+
+ DSASignature localSignature =
crypto.sign(SHA256.digest(assembleDHParams(nonceInitiator, nonceResponder,
_initiatorExponential, _responderExponential, pn.identity)));
+ byte[] r =
localSignature.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+ byte[] s =
localSignature.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+
+ byte[] myRef = crypto.myCompressedSetupRef();
+ byte[] data = new byte[8 + myRef.length];
+ System.arraycopy(Fields.longToBytes(node.bootID), 0, data, 0,
8);
+ System.arraycopy(myRef, 0, data, 8, myRef.length);
+
+ PCFBMode pk=PCFBMode.create(c);
+ int ivLength = pk.lengthIV();
+ byte[] iv=new byte[ivLength];
+ node.random.nextBytes(iv);
+ pk.reset(iv);
+ byte[] prefix = null;
+ try { prefix = "R".getBytes("UTF-8"); } catch
(UnsupportedEncodingException e) {}
+
+ byte[] cyphertext = new byte[prefix.length + ivLength +
Node.SIGNATURE_PARAMETER_LENGTH * 2 + data.length];
+ int cleartextOffset = 0;
+ System.arraycopy(prefix, 0, cyphertext, cleartextOffset,
prefix.length);
+ cleartextOffset += prefix.length;
+ System.arraycopy(iv, 0, cyphertext, cleartextOffset, ivLength);
+ cleartextOffset += ivLength;
+ System.arraycopy(r, 0, cyphertext, cleartextOffset,
Node.SIGNATURE_PARAMETER_LENGTH);
+ cleartextOffset += Node.SIGNATURE_PARAMETER_LENGTH;
+ System.arraycopy(s, 0, cyphertext, cleartextOffset,
Node.SIGNATURE_PARAMETER_LENGTH);
+ cleartextOffset += Node.SIGNATURE_PARAMETER_LENGTH;
+ System.arraycopy(data, 0, cyphertext, cleartextOffset,
data.length);
+ cleartextOffset += data.length;
+ // Now encrypt the cleartext[Signature]
+ int cleartextToEncypherOffset = prefix.length + ivLength;
+ pk.blockEncipher(cyphertext, cleartextToEncypherOffset,
cyphertext.length - cleartextToEncypherOffset);
+
+ // We compute the HMAC of (prefix + iv + signature)
+ HMAC mac = new HMAC(SHA256.getInstance());
+ byte[] hmac = mac.mac(Ka, cyphertext, HASH_LENGTH);
+
+ // Message4 = hmac + IV + encryptedSignature
+ byte[] message4 = new byte[HASH_LENGTH + ivLength +
(cyphertext.length - cleartextToEncypherOffset)];
+ int offset = 0;
+ System.arraycopy(hmac, 0, message4, offset, HASH_LENGTH);
+ offset += HASH_LENGTH;
+ System.arraycopy(iv, 0, message4, offset, ivLength);
+ offset += ivLength;
+ System.arraycopy(cyphertext, cleartextToEncypherOffset,
message4, offset, cyphertext.length - cleartextToEncypherOffset);
+
+ // cache the message
+ synchronized (authenticatorCache) {
+ if(authenticatorCache.size() > AUTHENTICATOR_CACHE_SIZE)
+ resetTransientKey();
+ else
+ authenticatorCache.put(authenticator, message4);
+ }
+ sendAuthPacket(1, 2, 3, message4, pn, replyTo);
+ }
+
+ /**
+ * Send a signed DH completion message.
+ * Format:
+ * IV
+ * Signature on { My exponential, his exponential, data }
+ * Data
+ * @param phase The packet phase number. Either 2 or 3.
+ * @param cipher The negotiated cipher.
+ * @param pn The PeerNode which we are talking to.
+ * @param replyTo The Peer to which to send the packet (not necessarily
the same
+ * as the one on pn as the IP may have changed).
+ */
+ private void sendSignedDHCompletion(int phase, BlockCipher cipher,
PeerNode pn, Peer replyTo, DiffieHellmanContext ctx) {
+ PCFBMode pcfb = PCFBMode.create(cipher);
+ byte[] iv = new byte[pcfb.lengthIV()];
+
+ byte[] myRef = crypto.myCompressedSetupRef();
+ byte[] data = new byte[myRef.length + 8];
+ System.arraycopy(Fields.longToBytes(node.bootID), 0, data, 0,
8);
+ System.arraycopy(myRef, 0, data, 8, myRef.length);
+ byte[] myExp = ctx.getOurExponential().toByteArray();
+ byte[] hisExp = ctx.getHisExponential().toByteArray();
+
+ MessageDigest md = SHA256.getMessageDigest();
+ md.update(myExp);
+ md.update(hisExp);
+ md.update(data);
+ byte[] hash = md.digest();
+
+ DSASignature sig = crypto.sign(hash);
+
+ byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+ byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+
+ Logger.minor(this, "Sending DH completion: "+pn+" hash
"+HexUtil.bytesToHex(hash)+" r="+HexUtil.bytesToHex(sig.getR().toByteArray())+"
s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
+
+ int outputLength = iv.length + data.length + r.length +
s.length + 2;
+
+ byte[] output = new byte[outputLength];
+
+ System.arraycopy(iv, 0, output, 0, iv.length);
+ int count = iv.length;
+ if(r.length > 255 || s.length > 255)
+ throw new IllegalStateException("R or S is too long:
r.length="+r.length+" s.length="+s.length);
+ output[count++] = (byte) r.length;
+ System.arraycopy(r, 0, output, count, r.length);
+ count += r.length;
+ output[count++] = (byte) s.length;
+ System.arraycopy(s, 0, output, count, s.length);
+ count += s.length;
+ System.arraycopy(data, 0, output, count, data.length);
+
+ pcfb.blockEncipher(output, 0, output.length);
+
+ sendAuthPacket(1, 1, phase, output, pn, replyTo);
+ }
+
+ /**
+ * Send a first-half (phase 0 or 1) DH negotiation packet to the node.
+ * @param phase The phase of the message to be sent (0 or 1).
+ * @param negType The negotiation type.
+ * @param integer Our exponential
+ * @param replyTo The peer to reply to
+ */
+ private void sendFirstHalfDHPacket(int phase, int negType,
NativeBigInteger integer, PeerNode pn, Peer replyTo) {
+ long time1 = System.currentTimeMillis();
+ if(logMINOR) Logger.minor(this, "Sending ("+phase+")
"+integer.toHexString()+" to "+pn.getPeer());
+ byte[] data = stripBigIntegerToNetworkFormat(integer);
+ if(logMINOR) Logger.minor(this, "Processed:
"+HexUtil.bytesToHex(data));
+ long time2 = System.currentTimeMillis();
+ if((time2 - time1) > 200) {
+ Logger.error(this, "sendFirstHalfDHPacket: time2 is
more than 200ms after time1 ("+(time2 - time1)+") working on "+replyTo+" of
"+pn.userToString());
+ }
+ sendAuthPacket(1, negType, phase, data, pn, replyTo);
+ long time3 = System.currentTimeMillis();
+ if((time3 - time2) > 500) {
+ Logger.error(this,
"sendFirstHalfDHPacket:sendAuthPacket() time3 is more than half a second after
time2 ("+(time3 - time2)+") working on "+replyTo+" of "+pn.userToString());
+ }
+ if((time3 - time1) > 500) {
+ Logger.error(this, "sendFirstHalfDHPacket: time3 is
more than half a second after time1 ("+(time3 - time1)+") working on
"+replyTo+" of "+pn.userToString());
+ }
+ }
+
+ /**
+ * Send an auth packet.
+ */
+ private void sendAuthPacket(int version, int negType, int phase, byte[]
data, PeerNode pn, Peer replyTo) {
+ long now = System.currentTimeMillis();
+ long delta = now - pn.lastSentPacketTime();
+ byte[] output = new byte[data.length+3];
+ output[0] = (byte) version;
+ output[1] = (byte) negType;
+ output[2] = (byte) phase;
+ System.arraycopy(data, 0, output, 3, data.length);
+ if(logMINOR) Logger.minor(this, "Sending auth packet for
"+pn.getPeer()+" (phase="+phase+", ver="+version+", nt="+negType+") (last
packet sent "+TimeUtil.formatTime(delta, 2, true)+" ago) to "+replyTo+"
data.length="+data.length);
+ sendAuthPacket(output, pn, replyTo);
+ }
+
+ /**
+ * Send an auth packet (we have constructed the payload, now hash it,
pad it, encrypt it).
+ */
+ private void sendAuthPacket(byte[] output, PeerNode pn, Peer replyTo) {
+ int length = output.length;
+ if(length > sock.getMaxPacketSize()) {
+ throw new IllegalStateException("Cannot send auth
packet: too long: "+length);
+ }
+ BlockCipher cipher = pn.outgoingSetupCipher;
+ if(logMINOR) Logger.minor(this, "Outgoing cipher:
"+HexUtil.bytesToHex(pn.outgoingSetupKey));
+ PCFBMode pcfb = PCFBMode.create(cipher);
+ int paddingLength = node.fastWeakRandom.nextInt(100);
+ byte[] iv = new byte[pcfb.lengthIV()];
+ node.random.nextBytes(iv);
+ byte[] hash = SHA256.digest(output);
+ if(logMINOR) Logger.minor(this, "Data hash:
"+HexUtil.bytesToHex(hash));
+ byte[] data = new byte[iv.length + hash.length + 2 /* length */
+ output.length + paddingLength];
+ pcfb.reset(iv);
+ System.arraycopy(iv, 0, data, 0, iv.length);
+ pcfb.blockEncipher(hash, 0, hash.length);
+ System.arraycopy(hash, 0, data, iv.length, hash.length);
+ if(logMINOR) Logger.minor(this, "Payload length: "+length);
+ data[hash.length+iv.length] = (byte)
pcfb.encipher((byte)(length>>8));
+ data[hash.length+iv.length+1] = (byte)
pcfb.encipher((byte)length);
+ pcfb.blockEncipher(output, 0, output.length);
+ System.arraycopy(output, 0, data, hash.length+iv.length+2,
output.length);
+ byte[] random = new byte[paddingLength];
+ node.fastWeakRandom.nextBytes(random);
+ System.arraycopy(random, 0, data,
hash.length+iv.length+2+output.length, random.length);
+ try {
+ sendPacket(data, replyTo, pn, 0);
} catch (LocalAddressException e) {
Logger.error(this, "Tried to send auth packet to local
address: "+replyTo+" for "+pn+" - maybe you should set allowLocalAddresses for
this peer??");
}
- }
+ }
- private void sendPacket(byte[] data, Peer replyTo, PeerNode pn, int
alreadyReportedBytes) throws LocalAddressException {
- if(pn.isIgnoreSource()) {
- Peer p = pn.getPeer();
- if(p != null) replyTo = p;
- }
- sock.sendPacket(data, replyTo, pn.allowLocalAddresses());
- pn.reportOutgoingBytes(data.length);
- node.outputThrottle.forceGrab(data.length - alreadyReportedBytes);
+ private void sendPacket(byte[] data, Peer replyTo, PeerNode pn, int
alreadyReportedBytes) throws LocalAddressException {
+ if(pn.isIgnoreSource()) {
+ Peer p = pn.getPeer();
+ if(p != null) replyTo = p;
+ }
+ sock.sendPacket(data, replyTo, pn.allowLocalAddresses());
+ pn.reportOutgoingBytes(data.length);
+ node.outputThrottle.forceGrab(data.length -
alreadyReportedBytes);
}
- /**
- * Process a stage 2 or stage 3 auth packet.
- * Send a signed DH completion message.
- * Format:
- * IV
- * Signature on { My exponential, his exponential, data }
- * Data
- *
- * May decrypt in place.
- * @param oldOpennetPeer If true, the peer we are negotiating with is not
in
- * the primary routing table, it needs to be promoted from the list of old
opennet
- * nodes.
- */
- private DiffieHellmanContext processSignedDHTwoOrThree(int phase, byte[]
payload, PeerNode pn, Peer replyTo, boolean sendCompletion, boolean
oldOpennetPeer) {
- if(logMINOR) Logger.minor(this, "Handling signed stage "+phase+" auth
packet");
- // Get context, cipher, IV
- DiffieHellmanContext ctx = (DiffieHellmanContext)
pn.getKeyAgreementSchemeContext();
- if((ctx == null) || !ctx.canGetCipher()) {
- if(shouldLogErrorInHandshake()) {
- Logger.error(this, "Cannot get cipher");
- }
- return null;
- }
- byte[] encKey = ctx.getKey();
- BlockCipher cipher = ctx.getCipher();
- PCFBMode pcfb = PCFBMode.create(cipher);
- int ivLength = pcfb.lengthIV();
- if(payload.length-3 < HASH_LENGTH + ivLength + 8) {
- Logger.error(this, "Too short phase "+phase+" packet from
"+replyTo+" probably from "+pn);
- return null;
- }
- pcfb.reset(payload, 3); // IV
-
- // Decrypt the rest
- pcfb.blockDecipher(payload, 3, payload.length - 3);
-
- int count = 3 + ivLength;
-
- // R
- int rLen = payload[count++] & 0xFF;
- byte[] rBytes = new byte[rLen];
- System.arraycopy(payload, count, rBytes, 0, rLen);
- count += rLen;
- NativeBigInteger r = new NativeBigInteger(1, rBytes);
-
- // S
- int sLen = payload[count++] & 0xFF;
- byte[] sBytes = new byte[sLen];
- System.arraycopy(payload, count, sBytes, 0, sLen);
- count += sLen;
- NativeBigInteger s = new NativeBigInteger(1, sBytes);
-
- DSASignature sig = new DSASignature(r, s);
-
- // Data
- byte[] data = new byte[payload.length - count];
- System.arraycopy(payload, count, data, 0, payload.length - count);
-
- // Now verify
- MessageDigest md = SHA256.getMessageDigest();
- md.update(ctx.getHisExponential().toByteArray());
- md.update(ctx.getOurExponential().toByteArray());
- md.update(data);
- byte[] hash = md.digest();
- if(!pn.verify(hash, sig)) {
- Logger.error(this, "Signature verification failed for "+pn+"
hash "+HexUtil.bytesToHex(hash)+"
r="+HexUtil.bytesToHex(sig.getR().toByteArray())+"
s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
- return null;
- }
-
- // Success!
- long bootID = Fields.bytesToLong(data);
-
- // Promote if necessary
- boolean dontWant = false;
- if(oldOpennetPeer) {
- OpennetManager opennet = node.getOpennet();
- if(opennet == null) {
- Logger.normal(this, "Dumping incoming old-opennet peer
as opennet just turned off: "+pn+".");
- return null;
- }
- if(!opennet.wantPeer(pn, true)) {
- Logger.normal(this, "No longer want peer "+pn+"
- dumping it after connecting");
- dontWant = true;
- }
- // wantPeer will call node.peers.addPeer(), we don't
have to.
- }
-
- // Send the completion before parsing the data, because this is easiest
- // Doesn't really matter - if it fails, we get loads of errors
anyway...
- // Only downside is that the other side might still think we are
connected for a while.
- // But this should be extremely rare.
- // REDFLAG?
- // We need to send the completion before the PN sends any packets,
that's all...
- if(pn.completedHandshake(bootID, data, 8, data.length-8, cipher,
encKey, replyTo, phase == 2)) {
- if(sendCompletion)
- sendSignedDHCompletion(3, ctx.getCipher(), pn, replyTo,
ctx);
- if(dontWant)
- node.peers.disconnect(pn, true, false);
- else
- pn.maybeSendInitialMessages();
- } else {
- Logger.error(this, "Handshake not completed");
- }
- return ctx;
- }
+ /**
+ * Process a stage 2 or stage 3 auth packet.
+ * Send a signed DH completion message.
+ * Format:
+ * IV
+ * Signature on { My exponential, his exponential, data }
+ * Data
+ *
+ * May decrypt in place.
+ * @param oldOpennetPeer If true, the peer we are negotiating with is
not in
+ * the primary routing table, it needs to be promoted from the list of
old opennet
+ * nodes.
+ */
+ private DiffieHellmanContext processSignedDHTwoOrThree(int phase,
byte[] payload, PeerNode pn, Peer replyTo, boolean sendCompletion, boolean
oldOpennetPeer) {
+ if(logMINOR) Logger.minor(this, "Handling signed stage
"+phase+" auth packet");
+ // Get context, cipher, IV
+ DiffieHellmanContext ctx = (DiffieHellmanContext)
pn.getKeyAgreementSchemeContext();
+ if((ctx == null) || !ctx.canGetCipher()) {
+ if(shouldLogErrorInHandshake()) {
+ Logger.error(this, "Cannot get cipher");
+ }
+ return null;
+ }
+ byte[] encKey = ctx.getKey();
+ BlockCipher cipher = ctx.getCipher();
+ PCFBMode pcfb = PCFBMode.create(cipher);
+ int ivLength = pcfb.lengthIV();
+ if(payload.length-3 < HASH_LENGTH + ivLength + 8) {
+ Logger.error(this, "Too short phase "+phase+" packet
from "+replyTo+" probably from "+pn);
+ return null;
+ }
+ pcfb.reset(payload, 3); // IV
- /**
- * Should we log an error for an event that could easily be
- * caused by a handshake across a restart boundary?
- */
- private boolean shouldLogErrorInHandshake() {
- long now = System.currentTimeMillis();
- if(now - node.startupTime < Node.HANDSHAKE_TIMEOUT*2)
- return false;
- return true;
- }
+ // Decrypt the rest
+ pcfb.blockDecipher(payload, 3, payload.length - 3);
- /**
- * Process a phase-0 or phase-1 Diffie-Hellman packet.
- * @return a DiffieHellmanContext if we succeeded, otherwise null.
- */
- private DiffieHellmanContext processDHZeroOrOne(int phase, byte[] payload,
PeerNode pn) {
-
- if((phase == 0) && pn.hasLiveHandshake(System.currentTimeMillis())) {
- if(logMINOR) Logger.minor(this, "Rejecting phase "+phase+"
handshake on "+pn+" - already running one");
- return null;
- }
-
- // First, get the BigInteger
- int length = DiffieHellman.modulusLengthInBytes();
- if(payload.length < length + 3) {
- Logger.error(this, "Packet too short: "+payload.length+" after
decryption in DH("+phase+"), should be "+(length + 3));
- return null;
- }
- byte[] aAsBytes = new byte[length];
- System.arraycopy(payload, 3, aAsBytes, 0, length);
- NativeBigInteger a = new NativeBigInteger(1, aAsBytes);
- DiffieHellmanContext ctx;
- if(phase == 1) {
- ctx = (DiffieHellmanContext) pn.getKeyAgreementSchemeContext();
- if(ctx == null) {
- if(shouldLogErrorInHandshake())
- Logger.error(this, "Could not get context for phase 1
handshake from "+pn);
- return null;
- }
- } else {
- ctx = DiffieHellman.generateContext();
- // Don't calculate the key until we need it
- pn.setKeyAgreementSchemeContext(ctx);
- }
- ctx.setOtherSideExponential(a);
- if(logMINOR) Logger.minor(this, "His exponential: "+a.toHexString());
- // REDFLAG: This is of course easily DoS'ed if you know the node.
- // We will fix this by means of JFKi.
- return ctx;
- }
+ int count = 3 + ivLength;
- /**
- * Try to process an incoming packet with a given PeerNode.
- * We need to know where the packet has come from in order to
- * decrypt and authenticate it.
- */
- private boolean tryProcess(byte[] buf, int offset, int length, KeyTracker
tracker) {
- // Need to be able to call with tracker == null to simplify code above
- if(tracker == null) {
- if(Logger.shouldLog(Logger.DEBUG, this)) Logger.debug(this,
"Tracker == null");
- return false;
- }
- if(logMINOR) Logger.minor(this,"Entering tryProcess:
"+Fields.hashCode(buf)+ ',' +offset+ ',' +length+ ',' +tracker);
- /**
- * E_pcbc_session(H(seq+random+data)) E_pcfb_session(seq+random+data)
- *
- * So first two blocks are the hash, PCBC encoded (meaning the
- * first one is ECB, and the second one is ECB XORed with the
- * ciphertext and plaintext of the first block).
- */
- BlockCipher sessionCipher = tracker.sessionCipher;
- if(sessionCipher == null) {
- if(logMINOR) Logger.minor(this, "No cipher");
- return false;
- }
- if(logMINOR) Logger.minor(this, "Decrypting with
"+HexUtil.bytesToHex(tracker.sessionKey));
- int blockSize = sessionCipher.getBlockSize() >> 3;
- if(sessionCipher.getKeySize() != sessionCipher.getBlockSize())
- throw new IllegalStateException("Block size must be equal to key
size");
-
- if(HASH_LENGTH != blockSize)
- throw new IllegalStateException("Block size must be digest
length!");
+ // R
+ int rLen = payload[count++] & 0xFF;
+ byte[] rBytes = new byte[rLen];
+ System.arraycopy(payload, count, rBytes, 0, rLen);
+ count += rLen;
+ NativeBigInteger r = new NativeBigInteger(1, rBytes);
- byte[] packetHash = new byte[HASH_LENGTH];
- System.arraycopy(buf, offset, packetHash, 0, HASH_LENGTH);
-
- // Decrypt the sequence number and see if it's plausible
- // Verify the hash later
-
- PCFBMode pcfb;
- pcfb = PCFBMode.create(sessionCipher);
- // Set IV to the hash, after it is encrypted
- pcfb.reset(packetHash);
- //Logger.minor(this,"IV:\n"+HexUtil.bytesToHex(packetHash));
-
- byte[] seqBuf = new byte[4];
- System.arraycopy(buf, offset+HASH_LENGTH, seqBuf, 0, 4);
- //Logger.minor(this, "Encypted sequence number:
"+HexUtil.bytesToHex(seqBuf));
- pcfb.blockDecipher(seqBuf, 0, 4);
- //Logger.minor(this, "Decrypted sequence number:
"+HexUtil.bytesToHex(seqBuf));
-
- int seqNumber = ((((((seqBuf[0] & 0xff) << 8)
- + (seqBuf[1] & 0xff)) << 8) +
- (seqBuf[2] & 0xff)) << 8) +
- (seqBuf[3] & 0xff);
+ // S
+ int sLen = payload[count++] & 0xFF;
+ byte[] sBytes = new byte[sLen];
+ System.arraycopy(payload, count, sBytes, 0, sLen);
+ count += sLen;
+ NativeBigInteger s = new NativeBigInteger(1, sBytes);
- int targetSeqNumber = tracker.highestReceivedIncomingSeqNumber();
- if(logMINOR) Logger.minor(this, "Seqno: "+seqNumber+" (highest seen
"+targetSeqNumber+") receiving packet from "+tracker.pn.getPeer());
+ DSASignature sig = new DSASignature(r, s);
- if(seqNumber == -1) {
- // Ack/resendreq-only packet
- } else {
- // Now is it credible?
- // As long as it's within +/- 256, this is valid.
- if((targetSeqNumber != -1) && (Math.abs(targetSeqNumber -
seqNumber) > MAX_PACKETS_IN_FLIGHT))
- return false;
- }
- if(logMINOR) Logger.minor(this, "Sequence number received:
"+seqNumber);
-
- // Plausible, so lets decrypt the rest of the data
-
- byte[] plaintext = new byte[length-(4+HASH_LENGTH)];
- System.arraycopy(buf, offset+HASH_LENGTH+4, plaintext, 0,
length-(HASH_LENGTH+4));
-
- pcfb.blockDecipher(plaintext, 0, length-(HASH_LENGTH+4));
-
- //Logger.minor(this, "Plaintext:\n"+HexUtil.bytesToHex(plaintext));
-
- MessageDigest md = SHA256.getMessageDigest();
- md.update(seqBuf);
- md.update(plaintext);
- byte[] realHash = md.digest();
- SHA256.returnMessageDigest(md); md = null;
+ // Data
+ byte[] data = new byte[payload.length - count];
+ System.arraycopy(payload, count, data, 0, payload.length -
count);
- // Now decrypt the original hash
-
- byte[] temp = new byte[blockSize];
- System.arraycopy(buf, offset, temp, 0, blockSize);
- sessionCipher.decipher(temp, temp);
- System.arraycopy(temp, 0, packetHash, 0, blockSize);
-
- // Check the hash
- if(!Arrays.equals(packetHash, realHash)) {
- if(logMINOR) Logger.minor(this, "Packet possibly from "+tracker+"
hash does not match:\npacketHash="+
- HexUtil.bytesToHex(packetHash)+"\n
realHash="+HexUtil.bytesToHex(realHash)+" ("+(length-HASH_LENGTH)+" bytes
payload)");
- return false;
- }
-
- // Verify
- tracker.pn.verified(tracker);
-
- for(int i=0;i<HASH_LENGTH;i++) {
- packetHash[i] ^= buf[offset+i];
- }
- if(logMINOR) Logger.minor(this, "Contributing entropy");
- node.random.acceptEntropyBytes(myPacketDataSource, packetHash, 0,
HASH_LENGTH, 0.5);
- if(logMINOR) Logger.minor(this, "Contributed entropy");
-
- // Lots more to do yet!
- processDecryptedData(plaintext, seqNumber, tracker, length -
plaintext.length);
- tracker.pn.reportIncomingBytes(length);
- return true;
- }
+ // Now verify
+ MessageDigest md = SHA256.getMessageDigest();
+ md.update(ctx.getHisExponential().toByteArray());
+ md.update(ctx.getOurExponential().toByteArray());
+ md.update(data);
+ byte[] hash = md.digest();
+ if(!pn.verify(hash, sig)) {
+ Logger.error(this, "Signature verification failed for
"+pn+" hash "+HexUtil.bytesToHex(hash)+"
r="+HexUtil.bytesToHex(sig.getR().toByteArray())+"
s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
+ return null;
+ }
- /**
- * Process an incoming packet, once it has been decrypted.
- * @param decrypted The packet's contents.
- * @param seqNumber The detected sequence number of the packet.
- * @param tracker The KeyTracker responsible for the key used to encrypt
the packet.
- */
- private void processDecryptedData(byte[] decrypted, int seqNumber,
KeyTracker tracker, int overhead) {
- /**
- * Decoded format:
- * 1 byte - version number (0)
- * 1 byte - number of acknowledgements
- * Acknowledgements:
- * 1 byte - ack (+ve integer, subtract from seq-1) to get seq# to ack
- *
- * 1 byte - number of explicit retransmit requests
- * Explicit retransmit requests:
- * 1 byte - retransmit request (+ve integer, subtract from seq-1) to
get seq# to resend
- *
- * 1 byte - number of packets forgotten
- * Forgotten packets:
- * 1 byte - forgotten packet seq# (+ve integer, subtract from seq-1)
to get seq# lost
- *
- * 1 byte - number of messages
- * 2 bytes - message length
- * first message
- * 2 bytes - second message length
- * second message
- * ...
- * last message
- * anything beyond this point is padding, to be ignored
- */
-
- // Use ptr to simplify code
- int ptr = RANDOM_BYTES_LENGTH;
-
- int version = decrypted[ptr++];
- if(ptr > decrypted.length) {
- Logger.error(this, "Packet not long enough at byte "+ptr+" on
"+tracker);
- return;
- }
- if(version != 0) {
- Logger.error(this,"Packet from "+tracker+" decrypted but invalid
version: "+version);
- return;
- }
+ // Success!
+ long bootID = Fields.bytesToLong(data);
- /** Highest sequence number sent - not the same as this packet's seq
number */
- int realSeqNumber = seqNumber;
-
- if(seqNumber == -1) {
- if(ptr+4 > decrypted.length) {
- Logger.error(this, "Packet not long enough at byte "+ptr+" on
"+tracker);
- return;
- }
- realSeqNumber =
- ((((((decrypted[ptr+0] & 0xff) << 8) + (decrypted[ptr+1] &
0xff)) << 8) +
- (decrypted[ptr+2] & 0xff)) << 8) + (decrypted[ptr+3] &
0xff);
- ptr+=4;
- } else {
- if(ptr > decrypted.length) {
- Logger.error(this, "Packet not long enough at byte "+ptr+" on
"+tracker);
- return;
- }
- realSeqNumber = seqNumber + (decrypted[ptr++] & 0xff);
- }
-
- //Logger.minor(this, "Reference seq number:
"+HexUtil.bytesToHex(decrypted, ptr, 4));
-
- if(ptr+4 > decrypted.length) {
- Logger.error(this, "Packet not long enough at byte "+ptr+" on
"+tracker);
- return;
- }
- int referenceSeqNumber =
- ((((((decrypted[ptr+0] & 0xff) << 8) + (decrypted[ptr+1] & 0xff))
<< 8) +
- (decrypted[ptr+2] & 0xff)) << 8) + (decrypted[ptr+3] &
0xff);
- ptr+=4;
-
- if(logMINOR) Logger.minor(this, "Reference sequence number:
"+referenceSeqNumber);
+ // Promote if necessary
+ boolean dontWant = false;
+ if(oldOpennetPeer) {
+ OpennetManager opennet = node.getOpennet();
+ if(opennet == null) {
+ Logger.normal(this, "Dumping incoming
old-opennet peer as opennet just turned off: "+pn+".");
+ return null;
+ }
+ if(!opennet.wantPeer(pn, true)) {
+ Logger.normal(this, "No longer want peer "+pn+"
- dumping it after connecting");
+ dontWant = true;
+ }
+ // wantPeer will call node.peers.addPeer(), we don't
have to.
+ }
- int ackCount = decrypted[ptr++] & 0xff;
- if(logMINOR) Logger.minor(this, "Acks: "+ackCount);
+ // Send the completion before parsing the data, because this is
easiest
+ // Doesn't really matter - if it fails, we get loads of errors
anyway...
+ // Only downside is that the other side might still think we
are connected for a while.
+ // But this should be extremely rare.
+ // REDFLAG?
+ // We need to send the completion before the PN sends any
packets, that's all...
+ if(pn.completedHandshake(bootID, data, 8, data.length-8,
cipher, encKey, replyTo, phase == 2)) {
+ if(sendCompletion)
+ sendSignedDHCompletion(3, ctx.getCipher(), pn,
replyTo, ctx);
+ if(dontWant)
+ node.peers.disconnect(pn, true, false);
+ else
+ pn.maybeSendInitialMessages();
+ } else {
+ Logger.error(this, "Handshake not completed");
+ }
+ return ctx;
+ }
- int[] acks = new int[ackCount];
- for(int i=0;i<ackCount;i++) {
- int offset = decrypted[ptr++] & 0xff;
- if(ptr > decrypted.length) {
- Logger.error(this, "Packet not long enough at byte "+ptr+" on
"+tracker);
- return;
- }
- acks[i] = referenceSeqNumber - offset;
- }
-
- tracker.acknowledgedPackets(acks);
-
- int retransmitCount = decrypted[ptr++] & 0xff;
- if(logMINOR) Logger.minor(this, "Retransmit requests:
"+retransmitCount);
-
- for(int i=0;i<retransmitCount;i++) {
- int offset = decrypted[ptr++] & 0xff;
- if(ptr > decrypted.length) {
- Logger.error(this, "Packet not long enough at byte "+ptr+" on
"+tracker);
- }
- int realSeqNo = referenceSeqNumber - offset;
- if(logMINOR) Logger.minor(this, "RetransmitRequest: "+realSeqNo);
- tracker.resendPacket(realSeqNo);
- }
+ /**
+ * Should we log an error for an event that could easily be
+ * caused by a handshake across a restart boundary?
+ */
+ private boolean shouldLogErrorInHandshake() {
+ long now = System.currentTimeMillis();
+ if(now - node.startupTime < Node.HANDSHAKE_TIMEOUT*2)
+ return false;
+ return true;
+ }
- int ackRequestsCount = decrypted[ptr++] & 0xff;
- if(logMINOR) Logger.minor(this, "Ack requests: "+ackRequestsCount);
-
- // These two are relative to our outgoing packet number
- // Because they relate to packets we have sent.
- for(int i=0;i<ackRequestsCount;i++) {
- int offset = decrypted[ptr++] & 0xff;
- if(ptr > decrypted.length) {
- Logger.error(this, "Packet not long enough at byte "+ptr+" on
"+tracker);
- }
- int realSeqNo = realSeqNumber - offset;
- if(logMINOR) Logger.minor(this, "AckRequest: "+realSeqNo);
- tracker.receivedAckRequest(realSeqNo);
- }
-
- int forgottenCount = decrypted[ptr++] & 0xff;
- if(logMINOR) Logger.minor(this, "Forgotten packets: "+forgottenCount);
-
- for(int i=0;i<forgottenCount;i++) {
- int offset = decrypted[ptr++] & 0xff;
- if(ptr > decrypted.length) {
- Logger.error(this, "Packet not long enough at byte "+ptr+" on
"+tracker);
- }
- int realSeqNo = realSeqNumber - offset;
- tracker.destForgotPacket(realSeqNo);
- }
+ /**
+ * Process a phase-0 or phase-1 Diffie-Hellman packet.
+ * @return a DiffieHellmanContext if we succeeded, otherwise null.
+ */
+ private DiffieHellmanContext processDHZeroOrOne(int phase, byte[]
payload, PeerNode pn) {
+ if((phase == 0) &&
pn.hasLiveHandshake(System.currentTimeMillis())) {
+ if(logMINOR) Logger.minor(this, "Rejecting phase
"+phase+" handshake on "+pn+" - already running one");
+ return null;
+ }
+
+ // First, get the BigInteger
+ int length = DiffieHellman.modulusLengthInBytes();
+ if(payload.length < length + 3) {
+ Logger.error(this, "Packet too short:
"+payload.length+" after decryption in DH("+phase+"), should be "+(length + 3));
+ return null;
+ }
+ byte[] aAsBytes = new byte[length];
+ System.arraycopy(payload, 3, aAsBytes, 0, length);
+ NativeBigInteger a = new NativeBigInteger(1, aAsBytes);
+ DiffieHellmanContext ctx;
+ if(phase == 1) {
+ ctx = (DiffieHellmanContext)
pn.getKeyAgreementSchemeContext();
+ if(ctx == null) {
+ if(shouldLogErrorInHandshake())
+ Logger.error(this, "Could not get
context for phase 1 handshake from "+pn);
+ return null;
+ }
+ } else {
+ ctx = DiffieHellman.generateContext();
+ // Don't calculate the key until we need it
+ pn.setKeyAgreementSchemeContext(ctx);
+ }
+ ctx.setOtherSideExponential(a);
+ if(logMINOR) Logger.minor(this, "His exponential:
"+a.toHexString());
+ // REDFLAG: This is of course easily DoS'ed if you know the
node.
+ // We will fix this by means of JFKi.
+ return ctx;
+ }
+
+ /**
+ * Try to process an incoming packet with a given PeerNode.
+ * We need to know where the packet has come from in order to
+ * decrypt and authenticate it.
+ */
+ private boolean tryProcess(byte[] buf, int offset, int length,
KeyTracker tracker) {
+ // Need to be able to call with tracker == null to simplify
code above
+ if(tracker == null) {
+ if(Logger.shouldLog(Logger.DEBUG, this))
Logger.debug(this, "Tracker == null");
+ return false;
+ }
+ if(logMINOR) Logger.minor(this,"Entering tryProcess:
"+Fields.hashCode(buf)+ ',' +offset+ ',' +length+ ',' +tracker);
+ /**
+ * E_pcbc_session(H(seq+random+data))
E_pcfb_session(seq+random+data)
+ *
+ * So first two blocks are the hash, PCBC encoded (meaning the
+ * first one is ECB, and the second one is ECB XORed with the
+ * ciphertext and plaintext of the first block).
+ */
+ BlockCipher sessionCipher = tracker.sessionCipher;
+ if(sessionCipher == null) {
+ if(logMINOR) Logger.minor(this, "No cipher");
+ return false;
+ }
+ if(logMINOR) Logger.minor(this, "Decrypting with
"+HexUtil.bytesToHex(tracker.sessionKey));
+ int blockSize = sessionCipher.getBlockSize() >> 3;
+ if(sessionCipher.getKeySize() != sessionCipher.getBlockSize())
+ throw new IllegalStateException("Block size must be
equal to key size");
+
+ if(HASH_LENGTH != blockSize)
+ throw new IllegalStateException("Block size must be
digest length!");
+
+ byte[] packetHash = new byte[HASH_LENGTH];
+ System.arraycopy(buf, offset, packetHash, 0, HASH_LENGTH);
+
+ // Decrypt the sequence number and see if it's plausible
+ // Verify the hash later
+
+ PCFBMode pcfb;
+ pcfb = PCFBMode.create(sessionCipher);
+ // Set IV to the hash, after it is encrypted
+ pcfb.reset(packetHash);
+ //Logger.minor(this,"IV:\n"+HexUtil.bytesToHex(packetHash));
+
+ byte[] seqBuf = new byte[4];
+ System.arraycopy(buf, offset+HASH_LENGTH, seqBuf, 0, 4);
+ //Logger.minor(this, "Encypted sequence number:
"+HexUtil.bytesToHex(seqBuf));
+ pcfb.blockDecipher(seqBuf, 0, 4);
+ //Logger.minor(this, "Decrypted sequence number:
"+HexUtil.bytesToHex(seqBuf));
+
+ int seqNumber = ((((((seqBuf[0] & 0xff) << 8)
+ + (seqBuf[1] & 0xff)) << 8) +
+ (seqBuf[2] & 0xff)) << 8) +
+ (seqBuf[3] & 0xff);
+
+ int targetSeqNumber =
tracker.highestReceivedIncomingSeqNumber();
+ if(logMINOR) Logger.minor(this, "Seqno: "+seqNumber+" (highest
seen "+targetSeqNumber+") receiving packet from "+tracker.pn.getPeer());
+
+ if(seqNumber == -1) {
+ // Ack/resendreq-only packet
+ } else {
+ // Now is it credible?
+ // As long as it's within +/- 256, this is valid.
+ if((targetSeqNumber != -1) && (Math.abs(targetSeqNumber
- seqNumber) > MAX_PACKETS_IN_FLIGHT))
+ return false;
+ }
+ if(logMINOR) Logger.minor(this, "Sequence number received:
"+seqNumber);
+
+ // Plausible, so lets decrypt the rest of the data
+
+ byte[] plaintext = new byte[length-(4+HASH_LENGTH)];
+ System.arraycopy(buf, offset+HASH_LENGTH+4, plaintext, 0,
length-(HASH_LENGTH+4));
+
+ pcfb.blockDecipher(plaintext, 0, length-(HASH_LENGTH+4));
+
+ //Logger.minor(this,
"Plaintext:\n"+HexUtil.bytesToHex(plaintext));
+
+ MessageDigest md = SHA256.getMessageDigest();
+ md.update(seqBuf);
+ md.update(plaintext);
+ byte[] realHash = md.digest();
+ SHA256.returnMessageDigest(md); md = null;
+
+ // Now decrypt the original hash
+
+ byte[] temp = new byte[blockSize];
+ System.arraycopy(buf, offset, temp, 0, blockSize);
+ sessionCipher.decipher(temp, temp);
+ System.arraycopy(temp, 0, packetHash, 0, blockSize);
+
+ // Check the hash
+ if(!Arrays.equals(packetHash, realHash)) {
+ if(logMINOR) Logger.minor(this, "Packet possibly from
"+tracker+" hash does not match:\npacketHash="+
+ HexUtil.bytesToHex(packetHash)+"\n
realHash="+HexUtil.bytesToHex(realHash)+" ("+(length-HASH_LENGTH)+" bytes
payload)");
+ return false;
+ }
+
+ // Verify
+ tracker.pn.verified(tracker);
+
+ for(int i=0;i<HASH_LENGTH;i++) {
+ packetHash[i] ^= buf[offset+i];
+ }
+ if(logMINOR) Logger.minor(this, "Contributing entropy");
+ node.random.acceptEntropyBytes(myPacketDataSource, packetHash,
0, HASH_LENGTH, 0.5);
+ if(logMINOR) Logger.minor(this, "Contributed entropy");
+
+ // Lots more to do yet!
+ processDecryptedData(plaintext, seqNumber, tracker, length -
plaintext.length);
+ tracker.pn.reportIncomingBytes(length);
+ return true;
+ }
+
+ /**
+ * Process an incoming packet, once it has been decrypted.
+ * @param decrypted The packet's contents.
+ * @param seqNumber The detected sequence number of the packet.
+ * @param tracker The KeyTracker responsible for the key used to
encrypt the packet.
+ */
+ private void processDecryptedData(byte[] decrypted, int seqNumber,
KeyTracker tracker, int overhead) {
+ /**
+ * Decoded format:
+ * 1 byte - version number (0)
+ * 1 byte - number of acknowledgements
+ * Acknowledgements:
+ * 1 byte - ack (+ve integer, subtract from seq-1) to get seq#
to ack
+ *
+ * 1 byte - number of explicit retransmit requests
+ * Explicit retransmit requests:
+ * 1 byte - retransmit request (+ve integer, subtract from
seq-1) to get seq# to resend
+ *
+ * 1 byte - number of packets forgotten
+ * Forgotten packets:
+ * 1 byte - forgotten packet seq# (+ve integer, subtract from
seq-1) to get seq# lost
+ *
+ * 1 byte - number of messages
+ * 2 bytes - message length
+ * first message
+ * 2 bytes - second message length
+ * second message
+ * ...
+ * last message
+ * anything beyond this point is padding, to be ignored
+ */
+
+ // Use ptr to simplify code
+ int ptr = RANDOM_BYTES_LENGTH;
+
+ int version = decrypted[ptr++];
+ if(ptr > decrypted.length) {
+ Logger.error(this, "Packet not long enough at byte
"+ptr+" on "+tracker);
+ return;
+ }
+ if(version != 0) {
+ Logger.error(this,"Packet from "+tracker+" decrypted
but invalid version: "+version);
+ return;
+ }
+
+ /** Highest sequence number sent - not the same as this
packet's seq number */
+ int realSeqNumber = seqNumber;
+
+ if(seqNumber == -1) {
+ if(ptr+4 > decrypted.length) {
+ Logger.error(this, "Packet not long enough at
byte "+ptr+" on "+tracker);
+ return;
+ }
+ realSeqNumber =
+ ((((((decrypted[ptr+0] & 0xff) << 8) +
(decrypted[ptr+1] & 0xff)) << 8) +
+ (decrypted[ptr+2] & 0xff)) <<
8) + (decrypted[ptr+3] & 0xff);
+ ptr+=4;
+ } else {
+ if(ptr > decrypted.length) {
+ Logger.error(this, "Packet not long enough at
byte "+ptr+" on "+tracker);
+ return;
+ }
+ realSeqNumber = seqNumber + (decrypted[ptr++] & 0xff);
+ }
+
+ //Logger.minor(this, "Reference seq number:
"+HexUtil.bytesToHex(decrypted, ptr, 4));
+
+ if(ptr+4 > decrypted.length) {
+ Logger.error(this, "Packet not long enough at byte
"+ptr+" on "+tracker);
+ return;
+ }
+ int referenceSeqNumber =
+ ((((((decrypted[ptr+0] & 0xff) << 8) +
(decrypted[ptr+1] & 0xff)) << 8) +
+ (decrypted[ptr+2] & 0xff)) << 8) +
(decrypted[ptr+3] & 0xff);
+ ptr+=4;
+
+ if(logMINOR) Logger.minor(this, "Reference sequence number:
"+referenceSeqNumber);
+
+ int ackCount = decrypted[ptr++] & 0xff;
+ if(logMINOR) Logger.minor(this, "Acks: "+ackCount);
+
+ int[] acks = new int[ackCount];
+ for(int i=0;i<ackCount;i++) {
+ int offset = decrypted[ptr++] & 0xff;
+ if(ptr > decrypted.length) {
+ Logger.error(this, "Packet not long enough at
byte "+ptr+" on "+tracker);
+ return;
+ }
+ acks[i] = referenceSeqNumber - offset;
+ }
+
+ tracker.acknowledgedPackets(acks);
+
+ int retransmitCount = decrypted[ptr++] & 0xff;
+ if(logMINOR) Logger.minor(this, "Retransmit requests:
"+retransmitCount);
+
+ for(int i=0;i<retransmitCount;i++) {
+ int offset = decrypted[ptr++] & 0xff;
+ if(ptr > decrypted.length) {
+ Logger.error(this, "Packet not long enough at
byte "+ptr+" on "+tracker);
+ }
+ int realSeqNo = referenceSeqNumber - offset;
+ if(logMINOR) Logger.minor(this, "RetransmitRequest:
"+realSeqNo);
+ tracker.resendPacket(realSeqNo);
+ }
+
+ int ackRequestsCount = decrypted[ptr++] & 0xff;
+ if(logMINOR) Logger.minor(this, "Ack requests:
"+ackRequestsCount);
+
+ // These two are relative to our outgoing packet number
+ // Because they relate to packets we have sent.
+ for(int i=0;i<ackRequestsCount;i++) {
+ int offset = decrypted[ptr++] & 0xff;
+ if(ptr > decrypted.length) {
+ Logger.error(this, "Packet not long enough at
byte "+ptr+" on "+tracker);
+ }
+ int realSeqNo = realSeqNumber - offset;
+ if(logMINOR) Logger.minor(this, "AckRequest:
"+realSeqNo);
+ tracker.receivedAckRequest(realSeqNo);
+ }
+
+ int forgottenCount = decrypted[ptr++] & 0xff;
+ if(logMINOR) Logger.minor(this, "Forgotten packets:
"+forgottenCount);
+
+ for(int i=0;i<forgottenCount;i++) {
+ int offset = decrypted[ptr++] & 0xff;
+ if(ptr > decrypted.length) {
+ Logger.error(this, "Packet not long enough at
byte "+ptr+" on "+tracker);
+ }
+ int realSeqNo = realSeqNumber - offset;
+ tracker.destForgotPacket(realSeqNo);
+ }
+
tracker.pn.receivedPacket(false); // Must keep the connection
open, even if it's an ack packet only and on an incompatible connection - we
may want to do a UOM transfer e.g.
-
- if(seqNumber == -1) {
- if(logMINOR) Logger.minor(this, "Returning because seqno =
"+seqNumber);
- return;
- }
- // No sequence number == no messages
- if((seqNumber != -1) && tracker.alreadyReceived(seqNumber)) {
- tracker.queueAck(seqNumber); // Must keep the connection open!
- Logger.error(this, "Received packet twice ("+seqNumber+") from
"+tracker.pn.getPeer()+": "+seqNumber+" ("+TimeUtil.formatTime((long)
tracker.pn.pingAverage.currentValue(), 2, true)+" ping avg)");
- return;
- }
-
- tracker.receivedPacket(seqNumber);
-
- int messages = decrypted[ptr++] & 0xff;
-
- overhead += ptr;
-
- for(int i=0;i<messages;i++) {
- if(ptr+1 >= decrypted.length) {
- Logger.error(this, "Packet not long enough at byte "+ptr+" on
"+tracker);
- }
- int length = ((decrypted[ptr++] & 0xff) << 8) +
- (decrypted[ptr++] & 0xff);
- if(length > decrypted.length - ptr) {
- Logger.error(this, "Message longer than remaining space:
"+length);
- return;
- }
- if(logMINOR) Logger.minor(this, "Message "+i+" length "+length+",
hash code: "+Fields.hashCode(decrypted, ptr, length));
- Message m = usm.decodeSingleMessage(decrypted, ptr, length,
tracker.pn, 1 + (overhead / messages));
- ptr+=length;
- if(m != null) {
- //Logger.minor(this, "Dispatching packet: "+m);
- usm.checkFilters(m, sock);
- }
- }
- if(logMINOR) Logger.minor(this, "Done");
- }
+ if(seqNumber == -1) {
+ if(logMINOR) Logger.minor(this, "Returning because
seqno = "+seqNumber);
+ return;
+ }
+ // No sequence number == no messages
- /* (non-Javadoc)
+ if((seqNumber != -1) && tracker.alreadyReceived(seqNumber)) {
+ tracker.queueAck(seqNumber); // Must keep the
connection open!
+ Logger.error(this, "Received packet twice
("+seqNumber+") from "+tracker.pn.getPeer()+": "+seqNumber+"
("+TimeUtil.formatTime((long) tracker.pn.pingAverage.currentValue(), 2, true)+"
ping avg)");
+ return;
+ }
+
+ tracker.receivedPacket(seqNumber);
+
+ int messages = decrypted[ptr++] & 0xff;
+
+ overhead += ptr;
+
+ for(int i=0;i<messages;i++) {
+ if(ptr+1 >= decrypted.length) {
+ Logger.error(this, "Packet not long enough at
byte "+ptr+" on "+tracker);
+ }
+ int length = ((decrypted[ptr++] & 0xff) << 8) +
+ (decrypted[ptr++] & 0xff);
+ if(length > decrypted.length - ptr) {
+ Logger.error(this, "Message longer than
remaining space: "+length);
+ return;
+ }
+ if(logMINOR) Logger.minor(this, "Message "+i+" length
"+length+", hash code: "+Fields.hashCode(decrypted, ptr, length));
+ Message m = usm.decodeSingleMessage(decrypted, ptr,
length, tracker.pn, 1 + (overhead / messages));
+ ptr+=length;
+ if(m != null) {
+ //Logger.minor(this, "Dispatching packet: "+m);
+ usm.checkFilters(m, sock);
+ }
+ }
+ if(logMINOR) Logger.minor(this, "Done");
+ }
+
+ /* (non-Javadoc)
* @see
freenet.node.OutgoingPacketMangler#processOutgoingOrRequeue(freenet.node.MessageItem[],
freenet.node.PeerNode, boolean, boolean)
*/
- public void processOutgoingOrRequeue(MessageItem[] messages, PeerNode pn,
boolean neverWaitForPacketNumber, boolean dontRequeue) {
- String requeueLogString = "";
- if(!dontRequeue) {
- requeueLogString = ", requeueing";
- }
- if(logMINOR) Logger.minor(this, "processOutgoingOrRequeue
"+messages.length+" messages for "+pn+" ("+neverWaitForPacketNumber+ ')');
- byte[][] messageData = new byte[messages.length][];
- int[] alreadyReported = new int[messages.length];
- MessageItem[] newMsgs = new MessageItem[messages.length];
- KeyTracker kt = pn.getCurrentKeyTracker();
- if(kt == null) {
- Logger.error(this, "Not connected while sending packets: "+pn);
- return;
- }
- int length = 1;
- length += kt.countAcks() + kt.countAckRequests() +
kt.countResendRequests();
- int callbacksCount = 0;
- int x = 0;
+ public void processOutgoingOrRequeue(MessageItem[] messages, PeerNode
pn, boolean neverWaitForPacketNumber, boolean dontRequeue) {
+ String requeueLogString = "";
+ if(!dontRequeue) {
+ requeueLogString = ", requeueing";
+ }
+ if(logMINOR) Logger.minor(this, "processOutgoingOrRequeue
"+messages.length+" messages for "+pn+" ("+neverWaitForPacketNumber+ ')');
+ byte[][] messageData = new byte[messages.length][];
+ int[] alreadyReported = new int[messages.length];
+ MessageItem[] newMsgs = new MessageItem[messages.length];
+ KeyTracker kt = pn.getCurrentKeyTracker();
+ if(kt == null) {
+ Logger.error(this, "Not connected while sending
packets: "+pn);
+ return;
+ }
+ int length = 1;
+ length += kt.countAcks() + kt.countAckRequests() +
kt.countResendRequests();
+ int callbacksCount = 0;
+ int x = 0;
String mi_name = null;
- for(int i=0;i<messageData.length;i++) {
- MessageItem mi = messages[i];
- if(logMINOR) Logger.minor(this, "Handling formatted MessageItem
"+mi+" : "+mi.getData(pn).length);
+ for(int i=0;i<messageData.length;i++) {
+ MessageItem mi = messages[i];
+ if(logMINOR) Logger.minor(this, "Handling formatted
MessageItem "+mi+" : "+mi.getData(pn).length);
mi_name = (mi.msg == null ? "(not a Message)" :
mi.msg.getSpec().getName());
- if(mi.formatted) {
- try {
- byte[] buf = mi.getData(pn);
- kt = pn.getCurrentKeyTracker();
- if(kt == null) {
- if(logMINOR) Logger.minor(this, "kt = null");
- pn.requeueMessageItems(messages, i, messages.length-i,
false, "kt = null");
- return;
- }
- int packetNumber =
kt.allocateOutgoingPacketNumberNeverBlock();
- this.processOutgoingPreformatted(buf, 0, buf.length,
pn.getCurrentKeyTracker(), packetNumber, mi.cb, mi.alreadyReportedBytes);
- mi.onSent(buf.length + fullHeadersLengthOneMessage);
- } catch (NotConnectedException e) {
- Logger.normal(this, "Caught "+e+" while sending messages
("+mi_name+") to "+pn.getPeer()+requeueLogString);
- // Requeue
- if(!dontRequeue) {
- pn.requeueMessageItems(messages, 0, x, false,
"NotConnectedException(1a)");
- pn.requeueMessageItems(messages, i, messages.length-i,
false, "NotConnectedException(1b)");
- }
- return;
- } catch (WouldBlockException e) {
- if(logMINOR) Logger.minor(this, "Caught "+e+" while
sending messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
- // Requeue
- if(!dontRequeue) {
- pn.requeueMessageItems(messages, 0, x, false,
"WouldBlockException(1a)");
- pn.requeueMessageItems(messages, i, messages.length-i,
false, "WouldBlockException(1b)");
- }
- return;
- } catch (KeyChangedException e) {
- if(logMINOR) Logger.minor(this, "Caught "+e+" while
sending messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
- // Requeue
- if(!dontRequeue) {
- pn.requeueMessageItems(messages, 0, x, false,
"KeyChangedException(1a)");
- pn.requeueMessageItems(messages, i, messages.length-i,
false, "KeyChangedException(1b)");
- }
- return;
- } catch (Throwable e) {
- Logger.error(this, "Caught "+e+" while sending messages
("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
- // Requeue
- if(!dontRequeue) {
- pn.requeueMessageItems(messages, 0, x, false,
"Throwable(1)");
- pn.requeueMessageItems(messages, i, messages.length-i,
false, "Throwable(1)");
- }
- return;
- }
- } else {
- byte[] data = mi.getData(pn);
- messageData[x] = data;
- if(data.length > sock.getMaxPacketSize()) {
- Logger.error(this, "Message exceeds packet size:
"+messages[i]+" size "+data.length+" message "+mi.msg);
- // Will be handled later
- }
- newMsgs[x] = mi;
- alreadyReported[x] = mi.alreadyReportedBytes;
- x++;
- if(mi.cb != null) callbacksCount += mi.cb.length;
- if(logMINOR) Logger.minor(this, "Sending: "+mi+" length
"+data.length+" cb "+ StringArray.toString(mi.cb));
- length += (data.length + 2);
- }
- }
- if(x != messageData.length) {
- byte[][] newMessageData = new byte[x][];
- System.arraycopy(messageData, 0, newMessageData, 0, x);
- messageData = newMessageData;
- messages = newMsgs;
- newMsgs = new MessageItem[x];
- System.arraycopy(messages, 0, newMsgs, 0, x);
- messages = newMsgs;
- }
- AsyncMessageCallback callbacks[] = new
AsyncMessageCallback[callbacksCount];
- x=0;
- int alreadyReportedBytes = 0;
- for(int i=0;i<messages.length;i++) {
- if(messages[i].formatted) continue;
- if(messages[i].cb != null) {
- alreadyReportedBytes += messages[i].alreadyReportedBytes;
- System.arraycopy(messages[i].cb, 0, callbacks, x,
messages[i].cb.length);
- x += messages[i].cb.length;
- }
- }
- if(x != callbacksCount) throw new IllegalStateException();
-
- if((length + HEADERS_LENGTH_MINIMUM < sock.getMaxPacketSize()) &&
- (messageData.length < 256)) {
+ if(mi.formatted) {
+ try {
+ byte[] buf = mi.getData(pn);
+ kt = pn.getCurrentKeyTracker();
+ if(kt == null) {
+ if(logMINOR) Logger.minor(this,
"kt = null");
+
pn.requeueMessageItems(messages, i, messages.length-i, false, "kt = null");
+ return;
+ }
+ int packetNumber =
kt.allocateOutgoingPacketNumberNeverBlock();
+ this.processOutgoingPreformatted(buf,
0, buf.length, pn.getCurrentKeyTracker(), packetNumber, mi.cb,
mi.alreadyReportedBytes);
+ mi.onSent(buf.length +
fullHeadersLengthOneMessage);
+ } catch (NotConnectedException e) {
+ Logger.normal(this, "Caught "+e+" while
sending messages ("+mi_name+") to "+pn.getPeer()+requeueLogString);
+ // Requeue
+ if(!dontRequeue) {
+
pn.requeueMessageItems(messages, 0, x, false, "NotConnectedException(1a)");
+
pn.requeueMessageItems(messages, i, messages.length-i, false,
"NotConnectedException(1b)");
+ }
+ return;
+ } catch (WouldBlockException e) {
+ if(logMINOR) Logger.minor(this, "Caught
"+e+" while sending messages ("+mi_name+") to "+pn.getPeer()+requeueLogString,
e);
+ // Requeue
+ if(!dontRequeue) {
+
pn.requeueMessageItems(messages, 0, x, false, "WouldBlockException(1a)");
+
pn.requeueMessageItems(messages, i, messages.length-i, false,
"WouldBlockException(1b)");
+ }
+ return;
+ } catch (KeyChangedException e) {
+ if(logMINOR) Logger.minor(this, "Caught
"+e+" while sending messages ("+mi_name+") to "+pn.getPeer()+requeueLogString,
e);
+ // Requeue
+ if(!dontRequeue) {
+
pn.requeueMessageItems(messages, 0, x, false, "KeyChangedException(1a)");
+
pn.requeueMessageItems(messages, i, messages.length-i, false,
"KeyChangedException(1b)");
+ }
+ return;
+ } catch (Throwable e) {
+ Logger.error(this, "Caught "+e+" while
sending messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
+ // Requeue
+ if(!dontRequeue) {
+
pn.requeueMessageItems(messages, 0, x, false, "Throwable(1)");
+
pn.requeueMessageItems(messages, i, messages.length-i, false, "Throwable(1)");
+ }
+ return;
+ }
+ } else {
+ byte[] data = mi.getData(pn);
+ messageData[x] = data;
+ if(data.length > sock.getMaxPacketSize()) {
+ Logger.error(this, "Message exceeds
packet size: "+messages[i]+" size "+data.length+" message "+mi.msg);
+ // Will be handled later
+ }
+ newMsgs[x] = mi;
+ alreadyReported[x] = mi.alreadyReportedBytes;
+ x++;
+ if(mi.cb != null) callbacksCount +=
mi.cb.length;
+ if(logMINOR) Logger.minor(this, "Sending:
"+mi+" length "+data.length+" cb "+ StringArray.toString(mi.cb));
+ length += (data.length + 2);
+ }
+ }
+ if(x != messageData.length) {
+ byte[][] newMessageData = new byte[x][];
+ System.arraycopy(messageData, 0, newMessageData, 0, x);
+ messageData = newMessageData;
+ messages = newMsgs;
+ newMsgs = new MessageItem[x];
+ System.arraycopy(messages, 0, newMsgs, 0, x);
+ messages = newMsgs;
+ }
+ AsyncMessageCallback callbacks[] = new
AsyncMessageCallback[callbacksCount];
+ x=0;
+ int alreadyReportedBytes = 0;
+ for(int i=0;i<messages.length;i++) {
+ if(messages[i].formatted) continue;
+ if(messages[i].cb != null) {
+ alreadyReportedBytes +=
messages[i].alreadyReportedBytes;
+ System.arraycopy(messages[i].cb, 0, callbacks,
x, messages[i].cb.length);
+ x += messages[i].cb.length;
+ }
+ }
+ if(x != callbacksCount) throw new IllegalStateException();
+
+ if((length + HEADERS_LENGTH_MINIMUM < sock.getMaxPacketSize())
&&
+ (messageData.length < 256)) {
mi_name = null;
- try {
- innerProcessOutgoing(messageData, 0, messageData.length,
length, pn, neverWaitForPacketNumber, callbacks, alreadyReportedBytes);
- for(int i=0;i<messageData.length;i++) {
- MessageItem mi = newMsgs[i];
+ try {
+ innerProcessOutgoing(messageData, 0,
messageData.length, length, pn, neverWaitForPacketNumber, callbacks,
alreadyReportedBytes);
+ for(int i=0;i<messageData.length;i++) {
+ MessageItem mi = newMsgs[i];
mi_name = (mi.msg == null ? "(not a
Message)" : mi.msg.getSpec().getName());
mi.onSent(messageData[i].length + 2 +
(fullHeadersLengthMinimum / messageData.length));
- }
- } catch (NotConnectedException e) {
- Logger.normal(this, "Caught "+e+" while sending messages
("+mi_name+") to "+pn.getPeer()+requeueLogString);
- // Requeue
- if(!dontRequeue)
- pn.requeueMessageItems(messages, 0, messages.length,
false, "NotConnectedException(2)");
- return;
- } catch (WouldBlockException e) {
- if(logMINOR) Logger.minor(this, "Caught "+e+" while sending
messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
- // Requeue
- if(!dontRequeue)
- pn.requeueMessageItems(messages, 0, messages.length,
false, "WouldBlockException(2)");
- return;
- } catch (Throwable e) {
- Logger.error(this, "Caught "+e+" while sending messages
("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
- // Requeue
- if(!dontRequeue)
- pn.requeueMessageItems(messages, 0, messages.length,
false, "Throwable(2)");
- return;
-
- }
- } else {
+ }
+ } catch (NotConnectedException e) {
+ Logger.normal(this, "Caught "+e+" while sending
messages ("+mi_name+") to "+pn.getPeer()+requeueLogString);
+ // Requeue
+ if(!dontRequeue)
+ pn.requeueMessageItems(messages, 0,
messages.length, false, "NotConnectedException(2)");
+ return;
+ } catch (WouldBlockException e) {
+ if(logMINOR) Logger.minor(this, "Caught "+e+"
while sending messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
+ // Requeue
+ if(!dontRequeue)
+ pn.requeueMessageItems(messages, 0,
messages.length, false, "WouldBlockException(2)");
+ return;
+ } catch (Throwable e) {
+ Logger.error(this, "Caught "+e+" while sending
messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
+ // Requeue
+ if(!dontRequeue)
+ pn.requeueMessageItems(messages, 0,
messages.length, false, "Throwable(2)");
+ return;
+
+ }
+ } else {
if(!dontRequeue) {
requeueLogString = ", requeueing remaining
messages";
}
- length = 1;
- length += kt.countAcks() + kt.countAckRequests() +
kt.countResendRequests();
- int count = 0;
- int lastIndex = 0;
- alreadyReportedBytes = 0;
- for(int i=0;i<=messageData.length;i++) {
- int thisLength;
- if(i == messages.length) thisLength = 0;
- else thisLength = (messageData[i].length + 2);
- int newLength = length + thisLength;
- count++;
- if((newLength + HEADERS_LENGTH_MINIMUM >
sock.getMaxPacketSize()) || (count > 255) || (i == messages.length)) {
- // lastIndex up to the message right before this one
- // e.g. lastIndex = 0, i = 1, we just send message 0
- if(lastIndex != i) {
+ length = 1;
+ length += kt.countAcks() + kt.countAckRequests() +
kt.countResendRequests();
+ int count = 0;
+ int lastIndex = 0;
+ alreadyReportedBytes = 0;
+ for(int i=0;i<=messageData.length;i++) {
+ int thisLength;
+ if(i == messages.length) thisLength = 0;
+ else thisLength = (messageData[i].length + 2);
+ int newLength = length + thisLength;
+ count++;
+ if((newLength + HEADERS_LENGTH_MINIMUM >
sock.getMaxPacketSize()) || (count > 255) || (i == messages.length)) {
+ // lastIndex up to the message right
before this one
+ // e.g. lastIndex = 0, i = 1, we just
send message 0
+ if(lastIndex != i) {
mi_name = null;
- try {
- innerProcessOutgoing(messageData, lastIndex,
i-lastIndex, length, pn, neverWaitForPacketNumber, callbacks,
alreadyReportedBytes);
- for(int j=lastIndex;j<i;j++) {
- MessageItem mi = newMsgs[j];
+ try {
+
innerProcessOutgoing(messageData, lastIndex, i-lastIndex, length, pn,
neverWaitForPacketNumber, callbacks, alreadyReportedBytes);
+ for(int
j=lastIndex;j<i;j++) {
+ MessageItem mi
= newMsgs[j];
mi_name =
(mi.msg == null ? "(not a Message)" : mi.msg.getSpec().getName());
mi.onSent(messageData[j].length + 2 + (fullHeadersLengthMinimum /
(i-lastIndex)));
- }
- } catch (NotConnectedException e) {
- Logger.normal(this, "Caught "+e+" while sending
messages ("+mi_name+") to "+pn.getPeer()+requeueLogString);
- // Requeue
- if(!dontRequeue)
- pn.requeueMessageItems(messages, lastIndex,
messages.length - lastIndex, false, "NotConnectedException(3)");
- return;
- } catch (WouldBlockException e) {
- if(logMINOR) Logger.minor(this, "Caught "+e+"
while sending messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
- // Requeue
- if(!dontRequeue)
- pn.requeueMessageItems(messages, lastIndex,
messages.length - lastIndex, false, "WouldBlockException(3)");
- return;
- } catch (Throwable e) {
- Logger.error(this, "Caught "+e+" while sending
messages ("+mi_name+") to "+pn.getPeer()+requeueLogString, e);
- // Requeue
- if(!dontRequeue)
- pn.requeueMessageItems(messages, lastIndex,
messages.length - lastIndex, false, "Throwable(3)");
- return;
- }
- }
- lastIndex = i;
- if(i != messageData.length)
- length = 1 + (messageData[i].length + 2);
- count = 0;
- } else {
- length = newLength;
- alreadyReportedBytes += alreadyReported[i];
- }
- }
- }
- }
-
- /**
- * Send some messages.
- * @param messageData An array block of messages.
- * @param start Index to start reading the array.
- * @param length Number of messages to read.
- * @param bufferLength Size of the buffer to write into.
- * @param pn Node to send the messages to.
- * @throws PacketSequenceException
- */
- private void innerProcessOutgoing(byte[][] messageData, int start, int
length, int bufferLength,
- PeerNode pn, boolean neverWaitForPacketNumber,
AsyncMessageCallback[] callbacks, int alreadyReportedBytes) throws
NotConnectedException, WouldBlockException, PacketSequenceException {
- if(logMINOR) Logger.minor(this, "innerProcessOutgoing(...,"+start+ ','
+length+ ',' +bufferLength+ ')');
- byte[] buf = new byte[bufferLength];
- buf[0] = (byte)length;
- int loc = 1;
- for(int i=start;i<(start+length);i++) {
- byte[] data = messageData[i];
- int len = data.length;
- buf[loc++] = (byte)(len >> 8);
- buf[loc++] = (byte)len;
- System.arraycopy(data, 0, buf, loc, len);
- loc += len;
- }
- processOutgoingPreformatted(buf, 0, loc, pn, neverWaitForPacketNumber,
callbacks, alreadyReportedBytes);
- }
+ }
+ } catch (NotConnectedException
e) {
+ Logger.normal(this,
"Caught "+e+" while sending messages ("+mi_name+") to
"+pn.getPeer()+requeueLogString);
+ // Requeue
+ if(!dontRequeue)
+
pn.requeueMessageItems(messages, lastIndex, messages.length - lastIndex, false,
"NotConnectedException(3)");
+ return;
+ } catch (WouldBlockException e)
{
+ if(logMINOR)
Logger.minor(this, "Caught "+e+" while sending messages ("+mi_name+") to
"+pn.getPeer()+requeueLogString, e);
+ // Requeue
+ if(!dontRequeue)
+
pn.requeueMessageItems(messages, lastIndex, messages.length - lastIndex, false,
"WouldBlockException(3)");
+ return;
+ } catch (Throwable e) {
+ Logger.error(this,
"Caught "+e+" while sending messages ("+mi_name+") to
"+pn.getPeer()+requeueLogString, e);
+ // Requeue
+ if(!dontRequeue)
+
pn.requeueMessageItems(messages, lastIndex, messages.length - lastIndex, false,
"Throwable(3)");
+ return;
+ }
+ }
+ lastIndex = i;
+ if(i != messageData.length)
+ length = 1 +
(messageData[i].length + 2);
+ count = 0;
+ } else {
+ length = newLength;
+ alreadyReportedBytes +=
alreadyReported[i];
+ }
+ }
+ }
+ }
- /* (non-Javadoc)
+ /**
+ * Send some messages.
+ * @param messageData An array block of messages.
+ * @param start Index to start reading the array.
+ * @param length Number of messages to read.
+ * @param bufferLength Size of the buffer to write into.
+ * @param pn Node to send the messages to.
+ * @throws PacketSequenceException
+ */
+ private void innerProcessOutgoing(byte[][] messageData, int start, int
length, int bufferLength,
+ PeerNode pn, boolean neverWaitForPacketNumber,
AsyncMessageCallback[] callbacks, int alreadyReportedBytes) throws
NotConnectedException, WouldBlockException, PacketSequenceException {
+ if(logMINOR) Logger.minor(this,
"innerProcessOutgoing(...,"+start+ ',' +length+ ',' +bufferLength+ ')');
+ byte[] buf = new byte[bufferLength];
+ buf[0] = (byte)length;
+ int loc = 1;
+ for(int i=start;i<(start+length);i++) {
+ byte[] data = messageData[i];
+ int len = data.length;
+ buf[loc++] = (byte)(len >> 8);
+ buf[loc++] = (byte)len;
+ System.arraycopy(data, 0, buf, loc, len);
+ loc += len;
+ }
+ processOutgoingPreformatted(buf, 0, loc, pn,
neverWaitForPacketNumber, callbacks, alreadyReportedBytes);
+ }
+
+ /* (non-Javadoc)
* @see freenet.node.OutgoingPacketMangler#processOutgoing(byte[], int,
int, freenet.node.KeyTracker, int)
*/
- public void processOutgoing(byte[] buf, int offset, int length, KeyTracker
tracker, int alreadyReportedBytes) throws KeyChangedException,
NotConnectedException, PacketSequenceException, WouldBlockException {
- byte[] newBuf = preformat(buf, offset, length);
- processOutgoingPreformatted(newBuf, 0, newBuf.length, tracker, -1,
null, alreadyReportedBytes);
- }
-
- /**
- * Send a packet using the current key. Retry if it fails solely because
- * the key changes.
- * @throws PacketSequenceException
- */
- void processOutgoingPreformatted(byte[] buf, int offset, int length,
PeerNode peer, boolean neverWaitForPacketNumber, AsyncMessageCallback[]
callbacks, int alreadyReportedBytes) throws NotConnectedException,
WouldBlockException, PacketSequenceException {
- KeyTracker last = null;
- while(true) {
- try {
- if(!peer.isConnected())
- throw new NotConnectedException();
- KeyTracker tracker = peer.getCurrentKeyTracker();
- last = tracker;
- if(tracker == null) {
- Logger.normal(this, "Dropping packet: Not connected to
"+peer.getPeer()+" yet(2)");
- throw new NotConnectedException();
- }
- int seqNo = neverWaitForPacketNumber ?
tracker.allocateOutgoingPacketNumberNeverBlock() :
- tracker.allocateOutgoingPacketNumber();
- processOutgoingPreformatted(buf, offset, length, tracker,
seqNo, callbacks, alreadyReportedBytes);
- return;
- } catch (KeyChangedException e) {
- Logger.normal(this, "Key changed(2) for "+peer.getPeer());
- if(last == peer.getCurrentKeyTracker()) {
- if(peer.isConnected()) {
- Logger.error(this, "Peer is connected, yet
current tracker is deprecated !!: "+e, e);
- throw new NotConnectedException("Peer is
connected, yet current tracker is deprecated !!: "+e);
- }
- }
- // Go around again
- }
- }
- }
+ public void processOutgoing(byte[] buf, int offset, int length,
KeyTracker tracker, int alreadyReportedBytes) throws KeyChangedException,
NotConnectedException, PacketSequenceException, WouldBlockException {
+ byte[] newBuf = preformat(buf, offset, length);
+ processOutgoingPreformatted(newBuf, 0, newBuf.length, tracker,
-1, null, alreadyReportedBytes);
+ }
- byte[] preformat(byte[] buf, int offset, int length) {
- byte[] newBuf;
- if(buf != null) {
- newBuf = new byte[length+3];
- newBuf[0] = 1;
- newBuf[1] = (byte)(length >> 8);
- newBuf[2] = (byte)length;
- System.arraycopy(buf, offset, newBuf, 3, length);
- } else {
- newBuf = new byte[1];
- newBuf[0] = 0;
- }
- return newBuf;
- }
-
- /* (non-Javadoc)
+ /**
+ * Send a packet using the current key. Retry if it fails solely because
+ * the key changes.
+ * @throws PacketSequenceException
+ */
+ void processOutgoingPreformatted(byte[] buf, int offset, int length,
PeerNode peer, boolean neverWaitForPacketNumber, AsyncMessageCallback[]
callbacks, int alreadyReportedBytes) throws NotConnectedException,
WouldBlockException, PacketSequenceException {
+ KeyTracker last = null;
+ while(true) {
+ try {
+ if(!peer.isConnected())
+ throw new NotConnectedException();
+ KeyTracker tracker =
peer.getCurrentKeyTracker();
+ last = tracker;
+ if(tracker == null) {
+ Logger.normal(this, "Dropping packet:
Not connected to "+peer.getPeer()+" yet(2)");
+ throw new NotConnectedException();
+ }
+ int seqNo = neverWaitForPacketNumber ?
tracker.allocateOutgoingPacketNumberNeverBlock() :
+ tracker.allocateOutgoingPacketNumber();
+ processOutgoingPreformatted(buf, offset,
length, tracker, seqNo, callbacks, alreadyReportedBytes);
+ return;
+ } catch (KeyChangedException e) {
+ Logger.normal(this, "Key changed(2) for
"+peer.getPeer());
+ if(last == peer.getCurrentKeyTracker()) {
+ if(peer.isConnected()) {
+ Logger.error(this, "Peer is
connected, yet current tracker is deprecated !!: "+e, e);
+ throw new
NotConnectedException("Peer is connected, yet current tracker is deprecated !!:
"+e);
+ }
+ }
+ // Go around again
+ }
+ }
+ }
+
+ byte[] preformat(byte[] buf, int offset, int length) {
+ byte[] newBuf;
+ if(buf != null) {
+ newBuf = new byte[length+3];
+ newBuf[0] = 1;
+ newBuf[1] = (byte)(length >> 8);
+ newBuf[2] = (byte)length;
+ System.arraycopy(buf, offset, newBuf, 3, length);
+ } else {
+ newBuf = new byte[1];
+ newBuf[0] = 0;
+ }
+ return newBuf;
+ }
+
+ /* (non-Javadoc)
* @see
freenet.node.OutgoingPacketMangler#processOutgoingPreformatted(byte[], int,
int, freenet.node.KeyTracker, int, freenet.node.AsyncMessageCallback[], int)
*/
- public void processOutgoingPreformatted(byte[] buf, int offset, int
length, KeyTracker tracker, int packetNumber, AsyncMessageCallback[] callbacks,
int alreadyReportedBytes) throws KeyChangedException, NotConnectedException,
PacketSequenceException, WouldBlockException {
- if(logMINOR) {
- String log =
"processOutgoingPreformatted("+Fields.hashCode(buf)+", "+offset+ ',' +length+
',' +tracker+ ',' +packetNumber+ ',';
- if(callbacks == null) log += "null";
- else log += (""+callbacks.length+(callbacks.length >= 1 ?
String.valueOf(callbacks[0]) : ""));
- Logger.minor(this, log);
- }
- if((tracker == null) || (!tracker.pn.isConnected())) {
- throw new NotConnectedException();
- }
-
- // We do not support forgotten packets at present
+ public void processOutgoingPreformatted(byte[] buf, int offset, int
length, KeyTracker tracker, int packetNumber, AsyncMessageCallback[] callbacks,
int alreadyReportedBytes) throws KeyChangedException, NotConnectedException,
PacketSequenceException, WouldBlockException {
+ if(logMINOR) {
+ String log =
"processOutgoingPreformatted("+Fields.hashCode(buf)+", "+offset+ ',' +length+
',' +tracker+ ',' +packetNumber+ ',';
+ if(callbacks == null) log += "null";
+ else log += (""+callbacks.length+(callbacks.length >= 1
? String.valueOf(callbacks[0]) : ""));
+ Logger.minor(this, log);
+ }
+ if((tracker == null) || (!tracker.pn.isConnected())) {
+ throw new NotConnectedException();
+ }
- int[] acks, resendRequests, ackRequests, forgotPackets;
- int seqNumber;
- /* Locking:
- * Avoid allocating a packet number, then a long pause due to
- * overload, during which many other packets are sent,
- * resulting in the other side asking us to resend a packet
- * which doesn't exist yet.
- * => grabbing resend reqs, packet no etc must be as
- * close together as possible.
- *
- * HOWEVER, tracker.allocateOutgoingPacketNumber can block,
- * so should not be locked.
- */
-
- if(packetNumber > 0)
- seqNumber = packetNumber;
- else {
- if(buf.length == 1)
- // Ack/resendreq only packet
- seqNumber = -1;
- else
- seqNumber =
tracker.allocateOutgoingPacketNumberNeverBlock();
- }
-
- if(logMINOR) Logger.minor(this, "Sequence number (sending):
"+seqNumber+" ("+packetNumber+") to "+tracker.pn.getPeer());
-
- /** The last sent sequence number, so that we can refer to packets
- * sent after this packet was originally sent (it may be a resend) */
- int realSeqNumber;
-
- int otherSideSeqNumber;
-
- synchronized(tracker) {
- acks = tracker.grabAcks();
- forgotPackets = tracker.grabForgotten();
- resendRequests = tracker.grabResendRequests();
- ackRequests = tracker.grabAckRequests();
- realSeqNumber = tracker.getLastOutgoingSeqNumber();
- otherSideSeqNumber = tracker.highestReceivedIncomingSeqNumber();
- if(logMINOR) Logger.minor(this, "Sending packet to
"+tracker.pn.getPeer()+", other side max seqno: "+otherSideSeqNumber);
- }
-
- int packetLength = 4 + // seq number
- RANDOM_BYTES_LENGTH + // random junk
- 1 + // version
- ((packetNumber == -1) ? 4 : 1) + // highest sent seqno - 4
bytes if seqno = -1
- 4 + // other side's seqno
- 1 + // number of acks
- acks.length + // acks
- 1 + // number of resend reqs
- resendRequests.length + // resend requests
- 1 + // number of ack requests
- ackRequests.length + // ack requests
- 1 + // no forgotten packets
- length; // the payload !
-
- // Padding
- // This will do an adequate job of disguising the contents, and a poor
(but not totally
- // worthless) job of disguising the traffic. FIXME!!!!!
- // Ideally we'd mimic the size profile - and the session bytes! - of a
common protocol.
-
- int paddedLen = ((packetLength + 63) / 64) * 64;
- paddedLen += node.fastWeakRandom.nextInt(64);
- if(packetLength <= 1280 && paddedLen > 1280) paddedLen = 1280;
+ // We do not support forgotten packets at present
- byte[] padding = new byte[paddedLen - packetLength];
- node.fastWeakRandom.nextBytes(padding);
-
- packetLength = paddedLen;
-
- if(logMINOR) Logger.minor(this, "Packet length: "+packetLength+"
("+length+")");
+ int[] acks, resendRequests, ackRequests, forgotPackets;
+ int seqNumber;
+ /* Locking:
+ * Avoid allocating a packet number, then a long pause due to
+ * overload, during which many other packets are sent,
+ * resulting in the other side asking us to resend a packet
+ * which doesn't exist yet.
+ * => grabbing resend reqs, packet no etc must be as
+ * close together as possible.
+ *
+ * HOWEVER, tracker.allocateOutgoingPacketNumber can block,
+ * so should not be locked.
+ */
- byte[] plaintext = new byte[packetLength];
-
- byte[] randomJunk = new byte[RANDOM_BYTES_LENGTH];
-
- int ptr = offset;
+ if(packetNumber > 0)
+ seqNumber = packetNumber;
+ else {
+ if(buf.length == 1)
+ // Ack/resendreq only packet
+ seqNumber = -1;
+ else
+ seqNumber =
tracker.allocateOutgoingPacketNumberNeverBlock();
+ }
- plaintext[ptr++] = (byte)(seqNumber >> 24);
- plaintext[ptr++] = (byte)(seqNumber >> 16);
- plaintext[ptr++] = (byte)(seqNumber >> 8);
- plaintext[ptr++] = (byte)seqNumber;
-
- if(logMINOR) Logger.minor(this, "Getting random junk");
- node.random.nextBytes(randomJunk);
- System.arraycopy(randomJunk, 0, plaintext, ptr, RANDOM_BYTES_LENGTH);
- ptr += RANDOM_BYTES_LENGTH;
-
- plaintext[ptr++] = 0; // version number
+ if(logMINOR) Logger.minor(this, "Sequence number (sending):
"+seqNumber+" ("+packetNumber+") to "+tracker.pn.getPeer());
- if(seqNumber == -1) {
- plaintext[ptr++] = (byte)(realSeqNumber >> 24);
- plaintext[ptr++] = (byte)(realSeqNumber >> 16);
- plaintext[ptr++] = (byte)(realSeqNumber >> 8);
- plaintext[ptr++] = (byte)realSeqNumber;
- } else {
- plaintext[ptr++] = (byte)(realSeqNumber - seqNumber);
- }
-
- plaintext[ptr++] = (byte)(otherSideSeqNumber >> 24);
- plaintext[ptr++] = (byte)(otherSideSeqNumber >> 16);
- plaintext[ptr++] = (byte)(otherSideSeqNumber >> 8);
- plaintext[ptr++] = (byte)otherSideSeqNumber;
-
- plaintext[ptr++] = (byte) acks.length;
- for(int i=0;i<acks.length;i++) {
- int ackSeq = acks[i];
- if(logMINOR) Logger.minor(this, "Acking "+ackSeq);
- int offsetSeq = otherSideSeqNumber - ackSeq;
- if((offsetSeq > 255) || (offsetSeq < 0))
- throw new PacketSequenceException("bad ack offset "+offsetSeq+
- " - seqNumber="+otherSideSeqNumber+",
ackNumber="+ackSeq+" talking to "+tracker.pn.getPeer());
- plaintext[ptr++] = (byte)offsetSeq;
- }
-
- plaintext[ptr++] = (byte) resendRequests.length;
- for(int i=0;i<resendRequests.length;i++) {
- int reqSeq = resendRequests[i];
- if(logMINOR) Logger.minor(this, "Resend req: "+reqSeq);
- int offsetSeq = otherSideSeqNumber - reqSeq;
- if((offsetSeq > 255) || (offsetSeq < 0))
- throw new PacketSequenceException("bad resend request offset
"+offsetSeq+
- " - reqSeq="+reqSeq+",
otherSideSeqNumber="+otherSideSeqNumber+" talking to "+tracker.pn.getPeer());
- plaintext[ptr++] = (byte)offsetSeq;
- }
+ /** The last sent sequence number, so that we can refer to
packets
+ * sent after this packet was originally sent (it may be a
resend) */
+ int realSeqNumber;
- plaintext[ptr++] = (byte) ackRequests.length;
- if(logMINOR) Logger.minor(this, "Ackrequests: "+ackRequests.length);
- for(int i=0;i<ackRequests.length;i++) {
- int ackReqSeq = ackRequests[i];
- if(logMINOR) Logger.minor(this, "Ack request "+i+": "+ackReqSeq);
- // Relative to packetNumber - we are asking them to ack
- // a packet we sent to them.
- int offsetSeq = realSeqNumber - ackReqSeq;
- if((offsetSeq > 255) || (offsetSeq < 0))
- throw new PacketSequenceException("bad ack requests offset:
"+offsetSeq+
- " - ackReqSeq="+ackReqSeq+",
packetNumber="+realSeqNumber+" talking to "+tracker.pn.getPeer());
- plaintext[ptr++] = (byte)offsetSeq;
- }
-
- byte[] forgotOffsets = null;
- int forgotCount = 0;
-
- if(forgotPackets.length > 0) {
- for(int i=0;i<forgotPackets.length;i++) {
- int seq = forgotPackets[i];
- if(logMINOR) Logger.minor(this, "Forgot packet "+i+":
"+seq);
- int offsetSeq = realSeqNumber - seq;
- if((offsetSeq > 255) || (offsetSeq < 0)) {
- if(tracker.isDeprecated()) {
- // Oh well
- Logger.error(this, "Dropping
forgot-packet notification on deprecated tracker: "+seq+" on "+tracker+" - real
seq="+realSeqNumber);
- // Ignore it
- continue;
- } else {
- Logger.error(this, "bad forgot packet
offset: "+offsetSeq+
- " - forgotSeq="+seq+",
packetNumber="+realSeqNumber+" talking to "+tracker.pn.getPeer(), new
Exception("error"));
- }
- } else {
- if(forgotOffsets == null)
- forgotOffsets = new
byte[forgotPackets.length - i];
- forgotOffsets[i] = (byte) offsetSeq;
- forgotCount++;
- if(forgotCount == 256)
- tracker.requeueForgot(forgotPackets,
forgotCount, forgotPackets.length - forgotCount);
- }
- }
- }
-
- plaintext[ptr++] = (byte) forgotCount;
-
- if(forgotOffsets != null) {
- System.arraycopy(forgotOffsets, 0, plaintext, ptr, forgotCount);
- ptr += forgotCount;
- }
-
- System.arraycopy(buf, offset, plaintext, ptr, length);
- ptr += length;
-
- System.arraycopy(padding, 0, plaintext, ptr, padding.length);
- ptr += padding.length;
-
- if(ptr != plaintext.length) {
- Logger.error(this, "Inconsistent length: "+plaintext.length+"
buffer but "+(ptr)+" actual");
- byte[] newBuf = new byte[ptr];
- System.arraycopy(plaintext, 0, newBuf, 0, ptr);
- plaintext = newBuf;
- }
+ int otherSideSeqNumber;
- if(seqNumber != -1) {
- byte[] saveable = new byte[length];
- System.arraycopy(buf, offset, saveable, 0, length);
- tracker.sentPacket(saveable, seqNumber, callbacks);
- }
-
- if(logMINOR) Logger.minor(this, "Sending... "+seqNumber);
+ synchronized(tracker) {
+ acks = tracker.grabAcks();
+ forgotPackets = tracker.grabForgotten();
+ resendRequests = tracker.grabResendRequests();
+ ackRequests = tracker.grabAckRequests();
+ realSeqNumber = tracker.getLastOutgoingSeqNumber();
+ otherSideSeqNumber =
tracker.highestReceivedIncomingSeqNumber();
+ if(logMINOR) Logger.minor(this, "Sending packet to
"+tracker.pn.getPeer()+", other side max seqno: "+otherSideSeqNumber);
+ }
- processOutgoingFullyFormatted(plaintext, tracker,
alreadyReportedBytes);
- if(logMINOR) Logger.minor(this, "Sent packet "+seqNumber);
- }
+ int packetLength = 4 + // seq number
+ RANDOM_BYTES_LENGTH + // random junk
+ 1 + // version
+ ((packetNumber == -1) ? 4 : 1) + // highest sent seqno - 4
bytes if seqno = -1
+ 4 + // other side's seqno
+ 1 + // number of acks
+ acks.length + // acks
+ 1 + // number of resend reqs
+ resendRequests.length + // resend requests
+ 1 + // number of ack requests
+ ackRequests.length + // ack requests
+ 1 + // no forgotten packets
+ length; // the payload !
- /**
- * Encrypt and send a packet.
- * @param plaintext The packet's plaintext, including all formatting,
- * including acks and resend requests. Is clobbered.
- */
- private void processOutgoingFullyFormatted(byte[] plaintext, KeyTracker
kt, int alreadyReportedBytes) {
- BlockCipher sessionCipher = kt.sessionCipher;
- if(logMINOR) Logger.minor(this, "Encrypting with
"+HexUtil.bytesToHex(kt.sessionKey));
- if(sessionCipher == null) {
- Logger.error(this, "Dropping packet send - have not handshaked
yet");
- return;
- }
- int blockSize = sessionCipher.getBlockSize() >> 3;
- if(sessionCipher.getKeySize() != sessionCipher.getBlockSize())
- throw new IllegalStateException("Block size must be half key size:
blockSize="+
- sessionCipher.getBlockSize()+",
keySize="+sessionCipher.getKeySize());
-
- MessageDigest md = SHA256.getMessageDigest();
-
- int digestLength = md.getDigestLength();
-
- if(digestLength != blockSize)
- throw new IllegalStateException("Block size must be digest
length!");
-
- byte[] output = new byte[plaintext.length + digestLength];
- System.arraycopy(plaintext, 0, output, digestLength, plaintext.length);
-
- md.update(plaintext);
-
- //Logger.minor(this, "Plaintext:\n"+HexUtil.bytesToHex(plaintext));
-
- byte[] digestTemp;
-
- digestTemp = md.digest();
-
- SHA256.returnMessageDigest(md); md = null;
+ // Padding
+ // This will do an adequate job of disguising the contents, and
a poor (but not totally
+ // worthless) job of disguising the traffic. FIXME!!!!!
+ // Ideally we'd mimic the size profile - and the session bytes!
- of a common protocol.
- if(logMINOR) Logger.minor(this, "\nHash:
"+HexUtil.bytesToHex(digestTemp));
-
- // Put encrypted digest in output
- sessionCipher.encipher(digestTemp, digestTemp);
-
- // Now copy it back
- System.arraycopy(digestTemp, 0, output, 0, digestLength);
- // Yay, we have an encrypted hash
+ int paddedLen = ((packetLength + 63) / 64) * 64;
+ paddedLen += node.fastWeakRandom.nextInt(64);
+ if(packetLength <= 1280 && paddedLen > 1280) paddedLen = 1280;
- if(logMINOR) Logger.minor(this, "\nEncrypted:
"+HexUtil.bytesToHex(digestTemp)+" ("+plaintext.length+" bytes plaintext)");
-
- PCFBMode pcfb = PCFBMode.create(sessionCipher, digestTemp);
- pcfb.blockEncipher(output, digestLength, plaintext.length);
-
- //Logger.minor(this, "Ciphertext:\n"+HexUtil.bytesToHex(output,
digestLength, plaintext.length));
-
- // We have a packet
- // Send it
-
- if(logMINOR) Logger.minor(this,"Sending packet of length
"+output.length+" (" + Fields.hashCode(output) + " to "+kt.pn);
-
- // pn.getPeer() cannot be null
- try {
- sendPacket(output, kt.pn.getPeer(), kt.pn,
alreadyReportedBytes);
+ byte[] padding = new byte[paddedLen - packetLength];
+ node.fastWeakRandom.nextBytes(padding);
+
+ packetLength = paddedLen;
+
+ if(logMINOR) Logger.minor(this, "Packet length:
"+packetLength+" ("+length+")");
+
+ byte[] plaintext = new byte[packetLength];
+
+ byte[] randomJunk = new byte[RANDOM_BYTES_LENGTH];
+
+ int ptr = offset;
+
+ plaintext[ptr++] = (byte)(seqNumber >> 24);
+ plaintext[ptr++] = (byte)(seqNumber >> 16);
+ plaintext[ptr++] = (byte)(seqNumber >> 8);
+ plaintext[ptr++] = (byte)seqNumber;
+
+ if(logMINOR) Logger.minor(this, "Getting random junk");
+ node.random.nextBytes(randomJunk);
+ System.arraycopy(randomJunk, 0, plaintext, ptr,
RANDOM_BYTES_LENGTH);
+ ptr += RANDOM_BYTES_LENGTH;
+
+ plaintext[ptr++] = 0; // version number
+
+ if(seqNumber == -1) {
+ plaintext[ptr++] = (byte)(realSeqNumber >> 24);
+ plaintext[ptr++] = (byte)(realSeqNumber >> 16);
+ plaintext[ptr++] = (byte)(realSeqNumber >> 8);
+ plaintext[ptr++] = (byte)realSeqNumber;
+ } else {
+ plaintext[ptr++] = (byte)(realSeqNumber - seqNumber);
+ }
+
+ plaintext[ptr++] = (byte)(otherSideSeqNumber >> 24);
+ plaintext[ptr++] = (byte)(otherSideSeqNumber >> 16);
+ plaintext[ptr++] = (byte)(otherSideSeqNumber >> 8);
+ plaintext[ptr++] = (byte)otherSideSeqNumber;
+
+ plaintext[ptr++] = (byte) acks.length;
+ for(int i=0;i<acks.length;i++) {
+ int ackSeq = acks[i];
+ if(logMINOR) Logger.minor(this, "Acking "+ackSeq);
+ int offsetSeq = otherSideSeqNumber - ackSeq;
+ if((offsetSeq > 255) || (offsetSeq < 0))
+ throw new PacketSequenceException("bad ack
offset "+offsetSeq+
+ " -
seqNumber="+otherSideSeqNumber+", ackNumber="+ackSeq+" talking to
"+tracker.pn.getPeer());
+ plaintext[ptr++] = (byte)offsetSeq;
+ }
+
+ plaintext[ptr++] = (byte) resendRequests.length;
+ for(int i=0;i<resendRequests.length;i++) {
+ int reqSeq = resendRequests[i];
+ if(logMINOR) Logger.minor(this, "Resend req: "+reqSeq);
+ int offsetSeq = otherSideSeqNumber - reqSeq;
+ if((offsetSeq > 255) || (offsetSeq < 0))
+ throw new PacketSequenceException("bad resend
request offset "+offsetSeq+
+ " - reqSeq="+reqSeq+",
otherSideSeqNumber="+otherSideSeqNumber+" talking to "+tracker.pn.getPeer());
+ plaintext[ptr++] = (byte)offsetSeq;
+ }
+
+ plaintext[ptr++] = (byte) ackRequests.length;
+ if(logMINOR) Logger.minor(this, "Ackrequests:
"+ackRequests.length);
+ for(int i=0;i<ackRequests.length;i++) {
+ int ackReqSeq = ackRequests[i];
+ if(logMINOR) Logger.minor(this, "Ack request "+i+":
"+ackReqSeq);
+ // Relative to packetNumber - we are asking them to ack
+ // a packet we sent to them.
+ int offsetSeq = realSeqNumber - ackReqSeq;
+ if((offsetSeq > 255) || (offsetSeq < 0))
+ throw new PacketSequenceException("bad ack
requests offset: "+offsetSeq+
+ " - ackReqSeq="+ackReqSeq+",
packetNumber="+realSeqNumber+" talking to "+tracker.pn.getPeer());
+ plaintext[ptr++] = (byte)offsetSeq;
+ }
+
+ byte[] forgotOffsets = null;
+ int forgotCount = 0;
+
+ if(forgotPackets.length > 0) {
+ for(int i=0;i<forgotPackets.length;i++) {
+ int seq = forgotPackets[i];
+ if(logMINOR) Logger.minor(this, "Forgot packet
"+i+": "+seq);
+ int offsetSeq = realSeqNumber - seq;
+ if((offsetSeq > 255) || (offsetSeq < 0)) {
+ if(tracker.isDeprecated()) {
+ // Oh well
+ Logger.error(this, "Dropping
forgot-packet notification on deprecated tracker: "+seq+" on "+tracker+" - real
seq="+realSeqNumber);
+ // Ignore it
+ continue;
+ } else {
+ Logger.error(this, "bad forgot
packet offset: "+offsetSeq+
+ " -
forgotSeq="+seq+", packetNumber="+realSeqNumber+" talking to
"+tracker.pn.getPeer(), new Exception("error"));
+ }
+ } else {
+ if(forgotOffsets == null)
+ forgotOffsets = new
byte[forgotPackets.length - i];
+ forgotOffsets[i] = (byte) offsetSeq;
+ forgotCount++;
+ if(forgotCount == 256)
+
tracker.requeueForgot(forgotPackets, forgotCount, forgotPackets.length -
forgotCount);
+ }
+ }
+ }
+
+ plaintext[ptr++] = (byte) forgotCount;
+
+ if(forgotOffsets != null) {
+ System.arraycopy(forgotOffsets, 0, plaintext, ptr,
forgotCount);
+ ptr += forgotCount;
+ }
+
+ System.arraycopy(buf, offset, plaintext, ptr, length);
+ ptr += length;
+
+ System.arraycopy(padding, 0, plaintext, ptr, padding.length);
+ ptr += padding.length;
+
+ if(ptr != plaintext.length) {
+ Logger.error(this, "Inconsistent length:
"+plaintext.length+" buffer but "+(ptr)+" actual");
+ byte[] newBuf = new byte[ptr];
+ System.arraycopy(plaintext, 0, newBuf, 0, ptr);
+ plaintext = newBuf;
+ }
+
+ if(seqNumber != -1) {
+ byte[] saveable = new byte[length];
+ System.arraycopy(buf, offset, saveable, 0, length);
+ tracker.sentPacket(saveable, seqNumber, callbacks);
+ }
+
+ if(logMINOR) Logger.minor(this, "Sending... "+seqNumber);
+
+ processOutgoingFullyFormatted(plaintext, tracker,
alreadyReportedBytes);
+ if(logMINOR) Logger.minor(this, "Sent packet "+seqNumber);
+ }
+
+ /**
+ * Encrypt and send a packet.
+ * @param plaintext The packet's plaintext, including all formatting,
+ * including acks and resend requests. Is clobbered.
+ */
+ private void processOutgoingFullyFormatted(byte[] plaintext, KeyTracker
kt, int alreadyReportedBytes) {
+ BlockCipher sessionCipher = kt.sessionCipher;
+ if(logMINOR) Logger.minor(this, "Encrypting with
"+HexUtil.bytesToHex(kt.sessionKey));
+ if(sessionCipher == null) {
+ Logger.error(this, "Dropping packet send - have not
handshaked yet");
+ return;
+ }
+ int blockSize = sessionCipher.getBlockSize() >> 3;
+ if(sessionCipher.getKeySize() != sessionCipher.getBlockSize())
+ throw new IllegalStateException("Block size must be
half key size: blockSize="+
+ sessionCipher.getBlockSize()+",
keySize="+sessionCipher.getKeySize());
+
+ MessageDigest md = SHA256.getMessageDigest();
+
+ int digestLength = md.getDigestLength();
+
+ if(digestLength != blockSize)
+ throw new IllegalStateException("Block size must be
digest length!");
+
+ byte[] output = new byte[plaintext.length + digestLength];
+ System.arraycopy(plaintext, 0, output, digestLength,
plaintext.length);
+
+ md.update(plaintext);
+
+ //Logger.minor(this,
"Plaintext:\n"+HexUtil.bytesToHex(plaintext));
+
+ byte[] digestTemp;
+
+ digestTemp = md.digest();
+
+ SHA256.returnMessageDigest(md); md = null;
+
+ if(logMINOR) Logger.minor(this, "\nHash:
"+HexUtil.bytesToHex(digestTemp));
+
+ // Put encrypted digest in output
+ sessionCipher.encipher(digestTemp, digestTemp);
+
+ // Now copy it back
+ System.arraycopy(digestTemp, 0, output, 0, digestLength);
+ // Yay, we have an encrypted hash
+
+ if(logMINOR) Logger.minor(this, "\nEncrypted:
"+HexUtil.bytesToHex(digestTemp)+" ("+plaintext.length+" bytes plaintext)");
+
+ PCFBMode pcfb = PCFBMode.create(sessionCipher, digestTemp);
+ pcfb.blockEncipher(output, digestLength, plaintext.length);
+
+ //Logger.minor(this, "Ciphertext:\n"+HexUtil.bytesToHex(output,
digestLength, plaintext.length));
+
+ // We have a packet
+ // Send it
+
+ if(logMINOR) Logger.minor(this,"Sending packet of length
"+output.length+" (" + Fields.hashCode(output) + " to "+kt.pn);
+
+ // pn.getPeer() cannot be null
+ try {
+ sendPacket(output, kt.pn.getPeer(), kt.pn,
alreadyReportedBytes);
} catch (LocalAddressException e) {
Logger.error(this, "Tried to send data packet to local
address: "+kt.pn.getPeer()+" for "+kt.pn.allowLocalAddresses());
}
- kt.pn.sentPacket();
- }
+ kt.pn.sentPacket();
+ }
- /* (non-Javadoc)
+ /* (non-Javadoc)
* @see
freenet.node.OutgoingPacketMangler#sendHandshake(freenet.node.PeerNode)
*/
- public void sendHandshake(PeerNode pn) {
- int negType = pn.bestNegType(this);
- if(negType == -1) {
- if(pn.isRoutingCompatible())
- Logger.error(this, "Could not negotiate with "+pn+" :
no common negTypes available!: his negTypes:
"+StringArray.toString(pn.negTypes)+" my negTypes:
"+StringArray.toString(supportedNegTypes())+" despite being up to date!!");
- else
- Logger.minor(this, "Could not negotiate with "+pn+" :
no common negTypes available!: his negTypes:
"+StringArray.toString(pn.negTypes)+" my negTypes:
"+StringArray.toString(supportedNegTypes())+" (probably just too old)");
- return;
- }
- if(logMINOR) Logger.minor(this, "Possibly sending handshake to "+pn+"
negotiation type "+negType);
- DiffieHellmanContext ctx;
- Peer[] handshakeIPs;
- if(!pn.shouldSendHandshake()) {
- if(logMINOR) Logger.minor(this, "Not sending handshake to
"+pn.getPeer()+" because pn.shouldSendHandshake() returned false");
- return;
- }
- long firstTime = System.currentTimeMillis();
- handshakeIPs = pn.getHandshakeIPs();
- long secondTime = System.currentTimeMillis();
- if((secondTime - firstTime) > 1000)
- Logger.error(this, "getHandshakeIPs() took more than a second to
execute ("+(secondTime - firstTime)+") working on "+pn.userToString());
- if(handshakeIPs.length == 0) {
- pn.couldNotSendHandshake();
- long thirdTime = System.currentTimeMillis();
- if((thirdTime - secondTime) > 1000)
- Logger.error(this, "couldNotSendHandshake() (after
getHandshakeIPs()) took more than a second to execute ("+(thirdTime -
secondTime)+") working on "+pn.userToString());
- return;
- } else {
- long DHTime1 = System.currentTimeMillis();
- ctx = DiffieHellman.generateContext();
- long DHTime2 = System.currentTimeMillis();
- if((DHTime2 - DHTime1) > 1000)
- Logger.error(this, "DHTime2 is more than a second after
DHTime1 ("+(DHTime2 - DHTime1)+") working on "+pn.userToString());
- pn.setKeyAgreementSchemeContext(ctx);
- long DHTime3 = System.currentTimeMillis();
- if((DHTime3 - DHTime2) > 1000)
- Logger.error(this, "DHTime3 is more than a second after
DHTime2 ("+(DHTime3 - DHTime2)+") working on "+pn.userToString());
- }
- int sentCount = 0;
- long loopTime1 = System.currentTimeMillis();
- for(int i=0;i<handshakeIPs.length;i++){
- Peer peer = handshakeIPs[i];
- FreenetInetAddress addr = peer.getFreenetAddress();
- if(!crypto.allowConnection(pn, addr)) {
- if(logMINOR)
- Logger.minor(this, "Not sending handshake
packet to "+peer+" for "+pn);
- }
- if(peer.getAddress(false) == null) {
- if(logMINOR) Logger.minor(this, "Not sending handshake
to "+handshakeIPs[i]+" for "+pn.getPeer()+" because the DNS lookup failed or
it's a currently unsupported IPv6 address");
- continue;
- }
- if((!pn.allowLocalAddresses()) &&
(!peer.isRealInternetAddress(false, false))) {
- if(logMINOR) Logger.minor(this, "Not sending handshake
to "+handshakeIPs[i]+" for "+pn.getPeer()+" because it's not a real Internet
address and metadata.allowLocalAddresses is not true");
- continue;
- }
- if(logMINOR)
- Logger.minor(this, "Sending handshake to "+peer+" for
"+pn+" ("+i+" of "+handshakeIPs.length);
- sendFirstHalfDHPacket(0, negType, ctx.getOurExponential(), pn,
peer);
- pn.sentHandshake();
- sentCount += 1;
- }
- long loopTime2 = System.currentTimeMillis();
- if((loopTime2 - loopTime1) > 1000)
- Logger.normal(this, "loopTime2 is more than a second after
loopTime1 ("+(loopTime2 - loopTime1)+") working on "+pn.userToString());
- if(sentCount==0) {
- pn.couldNotSendHandshake();
- }
- }
+ public void sendHandshake(PeerNode pn) {
+ int negType = pn.bestNegType(this);
+ if(negType == -1) {
+ if(pn.isRoutingCompatible())
+ Logger.error(this, "Could not negotiate with
"+pn+" : no common negTypes available!: his negTypes:
"+StringArray.toString(pn.negTypes)+" my negTypes:
"+StringArray.toString(supportedNegTypes())+" despite being up to date!!");
+ else
+ Logger.minor(this, "Could not negotiate with
"+pn+" : no common negTypes available!: his negTypes:
"+StringArray.toString(pn.negTypes)+" my negTypes:
"+StringArray.toString(supportedNegTypes())+" (probably just too old)");
+ return;
+ }
+ if(logMINOR) Logger.minor(this, "Possibly sending handshake to
"+pn+" negotiation type "+negType);
+ DiffieHellmanContext ctx = null;
+ Peer[] handshakeIPs;
+ if(!pn.shouldSendHandshake()) {
+ if(logMINOR) Logger.minor(this, "Not sending handshake
to "+pn.getPeer()+" because pn.shouldSendHandshake() returned false");
+ return;
+ }
+ long firstTime = System.currentTimeMillis();
+ handshakeIPs = pn.getHandshakeIPs();
+ long secondTime = System.currentTimeMillis();
+ if((secondTime - firstTime) > 1000)
+ Logger.error(this, "getHandshakeIPs() took more than a
second to execute ("+(secondTime - firstTime)+") working on
"+pn.userToString());
+ if(handshakeIPs.length == 0) {
+ pn.couldNotSendHandshake();
+ long thirdTime = System.currentTimeMillis();
+ if((thirdTime - secondTime) > 1000)
+ Logger.error(this, "couldNotSendHandshake()
(after getHandshakeIPs()) took more than a second to execute ("+(thirdTime -
secondTime)+") working on "+pn.userToString());
+ return;
+ } else if(negType < 2){
+ long DHTime1 = System.currentTimeMillis();
+ ctx = DiffieHellman.generateContext();
+ long DHTime2 = System.currentTimeMillis();
+ if((DHTime2 - DHTime1) > 1000)
+ Logger.error(this, "DHTime2 is more than a
second after DHTime1 ("+(DHTime2 - DHTime1)+") working on "+pn.userToString());
+ pn.setKeyAgreementSchemeContext(ctx);
+ long DHTime3 = System.currentTimeMillis();
+ if((DHTime3 - DHTime2) > 1000)
+ Logger.error(this, "DHTime3 is more than a
second after DHTime2 ("+(DHTime3 - DHTime2)+") working on "+pn.userToString());
+ }
+ int sentCount = 0;
+ long loopTime1 = System.currentTimeMillis();
+ for(int i=0;i<handshakeIPs.length;i++){
+ Peer peer = handshakeIPs[i];
+ FreenetInetAddress addr = peer.getFreenetAddress();
+ if(!crypto.allowConnection(pn, addr)) {
+ if(logMINOR)
+ Logger.minor(this, "Not sending
handshake packet to "+peer+" for "+pn);
+ }
+ if(peer.getAddress(false) == null) {
+ if(logMINOR) Logger.minor(this, "Not sending
handshake to "+handshakeIPs[i]+" for "+pn.getPeer()+" because the DNS lookup
failed or it's a currently unsupported IPv6 address");
+ continue;
+ }
+ if((!pn.allowLocalAddresses()) &&
(!peer.isRealInternetAddress(false, false))) {
+ if(logMINOR) Logger.minor(this, "Not sending
handshake to "+handshakeIPs[i]+" for "+pn.getPeer()+" because it's not a real
Internet address and metadata.allowLocalAddresses is not true");
+ continue;
+ }
+ if(negType == 1)
+ sendFirstHalfDHPacket(0, negType,
ctx.getOurExponential(), pn, peer);
+ else
+ sendJFKMessage1(pn, peer);
+ if(logMINOR)
+ Logger.minor(this, "Sending handshake to
"+peer+" for "+pn+" ("+i+" of "+handshakeIPs.length);
+ pn.sentHandshake();
+ sentCount += 1;
+ }
+ long loopTime2 = System.currentTimeMillis();
+ if((loopTime2 - loopTime1) > 1000)
+ Logger.normal(this, "loopTime2 is more than a second
after loopTime1 ("+(loopTime2 - loopTime1)+") working on "+pn.userToString());
+ if(sentCount==0) {
+ pn.couldNotSendHandshake();
+ }
+ }
- /* (non-Javadoc)
+ /* (non-Javadoc)
* @see
freenet.node.OutgoingPacketMangler#isDisconnected(freenet.io.comm.PeerContext)
*/
- public boolean isDisconnected(PeerContext context) {
- if(context == null) return false;
- return !((PeerNode)context).isConnected();
- }
+ public boolean isDisconnected(PeerContext context) {
+ if(context == null) return false;
+ return !((PeerNode)context).isConnected();
+ }
public void resend(ResendPacketItem item) throws
PacketSequenceException, WouldBlockException, KeyChangedException,
NotConnectedException {
processOutgoingPreformatted(item.buf, 0, item.buf.length,
item.kt, item.packetNumber, item.callbacks, 0);
}
public int[] supportedNegTypes() {
- return new int[] { 1 };
+ return new int[] { 2 };
}
public int fullHeadersLengthOneMessage() {
@@ -1611,4 +2423,109 @@
public boolean alwaysAllowLocalAddresses() {
return crypto.config.alwaysAllowLocalAddresses();
}
+
+ private DiffieHellmanLightContext getLightDiffieHellmanContext(PeerNode
pn) {
+ final long now = System.currentTimeMillis();
+
+ synchronized (this) {
+ if((currentDHContext == null) ||
(currentDHContextLifetime + 1800000 /*30mins*/) < now) {
+ currentDHContextLifetime = now;
+ currentDHContext =
DiffieHellman.generateLightContext();
+
currentDHContext.setSignature(signDHParams(currentDHContext.myExponential,
pn.peerCryptoGroup));
+ }
+ }
+ return currentDHContext;
+ }
+
+ /*
+ * Prepare DH parameters of message2 for them to be signed (useful in
message3 to check the sig)
+ */
+ private byte[] assembleDHParams(BigInteger exponential, DSAGroup group)
{
+ byte[] _myExponential =
stripBigIntegerToNetworkFormat(exponential);
+ byte[] _myGroup = group.getP().toByteArray();
+ byte[] toSign = new byte[_myExponential.length +
_myGroup.length];
+
+ System.arraycopy(_myExponential, 0, toSign, 0,
_myExponential.length);
+ System.arraycopy(_myGroup, 0, toSign, _myExponential.length,
_myGroup.length);
+
+ return toSign;
+ }
+
+ private byte[] assembleDHParams(byte[] nonceInitiator,byte[]
nonceResponder,BigInteger initiatorExponential, BigInteger
responderExponential, byte[] id) {
+ byte[] _initiatorExponential =
stripBigIntegerToNetworkFormat(initiatorExponential);
+ byte[] _responderExponential =
stripBigIntegerToNetworkFormat(responderExponential);
+ byte[] result = new byte[nonceInitiator.length +
nonceResponder.length + _initiatorExponential.length +
_responderExponential.length + id.length];
+ int offset = 0;
+
+ System.arraycopy(nonceInitiator,
0,result,offset,nonceInitiator.length);
+ offset += nonceInitiator.length;
+ System.arraycopy(nonceResponder,0
,result,offset,nonceResponder.length);
+ offset += nonceResponder.length;
+ System.arraycopy(_initiatorExponential, 0, result,offset,
_initiatorExponential.length);
+ offset += _initiatorExponential.length;
+ System.arraycopy(_responderExponential, 0, result, offset,
_responderExponential.length);
+ offset += _responderExponential.length;
+ System.arraycopy(id, 0, result , offset,id.length);
+
+ return result;
+ }
+
+ /*
+ * Actually sign the DH parameters for message2
+ */
+ private DSASignature signDHParams(BigInteger exponential, DSAGroup
group) {
+ return crypto.sign(SHA256.digest(assembleDHParams(exponential,
group)));
+ }
+
+ private byte[] getTransientKey() {
+ synchronized (authenticatorCache) {
+ return transientKey;
+ }
+ }
+
+ private byte[] computeJFKSharedKey(BigInteger exponential, byte[] nI,
byte[] nR, String what) {
+ assert("0".equals(what) || "1".equals(what) ||
"2".equals(what));
+ HMAC mac = new HMAC(SHA256.getInstance());
+ byte[] number = null;
+ try { number = what.getBytes("UTF-8"); } catch
(UnsupportedEncodingException e) {}
+
+ byte[] toHash = new byte[NONCE_SIZE * 2 + number.length];
+ int offset = 0;
+ System.arraycopy(nI, 0, toHash, offset, NONCE_SIZE);
+ offset += NONCE_SIZE;
+ System.arraycopy(nR, 0, toHash, offset, NONCE_SIZE);
+ offset += NONCE_SIZE;
+ System.arraycopy(number, 0, toHash, offset, number.length);
+
+ return mac.mac(exponential.toByteArray(), toHash, HASH_LENGTH);
+ }
+
+ private void resetTransientKey() {
+ Logger.normal(this, "JFK's TransientKey has been changed and
the message cache flushed.");
+ synchronized (authenticatorCache) {
+ node.random.nextBytes(transientKey);
+
+ // reset the authenticator cache
+ authenticatorCache.clear();
+ }
+ }
+
+ private byte[] stripBigIntegerToNetworkFormat(BigInteger exponential) {
+ byte[] data = exponential.toByteArray();
+ int targetLength = DiffieHellman.modulusLengthInBytes();
+
+ if(data.length != targetLength) {
+ byte[] newData = new byte[targetLength];
+ if((data.length == targetLength+1) && (data[0] == 0)) {
+ // Sign bit
+ System.arraycopy(data, 1, newData, 0,
targetLength);
+ } else if(data.length < targetLength) {
+ System.arraycopy(data, 0, newData,
targetLength-data.length, data.length);
+ } else {
+ throw new IllegalStateException("Too long!");
+ }
+ data = newData;
+ }
+ return data;
+ }
}
Modified: trunk/freenet/src/freenet/node/PeerNode.java
===================================================================
--- trunk/freenet/src/freenet/node/PeerNode.java 2007-09-29 19:22:11 UTC
(rev 15394)
+++ trunk/freenet/src/freenet/node/PeerNode.java 2007-09-29 19:28:19 UTC
(rev 15395)
@@ -85,6 +85,15 @@
* Set true if this peer has a incompatible newer build than we are
*/
protected boolean verifiedIncompatibleNewerVersion;
+
+ /*
+ * Buffer of Ni,Nr,g^i,g^r,ID
+ */
+ private byte[] jfkBuffer;
+ //TODO: sync ?
+ protected byte[] jfkKa;
+ protected byte[] jfkKe;
+ protected byte[] jfkKs;
/** My low-level address for SocketManager purposes */
private Peer detectedPeer;
@@ -2756,4 +2765,12 @@
public synchronized boolean isDisconnecting() {
return disconnecting;
}
+
+ protected byte[] getJFKBuffer() {
+ return jfkBuffer;
+ }
+
+ protected void setJFKBuffer(byte[] bufferJFK) {
+ this.jfkBuffer = bufferJFK;
+ }
}