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