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 io.netty.util.internal.UnstableApi;
20
21 import javax.net.ssl.KeyManager;
22 import javax.net.ssl.KeyManagerFactory;
23 import javax.net.ssl.SSLEngine;
24 import javax.net.ssl.SSLException;
25 import javax.net.ssl.TrustManager;
26 import javax.net.ssl.TrustManagerFactory;
27 import java.io.File;
28 import java.io.InputStream;
29 import java.security.KeyStore;
30 import java.security.PrivateKey;
31 import java.security.Provider;
32 import java.security.cert.X509Certificate;
33 import java.util.ArrayList;
34 import java.util.List;
35
36 import static io.netty.util.internal.EmptyArrays.EMPTY_STRINGS;
37 import static io.netty.util.internal.EmptyArrays.EMPTY_X509_CERTIFICATES;
38 import static io.netty.util.internal.ObjectUtil.checkNotNull;
39
40 /**
41  * Builder for configuring a new SslContext for creation.
42  */

43 public final class SslContextBuilder {
44
45     /**
46      * Creates a builder for new client-side {@link SslContext}.
47      */

48     public static SslContextBuilder forClient() {
49         return new SslContextBuilder(false);
50     }
51
52     /**
53      * Creates a builder for new server-side {@link SslContext}.
54      *
55      * @param keyCertChainFile an X.509 certificate chain file in PEM format
56      * @param keyFile a PKCS#8 private key file in PEM format
57      * @see #keyManager(File, File)
58      */

59     public static SslContextBuilder forServer(File keyCertChainFile, File keyFile) {
60         return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile);
61     }
62
63     /**
64      * Creates a builder for new server-side {@link SslContext}.
65      *
66      * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
67      * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
68      * @see #keyManager(InputStream, InputStream)
69      */

70     public static SslContextBuilder forServer(InputStream keyCertChainInputStream, InputStream keyInputStream) {
71         return new SslContextBuilder(true).keyManager(keyCertChainInputStream, keyInputStream);
72     }
73
74     /**
75      * Creates a builder for new server-side {@link SslContext}.
76      *
77      * @param key a PKCS#8 private key
78      * @param keyCertChain the X.509 certificate chain
79      * @see #keyManager(PrivateKey, X509Certificate[])
80      */

81     public static SslContextBuilder forServer(PrivateKey key, X509Certificate... keyCertChain) {
82         return new SslContextBuilder(true).keyManager(key, keyCertChain);
83     }
84
85     /**
86      * Creates a builder for new server-side {@link SslContext}.
87      *
88      * @param key a PKCS#8 private key
89      * @param keyCertChain the X.509 certificate chain
90      * @see #keyManager(PrivateKey, X509Certificate[])
91      */

92     public static SslContextBuilder forServer(PrivateKey key, Iterable<? extends X509Certificate> keyCertChain) {
93         return forServer(key, toArray(keyCertChain, EMPTY_X509_CERTIFICATES));
94     }
95
96     /**
97      * Creates a builder for new server-side {@link SslContext}.
98      *
99      * @param keyCertChainFile an X.509 certificate chain file in PEM format
100      * @param keyFile a PKCS#8 private key file in PEM format
101      * @param keyPassword the password of the {@code keyFile}, or {@code nullif it's not
102      *     password-protected
103      * @see #keyManager(File, File, String)
104      */

105     public static SslContextBuilder forServer(
106             File keyCertChainFile, File keyFile, String keyPassword) {
107         return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile, keyPassword);
108     }
109
110     /**
111      * Creates a builder for new server-side {@link SslContext}.
112      *
113      * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
114      * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
115      * @param keyPassword the password of the {@code keyFile}, or {@code nullif it's not
116      *     password-protected
117      * @see #keyManager(InputStream, InputStream, String)
118      */

119     public static SslContextBuilder forServer(
120             InputStream keyCertChainInputStream, InputStream keyInputStream, String keyPassword) {
121         return new SslContextBuilder(true).keyManager(keyCertChainInputStream, keyInputStream, keyPassword);
122     }
123
124     /**
125      * Creates a builder for new server-side {@link SslContext}.
126      *
127      * @param key a PKCS#8 private key
128      * @param keyCertChain the X.509 certificate chain
129      * @param keyPassword the password of the {@code keyFile}, or {@code nullif it's not
130      *     password-protected
131      * @see #keyManager(File, File, String)
132      */

133     public static SslContextBuilder forServer(
134             PrivateKey key, String keyPassword, X509Certificate... keyCertChain) {
135         return new SslContextBuilder(true).keyManager(key, keyPassword, keyCertChain);
136     }
137
138     /**
139      * Creates a builder for new server-side {@link SslContext}.
140      *
141      * @param key a PKCS#8 private key
142      * @param keyCertChain the X.509 certificate chain
143      * @param keyPassword the password of the {@code keyFile}, or {@code nullif it's not
144      *     password-protected
145      * @see #keyManager(File, File, String)
146      */

147     public static SslContextBuilder forServer(
148             PrivateKey key, String keyPassword, Iterable<? extends X509Certificate> keyCertChain) {
149         return forServer(key, keyPassword, toArray(keyCertChain, EMPTY_X509_CERTIFICATES));
150     }
151
152     /**
153      * Creates a builder for new server-side {@link SslContext}.
154      *
155      * If you use {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} consider using
156      * {@link OpenSslX509KeyManagerFactory} or {@link OpenSslCachingX509KeyManagerFactory}.
157      *
158      * @param keyManagerFactory non-{@code null} factory for server's private key
159      * @see #keyManager(KeyManagerFactory)
160      */

161     public static SslContextBuilder forServer(KeyManagerFactory keyManagerFactory) {
162         return new SslContextBuilder(true).keyManager(keyManagerFactory);
163     }
164
165     /**
166      * Creates a builder for new server-side {@link SslContext} with {@link KeyManager}.
167      *
168      * @param keyManager non-{@code null} KeyManager for server's private key
169      */

170     public static SslContextBuilder forServer(KeyManager keyManager) {
171         return new SslContextBuilder(true).keyManager(keyManager);
172     }
173
174     private final boolean forServer;
175     private SslProvider provider;
176     private Provider sslContextProvider;
177     private X509Certificate[] trustCertCollection;
178     private TrustManagerFactory trustManagerFactory;
179     private X509Certificate[] keyCertChain;
180     private PrivateKey key;
181     private String keyPassword;
182     private KeyManagerFactory keyManagerFactory;
183     private Iterable<String> ciphers;
184     private CipherSuiteFilter cipherFilter = IdentityCipherSuiteFilter.INSTANCE;
185     private ApplicationProtocolConfig apn;
186     private long sessionCacheSize;
187     private long sessionTimeout;
188     private ClientAuth clientAuth = ClientAuth.NONE;
189     private String[] protocols;
190     private boolean startTls;
191     private boolean enableOcsp;
192     private String keyStoreType = KeyStore.getDefaultType();
193
194     private SslContextBuilder(boolean forServer) {
195         this.forServer = forServer;
196     }
197
198     /**
199      * The {@link SslContext} implementation to use. {@code null} uses the default one.
200      */

201     public SslContextBuilder sslProvider(SslProvider provider) {
202         this.provider = provider;
203         return this;
204     }
205
206     /**
207      * Sets the {@link KeyStore} type that should be used. {@code null} uses the default one.
208      */

209     public SslContextBuilder keyStoreType(String keyStoreType) {
210         this.keyStoreType = keyStoreType;
211         return this;
212     }
213
214     /**
215      * The SSLContext {@link Provider} to use. {@code null} uses the default one. This is only
216      * used with {@link SslProvider#JDK}.
217      */

218     public SslContextBuilder sslContextProvider(Provider sslContextProvider) {
219         this.sslContextProvider = sslContextProvider;
220         return this;
221     }
222
223     /**
224      * Trusted certificates for verifying the remote endpoint's certificate. The file should
225      * contain an X.509 certificate collection in PEM format. {@code null} uses the system default.
226      */

227     public SslContextBuilder trustManager(File trustCertCollectionFile) {
228         try {
229             return trustManager(SslContext.toX509Certificates(trustCertCollectionFile));
230         } catch (Exception e) {
231             throw new IllegalArgumentException("File does not contain valid certificates: "
232                     + trustCertCollectionFile, e);
233         }
234     }
235
236     /**
237      * Trusted certificates for verifying the remote endpoint's certificate. The input stream should
238      * contain an X.509 certificate collection in PEM format. {@code null} uses the system default.
239      */

240     public SslContextBuilder trustManager(InputStream trustCertCollectionInputStream) {
241         try {
242             return trustManager(SslContext.toX509Certificates(trustCertCollectionInputStream));
243         } catch (Exception e) {
244             throw new IllegalArgumentException("Input stream does not contain valid certificates.", e);
245         }
246     }
247
248     /**
249      * Trusted certificates for verifying the remote endpoint's certificate, {@code null} uses the system default.
250      */

251     public SslContextBuilder trustManager(X509Certificate... trustCertCollection) {
252         this.trustCertCollection = trustCertCollection != null ? trustCertCollection.clone() : null;
253         trustManagerFactory = null;
254         return this;
255     }
256
257     /**
258      * Trusted certificates for verifying the remote endpoint's certificate, {@code null} uses the system default.
259      */

260     public SslContextBuilder trustManager(Iterable<? extends X509Certificate> trustCertCollection) {
261         return trustManager(toArray(trustCertCollection, EMPTY_X509_CERTIFICATES));
262     }
263
264     /**
265      * Trusted manager for verifying the remote endpoint's certificate. {@code null} uses the system default.
266      */

267     public SslContextBuilder trustManager(TrustManagerFactory trustManagerFactory) {
268         trustCertCollection = null;
269         this.trustManagerFactory = trustManagerFactory;
270         return this;
271     }
272
273     /**
274      * A single trusted manager for verifying the remote endpoint's certificate.
275      * This is helpful when custom implementation of {@link TrustManager} is needed.
276      * Internally, a simple wrapper of {@link TrustManagerFactory} that only produces this
277      * specified {@link TrustManager} will be created, thus all the requirements specified in
278      * {@link #trustManager(TrustManagerFactory trustManagerFactory)} also apply here.
279      */

280     public SslContextBuilder trustManager(TrustManager trustManager) {
281         this.trustManagerFactory = new TrustManagerFactoryWrapper(trustManager);
282         trustCertCollection = null;
283         return this;
284     }
285
286     /**
287      * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
288      * be {@code nullfor client contexts, which disables mutual authentication.
289      *
290      * @param keyCertChainFile an X.509 certificate chain file in PEM format
291      * @param keyFile a PKCS#8 private key file in PEM format
292      */

293     public SslContextBuilder keyManager(File keyCertChainFile, File keyFile) {
294         return keyManager(keyCertChainFile, keyFile, null);
295     }
296
297     /**
298      * Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may
299      * be {@code nullfor client contexts, which disables mutual authentication.
300      *
301      * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
302      * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
303      */

304     public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream) {
305         return keyManager(keyCertChainInputStream, keyInputStream, null);
306     }
307
308     /**
309      * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
310      * be {@code nullfor client contexts, which disables mutual authentication.
311      *
312      * @param key a PKCS#8 private key
313      * @param keyCertChain an X.509 certificate chain
314      */

315     public SslContextBuilder keyManager(PrivateKey key, X509Certificate... keyCertChain) {
316         return keyManager(key, null, keyCertChain);
317     }
318
319     /**
320      * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
321      * be {@code nullfor client contexts, which disables mutual authentication.
322      *
323      * @param key a PKCS#8 private key
324      * @param keyCertChain an X.509 certificate chain
325      */

326     public SslContextBuilder keyManager(PrivateKey key, Iterable<? extends X509Certificate> keyCertChain) {
327         return keyManager(key, toArray(keyCertChain, EMPTY_X509_CERTIFICATES));
328     }
329
330     /**
331      * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
332      * be {@code nullfor client contexts, which disables mutual authentication.
333      *
334      * @param keyCertChainFile an X.509 certificate chain file in PEM format
335      * @param keyFile a PKCS#8 private key file in PEM format
336      * @param keyPassword the password of the {@code keyFile}, or {@code nullif it's not
337      *     password-protected
338      */

339     public SslContextBuilder keyManager(File keyCertChainFile, File keyFile, String keyPassword) {
340         X509Certificate[] keyCertChain;
341         PrivateKey key;
342         try {
343             keyCertChain = SslContext.toX509Certificates(keyCertChainFile);
344         } catch (Exception e) {
345             throw new IllegalArgumentException("File does not contain valid certificates: " + keyCertChainFile, e);
346         }
347         try {
348             key = SslContext.toPrivateKey(keyFile, keyPassword);
349         } catch (Exception e) {
350             throw new IllegalArgumentException("File does not contain valid private key: " + keyFile, e);
351         }
352         return keyManager(key, keyPassword, keyCertChain);
353     }
354
355     /**
356      * Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may
357      * be {@code nullfor client contexts, which disables mutual authentication.
358      *
359      * @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
360      * @param keyInputStream an input stream for a PKCS#8 private key in PEM format
361      * @param keyPassword the password of the {@code keyInputStream}, or {@code nullif it's not
362      *     password-protected
363      */

364     public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream,
365             String keyPassword) {
366         X509Certificate[] keyCertChain;
367         PrivateKey key;
368         try {
369             keyCertChain = SslContext.toX509Certificates(keyCertChainInputStream);
370         } catch (Exception e) {
371             throw new IllegalArgumentException("Input stream not contain valid certificates.", e);
372         }
373         try {
374             key = SslContext.toPrivateKey(keyInputStream, keyPassword);
375         } catch (Exception e) {
376             throw new IllegalArgumentException("Input stream does not contain valid private key.", e);
377         }
378         return keyManager(key, keyPassword, keyCertChain);
379     }
380
381     /**
382      * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
383      * be {@code nullfor client contexts, which disables mutual authentication.
384      *
385      * @param key a PKCS#8 private key file
386      * @param keyPassword the password of the {@code key}, or {@code nullif it's not
387      *     password-protected
388      * @param keyCertChain an X.509 certificate chain
389      */

390     public SslContextBuilder keyManager(PrivateKey key, String keyPassword, X509Certificate... keyCertChain) {
391         if (forServer) {
392             checkNotNull(keyCertChain, "keyCertChain required for servers");
393             if (keyCertChain.length == 0) {
394                 throw new IllegalArgumentException("keyCertChain must be non-empty");
395             }
396             checkNotNull(key, "key required for servers");
397         }
398         if (keyCertChain == null || keyCertChain.length == 0) {
399             this.keyCertChain = null;
400         } else {
401             for (X509Certificate cert: keyCertChain) {
402                 if (cert == null) {
403                     throw new IllegalArgumentException("keyCertChain contains null entry");
404                 }
405             }
406             this.keyCertChain = keyCertChain.clone();
407         }
408         this.key = key;
409         this.keyPassword = keyPassword;
410         keyManagerFactory = null;
411         return this;
412     }
413
414     /**
415      * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
416      * be {@code nullfor client contexts, which disables mutual authentication.
417      *
418      * @param key a PKCS#8 private key file
419      * @param keyPassword the password of the {@code key}, or {@code nullif it's not
420      *     password-protected
421      * @param keyCertChain an X.509 certificate chain
422      */

423     public SslContextBuilder keyManager(PrivateKey key, String keyPassword,
424                                         Iterable<? extends X509Certificate> keyCertChain) {
425         return keyManager(key, keyPassword, toArray(keyCertChain, EMPTY_X509_CERTIFICATES));
426     }
427
428     /**
429      * Identifying manager for this host. {@code keyManagerFactory} may be {@code nullfor
430      * client contexts, which disables mutual authentication. Using a {@link KeyManagerFactory}
431      * is only supported for {@link SslProvider#JDK} or {@link SslProvider#OPENSSL} / {@link SslProvider#OPENSSL_REFCNT}
432      * if the used openssl version is 1.0.1+. You can check if your openssl version supports using a
433      * {@link KeyManagerFactory} by calling {@link OpenSsl#supportsKeyManagerFactory()}. If this is not the case
434      * you must use {@link #keyManager(File, File)} or {@link #keyManager(File, File, String)}.
435      *
436      * If you use {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} consider using
437      * {@link OpenSslX509KeyManagerFactory} or {@link OpenSslCachingX509KeyManagerFactory}.
438      */

439     public SslContextBuilder keyManager(KeyManagerFactory keyManagerFactory) {
440         if (forServer) {
441             checkNotNull(keyManagerFactory, "keyManagerFactory required for servers");
442         }
443         keyCertChain = null;
444         key = null;
445         keyPassword = null;
446         this.keyManagerFactory = keyManagerFactory;
447         return this;
448     }
449
450     /**
451      * A single key manager managing the identity information of this host.
452      * This is helpful when custom implementation of {@link KeyManager} is needed.
453      * Internally, a wrapper of {@link KeyManagerFactory} that only produces this specified
454      * {@link KeyManager} will be created, thus all the requirements specified in
455      * {@link #keyManager(KeyManagerFactory keyManagerFactory)} also apply here.
456      */

457     public SslContextBuilder keyManager(KeyManager keyManager) {
458         if (forServer) {
459             checkNotNull(keyManager, "keyManager required for servers");
460         }
461         if (keyManager != null) {
462             this.keyManagerFactory = new KeyManagerFactoryWrapper(keyManager);
463         } else {
464             this.keyManagerFactory = null;
465         }
466         keyCertChain = null;
467         key = null;
468         keyPassword = null;
469         return this;
470     }
471
472     /**
473      * The cipher suites to enable, in the order of preference. {@code null} to use default
474      * cipher suites.
475      */

476     public SslContextBuilder ciphers(Iterable<String> ciphers) {
477         return ciphers(ciphers, IdentityCipherSuiteFilter.INSTANCE);
478     }
479
480     /**
481      * The cipher suites to enable, in the order of preference. {@code cipherFilter} will be
482      * applied to the ciphers before use. If {@code ciphers} is {@code null}, then the default
483      * cipher suites will be used.
484      */

485     public SslContextBuilder ciphers(Iterable<String> ciphers, CipherSuiteFilter cipherFilter) {
486         this.cipherFilter = checkNotNull(cipherFilter, "cipherFilter");
487         this.ciphers = ciphers;
488         return this;
489     }
490
491     /**
492      * Application protocol negotiation configuration. {@code null} disables support.
493      */

494     public SslContextBuilder applicationProtocolConfig(ApplicationProtocolConfig apn) {
495         this.apn = apn;
496         return this;
497     }
498
499     /**
500      * Set the size of the cache used for storing SSL session objects. {@code 0} to use the
501      * default value.
502      */

503     public SslContextBuilder sessionCacheSize(long sessionCacheSize) {
504         this.sessionCacheSize = sessionCacheSize;
505         return this;
506     }
507
508     /**
509      * Set the timeout for the cached SSL session objects, in seconds. {@code 0} to use the
510      * default value.
511      */

512     public SslContextBuilder sessionTimeout(long sessionTimeout) {
513         this.sessionTimeout = sessionTimeout;
514         return this;
515     }
516
517     /**
518      * Sets the client authentication mode.
519      */

520     public SslContextBuilder clientAuth(ClientAuth clientAuth) {
521         this.clientAuth = checkNotNull(clientAuth, "clientAuth");
522         return this;
523     }
524
525     /**
526      * The TLS protocol versions to enable.
527      * @param protocols The protocols to enable, or {@code null} to enable the default protocols.
528      * @see SSLEngine#setEnabledCipherSuites(String[])
529      */

530     public SslContextBuilder protocols(String... protocols) {
531         this.protocols = protocols == null ? null : protocols.clone();
532         return this;
533     }
534
535     /**
536      * The TLS protocol versions to enable.
537      * @param protocols The protocols to enable, or {@code null} to enable the default protocols.
538      * @see SSLEngine#setEnabledCipherSuites(String[])
539      */

540     public SslContextBuilder protocols(Iterable<String> protocols) {
541         return protocols(toArray(protocols, EMPTY_STRINGS));
542     }
543
544     /**
545      * {@code trueif the first write request shouldn't be encrypted.
546      */

547     public SslContextBuilder startTls(boolean startTls) {
548         this.startTls = startTls;
549         return this;
550     }
551
552     /**
553      * Enables OCSP stapling. Please note that not all {@link SslProvider} implementations support OCSP
554      * stapling and an exception will be thrown upon {@link #build()}.
555      *
556      * @see OpenSsl#isOcspSupported()
557      */

558     @UnstableApi
559     public SslContextBuilder enableOcsp(boolean enableOcsp) {
560         this.enableOcsp = enableOcsp;
561         return this;
562     }
563
564     /**
565      * Create new {@code SslContext} instance with configured settings.
566      * <p>If {@link #sslProvider(SslProvider)} is set to {@link SslProvider#OPENSSL_REFCNT} then the caller is
567      * responsible for releasing this object, or else native memory may leak.
568      */

569     public SslContext build() throws SSLException {
570         if (forServer) {
571             return SslContext.newServerContextInternal(provider, sslContextProvider, trustCertCollection,
572                 trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
573                 ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls,
574                 enableOcsp, keyStoreType);
575         } else {
576             return SslContext.newClientContextInternal(provider, sslContextProvider, trustCertCollection,
577                 trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
578                 ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout, enableOcsp, keyStoreType);
579         }
580     }
581
582     private static <T> T[] toArray(Iterable<? extends T> iterable, T[] prototype) {
583         if (iterable == null) {
584             return null;
585         }
586         final List<T> list = new ArrayList<T>();
587         for (T element : iterable) {
588             list.add(element);
589         }
590         return list.toArray(prototype);
591     }
592 }
593