blob: edff5c9990373edde1148d39eae234b30ede298b [file] [log] [blame]
/*
* 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.ignite.ssl;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import javax.cache.configuration.Factory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.util.typedef.internal.A;
/**
* This SSL context factory that provides ssl context configuration with specified key
* and trust stores.
* <p>
* In some cases it is useful to disable certificate validation of client side (e.g. when connecting
* to a server with self-signed certificate). This can be achieved by setting a disabled trust manager
* to this factory, which can be obtained by {@link #getDisabledTrustManager()} method:
* <pre>
* SslContextFactory factory = new SslContextFactory();
* factory.setTrustManagers(SslContextFactory.getDisabledTrustManager());
* // Rest of initialization.
* </pre>
*/
public class SslContextFactory implements Factory<SSLContext> {
/** */
private static final long serialVersionUID = 0L;
/** Default key store type. */
public static final String DFLT_STORE_TYPE = "JKS";
/** Default SSL protocol. */
public static final String DFLT_SSL_PROTOCOL = "TLS";
/** Default key manager algorithm. */
public static final String DFLT_KEY_ALGORITHM = "SunX509";
/** SSL protocol. */
private String proto = DFLT_SSL_PROTOCOL;
/** Key manager algorithm. */
private String keyAlgorithm = DFLT_KEY_ALGORITHM;
/** Key store type. */
private String keyStoreType = DFLT_STORE_TYPE;
/** Path to key store file */
private String keyStoreFilePath;
/** Key store password */
private char[] keyStorePwd;
/** Trust store type. */
private String trustStoreType = DFLT_STORE_TYPE;
/** Path to trust store. */
private String trustStoreFilePath;
/** Trust store password */
private char[] trustStorePwd;
/** Trust managers. */
private TrustManager[] trustMgrs;
/** Enabled cipher suites. */
private String[] cipherSuites;
/** Enabled cipher suites. */
private String[] protocols;
/**
* Gets key store type used for context creation.
*
* @return Key store type.
*/
public String getKeyStoreType() {
return keyStoreType;
}
/**
* Sets key store type used in context initialization. If not provided, {@link #DFLT_STORE_TYPE} will
* be used.
*
* @param keyStoreType Key store type.
*/
public void setKeyStoreType(String keyStoreType) {
A.notNull(keyStoreType, "keyStoreType");
this.keyStoreType = keyStoreType;
}
/**
* Gets trust store type used for context creation.
*
* @return trust store type.
*/
public String getTrustStoreType() {
return trustStoreType;
}
/**
* Sets trust store type used in context initialization. If not provided, {@link #DFLT_STORE_TYPE} will
* be used.
*
* @param trustStoreType Trust store type.
*/
public void setTrustStoreType(String trustStoreType) {
A.notNull(trustStoreType, "trustStoreType");
this.trustStoreType = trustStoreType;
}
/**
* Gets protocol for secure transport.
*
* @return SSL protocol name.
*/
public String getProtocol() {
return proto;
}
/**
* Sets protocol for secure transport. If not specified, {@link #DFLT_SSL_PROTOCOL} will be used.
*
* @param proto SSL protocol name.
*/
public void setProtocol(String proto) {
A.notNull(proto, "proto");
this.proto = proto;
}
/**
* Gets algorithm that will be used to create a key manager. If not specified, {@link #DFLT_KEY_ALGORITHM}
* will be used.
*
* @return Key manager algorithm.
*/
public String getKeyAlgorithm() {
return keyAlgorithm;
}
/**
* Sets key manager algorithm that will be used to create a key manager. Notice that in most cased default value
* suites well, however, on Android platform this value need to be set to <tt>X509<tt/>.
*
* @param keyAlgorithm Key algorithm name.
*/
public void setKeyAlgorithm(String keyAlgorithm) {
A.notNull(keyAlgorithm, "keyAlgorithm");
this.keyAlgorithm = keyAlgorithm;
}
/**
* Gets path to the key store file.
*
* @return Path to key store file.
*/
public String getKeyStoreFilePath() {
return keyStoreFilePath;
}
/**
* Sets path to the key store file. This is a mandatory parameter since
* ssl context could not be initialized without key manager.
*
* @param keyStoreFilePath Path to key store file.
*/
public void setKeyStoreFilePath(String keyStoreFilePath) {
A.notNull(keyStoreFilePath, "keyStoreFilePath");
this.keyStoreFilePath = keyStoreFilePath;
}
/**
* Gets key store password.
*
* @return Key store password.
*/
public char[] getKeyStorePassword() {
return keyStorePwd;
}
/**
* Sets key store password.
*
* @param keyStorePwd Key store password.
*/
public void setKeyStorePassword(char[] keyStorePwd) {
A.notNull(keyStorePwd, "keyStorePwd");
this.keyStorePwd = keyStorePwd;
}
/**
* Gets path to the trust store file.
*
* @return Path to the trust store file.
*/
public String getTrustStoreFilePath() {
return trustStoreFilePath;
}
/**
* Sets path to the trust store file. This is an optional parameter,
* however one of the {@code setTrustStoreFilePath(String)}, {@link #setTrustManagers(TrustManager[])}
* properties must be set.
*
* @param trustStoreFilePath Path to the trust store file.
*/
public void setTrustStoreFilePath(String trustStoreFilePath) {
this.trustStoreFilePath = trustStoreFilePath;
}
/**
* Gets trust store password.
*
* @return Trust store password.
*/
public char[] getTrustStorePassword() {
return trustStorePwd;
}
/**
* Sets trust store password.
*
* @param trustStorePwd Trust store password.
*/
public void setTrustStorePassword(char[] trustStorePwd) {
this.trustStorePwd = trustStorePwd;
}
/**
* Gets pre-configured trust managers.
*
* @return Trust managers.
*/
public TrustManager[] getTrustManagers() {
return trustMgrs;
}
/**
* Sets pre-configured trust managers. This is an optional parameter,
* however one of the {@link #setTrustStoreFilePath(String)}, {@code #setTrustManagers(TrustManager[])}
*
* @param trustMgrs Pre-configured trust managers.
*/
public void setTrustManagers(TrustManager... trustMgrs) {
this.trustMgrs = trustMgrs;
}
/**
* Returns an instance of trust manager that will always succeed regardless of certificate provided.
*
* @return Trust manager instance.
*/
public static TrustManager getDisabledTrustManager() {
return new DisabledX509TrustManager();
}
/**
* Sets enabled cipher suites.
* @param cipherSuites enabled cipher suites.
*/
public void setCipherSuites(String... cipherSuites) {
this.cipherSuites = cipherSuites;
}
/**
* Gets enabled cipher suites
* @return enabled cipher suites
*/
public String[] getCipherSuites() {
return cipherSuites;
}
/**
* Gets enabled cipher suites
* @return enabled cipher suites
*/
public String[] getProtocols() {
return protocols;
}
/**
* Sets enabled protocols.
* @param protocols enabled protocols.
*/
public void setProtocols(String... protocols) {
this.protocols = protocols;
}
/**
* Creates SSL context based on factory settings.
*
* @return Initialized SSL context.
* @throws SSLException If SSL context could not be created.
*/
private SSLContext createSslContext() throws SSLException {
checkParameters();
try {
KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance(keyAlgorithm);
KeyStore keyStore = loadKeyStore(keyStoreType, keyStoreFilePath, keyStorePwd);
keyMgrFactory.init(keyStore, keyStorePwd);
TrustManager[] mgrs = trustMgrs;
if (mgrs == null) {
TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(keyAlgorithm);
KeyStore trustStore = loadKeyStore(trustStoreType, trustStoreFilePath, trustStorePwd);
trustMgrFactory.init(trustStore);
mgrs = trustMgrFactory.getTrustManagers();
}
SSLContext ctx = SSLContext.getInstance(proto);
if (cipherSuites != null || protocols != null) {
SSLParameters sslParameters = new SSLParameters();
if (cipherSuites != null)
sslParameters.setCipherSuites(cipherSuites);
if (protocols != null)
sslParameters.setProtocols(protocols);
ctx = new SSLContextWrapper(ctx, sslParameters);
}
ctx.init(keyMgrFactory.getKeyManagers(), mgrs, null);
return ctx;
}
catch (GeneralSecurityException e) {
throw new SSLException("Failed to initialize SSL context " + parameters(), e);
}
}
/**
* Builds human-readable string with factory parameters.
*
* @return Parameters string.
*/
private String parameters() {
StringBuilder buf = new StringBuilder("[keyStoreType=").append(keyStoreType);
buf.append(", proto=").append(proto).append(", keyStoreFile=").append(keyStoreFilePath);
if (trustMgrs != null)
buf.append(", trustMgrs=").append(Arrays.toString(trustMgrs));
else
buf.append(", trustStoreFile=").append(trustStoreFilePath);
buf.append(']');
return buf.toString();
}
/**
* Checks that all required parameters are set.
*
* @throws SSLException If any of required parameters is missing.
*/
private void checkParameters() throws SSLException {
assert keyStoreType != null;
assert proto != null;
checkNullParameter(keyStoreFilePath, "keyStoreFilePath");
checkNullParameter(keyStorePwd, "keyStorePwd");
if (trustMgrs == null) {
if (trustStoreFilePath == null)
throw new SSLException("Failed to initialize SSL context (either trustStoreFilePath or " +
"trustManagers must be provided)");
else
checkNullParameter(trustStorePwd, "trustStorePwd");
}
}
/**
* @param param Value.
* @param name Name.
* @throws SSLException If {@code null}.
*/
private void checkNullParameter(Object param, String name) throws SSLException {
if (param == null)
throw new SSLException("Failed to initialize SSL context (parameter cannot be null): " + name);
}
/**
* By default, this method simply opens a raw file input stream. Subclasses may override this method
* if some specific location should be handled (this may be a case for Android users).
*
* @param filePath Path to the file.
* @return Opened input stream.
* @throws IOException If stream could not be opened.
*/
protected InputStream openFileInputStream(String filePath) throws IOException {
return new FileInputStream(filePath);
}
/**
* Loads key store with configured parameters.
*
* @param keyStoreType Type of key store.
* @param storeFilePath Path to key store file.
* @param keyStorePwd Store password.
* @return Initialized key store.
* @throws SSLException If key store could not be initialized.
*/
private KeyStore loadKeyStore(String keyStoreType, String storeFilePath, char[] keyStorePwd) throws SSLException {
InputStream input = null;
try {
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
input = openFileInputStream(storeFilePath);
keyStore.load(input, keyStorePwd);
return keyStore;
}
catch (GeneralSecurityException e) {
throw new SSLException("Failed to initialize key store (security exception occurred) [type=" +
keyStoreType + ", keyStorePath=" + storeFilePath + ']', e);
}
catch (FileNotFoundException e) {
throw new SSLException("Failed to initialize key store (key store file was not found): [path=" +
storeFilePath + ", msg=" + e.getMessage() + ']');
}
catch (IOException e) {
throw new SSLException("Failed to initialize key store (I/O error occurred): " + storeFilePath, e);
}
finally {
if (input != null) {
try {
input.close();
}
catch (IOException ignored) {
}
}
}
}
/** {@inheritDoc} */
@Override public String toString() {
return getClass().getSimpleName() + parameters();
}
/**
* Disabled trust manager, will skip all certificate checks.
*/
private static class DisabledX509TrustManager implements X509TrustManager {
/** Empty certificate array. */
private static final X509Certificate[] CERTS = new X509Certificate[0];
/** {@inheritDoc} */
@Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
throws CertificateException {
// No-op, all clients are trusted.
}
/** {@inheritDoc} */
@Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
throws CertificateException {
// No-op, all servers are trusted.
}
/** {@inheritDoc} */
@Override public X509Certificate[] getAcceptedIssuers() {
return CERTS;
}
}
/** {@inheritDoc} */
@Override public SSLContext create() {
try {
return createSslContext();
}
catch (SSLException e) {
throw new IgniteException(e);
}
}
}