001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.activemq; 019 020import java.io.ByteArrayInputStream; 021import java.io.ByteArrayOutputStream; 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.IOException; 025import java.io.InputStream; 026import java.net.MalformedURLException; 027import java.net.URI; 028import java.net.URL; 029import java.security.KeyStore; 030import java.security.SecureRandom; 031 032import javax.jms.JMSException; 033import javax.net.ssl.KeyManager; 034import javax.net.ssl.KeyManagerFactory; 035import javax.net.ssl.TrustManager; 036import javax.net.ssl.TrustManagerFactory; 037 038import org.apache.activemq.broker.SslContext; 039import org.apache.activemq.transport.Transport; 040import org.apache.activemq.util.JMSExceptionSupport; 041 042/** 043 * An ActiveMQConnectionFactory that allows access to the key and trust managers 044 * used for SslConnections. There is no reason to use this class unless SSL is 045 * being used AND the key and trust managers need to be specified from within 046 * code. In fact, if the URI passed to this class does not have an "ssl" scheme, 047 * this class will pass all work on to its superclass. 048 * 049 * There are two alternative approaches you can use to provide X.509 050 * certificates for the SSL connections: 051 * 052 * Call <code>setTrustStore</code>, <code>setTrustStorePassword</code>, 053 * <code>setKeyStore</code>, and <code>setKeyStorePassword</code>. 054 * 055 * Call <code>setKeyAndTrustManagers</code>. 056 * 057 * @author sepandm@gmail.com 058 */ 059public class ActiveMQSslConnectionFactory extends ActiveMQConnectionFactory { 060 061 // The key and trust managers used to initialize the used SSLContext. 062 protected KeyManager[] keyManager; 063 protected TrustManager[] trustManager; 064 protected SecureRandom secureRandom; 065 protected String trustStore; 066 protected String trustStorePassword; 067 protected String keyStore; 068 protected String keyStorePassword; 069 protected String keyStoreKeyPassword; 070 071 public ActiveMQSslConnectionFactory() { 072 super(); 073 } 074 075 public ActiveMQSslConnectionFactory(String brokerURL) { 076 super(brokerURL); 077 } 078 079 public ActiveMQSslConnectionFactory(URI brokerURL) { 080 super(brokerURL); 081 } 082 083 /** 084 * Sets the key and trust managers used when creating SSL connections. 085 * 086 * @param km 087 * The KeyManagers used. 088 * @param tm 089 * The TrustManagers used. 090 * @param random 091 * The SecureRandom number used. 092 */ 093 public void setKeyAndTrustManagers(final KeyManager[] km, final TrustManager[] tm, final SecureRandom random) { 094 keyManager = km; 095 trustManager = tm; 096 secureRandom = random; 097 } 098 099 /** 100 * Overriding to make special considerations for SSL connections. If we are 101 * not using SSL, the superclass's method is called. If we are using SSL, an 102 * SslConnectionFactory is used and it is given the needed key and trust 103 * managers. 104 * 105 * @author sepandm@gmail.com 106 */ 107 @Override 108 protected Transport createTransport() throws JMSException { 109 SslContext existing = SslContext.getCurrentSslContext(); 110 try { 111 if (keyStore != null || trustStore != null) { 112 keyManager = createKeyManager(); 113 trustManager = createTrustManager(); 114 } 115 if (keyManager != null || trustManager != null) { 116 SslContext.setCurrentSslContext(new SslContext(keyManager, trustManager, secureRandom)); 117 } 118 return super.createTransport(); 119 } catch (Exception e) { 120 throw JMSExceptionSupport.create("Could not create Transport. Reason: " + e, e); 121 } finally { 122 SslContext.setCurrentSslContext(existing); 123 } 124 } 125 126 protected TrustManager[] createTrustManager() throws Exception { 127 TrustManager[] trustStoreManagers = null; 128 KeyStore trustedCertStore = KeyStore.getInstance("jks"); 129 130 if (trustStore != null) { 131 try(InputStream tsStream = getInputStream(trustStore)) { 132 133 trustedCertStore.load(tsStream, trustStorePassword.toCharArray()); 134 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 135 136 tmf.init(trustedCertStore); 137 trustStoreManagers = tmf.getTrustManagers(); 138 } 139 } 140 return trustStoreManagers; 141 } 142 143 protected KeyManager[] createKeyManager() throws Exception { 144 KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 145 KeyStore ks = KeyStore.getInstance("jks"); 146 KeyManager[] keystoreManagers = null; 147 if (keyStore != null) { 148 byte[] sslCert = loadClientCredential(keyStore); 149 150 if (sslCert != null && sslCert.length > 0) { 151 try(ByteArrayInputStream bin = new ByteArrayInputStream(sslCert)) { 152 ks.load(bin, keyStorePassword.toCharArray()); 153 kmf.init(ks, keyStoreKeyPassword !=null ? keyStoreKeyPassword.toCharArray() : keyStorePassword.toCharArray()); 154 keystoreManagers = kmf.getKeyManagers(); 155 } 156 } 157 } 158 return keystoreManagers; 159 } 160 161 protected byte[] loadClientCredential(String fileName) throws IOException { 162 if (fileName == null) { 163 return null; 164 } 165 try(InputStream in = getInputStream(fileName); 166 ByteArrayOutputStream out = new ByteArrayOutputStream()) { 167 byte[] buf = new byte[512]; 168 int i = in.read(buf); 169 while (i > 0) { 170 out.write(buf, 0, i); 171 i = in.read(buf); 172 } 173 return out.toByteArray(); 174 } 175 } 176 177 protected InputStream getInputStream(String urlOrResource) throws IOException { 178 try { 179 File ifile = new File(urlOrResource); 180 // only open the file if and only if it exists 181 if (ifile.exists()) { 182 return new FileInputStream(ifile); 183 } 184 } catch (Exception e) { 185 } 186 187 InputStream ins = null; 188 189 try { 190 URL url = new URL(urlOrResource); 191 ins = url.openStream(); 192 if (ins != null) { 193 return ins; 194 } 195 } catch (MalformedURLException ignore) { 196 } 197 198 // Alternatively, treat as classpath resource 199 if (ins == null) { 200 ins = Thread.currentThread().getContextClassLoader().getResourceAsStream(urlOrResource); 201 } 202 203 if (ins == null) { 204 throw new IOException("Could not load resource: " + urlOrResource); 205 } 206 207 return ins; 208 } 209 210 public String getTrustStore() { 211 return trustStore; 212 } 213 214 /** 215 * The location of a keystore file (in <code>jks</code> format) containing 216 * one or more trusted certificates. 217 * 218 * @param trustStore 219 * If specified with a scheme, treat as a URL, otherwise treat as 220 * a classpath resource. 221 */ 222 public void setTrustStore(String trustStore) throws Exception { 223 this.trustStore = trustStore; 224 trustManager = null; 225 } 226 227 public String getTrustStorePassword() { 228 return trustStorePassword; 229 } 230 231 /** 232 * The password to match the trust store specified by {@link setTrustStore}. 233 * 234 * @param trustStorePassword 235 * The password used to unlock the keystore file. 236 */ 237 public void setTrustStorePassword(String trustStorePassword) { 238 this.trustStorePassword = trustStorePassword; 239 } 240 241 public String getKeyStore() { 242 return keyStore; 243 } 244 245 /** 246 * The location of a keystore file (in <code>jks</code> format) containing a 247 * certificate and its private key. 248 * 249 * @param keyStore 250 * If specified with a scheme, treat as a URL, otherwise treat as 251 * a classpath resource. 252 */ 253 public void setKeyStore(String keyStore) throws Exception { 254 this.keyStore = keyStore; 255 keyManager = null; 256 } 257 258 public String getKeyStorePassword() { 259 return keyStorePassword; 260 } 261 262 /** 263 * The password to match the key store specified by {@link setKeyStore}. 264 * 265 * @param keyStorePassword 266 * The password, which is used both to unlock the keystore file 267 * and as the pass phrase for the private key stored in the 268 * keystore. 269 */ 270 public void setKeyStorePassword(String keyStorePassword) { 271 this.keyStorePassword = keyStorePassword; 272 } 273 274 275 public String getKeyStoreKeyPassword() { 276 return keyStoreKeyPassword; 277 } 278 279 /** 280 * The password to match the key from the keyStore. 281 * 282 * @param keyStoreKeyPassword 283 * The password for the private key stored in the 284 * keyStore if different from keyStorePassword. 285 */ 286 public void setKeyStoreKeyPassword(String keyStoreKeyPassword) { 287 this.keyStoreKeyPassword = keyStoreKeyPassword; 288 } 289 290}