/*******************************************************************************
* Copyright (c) 2007, 2014 compeople AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* compeople AG - initial API and implementation
*******************************************************************************/
package org.eclipse.riena.internal.communication.core.ssl;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import javax.crypto.Cipher;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.osgi.framework.Bundle;
import org.osgi.service.log.LogService;
import org.eclipse.equinox.log.Logger;
import org.eclipse.riena.core.Log4r;
import org.eclipse.riena.core.util.Base16Util;
import org.eclipse.riena.core.util.CipherUtils;
import org.eclipse.riena.core.util.Iter;
import org.eclipse.riena.core.wire.InjectExtension;
import org.eclipse.riena.internal.communication.core.Activator;
/**
* This class performs the configuration of the trust store for jre SSL Protocol
* implementation.
*/
public class SSLConfiguration {
private String protocol;
private String keystore;
private String password;
private String encrypt;
private HostnameVerifier hostnameVerifier;
private Bundle contributingBundle;
private boolean configured;
private String previousHttpsProtocol;
private HostnameVerifier previousHostnameVerifier;
private SSLSocketFactory previousSSLSocketFactor;
private static final String JRE_CACERTS_MARKER = "#jre-cacerts#"; //$NON-NLS-1$
private static final String HTTPS_PROTOCOLS_PROPERTY_KEY = "https.protocols"; //$NON-NLS-1$
private final static Logger LOGGER = Log4r.getLogger(Activator.getDefault(), SSLConfiguration.class);
@InjectExtension(min = 0, max = 1)
public void configure(final ISSLPropertiesExtension sslProperties) {
if (configured && sslProperties == null) {
restore();
return;
}
configured = false;
if (sslProperties == null) {
LOGGER.log(LogService.LOG_INFO, "No configuration given!."); //$NON-NLS-1$
return;
}
protocol = sslProperties.getProtocol();
keystore = sslProperties.getKeystore();
password = sslProperties.getPassword();
encrypt = sslProperties.getEncrypt();
contributingBundle = sslProperties.getContributingBundle();
LOGGER.log(LogService.LOG_INFO, "Configuring SSL protocol '" + protocol + "' with keystore '" + keystore //$NON-NLS-1$ //$NON-NLS-2$
+ "'."); //$NON-NLS-1$
// Check protocol & keystore
if (keystore == null || keystore.length() == 0 || protocol == null || protocol.length() == 0) {
// no keystore configured. Apparently no SSL used in this context.
LOGGER.log(LogService.LOG_WARNING, "Neither keystore nor protocol given!"); //$NON-NLS-1$
return;
}
// save previous value
previousHttpsProtocol = System.getProperty(HTTPS_PROTOCOLS_PROPERTY_KEY);
// set new value
System.setProperty(HTTPS_PROTOCOLS_PROPERTY_KEY, protocol);
try {
// obtain some debug information related to security providers
final Provider[] providers = Security.getProviders();
if (providers == null) {
LOGGER.log(LogService.LOG_WARNING,
"Security did not find any providers. This might be a problem. Check imported jar files for sunjce_provider.jar!"); //$NON-NLS-1$
} else {
LOGGER.log(LogService.LOG_INFO, "Security found " + providers.length + " security providers."); //$NON-NLS-1$ //$NON-NLS-2$
for (int i = 0; i < providers.length; i++) {
LOGGER.log(LogService.LOG_DEBUG, "Security provider[" + i + "]: " + providers[i].getName()); //$NON-NLS-1$ //$NON-NLS-2$
}
}
final KeyStore keyStore = KeyStore.getInstance("JKS"); //$NON-NLS-1$
final URL keystoreUrl = getKeystoreUrl();
if (keystoreUrl == null) {
LOGGER.log(LogService.LOG_ERROR, "Specified keystore '" + keystore //$NON-NLS-1$
+ "' can not be found. SSL not initialized."); //$NON-NLS-1$
return;
}
LOGGER.log(LogService.LOG_DEBUG, "Keystore is '" + keystoreUrl + "'."); //$NON-NLS-1$ //$NON-NLS-2$
final char[] passwordChars = getPasswordChars(password, encrypt);
keyStore.load(keystoreUrl.openStream(), passwordChars);
// Some debug information
final Enumeration<String> enumeration = keyStore.aliases();
if (enumeration == null) {
LOGGER.log(LogService.LOG_ERROR, "Found no certificate."); //$NON-NLS-1$
throw new Exception("Found no certificate."); //$NON-NLS-1$
} else {
for (final String alias : Iter.able(enumeration)) {
LOGGER.log(LogService.LOG_DEBUG, "Found certificate: " + alias); //$NON-NLS-1$
final Certificate certificate = keyStore.getCertificate(alias);
if (certificate instanceof X509Certificate) {
final X509Certificate x509Certificate = (X509Certificate) certificate;
LOGGER.log(LogService.LOG_DEBUG, " Subject: " + x509Certificate.getSubjectDN()); //$NON-NLS-1$
LOGGER.log(LogService.LOG_DEBUG, " Issuer : " + x509Certificate.getIssuerDN()); //$NON-NLS-1$
LOGGER.log(LogService.LOG_DEBUG, " Valid from " + x509Certificate.getNotBefore() + " to " //$NON-NLS-1$ //$NON-NLS-2$
+ x509Certificate.getNotAfter());
} else {
LOGGER.log(LogService.LOG_DEBUG, " " + certificate); //$NON-NLS-1$
}
}
}
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); //$NON-NLS-1$
trustManagerFactory.init(keyStore);
final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
final SSLContext sslContext = SSLContext.getInstance("SSL"); //$NON-NLS-1$
sslContext.init(null, trustManagers, null);
LOGGER.log(LogService.LOG_DEBUG, "SSLContext protocol: " + sslContext.getProtocol()); //$NON-NLS-1$
LOGGER.log(LogService.LOG_DEBUG, "SSLContext SocketFactory: " + sslContext.getSocketFactory()); //$NON-NLS-1$
// save old value
previousSSLSocketFactor = HttpsURLConnection.getDefaultSSLSocketFactory();
// set new value
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
hostnameVerifier = sslProperties.createHostnameVerifier();
if (hostnameVerifier == null) {
hostnameVerifier = new StrictHostnameVerifier();
} else {
LOGGER.log(LogService.LOG_DEBUG, "Using custom host name verifier " //$NON-NLS-1$
+ hostnameVerifier.getClass().getName());
}
// save old value
previousHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
// set new value
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
LOGGER.log(LogService.LOG_INFO, "Configuring the SSL protocol finished!"); //$NON-NLS-1$
configured = true;
} catch (final Exception ex) {
LOGGER.log(LogService.LOG_ERROR, "Configuration of SSL protocol failed. SSL will not work properly!", ex); //$NON-NLS-1$
}
}
private char[] getPasswordChars(final String password, final String encrypt) throws Exception {
if (password == null) {
return null;
}
if (!Boolean.parseBoolean(encrypt)) {
return password.toCharArray();
}
final Cipher decrypt = CipherUtils.getCipher(null, Cipher.DECRYPT_MODE);
return new String(decrypt.doFinal(Base16Util.toBytes(password))).toCharArray();
}
/**
* @return
* @throws MalformedURLException
*/
private URL getKeystoreUrl() throws MalformedURLException {
if (keystore.equals(JRE_CACERTS_MARKER)) {
final String jreDir = System.getProperty("java.home"); //$NON-NLS-1$
LOGGER.log(LogService.LOG_DEBUG, "Attempting to load keystore from cacerts of the jre: " + jreDir); //$NON-NLS-1$
// walk down
final File cacertFile = new File(new File(new File(new File(jreDir), "lib"), "security"), "cacerts"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
return cacertFile.canRead() ? cacertFile.toURL() : null;
}
// maybe it is a entry?
URL keystoreUrl = contributingBundle.getEntry(keystore);
if (keystoreUrl != null) {
return keystoreUrl;
}
// maybe it is a resource?
keystoreUrl = contributingBundle.getResource(keystore);
if (keystoreUrl != null) {
return keystoreUrl;
}
// keystore location a file?
final File keystoreFile = new File(keystore);
if (keystoreFile.canRead()) {
return keystoreFile.toURL();
}
// and finally try as a url?
return new URL(keystore);
}
public boolean isConfigured() {
return configured;
}
/**
* Restore to previous settings.
*/
private void restore() {
if (previousHttpsProtocol != null) {
System.setProperty(HTTPS_PROTOCOLS_PROPERTY_KEY, previousHttpsProtocol);
}
if (previousSSLSocketFactor != null) {
HttpsURLConnection.setDefaultSSLSocketFactory(previousSSLSocketFactor);
}
if (previousHostnameVerifier != null) {
HttpsURLConnection.setDefaultHostnameVerifier(previousHostnameVerifier);
}
}
@Override
public String toString() {
return "SSLConfiguration [keystore=" + keystore + ", protocol=" + protocol + ", hostnameVerifier=" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ hostnameVerifier + "]"; //$NON-NLS-1$
}
public final static class StrictHostnameVerifier implements HostnameVerifier {
public boolean verify(final String hostname, final SSLSession session) {
LOGGER.log(LogService.LOG_ERROR, "Hostname '" + hostname //$NON-NLS-1$
+ "' does not match the certificate�s host name (" + session.getPeerHost() + ")!"); //$NON-NLS-1$ //$NON-NLS-2$
return false;
}
}
}