All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.red5.server.net.rtmp.InboundHandshake Maven / Gradle / Ivy

Go to download

Ant Media Server supports RTMP, RTSP, MP4, HLS, WebRTC, Adaptive Streaming, etc.

There is a newer version: 2.10.0
Show newest version
/*
 * RED5 Open Source Media Server - https://github.com/Red5/
 * 
 * Copyright 2006-2016 by respective authors (see below). All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.red5.server.net.rtmp;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.KeyPair;
import java.util.Arrays;

import org.apache.commons.codec.binary.Hex;
import org.apache.mina.core.buffer.IoBuffer;
import org.bouncycastle.util.BigIntegers;
import org.red5.server.api.Red5;
import org.red5.server.net.rtmp.message.Constants;
import org.slf4j.LoggerFactory;

/**
 * Performs handshaking for server connections.
 * 
 * @author Paul Gregoire
 */
public class InboundHandshake extends RTMPHandshake {

    // server initial response S1
    private byte[] s1;

    // client initial request C1
    private byte[] c1;

    // position for the server digest in S1
    private int digestPosServer;

    private boolean unvalidatedConnectionAllowed;

    public InboundHandshake() {
        super(RTMPConnection.RTMP_NON_ENCRYPTED);
        log = LoggerFactory.getLogger(InboundHandshake.class);
    }

    public InboundHandshake(byte handshakeType) {
        super(handshakeType);
        log = LoggerFactory.getLogger(InboundHandshake.class);
    }

    public InboundHandshake(byte handshakeType, int algorithm) {
        this(handshakeType);
        this.algorithm = algorithm;
    }

    /**
     * Generates response for versioned connections.
     * 
     * @param in
     *            incoming RTMP handshake bytes
     * @return outgoing handshake
     */
    public IoBuffer doHandshake(IoBuffer in) {
        if (log.isTraceEnabled()) {
            log.trace("doHandshake: {}", in);
        }
        return decodeClientRequest1(in);
    }

    /**
     * Decodes the first client request (C1) and returns a server response (S0S1).
     * 
     * 
     * C1 = 1536 bytes from the client
     * S0 = 0x03 (server handshake type - 0x03, 0x06, 0x08, or 0x09)
     * S1 = 1536 bytes from server
     * 
* * @param in * incoming handshake C1 * @return server response S0+S1 */ public IoBuffer decodeClientRequest1(IoBuffer in) { if (log.isTraceEnabled()) { log.debug("decodeClientRequest1: {}", Hex.encodeHexString(in.array())); } if (in.hasArray()) { c1 = in.array(); } else { c1 = new byte[Constants.HANDSHAKE_SIZE]; in.get(c1); } //if (log.isTraceEnabled()) { // log.trace("C1: {}", Hex.encodeHexString(c1)); //} // holder for S1 s1 = new byte[Constants.HANDSHAKE_SIZE]; if (log.isDebugEnabled()) { log.debug("Flash player version {}", Hex.encodeHexString(Arrays.copyOfRange(c1, 4, 8))); } // check for un-versioned handshake fp9Handshake = (c1[4] & 0xff) != 0; if (!fp9Handshake) { return generateUnversionedHandshake(c1); } // make sure this is a client we can communicate with //if (validate(c1)) { // log.debug("Valid RTMP client detected, algorithm: {}", algorithm); //} else { // log.info("Invalid RTMP connection data detected, you may experience errors"); //} if (log.isTraceEnabled()) { log.debug("Server handshake bytes: {}", Hex.encodeHexString(handshakeBytes)); } // handle encryption setup if (useEncryption()) { // start off with algorithm 1 if we're type 6, 8, or 9 algorithm = 1; // set to xtea type 8 if client is fp10 capable //if (clientVersionByte == 128) { // handshakeType = 8; //} // get the DH offset in the handshake bytes, generates DH keypair, and adds the public key to handshake bytes int clientDHOffset = getDHOffset(algorithm, c1, 0); log.trace("Incoming DH offset: {}", clientDHOffset); // get the clients public key outgoingPublicKey = new byte[KEY_LENGTH]; System.arraycopy(c1, clientDHOffset, outgoingPublicKey, 0, KEY_LENGTH); log.debug("Client public key: {}", Hex.encodeHexString(outgoingPublicKey)); // get the servers dh offset int serverDHOffset = getDHOffset(algorithm, handshakeBytes, 0); log.trace("Outgoing DH offset: {}", serverDHOffset); // create keypair KeyPair keys = generateKeyPair(); // get public key incomingPublicKey = getPublicKey(keys); log.debug("Server public key: {}", Hex.encodeHexString(incomingPublicKey)); // add to handshake bytes System.arraycopy(incomingPublicKey, 0, handshakeBytes, serverDHOffset, KEY_LENGTH); // create the RC4 ciphers initRC4Encryption(getSharedSecret(outgoingPublicKey, keyAgreement)); } // create the server digest digestPosServer = getDigestOffset(algorithm, handshakeBytes, 0); log.debug("Server digest position offset: {} algorithm: {}", digestPosServer, algorithm); System.arraycopy(handshakeBytes, 0, s1, 0, Constants.HANDSHAKE_SIZE); // calculate the server hash and add to the handshake bytes (S1) calculateDigest(digestPosServer, handshakeBytes, 0, GENUINE_FMS_KEY, 36, s1, digestPosServer); log.debug("Server digest: {}", Hex.encodeHexString(Arrays.copyOfRange(s1, digestPosServer, digestPosServer + DIGEST_LENGTH))); // get the client digest log.trace("Trying algorithm: {}", algorithm); int digestPosClient = getDigestOffset(algorithm, c1, 0); log.debug("Client digest position offset: {}", digestPosClient); if (!verifyDigest(digestPosClient, c1, GENUINE_FP_KEY, 30)) { // try a different position algorithm ^= 1; log.trace("Trying algorithm: {}", algorithm); digestPosClient = getDigestOffset(algorithm, c1, 0); log.debug("Client digest position offset: {}", digestPosClient); if (!verifyDigest(digestPosClient, c1, GENUINE_FP_KEY, 30)) { log.warn("Client digest verification failed"); return null; } } // digest verification passed log.debug("Client digest: {}", Hex.encodeHexString(Arrays.copyOfRange(c1, digestPosClient, digestPosClient + DIGEST_LENGTH))); // swfVerification bytes are the sha256 hmac hash of the decompressed swf, the key is the last 32 bytes of the server handshake if (swfSize > 0) { // how in the heck do we generate a hash for a swf when we dont know which one is requested byte[] swfHash = new byte[DIGEST_LENGTH]; calculateSwfVerification(s1, swfHash, swfSize); log.debug("Swf digest: {}", Hex.encodeHexString(swfHash)); } // calculate the response byte[] digestResp = new byte[DIGEST_LENGTH]; byte[] signatureResponse = new byte[DIGEST_LENGTH]; // compute digest key calculateHMAC_SHA256(c1, digestPosClient, DIGEST_LENGTH, GENUINE_FMS_KEY, GENUINE_FMS_KEY.length, digestResp, 0); log.debug("Digest response (key): {}", Hex.encodeHexString(digestResp)); calculateHMAC_SHA256(c1, 0, (Constants.HANDSHAKE_SIZE - DIGEST_LENGTH), digestResp, DIGEST_LENGTH, signatureResponse, 0); log.debug("Signature response: {}", Hex.encodeHexString(signatureResponse)); if (useEncryption()) { switch (handshakeType) { case RTMPConnection.RTMP_ENCRYPTED_XTEA: log.debug("RTMPE type 8 XTEA"); // encrypt signatureResp for (int i = 0; i < DIGEST_LENGTH; i += 8) { //encryptXtea(signatureResp, i, digestResp[i] % 15); } break; case RTMPConnection.RTMP_ENCRYPTED_BLOWFISH: log.debug("RTMPE type 9 Blowfish"); // encrypt signatureResp for (int i = 0; i < DIGEST_LENGTH; i += 8) { //encryptBlowfish(signatureResp, i, digestResp[i] % 15); } break; } } // copy signature into C1 as S2 System.arraycopy(signatureResponse, 0, c1, (Constants.HANDSHAKE_SIZE - DIGEST_LENGTH), DIGEST_LENGTH); // create output buffer for S0+S1+S2 IoBuffer s0s1s2 = IoBuffer.allocate(Constants.HANDSHAKE_SIZE * 2 + 1); // 3073 // set handshake with encryption type s0s1s2.put(handshakeType); // 1 s0s1s2.put(s1); // 1536 s0s1s2.put(c1); // 1536 s0s1s2.flip(); // clear original base bytes handshakeBytes = null; if (log.isTraceEnabled()) { log.trace("S0+S1+S2 size: {}", s0s1s2.limit()); } return s0s1s2; } /** * Decodes the second client request (C2) and returns a server response (S2). * *
     * C2 = Copy of S1 bytes
     * S2 = Copy of C1 bytes
     * 
* * @param in * incoming handshake C2 * @return true if C2 was processed successfully and false otherwise */ public boolean decodeClientRequest2(IoBuffer in) { if (log.isTraceEnabled()) { log.debug("decodeClientRequest2: {}", Hex.encodeHexString(in.array())); } byte[] c2; if (in.hasArray()) { c2 = in.array(); } else { c2 = new byte[Constants.HANDSHAKE_SIZE]; in.get(c2); } if (fp9Handshake) { // client signature c2[HANDSHAKE_SIZE - DIGEST_LENGTH] byte[] digest = new byte[DIGEST_LENGTH]; byte[] signature = new byte[DIGEST_LENGTH]; log.debug("Client sent signature: {}", Hex.encodeHexString(Arrays.copyOfRange(c2, (Constants.HANDSHAKE_SIZE - DIGEST_LENGTH), (Constants.HANDSHAKE_SIZE - DIGEST_LENGTH) + DIGEST_LENGTH))); // verify client response calculateHMAC_SHA256(s1, digestPosServer, DIGEST_LENGTH, GENUINE_FP_KEY, GENUINE_FP_KEY.length, digest, 0); calculateHMAC_SHA256(c2, 0, Constants.HANDSHAKE_SIZE - DIGEST_LENGTH, digest, DIGEST_LENGTH, signature, 0); if (useEncryption()) { switch (handshakeType) { case RTMPConnection.RTMP_ENCRYPTED_XTEA: log.debug("RTMPE type 8 XTEA"); // encrypt signature for (int i = 0; i < DIGEST_LENGTH; i += 8) { //encryptXtea(signature, i, digest[i] % 15); } break; case RTMPConnection.RTMP_ENCRYPTED_BLOWFISH: log.debug("RTMPE type 9 Blowfish"); // encrypt signature for (int i = 0; i < DIGEST_LENGTH; i += 8) { //encryptBlowfish(signature, i, digest[i] % 15); } break; } // 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); } // show some information if (log.isDebugEnabled()) { log.debug("Digest key: {}", Hex.encodeHexString(digest)); log.debug("Signature calculated: {}", Hex.encodeHexString(signature)); } byte[] sentSignature = Arrays.copyOfRange(c2, (Constants.HANDSHAKE_SIZE - DIGEST_LENGTH), (Constants.HANDSHAKE_SIZE - DIGEST_LENGTH) + DIGEST_LENGTH); if (log.isDebugEnabled()) { log.debug("Client sent signature: {}", Hex.encodeHexString(sentSignature)); } if (!Arrays.equals(signature, sentSignature)) { log.warn("Client not compatible"); if (unvalidatedConnectionAllowed) { // accept and unvalidated handshake; used to deal with ffmpeg log.debug("Unvalidated client allowed to proceed"); return true; } else { return false; } } else { log.debug("Compatible client, handshake complete"); } } else { if (!Arrays.equals(s1, c2)) { log.info("Client signature doesn't match!"); } } return true; } /** * Generates response for non-versioned connections, such as those before FP9. * * @param input * incoming RTMP bytes * @return outgoing handshake */ private IoBuffer generateUnversionedHandshake(byte[] input) { log.debug("Using old style (un-versioned) handshake"); IoBuffer output = IoBuffer.allocate((Constants.HANDSHAKE_SIZE * 2) + 1); // 3073 // non-encrypted output.put(RTMPConnection.RTMP_NON_ENCRYPTED); // set server uptime in seconds output.putInt((int) Red5.getUpTime() / 1000); //0x01 output.position(Constants.HANDSHAKE_SIZE + 1); output.put(input); output.flip(); // fill S1 with handshake data (nearly all 0's) output.mark(); output.position(1); output.get(s1); output.reset(); return output; } /** * Creates the servers handshake bytes */ @Override protected void createHandshakeBytes() { handshakeBytes = new byte[Constants.HANDSHAKE_SIZE]; // timestamp int time = (int) (Red5.getUpTime() / 1000); handshakeBytes[0] = (byte) (time >>> 24); handshakeBytes[1] = (byte) (time >>> 16); handshakeBytes[2] = (byte) (time >>> 8); handshakeBytes[3] = (byte) time; // version 4 handshakeBytes[4] = 4; handshakeBytes[5] = 0; handshakeBytes[6] = 0; handshakeBytes[7] = 1; // fill the rest with random bytes int randomHandshakeLength = (Constants.HANDSHAKE_SIZE - 8); BigInteger bi = new BigInteger((randomHandshakeLength * 8), random); byte[] rndBytes = BigIntegers.asUnsignedByteArray(bi); // prevent AOOB error that can occur, sometimes if (rndBytes.length == randomHandshakeLength) { // copy random bytes into our handshake array System.arraycopy(rndBytes, 0, handshakeBytes, 8, randomHandshakeLength); } else { // copy random bytes into our handshake array ByteBuffer b = ByteBuffer.allocate(randomHandshakeLength); b.put(rndBytes); b.put((byte) 0x69); b.flip(); // copy mostly random bytes into our handshake array System.arraycopy(b.array(), 0, handshakeBytes, 8, randomHandshakeLength); } } /** * Determines the validation scheme for given input. * * @param handshake * handshake bytes from the client * @return true if client used a supported validation scheme, false if unsupported */ @Override public boolean validate(byte[] handshake) { if (validateScheme(handshake, 0)) { algorithm = 0; return true; } if (validateScheme(handshake, 1)) { algorithm = 1; return true; } log.error("Unable to validate client"); return false; } private boolean validateScheme(byte[] handshake, int scheme) { int digestOffset = -1; switch (scheme) { case 0: digestOffset = getDigestOffset1(handshake, 0); break; case 1: digestOffset = getDigestOffset2(handshake, 0); break; default: log.error("Unknown algorithm: {}", scheme); } log.debug("Algorithm: {} digest offset: {}", scheme, digestOffset); byte[] tempBuffer = new byte[Constants.HANDSHAKE_SIZE - DIGEST_LENGTH]; System.arraycopy(handshake, 0, tempBuffer, 0, digestOffset); System.arraycopy(handshake, digestOffset + DIGEST_LENGTH, tempBuffer, digestOffset, Constants.HANDSHAKE_SIZE - digestOffset - DIGEST_LENGTH); byte[] tempHash = new byte[DIGEST_LENGTH]; calculateHMAC_SHA256(tempBuffer, 0, tempBuffer.length, GENUINE_FP_KEY, 30, tempHash, 0); log.debug("Hash: {}", Hex.encodeHexString(tempHash)); boolean result = true; for (int i = 0; i < DIGEST_LENGTH; i++) { if (handshake[digestOffset + i] != tempHash[i]) { result = false; break; } } return result; } public void setHandshakeBytes(byte[] handshake) { this.handshakeBytes = handshake; } public byte[] getHandshakeBytes() { return s1; } public void setUnvalidatedConnectionAllowed(boolean unvalidatedConnectionAllowed) { this.unvalidatedConnectionAllowed = unvalidatedConnectionAllowed; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy