/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright 2006-2012 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.rtmps;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.NotActiveException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.ssl.SslFilter;
import org.red5.server.net.rtmp.RTMPMinaIoHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles Native RTMPS protocol events fired by the MINA framework.
* <pre>
* var nc:NetConnection = new NetConnection();
* nc.proxyType = "best";
* nc.connect("rtmps:\\localhost\app");
* </pre>
* Originally created by: Kevin Green
*
* http://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html
* http://java.sun.com/j2se/1.5.0/docs/guide/security/CryptoSpec.html#AppA
* http://java.sun.com/j2se/1.5.0/docs/api/java/security/KeyStore.html
* http://tomcat.apache.org/tomcat-3.3-doc/tomcat-ssl-howto.html
*
* @author Kevin Green (kevygreen@gmail.com)
* @author Paul Gregoire (mondain@gmail.com)
*/
public class RTMPSMinaIoHandler extends RTMPMinaIoHandler {
private static Logger log = LoggerFactory.getLogger(RTMPSMinaIoHandler.class);
/**
* Password for accessing the keystore.
*/
private char[] password;
/**
* Stores the keystore file bytes.
*/
private byte[] keystore;
/**
* The keystore type, valid options are JKS and PKCS12
*/
private String keyStoreType = "JKS";
/** {@inheritDoc} */
@Override
public void sessionOpened(IoSession session) throws Exception {
if (password == null || keystore == null) {
throw new NotActiveException("Keystore or password are null");
}
// START OF NATIVE SSL STUFF
SSLContext context = SSLContext.getInstance("TLS"); //TLS, TLSv1, TLSv1.1
// The reference implementation only supports X.509 keys
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
//initialize the key manager
kmf.init(getKeyStore(), password);
// initialize the ssl context
context.init(kmf.getKeyManagers(), null, null);
//create the ssl filter using server mode
SslFilter sslFilter = new SslFilter(context);
if (sslFilter != null) {
session.getFilterChain().addFirst("sslFilter", sslFilter);
}
// END OF NATIVE SSL STUFF
super.sessionOpened(session);
}
/** {@inheritDoc} */
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
log.warn("Exception caught {}", cause.getMessage());
if (log.isDebugEnabled()) {
log.error("Exception detail", cause);
}
//if there are any errors using ssl, kill the session
session.close(true);
}
/**
* Returns a KeyStore.
* @return KeyStore
* @throws IOException
* @throws CertificateException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
*/
private KeyStore getKeyStore() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
// Sun's default kind of key store
KeyStore ks = KeyStore.getInstance(keyStoreType);
// For security, every key store is encrypted with a
// pass phrase that must be provided before we can load
// it from disk. The pass phrase is stored as a char[] array
// so it can be wiped from memory quickly rather than
// waiting for a garbage collector. Of course using a string
// literal here completely defeats that purpose.
ks.load(new ByteArrayInputStream(keystore), password);
return ks;
}
/**
* Password used to access the keystore file.
*
* @param password
*/
public void setKeyStorePassword(String password) {
this.password = password.toCharArray();
}
/**
* Set keystore data from a file.
*
* @param path contains keystore
*/
public void setKeystoreFile(String path) {
FileInputStream fis = null;
try {
File file = new File(path);
if (file.exists()) {
fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteBuffer fb = ByteBuffer.allocate(Long.valueOf(file.length()).intValue());
fc.read(fb);
fb.flip();
keystore = IoBuffer.wrap(fb).array();
} else {
log.warn("Keystore file does not exist: {}", path);
}
file = null;
} catch (Exception e) {
log.warn("Error setting keystore data", e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}
}
/**
* Set keystore data from a file.
*
* @param arr keystore bytes
*/
public void setKeystoreBytes(byte[] arr) {
keystore = new byte[arr.length];
System.arraycopy(arr, 0, keystore, 0, arr.length);
}
/**
* Set the key store type, JKS or PKCS12.
*
* @param keyStoreType
*/
public void setKeyStoreType(String keyStoreType) {
this.keyStoreType = keyStoreType;
}
}