Package

Source Code of RTSPResponder

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.security.KeyPair;
import java.security.Security;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.crypto.Cipher;
import java.security.MessageDigest;
import java.math.BigInteger;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;


/**
* An primitive RTSP responder for replying iTunes
* @author bencall
*
*/
public class RTSPResponder extends Thread {

  private Socket socket;          // Connected socket
  private int[] fmtp;
  private byte[] aesiv, aeskey;      // ANNOUNCE request infos
  private AudioServer serv;         // Audio listener
  byte[] hwAddr;
  private BufferedReader in;
  private String password;
  private RTSPResponse response;
 
  // Pre-define patterns
  private static final Pattern authPattern = Pattern.compile("Digest username=\"(.*)\", realm=\"(.*)\", nonce=\"(.*)\", uri=\"(.*)\", response=\"(.*)\"");
  private static final Pattern completedPacket = Pattern.compile("(.*)\r\n\r\n");

  private static final String key = 
    "-----BEGIN RSA PRIVATE KEY-----\n"
    +"MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n"
    +"wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n"
    +"wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n"
    +"/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n"
    +"UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n"
    +"BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n"
    +"LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n"
    +"NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n"
    +"lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n"
    +"aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n"
    +"a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n"
    +"oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n"
    +"oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n"
    +"k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n"
    +"AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n"
    +"cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
    +"54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n"
    +"17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n"
    +"1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n"
    +"LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n"
    +"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKaXTyY=\n"
    +"-----END RSA PRIVATE KEY-----\n";

  public RTSPResponder(byte[] hwAddr, Socket socket) throws IOException {
    this.hwAddr = hwAddr;
    this.socket = socket;
    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  }

  public RTSPResponder(byte[] hwAddr, Socket socket, String pass) throws IOException {
    this.hwAddr = hwAddr;
    this.socket = socket;
    this.password = pass;
    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  }

  public RTSPResponse handlePacket(RTSPPacket packet) {

    if(password == null) {
      // No pass = ok!
      response = new RTSPResponse("RTSP/1.0 200 OK");
      response.append("Audio-Jack-Status", "connected; type=analog");
      response.append("CSeq", packet.valueOfHeader("CSeq"));
    } else {
      // Default response (deny, deny, deny!)
      response = new RTSPResponse("RTSP/1.0 401 UNAUTHORIZED");
      response.append("WWW-Authenticate", "Digest realm=\"*\" nonce=\"*\"");
      response.append("Method", "DENIED");

      String authRaw = packet.valueOfHeader("Authorization");
     
      // If supplied, check response
      if(authRaw != null) {
            Matcher auth = authPattern.matcher(authRaw);
         
            if (auth.find()) {
               String username = auth.group(1);
                 String realm = auth.group(2);
                 String nonce = auth.group(3);
               String uri = auth.group(4);
               String resp = auth.group(5);
                  String method = packet.getReq();
                 
                  String hash1 = md5Hash(username+":"+realm+":"+password).toUpperCase();
                  String hash2 = md5Hash(method+":"+uri).toUpperCase();
                  String hash = md5Hash(hash1+":"+nonce+":"+hash2).toUpperCase();
                 
                  // Check against password
                  if(hash.equals(resp)) {
            // Success!
                    response = new RTSPResponse("RTSP/1.0 200 OK");
            response.append("Audio-Jack-Status", "connected; type=analog");
            response.append("CSeq", packet.valueOfHeader("CSeq"));
                  }
            }
      }
    }
       
       
       
    // Apple Challenge-Response field if needed
      String challenge;
      if( (challenge = packet.valueOfHeader("Apple-Challenge")) != null){
        // BASE64 DECODE
        byte[] decoded = Base64.decodeBase64(challenge);

        // IP byte array
        //byte[] ip = socket.getLocalAddress().getAddress();
        SocketAddress localAddress = socket.getLocalSocketAddress(); //.getRemoteSocketAddress();
               
        byte[] ip =  ((InetSocketAddress) localAddress).getAddress().getAddress();
       
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // Challenge
        try {
        out.write(decoded);
        // IP-Address
        out.write(ip);
        // HW-Addr
        out.write(hwAddr);

        // Pad to 32 Bytes
        int padLen = 32 - out.size();
        for(int i = 0; i < padLen; ++i) {
          out.write(0x00);
        }

      } catch (IOException e) {
        e.printStackTrace();
      }

        
        // RSA
        byte[] crypted = this.encryptRSA(out.toByteArray());
       
        // Encode64
        String ret = Base64.encodeBase64String(crypted);
       
        // On retire les ==
          ret = ret.replace("=", "").replace("\r", "").replace("\n", "");

        // Write
          response.append("Apple-Response", ret);
      }
     
    // Paquet request
    String REQ = packet.getReq();
        if(REQ.contentEquals("OPTIONS")){
          // The response field
          response.append("Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");

        } else if (REQ.contentEquals("ANNOUNCE")){
          // Nothing to do here. Juste get the keys and values
          Pattern p = Pattern.compile("^a=([^:]+):(.+)", Pattern.MULTILINE);
          Matcher m = p.matcher(packet.getContent());
          while(m.find()){
            if(m.group(1).contentEquals("fmtp")){
              // Parse FMTP as array
              String[] temp = m.group(2).split(" ");
              fmtp = new int[temp.length];
              for (int i = 0; i< temp.length; i++){
                fmtp[i] = Integer.valueOf(temp[i]);
              }
             
            } else if(m.group(1).contentEquals("rsaaeskey")){
              aeskey = this.decryptRSA(Base64.decodeBase64(m.group(2)));
            } else if(m.group(1).contentEquals("aesiv")){
              aesiv = Base64.decodeBase64(m.group(2));
            }
          }
         
        } else if (REQ.contentEquals("SETUP")){
          int controlPort = 0;
          int timingPort = 0;
         
          String value = packet.valueOfHeader("Transport");         
         
          // Control port
          Pattern p = Pattern.compile(";control_port=(\\d+)");
          Matcher m = p.matcher(value);
          if(m.find()){
            controlPort =  Integer.valueOf(m.group(1));
          }
         
          // Timing port
          p = Pattern.compile(";timing_port=(\\d+)");
          m = p.matcher(value);
          if(m.find()){
            timingPort =  Integer.valueOf(m.group(1));
          }
           
          // Launching audioserver
      serv = new AudioServer(new AudioSession(aesiv, aeskey, fmtp, controlPort, timingPort));

          response.append("Transport", packet.valueOfHeader("Transport") + ";server_port=" + serv.getServerPort());
             
          // ??? Why ???
          response.append("Session", "DEADBEEF");
        } else if (REQ.contentEquals("RECORD")){
//          Headers 
//          Range: ntp=0-
//          RTP-Info: seq={Note 1};rtptime={Note 2}
//          Note 1: Initial value for the RTP Sequence Number, random 16 bit value
//          Note 2: Initial value for the RTP Timestamps, random 32 bit value

        } else if (REQ.contentEquals("FLUSH")){
          serv.flush();
       
        } else if (REQ.contentEquals("TEARDOWN")){
          response.append("Connection", "close");
         
        } else if (REQ.contentEquals("SET_PARAMETER")){
          // Timing port
          Pattern p = Pattern.compile("volume: (.+)");
          Matcher m = p.matcher(packet.getContent());
          if(m.find()){
                double volume = (double) Math.pow(10.0,0.05*Double.parseDouble(m.group(1)));
                serv.setVolume(65536.0 * volume);
          }
         
        } else {
          System.out.println("REQUEST(" + REQ + "): Not Supported Yet!");
          System.out.println(packet.getRawPacket());
        }
       
      // We close the response
      response.finalize();
      return response;
  }

  /**
   * Generates md5 hash of a string.
   * @param plaintext string
   * @return hash string
   */
  public String md5Hash(String plaintext) {
    String hashtext = "";
   
    try {
      MessageDigest md = MessageDigest.getInstance("MD5");
      md.update(plaintext.getBytes());
      byte[] digest = md.digest();
   
      BigInteger bigInt = new BigInteger(1,digest);
      hashtext = bigInt.toString(16);
   
      // Now we need to zero pad it if you actually want the full 32 chars.
      while(hashtext.length() < 32 ) {
          hashtext = "0"+hashtext;
      }
    } catch(java.security.NoSuchAlgorithmException e) {
      //
    }
   
    return hashtext;
  }

  /**
   * Crypts with private key
   * @param array  data to encrypt
   * @return encrypted data
   */
  public byte[] encryptRSA(byte[] array){
    try{
      Security.addProvider(new BouncyCastleProvider());

          PEMReader pemReader = new PEMReader(new StringReader(key));
          KeyPair pObj = (KeyPair) pemReader.readObject();

          // Encrypt
          Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
          cipher.init(Cipher.ENCRYPT_MODE, pObj.getPrivate());
          return cipher.doFinal(array);

    }catch(Exception e){
      e.printStackTrace();
    }

    return null;
  }

  /**
   * Decrypt with RSA priv key
   * @param array
   * @return
   */
  public byte[] decryptRSA(byte[] array){
    try{
      Security.addProvider(new BouncyCastleProvider());

      // La clef RSA
          PEMReader pemReader = new PEMReader(new StringReader(key));
          KeyPair pObj = (KeyPair) pemReader.readObject();

          // Encrypt
          Cipher cipher = Cipher.getInstance("RSA/NONE/OAEPPadding");
          cipher.init(Cipher.DECRYPT_MODE, pObj.getPrivate());
          return cipher.doFinal(array);

    }catch(Exception e){
      e.printStackTrace();
    }

    return null;
  }

  /**
   * Thread to listen packets
   */
  public void run() {
    try {
      do {
        System.out.println("listening packets ... ");
        // feed buffer until packet completed
        StringBuffer packet = new StringBuffer();
        int ret = 0;
        do {
          char[] buffer = new char[4096];
          ret = in.read(buffer);
          packet.append(new String(buffer));
        } while (ret!=-1 && !completedPacket.matcher(packet.toString()).find());
       
        if (ret!=-1) {
          // We handle the packet
          RTSPPacket request = new RTSPPacket(packet.toString());
          RTSPResponse response = this.handlePacket(request);   
          System.out.println(request.toString())
          System.out.println(response.toString());
   
            // Write the response to the wire
            try {     
              BufferedWriter oStream = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
              oStream.write(response.getRawPacket());
              oStream.flush();
          } catch (IOException e) {
            e.printStackTrace();
          }
         
            if("TEARDOWN".equals(request.getReq())){
              socket.close();
              socket = null;
            }
        } else {
            socket.close();
            socket = null;
        }
      } while (socket!=null);
     
    } catch (IOException e) {
      e.printStackTrace();
     
    } finally {
      try {
        if (in!=null) in.close();
      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        try {
          if (socket!=null) socket.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    System.out.println("connection ended.");
  }
   
}
TOP

Related Classes of RTSPResponder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.