
org.xipki.http.server.HttpServers Maven / Gradle / Ivy
The newest version!
/*
*
* Copyright (c) 2013 - 2018 Lijun Liao
*
* 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.xipki.http.server;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.http.server.conf.FileOrBinaryType;
import org.xipki.http.server.conf.HttpServersConf;
import org.xipki.http.server.conf.HttpserverType;
import org.xipki.http.server.conf.KeystoreType;
import org.xipki.http.server.conf.TlsType;
import org.xipki.http.server.conf.TruststoreType;
import org.xipki.http.servlet.SslReverseProxyMode;
import org.xipki.password.PasswordResolver;
import com.alibaba.fastjson.JSON;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.OpenSsl;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
/**
* TODO.
* @author Lijun Liao
* @since 2.2.0
*/
public final class HttpServers implements Closeable {
private static final Logger LOG = LoggerFactory.getLogger(HttpServers.class);
private final Set servers = new HashSet<>();
private ServletListener servletListener;
private String confFile;
private HttpServersConf conf;
private PasswordResolver passwordResolver;
public void setServletListener(ServletListener servletListener) {
this.servletListener = servletListener;
for (HttpServer server : servers) {
server.setServletListener(servletListener);
}
}
public void setPasswordResolver(PasswordResolver passwordResolver) {
this.passwordResolver = passwordResolver;
}
public void setConfFile(String confFile) {
this.confFile = confFile;
}
public void setConf(HttpServersConf conf) {
this.conf = conf;
}
public void start() throws Exception {
if (conf == null && confFile == null) {
throw new IllegalStateException("neither conf nor confFile is not set");
} else if (conf == null) {
InputStream stream = Files.newInputStream(Paths.get(confFile));
try {
conf = JSON.parseObject(stream, HttpServersConf.class);
} finally {
stream.close();
}
}
if (servletListener == null) {
throw new IllegalStateException("servletListener is not set");
}
List serverConfs = conf.getHttpservers();
Set ports = new HashSet<>();
for (HttpserverType conf : serverConfs) {
if (!conf.isEnabled()) {
LOG.info("HTTP server on port {} is disabled, ignore it", conf.getPort());
}
int port = conf.getPort();
if (ports.contains(port)) {
throw new Exception("Duplicated use of the port " + port);
}
ports.add(port);
int numThreads = (conf.getThreads() == null) ? 0 : conf.getThreads().intValue();
String str = conf.getReverseProxy();
SslReverseProxyMode mode;
if (str == null || str.equalsIgnoreCase("NONE")) {
mode = SslReverseProxyMode.NONE;
} else if (str.equalsIgnoreCase("APACHE")) {
mode = SslReverseProxyMode.APACHE;
} else {
throw new Exception("invalid reverseProxy " + str);
}
HttpServer server = new HttpServer(buildSslContext(conf), port, numThreads);
if (conf.getMaxRequestSize() != null) {
server.setMaxRequestBodySize(conf.getMaxRequestSize().intValue());
}
if (conf.getMaxUriSize() != null) {
server.setMaxUriPathSize(conf.getMaxUriSize().intValue());
}
server.setServletListener(servletListener);
server.setSslReverseProxyMode(mode);
servers.add(server);
}
for (HttpServer server : servers) {
server.start();
}
}
@Deprecated
public void shutdown() {
close();
}
@Override
public void close() {
if (servers.isEmpty()) {
LOG.info("found no HTTP server to shutdown");
return;
}
for (HttpServer server : servers) {
server.close();
LOG.info("close HTTP server {}", server);
}
servers.clear();
}
private SslContext buildSslContext(HttpserverType conf)
throws Exception {
TlsType tt = conf.getTls();
if (tt == null) {
return null;
}
KeystoreType kst = tt.getKeystore();
SslContextBuilder builder;
// key and certificate
if (kst == null) {
throw new IllegalArgumentException("no keystore is configured");
} else {
char[] kstPwd = passwordResolver.resolvePassword(kst.getPassword());
KeyStore ks = loadKeyStore(kst.getType(), kst.getStore(), kstPwd);
String alias = kst.getKeyAlias();
if (alias != null) {
if (!ks.isKeyEntry(alias)) {
throw new Exception("'" + alias + "' is not a valid key alias");
}
} else {
Enumeration aliases = ks.aliases();
while (aliases.hasMoreElements()) {
String al = aliases.nextElement();
if (ks.isKeyEntry(al)) {
alias = al;
break;
}
}
if (alias == null) {
throw new Exception("found no key entries in the keystore");
}
}
char[] keypwd = (kst.getKeyPassword() == null)
? kstPwd : passwordResolver.resolvePassword(kst.getKeyPassword());
PrivateKey key = (PrivateKey) ks.getKey(alias, keypwd);
Certificate[] certs = ks.getCertificateChain(alias);
X509Certificate[] keyCertChain = new X509Certificate[certs.length];
for (int i = 0; i < certs.length; i++) {
keyCertChain[i] = (X509Certificate) certs[i];
}
builder = SslContextBuilder.forServer(key, keyCertChain);
}
boolean opensslAvailable = OpenSsl.isAvailable();
// providers
SslProvider sslProvider;
if (tt.getProvider() == null
|| tt.getProvider().isEmpty()
|| tt.getProvider().equalsIgnoreCase("DEFAULT")) {
if (!opensslAvailable) {
logOpenSslWarning();
}
sslProvider = SslContext.defaultServerProvider();
} else {
String providerStr = tt.getProvider();
String providerStr0 = providerStr.toLowerCase().replaceAll("[^a-z0-9]+", "");
if ("jdk".equals(providerStr0)) {
sslProvider = SslProvider.JDK;
} else if ("openssl".equals(providerStr0) || "opensslrefcnt".equals(providerStr0)) {
if (!opensslAvailable) {
logOpenSslWarning();
throw new Exception("OpenSSL not available");
}
sslProvider = "openssl".equals(providerStr0)
? SslProvider.OPENSSL : SslProvider.OPENSSL_REFCNT;
} else {
throw new Exception("unknwon SSL provider " + providerStr);
}
}
LOG.info("use SSL provider {}", sslProvider);
builder.sslProvider(sslProvider);
List availableProtocols;
List availableCiphersuits;
switch (sslProvider) {
case JDK:
SSLParameters sslParams = SSLContext.getDefault().getSupportedSSLParameters();
availableProtocols = Arrays.asList(sslParams.getProtocols());
availableCiphersuits = Arrays.asList(sslParams.getCipherSuites());
break;
case OPENSSL:
case OPENSSL_REFCNT:
// any way to get the supported protocols of OpenSSL?
availableProtocols = Arrays.asList("TLSv1.1", "TLSv1.2");
availableCiphersuits = new ArrayList<>(OpenSsl.availableJavaCipherSuites());
break;
default:
throw new IllegalStateException(
"should not reach here, unknown SssProvider " + sslProvider);
}
LOG.debug("available SSL protocols {}", availableProtocols);
LOG.debug("available SSL cipher suites {}", availableCiphersuits);
// protocols
List protocols;
if (tt.getProtocols() != null) {
protocols = tt.getProtocols();
} else {
protocols = Arrays.asList("TLSv1.1", "TLSv1.2");
}
final String[] strArray = new String[0];
Set usedProtocols = new HashSet<>();
for (String protocol : protocols) {
boolean added = false;
for (String supported : availableProtocols) {
if (protocol.equalsIgnoreCase(supported)) {
usedProtocols.add(supported);
added = true;
break;
}
}
if (!added) {
LOG.warn("SSL Protocol {} unsupported, ignore it", protocol);
}
}
if (usedProtocols.isEmpty()) {
throw new Exception("None of the configured SSL protocols is supported");
}
LOG.info("use SSL protocols {}", usedProtocols);
builder.protocols(usedProtocols.toArray(strArray));
// canonicalize the cipher suites
boolean cipherWithTls = availableCiphersuits.get(0).startsWith("TLS_");
// cipher suites
Set usedCipherSuites = new HashSet<>();
if (tt.getCiphersuites() != null) {
for (String cipherSuite : tt.getCiphersuites()) {
if (cipherSuite.length() < 5) {
LOG.warn("cipher suite {} unsupported, ignore it", cipherSuite);
continue;
}
String adaptedCipher;
if (cipherWithTls == cipherSuite.startsWith("TLS_")) {
adaptedCipher = cipherSuite;
} else {
if (cipherWithTls) {
adaptedCipher = "TLS_" + cipherSuite.substring(4);
} else {
adaptedCipher = "SSL_" + cipherSuite.substring(4);
}
}
boolean added = false;
for (String supported : availableCiphersuits) {
if (adaptedCipher.equalsIgnoreCase(supported)) {
usedCipherSuites.add(supported);
added = true;
break;
}
}
if (!added) {
LOG.warn("SSL cipher suite {} unsupported, ignore it", cipherSuite);
}
}
} else {
String[] excludeMiddlePatterns = {"_3DES", "_DES", "EMPTY", "EXPORT", "anno", "NULL"};
String[] excludeEndPatterns = {"MD5", "SHA"};
for (String cipherSuite : availableCiphersuits) {
boolean add = true;
for (String p : excludeMiddlePatterns) {
if (cipherSuite.contains(p)) {
add = false;
break;
}
}
if (add) {
for (String p : excludeEndPatterns) {
if (cipherSuite.endsWith(p)) {
add = false;
break;
}
}
}
if (add) {
usedCipherSuites.add(cipherSuite);
}
}
}
LOG.info("use SSL cipher suites {}", usedCipherSuites);
builder.ciphers(usedCipherSuites);
// client authentication
ClientAuth clientAuth;
String str = tt.getClientauth();
if ("none".equalsIgnoreCase(str)) {
clientAuth = ClientAuth.NONE;
} else if ("optional".equalsIgnoreCase(str)) {
clientAuth = ClientAuth.OPTIONAL;
} else if ("require".equalsIgnoreCase(str)) {
clientAuth = ClientAuth.REQUIRE;
} else {
throw new Exception("invalid client authentication '" + str + "'");
}
builder.clientAuth(clientAuth);
if (clientAuth != ClientAuth.NONE) {
TruststoreType tst = tt.getTruststore();
if (tst == null) {
throw new Exception("Client authentication is activated, but no truststore is configured");
}
char[] pwd = passwordResolver.resolvePassword(tst.getPassword());
KeyStore ks = loadKeyStore(tst.getType(), tst.getStore(), pwd);
List trustcerts = new LinkedList<>();
Enumeration aliases = ks.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
Certificate cert = ks.getCertificate(alias);
trustcerts.add((X509Certificate) cert);
}
if (trustcerts.isEmpty()) {
throw new Exception("No certificates found int the truststore. Please verify it via"
+ " JDK's keytool.");
}
builder.trustManager(trustcerts.toArray(new X509Certificate[0]));
}
return builder.build();
}
private KeyStore loadKeyStore(String storeType, FileOrBinaryType store, char[] password)
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
InputStream stream;
if (store.getBinary() != null) {
stream = new ByteArrayInputStream(store.getBinary());
} else {
stream = Files.newInputStream(Paths.get(store.getFile()));
}
KeyStore keystore = KeyStore.getInstance(storeType);
try {
keystore.load(stream, password);
} finally {
stream.close();
}
return keystore;
}
private static void logOpenSslWarning() {
if (LOG.isWarnEnabled()) {
LOG.warn("To use the OpenSSL as SSL provider, both libapr-1 and OpenSSL must be installed "
+ "and configured. Note that OpenSSL cannot be applied in Fedora distribution");
}
}
public static void main(String[] args) {
try {
HttpServers hs = new HttpServers();
hs.setConfFile("../doc/examples/httpservers.json");
hs.setServletListener(new ServletListener());
hs.start();
hs.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy