View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2014 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.caravan.commons.httpclient.impl.helpers;
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.security.GeneralSecurityException;
28  import java.security.KeyManagementException;
29  import java.security.KeyStore;
30  import java.security.NoSuchAlgorithmException;
31  
32  import javax.net.ssl.KeyManagerFactory;
33  import javax.net.ssl.SSLContext;
34  import javax.net.ssl.TrustManagerFactory;
35  
36  import org.apache.commons.lang3.StringUtils;
37  import org.apache.http.conn.ssl.SSLInitializationException;
38  import org.jetbrains.annotations.NotNull;
39  import org.jetbrains.annotations.Nullable;
40  
41  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
42  import io.wcm.caravan.commons.httpclient.HttpClientConfig;
43  
44  /**
45   * Helper class for loading certificates for SSL communication.
46   */
47  public final class CertificateLoader {
48  
49    /**
50     * Default SSL context type
51     */
52    public static final String SSL_CONTEXT_TYPE_DEFAULT = "TLS";
53  
54    /**
55     * Default key manager type
56     */
57    public static final String KEY_MANAGER_TYPE_DEFAULT = "SunX509";
58  
59    /**
60     * Default key store type
61     */
62    public static final String KEY_STORE_TYPE_DEFAULT = "PKCS12";
63  
64    /**
65     * Default trust manager type
66     */
67    public static final String TRUST_MANAGER_TYPE_DEFAULT = "SunX509";
68  
69    /**
70     * Default trust store type
71     */
72    public static final String TRUST_STORE_TYPE_DEFAULT = "JKS";
73  
74  
75    private CertificateLoader() {
76      // static methods only
77    }
78  
79    /**
80     * Build SSL Socket factory.
81     * @param config Http client configuration
82     * @return SSL socket factory.
83     * @throws IOException I/O exception
84     * @throws GeneralSecurityException General security exception
85     */
86    @SuppressWarnings("null")
87    @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") // null checks happen in separate methods
88    public static @NotNull SSLContext buildSSLContext(@NotNull HttpClientConfig config) throws IOException, GeneralSecurityException {
89  
90      KeyManagerFactory kmf = null;
91      if (isSslKeyManagerEnabled(config)) {
92        kmf = getKeyManagerFactory(config.getKeyStorePath(),
93            new StoreProperties(config.getKeyStorePassword(), config.getKeyManagerType(),
94                config.getKeyStoreType(), config.getKeyStoreProvider()));
95      }
96      TrustManagerFactory tmf = null;
97      if (isSslTrustStoreEnbaled(config)) {
98        tmf = getTrustManagerFactory(config.getTrustStorePath(), new StoreProperties(config.getTrustStorePassword(),
99            config.getTrustManagerType(), config.getTrustStoreType(), config.getTrustStoreProvider()));
100     }
101 
102     SSLContext sslContext = SSLContext.getInstance(config.getSslContextType());
103     sslContext.init(kmf != null ? kmf.getKeyManagers() : null,
104         tmf != null ? tmf.getTrustManagers() : null,
105         null);
106 
107     return sslContext;
108   }
109 
110   /**
111    * Get key manager factory
112    * @param keyStoreFilename Keystore file name
113    * @param storeProperties store properties
114    * @return Key manager factory
115    * @throws IOException I/O exception
116    * @throws GeneralSecurityException General security exception
117    */
118   public static @NotNull KeyManagerFactory getKeyManagerFactory(@NotNull String keyStoreFilename, @NotNull StoreProperties storeProperties)
119       throws IOException, GeneralSecurityException {
120     InputStream is = getResourceAsStream(keyStoreFilename);
121     if (is == null) {
122       throw new FileNotFoundException("Certificate file not found: " + getFilenameInfo(keyStoreFilename));
123     }
124     try {
125       return getKeyManagerFactory(is, storeProperties);
126     }
127     finally {
128       try {
129         is.close();
130       }
131       catch (IOException ex) {
132         // ignore
133       }
134     }
135   }
136 
137   /**
138    * Get key manager factory
139    * @param keyStoreStream Keystore input stream
140    * @param storeProperties store properties
141    * @return Key manager factory
142    * @throws IOException I/O exception
143    * @throws GeneralSecurityException General security exception
144    */
145   private static @NotNull KeyManagerFactory getKeyManagerFactory(@NotNull InputStream keyStoreStream, @NotNull StoreProperties storeProperties)
146       throws IOException, GeneralSecurityException {
147     // use provider if given, otherwise use the first matching security provider
148     final KeyStore ks;
149     if (StringUtils.isNotBlank(storeProperties.getProvider())) {
150       ks = KeyStore.getInstance(storeProperties.getType(), storeProperties.getProvider());
151     }
152     else {
153       ks = KeyStore.getInstance(storeProperties.getType());
154     }
155     ks.load(keyStoreStream, storeProperties.getPassword().toCharArray());
156     KeyManagerFactory kmf = KeyManagerFactory.getInstance(storeProperties.getManagerType());
157     kmf.init(ks, storeProperties.getPassword().toCharArray());
158     return kmf;
159   }
160 
161   /**
162    * Build TrustManagerFactory.
163    * @param trustStoreFilename Truststore file name
164    * @param storeProperties store properties
165    * @return TrustManagerFactory
166    * @throws IOException I/O exception
167    * @throws GeneralSecurityException General security exception
168    */
169   public static @NotNull TrustManagerFactory getTrustManagerFactory(@NotNull String trustStoreFilename, @NotNull StoreProperties storeProperties)
170       throws IOException, GeneralSecurityException {
171     InputStream is = getResourceAsStream(trustStoreFilename);
172     if (is == null) {
173       throw new FileNotFoundException("Certificate file not found: " + getFilenameInfo(trustStoreFilename));
174     }
175     try {
176       return getTrustManagerFactory(is, storeProperties);
177     }
178     finally {
179       try {
180         is.close();
181       }
182       catch (IOException ex) {
183         // ignore
184       }
185     }
186   }
187 
188   /**
189    * Build TrustManagerFactory.
190    * @param trustStoreStream Truststore input stream
191    * @param storeProperties store properties
192    * @return TrustManagerFactory
193    * @throws IOException I/O exception
194    * @throws GeneralSecurityException General security exception
195    */
196   private static @NotNull TrustManagerFactory getTrustManagerFactory(@NotNull InputStream trustStoreStream, @NotNull StoreProperties storeProperties)
197       throws IOException, GeneralSecurityException {
198 
199     // use provider if given, otherwise use the first matching security provider
200     final KeyStore ks;
201     if (StringUtils.isNotBlank(storeProperties.getProvider())) {
202       ks = KeyStore.getInstance(storeProperties.getType(), storeProperties.getProvider());
203     }
204     else {
205       ks = KeyStore.getInstance(storeProperties.getType());
206     }
207 
208     ks.load(trustStoreStream, storeProperties.getPassword().toCharArray());
209     TrustManagerFactory tmf = TrustManagerFactory.getInstance(storeProperties.getManagerType());
210     tmf.init(ks);
211     return tmf;
212   }
213 
214   /**
215    * Tries to load the given resource as file, or if no file exists as classpath resource.
216    * @param path Filesystem or classpath path
217    * @return InputStream or null if neither file nor classpath resource is found
218    * @throws IOException I/O exception
219    */
220   private static @Nullable InputStream getResourceAsStream(@NotNull String path) throws IOException {
221     if (StringUtils.isEmpty(path)) {
222       return null;
223     }
224 
225     // first try to load as file
226     File file = new File(path);
227     if (file.exists() && file.isFile()) {
228       return new FileInputStream(file);
229     }
230 
231     // if not a file fallback to classloader resource
232     return CertificateLoader.class.getResourceAsStream(path);
233   }
234 
235   /**
236    * Generate filename info for given path for error messages.
237    * @param path Path
238    * @return Absolute path
239    */
240   private static @Nullable String getFilenameInfo(@Nullable String path) {
241     if (StringUtils.isEmpty(path)) {
242       return null;
243     }
244     try {
245       return new File(path).getCanonicalPath();
246     }
247     catch (IOException ex) {
248       return new File(path).getAbsolutePath();
249     }
250   }
251 
252   /**
253    * Checks whether a SSL key store is configured.
254    * @param config Http client configuration
255    * @return true if client certificates are enabled
256    */
257   public static boolean isSslKeyManagerEnabled(@NotNull HttpClientConfig config) {
258     return StringUtils.isNotEmpty(config.getSslContextType())
259         && StringUtils.isNotEmpty(config.getKeyManagerType())
260         && StringUtils.isNotEmpty(config.getKeyStoreType())
261         && StringUtils.isNotEmpty(config.getKeyStorePath());
262   }
263 
264   /**
265    * Checks whether a SSL trust store is configured.
266    * @param config Http client configuration
267    * @return true if client certificates are enabled
268    */
269   public static boolean isSslTrustStoreEnbaled(@NotNull HttpClientConfig config) {
270     return StringUtils.isNotEmpty(config.getSslContextType())
271         && StringUtils.isNotEmpty(config.getTrustManagerType())
272         && StringUtils.isNotEmpty(config.getTrustStoreType())
273         && StringUtils.isNotEmpty(config.getTrustStorePath());
274   }
275 
276   /**
277    * Creates default SSL context.
278    * @return SSL context
279    */
280   public static @NotNull SSLContext createDefaultSSlContext() throws SSLInitializationException {
281     try {
282       final SSLContext sslcontext = SSLContext.getInstance(SSL_CONTEXT_TYPE_DEFAULT);
283       sslcontext.init(null, null, null);
284       return sslcontext;
285     }
286     catch (NoSuchAlgorithmException | KeyManagementException ex) {
287       throw new SSLInitializationException(ex.getMessage(), ex);
288     }
289   }
290 
291 }