/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.vfs;
import com.caucho.config.ConfigException;
import com.caucho.env.service.RootDirectorySystem;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.util.IoUtil;
import com.caucho.util.L10N;
import javax.annotation.PostConstruct;
import javax.crypto.*;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.logging.*;
import java.security.*;
import java.security.cert.Certificate;
import java.net.*;
/**
* Abstract socket to handle both normal sockets and bin/resin sockets.
*/
public class JsseSSLFactory implements SSLFactory {
private static final Logger log
= Logger.getLogger(JsseSSLFactory.class.getName());
private static final L10N L = new L10N(JsseSSLFactory.class);
private Path _keyStoreFile;
private String _alias;
private String _password;
private String _verifyClient;
private String _keyStoreType = "jks";
private String _keyManagerFactory = "SunX509";
private String _sslContext = "TLS";
private String []_cipherSuites;
private String []_cipherSuitesForbidden;
private String []_protocols;
private String _selfSignedName;
private KeyStore _keyStore;
/**
* Creates a ServerSocket factory without initializing it.
*/
public JsseSSLFactory()
{
}
/**
* Sets the enabled cipher suites
*/
public void setCipherSuites(String []ciphers)
{
_cipherSuites = ciphers;
}
/**
* Sets the enabled cipher suites
*/
public void setCipherSuitesForbidden(String []ciphers)
{
_cipherSuitesForbidden = ciphers;
}
/**
* Sets the key store
*/
public void setKeyStoreFile(Path keyStoreFile)
{
_keyStoreFile = keyStoreFile;
}
/**
* Returns the certificate file.
*/
public Path getKeyStoreFile()
{
return _keyStoreFile;
}
/**
* Sets the password.
*/
public void setPassword(String password)
{
_password = password;
}
/**
* Returns the key file.
*/
public String getPassword()
{
return _password;
}
/**
* Sets the certificate alias
*/
public void setAlias(String alias)
{
_alias = alias;
}
/**
* Returns the alias.
*/
public String getAlias()
{
return _alias;
}
/**
* Sets the verifyClient.
*/
public void setVerifyClient(String verifyClient)
{
_verifyClient = verifyClient;
}
/**
* Returns the key file.
*/
public String getVerifyClient()
{
return _verifyClient;
}
/**
* Sets the key-manager-factory
*/
public void setKeyManagerFactory(String keyManagerFactory)
{
_keyManagerFactory = keyManagerFactory;
}
/**
* Sets the self-signed certificate name
*/
public void setSelfSignedCertificateName(String name)
{
_selfSignedName = name;
}
/**
* Sets the ssl-context
*/
public void setSSLContext(String sslContext)
{
_sslContext = sslContext;
}
/**
* Sets the key-store
*/
public void setKeyStoreType(String keyStore)
{
_keyStoreType = keyStore;
}
/**
* Sets the protocol
*/
public void setProtocol(String protocol)
{
_protocols = protocol.split("[\\s,]+");
}
/**
* Initialize
*/
@PostConstruct
public void init()
throws ConfigException, IOException, GeneralSecurityException
{
if (_keyStoreFile != null && _password == null)
throw new ConfigException(L.l("'password' is required for JSSE."));
if (_password != null && _keyStoreFile == null)
throw new ConfigException(L.l("'key-store-file' is required for JSSE."));
if (_alias != null && _keyStoreFile == null)
throw new ConfigException(L.l("'alias' requires a key store for JSSE."));
if (_keyStoreFile == null && _selfSignedName == null)
throw new ConfigException(L.l("JSSE requires a key-store-file or a self-signed-certificate-name."));
if (_keyStoreFile == null)
return;
_keyStore = KeyStore.getInstance(_keyStoreType);
InputStream is = _keyStoreFile.openRead();
try {
_keyStore.load(is, _password.toCharArray());
} finally {
is.close();
}
if (_alias != null) {
Key key = _keyStore.getKey(_alias, _password.toCharArray());
if (key == null)
throw new ConfigException(L.l("JSSE alias '{0}' does not have a corresponding key.",
_alias));
Certificate []certChain = _keyStore.getCertificateChain(_alias);
if (certChain == null)
throw new ConfigException(L.l("JSSE alias '{0}' does not have a corresponding certificate chain.",
_alias));
_keyStore = KeyStore.getInstance(_keyStoreType);
_keyStore.load(null, _password.toCharArray());
_keyStore.setKeyEntry(_alias, key, _password.toCharArray(), certChain);
}
}
/**
* Creates the SSL ServerSocket.
*/
public QServerSocket create(InetAddress host, int port)
throws IOException, GeneralSecurityException
{
SSLServerSocketFactory factory = null;
if (_keyStore != null) {
SSLContext sslContext = SSLContext.getInstance(_sslContext);
KeyManagerFactory kmf
= KeyManagerFactory.getInstance(_keyManagerFactory);
kmf.init(_keyStore, _password.toCharArray());
sslContext.init(kmf.getKeyManagers(), null, null);
/*
if (_cipherSuites != null)
sslContext.createSSLEngine().setEnabledCipherSuites(_cipherSuites);
if (_protocols != null)
sslContext.createSSLEngine().setEnabledProtocols(_protocols);
*/
factory = sslContext.getServerSocketFactory();
}
else {
factory = createAnonymousFactory(host, port);
}
ServerSocket serverSocket;
int listen = 100;
if (host == null)
serverSocket = factory.createServerSocket(port, listen);
else
serverSocket = factory.createServerSocket(port, listen, host);
SSLServerSocket sslServerSocket = (SSLServerSocket) serverSocket;
if (_cipherSuites != null) {
sslServerSocket.setEnabledCipherSuites(_cipherSuites);
}
if (_cipherSuitesForbidden != null) {
String []cipherSuites = sslServerSocket.getEnabledCipherSuites();
if (cipherSuites == null)
cipherSuites = sslServerSocket.getSupportedCipherSuites();
ArrayList<String> cipherList = new ArrayList<String>();
for (String cipher : cipherSuites) {
if (! isCipherForbidden(cipher, _cipherSuitesForbidden)) {
cipherList.add(cipher);
}
}
cipherSuites = new String[cipherList.size()];
cipherList.toArray(cipherSuites);
sslServerSocket.setEnabledCipherSuites(cipherSuites);
}
if (_protocols != null) {
sslServerSocket.setEnabledProtocols(_protocols);
}
if ("required".equals(_verifyClient))
sslServerSocket.setNeedClientAuth(true);
else if ("optional".equals(_verifyClient))
sslServerSocket.setWantClientAuth(true);
return new QServerSocketWrapper(serverSocket);
}
private boolean isCipherForbidden(String cipher,
String []forbiddenList)
{
for (String forbidden : forbiddenList) {
if (cipher.equals(forbidden))
return true;
}
return false;
}
private SSLServerSocketFactory createAnonymousFactory(InetAddress hostAddr,
int port)
throws IOException, GeneralSecurityException
{
SSLContext sslContext = SSLContext.getInstance(_sslContext);
String []cipherSuites = _cipherSuites;
/*
if (cipherSuites == null) {
cipherSuites = sslContext.createSSLEngine().getSupportedCipherSuites();
}
*/
String selfSignedName = _selfSignedName;
if (selfSignedName == null
|| "".equals(selfSignedName)
|| "*".equals(selfSignedName)) {
if (hostAddr != null)
selfSignedName = hostAddr.getHostName();
else {
InetAddress addr = InetAddress.getLocalHost();
selfSignedName = addr.getHostAddress();
}
}
SelfSignedCert cert = createSelfSignedCert(selfSignedName, cipherSuites);
if (cert == null)
throw new ConfigException(L.l("Cannot generate anonymous certificate"));
sslContext.init(cert.getKeyManagers(), null, null);
// SSLEngine engine = sslContext.createSSLEngine();
SSLServerSocketFactory factory = sslContext.getServerSocketFactory();
return factory;
}
private SelfSignedCert createSelfSignedCert(String name,
String []cipherSuites)
{
Path dataDir = RootDirectorySystem.getCurrentDataDirectory();
Path certDir = dataDir.lookup("certs");
SelfSignedCert cert = null;
try {
Path certPath = certDir.lookup(name + ".cert");
if (certPath.canRead()) {
ReadStream is = certPath.openRead();
try {
Hessian2Input hIn = new Hessian2Input(is);
cert = (SelfSignedCert) hIn.readObject(SelfSignedCert.class);
hIn.close();
return cert;
} finally {
IoUtil.close(is);
}
}
} catch (Exception e) {
log.log(Level.FINER, e.toString(), e);
}
cert = SelfSignedCert.create(name, cipherSuites);
try {
certDir.mkdirs();
Path certPath = certDir.lookup(name + ".cert");
WriteStream os = certPath.openWrite();
try {
Hessian2Output hOut = new Hessian2Output(os);
hOut.writeObject(cert);
hOut.close();
} finally {
IoUtil.close(os);
}
} catch (Exception e) {
log.log(Level.FINER, e.toString(), e);
}
return cert;
}
/**
* Creates the SSL ServerSocket.
*/
public QServerSocket bind(QServerSocket ss)
throws ConfigException, IOException, GeneralSecurityException
{
throw new ConfigException(L.l("jsse is not allowed here"));
}
}