package javaforce.flash;
//package org.red5.server.net.rtmp;
/*
based on Red5 1.0RC1
modifed by Peter Quiring for JavaForce SDK
*/
import java.security.KeyPair;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Hex;
//import org.apache.mina.core.buffer.IoBuffer;
//import org.red5.server.api.Red5;
//import org.red5.server.net.rtmp.message.Constants;
import javaforce.JFLog;
/**
* Performs handshaking for server connections.
*
* @author Paul Gregoire
*/
class InboundHandshake extends RTMPHandshake {
private byte input[];
private int input_pos;
private int input_saved_pos;
private void input_mark() { input_saved_pos = input_pos; }
private void input_reset() { input_pos = input_saved_pos; }
private byte input_get() {
return input[input_pos++];
}
private void input_get(byte buf[]) {
input_get(buf, 0, buf.length);
}
private void input_get(byte buf[], int pos, int len) {
int avail = input.length - input_pos;
if (len > avail) len = avail;
System.arraycopy(input, input_pos, buf, pos, len);
input_pos += len;
}
private int input_remaining() { return input.length - input_pos; }
private void input_position(int newpos) { input_pos = newpos; }
private int input_limit() {return input.length - input_pos; }
private byte output[];
private int output_pos;
private void output_put(byte data) {
output[output_pos++] = data;
}
private void output_put(byte data[]) {
System.arraycopy(data, 0, output, output_pos, data.length);
output_pos += data.length;
}
private void output_putInt(int val) {
output[output_pos+3] = (byte)(val & 0xff);
val >>= 8;
output[output_pos+2] = (byte)(val & 0xff);
val >>= 8;
output[output_pos+1] = (byte)(val & 0xff);
val >>= 8;
output[output_pos+0] = (byte)(val & 0xff);
output_pos += 4;
}
private void output_flip() { } //???
/**
* Marker byte for standard or non-encrypted RTMP data.
*/
public static final byte RTMP_NON_ENCRYPTED = (byte) 0x03;
/**
* Marker byte for encrypted RTMP data.
*/
public static final byte RTMP_ENCRYPTED = (byte) 0x06;
/**
* Generates response for versioned connections.
*
* @param input incoming RTMP bytes (1537 bytes)
* @return outgoing handshake (1537 bytes)
*/
public synchronized byte[] doHandshake(byte input[]) {
this.input = input;
input_pos = 0;
byte versionByte = input_get();
if (versionByte == 0) {
JFLog.log("unversioned response");
return generateUnversionedHandshake(input);
}
//JFLog.log("versioned response" + versionByte);
//create output buffer
output = new byte[HANDSHAKE_SIZE_SERVER];
output_pos = 0;
input_mark();
//make sure this is a client we can communicate with
validate(input);
input_reset();
input_mark();
//create all the dh stuff and add to handshake bytes
prepareResponse(input);
input_reset();
if (handshakeType == RTMP_ENCRYPTED) {
byte[] sharedSecret = getSharedSecret(outgoingPublicKey, keyAgreement);
// create output cipher
byte[] digestOut = calculateHMAC_SHA256(outgoingPublicKey, sharedSecret);
try {
cipherOut = Cipher.getInstance("RC4");
cipherOut.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(digestOut, 0, 16, "RC4"));
} catch (Exception e) {
JFLog.log("Encryption cipher creation failed" + e);
}
// create input cipher
byte[] digestIn = calculateHMAC_SHA256(incomingPublicKey, sharedSecret);
try {
cipherIn = Cipher.getInstance("RC4");
cipherIn.init(Cipher.DECRYPT_MODE, new SecretKeySpec(digestIn, 0, 16, "RC4"));
} catch (Exception e) {
JFLog.log("Decryption cipher creation failed" + e);
}
// update 'encoder / decoder state' for the RC4 keys
// both parties *pretend* as if handshake part 2 (1536 bytes) was encrypted
// effectively this hides / discards the first few bytes of encrypted session
// which is known to increase the secure-ness of RC4
// RC4 state is just a function of number of bytes processed so far
// that's why we just run 1536 arbitrary bytes through the keys below
byte[] dummyBytes = new byte[Constants.HANDSHAKE_SIZE];
cipherIn.update(dummyBytes);
cipherOut.update(dummyBytes);
}
input_mark();
//create the server digest
int serverDigestOffset = getDigestOffset(handshakeBytes);
byte[] tempBuffer = new byte[Constants.HANDSHAKE_SIZE - DIGEST_LENGTH];
System.arraycopy(handshakeBytes, 0, tempBuffer, 0, serverDigestOffset);
System.arraycopy(handshakeBytes, serverDigestOffset + DIGEST_LENGTH, tempBuffer, serverDigestOffset, Constants.HANDSHAKE_SIZE - serverDigestOffset - DIGEST_LENGTH);
//calculate the hash
byte[] tempHash = calculateHMAC_SHA256(tempBuffer, GENUINE_FMS_KEY, 36);
//add the digest
System.arraycopy(tempHash, 0, handshakeBytes, serverDigestOffset, DIGEST_LENGTH);
//compute the challenge digest
byte[] inputBuffer = new byte[Constants.HANDSHAKE_SIZE - DIGEST_LENGTH];
input_get(inputBuffer);
int keyChallengeIndex = getDigestOffset(inputBuffer);
byte[] challengeKey = new byte[DIGEST_LENGTH];
input_position(keyChallengeIndex);
input_get(challengeKey, 0, DIGEST_LENGTH);
input_reset();
//compute key
tempHash = calculateHMAC_SHA256(challengeKey, GENUINE_FMS_KEY, 68);
//generate hash
byte[] randBytes = new byte[Constants.HANDSHAKE_SIZE - DIGEST_LENGTH];
random.nextBytes(randBytes);
byte[] lastHash = calculateHMAC_SHA256(randBytes, tempHash, DIGEST_LENGTH);
//set handshake with encryption type
output_put(handshakeType);
output_put(handshakeBytes);
output_put(randBytes);
output_put(lastHash);
output_flip();
return output;
}
/**
* Generates response for non-versioned connections, such as those before FP9.
*
* @param input incoming RTMP bytes
* @return outgoing handshake
*/
private byte[] generateUnversionedHandshake(byte input[]) {
//save resource by only doing this after the first request
if (HANDSHAKE_PAD_BYTES == null) {
HANDSHAKE_PAD_BYTES = new byte[Constants.HANDSHAKE_SIZE - 4];
//fill pad bytes
Arrays.fill(HANDSHAKE_PAD_BYTES, (byte) 0x00);
}
byte output[] = new byte[HANDSHAKE_SIZE_SERVER];
//non-encrypted
output_put(RTMP_NON_ENCRYPTED);
//set server uptime in seconds
output_putInt(0x01); //(int) Red5.getUpTime() / 1000); //0x01
output_put(RTMPHandshake.HANDSHAKE_PAD_BYTES);
output_put(input);
output_flip();
return output;
}
/**
* Creates the servers handshake bytes
*/
@Override
protected void createHandshakeBytes() {
handshakeBytes = new byte[Constants.HANDSHAKE_SIZE];
//timestamp
handshakeBytes[0] = 0;
handshakeBytes[1] = 0;
handshakeBytes[2] = 0;
handshakeBytes[3] = 0;
//version (0x01020304)
handshakeBytes[4] = 1;
handshakeBytes[5] = 2;
handshakeBytes[6] = 3;
handshakeBytes[7] = 4;
//fill the rest with random bytes
byte[] rndBytes = new byte[Constants.HANDSHAKE_SIZE - 8];
random.nextBytes(rndBytes);
//copy random bytes into our handshake array
System.arraycopy(rndBytes, 0, handshakeBytes, 8, (Constants.HANDSHAKE_SIZE - 8));
}
/**
* Gets the DH offset in the handshake bytes array based on validation scheme
* Generates DH keypair
* Adds public key to handshake bytes
* @param input
*/
private void prepareResponse(byte input[]) {
//put the clients input into a byte array
byte[] inputBuffer = new byte[input_limit()];
input_get(inputBuffer);
//get the clients dh offset
int clientDHOffset = getDHOffset(inputBuffer);
//get the clients public key
outgoingPublicKey = new byte[KEY_LENGTH];
System.arraycopy(inputBuffer, clientDHOffset, outgoingPublicKey, 0, KEY_LENGTH);
//get the servers dh offset
int serverDHOffset = getDHOffset(handshakeBytes);
//create keypair
KeyPair keys = generateKeyPair();
//get public key
incomingPublicKey = getPublicKey(keys);
//add to handshake bytes
System.arraycopy(incomingPublicKey, 0, handshakeBytes, serverDHOffset, KEY_LENGTH);
}
/**
* Determines the validation scheme for given input.
*
* @param input
* @return true if client used a supported validation scheme, false if unsupported
*/
@Override
public boolean validate(byte input[]) {
byte[] pBuffer = new byte[input_remaining()];
//put all the input bytes into our buffer
input_get(pBuffer, 0, input_remaining());
if (validateScheme(pBuffer, 0)) {
validationScheme = 0;
return true;
}
if (validateScheme(pBuffer, 1)) {
validationScheme = 1;
return true;
}
return false;
}
private boolean validateScheme(byte[] pBuffer, int scheme) {
int digestOffset = -1;
switch (scheme) {
case 0:
digestOffset = getDigestOffset0(pBuffer);
break;
case 1:
digestOffset = getDigestOffset1(pBuffer);
break;
default:
}
byte[] tempBuffer = new byte[Constants.HANDSHAKE_SIZE - DIGEST_LENGTH];
System.arraycopy(pBuffer, 0, tempBuffer, 0, digestOffset);
System.arraycopy(pBuffer, digestOffset + DIGEST_LENGTH, tempBuffer, digestOffset, Constants.HANDSHAKE_SIZE - digestOffset - DIGEST_LENGTH);
byte[] tempHash = calculateHMAC_SHA256(tempBuffer, GENUINE_FP_KEY, 30);
boolean result = true;
for (int i = 0; i < DIGEST_LENGTH; i++) {
//log.trace("Digest: {} Temp: {}", (pBuffer[digestOffset + i] & 0x0ff), (tempHash[i] & 0x0ff));
if (pBuffer[digestOffset + i] != tempHash[i]) {
result = false;
break;
}
}
return result;
}
}