/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.agent;
import java.io.FileInputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* If you need to make a connection to an external HTTP server using the https protocol,
* use this class to build the connection. You can specify which trusted certificates can be used
* to verify and authenticate the remote server via truststore file parameters passed into the constructor.
* In other words, you don't have to rely on the global system properties javax.net.ssl.keyStore and
* javax.net.ssl.trustStore to make your connection.
*
* This is useful if some other object running in your JVM requires
* javax.net.ssl.keyStore/javax.net.ssl.trustStore set to something other than what you want to use.
* Since those system properties can only be set once during the lifetime of the JVM, it is not possible
* to change them at runtime and expect the change to take effect. Besides, even if you could change them,
* this could then effect the other objects that do need those properties set to particular values.
*
* This object has the ability to make an encrypted call without requiring a truststore to exist (that is,
* you can encrypt the call but you will not have the server endpoint verified and authenticated). This
* can be useful if you aren't concerned with authenticating the endpoint but do want to encrypt the
* traffic.
*
* @author John Mazzitelli
*/
public class SecureConnector {
private final String secureSocketProtocol;
private final AgentTrustStore agentTrustStore;
private final AgentKeystore agentKeystore;
/**
* The {@link #openSecureConnection(URL) secure connections} built by this object will
* not authenticate the server endpoint, but they will use the given secure socket protocol
* to encrypt the connection traffic.
*
* @param secureSocketProtocol the secure socket protocol to use (e.g. "TLS")
*/
public SecureConnector(String secureSocketProtocol) {
this(secureSocketProtocol, null, null);
}
/**
* The {@link #openSecureConnection(URL) secure connections} built by this object will
* authenticate the server endpoint using the given truststore and keystore.
* The connection will use the given secure socket protocol to encrypt the connection traffic.
*
* @param secureSocketProtocol the secure socket protocol to use (e.g. "TLS")
* @param agentTrustStore the truststore containing authorized certificates
* @param agentKeystore the keystore containing authorized keys
*/
public SecureConnector(String secureSocketProtocol, AgentTrustStore agentTrustStore, AgentKeystore agentKeystore) {
if (secureSocketProtocol == null) {
throw new IllegalArgumentException("secure socket protocol cannot be null");
}
this.secureSocketProtocol = secureSocketProtocol;
this.agentTrustStore = agentTrustStore;
if (agentTrustStore != null) {
if (agentTrustStore.getPassword() == null) {
throw new IllegalArgumentException("truststore password cannot be null");
}
if (agentTrustStore.getType() == null) {
agentTrustStore.setType(KeyStore.getDefaultType());
}
if (agentTrustStore.getAlgorithm() == null) {
agentTrustStore.setAlgorithm(TrustManagerFactory.getDefaultAlgorithm());
}
}
this.agentKeystore = agentKeystore;
if (agentKeystore != null) {
if (agentKeystore.getPassword() == null) {
throw new IllegalArgumentException("keystore password cannot be null");
}
if (agentKeystore.getKeyPassword() == null) {
throw new IllegalArgumentException("keystore key-password cannot be null");
}
if (agentKeystore.getType() == null) {
agentKeystore.setType(KeyStore.getDefaultType());
}
if (agentKeystore.getAlgorithm() == null) {
agentKeystore.setAlgorithm(TrustManagerFactory.getDefaultAlgorithm());
}
}
return;
}
public HttpsURLConnection openSecureConnection(URL url) throws Exception {
// we assume the URL is https - if it is not, its an error so just let the cast throw exception
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
TrustManager[] trustManagers;
SSLContext sslContext = SSLContext.getInstance(getSecureSocketProtocol());
KeyManager[] keyManagers = null;
if (getAgentTrustStore() == null) {
// we are configured to not care about authenticating the server, just encrypt but don't worry about certificates
trustManagers = new TrustManager[] { NO_OP_TRUST_MANAGER };
connection.setHostnameVerifier(NO_OP_HOSTNAME_VERIFIER);
} else {
// We need to configure our SSL connection with the agent's truststore so we can authenticate the server.
// First, create a KeyStore, but load it with our truststore entries.
KeyStore keyStore = KeyStore.getInstance(getAgentTrustStore().getType());
keyStore.load(new FileInputStream(getAgentTrustStore().getFile()), getAgentTrustStore().getPassword().toCharArray());
// now create a truststore manager instance and initialize it with our KeyStore we created with all our truststore entries
TrustManagerFactory tmf = TrustManagerFactory.getInstance(getAgentTrustStore().getAlgorithm());
tmf.init(keyStore);
trustManagers = tmf.getTrustManagers();
}
if (getAgentKeystore() != null) {
KeyStore keyStore = KeyStore.getInstance(getAgentKeystore().getType());
keyStore.load(new FileInputStream(getAgentKeystore().getFile()), getAgentKeystore().getPassword().toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(getAgentKeystore().getAlgorithm());
keyManagerFactory.init(keyStore, getAgentKeystore().getKeyPassword().toCharArray());
keyManagers = keyManagerFactory.getKeyManagers();
}
sslContext.init(keyManagers, trustManagers, null);
connection.setSSLSocketFactory(sslContext.getSocketFactory());
return connection;
}
public String getSecureSocketProtocol() {
return this.secureSocketProtocol;
}
public AgentTrustStore getAgentTrustStore() {
return agentTrustStore;
}
public AgentKeystore getAgentKeystore() {
return agentKeystore;
}
private static TrustManager NO_OP_TRUST_MANAGER = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
return;
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
return;
}
};
private static HostnameVerifier NO_OP_HOSTNAME_VERIFIER = new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
};
public static void main(String[] args) throws Exception {
HttpsURLConnection conn = new SecureConnector("TLS").openSecureConnection(new URL(args[0]));
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
java.io.InputStream input = new java.io.BufferedInputStream(conn.getInputStream(), 32768);
byte[] buffer = new byte[32768];
for (int bytesRead = input.read(buffer); bytesRead != -1; bytesRead = input.read(buffer)) {
out.write(buffer, 0, bytesRead);
}
out.flush();
System.out.println(out.toString());
}
}