/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.tomcat.util.net.jsse;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CRL;
import java.security.cert.CRLException;
import java.security.cert.CertPathParameters;
import java.security.cert.CertStore;
import java.security.cert.CertStoreParameters;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.compat.JreVendor;
import org.apache.tomcat.util.file.ConfigFileLoader;
import org.apache.tomcat.util.net.Constants;
import org.apache.tomcat.util.net.SSLContext;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
import org.apache.tomcat.util.net.SSLUtilBase;
import org.apache.tomcat.util.res.StringManager;
/**
* SSLUtil implementation for JSSE.
*
* @author Harish Prabandham
* @author Costin Manolache
* @author Stefan Freyr Stefansson
* @author EKR
* @author Jan Luehe
*/
public class JSSEUtil extends SSLUtilBase {
private static final Log log = LogFactory.getLog(JSSEUtil.class);
private static final StringManager sm = StringManager.getManager(JSSEUtil.class);
private static final Set<String> implementedProtocols;
private static final Set<String> implementedCiphers;
static {
SSLContext context;
try {
context = new JSSESSLContext(Constants.SSL_PROTO_TLS);
context.init(null, null, null);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
// This is fatal for the connector so throw an exception to prevent
// it from starting
throw new IllegalArgumentException(e);
}
String[] implementedProtocolsArray = context.getSupportedSSLParameters().getProtocols();
implementedProtocols = new HashSet<>(implementedProtocolsArray.length);
// Filter out all the SSL protocols (SSLv2 and SSLv3) from the list of
// implemented protocols since they are no longer considered secure but
// allow SSLv2Hello. This has the effect of making it impossible to use
// SSLv2 or SSLv3 without source code changes.
for (String protocol : implementedProtocolsArray) {
String protocolUpper = protocol.toUpperCase(Locale.ENGLISH);
if (!"SSLV2HELLO".equals(protocolUpper)) {
if (protocolUpper.contains("SSL")) {
log.debug(sm.getString("jsse.excludeProtocol", protocol));
continue;
}
}
implementedProtocols.add(protocol);
}
if (implementedProtocols.size() == 0) {
log.warn(sm.getString("jsse.noDefaultProtocols"));
}
String[] implementedCipherSuiteArray = context.getSupportedSSLParameters().getCipherSuites();
// The IBM JRE will accept cipher suites names SSL_xxx or TLS_xxx but
// only returns the SSL_xxx form for supported cipher suites. Therefore
// need to filter the requested cipher suites using both forms with an
// IBM JRE.
if (JreVendor.IS_IBM_JVM) {
implementedCiphers = new HashSet<>(implementedCipherSuiteArray.length * 2);
for (String name : implementedCipherSuiteArray) {
implementedCiphers.add(name);
if (name.startsWith("SSL")) {
implementedCiphers.add("TLS" + name.substring(3));
}
}
} else {
implementedCiphers = new HashSet<>(implementedCipherSuiteArray.length);
implementedCiphers.addAll(Arrays.asList(implementedCipherSuiteArray));
}
}
private final SSLHostConfig sslHostConfig;
public JSSEUtil (SSLHostConfigCertificate certificate) {
super(certificate);
this.sslHostConfig = certificate.getSSLHostConfig();
}
@Override
protected Log getLog() {
return log;
}
@Override
protected Set<String> getImplementedProtocols() {
return implementedProtocols;
}
@Override
protected Set<String> getImplementedCiphers() {
return implementedCiphers;
}
@Override
public SSLContext createSSLContext(List<String> negotiableProtocols) throws NoSuchAlgorithmException {
return new JSSESSLContext(sslHostConfig.getSslProtocol());
}
@Override
public KeyManager[] getKeyManagers() throws Exception {
String keystoreType = certificate.getCertificateKeystoreType();
String keyAlias = certificate.getCertificateKeyAlias();
String algorithm = sslHostConfig.getKeyManagerAlgorithm();
String keyPass = certificate.getCertificateKeyPassword();
// This has to be here as it can't be moved to SSLHostConfig since the
// defaults vary between JSSE and OpenSSL.
if (keyPass == null) {
keyPass = certificate.getCertificateKeystorePassword();
}
KeyManager[] kms = null;
KeyStore ks = certificate.getCertificateKeystore();
if (ks == null) {
// create an in-memory keystore and import the private key
// and the certificate chain from the PEM files
ks = KeyStore.getInstance("JKS");
ks.load(null, null);
PEMFile privateKeyFile = new PEMFile(SSLHostConfig.adjustRelativePath
(certificate.getCertificateKeyFile() != null ? certificate.getCertificateKeyFile() : certificate.getCertificateFile()),
keyPass);
PEMFile certificateFile = new PEMFile(SSLHostConfig.adjustRelativePath(certificate.getCertificateFile()));
Collection<Certificate> chain = new ArrayList<>();
chain.addAll(certificateFile.getCertificates());
if (certificate.getCertificateChainFile() != null) {
PEMFile certificateChainFile = new PEMFile(SSLHostConfig.adjustRelativePath(certificate.getCertificateChainFile()));
chain.addAll(certificateChainFile.getCertificates());
}
if (keyAlias == null) {
keyAlias = "tomcat";
}
ks.setKeyEntry(keyAlias, privateKeyFile.getPrivateKey(), keyPass.toCharArray(), chain.toArray(new Certificate[chain.size()]));
}
if (keyAlias != null && !ks.isKeyEntry(keyAlias)) {
throw new IOException(sm.getString("jsse.alias_no_key_entry", keyAlias));
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(ks, keyPass.toCharArray());
kms = kmf.getKeyManagers();
if (kms == null) {
return kms;
}
if (keyAlias != null) {
String alias = keyAlias;
// JKS keystores always convert the alias name to lower case
if ("JKS".equals(keystoreType)) {
alias = alias.toLowerCase(Locale.ENGLISH);
}
for(int i = 0; i < kms.length; i++) {
kms[i] = new JSSEKeyManager((X509KeyManager)kms[i], alias);
}
}
return kms;
}
@Override
public TrustManager[] getTrustManagers() throws Exception {
String className = sslHostConfig.getTrustManagerClassName();
if(className != null && className.length() > 0) {
ClassLoader classLoader = getClass().getClassLoader();
Class<?> clazz = classLoader.loadClass(className);
if(!(TrustManager.class.isAssignableFrom(clazz))){
throw new InstantiationException(sm.getString(
"jsse.invalidTrustManagerClassName", className));
}
Object trustManagerObject = clazz.newInstance();
TrustManager trustManager = (TrustManager) trustManagerObject;
return new TrustManager[]{ trustManager };
}
TrustManager[] tms = null;
KeyStore trustStore = sslHostConfig.getTruststore();
if (trustStore != null) {
checkTrustStoreEntries(trustStore);
String algorithm = sslHostConfig.getTruststoreAlgorithm();
String crlf = sslHostConfig.getCertificateRevocationListFile();
boolean revocationEnabled = sslHostConfig.getRevocationEnabled();
if ("PKIX".equalsIgnoreCase(algorithm)) {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
CertPathParameters params = getParameters(crlf, trustStore, revocationEnabled);
ManagerFactoryParameters mfp = new CertPathTrustManagerParameters(params);
tmf.init(mfp);
tms = tmf.getTrustManagers();
} else {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
tmf.init(trustStore);
tms = tmf.getTrustManagers();
if (crlf != null && crlf.length() > 0) {
throw new CRLException(sm.getString("jsseUtil.noCrlSupport", algorithm));
}
// Only warn if the attribute has been explicitly configured
if (sslHostConfig.isCertificateVerificationDepthConfigured()) {
log.warn(sm.getString("jsseUtil.noVerificationDepth", algorithm));
}
}
}
return tms;
}
private void checkTrustStoreEntries(KeyStore trustStore) throws Exception {
Enumeration<String> aliases = trustStore.aliases();
if (aliases != null) {
Date now = new Date();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
if (trustStore.isCertificateEntry(alias)) {
Certificate cert = trustStore.getCertificate(alias);
if (cert instanceof X509Certificate) {
try {
((X509Certificate) cert).checkValidity(now);
} catch (CertificateExpiredException | CertificateNotYetValidException e) {
String msg = sm.getString("jsseUtil.trustedCertNotValid", alias,
((X509Certificate) cert).getSubjectDN(), e.getMessage());
if (log.isDebugEnabled()) {
log.debug(msg, e);
} else {
log.warn(msg);
}
}
} else {
if (log.isDebugEnabled()) {
log.debug(sm.getString("jsseUtil.trustedCertNotChecked", alias));
}
}
}
}
}
}
@Override
public void configureSessionContext(SSLSessionContext sslSessionContext) {
sslSessionContext.setSessionCacheSize(sslHostConfig.getSessionCacheSize());
sslSessionContext.setSessionTimeout(sslHostConfig.getSessionTimeout());
}
/**
* Return the initialization parameters for the TrustManager.
* Currently, only the default <code>PKIX</code> is supported.
*
* @param crlf The path to the CRL file.
* @param trustStore The configured TrustStore.
* @param revocationEnabled Should the JSSE provider perform revocation
* checks? Ignored if {@code crlf} is non-null.
* Configuration of revocation checks are expected
* to be via proprietary JSSE provider methods.
* @return The parameters including the CRLs and TrustStore.
* @throws Exception An error occurred
*/
protected CertPathParameters getParameters(String crlf, KeyStore trustStore,
boolean revocationEnabled) throws Exception {
PKIXBuilderParameters xparams =
new PKIXBuilderParameters(trustStore, new X509CertSelector());
if (crlf != null && crlf.length() > 0) {
Collection<? extends CRL> crls = getCRLs(crlf);
CertStoreParameters csp = new CollectionCertStoreParameters(crls);
CertStore store = CertStore.getInstance("Collection", csp);
xparams.addCertStore(store);
xparams.setRevocationEnabled(true);
} else {
xparams.setRevocationEnabled(revocationEnabled);
}
xparams.setMaxPathLength(sslHostConfig.getCertificateVerificationDepth());
return xparams;
}
/**
* Load the collection of CRLs.
* @param crlf The path to the CRL file.
* @return the CRLs collection
* @throws IOException Error reading CRL file
* @throws CRLException CRL error
* @throws CertificateException Error processing certificate
*/
protected Collection<? extends CRL> getCRLs(String crlf)
throws IOException, CRLException, CertificateException {
Collection<? extends CRL> crls = null;
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
try (InputStream is = ConfigFileLoader.getInputStream(crlf)) {
crls = cf.generateCRLs(is);
}
} catch(IOException iex) {
throw iex;
} catch(CRLException crle) {
throw crle;
} catch(CertificateException ce) {
throw ce;
}
return crls;
}
}