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 }