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.IOException;
023    import java.io.InputStream;
024    import java.net.MalformedURLException;
025    import java.net.URI;
026    import java.net.URL;
027    import java.security.KeyStore;
028    import java.security.SecureRandom;
029    
030    import javax.jms.JMSException;
031    import javax.net.ssl.KeyManager;
032    import javax.net.ssl.KeyManagerFactory;
033    import javax.net.ssl.TrustManager;
034    import javax.net.ssl.TrustManagerFactory;
035    
036    import org.apache.activemq.broker.SslContext;
037    import org.apache.activemq.transport.Transport;
038    import org.apache.activemq.util.JMSExceptionSupport;
039    
040    /**
041     * An ActiveMQConnectionFactory that allows access to the key and trust managers
042     * used for SslConnections. There is no reason to use this class unless SSL is
043     * being used AND the key and trust managers need to be specified from within
044     * code. In fact, if the URI passed to this class does not have an "ssl" scheme,
045     * this class will pass all work on to its superclass.
046     *
047     * There are two alternative approaches you can use to provide X.509 certificates
048     * for the SSL connections:
049     *
050     * Call <code>setTrustStore</code>, <code>setTrustStorePassword</code>, <code>setKeyStore</code>,
051     * and <code>setKeyStorePassword</code>.
052     *
053     * Call <code>setKeyAndTrustManagers</code>.
054     *
055     * @author sepandm@gmail.com
056     */
057    public class ActiveMQSslConnectionFactory extends ActiveMQConnectionFactory {
058    
059        // The key and trust managers used to initialize the used SSLContext.
060        protected KeyManager[] keyManager;
061        protected TrustManager[] trustManager;
062        protected SecureRandom secureRandom;
063        protected String trustStore;
064        protected String trustStorePassword;
065        protected String keyStore;
066        protected String keyStorePassword;
067    
068        public ActiveMQSslConnectionFactory() {
069            super();
070        }
071    
072        public ActiveMQSslConnectionFactory(String brokerURL) {
073            super(brokerURL);
074        }
075    
076        public ActiveMQSslConnectionFactory(URI brokerURL) {
077            super(brokerURL);
078        }
079    
080        /**
081         * Sets the key and trust managers used when creating SSL connections.
082         *
083         * @param km The KeyManagers used.
084         * @param tm The TrustManagers used.
085         * @param random The SecureRandom number used.
086         */
087        public void setKeyAndTrustManagers(final KeyManager[] km, final TrustManager[] tm, final SecureRandom random) {
088            keyManager = km;
089            trustManager = tm;
090            secureRandom = random;
091        }
092    
093        /**
094         * Overriding to make special considerations for SSL connections. If we are
095         * not using SSL, the superclass's method is called. If we are using SSL, an
096         * SslConnectionFactory is used and it is given the needed key and trust
097         * managers.
098         *
099         * @author sepandm@gmail.com
100         */
101        protected Transport createTransport() throws JMSException {
102            SslContext existing = SslContext.getCurrentSslContext();
103            try {
104                if (keyStore != null || trustStore != null) {
105                    keyManager = createKeyManager();
106                    trustManager = createTrustManager();
107                }
108                if (keyManager != null || trustManager != null) {
109                    SslContext.setCurrentSslContext(new SslContext(keyManager, trustManager, secureRandom));
110                }
111                return super.createTransport();
112            } catch (Exception e) {
113                throw JMSExceptionSupport.create("Could not create Transport. Reason: " + e, e);
114            } finally {
115                SslContext.setCurrentSslContext(existing);
116            }
117        }
118    
119        protected TrustManager[] createTrustManager() throws Exception {
120            TrustManager[] trustStoreManagers = null;
121            KeyStore trustedCertStore = KeyStore.getInstance("jks");
122    
123            if (trustStore != null) {
124                InputStream tsStream = getUrlOrResourceAsStream(trustStore);
125    
126                trustedCertStore.load(tsStream, trustStorePassword.toCharArray());
127                TrustManagerFactory tmf  =
128                        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
129    
130                tmf.init(trustedCertStore);
131                trustStoreManagers = tmf.getTrustManagers();
132            }
133            return trustStoreManagers;
134        }
135    
136        protected KeyManager[] createKeyManager() throws Exception {
137            KeyManagerFactory kmf =
138                KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
139            KeyStore ks = KeyStore.getInstance("jks");
140            KeyManager[] keystoreManagers = null;
141            if (keyStore != null) {
142                byte[] sslCert = loadClientCredential(keyStore);
143    
144                if (sslCert != null && sslCert.length > 0) {
145                    ByteArrayInputStream bin = new ByteArrayInputStream(sslCert);
146                    ks.load(bin, keyStorePassword.toCharArray());
147                    kmf.init(ks, keyStorePassword.toCharArray());
148                    keystoreManagers = kmf.getKeyManagers();
149                }
150            }
151            return keystoreManagers;
152        }
153    
154        protected byte[] loadClientCredential(String fileName) throws IOException {
155            if (fileName == null) {
156                return null;
157            }
158            InputStream in = getUrlOrResourceAsStream(fileName);
159            ByteArrayOutputStream out = new ByteArrayOutputStream();
160            byte[] buf = new byte[512];
161            int i = in.read(buf);
162            while (i  > 0) {
163                out.write(buf, 0, i);
164                i = in.read(buf);
165            }
166            in.close();
167            return out.toByteArray();
168        }
169    
170        protected InputStream getUrlOrResourceAsStream(String urlOrResource) throws IOException {
171            InputStream ins = null;
172            try {
173                URL url = new URL(urlOrResource);
174                ins = url.openStream();
175            }
176            catch (MalformedURLException ignore) {
177                ins = null;
178            }
179    
180            // Alternatively, treat as classpath resource
181            if (ins == null) {
182                ins = getClass().getClassLoader().getResourceAsStream(urlOrResource);
183            }
184    
185            if (ins == null) {
186                throw new java.io.IOException("Could not load resource: " + urlOrResource);
187            }
188    
189            return ins;
190        }
191    
192        public String getTrustStore() {
193            return trustStore;
194        }
195    
196        /**
197         * The location of a keystore file (in <code>jks</code> format) containing one or more
198         * trusted certificates.
199         *
200         * @param trustStore If specified with a scheme, treat as a URL, otherwise treat as a classpath resource.
201         */
202        public void setTrustStore(String trustStore) throws Exception {
203            this.trustStore = trustStore;
204            trustManager = null;
205        }
206    
207        public String getTrustStorePassword() {
208            return trustStorePassword;
209        }
210    
211        /**
212         * The password to match the trust store specified by {@link setTrustStore}.
213         *
214         * @param trustStorePassword The password used to unlock the keystore file.
215         */
216        public void setTrustStorePassword(String trustStorePassword) {
217            this.trustStorePassword = trustStorePassword;
218        }
219    
220        public String getKeyStore() {
221            return keyStore;
222        }
223    
224        /**
225         * The location of a keystore file (in <code>jks</code> format) containing a certificate
226         * and its private key.
227         *
228         * @param keyStore If specified with a scheme, treat as a URL, otherwise treat as a classpath resource.
229         */
230        public void setKeyStore(String keyStore) throws Exception {
231            this.keyStore = keyStore;
232            keyManager = null;
233        }
234    
235        public String getKeyStorePassword() {
236            return keyStorePassword;
237        }
238    
239        /**
240         * The password to match the key store specified by {@link setKeyStore}.
241         *
242         * @param keyStorePassword The password, which is used both to unlock the keystore file
243         * and as the pass phrase for the private key stored in the keystore.
244         */
245        public void setKeyStorePassword(String keyStorePassword) {
246            this.keyStorePassword = keyStorePassword;
247        }
248    
249    }