/*
* 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.activemq.artemis.core.remoting.impl.ssl;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
import java.security.KeyStore;
import java.security.PrivilegedAction;
import java.security.SecureRandom;
import org.apache.activemq.artemis.utils.ClassloadingUtil;
/**
* Please note, this class supports PKCS#11 keystores, but there are no specific tests in the ActiveMQ Artemis test-suite to
* validate/verify this works because this requires a functioning PKCS#11 provider which is not available by default
* (see java.security.Security#getProviders()). The main thing to keep in mind is that PKCS#11 keystores will have a
* null keystore path.
*/
public class SSLSupport {
// Public --------------------------------------------------------
public static SSLContext createContext(final String keystoreProvider,
final String keystorePath,
final String keystorePassword,
final String trustStoreProvider,
final String trustStorePath,
final String trustStorePassword) throws Exception {
SSLContext context = SSLContext.getInstance("TLS");
KeyManager[] keyManagers = SSLSupport.loadKeyManagers(keystoreProvider, keystorePath, keystorePassword);
TrustManager[] trustManagers = SSLSupport.loadTrustManager(trustStoreProvider, trustStorePath, trustStorePassword);
context.init(keyManagers, trustManagers, new SecureRandom());
return context;
}
public static String[] parseCommaSeparatedListIntoArray(String suites) {
String[] cipherSuites = suites.split(",");
for (int i = 0; i < cipherSuites.length; i++) {
cipherSuites[i] = cipherSuites[i].trim();
}
return cipherSuites;
}
public static String parseArrayIntoCommandSeparatedList(String[] suites) {
StringBuilder supportedSuites = new StringBuilder();
for (String suite : suites) {
supportedSuites.append(suite);
supportedSuites.append(", ");
}
// trim the last 2 characters (i.e. unnecessary comma and space)
return supportedSuites.delete(supportedSuites.length() - 2, supportedSuites.length()).toString();
}
// Private -------------------------------------------------------
private static TrustManager[] loadTrustManager(final String trustStoreProvider,
final String trustStorePath,
final String trustStorePassword) throws Exception {
if (trustStorePath == null && (trustStoreProvider == null || !"PKCS11".equals(trustStoreProvider.toUpperCase()))) {
return null;
} else {
TrustManagerFactory trustMgrFactory;
KeyStore trustStore = SSLSupport.loadKeystore(trustStoreProvider, trustStorePath, trustStorePassword);
trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustMgrFactory.init(trustStore);
return trustMgrFactory.getTrustManagers();
}
}
private static KeyStore loadKeystore(final String keystoreProvider,
final String keystorePath,
final String keystorePassword) throws Exception {
KeyStore ks = KeyStore.getInstance(keystoreProvider);
InputStream in = null;
try {
if (keystorePath != null) {
URL keystoreURL = SSLSupport.validateStoreURL(keystorePath);
in = keystoreURL.openStream();
}
ks.load(in, keystorePassword == null ? null : keystorePassword.toCharArray());
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ignored) {
}
}
}
return ks;
}
private static KeyManager[] loadKeyManagers(final String keyStoreProvider,
final String keystorePath,
final String keystorePassword) throws Exception {
if (keystorePath == null && (keyStoreProvider == null || !"PKCS11".equals(keyStoreProvider.toUpperCase()))) {
return null;
} else {
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore ks = SSLSupport.loadKeystore(keyStoreProvider, keystorePath, keystorePassword);
kmf.init(ks, keystorePassword == null ? null : keystorePassword.toCharArray());
return kmf.getKeyManagers();
}
}
private static URL validateStoreURL(final String storePath) throws Exception {
assert storePath != null;
// First see if this is a URL
try {
return new URL(storePath);
} catch (MalformedURLException e) {
File file = new File(storePath);
if (file.exists() == true && file.isFile()) {
return file.toURI().toURL();
} else {
URL url = findResource(storePath);
if (url != null) {
return url;
}
}
}
throw new Exception("Failed to find a store at " + storePath);
}
/**
* This seems duplicate code all over the place, but for security reasons we can't let something like this to be open in a
* utility class, as it would be a door to load anything you like in a safe VM.
* For that reason any class trying to do a privileged block should do with the AccessController directly.
*/
private static URL findResource(final String resourceName) {
return AccessController.doPrivileged(new PrivilegedAction<URL>() {
@Override
public URL run() {
return ClassloadingUtil.findResource(resourceName);
}
});
}
}