int modulusLength = getModulusLength(negType);
int nonceSize = getNonceSize(negType);
if(logMINOR) Logger.minor(this, "Got a JFK(3) message, processing it - "+pn);
BlockCipher c = null;
try { c = new Rijndael(256, 256); } catch (UnsupportedCipherException e) { throw new RuntimeException(e); }
final int expectedLength =
nonceSize*2 + // Ni, Nr
modulusLength*2 + // g^i, g^r
HASH_LENGTH + // authenticator
HASH_LENGTH + // HMAC of the cyphertext
(c.getBlockSize() >> 3) + // IV
HASH_LENGTH + // it's at least a signature
8 + // a bootid
8 + // packet tracker ID
1; // znoderefI* is at least 1 byte long
if(payload.length < expectedLength + 3) {
Logger.error(this, "Packet too short from "+pn+": "+payload.length+" after decryption in JFK(3), should be "+(expectedLength + 3));
return;
}
// Ni
byte[] nonceInitiator = new byte[nonceSize];
System.arraycopy(payload, inputOffset, nonceInitiator, 0, nonceSize);
inputOffset += nonceSize;
if(logDEBUG) Logger.debug(this, "We are receiving Ni : " + HexUtil.bytesToHex(nonceInitiator));
// Before negtype 9 we didn't hash it!
byte[] nonceInitiatorHashed = (negType > 8 ? SHA256.digest(nonceInitiator) : nonceInitiator);
// Nr
byte[] nonceResponder = new byte[nonceSize];
System.arraycopy(payload, inputOffset, nonceResponder, 0, nonceSize);
inputOffset += nonceSize;
// g^i
byte[] initiatorExponential = Arrays.copyOfRange(payload, inputOffset, inputOffset+modulusLength);
inputOffset += modulusLength;
// g^r
byte[] responderExponential = Arrays.copyOfRange(payload, inputOffset, inputOffset+modulusLength);
inputOffset += modulusLength;
byte[] authenticator = Arrays.copyOfRange(payload, inputOffset, inputOffset+HASH_LENGTH);
inputOffset += HASH_LENGTH;
// We *WANT* to check the hmac before we do the lookup on the hashmap
// @see https://bugs.freenetproject.org/view.php?id=1604
if(!HMAC.verifyWithSHA256(getTransientKey(), assembleJFKAuthenticator(responderExponential, initiatorExponential, nonceResponder, nonceInitiatorHashed, replyTo.getAddress().getAddress()) , authenticator)) {
if(shouldLogErrorInHandshake(t1)) {
if(logDEBUG) Logger.debug(this, "We received the following HMAC : " + HexUtil.bytesToHex(authenticator));
if(logDEBUG) Logger.debug(this, "We have Ni' : " + HexUtil.bytesToHex(nonceInitiatorHashed));
Logger.normal(this, "The HMAC doesn't match; let's discard the packet (either we rekeyed or we are victim of forgery) - JFK3 - "+pn);
}
return;
}
// Check try to find the authenticator in the cache.
// If authenticator is already present, indicates duplicate/replayed message3
// Now simply transmit the corresponding message4
Object message4 = null;
synchronized (authenticatorCache) {
message4 = authenticatorCache.get(new ByteArrayWrapper(authenticator));
}
if(message4 != null) {
Logger.normal(this, "We replayed a message from the cache (shouldn't happen often) - "+pn);
// We are replaying a JFK(4).
// Therefore if it is anon-initiator it is encrypted with our setup key.
if(unknownInitiator) {
sendAnonAuthPacket(1,negType,3,setupType, (byte[]) message4, null, replyTo, crypto.anonSetupCipher);
} else {
sendAuthPacket(1, negType, 3, (byte[]) message4, pn, replyTo);
}
return;
} else {
if(logDEBUG) Logger.debug(this, "No message4 found for "+HexUtil.bytesToHex(authenticator)+" responderExponential "+Fields.hashCode(responderExponential)+" initiatorExponential "+Fields.hashCode(initiatorExponential)+" nonceResponder "+Fields.hashCode(nonceResponder)+" nonceInitiator "+Fields.hashCode(nonceInitiatorHashed)+" address "+HexUtil.bytesToHex(replyTo.getAddress().getAddress()));
}
byte[] hmac = Arrays.copyOfRange(payload, inputOffset, inputOffset+HASH_LENGTH);
inputOffset += HASH_LENGTH;
byte[] computedExponential;
if(negType < 8) { // Legacy DH
NativeBigInteger _hisExponential = new NativeBigInteger(1, initiatorExponential);
NativeBigInteger _ourExponential = new NativeBigInteger(1, responderExponential);
DiffieHellmanLightContext ctx = findContextByExponential(_ourExponential);
if(ctx == null) {
Logger.error(this, "WTF? the HMAC verified but we don't know about that exponential! SHOULDN'T HAPPEN! - JFK3 - "+pn);
// Possible this is a replay or severely delayed? We don't keep every exponential we ever use.
return;
}
computedExponential = ctx.getHMACKey(_hisExponential);
} else {
ECPublicKey initiatorKey = ECDH.getPublicKey(initiatorExponential, ecdhCurveToUse);
ECPublicKey responderKey = ECDH.getPublicKey(responderExponential, ecdhCurveToUse);
ECDHLightContext ctx = findECDHContextByPubKey(responderKey);
if (ctx == null) {
Logger.error(this, "WTF? the HMAC verified but we don't know about that exponential! SHOULDN'T HAPPEN! - JFK3 - "+pn);
// Possible this is a replay or severely delayed? We don't keep
// every exponential we ever use.
return;
}
computedExponential = ctx.getHMACKey(initiatorKey);
}
if(logDEBUG) Logger.debug(this, "The shared Master secret is : "+HexUtil.bytesToHex(computedExponential) +" for " + pn);
/* 0 is the outgoing key for the initiator, 7 for the responder */
byte[] outgoingKey = computeJFKSharedKey(computedExponential, nonceInitiatorHashed, nonceResponder, "7");
byte[] incommingKey = computeJFKSharedKey(computedExponential, nonceInitiatorHashed, nonceResponder, "0");
byte[] Ke = computeJFKSharedKey(computedExponential, nonceInitiatorHashed, nonceResponder, "1");
byte[] Ka = computeJFKSharedKey(computedExponential, nonceInitiatorHashed, nonceResponder, "2");
byte[] hmacKey = computeJFKSharedKey(computedExponential, nonceInitiatorHashed, nonceResponder, "3");
byte[] ivKey = computeJFKSharedKey(computedExponential, nonceInitiatorHashed, nonceResponder, "4");
byte[] ivNonce = computeJFKSharedKey(computedExponential, nonceInitiatorHashed, nonceResponder, "5");
/* Bytes 1-4: Initial sequence number for the initiator
* Bytes 5-8: Initial sequence number for the responder
* Bytes 9-12: Initial message id for the initiator
* Bytes 13-16: Initial message id for the responder
* Note that we are the responder */
byte[] sharedData = computeJFKSharedKey(computedExponential, nonceInitiatorHashed, nonceResponder, "6");
Arrays.fill(computedExponential, (byte)0);
int theirInitialSeqNum = ((sharedData[0] & 0xFF) << 24)
| ((sharedData[1] & 0xFF) << 16)
| ((sharedData[2] & 0xFF) << 8)
| (sharedData[3] & 0xFF);
int ourInitialSeqNum = ((sharedData[4] & 0xFF) << 24)
| ((sharedData[5] & 0xFF) << 16)
| ((sharedData[6] & 0xFF) << 8)
| (sharedData[7] & 0xFF);
int theirInitialMsgID, ourInitialMsgID;
if(negType >= 7) {
theirInitialMsgID =
unknownInitiator ? getInitialMessageID(crypto.myIdentity) :
getInitialMessageID(pn.identity, crypto.myIdentity);
ourInitialMsgID =
unknownInitiator ? getInitialMessageID(crypto.myIdentity) :
getInitialMessageID(crypto.myIdentity, pn.identity);
} else {
theirInitialMsgID= ((sharedData[8] & 0xFF) << 24)
| ((sharedData[9] & 0xFF) << 16)
| ((sharedData[10] & 0xFF) << 8)
| (sharedData[11] & 0xFF);
ourInitialMsgID= ((sharedData[12] & 0xFF) << 24)
| ((sharedData[13] & 0xFF) << 16)
| ((sharedData[14] & 0xFF) << 8)
| (sharedData[15] & 0xFF);
}
if(logMINOR)
Logger.minor(this, "Their initial message ID: "+theirInitialMsgID+" ours "+ourInitialMsgID);
c.initialize(Ke);
int ivLength = PCFBMode.lengthIV(c);
int decypheredPayloadOffset = 0;
// We compute the HMAC of ("I"+cyphertext) : the cyphertext includes the IV!
byte[] decypheredPayload = Arrays.copyOf(JFK_PREFIX_INITIATOR, JFK_PREFIX_INITIATOR.length + payload.length - inputOffset);
decypheredPayloadOffset += JFK_PREFIX_INITIATOR.length;
System.arraycopy(payload, inputOffset, decypheredPayload, decypheredPayloadOffset, decypheredPayload.length-decypheredPayloadOffset);
if(!HMAC.verifyWithSHA256(Ka, decypheredPayload, hmac)) {
Logger.error(this, "The inner-HMAC doesn't match; let's discard the packet JFK(3) - "+pn);
return;
}
final PCFBMode pk = PCFBMode.create(c, decypheredPayload, decypheredPayloadOffset);
// Get the IV
decypheredPayloadOffset += ivLength;
// Decrypt the payload
pk.blockDecipher(decypheredPayload, decypheredPayloadOffset, decypheredPayload.length-decypheredPayloadOffset);
/*
* DecipheredData Format:
* Signature
* Node Data (starting with BootID)
*/
int sigLength = getSignatureLength(negType);
byte[] sig = new byte[sigLength];
System.arraycopy(decypheredPayload, decypheredPayloadOffset, sig, 0, sigLength);
decypheredPayloadOffset += sigLength;
byte[] data = new byte[decypheredPayload.length - decypheredPayloadOffset];
System.arraycopy(decypheredPayload, decypheredPayloadOffset, data, 0, decypheredPayload.length - decypheredPayloadOffset);
int ptr = 0;
long trackerID;
trackerID = Fields.bytesToLong(data, ptr);
if(trackerID < 0) trackerID = -1;
ptr += 8;
long bootID = Fields.bytesToLong(data, ptr);
ptr += 8;
byte[] hisRef = Arrays.copyOfRange(data, ptr, data.length);
// construct the peernode
if(unknownInitiator) {
pn = getPeerNodeFromUnknownInitiator(hisRef, setupType, pn, replyTo);
}
if(pn == null) {
if(unknownInitiator) {
// Reject
Logger.normal(this, "Rejecting... unable to construct PeerNode");
} else {
Logger.error(this, "PeerNode is null and unknownInitiator is false!");
}
return;
}
// verify the signature
byte[] toVerify = assembleDHParams(nonceInitiatorHashed, nonceResponder, initiatorExponential, responderExponential, crypto.getIdentity(negType, false), data);
if(negType < 9) {
byte[] r = new byte[Node.SIGNATURE_PARAMETER_LENGTH];
System.arraycopy(sig, 0, r, 0, Node.SIGNATURE_PARAMETER_LENGTH);
byte[] s = new byte[Node.SIGNATURE_PARAMETER_LENGTH];
System.arraycopy(sig, Node.SIGNATURE_PARAMETER_LENGTH, s, 0, Node.SIGNATURE_PARAMETER_LENGTH);
DSASignature remoteSignature = new DSASignature(new NativeBigInteger(1,r), new NativeBigInteger(1,s));
if(!DSA.verify(pn.peerPubKey, remoteSignature, new NativeBigInteger(1, SHA256.digest(toVerify)), false)) {
Logger.error(this, "The signature verification has failed!! JFK(3) - "+pn.getPeer());
return;
}
} else {
if(!ECDSA.verify(Curves.P256, pn.peerECDSAPubKey(), sig, toVerify)) {
Logger.error(this, "The ECDSA signature verification has failed!! JFK(3) - "+pn.getPeer());
return;
}
}
// At this point we know it's from the peer, so we can report a packet received.
pn.receivedPacket(true, false);
BlockCipher outgoingCipher = null;
BlockCipher incommingCipher = null;
BlockCipher ivCipher = null;
try {
outgoingCipher = new Rijndael(256, 256);
incommingCipher = new Rijndael(256, 256);
ivCipher = new Rijndael(256, 256);
} catch (UnsupportedCipherException e) {
throw new RuntimeException(e);
}
outgoingCipher.initialize(outgoingKey);
incommingCipher.initialize(incommingKey);