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    }