[freenet-cvs] r15084 - branches/freenet-jfk/src/freenet/node
nextgens at freenetproject.org
nextgens at freenetproject.org
Sun Sep 9 15:59:07 UTC 2007
Author: nextgens
Date: 2007-09-09 15:59:07 +0000 (Sun, 09 Sep 2007)
New Revision: 15084
Modified:
branches/freenet-jfk/src/freenet/node/FNPPacketMangler.java
Log:
indent
Modified: branches/freenet-jfk/src/freenet/node/FNPPacketMangler.java
===================================================================
--- branches/freenet-jfk/src/freenet/node/FNPPacketMangler.java 2007-09-09 15:58:22 UTC (rev 15083)
+++ branches/freenet-jfk/src/freenet/node/FNPPacketMangler.java 2007-09-09 15:59:07 UTC (rev 15084)
@@ -56,2059 +56,2059 @@
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;
- /*
- * Objects cached during JFK message exchange: Message3,Message4 and authenticator
- * 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.
- */
- final Map message3Cache;
- final Map message4Cache;
- final Map authenticatorCache;
- final eKey encryptionKey;
- final DSAGroup g;
- static DSAPrivateKey PKR,PKI;
- final RandomSource r;
- 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: Message3,Message4 and authenticator
+ * 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.
+ */
+ final Map message3Cache;
+ final Map message4Cache;
+ final Map authenticatorCache;
+ final eKey encryptionKey;
+ final DSAGroup g;
+ static DSAPrivateKey PKR,PKI;
+ final RandomSource r;
+ 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 */
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();
- message3Cache = new HashMap();
- message4Cache = new HashMap();
- authenticatorCache = new HashMap();
- encryptionKey = new eKey();
- g = Global.DSAgroupBigA;
- r=node.random;
- 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();
+ message3Cache = new HashMap();
+ message4Cache = new HashMap();
+ authenticatorCache = new HashMap();
+ encryptionKey = new eKey();
+ g = Global.DSAgroupBigA;
+ r=node.random;
+ fullHeadersLengthMinimum = HEADERS_LENGTH_MINIMUM + sock.getHeadersLength();
+ fullHeadersLengthOneMessage = HEADERS_LENGTH_ONE_MESSAGE + sock.getHeadersLength();
logMINOR = Logger.shouldLog(Logger.MINOR, this);
- }
+ }
- /**
- * 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) {
- // Might be an auth packet
- if(tryProcessAuth(buf, offset, length, opn, peer)) return;
- }
- }
- PeerNode[] peers = crypto.getPeerNodes();
- 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(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)) 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) {
- 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);
- 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) {
+ // Might be an auth packet
+ if(tryProcessAuth(buf, offset, length, opn, peer)) return;
+ }
+ }
+ PeerNode[] peers = crypto.getPeerNodes();
+ 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(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)) 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) {
- 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) {
+ 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);
- } else if(packetType == 3) {
- // We are Alice
- processSignedDHTwoOrThree(3, payload, pn, replyTo, false);
- }
- }
- 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
- */
- ProcessMessage1(payload,pn,replyTo,0);
-
- }
- 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.
- */
- ProcessMessage2(payload,pn,replyTo,1);
- }
- 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
- */
- sendProcessMessage3(pn,replyTo,2);
- }
- 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
- */
- ProcessMessage4(payload,pn,replyTo,3);
- }
- }
- else {
- Logger.error(this, "Decrypted auth packet but unknown negotiation type "+negType+" from "+replyTo+" possibly from "+pn);
- return;
- }
- }
- /*
- * Initiator DH Exponential
- */
- private synchronized byte[] Gi(PeerNode pn){
- DiffieHellmanContext dh=(DiffieHellmanContext)pn.getKeyAgreementSchemeContext();
- if(dh==null)
- {
- if(shouldLogErrorInHandshake())
- Logger.error(this,"Failed getting exponentials");
-
- }
- return dh.getOurExponential().toByteArray();
- }
-
- /*
- * Responder DH Exponential
- */
- private synchronized byte[] Gr(PeerNode pn){
- DiffieHellmanContext dh=(DiffieHellmanContext)pn.getKeyAgreementSchemeContext();
- if(dh==null)
- {
- if(shouldLogErrorInHandshake())
- Logger.error(this,"Failed getting exponentials");
-
- }
- return dh.getHisExponential().toByteArray();
- }
-
- /*
- * Shared Secret key
- * Alice generates random number x and computes exponential g^x
- * Bob generates random number y and computes exponential g^y
- * Shared secret key= (g^x)^y used as key for computing hash of the encryption key
- */
- private synchronized byte[] sharedSecretKey(PeerNode pn){
- DiffieHellmanContext dh=(DiffieHellmanContext)pn.getKeyAgreementSchemeContext();
- if(dh==null)
- {
- if(shouldLogErrorInHandshake())
- Logger.error(this,"Failed getting exponentials");
-
- }
- return dh.getKey();
- }
- /*
- * The Initiator and Responder nonce are random bytes used to provide key independence
- */
- private synchronized byte[] iNonce(){
-
- byte[] n=new byte[16];
- node.random.nextBytes(n);
- return n;
- }
- private synchronized byte[] rNonce(){
- byte[] n=new byte[16];
- node.random.nextBytes(n);
- return n;
- }
- /*
- * 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
- */
- private void ProcessMessage1(byte[] payload,PeerNode pn,Peer replyTo,int phase)
- {
- long t1=System.currentTimeMillis();
- byte[] Ni = iNonce();
- byte[] DHExpi = Gi(pn);
- byte[] message1=new byte[Ni.length + DHExpi.length+1];
- System.arraycopy(Ni,0,message1,0,Ni.length);
- System.arraycopy(DHExpi,0,message1,Ni.length+1,DHExpi.length);
- //Send params:Version,negType,phase,data,peernode,peer
- sendMessage1or2Packet(1,2,0,message1,pn,replyTo);
- long t2=System.currentTimeMillis();
- if((t2-t1)>500)
- Logger.error(this,"Message1 timeout error:Sending packet for"+pn.getPeer());
- }
- /*
- * Authenticator computed over the Responder exponentials and the Nonces
- * Used by the responder to verify the authenticity of the received data
- */
- private byte[] processMessageAuth(PeerNode pn){
- byte[] Ni = iNonce();
- byte[] Nr = rNonce();
- byte[] DHExpr = Gr(pn);
- byte[] authData=new byte[Ni.length+Nr.length+DHExpr.length+1];
- int offset = 0;
- System.arraycopy(Ni,0,authData,offset,Ni.length);
- offset += Ni.length+1;
- System.arraycopy(Nr,0,authData,offset,Nr.length);
- offset += Nr.length+1;
+ 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);
+ 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;
+ }
+ }
+
+ /**
+ * 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) {
+ 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;
+ }
+
+ 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);
+ } else if(packetType == 3) {
+ // We are Alice
+ processSignedDHTwoOrThree(3, payload, pn, replyTo, false);
+ }
+ }
+ 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
+ */
+ ProcessMessage1(payload,pn,replyTo,0);
+
+ }
+ 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.
+ */
+ ProcessMessage2(payload,pn,replyTo,1);
+ }
+ 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
+ */
+ sendProcessMessage3(pn,replyTo,2);
+ }
+ 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
+ */
+ ProcessMessage4(payload,pn,replyTo,3);
+ }
+ }
+ else {
+ Logger.error(this, "Decrypted auth packet but unknown negotiation type "+negType+" from "+replyTo+" possibly from "+pn);
+ return;
+ }
+ }
+ /*
+ * Initiator DH Exponential
+ */
+ private synchronized byte[] Gi(PeerNode pn){
+ DiffieHellmanContext dh=(DiffieHellmanContext)pn.getKeyAgreementSchemeContext();
+ if(dh==null)
+ {
+ if(shouldLogErrorInHandshake())
+ Logger.error(this,"Failed getting exponentials");
+
+ }
+ return dh.getOurExponential().toByteArray();
+ }
+
+ /*
+ * Responder DH Exponential
+ */
+ private synchronized byte[] Gr(PeerNode pn){
+ DiffieHellmanContext dh=(DiffieHellmanContext)pn.getKeyAgreementSchemeContext();
+ if(dh==null)
+ {
+ if(shouldLogErrorInHandshake())
+ Logger.error(this,"Failed getting exponentials");
+
+ }
+ return dh.getHisExponential().toByteArray();
+ }
+
+ /*
+ * Shared Secret key
+ * Alice generates random number x and computes exponential g^x
+ * Bob generates random number y and computes exponential g^y
+ * Shared secret key= (g^x)^y used as key for computing hash of the encryption key
+ */
+ private synchronized byte[] sharedSecretKey(PeerNode pn){
+ DiffieHellmanContext dh=(DiffieHellmanContext)pn.getKeyAgreementSchemeContext();
+ if(dh==null)
+ {
+ if(shouldLogErrorInHandshake())
+ Logger.error(this,"Failed getting exponentials");
+
+ }
+ return dh.getKey();
+ }
+ /*
+ * The Initiator and Responder nonce are random bytes used to provide key independence
+ */
+ private synchronized byte[] iNonce(){
+
+ byte[] n=new byte[16];
+ node.random.nextBytes(n);
+ return n;
+ }
+ private synchronized byte[] rNonce(){
+ byte[] n=new byte[16];
+ node.random.nextBytes(n);
+ return n;
+ }
+ /*
+ * 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
+ */
+ private void ProcessMessage1(byte[] payload,PeerNode pn,Peer replyTo,int phase)
+ {
+ long t1=System.currentTimeMillis();
+ byte[] Ni = iNonce();
+ byte[] DHExpi = Gi(pn);
+ byte[] message1=new byte[Ni.length + DHExpi.length+1];
+ System.arraycopy(Ni,0,message1,0,Ni.length);
+ System.arraycopy(DHExpi,0,message1,Ni.length+1,DHExpi.length);
+ //Send params:Version,negType,phase,data,peernode,peer
+ sendMessage1or2Packet(1,2,0,message1,pn,replyTo);
+ long t2=System.currentTimeMillis();
+ if((t2-t1)>500)
+ Logger.error(this,"Message1 timeout error:Sending packet for"+pn.getPeer());
+ }
+ /*
+ * Authenticator computed over the Responder exponentials and the Nonces
+ * Used by the responder to verify the authenticity of the received data
+ */
+ private byte[] processMessageAuth(PeerNode pn){
+ byte[] Ni = iNonce();
+ byte[] Nr = rNonce();
+ byte[] DHExpr = Gr(pn);
+ byte[] authData=new byte[Ni.length+Nr.length+DHExpr.length+1];
+ int offset = 0;
+ System.arraycopy(Ni,0,authData,offset,Ni.length);
+ offset += Ni.length+1;
+ System.arraycopy(Nr,0,authData,offset,Nr.length);
+ offset += Nr.length+1;
System.arraycopy(DHExpr,0,authData,offset,DHExpr.length);
/*
- * Calculate the Hash of the Concatenated data(Responder exponentials, nonces)
+ * Calculate the Hash of the Concatenated data(Responder exponentials, nonces)
* using a key that will be private to the responder
- */
+ */
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
- * @param The packet phase number
- * @param The peer to which we need to send the packet
- * @param The peerNode we are talking to
- */
+ }
+ /*
+ * 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
+ * @param The packet phase number
+ * @param The peer to which we need to send the packet
+ * @param The peerNode we are talking to
+ */
- private void ProcessMessage2(byte[] payload,PeerNode pn,Peer replyTo,int phase)
- {
+ private void ProcessMessage2(byte[] payload,PeerNode pn,Peer replyTo,int phase)
+ {
long t1=System.currentTimeMillis();
- byte[] Ni = iNonce();
- byte[] Nr = rNonce();
- byte[] DHExpr = Gr(pn);
- byte[] authData=new byte[Ni.length+Nr.length+DHExpr.length+1];
- System.arraycopy(Ni,0,authData,0,Ni.length);
- System.arraycopy(Nr,0,authData,Ni.length+1,Nr.length);
+ byte[] Ni = iNonce();
+ byte[] Nr = rNonce();
+ byte[] DHExpr = Gr(pn);
+ byte[] authData=new byte[Ni.length+Nr.length+DHExpr.length+1];
+ System.arraycopy(Ni,0,authData,0,Ni.length);
+ System.arraycopy(Nr,0,authData,Ni.length+1,Nr.length);
System.arraycopy(DHExpr,0,authData,Ni.length+Nr.length+1,DHExpr.length);
byte[] signData=new byte[DHExpr.length+1];
- System.arraycopy(DHExpr,0,signData,0,DHExpr.length);
+ System.arraycopy(DHExpr,0,signData,0,DHExpr.length);
//Compute the Signature:DSA
- PKR=new DSAPrivateKey(g, r);
- //Params: Data,DSAGroup,DSAPrivateKey,randomSource
- DSASignature sig = crypto.sign(signData,g,PKR,r);
+ PKR=new DSAPrivateKey(g, r);
+ //Params: Data,DSAGroup,DSAPrivateKey,randomSource
+ DSASignature sig = crypto.sign(signData,g,PKR,r);
byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
- byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
- Logger.minor(this, " r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
- if(r.length > 255 || s.length > 255)
- throw new IllegalStateException("R or S is too long: r.length="+r.length+" s.length="+s.length);
+ byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+ Logger.minor(this, " r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
+ if(r.length > 255 || s.length > 255)
+ throw new IllegalStateException("R or S is too long: r.length="+r.length+" s.length="+s.length);
//Data sent in the clear
byte[] unVerifiedData=new byte[Ni.length+Nr.length+DHExpr.length+1];
- System.arraycopy(Ni,0,unVerifiedData,0,Ni.length);
- System.arraycopy(Nr,0,unVerifiedData,Ni.length+1,Nr.length);
+ System.arraycopy(Ni,0,unVerifiedData,0,Ni.length);
+ System.arraycopy(Nr,0,unVerifiedData,Ni.length+1,Nr.length);
System.arraycopy(DHExpr,0,unVerifiedData,Ni.length+Nr.length+1,DHExpr.length);
/*
- * Compute the authenticator
- * Used by the responder in Message4 to verify the authenticity of the message
- * The same authenticator is used in Message3 and identified using the DSAPrivateKey
- */
+ * Compute the authenticator
+ * Used by the responder in Message4 to verify the authenticity of the message
+ * The same authenticator is used in Message3 and identified using the DSAPrivateKey
+ */
HKrGenerator trKey=new HKrGenerator(node);
- byte[] hkr=trKey.getNewHKr();
+ byte[] hkr=trKey.getNewHKr();
HMAC hash=new HMAC(SHA1.getInstance());
- byte[] authenticator = hash.mac(hkr,authData,hkr.length);
- authenticatorCache.put(PKR,authenticator);
+ byte[] authenticator = hash.mac(hkr,authData,hkr.length);
+ authenticatorCache.put(PKR,authenticator);
byte[] Message2=new byte[authenticator.length+unVerifiedData.length+s.length+r.length+1];
byte[] signedData=new byte[s.length+r.length];
System.arraycopy(signedData,0,Message2,0,signedData.length);
System.arraycopy(unVerifiedData,0,Message2,signData.length+1,unVerifiedData.length);
System.arraycopy(authenticator,0,Message2,signedData.length+unVerifiedData.length+1,authenticator.length);
- //Send params:Version,negType,phase,data,peernode,peer
+ //Send params:Version,negType,phase,data,peernode,peer
sendMessage1or2Packet(1,2,1,Message2,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 the legality of the message
- * Compute the signature of the unVerifiedData and encrypt it using a shared key
- * which is derived from DHExponentials and the nonces
- * @param The packet phase number
- * @param The peer to which we need to send the packet
- * @param The peerNode we are talking to
- * @return byte Message3
- */
- private byte[] ProcessMessage3(PeerNode pn,Peer replyTo,int phase)
- {
+ if((t2-t1)>500)
+ Logger.error(this,"Message1 timeout error:Sending packet for"+pn.getPeer());
- // Get the authenticator,which is the latest entry into the cache
- // It is basically a keyed hash(HMAC); size of output is that of the underlying hash function
- byte[] authenticator = new byte[16];
- try{
- // Intrinsic lock provided by the object authenticatorCache
- synchronized(authenticatorCache){
- authenticator = getBytes(authenticatorCache.get(PKR));
- }
- }
- catch(IOException e){
- Logger.error(this,"Error getting bytes");
- }
- byte[] Ni = iNonce();
- byte[] Nr = rNonce();
- byte[] DHExpi = Gi(pn);
- byte[] DHExpr = Gr(pn);
- byte[] unVerifiedData=new byte[Ni.length+Nr.length+DHExpr.length+DHExpi.length+1];
- System.arraycopy(Ni,0,unVerifiedData,0,Ni.length);
- System.arraycopy(Nr,0,unVerifiedData,Ni.length+1,Nr.length);
- System.arraycopy(DHExpi,0,unVerifiedData,Ni.length+Nr.length+1,DHExpi.length);
- System.arraycopy(DHExpr,0,unVerifiedData,Ni.length+Nr.length+DHExpi.length+1,DHExpr.length);
- /*
- * Digital Signature of the message with the private key belonging to the initiator/responder
- * It is assumed to be non-message recovering
- */
- PKI=new DSAPrivateKey(g, r);
- //Params: Data,DSAGroup,DSAPrivateKey,randomSource
- DSASignature sig = crypto.sign(unVerifiedData,g,PKI,r);
- byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
- byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
- Logger.minor(this, " r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
- BlockCipher c=pn.outgoingSetupCipher;
- if(logMINOR)
- Logger.minor(this,"Cipher"+HexUtil.bytesToHex(pn.outgoingSetupKey));
- /*
- * Initializes the cipher context with the given key
- * This would avoid the computation of key using the Rijndael key schedule(S boxes,Rcon etc)
- * The key used is generated from Hash of Message:(Ni, Nr, 1) over the shared key of DH
- */
- c.initialize(encryptionKey.getEncKey(sharedSecretKey(pn),iNonce(),rNonce()));
- PCFBMode pk=PCFBMode.create(c);
- byte[] iv=new byte[pk.lengthIV()];
- int encryptedDataLength = iv.length + r.length + s.length + 2;
- /*
- * Data sent in the clear is signed and encrypted using PCFB and rijndael
- */
- byte[] encryptedData = new byte[encryptedDataLength];
- System.arraycopy(iv, 0, encryptedData, 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);
- encryptedData[count++] = (byte) r.length;
- System.arraycopy(r, 0, encryptedData, count, r.length);
- count += r.length;
- encryptedData[count++] = (byte) s.length;
- System.arraycopy(s, 0, encryptedData, count, s.length);
- count += s.length;
- pk.blockEncipher(encryptedData, 0, encryptedData.length);
- byte[] message3=new byte[encryptedData.length+authenticator.length+unVerifiedData.length+1];
- System.arraycopy(encryptedData,0,message3,0,encryptedData.length);
- System.arraycopy(authenticator,0,message3,encryptedData.length+1,authenticator.length);
- System.arraycopy(unVerifiedData,0,message3,encryptedData.length+authenticator.length+1,unVerifiedData.length);
- return message3;
-
- }
-
- /*
- * Send computed Message3
- * @param The packet phase number
- * @param The peer to which we need to send the packet
- * @param The peerNode we are talking to
- */
- private void sendProcessMessage3(PeerNode pn,Peer replyTo,int phase){
-
- /*
- * The identifying information of the inititator is sent encrypted hence protected
- * from both passive and active attackers. The initiator's identity cannot be retrived
- * from message3 because an active attacker cannot complete the DH computation.
- */
- byte[] data = ProcessMessage3(pn,replyTo,phase);
- sendMessage3Packet(1,2,2,data,pn,replyTo);
- }
-
- /*
- * Responder Method:Message4
- * Process Message4
- * Encrypted message of the signature on both nonces, both exponentials using the same
- * keys as in the previous message.The Initiator can verify that the Responder is present
- * and participating in the session, by decrypting the message and verifying the enclosed
- * signature.
- * @param The packet phase number
- * @param The peer to which we need to send the packet
- * @param The peerNode we are talking to
- */
-
- private void ProcessMessage4(byte[] payload,PeerNode pn,Peer replyTo,int phase)
- {
-
- long t1=System.currentTimeMillis();
- byte[] Ni = iNonce();
- byte[] Nr = rNonce();
- byte[] DHExpi = Gi(pn);
- byte[] DHExpr = Gr(pn);
- byte[] Data=new byte[Ni.length+Nr.length+DHExpr.length+DHExpi.length+1];
- System.arraycopy(Ni,0,Data,0,Ni.length);
- System.arraycopy(Nr,0,Data,Ni.length+1,Nr.length);
- System.arraycopy(DHExpi,0,Data,Ni.length+Nr.length+1,DHExpi.length);
- System.arraycopy(DHExpr,0,Data,Ni.length+Nr.length+DHExpi.length+1,DHExpr.length);
- DSASignature sig = crypto.sign(Data,g,PKR,r);
- byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
- byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
- Logger.minor(this, " r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
- BlockCipher c=pn.outgoingSetupCipher;
- if(logMINOR)
- Logger.minor(this,"Cipher"+HexUtil.bytesToHex(pn.outgoingSetupKey));
- /*
- * Initializes the cipher context with the given key
- * This would avoid the computation of key using the Rijndael key schedule(S boxes,Rcon etc)
- * The key used is generated from Hash of Message:(Ni, Nr, 1) over the shared key of DH
- */
- c.initialize(encryptionKey.getEncKey(sharedSecretKey(pn),iNonce(),rNonce()));
- PCFBMode pk=PCFBMode.create(c);
- byte[] iv=new byte[pk.lengthIV()];
- int message4Length = r.length + s.length + 2;
- byte[] message4 = new byte[message4Length];
- System.arraycopy(iv, 0, message4, 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);
- message4[count++] = (byte) r.length;
- System.arraycopy(r, 0, message4, count, r.length);
- count += r.length;
- message4[count++] = (byte) s.length;
- System.arraycopy(s, 0, message4, count, s.length);
- count += s.length;
- pk.blockEncipher(message4, 0, message4.length);
- //Send params:Version,negType,phase,data,peernode,peer
- sendMessage4Packet(1,2,3,message4,pn,replyTo);
- long t2=System.currentTimeMillis();
- if((t2-t1)>500)
- Logger.error(this,"Message4 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 the legality of the message
+ * Compute the signature of the unVerifiedData and encrypt it using a shared key
+ * which is derived from DHExponentials and the nonces
+ * @param The packet phase number
+ * @param The peer to which we need to send the packet
+ * @param The peerNode we are talking to
+ * @return byte Message3
+ */
+ private byte[] ProcessMessage3(PeerNode pn,Peer replyTo,int phase)
+ {
- }
-
- /*
- * Send Message1or2 packet
- * @param version
- * @param negType
- * @param The packet phase number
- * @param Concatenated data
- * @param The peerNode we are talking to
- * @param The peer to which we need to send the packet
- */
- private void sendMessage1or2Packet(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);
- try
+ // Get the authenticator,which is the latest entry into the cache
+ // It is basically a keyed hash(HMAC); size of output is that of the underlying hash function
+ byte[] authenticator = new byte[16];
+ try{
+ // Intrinsic lock provided by the object authenticatorCache
+ synchronized(authenticatorCache){
+ authenticator = getBytes(authenticatorCache.get(PKR));
+ }
+ }
+ catch(IOException e){
+ Logger.error(this,"Error getting bytes");
+ }
+ byte[] Ni = iNonce();
+ byte[] Nr = rNonce();
+ byte[] DHExpi = Gi(pn);
+ byte[] DHExpr = Gr(pn);
+ byte[] unVerifiedData=new byte[Ni.length+Nr.length+DHExpr.length+DHExpi.length+1];
+ System.arraycopy(Ni,0,unVerifiedData,0,Ni.length);
+ System.arraycopy(Nr,0,unVerifiedData,Ni.length+1,Nr.length);
+ System.arraycopy(DHExpi,0,unVerifiedData,Ni.length+Nr.length+1,DHExpi.length);
+ System.arraycopy(DHExpr,0,unVerifiedData,Ni.length+Nr.length+DHExpi.length+1,DHExpr.length);
+ /*
+ * Digital Signature of the message with the private key belonging to the initiator/responder
+ * It is assumed to be non-message recovering
+ */
+ PKI=new DSAPrivateKey(g, r);
+ //Params: Data,DSAGroup,DSAPrivateKey,randomSource
+ DSASignature sig = crypto.sign(unVerifiedData,g,PKI,r);
+ byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+ byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+ Logger.minor(this, " r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
+ BlockCipher c=pn.outgoingSetupCipher;
+ if(logMINOR)
+ Logger.minor(this,"Cipher"+HexUtil.bytesToHex(pn.outgoingSetupKey));
+ /*
+ * Initializes the cipher context with the given key
+ * This would avoid the computation of key using the Rijndael key schedule(S boxes,Rcon etc)
+ * The key used is generated from Hash of Message:(Ni, Nr, 1) over the shared key of DH
+ */
+ c.initialize(encryptionKey.getEncKey(sharedSecretKey(pn),iNonce(),rNonce()));
+ PCFBMode pk=PCFBMode.create(c);
+ byte[] iv=new byte[pk.lengthIV()];
+ int encryptedDataLength = iv.length + r.length + s.length + 2;
+ /*
+ * Data sent in the clear is signed and encrypted using PCFB and rijndael
+ */
+ byte[] encryptedData = new byte[encryptedDataLength];
+ System.arraycopy(iv, 0, encryptedData, 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);
+ encryptedData[count++] = (byte) r.length;
+ System.arraycopy(r, 0, encryptedData, count, r.length);
+ count += r.length;
+ encryptedData[count++] = (byte) s.length;
+ System.arraycopy(s, 0, encryptedData, count, s.length);
+ count += s.length;
+ pk.blockEncipher(encryptedData, 0, encryptedData.length);
+ byte[] message3=new byte[encryptedData.length+authenticator.length+unVerifiedData.length+1];
+ System.arraycopy(encryptedData,0,message3,0,encryptedData.length);
+ System.arraycopy(authenticator,0,message3,encryptedData.length+1,authenticator.length);
+ System.arraycopy(unVerifiedData,0,message3,encryptedData.length+authenticator.length+1,unVerifiedData.length);
+ return message3;
+
+ }
+
+ /*
+ * Send computed Message3
+ * @param The packet phase number
+ * @param The peer to which we need to send the packet
+ * @param The peerNode we are talking to
+ */
+ private void sendProcessMessage3(PeerNode pn,Peer replyTo,int phase){
+
+ /*
+ * The identifying information of the inititator is sent encrypted hence protected
+ * from both passive and active attackers. The initiator's identity cannot be retrived
+ * from message3 because an active attacker cannot complete the DH computation.
+ */
+ byte[] data = ProcessMessage3(pn,replyTo,phase);
+ sendMessage3Packet(1,2,2,data,pn,replyTo);
+ }
+
+ /*
+ * Responder Method:Message4
+ * Process Message4
+ * Encrypted message of the signature on both nonces, both exponentials using the same
+ * keys as in the previous message.The Initiator can verify that the Responder is present
+ * and participating in the session, by decrypting the message and verifying the enclosed
+ * signature.
+ * @param The packet phase number
+ * @param The peer to which we need to send the packet
+ * @param The peerNode we are talking to
+ */
+
+ private void ProcessMessage4(byte[] payload,PeerNode pn,Peer replyTo,int phase)
{
- sendPacket(output,replyTo,pn,0);
- }catch(LocalAddressException e)
+
+ long t1=System.currentTimeMillis();
+ byte[] Ni = iNonce();
+ byte[] Nr = rNonce();
+ byte[] DHExpi = Gi(pn);
+ byte[] DHExpr = Gr(pn);
+ byte[] Data=new byte[Ni.length+Nr.length+DHExpr.length+DHExpi.length+1];
+ System.arraycopy(Ni,0,Data,0,Ni.length);
+ System.arraycopy(Nr,0,Data,Ni.length+1,Nr.length);
+ System.arraycopy(DHExpi,0,Data,Ni.length+Nr.length+1,DHExpi.length);
+ System.arraycopy(DHExpr,0,Data,Ni.length+Nr.length+DHExpi.length+1,DHExpr.length);
+ DSASignature sig = crypto.sign(Data,g,PKR,r);
+ byte[] r = sig.getRBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+ byte[] s = sig.getSBytes(Node.SIGNATURE_PARAMETER_LENGTH);
+ Logger.minor(this, " r="+HexUtil.bytesToHex(sig.getR().toByteArray())+" s="+HexUtil.bytesToHex(sig.getS().toByteArray()));
+ BlockCipher c=pn.outgoingSetupCipher;
+ if(logMINOR)
+ Logger.minor(this,"Cipher"+HexUtil.bytesToHex(pn.outgoingSetupKey));
+ /*
+ * Initializes the cipher context with the given key
+ * This would avoid the computation of key using the Rijndael key schedule(S boxes,Rcon etc)
+ * The key used is generated from Hash of Message:(Ni, Nr, 1) over the shared key of DH
+ */
+ c.initialize(encryptionKey.getEncKey(sharedSecretKey(pn),iNonce(),rNonce()));
+ PCFBMode pk=PCFBMode.create(c);
+ byte[] iv=new byte[pk.lengthIV()];
+ int message4Length = r.length + s.length + 2;
+ byte[] message4 = new byte[message4Length];
+ System.arraycopy(iv, 0, message4, 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);
+ message4[count++] = (byte) r.length;
+ System.arraycopy(r, 0, message4, count, r.length);
+ count += r.length;
+ message4[count++] = (byte) s.length;
+ System.arraycopy(s, 0, message4, count, s.length);
+ count += s.length;
+ pk.blockEncipher(message4, 0, message4.length);
+ //Send params:Version,negType,phase,data,peernode,peer
+ sendMessage4Packet(1,2,3,message4,pn,replyTo);
+ long t2=System.currentTimeMillis();
+ if((t2-t1)>500)
+ Logger.error(this,"Message4 timeout error:Sending packet for"+pn.getPeer());
+
+ }
+
+ /*
+ * Send Message1or2 packet
+ * @param version
+ * @param negType
+ * @param The packet phase number
+ * @param Concatenated data
+ * @param The peerNode we are talking to
+ * @param The peer to which we need to send the packet
+ */
+ private void sendMessage1or2Packet(int version,int negType,int phase,byte[] data,PeerNode pn,Peer replyTo)
{
- Logger.error(this, "Tried to send auth packet to local address: "+replyTo+" for "+pn);
+ 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);
+ try
+ {
+ sendPacket(output,replyTo,pn,0);
+ }catch(LocalAddressException e)
+ {
+ Logger.error(this, "Tried to send auth packet to local address: "+replyTo+" for "+pn);
+ }
}
- }
-
- /*
- * 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;
- }
-
- /*
- * Send Message3 packet
- * Also check for duplicate message
- * @param version
- * @param negType
- * @param The packet phase number
- * @param Concatenated data
- * @param The peerNode we are talking to
- * @param The peer to which we need to send the packet
- */
- private void sendMessage3Packet(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];
- if((data.length+3) > sock.getMaxPacketSize())
- throw new IllegalStateException("Packet length too long");
- /*
- * The key for looking up messages in the cache is the authenticator
- * This prevents DOS attacks where the attacker randomly tries to replace encrypted blocks
- * of a valid message causing a cache miss
- * This would result in increased processing on the Responder side->CPU exhaustion attacks
- */
- byte[] cacheKey=processMessageAuth(pn);
- Object result;
- //All recent messages 3 and 4 are cached
- if(phase==2){
- // Intrinsic lock provided by the object message3Cache
- synchronized(message3Cache) {
- result = message3Cache.get(cacheKey);
- }
- if(result != null) {
- synchronized(message3Cache) {
- message3Cache.put(cacheKey,data);
- }
- // We don't want to keep the lock while sending
- try
- {
- sendMessage4Packet(1,2,3,getBytes(message4Cache.get(cacheKey)),pn,replyTo);
- }
- catch(IOException e){
- Logger.error(this,"Error getting bytes");
- }
- }
- sendProcessMessage3(pn,replyTo,phase);
- }
- else if(phase==3){
- message4Cache.put(cacheKey,data.toString());
- }
- else{
- Logger.error(this,"Wrong message");
-
- }
- 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);
- try
- {
- sendPacket(output,replyTo,pn,0);
- }catch(LocalAddressException e) {
- Logger.error(this, "Tried to send auth packet to local address: "+replyTo+" 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;
+ }
- /*
- * Send Message4 packet
- * @param version
- * @param negType
- * @param The packet phase number
- * @param Concatenated data
- * @param The peerNode we are talking to
- * @param The peer to which we need to send the packet
- */
+ /*
+ * Send Message3 packet
+ * Also check for duplicate message
+ * @param version
+ * @param negType
+ * @param The packet phase number
+ * @param Concatenated data
+ * @param The peerNode we are talking to
+ * @param The peer to which we need to send the packet
+ */
- private void sendMessage4Packet(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];
- if((data.length+3) > sock.getMaxPacketSize())
- throw new IllegalStateException("Packet length too long");
- 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);
- try
- {
- sendPacket(output,replyTo,pn,0);
- }catch(LocalAddressException e)
- {
- Logger.error(this, "Tried to send auth packet to local address: "+replyTo+" for "+pn.getPeer());
- }
-
- }
-
- /**
- * 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);
- }
+ private void sendMessage3Packet(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];
+ if((data.length+3) > sock.getMaxPacketSize())
+ throw new IllegalStateException("Packet length too long");
+ /*
+ * The key for looking up messages in the cache is the authenticator
+ * This prevents DOS attacks where the attacker randomly tries to replace encrypted blocks
+ * of a valid message causing a cache miss
+ * This would result in increased processing on the Responder side->CPU exhaustion attacks
+ */
+ byte[] cacheKey=processMessageAuth(pn);
+ Object result;
+ //All recent messages 3 and 4 are cached
+ if(phase==2){
+ // Intrinsic lock provided by the object message3Cache
+ synchronized(message3Cache) {
+ result = message3Cache.get(cacheKey);
+ }
+ if(result != null) {
+ synchronized(message3Cache) {
+ message3Cache.put(cacheKey,data);
+ }
+ // We don't want to keep the lock while sending
+ try
+ {
+ sendMessage4Packet(1,2,3,getBytes(message4Cache.get(cacheKey)),pn,replyTo);
+ }
+ catch(IOException e){
+ Logger.error(this,"Error getting bytes");
+ }
+ }
+ sendProcessMessage3(pn,replyTo,phase);
+ }
+ else if(phase==3){
+ message4Cache.put(cacheKey,data.toString());
+ }
+ else{
+ Logger.error(this,"Wrong message");
- /**
- * 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());
- }
- }
+ }
+ 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);
+ try
+ {
+ sendPacket(output,replyTo,pn,0);
+ }catch(LocalAddressException e) {
+ Logger.error(this, "Tried to send auth packet to local address: "+replyTo+" for "+pn.getPeer());
+ }
- /**
- * 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);
+ /*
+ * Send Message4 packet
+ * @param version
+ * @param negType
+ * @param The packet phase number
+ * @param Concatenated data
+ * @param The peerNode we are talking to
+ * @param The peer to which we need to send the packet
+ */
+
+ private void sendMessage4Packet(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];
+ if((data.length+3) > sock.getMaxPacketSize())
+ throw new IllegalStateException("Packet length too long");
+ 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);
+ try
+ {
+ sendPacket(output,replyTo,pn,0);
+ }catch(LocalAddressException e)
+ {
+ Logger.error(this, "Tried to send auth packet to local address: "+replyTo+" for "+pn.getPeer());
+ }
+
+ }
+
+ /**
+ * 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 = 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());
+ }
+ }
+
+ /**
+ * 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??");
}
if(logMINOR) Logger.minor(this, "Sending auth packet (long) to "+replyTo+" - size "+data.length+" data length: "+output.length);
- }
+ }
- 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.
- */
- private DiffieHellmanContext processSignedDHTwoOrThree(int phase, byte[] payload, PeerNode pn, Peer replyTo, boolean sendCompletion) {
- 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);
- // 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);
- 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.
+ */
+ private DiffieHellmanContext processSignedDHTwoOrThree(int phase, byte[] payload, PeerNode pn, Peer replyTo, boolean sendCompletion) {
+ 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);
+ // 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);
+ pn.maybeSendInitialMessages();
+ } else {
+ Logger.error(this, "Handshake not completed");
+ }
+ return ctx;
+ }
- /** 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);
+ /**
+ * 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 ackCount = decrypted[ptr++] & 0xff;
- if(logMINOR) Logger.minor(this, "Acks: "+ackCount);
+ /**
+ * 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) {
- 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);
- }
+ if((phase == 0) && pn.hasLiveHandshake(System.currentTimeMillis())) {
+ if(logMINOR) Logger.minor(this, "Rejecting phase "+phase+" handshake on "+pn+" - already running one");
+ return null;
+ }
- 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);
- }
+ // 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;
+ }
- if(seqNumber == -1) {
- if(logMINOR) Logger.minor(this, "Returning because seqno = "+seqNumber);
- return;
- }
- // No sequence number == no messages
+ /**
+ * 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((seqNumber != -1) && tracker.alreadyReceived(seqNumber)) {
- tracker.queueAck(seqNumber);
+ 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);
+ }
+
+ 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);
tracker.pn.receivedPacket(false);
- 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");
- }
+ Logger.error(this, "Received packet twice ("+seqNumber+") from "+tracker.pn.getPeer()+": "+seqNumber+" ("+TimeUtil.formatTime((long) tracker.pn.pingAverage.currentValue(), 2, true)+" ping avg)");
+ return;
+ }
- /* (non-Javadoc)
+ 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
-