View Javadoc
1   /*
2    * Copyright 2021 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    *   https://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.codec.quic;
18  
19  import io.netty.handler.ssl.ClientAuth;
20  import io.netty.handler.ssl.SslContextOption;
21  import io.netty.handler.ssl.util.KeyManagerFactoryWrapper;
22  import io.netty.handler.ssl.util.TrustManagerFactoryWrapper;
23  import io.netty.util.Mapping;
24  import org.jetbrains.annotations.Nullable;
25  
26  import javax.net.ssl.KeyManager;
27  import javax.net.ssl.KeyManagerFactory;
28  import javax.net.ssl.SSLEngine;
29  import javax.net.ssl.SSLParameters;
30  import javax.net.ssl.TrustManager;
31  import javax.net.ssl.TrustManagerFactory;
32  import javax.net.ssl.X509ExtendedKeyManager;
33  import java.io.File;
34  import java.net.Socket;
35  import java.security.KeyStore;
36  import java.security.Principal;
37  import java.security.PrivateKey;
38  import java.security.cert.X509Certificate;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.HashMap;
42  import java.util.List;
43  import java.util.Map;
44  
45  import static io.netty.util.internal.ObjectUtil.checkNotNull;
46  
47  /**
48   * Builder for configuring a new SslContext for creation.
49   */
50  public final class QuicSslContextBuilder {
51  
52      /**
53       * Special {@link X509ExtendedKeyManager} implementation which will just fail the certificate selection.
54       * This is used as a "dummy" implementation when SNI is used as we should always select an other
55       * {@link QuicSslContext} based on the provided hostname.
56       */
57      private static final X509ExtendedKeyManager SNI_KEYMANAGER = new X509ExtendedKeyManager() {
58          private final X509Certificate[] emptyCerts = new X509Certificate[0];
59          private final String[] emptyStrings = new String[0];
60  
61          @Override
62          public String[] getClientAliases(String keyType, Principal[] issuers) {
63              return emptyStrings;
64          }
65  
66          @Override
67          @Nullable
68          public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
69              return null;
70          }
71  
72          @Override
73          public String[] getServerAliases(String keyType, Principal[] issuers) {
74              return emptyStrings;
75          }
76  
77          @Override
78          @Nullable
79          public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
80              return null;
81          }
82  
83          @Override
84          public X509Certificate[] getCertificateChain(String alias) {
85              return emptyCerts;
86          }
87  
88          @Override
89          @Nullable
90          public PrivateKey getPrivateKey(String alias) {
91              return null;
92          }
93      };
94  
95      /**
96       * Creates a builder for new client-side {@link QuicSslContext} that can be used for {@code QUIC}.
97       */
98      public static QuicSslContextBuilder forClient() {
99          return new QuicSslContextBuilder(false);
100     }
101 
102     /**
103      * Creates a builder for new server-side {@link QuicSslContext} that can be used for {@code QUIC}.
104      *
105      * @param keyFile a PKCS#8 private key file in PEM format
106      * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
107      *     password-protected
108      * @param certChainFile an X.509 certificate chain file in PEM format
109      * @see #keyManager(File, String, File)
110      */
111     public static QuicSslContextBuilder forServer(
112             File keyFile, @Nullable String keyPassword, File certChainFile) {
113         return new QuicSslContextBuilder(true).keyManager(keyFile, keyPassword, certChainFile);
114     }
115 
116     /**
117      * Creates a builder for new server-side {@link QuicSslContext} that can be used for {@code QUIC}.
118      *
119      * @param key a PKCS#8 private key
120      * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
121      *     password-protected
122      * @param certChain the X.509 certificate chain
123      * @see #keyManager(File, String, File)
124      */
125     public static QuicSslContextBuilder forServer(
126             PrivateKey key, @Nullable String keyPassword, X509Certificate... certChain) {
127         return new QuicSslContextBuilder(true).keyManager(key, keyPassword, certChain);
128     }
129 
130     /**
131      * Creates a builder for new server-side {@link QuicSslContext} that can be used for {@code QUIC}.
132      *
133      * @param keyManagerFactory non-{@code null} factory for server's private key
134      * @see #keyManager(KeyManagerFactory, String)
135      */
136     public static QuicSslContextBuilder forServer(KeyManagerFactory keyManagerFactory, @Nullable String password) {
137         return new QuicSslContextBuilder(true).keyManager(keyManagerFactory, password);
138     }
139 
140     /**
141      * Creates a builder for new server-side {@link QuicSslContext} with {@link KeyManager} that can be used for
142      * {@code QUIC}.
143      *
144      * @param keyManager non-{@code null} KeyManager for server's private key
145      * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
146      *     password-protected
147      */
148     public static QuicSslContextBuilder forServer(KeyManager keyManager, @Nullable String keyPassword) {
149         return new QuicSslContextBuilder(true).keyManager(keyManager, keyPassword);
150     }
151 
152     /**
153      * Enables support for
154      * <a href="https://quicwg.org/ops-drafts/draft-ietf-quic-manageability.html#name-server-name-indication-sni">
155      *     SNI</a> on the server side.
156      *
157      * @param mapping   the {@link Mapping} that is used to map names to the {@link QuicSslContext} to use.
158      *                  Usually using {@link io.netty.util.DomainWildcardMappingBuilder} should be used
159      *                  to create the {@link Mapping}.
160      */
161     public static QuicSslContext buildForServerWithSni(Mapping<? super String, ? extends QuicSslContext> mapping) {
162         return forServer(SNI_KEYMANAGER, null).sni(mapping).build();
163     }
164 
165     private static final Map.Entry[] EMPTY_ENTRIES = new Map.Entry[0];
166 
167     private final boolean forServer;
168     private final Map<SslContextOption<?>, Object> options = new HashMap<>();
169     private TrustManagerFactory trustManagerFactory;
170     // The certificates trustManagerFactory was built from, or null if it was not built from plain certificates.
171     private X509Certificate[] trustCertCollection;
172     private String keyPassword;
173     private KeyManagerFactory keyManagerFactory;
174     // The certificate chain keyManagerFactory was built from, or null if it was not built from a plain chain.
175     private X509Certificate[] keyCertChain;
176     private long sessionCacheSize = 20480;
177     private long sessionTimeout = 300;
178     private ClientAuth clientAuth = ClientAuth.NONE;
179     private String[] applicationProtocols;
180     private Boolean earlyData;
181     private BoringSSLKeylog keylog;
182     private Mapping<? super String, ? extends QuicSslContext> mapping;
183     private String endpointIdentificationAlgorithm;
184 
185     private QuicSslContextBuilder(boolean forServer) {
186         this.forServer = forServer;
187         if (!forServer) {
188              this.endpointIdentificationAlgorithm = QuicheQuicSslContext.defaultEndpointVerificationAlgorithm;
189         }
190     }
191 
192     /**
193      * Enables
194      * <a href="https://quicwg.org/ops-drafts/draft-ietf-quic-manageability.html#name-server-name-indication-sni">
195      *     SNI</a> support on the server side.
196      * <p>
197      * The provided {@link Mapping} receives the hostname from the client and returns the
198      * {@link QuicSslContext} to use for that connection. The returned context's settings
199      * (such as {@link #clientAuth(ClientAuth)}, {@link #trustManager(TrustManagerFactory)}, etc.)
200      * will be applied to the connection.
201      * <p>
202      * Use {@link io.netty.util.DomainWildcardMappingBuilder} to create the {@link Mapping} when
203      * matching against domain patterns is needed.
204      *
205      * @param mapping the {@link Mapping} that maps hostnames to {@link QuicSslContext} instances
206      * @return this builder
207      * @throws NullPointerException if {@code mapping} is {@code null}
208      */
209     public QuicSslContextBuilder sni(Mapping<? super String, ? extends QuicSslContext> mapping) {
210         if (!forServer) {
211             throw new UnsupportedOperationException("Only supported for server");
212         }
213         this.mapping = checkNotNull(mapping, "mapping");
214         return this;
215     }
216 
217     /**
218      * Configure a {@link SslContextOption}.
219      */
220     public <T> QuicSslContextBuilder option(SslContextOption<T> option, T value) {
221         if (value == null) {
222             options.remove(option);
223         } else {
224             options.put(option, value);
225         }
226         return this;
227     }
228 
229     /**
230      * Enable / disable the usage of early data.
231      * <p>
232      * Early data is never accepted by a server that is configured with {@link ClientAuth#REQUIRE}, independent of
233      * this setting, as the client certificate can not be verified before the handshake completed.
234      */
235     public QuicSslContextBuilder earlyData(boolean enabled) {
236         this.earlyData = enabled;
237         return this;
238     }
239 
240     /**
241      * Enable / disable keylog. When enabled, TLS keys are logged to an internal logger named
242      * "io.netty.handler.codec.quic.BoringSSLLogginKeylog" with DEBUG level, see
243      * {@link BoringSSLKeylog} for detail, logging keys are following
244      * <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">
245      *     NSS Key Log Format</a>. This is intended for debugging use with tools like Wireshark.
246      */
247     public QuicSslContextBuilder keylog(boolean enabled) {
248         keylog(enabled ? BoringSSLLoggingKeylog.INSTANCE : null);
249         return this;
250     }
251 
252     /**
253      * Enable / disable keylog. When enabled, TLS keys are logged to {@link BoringSSLKeylog#logKey(SSLEngine, String)}
254      * logging keys are following
255      * <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">
256      *     NSS Key Log Format</a>. This is intended for debugging use with tools like Wireshark.
257      */
258     public QuicSslContextBuilder keylog(@Nullable BoringSSLKeylog keylog) {
259         this.keylog = keylog;
260         return this;
261     }
262 
263     /**
264      * Trusted certificates for verifying the remote endpoint's certificate. The file should
265      * contain an X.509 certificate collection in PEM format. {@code null} uses the system default
266      * which only works with Java 8u261 and later as these versions support TLS1.3,
267      * see <a href="https://www.oracle.com/java/technologies/javase/8u261-relnotes.html">
268      *     JDK 8u261 Update Release Notes</a>
269      */
270     public QuicSslContextBuilder trustManager(@Nullable File trustCertCollectionFile) {
271         try {
272             return trustManager(QuicheQuicSslContext.toX509Certificates0(trustCertCollectionFile));
273         } catch (Exception e) {
274             throw new IllegalArgumentException("File does not contain valid certificates: "
275                     + trustCertCollectionFile, e);
276         }
277     }
278 
279     /**
280      * Trusted certificates for verifying the remote endpoint's certificate. {@code null} uses the system default
281      * which only works with Java 8u261 and later as these versions support TLS1.3,
282      * see <a href="https://www.oracle.com/java/technologies/javase/8u261-relnotes.html">
283      *     JDK 8u261 Update Release Notes</a>
284      */
285     public QuicSslContextBuilder trustManager(X509Certificate @Nullable ... trustCertCollection) {
286         try {
287             trustManager(QuicheQuicSslContext.buildTrustManagerFactory0(trustCertCollection));
288         } catch (Exception e) {
289             throw new IllegalArgumentException(e);
290         }
291         // Remember the certificates as (unlike a custom TrustManagerFactory) they can be used to tell if two
292         // contexts trust the same certificates. Needs to be done after the TrustManagerFactory was set as this
293         // forgets about them.
294         this.trustCertCollection = trustCertCollection == null ? null :
295             Arrays.copyOf(trustCertCollection, trustCertCollection.length);
296         return this;
297     }
298 
299     /**
300      * Trusted manager for verifying the remote endpoint's certificate. {@code null} uses the system default
301      * which only works with Java 8u261 and later as these versions support TLS1.3,
302      * see <a href="https://www.oracle.com/java/technologies/javase/8u261-relnotes.html">
303      *     JDK 8u261 Update Release Notes</a>
304      */
305     public QuicSslContextBuilder trustManager(@Nullable TrustManagerFactory trustManagerFactory) {
306         this.trustManagerFactory = trustManagerFactory;
307         this.trustCertCollection = null;
308         return this;
309     }
310 
311     /**
312      * A single trusted manager for verifying the remote endpoint's certificate.
313      * This is helpful when custom implementation of {@link TrustManager} is needed.
314      * Internally, a simple wrapper of {@link TrustManagerFactory} that only produces this
315      * specified {@link TrustManager} will be created, thus all the requirements specified in
316      * {@link #trustManager(TrustManagerFactory trustManagerFactory)} also apply here.
317      */
318     public QuicSslContextBuilder trustManager(TrustManager trustManager) {
319         return trustManager(new TrustManagerFactoryWrapper(trustManager));
320     }
321 
322     /**
323      * Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
324      * be {@code null} for client contexts, which disables mutual authentication.
325      *
326      * @param keyFile a PKCS#8 private key file in PEM format
327      * @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
328      *     password-protected
329      * @param keyCertChainFile an X.509 certificate chain file in PEM format
330      */
331     public QuicSslContextBuilder keyManager(
332             @Nullable File keyFile, @Nullable String keyPassword, @Nullable File keyCertChainFile) {
333         X509Certificate[] keyCertChain;
334         PrivateKey key;
335         try {
336             keyCertChain = QuicheQuicSslContext.toX509Certificates0(keyCertChainFile);
337         } catch (Exception e) {
338             throw new IllegalArgumentException("File does not contain valid certificates: " + keyCertChainFile, e);
339         }
340         try {
341             key = QuicheQuicSslContext.toPrivateKey0(keyFile, keyPassword);
342         } catch (Exception e) {
343             throw new IllegalArgumentException("File does not contain valid private key: " + keyFile, e);
344         }
345         return keyManager(key, keyPassword, keyCertChain);
346     }
347 
348     /**
349      * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
350      * be {@code null} for client contexts, which disables mutual authentication.
351      *
352      * @param key a PKCS#8 private key file
353      * @param keyPassword the password of the {@code key}, or {@code null} if it's not
354      *     password-protected
355      * @param certChain an X.509 certificate chain
356      */
357     public QuicSslContextBuilder keyManager(
358             @Nullable PrivateKey key, @Nullable String keyPassword, X509Certificate @Nullable ... certChain) {
359         try {
360             java.security.KeyStore ks = java.security.KeyStore.getInstance(KeyStore.getDefaultType());
361             ks.load(null);
362             char[] pass = keyPassword == null ? new char[0]: keyPassword.toCharArray();
363             ks.setKeyEntry("alias", key, pass, certChain);
364             KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
365                     KeyManagerFactory.getDefaultAlgorithm());
366             keyManagerFactory.init(ks, pass);
367             keyManager(keyManagerFactory, keyPassword);
368         } catch (Exception e) {
369             throw new IllegalArgumentException(e);
370         }
371         // Remember the chain as (unlike a custom KeyManagerFactory) it can be used to tell if two contexts present
372         // the same certificates. Needs to be done after the KeyManagerFactory was set as this forgets about it.
373         this.keyCertChain = certChain == null ? null : Arrays.copyOf(certChain, certChain.length);
374         return this;
375     }
376 
377     /**
378      * Identifying manager for this host. {@code keyManagerFactory} may be {@code null} for
379      * client contexts, which disables mutual authentication.
380      */
381     public QuicSslContextBuilder keyManager(
382             @Nullable KeyManagerFactory keyManagerFactory, @Nullable String keyPassword) {
383         this.keyPassword = keyPassword;
384         this.keyManagerFactory = keyManagerFactory;
385         this.keyCertChain = null;
386         return this;
387     }
388 
389     /**
390      * A single key manager managing the identity information of this host.
391      * This is helpful when custom implementation of {@link KeyManager} is needed.
392      * Internally, a wrapper of {@link KeyManagerFactory} that only produces this specified
393      * {@link KeyManager} will be created, thus all the requirements specified in
394      * {@link #keyManager(KeyManagerFactory, String)} also apply here.
395      */
396     public QuicSslContextBuilder keyManager(KeyManager keyManager, @Nullable String password) {
397         return keyManager(new KeyManagerFactoryWrapper(keyManager), password);
398     }
399 
400     /**
401      * Application protocol negotiation configuration. {@code null} disables support.
402      */
403     public QuicSslContextBuilder applicationProtocols(String @Nullable ... applicationProtocols) {
404         this.applicationProtocols = applicationProtocols == null ? null :
405             Arrays.copyOf(applicationProtocols, applicationProtocols.length);
406         return this;
407     }
408 
409     /**
410      * Set the size of the cache used for storing SSL session objects. {@code 0} to use the
411      * default value.
412      */
413     public QuicSslContextBuilder sessionCacheSize(long sessionCacheSize) {
414         this.sessionCacheSize = sessionCacheSize;
415         return this;
416     }
417 
418     /**
419      * Set the timeout for the cached SSL session objects, in seconds. {@code 0} to use the
420      * default value.
421      */
422     public QuicSslContextBuilder sessionTimeout(long sessionTimeout) {
423         this.sessionTimeout = sessionTimeout;
424         return this;
425     }
426 
427     /**
428      * Sets the client authentication mode.
429      */
430     public QuicSslContextBuilder clientAuth(ClientAuth clientAuth) {
431         if (!forServer) {
432             throw new UnsupportedOperationException("Only supported for server");
433         }
434         this.clientAuth = checkNotNull(clientAuth, "clientAuth");
435         return this;
436     }
437 
438     /**
439      * Specify the endpoint identification algorithm (aka. hostname verification algorithm) that clients will use as
440      * part of authenticating servers.
441      * <p>
442      * See <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#jssenames">
443      *     Java Security Standard Names</a> for a list of supported algorithms.
444      *
445      * @param algorithm either {@code "HTTPS"}, {@code "LDAPS"}, or {@code null} (disables hostname verification).
446      * @see SSLParameters#setEndpointIdentificationAlgorithm(String)
447      */
448     public QuicSslContextBuilder endpointIdentificationAlgorithm(String algorithm) {
449         endpointIdentificationAlgorithm = algorithm;
450         return this;
451     }
452 
453     /**
454      * Create new {@link QuicSslContext} instance with configured settings that can be used for {@code QUIC}.
455      *
456      */
457     public QuicSslContext build() {
458         if (forServer) {
459             return new QuicheQuicSslContext(true, sessionTimeout, sessionCacheSize, clientAuth, trustManagerFactory,
460                     trustCertCollection, keyManagerFactory, keyCertChain, keyPassword, mapping, earlyData, keylog,
461                     applicationProtocols, null, toArray(options.entrySet(), EMPTY_ENTRIES));
462         } else {
463             return new QuicheQuicSslContext(false, sessionTimeout, sessionCacheSize, clientAuth, trustManagerFactory,
464                     trustCertCollection, keyManagerFactory, keyCertChain, keyPassword, mapping, earlyData, keylog,
465                     applicationProtocols, endpointIdentificationAlgorithm, toArray(options.entrySet(), EMPTY_ENTRIES));
466         }
467     }
468 
469     private static <T> T[] toArray(Iterable<? extends T> iterable, T[] prototype) {
470         if (iterable == null) {
471             return null;
472         }
473         final List<T> list = new ArrayList<>();
474         for (T element : iterable) {
475             list.add(element);
476         }
477         return list.toArray(prototype);
478     }
479 }