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