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
018 package org.apache.activemq;
019
020 import java.io.ByteArrayInputStream;
021 import java.io.ByteArrayOutputStream;
022 import java.io.File;
023 import java.io.FileInputStream;
024 import java.io.IOException;
025 import java.io.InputStream;
026 import java.net.MalformedURLException;
027 import java.net.URI;
028 import java.net.URL;
029 import java.security.KeyStore;
030 import java.security.SecureRandom;
031
032 import javax.jms.JMSException;
033 import javax.net.ssl.KeyManager;
034 import javax.net.ssl.KeyManagerFactory;
035 import javax.net.ssl.TrustManager;
036 import javax.net.ssl.TrustManagerFactory;
037
038 import org.apache.activemq.broker.SslContext;
039 import org.apache.activemq.transport.Transport;
040 import 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 */
059 public 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
070 public ActiveMQSslConnectionFactory() {
071 super();
072 }
073
074 public ActiveMQSslConnectionFactory(String brokerURL) {
075 super(brokerURL);
076 }
077
078 public ActiveMQSslConnectionFactory(URI brokerURL) {
079 super(brokerURL);
080 }
081
082 /**
083 * Sets the key and trust managers used when creating SSL connections.
084 *
085 * @param km
086 * The KeyManagers used.
087 * @param tm
088 * The TrustManagers used.
089 * @param random
090 * The SecureRandom number used.
091 */
092 public void setKeyAndTrustManagers(final KeyManager[] km, final TrustManager[] tm, final SecureRandom random) {
093 keyManager = km;
094 trustManager = tm;
095 secureRandom = random;
096 }
097
098 /**
099 * Overriding to make special considerations for SSL connections. If we are
100 * not using SSL, the superclass's method is called. If we are using SSL, an
101 * SslConnectionFactory is used and it is given the needed key and trust
102 * managers.
103 *
104 * @author sepandm@gmail.com
105 */
106 @Override
107 protected Transport createTransport() throws JMSException {
108 SslContext existing = SslContext.getCurrentSslContext();
109 try {
110 if (keyStore != null || trustStore != null) {
111 keyManager = createKeyManager();
112 trustManager = createTrustManager();
113 }
114 if (keyManager != null || trustManager != null) {
115 SslContext.setCurrentSslContext(new SslContext(keyManager, trustManager, secureRandom));
116 }
117 return super.createTransport();
118 } catch (Exception e) {
119 throw JMSExceptionSupport.create("Could not create Transport. Reason: " + e, e);
120 } finally {
121 SslContext.setCurrentSslContext(existing);
122 }
123 }
124
125 protected TrustManager[] createTrustManager() throws Exception {
126 TrustManager[] trustStoreManagers = null;
127 KeyStore trustedCertStore = KeyStore.getInstance("jks");
128
129 if (trustStore != null) {
130 InputStream tsStream = getInputStream(trustStore);
131
132 trustedCertStore.load(tsStream, trustStorePassword.toCharArray());
133 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
134
135 tmf.init(trustedCertStore);
136 trustStoreManagers = tmf.getTrustManagers();
137 }
138 return trustStoreManagers;
139 }
140
141 protected KeyManager[] createKeyManager() throws Exception {
142 KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
143 KeyStore ks = KeyStore.getInstance("jks");
144 KeyManager[] keystoreManagers = null;
145 if (keyStore != null) {
146 byte[] sslCert = loadClientCredential(keyStore);
147
148 if (sslCert != null && sslCert.length > 0) {
149 ByteArrayInputStream bin = new ByteArrayInputStream(sslCert);
150 ks.load(bin, keyStorePassword.toCharArray());
151 kmf.init(ks, keyStorePassword.toCharArray());
152 keystoreManagers = kmf.getKeyManagers();
153 }
154 }
155 return keystoreManagers;
156 }
157
158 protected byte[] loadClientCredential(String fileName) throws IOException {
159 if (fileName == null) {
160 return null;
161 }
162 InputStream in = getInputStream(fileName);
163 ByteArrayOutputStream out = new ByteArrayOutputStream();
164 byte[] buf = new byte[512];
165 int i = in.read(buf);
166 while (i > 0) {
167 out.write(buf, 0, i);
168 i = in.read(buf);
169 }
170 in.close();
171 return out.toByteArray();
172 }
173
174 protected InputStream getInputStream(String urlOrResource) throws IOException {
175 try {
176 File ifile = new File(urlOrResource);
177 // only open the file if and only if it exists
178 if (ifile.exists()) {
179 return new FileInputStream(ifile);
180 }
181 } catch (Exception e) {
182 }
183
184 InputStream ins = null;
185
186 try {
187 URL url = new URL(urlOrResource);
188 ins = url.openStream();
189 if (ins != null) {
190 return ins;
191 }
192 } catch (MalformedURLException ignore) {
193 }
194
195 // Alternatively, treat as classpath resource
196 if (ins == null) {
197 ins = Thread.currentThread().getContextClassLoader().getResourceAsStream(urlOrResource);
198 }
199
200 if (ins == null) {
201 throw new IOException("Could not load resource: " + urlOrResource);
202 }
203
204 return ins;
205 }
206
207 public String getTrustStore() {
208 return trustStore;
209 }
210
211 /**
212 * The location of a keystore file (in <code>jks</code> format) containing
213 * one or more trusted certificates.
214 *
215 * @param trustStore
216 * If specified with a scheme, treat as a URL, otherwise treat as
217 * a classpath resource.
218 */
219 public void setTrustStore(String trustStore) throws Exception {
220 this.trustStore = trustStore;
221 trustManager = null;
222 }
223
224 public String getTrustStorePassword() {
225 return trustStorePassword;
226 }
227
228 /**
229 * The password to match the trust store specified by {@link setTrustStore}.
230 *
231 * @param trustStorePassword
232 * The password used to unlock the keystore file.
233 */
234 public void setTrustStorePassword(String trustStorePassword) {
235 this.trustStorePassword = trustStorePassword;
236 }
237
238 public String getKeyStore() {
239 return keyStore;
240 }
241
242 /**
243 * The location of a keystore file (in <code>jks</code> format) containing a
244 * certificate and its private key.
245 *
246 * @param keyStore
247 * If specified with a scheme, treat as a URL, otherwise treat as
248 * a classpath resource.
249 */
250 public void setKeyStore(String keyStore) throws Exception {
251 this.keyStore = keyStore;
252 keyManager = null;
253 }
254
255 public String getKeyStorePassword() {
256 return keyStorePassword;
257 }
258
259 /**
260 * The password to match the key store specified by {@link setKeyStore}.
261 *
262 * @param keyStorePassword
263 * The password, which is used both to unlock the keystore file
264 * and as the pass phrase for the private key stored in the
265 * keystore.
266 */
267 public void setKeyStorePassword(String keyStorePassword) {
268 this.keyStorePassword = keyStorePassword;
269 }
270
271 }