View Javadoc
1   /*
2    * Copyright 2015 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package io.netty.handler.ssl;
18  
19  import static io.netty.util.internal.ObjectUtil.checkNotNull;
20  
21  import io.netty.util.internal.UnstableApi;
22  
23  import java.security.Provider;
24  import javax.net.ssl.KeyManagerFactory;
25  import javax.net.ssl.SSLException;
26  import javax.net.ssl.TrustManagerFactory;
27  
28  import java.io.File;
29  import java.io.InputStream;
30  import java.security.PrivateKey;
31  import java.security.cert.X509Certificate;
32  import javax.net.ssl.SSLEngine;
33  
34  /**
35   * Builder for configuring a new SslContext for creation.
36   */
37  public final class SslContextBuilder {
38  
39      /**
40       * Creates a builder for new client-side {@link SslContext}.
41       */
42      public static SslContextBuilder forClient() {
43          return new SslContextBuilder(false);
44      }
45  
46      /**
47       * Creates a builder for new server-side {@link SslContext}.
48       *
49       * @param keyCertChainFile an X.509 certificate chain file in PEM format
50       * @param keyFile a PKCS#8 private key file in PEM format
51       * @see #keyManager(File, File)
52       */
53      public static SslContextBuilder forServer(File keyCertChainFile, File keyFile) {
54          return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile);
55      }
56  
57      /**
58       * Creates a builder for new server-side {@link SslContext}.
59       *
60       * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
61       * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
62       * @see #keyManager(InputStream, InputStream)
63       */
64      public static SslContextBuilder forServer(InputStream keyCertChainInputStream, InputStream keyInputStream) {
65          return new SslContextBuilder(true).keyManager(keyCertChainInputStream, keyInputStream);
66      }
67  
68      /**
69       * Creates a builder for new server-side {@link SslContext}.
70       *
71       * @param key a PKCS#8 private key
72       * @param keyCertChain the X.509 certificate chain
73       * @see #keyManager(PrivateKey, X509Certificate[])
74       */
75      public static SslContextBuilder forServer(PrivateKey key, X509Certificate... keyCertChain) {
76          return new SslContextBuilder(true).keyManager(key, keyCertChain);
77      }
78  
79      /**
80       * Creates a builder for new server-side {@link SslContext}.
81       *
82       * @param keyCertChainFile an X.509 certificate chain file in PEM format
83       * @param keyFile a PKCS#8 private key file in PEM format
84       * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
85       *     password-protected
86       * @see #keyManager(File, File, String)
87       */
88      public static SslContextBuilder forServer(
89              File keyCertChainFile, File keyFile, String keyPassword) {
90          return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile, keyPassword);
91      }
92  
93      /**
94       * Creates a builder for new server-side {@link SslContext}.
95       *
96       * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
97       * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
98       * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
99       *     password-protected
100      * @see #keyManager(InputStream, InputStream, String)
101      */
102     public static SslContextBuilder forServer(
103             InputStream keyCertChainInputStream, InputStream keyInputStream, String keyPassword) {
104         return new SslContextBuilder(true).keyManager(keyCertChainInputStream, keyInputStream, keyPassword);
105     }
106 
107     /**
108      * Creates a builder for new server-side {@link SslContext}.
109      *
110      * @param key a PKCS#8 private key
111      * @param keyCertChain the X.509 certificate chain
112      * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
113      *     password-protected
114      * @see #keyManager(File, File, String)
115      */
116     public static SslContextBuilder forServer(
117             PrivateKey key, String keyPassword, X509Certificate... keyCertChain) {
118         return new SslContextBuilder(true).keyManager(key, keyPassword, keyCertChain);
119     }
120 
121     /**
122      * Creates a builder for new server-side {@link SslContext}.
123      *
124      * @param keyManagerFactory non-{@code null} factory for server's private key
125      * @see #keyManager(KeyManagerFactory)
126      */
127     public static SslContextBuilder forServer(KeyManagerFactory keyManagerFactory) {
128         return new SslContextBuilder(true).keyManager(keyManagerFactory);
129     }
130 
131     private final boolean forServer;
132     private SslProvider provider;
133     private Provider sslContextProvider;
134     private X509Certificate[] trustCertCollection;
135     private TrustManagerFactory trustManagerFactory;
136     private X509Certificate[] keyCertChain;
137     private PrivateKey key;
138     private String keyPassword;
139     private KeyManagerFactory keyManagerFactory;
140     private Iterable<String> ciphers;
141     private CipherSuiteFilter cipherFilter = IdentityCipherSuiteFilter.INSTANCE;
142     private ApplicationProtocolConfig apn;
143     private long sessionCacheSize;
144     private long sessionTimeout;
145     private ClientAuth clientAuth = ClientAuth.NONE;
146     private String[] protocols;
147     private boolean startTls;
148     private boolean enableOcsp;
149 
150     private SslContextBuilder(boolean forServer) {
151         this.forServer = forServer;
152     }
153 
154     /**
155      * The {@link SslContext} implementation to use. {@code null} uses the default one.
156      */
157     public SslContextBuilder sslProvider(SslProvider provider) {
158         this.provider = provider;
159         return this;
160     }
161 
162     /**
163      * The SSLContext {@link Provider} to use. {@code null} uses the default one. This is only
164      * used with {@link SslProvider#JDK}.
165      */
166     public SslContextBuilder sslContextProvider(Provider sslContextProvider) {
167         this.sslContextProvider = sslContextProvider;
168         return this;
169     }
170 
171     /**
172      * Trusted certificates for verifying the remote endpoint's certificate. The file should
173      * contain an X.509 certificate collection in PEM format. {@code null} uses the system default.
174      */
175     public SslContextBuilder trustManager(File trustCertCollectionFile) {
176         try {
177             return trustManager(SslContext.toX509Certificates(trustCertCollectionFile));
178         } catch (Exception e) {
179             throw new IllegalArgumentException("File does not contain valid certificates: "
180                     + trustCertCollectionFile, e);
181         }
182     }
183 
184     /**
185      * Trusted certificates for verifying the remote endpoint's certificate. The input stream should
186      * contain an X.509 certificate collection in PEM format. {@code null} uses the system default.
187      */
188     public SslContextBuilder trustManager(InputStream trustCertCollectionInputStream) {
189         try {
190             return trustManager(SslContext.toX509Certificates(trustCertCollectionInputStream));
191         } catch (Exception e) {
192             throw new IllegalArgumentException("Input stream does not contain valid certificates.", e);
193         }
194     }
195 
196     /**
197      * Trusted certificates for verifying the remote endpoint's certificate, {@code null} uses the system default.
198      */
199     public SslContextBuilder trustManager(X509Certificate... trustCertCollection) {
200         this.trustCertCollection = trustCertCollection != null ? trustCertCollection.clone() : null;
201         trustManagerFactory = null;
202         return this;
203     }
204 
205     /**
206      * Trusted manager for verifying the remote endpoint's certificate. {@code null} uses the system default.
207      */
208     public SslContextBuilder trustManager(TrustManagerFactory trustManagerFactory) {
209         trustCertCollection = null;
210         this.trustManagerFactory = trustManagerFactory;
211         return this;
212     }
213 
214     /**
215      * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
216      * be {@code null} for client contexts, which disables mutual authentication.
217      *
218      * @param keyCertChainFile an X.509 certificate chain file in PEM format
219      * @param keyFile a PKCS#8 private key file in PEM format
220      */
221     public SslContextBuilder keyManager(File keyCertChainFile, File keyFile) {
222         return keyManager(keyCertChainFile, keyFile, null);
223     }
224 
225     /**
226      * Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may
227      * be {@code null} for client contexts, which disables mutual authentication.
228      *
229      * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
230      * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
231      */
232     public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream) {
233         return keyManager(keyCertChainInputStream, keyInputStream, null);
234     }
235 
236     /**
237      * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
238      * be {@code null} for client contexts, which disables mutual authentication.
239      *
240      * @param key a PKCS#8 private key
241      * @param keyCertChain an X.509 certificate chain
242      */
243     public SslContextBuilder keyManager(PrivateKey key, X509Certificate... keyCertChain) {
244         return keyManager(key, null, keyCertChain);
245     }
246 
247     /**
248      * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
249      * be {@code null} for client contexts, which disables mutual authentication.
250      *
251      * @param keyCertChainFile an X.509 certificate chain file in PEM format
252      * @param keyFile a PKCS#8 private key file in PEM format
253      * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
254      *     password-protected
255      */
256     public SslContextBuilder keyManager(File keyCertChainFile, File keyFile, String keyPassword) {
257         X509Certificate[] keyCertChain;
258         PrivateKey key;
259         try {
260             keyCertChain = SslContext.toX509Certificates(keyCertChainFile);
261         } catch (Exception e) {
262             throw new IllegalArgumentException("File does not contain valid certificates: " + keyCertChainFile, e);
263         }
264         try {
265             key = SslContext.toPrivateKey(keyFile, keyPassword);
266         } catch (Exception e) {
267             throw new IllegalArgumentException("File does not contain valid private key: " + keyFile, e);
268         }
269         return keyManager(key, keyPassword, keyCertChain);
270     }
271 
272     /**
273      * Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may
274      * be {@code null} for client contexts, which disables mutual authentication.
275      *
276      * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
277      * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
278      * @param keyPassword the password of the {@code keyInputStream}, or {@code null} if it's not
279      *     password-protected
280      */
281     public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream,
282             String keyPassword) {
283         X509Certificate[] keyCertChain;
284         PrivateKey key;
285         try {
286             keyCertChain = SslContext.toX509Certificates(keyCertChainInputStream);
287         } catch (Exception e) {
288             throw new IllegalArgumentException("Input stream not contain valid certificates.", e);
289         }
290         try {
291             key = SslContext.toPrivateKey(keyInputStream, keyPassword);
292         } catch (Exception e) {
293             throw new IllegalArgumentException("Input stream does not contain valid private key.", e);
294         }
295         return keyManager(key, keyPassword, keyCertChain);
296     }
297 
298     /**
299      * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
300      * be {@code null} for client contexts, which disables mutual authentication.
301      *
302      * @param key a PKCS#8 private key file
303      * @param keyPassword the password of the {@code key}, or {@code null} if it's not
304      *     password-protected
305      * @param keyCertChain an X.509 certificate chain
306      */
307     public SslContextBuilder keyManager(PrivateKey key, String keyPassword, X509Certificate... keyCertChain) {
308         if (forServer) {
309             checkNotNull(keyCertChain, "keyCertChain required for servers");
310             if (keyCertChain.length == 0) {
311                 throw new IllegalArgumentException("keyCertChain must be non-empty");
312             }
313             checkNotNull(key, "key required for servers");
314         }
315         if (keyCertChain == null || keyCertChain.length == 0) {
316             this.keyCertChain = null;
317         } else {
318             for (X509Certificate cert: keyCertChain) {
319                 if (cert == null) {
320                     throw new IllegalArgumentException("keyCertChain contains null entry");
321                 }
322             }
323             this.keyCertChain = keyCertChain.clone();
324         }
325         this.key = key;
326         this.keyPassword = keyPassword;
327         keyManagerFactory = null;
328         return this;
329     }
330 
331     /**
332      * Identifying manager for this host. {@code keyManagerFactory} may be {@code null} for
333      * client contexts, which disables mutual authentication. Using a {@link KeyManagerFactory}
334      * is only supported for {@link SslProvider#JDK} or {@link SslProvider#OPENSSL} / {@link SslProvider#OPENSSL_REFCNT}
335      * if the used openssl version is 1.0.1+. You can check if your openssl version supports using a
336      * {@link KeyManagerFactory} by calling {@link OpenSsl#supportsKeyManagerFactory()}. If this is not the case
337      * you must use {@link #keyManager(File, File)} or {@link #keyManager(File, File, String)}.
338      */
339     public SslContextBuilder keyManager(KeyManagerFactory keyManagerFactory) {
340         if (forServer) {
341             checkNotNull(keyManagerFactory, "keyManagerFactory required for servers");
342         }
343         keyCertChain = null;
344         key = null;
345         keyPassword = null;
346         this.keyManagerFactory = keyManagerFactory;
347         return this;
348     }
349 
350     /**
351      * The cipher suites to enable, in the order of preference. {@code null} to use default
352      * cipher suites.
353      */
354     public SslContextBuilder ciphers(Iterable<String> ciphers) {
355         return ciphers(ciphers, IdentityCipherSuiteFilter.INSTANCE);
356     }
357 
358     /**
359      * The cipher suites to enable, in the order of preference. {@code cipherFilter} will be
360      * applied to the ciphers before use. If {@code ciphers} is {@code null}, then the default
361      * cipher suites will be used.
362      */
363     public SslContextBuilder ciphers(Iterable<String> ciphers, CipherSuiteFilter cipherFilter) {
364         checkNotNull(cipherFilter, "cipherFilter");
365         this.ciphers = ciphers;
366         this.cipherFilter = cipherFilter;
367         return this;
368     }
369 
370     /**
371      * Application protocol negotiation configuration. {@code null} disables support.
372      */
373     public SslContextBuilder applicationProtocolConfig(ApplicationProtocolConfig apn) {
374         this.apn = apn;
375         return this;
376     }
377 
378     /**
379      * Set the size of the cache used for storing SSL session objects. {@code 0} to use the
380      * default value.
381      */
382     public SslContextBuilder sessionCacheSize(long sessionCacheSize) {
383         this.sessionCacheSize = sessionCacheSize;
384         return this;
385     }
386 
387     /**
388      * Set the timeout for the cached SSL session objects, in seconds. {@code 0} to use the
389      * default value.
390      */
391     public SslContextBuilder sessionTimeout(long sessionTimeout) {
392         this.sessionTimeout = sessionTimeout;
393         return this;
394     }
395 
396     /**
397      * Sets the client authentication mode.
398      */
399     public SslContextBuilder clientAuth(ClientAuth clientAuth) {
400         this.clientAuth = checkNotNull(clientAuth, "clientAuth");
401         return this;
402     }
403 
404     /**
405      * The TLS protocol versions to enable.
406      * @param protocols The protocols to enable, or {@code null} to enable the default protocols.
407      * @see SSLEngine#setEnabledCipherSuites(String[])
408      */
409     public SslContextBuilder protocols(String... protocols) {
410         this.protocols = protocols == null ? null : protocols.clone();
411         return this;
412     }
413 
414     /**
415      * {@code true} if the first write request shouldn't be encrypted.
416      */
417     public SslContextBuilder startTls(boolean startTls) {
418         this.startTls = startTls;
419         return this;
420     }
421 
422     /**
423      * Enables OCSP stapling. Please note that not all {@link SslProvider} implementations support OCSP
424      * stapling and an exception will be thrown upon {@link #build()}.
425      *
426      * @see OpenSsl#isOcspSupported()
427      */
428     @UnstableApi
429     public SslContextBuilder enableOcsp(boolean enableOcsp) {
430         this.enableOcsp = enableOcsp;
431         return this;
432     }
433 
434     /**
435      * Create new {@code SslContext} instance with configured settings.
436      * <p>If {@link #sslProvider(SslProvider)} is set to {@link SslProvider#OPENSSL_REFCNT} then the caller is
437      * responsible for releasing this object, or else native memory may leak.
438      */
439     public SslContext build() throws SSLException {
440         if (forServer) {
441             return SslContext.newServerContextInternal(provider, sslContextProvider, trustCertCollection,
442                 trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
443                 ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls,
444                 enableOcsp);
445         } else {
446             return SslContext.newClientContextInternal(provider, sslContextProvider, trustCertCollection,
447                 trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
448                 ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout, enableOcsp);
449         }
450     }
451 }