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.FileInputStream;
023import java.io.IOException;
024import java.io.InputStream;
025import java.security.KeyStore;
026
027import java.net.MalformedURLException;
028import java.net.URI;
029import java.net.URL;
030import java.security.SecureRandom;
031import javax.jms.JMSException;
032import javax.net.ssl.KeyManager;
033import javax.net.ssl.KeyManagerFactory;
034import javax.net.ssl.TrustManager;
035import javax.net.ssl.TrustManagerFactory;
036
037import org.apache.activemq.broker.BrokerService;
038import org.apache.activemq.broker.SslContext;
039import org.apache.activemq.transport.Transport;
040import org.apache.activemq.transport.tcp.SslTransportFactory;
041import org.apache.activemq.util.JMSExceptionSupport;
042import org.apache.commons.logging.Log;
043import 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 */
062public 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}