1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.TrustManager;
30 import javax.net.ssl.TrustManagerFactory;
31 import javax.net.ssl.X509ExtendedKeyManager;
32 import java.io.File;
33 import java.net.Socket;
34 import java.security.KeyStore;
35 import java.security.Principal;
36 import java.security.PrivateKey;
37 import java.security.cert.X509Certificate;
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42
43 import static io.netty.util.internal.ObjectUtil.checkNotNull;
44
45
46
47
48 public final class QuicSslContextBuilder {
49
50
51
52
53
54
55 private static final X509ExtendedKeyManager SNI_KEYMANAGER = new X509ExtendedKeyManager() {
56 private final X509Certificate[] emptyCerts = new X509Certificate[0];
57 private final String[] emptyStrings = new String[0];
58
59 @Override
60 public String[] getClientAliases(String keyType, Principal[] issuers) {
61 return emptyStrings;
62 }
63
64 @Override
65 @Nullable
66 public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
67 return null;
68 }
69
70 @Override
71 public String[] getServerAliases(String keyType, Principal[] issuers) {
72 return emptyStrings;
73 }
74
75 @Override
76 @Nullable
77 public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
78 return null;
79 }
80
81 @Override
82 public X509Certificate[] getCertificateChain(String alias) {
83 return emptyCerts;
84 }
85
86 @Override
87 @Nullable
88 public PrivateKey getPrivateKey(String alias) {
89 return null;
90 }
91 };
92
93
94
95
96 public static QuicSslContextBuilder forClient() {
97 return new QuicSslContextBuilder(false);
98 }
99
100
101
102
103
104
105
106
107
108
109 public static QuicSslContextBuilder forServer(
110 File keyFile, @Nullable String keyPassword, File certChainFile) {
111 return new QuicSslContextBuilder(true).keyManager(keyFile, keyPassword, certChainFile);
112 }
113
114
115
116
117
118
119
120
121
122
123 public static QuicSslContextBuilder forServer(
124 PrivateKey key, @Nullable String keyPassword, X509Certificate... certChain) {
125 return new QuicSslContextBuilder(true).keyManager(key, keyPassword, certChain);
126 }
127
128
129
130
131
132
133
134 public static QuicSslContextBuilder forServer(KeyManagerFactory keyManagerFactory, @Nullable String password) {
135 return new QuicSslContextBuilder(true).keyManager(keyManagerFactory, password);
136 }
137
138
139
140
141
142
143
144
145
146 public static QuicSslContextBuilder forServer(KeyManager keyManager, @Nullable String keyPassword) {
147 return new QuicSslContextBuilder(true).keyManager(keyManager, keyPassword);
148 }
149
150
151
152
153
154
155
156
157
158
159 public static QuicSslContext buildForServerWithSni(Mapping<? super String, ? extends QuicSslContext> mapping) {
160 return forServer(SNI_KEYMANAGER, null).sni(mapping).build();
161 }
162
163 private static final Map.Entry[] EMPTY_ENTRIES = new Map.Entry[0];
164
165 private final boolean forServer;
166 private final Map<SslContextOption<?>, Object> options = new HashMap<>();
167 private TrustManagerFactory trustManagerFactory;
168 private String keyPassword;
169 private KeyManagerFactory keyManagerFactory;
170 private long sessionCacheSize = 20480;
171 private long sessionTimeout = 300;
172 private ClientAuth clientAuth = ClientAuth.NONE;
173 private String[] applicationProtocols;
174 private Boolean earlyData;
175 private BoringSSLKeylog keylog;
176 private Mapping<? super String, ? extends QuicSslContext> mapping;
177
178 private QuicSslContextBuilder(boolean forServer) {
179 this.forServer = forServer;
180 }
181
182 private QuicSslContextBuilder sni(Mapping<? super String, ? extends QuicSslContext> mapping) {
183 this.mapping = checkNotNull(mapping, "mapping");
184 return this;
185 }
186
187
188
189
190 public <T> QuicSslContextBuilder option(SslContextOption<T> option, T value) {
191 if (value == null) {
192 options.remove(option);
193 } else {
194 options.put(option, value);
195 }
196 return this;
197 }
198
199
200
201
202 public QuicSslContextBuilder earlyData(boolean enabled) {
203 this.earlyData = enabled;
204 return this;
205 }
206
207
208
209
210
211
212
213
214 public QuicSslContextBuilder keylog(boolean enabled) {
215 keylog(enabled ? BoringSSLLoggingKeylog.INSTANCE : null);
216 return this;
217 }
218
219
220
221
222
223
224
225 public QuicSslContextBuilder keylog(@Nullable BoringSSLKeylog keylog) {
226 this.keylog = keylog;
227 return this;
228 }
229
230
231
232
233
234
235
236
237 public QuicSslContextBuilder trustManager(@Nullable File trustCertCollectionFile) {
238 try {
239 return trustManager(QuicheQuicSslContext.toX509Certificates0(trustCertCollectionFile));
240 } catch (Exception e) {
241 throw new IllegalArgumentException("File does not contain valid certificates: "
242 + trustCertCollectionFile, e);
243 }
244 }
245
246
247
248
249
250
251
252 public QuicSslContextBuilder trustManager(X509Certificate @Nullable ... trustCertCollection) {
253 try {
254 return trustManager(QuicheQuicSslContext.buildTrustManagerFactory0(trustCertCollection));
255 } catch (Exception e) {
256 throw new IllegalArgumentException(e);
257 }
258 }
259
260
261
262
263
264
265
266 public QuicSslContextBuilder trustManager(@Nullable TrustManagerFactory trustManagerFactory) {
267 this.trustManagerFactory = trustManagerFactory;
268 return this;
269 }
270
271
272
273
274
275
276
277
278 public QuicSslContextBuilder trustManager(TrustManager trustManager) {
279 return trustManager(new TrustManagerFactoryWrapper(trustManager));
280 }
281
282
283
284
285
286
287
288
289
290
291 public QuicSslContextBuilder keyManager(
292 @Nullable File keyFile, @Nullable String keyPassword, @Nullable File keyCertChainFile) {
293 X509Certificate[] keyCertChain;
294 PrivateKey key;
295 try {
296 keyCertChain = QuicheQuicSslContext.toX509Certificates0(keyCertChainFile);
297 } catch (Exception e) {
298 throw new IllegalArgumentException("File does not contain valid certificates: " + keyCertChainFile, e);
299 }
300 try {
301 key = QuicheQuicSslContext.toPrivateKey0(keyFile, keyPassword);
302 } catch (Exception e) {
303 throw new IllegalArgumentException("File does not contain valid private key: " + keyFile, e);
304 }
305 return keyManager(key, keyPassword, keyCertChain);
306 }
307
308
309
310
311
312
313
314
315
316
317 public QuicSslContextBuilder keyManager(
318 @Nullable PrivateKey key, @Nullable String keyPassword, X509Certificate @Nullable ... certChain) {
319 try {
320 java.security.KeyStore ks = java.security.KeyStore.getInstance(KeyStore.getDefaultType());
321 ks.load(null);
322 char[] pass = keyPassword == null ? new char[0]: keyPassword.toCharArray();
323 ks.setKeyEntry("alias", key, pass, certChain);
324 KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
325 KeyManagerFactory.getDefaultAlgorithm());
326 keyManagerFactory.init(ks, pass);
327 return keyManager(keyManagerFactory, keyPassword);
328 } catch (Exception e) {
329 throw new IllegalArgumentException(e);
330 }
331 }
332
333
334
335
336
337 public QuicSslContextBuilder keyManager(
338 @Nullable KeyManagerFactory keyManagerFactory, @Nullable String keyPassword) {
339 this.keyPassword = keyPassword;
340 this.keyManagerFactory = keyManagerFactory;
341 return this;
342 }
343
344
345
346
347
348
349
350
351 public QuicSslContextBuilder keyManager(KeyManager keyManager, @Nullable String password) {
352 return keyManager(new KeyManagerFactoryWrapper(keyManager), password);
353 }
354
355
356
357
358 public QuicSslContextBuilder applicationProtocols(String @Nullable ... applicationProtocols) {
359 this.applicationProtocols = applicationProtocols;
360 return this;
361 }
362
363
364
365
366
367 public QuicSslContextBuilder sessionCacheSize(long sessionCacheSize) {
368 this.sessionCacheSize = sessionCacheSize;
369 return this;
370 }
371
372
373
374
375
376 public QuicSslContextBuilder sessionTimeout(long sessionTimeout) {
377 this.sessionTimeout = sessionTimeout;
378 return this;
379 }
380
381
382
383
384 public QuicSslContextBuilder clientAuth(ClientAuth clientAuth) {
385 if (!forServer) {
386 throw new UnsupportedOperationException("Only supported for server");
387 }
388 this.clientAuth = checkNotNull(clientAuth, "clientAuth");
389 return this;
390 }
391
392
393
394
395
396 public QuicSslContext build() {
397 if (forServer) {
398 return new QuicheQuicSslContext(true, sessionTimeout, sessionCacheSize, clientAuth, trustManagerFactory,
399 keyManagerFactory, keyPassword, mapping, earlyData, keylog,
400 applicationProtocols, toArray(options.entrySet(), EMPTY_ENTRIES));
401 } else {
402 return new QuicheQuicSslContext(false, sessionTimeout, sessionCacheSize, clientAuth, trustManagerFactory,
403 keyManagerFactory, keyPassword, mapping, earlyData, keylog,
404 applicationProtocols, toArray(options.entrySet(), EMPTY_ENTRIES));
405 }
406 }
407
408 private static <T> T[] toArray(Iterable<? extends T> iterable, T[] prototype) {
409 if (iterable == null) {
410 return null;
411 }
412 final List<T> list = new ArrayList<>();
413 for (T element : iterable) {
414 list.add(element);
415 }
416 return list.toArray(prototype);
417 }
418 }