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      * If you use {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} consider using
125      * {@link OpenSslX509KeyManagerFactory} or {@link OpenSslCachingX509KeyManagerFactory}.
126      *
127      * @param keyManagerFactory non-{@code null} factory for server's private key
128      * @see #keyManager(KeyManagerFactory)
129      */
130     public static SslContextBuilder forServer(KeyManagerFactory keyManagerFactory) {
131         return new SslContextBuilder(true).keyManager(keyManagerFactory);
132     }
133 
134     private final boolean forServer;
135     private SslProvider provider;
136     private Provider sslContextProvider;
137     private X509Certificate[] trustCertCollection;
138     private TrustManagerFactory trustManagerFactory;
139     private X509Certificate[] keyCertChain;
140     private PrivateKey key;
141     private String keyPassword;
142     private KeyManagerFactory keyManagerFactory;
143     private Iterable<String> ciphers;
144     private CipherSuiteFilter cipherFilter = IdentityCipherSuiteFilter.INSTANCE;
145     private ApplicationProtocolConfig apn;
146     private long sessionCacheSize;
147     private long sessionTimeout;
148     private ClientAuth clientAuth = ClientAuth.NONE;
149     private String[] protocols;
150     private boolean startTls;
151     private boolean enableOcsp;
152 
153     private SslContextBuilder(boolean forServer) {
154         this.forServer = forServer;
155     }
156 
157     /**
158      * The {@link SslContext} implementation to use. {@code null} uses the default one.
159      */
160     public SslContextBuilder sslProvider(SslProvider provider) {
161         this.provider = provider;
162         return this;
163     }
164 
165     /**
166      * The SSLContext {@link Provider} to use. {@code null} uses the default one. This is only
167      * used with {@link SslProvider#JDK}.
168      */
169     public SslContextBuilder sslContextProvider(Provider sslContextProvider) {
170         this.sslContextProvider = sslContextProvider;
171         return this;
172     }
173 
174     /**
175      * Trusted certificates for verifying the remote endpoint's certificate. The file should
176      * contain an X.509 certificate collection in PEM format. {@code null} uses the system default.
177      */
178     public SslContextBuilder trustManager(File trustCertCollectionFile) {
179         try {
180             return trustManager(SslContext.toX509Certificates(trustCertCollectionFile));
181         } catch (Exception e) {
182             throw new IllegalArgumentException("File does not contain valid certificates: "
183                     + trustCertCollectionFile, e);
184         }
185     }
186 
187     /**
188      * Trusted certificates for verifying the remote endpoint's certificate. The input stream should
189      * contain an X.509 certificate collection in PEM format. {@code null} uses the system default.
190      */
191     public SslContextBuilder trustManager(InputStream trustCertCollectionInputStream) {
192         try {
193             return trustManager(SslContext.toX509Certificates(trustCertCollectionInputStream));
194         } catch (Exception e) {
195             throw new IllegalArgumentException("Input stream does not contain valid certificates.", e);
196         }
197     }
198 
199     /**
200      * Trusted certificates for verifying the remote endpoint's certificate, {@code null} uses the system default.
201      */
202     public SslContextBuilder trustManager(X509Certificate... trustCertCollection) {
203         this.trustCertCollection = trustCertCollection != null ? trustCertCollection.clone() : null;
204         trustManagerFactory = null;
205         return this;
206     }
207 
208     /**
209      * Trusted manager for verifying the remote endpoint's certificate. {@code null} uses the system default.
210      */
211     public SslContextBuilder trustManager(TrustManagerFactory trustManagerFactory) {
212         trustCertCollection = null;
213         this.trustManagerFactory = trustManagerFactory;
214         return this;
215     }
216 
217     /**
218      * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
219      * be {@code null} for client contexts, which disables mutual authentication.
220      *
221      * @param keyCertChainFile an X.509 certificate chain file in PEM format
222      * @param keyFile a PKCS#8 private key file in PEM format
223      */
224     public SslContextBuilder keyManager(File keyCertChainFile, File keyFile) {
225         return keyManager(keyCertChainFile, keyFile, null);
226     }
227 
228     /**
229      * Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may
230      * be {@code null} for client contexts, which disables mutual authentication.
231      *
232      * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
233      * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
234      */
235     public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream) {
236         return keyManager(keyCertChainInputStream, keyInputStream, null);
237     }
238 
239     /**
240      * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
241      * be {@code null} for client contexts, which disables mutual authentication.
242      *
243      * @param key a PKCS#8 private key
244      * @param keyCertChain an X.509 certificate chain
245      */
246     public SslContextBuilder keyManager(PrivateKey key, X509Certificate... keyCertChain) {
247         return keyManager(key, null, keyCertChain);
248     }
249 
250     /**
251      * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
252      * be {@code null} for client contexts, which disables mutual authentication.
253      *
254      * @param keyCertChainFile an X.509 certificate chain file in PEM format
255      * @param keyFile a PKCS#8 private key file in PEM format
256      * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
257      *     password-protected
258      */
259     public SslContextBuilder keyManager(File keyCertChainFile, File keyFile, String keyPassword) {
260         X509Certificate[] keyCertChain;
261         PrivateKey key;
262         try {
263             keyCertChain = SslContext.toX509Certificates(keyCertChainFile);
264         } catch (Exception e) {
265             throw new IllegalArgumentException("File does not contain valid certificates: " + keyCertChainFile, e);
266         }
267         try {
268             key = SslContext.toPrivateKey(keyFile, keyPassword);
269         } catch (Exception e) {
270             throw new IllegalArgumentException("File does not contain valid private key: " + keyFile, e);
271         }
272         return keyManager(key, keyPassword, keyCertChain);
273     }
274 
275     /**
276      * Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may
277      * be {@code null} for client contexts, which disables mutual authentication.
278      *
279      * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
280      * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
281      * @param keyPassword the password of the {@code keyInputStream}, or {@code null} if it's not
282      *     password-protected
283      */
284     public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream,
285             String keyPassword) {
286         X509Certificate[] keyCertChain;
287         PrivateKey key;
288         try {
289             keyCertChain = SslContext.toX509Certificates(keyCertChainInputStream);
290         } catch (Exception e) {
291             throw new IllegalArgumentException("Input stream not contain valid certificates.", e);
292         }
293         try {
294             key = SslContext.toPrivateKey(keyInputStream, keyPassword);
295         } catch (Exception e) {
296             throw new IllegalArgumentException("Input stream does not contain valid private key.", e);
297         }
298         return keyManager(key, keyPassword, keyCertChain);
299     }
300 
301     /**
302      * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
303      * be {@code null} for client contexts, which disables mutual authentication.
304      *
305      * @param key a PKCS#8 private key file
306      * @param keyPassword the password of the {@code key}, or {@code null} if it's not
307      *     password-protected
308      * @param keyCertChain an X.509 certificate chain
309      */
310     public SslContextBuilder keyManager(PrivateKey key, String keyPassword, X509Certificate... keyCertChain) {
311         if (forServer) {
312             checkNotNull(keyCertChain, "keyCertChain required for servers");
313             if (keyCertChain.length == 0) {
314                 throw new IllegalArgumentException("keyCertChain must be non-empty");
315             }
316             checkNotNull(key, "key required for servers");
317         }
318         if (keyCertChain == null || keyCertChain.length == 0) {
319             this.keyCertChain = null;
320         } else {
321             for (X509Certificate cert: keyCertChain) {
322                 if (cert == null) {
323                     throw new IllegalArgumentException("keyCertChain contains null entry");
324                 }
325             }
326             this.keyCertChain = keyCertChain.clone();
327         }
328         this.key = key;
329         this.keyPassword = keyPassword;
330         keyManagerFactory = null;
331         return this;
332     }
333 
334     /**
335      * Identifying manager for this host. {@code keyManagerFactory} may be {@code null} for
336      * client contexts, which disables mutual authentication. Using a {@link KeyManagerFactory}
337      * is only supported for {@link SslProvider#JDK} or {@link SslProvider#OPENSSL} / {@link SslProvider#OPENSSL_REFCNT}
338      * if the used openssl version is 1.0.1+. You can check if your openssl version supports using a
339      * {@link KeyManagerFactory} by calling {@link OpenSsl#supportsKeyManagerFactory()}. If this is not the case
340      * you must use {@link #keyManager(File, File)} or {@link #keyManager(File, File, String)}.
341      *
342      * If you use {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} consider using
343      * {@link OpenSslX509KeyManagerFactory} or {@link OpenSslCachingX509KeyManagerFactory}.
344      */
345     public SslContextBuilder keyManager(KeyManagerFactory keyManagerFactory) {
346         if (forServer) {
347             checkNotNull(keyManagerFactory, "keyManagerFactory required for servers");
348         }
349         keyCertChain = null;
350         key = null;
351         keyPassword = null;
352         this.keyManagerFactory = keyManagerFactory;
353         return this;
354     }
355 
356     /**
357      * The cipher suites to enable, in the order of preference. {@code null} to use default
358      * cipher suites.
359      */
360     public SslContextBuilder ciphers(Iterable<String> ciphers) {
361         return ciphers(ciphers, IdentityCipherSuiteFilter.INSTANCE);
362     }
363 
364     /**
365      * The cipher suites to enable, in the order of preference. {@code cipherFilter} will be
366      * applied to the ciphers before use. If {@code ciphers} is {@code null}, then the default
367      * cipher suites will be used.
368      */
369     public SslContextBuilder ciphers(Iterable<String> ciphers, CipherSuiteFilter cipherFilter) {
370         checkNotNull(cipherFilter, "cipherFilter");
371         this.ciphers = ciphers;
372         this.cipherFilter = cipherFilter;
373         return this;
374     }
375 
376     /**
377      * Application protocol negotiation configuration. {@code null} disables support.
378      */
379     public SslContextBuilder applicationProtocolConfig(ApplicationProtocolConfig apn) {
380         this.apn = apn;
381         return this;
382     }
383 
384     /**
385      * Set the size of the cache used for storing SSL session objects. {@code 0} to use the
386      * default value.
387      */
388     public SslContextBuilder sessionCacheSize(long sessionCacheSize) {
389         this.sessionCacheSize = sessionCacheSize;
390         return this;
391     }
392 
393     /**
394      * Set the timeout for the cached SSL session objects, in seconds. {@code 0} to use the
395      * default value.
396      */
397     public SslContextBuilder sessionTimeout(long sessionTimeout) {
398         this.sessionTimeout = sessionTimeout;
399         return this;
400     }
401 
402     /**
403      * Sets the client authentication mode.
404      */
405     public SslContextBuilder clientAuth(ClientAuth clientAuth) {
406         this.clientAuth = checkNotNull(clientAuth, "clientAuth");
407         return this;
408     }
409 
410     /**
411      * The TLS protocol versions to enable.
412      * @param protocols The protocols to enable, or {@code null} to enable the default protocols.
413      * @see SSLEngine#setEnabledCipherSuites(String[])
414      */
415     public SslContextBuilder protocols(String... protocols) {
416         this.protocols = protocols == null ? null : protocols.clone();
417         return this;
418     }
419 
420     /**
421      * {@code true} if the first write request shouldn't be encrypted.
422      */
423     public SslContextBuilder startTls(boolean startTls) {
424         this.startTls = startTls;
425         return this;
426     }
427 
428     /**
429      * Enables OCSP stapling. Please note that not all {@link SslProvider} implementations support OCSP
430      * stapling and an exception will be thrown upon {@link #build()}.
431      *
432      * @see OpenSsl#isOcspSupported()
433      */
434     @UnstableApi
435     public SslContextBuilder enableOcsp(boolean enableOcsp) {
436         this.enableOcsp = enableOcsp;
437         return this;
438     }
439 
440     /**
441      * Create new {@code SslContext} instance with configured settings.
442      * <p>If {@link #sslProvider(SslProvider)} is set to {@link SslProvider#OPENSSL_REFCNT} then the caller is
443      * responsible for releasing this object, or else native memory may leak.
444      */
445     public SslContext build() throws SSLException {
446         if (forServer) {
447             return SslContext.newServerContextInternal(provider, sslContextProvider, trustCertCollection,
448                 trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
449                 ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls,
450                 enableOcsp);
451         } else {
452             return SslContext.newClientContextInternal(provider, sslContextProvider, trustCertCollection,
453                 trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
454                 ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout, enableOcsp);
455         }
456     }
457 }