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

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