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 trustStore;
066    protected String trustStorePassword;
067    protected String keyStore;
068    protected String keyStorePassword;
069    protected String keyStoreKeyPassword;
070
071    public ActiveMQSslConnectionFactory() {
072        super();
073    }
074
075    public ActiveMQSslConnectionFactory(String brokerURL) {
076        super(brokerURL);
077    }
078
079    public ActiveMQSslConnectionFactory(URI brokerURL) {
080        super(brokerURL);
081    }
082
083    /**
084     * Sets the key and trust managers used when creating SSL connections.
085     *
086     * @param km
087     *            The KeyManagers used.
088     * @param tm
089     *            The TrustManagers used.
090     * @param random
091     *            The SecureRandom number used.
092     */
093    public void setKeyAndTrustManagers(final KeyManager[] km, final TrustManager[] tm, final SecureRandom random) {
094        keyManager = km;
095        trustManager = tm;
096        secureRandom = random;
097    }
098
099    /**
100     * Overriding to make special considerations for SSL connections. If we are
101     * not using SSL, the superclass's method is called. If we are using SSL, an
102     * SslConnectionFactory is used and it is given the needed key and trust
103     * managers.
104     *
105     * @author sepandm@gmail.com
106     */
107    @Override
108    protected Transport createTransport() throws JMSException {
109        SslContext existing = SslContext.getCurrentSslContext();
110        try {
111            if (keyStore != null || trustStore != null) {
112                keyManager = createKeyManager();
113                trustManager = createTrustManager();
114            }
115            if (keyManager != null || trustManager != null) {
116                SslContext.setCurrentSslContext(new SslContext(keyManager, trustManager, secureRandom));
117            }
118            return super.createTransport();
119        } catch (Exception e) {
120            throw JMSExceptionSupport.create("Could not create Transport. Reason: " + e, e);
121        } finally {
122            SslContext.setCurrentSslContext(existing);
123        }
124    }
125
126    protected TrustManager[] createTrustManager() throws Exception {
127        TrustManager[] trustStoreManagers = null;
128        KeyStore trustedCertStore = KeyStore.getInstance("jks");
129
130        if (trustStore != null) {
131            try(InputStream tsStream = getInputStream(trustStore)) {
132
133                trustedCertStore.load(tsStream, trustStorePassword.toCharArray());
134                TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
135
136                tmf.init(trustedCertStore);
137                trustStoreManagers = tmf.getTrustManagers();
138            }
139        }
140        return trustStoreManagers;
141    }
142
143    protected KeyManager[] createKeyManager() throws Exception {
144        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
145        KeyStore ks = KeyStore.getInstance("jks");
146        KeyManager[] keystoreManagers = null;
147        if (keyStore != null) {
148            byte[] sslCert = loadClientCredential(keyStore);
149
150            if (sslCert != null && sslCert.length > 0) {
151                try(ByteArrayInputStream bin = new ByteArrayInputStream(sslCert)) {
152                    ks.load(bin, keyStorePassword.toCharArray());
153                    kmf.init(ks, keyStoreKeyPassword !=null ? keyStoreKeyPassword.toCharArray() : keyStorePassword.toCharArray());
154                    keystoreManagers = kmf.getKeyManagers();
155                }
156            }
157        }
158        return keystoreManagers;
159    }
160
161    protected byte[] loadClientCredential(String fileName) throws IOException {
162        if (fileName == null) {
163            return null;
164        }
165        try(InputStream in = getInputStream(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            return out.toByteArray();
174        }
175    }
176
177    protected InputStream getInputStream(String urlOrResource) throws IOException {
178        try {
179            File ifile = new File(urlOrResource);
180            // only open the file if and only if it exists
181            if (ifile.exists()) {
182                return new FileInputStream(ifile);
183            }
184        } catch (Exception e) {
185        }
186
187        InputStream ins = null;
188
189        try {
190            URL url = new URL(urlOrResource);
191            ins = url.openStream();
192            if (ins != null) {
193                return ins;
194            }
195        } catch (MalformedURLException ignore) {
196        }
197
198        // Alternatively, treat as classpath resource
199        if (ins == null) {
200            ins = Thread.currentThread().getContextClassLoader().getResourceAsStream(urlOrResource);
201        }
202
203        if (ins == null) {
204            throw new IOException("Could not load resource: " + urlOrResource);
205        }
206
207        return ins;
208    }
209
210    public String getTrustStore() {
211        return trustStore;
212    }
213
214    /**
215     * The location of a keystore file (in <code>jks</code> format) containing
216     * one or more trusted certificates.
217     *
218     * @param trustStore
219     *            If specified with a scheme, treat as a URL, otherwise treat as
220     *            a classpath resource.
221     */
222    public void setTrustStore(String trustStore) throws Exception {
223        this.trustStore = trustStore;
224        trustManager = null;
225    }
226
227    public String getTrustStorePassword() {
228        return trustStorePassword;
229    }
230
231    /**
232     * The password to match the trust store specified by {@link setTrustStore}.
233     *
234     * @param trustStorePassword
235     *            The password used to unlock the keystore file.
236     */
237    public void setTrustStorePassword(String trustStorePassword) {
238        this.trustStorePassword = trustStorePassword;
239    }
240
241    public String getKeyStore() {
242        return keyStore;
243    }
244
245    /**
246     * The location of a keystore file (in <code>jks</code> format) containing a
247     * certificate and its private key.
248     *
249     * @param keyStore
250     *            If specified with a scheme, treat as a URL, otherwise treat as
251     *            a classpath resource.
252     */
253    public void setKeyStore(String keyStore) throws Exception {
254        this.keyStore = keyStore;
255        keyManager = null;
256    }
257
258    public String getKeyStorePassword() {
259        return keyStorePassword;
260    }
261
262    /**
263     * The password to match the key store specified by {@link setKeyStore}.
264     *
265     * @param keyStorePassword
266     *            The password, which is used both to unlock the keystore file
267     *            and as the pass phrase for the private key stored in the
268     *            keystore.
269     */
270    public void setKeyStorePassword(String keyStorePassword) {
271        this.keyStorePassword = keyStorePassword;
272    }
273
274
275    public String getKeyStoreKeyPassword() {
276        return keyStoreKeyPassword;
277    }
278
279    /**
280     * The password to match the key from the keyStore.
281     *
282     * @param keyStoreKeyPassword
283     *            The password for the private key stored in the
284     *            keyStore if different from keyStorePassword.
285     */
286    public void setKeyStoreKeyPassword(String keyStoreKeyPassword) {
287        this.keyStoreKeyPassword = keyStoreKeyPassword;
288    }
289
290}