1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.quic;
17
18 import io.netty.buffer.ByteBufAllocator;
19 import io.netty.handler.ssl.ApplicationProtocolNegotiator;
20 import io.netty.handler.ssl.ClientAuth;
21 import io.netty.handler.ssl.SslContextOption;
22 import io.netty.handler.ssl.SslHandler;
23 import io.netty.util.AbstractReferenceCounted;
24 import io.netty.util.Mapping;
25 import io.netty.util.ReferenceCounted;
26 import io.netty.util.internal.EmptyArrays;
27 import io.netty.util.internal.SystemPropertyUtil;
28 import io.netty.util.internal.logging.InternalLogger;
29 import io.netty.util.internal.logging.InternalLoggerFactory;
30 import org.jetbrains.annotations.Nullable;
31
32 import javax.net.ssl.KeyManager;
33 import javax.net.ssl.KeyManagerFactory;
34 import javax.net.ssl.SSLSession;
35 import javax.net.ssl.TrustManager;
36 import javax.net.ssl.TrustManagerFactory;
37 import javax.net.ssl.X509ExtendedKeyManager;
38 import javax.net.ssl.X509TrustManager;
39 import java.io.File;
40 import java.io.IOException;
41 import java.security.KeyStore;
42 import java.security.KeyStoreException;
43 import java.security.NoSuchAlgorithmException;
44 import java.security.PrivateKey;
45 import java.security.cert.CertificateException;
46 import java.security.cert.X509Certificate;
47 import java.util.Arrays;
48 import java.util.Collections;
49 import java.util.Enumeration;
50 import java.util.Iterator;
51 import java.util.LinkedHashSet;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.NoSuchElementException;
55 import java.util.Set;
56 import java.util.concurrent.Executor;
57 import java.util.function.BiConsumer;
58 import java.util.function.LongFunction;
59
60 import static io.netty.util.internal.ObjectUtil.checkNotNull;
61
62 final class QuicheQuicSslContext extends QuicSslContext {
63
64 private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(QuicheQuicSslContext.class);
65
66
67
68
69 private static final String[] DEFAULT_NAMED_GROUPS = { "x25519", "secp256r1", "secp384r1", "secp521r1" };
70 private static final String[] NAMED_GROUPS;
71
72 static {
73 String[] namedGroups = DEFAULT_NAMED_GROUPS;
74 Set<String> defaultConvertedNamedGroups = new LinkedHashSet<>(namedGroups.length);
75 for (int i = 0; i < namedGroups.length; i++) {
76 defaultConvertedNamedGroups.add(GroupsConverter.toBoringSSL(namedGroups[i]));
77 }
78
79
80
81 if (Quic.isAvailable()) {
82 final long sslCtx = BoringSSL.SSLContext_new();
83 try {
84
85 Iterator<String> defaultGroupsIter = defaultConvertedNamedGroups.iterator();
86 while (defaultGroupsIter.hasNext()) {
87 if (BoringSSL.SSLContext_set1_groups_list(sslCtx, defaultGroupsIter.next()) == 0) {
88
89
90
91 defaultGroupsIter.remove();
92 }
93 }
94
95 String groups = SystemPropertyUtil.get("jdk.tls.namedGroups", null);
96 if (groups != null) {
97 String[] nGroups = groups.split(",");
98 Set<String> supportedNamedGroups = new LinkedHashSet<>(nGroups.length);
99 Set<String> supportedConvertedNamedGroups = new LinkedHashSet<>(nGroups.length);
100
101 Set<String> unsupportedNamedGroups = new LinkedHashSet<>();
102 for (String namedGroup : nGroups) {
103 String converted = GroupsConverter.toBoringSSL(namedGroup);
104
105 if (BoringSSL.SSLContext_set1_groups_list(sslCtx, converted) == 0) {
106 unsupportedNamedGroups.add(namedGroup);
107 } else {
108 supportedConvertedNamedGroups.add(converted);
109 supportedNamedGroups.add(namedGroup);
110 }
111 }
112
113 if (supportedNamedGroups.isEmpty()) {
114 namedGroups = defaultConvertedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
115 LOGGER.info("All configured namedGroups are not supported: {}. Use default: {}.",
116 Arrays.toString(unsupportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS)),
117 Arrays.toString(DEFAULT_NAMED_GROUPS));
118 } else {
119 String[] groupArray = supportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
120 if (unsupportedNamedGroups.isEmpty()) {
121 LOGGER.info("Using configured namedGroups -D 'jdk.tls.namedGroup': {} ",
122 Arrays.toString(groupArray));
123 } else {
124 LOGGER.info("Using supported configured namedGroups: {}. Unsupported namedGroups: {}. ",
125 Arrays.toString(groupArray),
126 Arrays.toString(unsupportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS)));
127 }
128 namedGroups = supportedConvertedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
129 }
130 } else {
131 namedGroups = defaultConvertedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
132 }
133 } finally {
134 BoringSSL.SSLContext_free(sslCtx);
135 }
136 }
137 NAMED_GROUPS = namedGroups;
138 }
139
140 final ClientAuth clientAuth;
141 private final boolean server;
142 @SuppressWarnings("deprecation")
143 private final ApplicationProtocolNegotiator apn;
144 private long sessionCacheSize;
145 private long sessionTimeout;
146 private final QuicheQuicSslSessionContext sessionCtx;
147 private final QuicheQuicSslEngineMap engineMap = new QuicheQuicSslEngineMap();
148 private final QuicClientSessionCache sessionCache;
149
150 private final BoringSSLSessionTicketCallback sessionTicketCallback = new BoringSSLSessionTicketCallback();
151
152 final NativeSslContext nativeSslContext;
153
154 QuicheQuicSslContext(boolean server, long sessionTimeout, long sessionCacheSize,
155 ClientAuth clientAuth, @Nullable TrustManagerFactory trustManagerFactory,
156 @Nullable KeyManagerFactory keyManagerFactory, String password,
157 @Nullable Mapping<? super String, ? extends QuicSslContext> mapping,
158 @Nullable Boolean earlyData, @Nullable BoringSSLKeylog keylog,
159 String[] applicationProtocols, Map.Entry<SslContextOption<?>, Object>... ctxOptions) {
160 Quic.ensureAvailability();
161 this.server = server;
162 this.clientAuth = server ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE;
163 final X509TrustManager trustManager;
164 if (trustManagerFactory == null) {
165 try {
166 trustManagerFactory =
167 TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
168 trustManagerFactory.init((KeyStore) null);
169 trustManager = chooseTrustManager(trustManagerFactory);
170 } catch (Exception e) {
171 throw new IllegalStateException(e);
172 }
173 } else {
174 trustManager = chooseTrustManager(trustManagerFactory);
175 }
176 final X509ExtendedKeyManager keyManager;
177 if (keyManagerFactory == null) {
178 if (server) {
179 throw new IllegalArgumentException("No KeyManagerFactory");
180 }
181 keyManager = null;
182 } else {
183 keyManager = chooseKeyManager(keyManagerFactory);
184 }
185 String[] groups = NAMED_GROUPS;
186 String[] sigalgs = EmptyArrays.EMPTY_STRINGS;
187 Map<String, String> serverKeyTypes = null;
188 Set<String> clientKeyTypes = null;
189
190 if (ctxOptions != null) {
191 for (Map.Entry<SslContextOption<?>, Object> ctxOpt : ctxOptions) {
192 SslContextOption<?> option = ctxOpt.getKey();
193
194 if (option == BoringSSLContextOption.GROUPS) {
195 String[] groupsArray = (String[]) ctxOpt.getValue();
196 Set<String> groupsSet = new LinkedHashSet<String>(groupsArray.length);
197 for (String group : groupsArray) {
198 groupsSet.add(GroupsConverter.toBoringSSL(group));
199 }
200 groups = groupsSet.toArray(EmptyArrays.EMPTY_STRINGS);
201 } else if (option == BoringSSLContextOption.SIGNATURE_ALGORITHMS) {
202 String[] sigalgsArray = (String[]) ctxOpt.getValue();
203 Set<String> sigalgsSet = new LinkedHashSet<String>(sigalgsArray.length);
204 for (String sigalg : sigalgsArray) {
205 sigalgsSet.add(sigalg);
206 }
207 sigalgs = sigalgsSet.toArray(EmptyArrays.EMPTY_STRINGS);
208 } else if (option == BoringSSLContextOption.CLIENT_KEY_TYPES) {
209 clientKeyTypes = (Set<String>) ctxOpt.getValue();
210 } else if (option == BoringSSLContextOption.SERVER_KEY_TYPES) {
211 serverKeyTypes = (Map<String, String>) ctxOpt.getValue();
212 } else {
213 LOGGER.debug("Skipping unsupported " + SslContextOption.class.getSimpleName()
214 + ": " + ctxOpt.getKey());
215 }
216 }
217 }
218 final BoringSSLPrivateKeyMethod privateKeyMethod;
219 if (keyManagerFactory instanceof BoringSSLKeylessManagerFactory) {
220 privateKeyMethod = new BoringSSLAsyncPrivateKeyMethodAdapter(engineMap,
221 ((BoringSSLKeylessManagerFactory) keyManagerFactory).privateKeyMethod);
222 } else {
223 privateKeyMethod = null;
224 }
225 sessionCache = server ? null : new QuicClientSessionCache();
226 int verifyMode = server ? boringSSLVerifyModeForServer(this.clientAuth) : BoringSSL.SSL_VERIFY_PEER;
227 nativeSslContext = new NativeSslContext(BoringSSL.SSLContext_new(server, applicationProtocols,
228 new BoringSSLHandshakeCompleteCallback(engineMap),
229 new BoringSSLCertificateCallback(engineMap, keyManager, password, serverKeyTypes, clientKeyTypes),
230 new BoringSSLCertificateVerifyCallback(engineMap, trustManager),
231 mapping == null ? null : new BoringSSLTlsextServernameCallback(engineMap, mapping),
232 keylog == null ? null : new BoringSSLKeylogCallback(engineMap, keylog),
233 server ? null : new BoringSSLSessionCallback(engineMap, sessionCache), privateKeyMethod,
234 sessionTicketCallback, verifyMode,
235 BoringSSL.subjectNames(trustManager.getAcceptedIssuers())));
236 boolean success = false;
237 try {
238 if (groups.length > 0 && BoringSSL.SSLContext_set1_groups_list(nativeSslContext.ctx, groups) == 0) {
239 String msg = "failed to set curves / groups list: " + Arrays.toString(groups);
240 String lastError = BoringSSL.ERR_last_error();
241 if (lastError != null) {
242
243 msg += ". " + lastError;
244 }
245 throw new IllegalStateException(msg);
246 }
247
248 if (sigalgs.length > 0 && BoringSSL.SSLContext_set1_sigalgs_list(nativeSslContext.ctx, sigalgs) == 0) {
249 String msg = "failed to set signature algorithm list: " + Arrays.toString(sigalgs);
250 String lastError = BoringSSL.ERR_last_error();
251 if (lastError != null) {
252
253 msg += ". " + lastError;
254 }
255 throw new IllegalStateException(msg);
256 }
257
258 apn = new QuicheQuicApplicationProtocolNegotiator(applicationProtocols);
259 if (this.sessionCache != null) {
260
261 this.sessionCache.setSessionCacheSize((int) sessionCacheSize);
262 this.sessionCache.setSessionTimeout((int) sessionTimeout);
263 } else {
264
265 BoringSSL.SSLContext_setSessionCacheSize(
266 nativeSslContext.address(), sessionCacheSize);
267 this.sessionCacheSize = sessionCacheSize;
268
269 BoringSSL.SSLContext_setSessionCacheTimeout(
270 nativeSslContext.address(), sessionTimeout);
271 this.sessionTimeout = sessionTimeout;
272 }
273 if (earlyData != null) {
274 BoringSSL.SSLContext_set_early_data_enabled(nativeSslContext.address(), earlyData);
275 }
276 sessionCtx = new QuicheQuicSslSessionContext(this);
277 success = true;
278 } finally {
279 if (!success) {
280 nativeSslContext.release();
281 }
282 }
283 }
284
285 private X509ExtendedKeyManager chooseKeyManager(KeyManagerFactory keyManagerFactory) {
286 for (KeyManager manager: keyManagerFactory.getKeyManagers()) {
287 if (manager instanceof X509ExtendedKeyManager) {
288 return (X509ExtendedKeyManager) manager;
289 }
290 }
291 throw new IllegalArgumentException("No X509ExtendedKeyManager included");
292 }
293
294 private static X509TrustManager chooseTrustManager(TrustManagerFactory trustManagerFactory) {
295 for (TrustManager manager: trustManagerFactory.getTrustManagers()) {
296 if (manager instanceof X509TrustManager) {
297 return (X509TrustManager) manager;
298 }
299 }
300 throw new IllegalArgumentException("No X509TrustManager included");
301 }
302
303 static X509Certificate @Nullable [] toX509Certificates0(@Nullable File file) throws CertificateException {
304 return toX509Certificates(file);
305 }
306
307 static PrivateKey toPrivateKey0(@Nullable File keyFile, @Nullable String keyPassword) throws Exception {
308 return toPrivateKey(keyFile, keyPassword);
309 }
310
311 static TrustManagerFactory buildTrustManagerFactory0(
312 X509Certificate @Nullable [] certCollection)
313 throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
314 return buildTrustManagerFactory(certCollection, null, null);
315 }
316
317 private static int boringSSLVerifyModeForServer(ClientAuth mode) {
318 switch (mode) {
319 case NONE:
320 return BoringSSL.SSL_VERIFY_NONE;
321 case REQUIRE:
322 return BoringSSL.SSL_VERIFY_PEER | BoringSSL.SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
323 case OPTIONAL:
324 return BoringSSL.SSL_VERIFY_PEER;
325 default:
326 throw new Error(mode.toString());
327 }
328 }
329
330 @Nullable
331 QuicheQuicConnection createConnection(LongFunction<Long> connectionCreator, QuicheQuicSslEngine engine) {
332 nativeSslContext.retain();
333 long ssl = BoringSSL.SSL_new(nativeSslContext.address(), isServer(), engine.tlsHostName);
334 engineMap.put(ssl, engine);
335 long connection = connectionCreator.apply(ssl);
336 if (connection == -1) {
337 engineMap.remove(ssl);
338
339
340 nativeSslContext.release();
341 return null;
342 }
343
344 return new QuicheQuicConnection(connection, ssl, engine, nativeSslContext);
345 }
346
347
348
349
350
351
352
353 long add(QuicheQuicSslEngine engine) {
354 nativeSslContext.retain();
355 engine.connection.reattach(nativeSslContext);
356 engineMap.put(engine.connection.ssl, engine);
357 return nativeSslContext.address();
358 }
359
360
361
362
363
364
365 void remove(QuicheQuicSslEngine engine) {
366 QuicheQuicSslEngine removed = engineMap.remove(engine.connection.ssl);
367 assert removed == null || removed == engine;
368 engine.removeSessionFromCacheIfInvalid();
369 }
370
371 @Nullable
372 QuicClientSessionCache getSessionCache() {
373 return sessionCache;
374 }
375
376 @Override
377 public boolean isClient() {
378 return !server;
379 }
380
381 @Override
382 public List<String> cipherSuites() {
383 return Arrays.asList("TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384");
384 }
385
386 @Override
387 public long sessionCacheSize() {
388 if (sessionCache != null) {
389 return sessionCache.getSessionCacheSize();
390 } else {
391 synchronized (this) {
392 return sessionCacheSize;
393 }
394 }
395 }
396
397 @Override
398 public long sessionTimeout() {
399 if (sessionCache != null) {
400 return sessionCache.getSessionTimeout();
401 } else {
402 synchronized (this) {
403 return sessionTimeout;
404 }
405 }
406 }
407
408 @Override
409 public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
410 return apn;
411 }
412
413 @Override
414 public QuicSslEngine newEngine(ByteBufAllocator alloc) {
415 return new QuicheQuicSslEngine(this, null, -1);
416 }
417
418 @Override
419 public QuicSslEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
420 return new QuicheQuicSslEngine(this, peerHost, peerPort);
421 }
422
423 @Override
424 public QuicSslSessionContext sessionContext() {
425 return sessionCtx;
426 }
427
428 @Override
429 protected SslHandler newHandler(ByteBufAllocator alloc, boolean startTls) {
430 throw new UnsupportedOperationException();
431 }
432
433 @Override
434 public SslHandler newHandler(ByteBufAllocator alloc, Executor delegatedTaskExecutor) {
435 throw new UnsupportedOperationException();
436 }
437
438 @Override
439 protected SslHandler newHandler(ByteBufAllocator alloc, boolean startTls, Executor executor) {
440 throw new UnsupportedOperationException();
441 }
442
443 @Override
444 protected SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort, boolean startTls) {
445 throw new UnsupportedOperationException();
446 }
447
448 @Override
449 public SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort,
450 Executor delegatedTaskExecutor) {
451 throw new UnsupportedOperationException();
452 }
453
454 @Override
455 protected SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort,
456 boolean startTls, Executor delegatedTaskExecutor) {
457 throw new UnsupportedOperationException();
458 }
459
460 @Override
461 protected void finalize() throws Throwable {
462 try {
463 nativeSslContext.release();
464 } finally {
465 super.finalize();
466 }
467 }
468
469 void setSessionTimeout(int seconds) throws IllegalArgumentException {
470 if (sessionCache != null) {
471 sessionCache.setSessionTimeout(seconds);
472 } else {
473 BoringSSL.SSLContext_setSessionCacheTimeout(nativeSslContext.address(), seconds);
474 this.sessionTimeout = seconds;
475 }
476 }
477
478 void setSessionCacheSize(int size) throws IllegalArgumentException {
479 if (sessionCache != null) {
480 sessionCache.setSessionCacheSize(size);
481 } else {
482 BoringSSL.SSLContext_setSessionCacheSize(nativeSslContext.address(), size);
483 sessionCacheSize = size;
484 }
485 }
486
487 void setSessionTicketKeys(SslSessionTicketKey @Nullable [] ticketKeys) {
488 sessionTicketCallback.setSessionTicketKeys(ticketKeys);
489 BoringSSL.SSLContext_setSessionTicketKeys(
490 nativeSslContext.address(), ticketKeys != null && ticketKeys.length != 0);
491 }
492
493 @SuppressWarnings("deprecation")
494 private static final class QuicheQuicApplicationProtocolNegotiator implements ApplicationProtocolNegotiator {
495 private final List<String> protocols;
496
497 QuicheQuicApplicationProtocolNegotiator(String @Nullable ... protocols) {
498 if (protocols == null) {
499 this.protocols = Collections.emptyList();
500 } else {
501 this.protocols = Collections.unmodifiableList(Arrays.asList(protocols));
502 }
503 }
504
505 @Override
506 public List<String> protocols() {
507 return protocols;
508 }
509 }
510
511 private static final class QuicheQuicSslSessionContext implements QuicSslSessionContext {
512 private final QuicheQuicSslContext context;
513
514 QuicheQuicSslSessionContext(QuicheQuicSslContext context) {
515 this.context = context;
516 }
517
518 @Override
519 @Nullable
520 public SSLSession getSession(byte[] sessionId) {
521 return null;
522 }
523
524 @Override
525 public Enumeration<byte[]> getIds() {
526 return new Enumeration<byte[]>() {
527 @Override
528 public boolean hasMoreElements() {
529 return false;
530 }
531
532 @Override
533 public byte[] nextElement() {
534 throw new NoSuchElementException();
535 }
536 };
537 }
538
539 @Override
540 public void setSessionTimeout(int seconds) throws IllegalArgumentException {
541 context.setSessionTimeout(seconds);
542 }
543
544 @Override
545 public int getSessionTimeout() {
546 return (int) context.sessionTimeout();
547 }
548
549 @Override
550 public void setSessionCacheSize(int size) throws IllegalArgumentException {
551 context.setSessionCacheSize(size);
552 }
553
554 @Override
555 public int getSessionCacheSize() {
556 return (int) context.sessionCacheSize();
557 }
558
559 @Override
560 public void setTicketKeys(SslSessionTicketKey @Nullable ... keys) {
561 context.setSessionTicketKeys(keys);
562 }
563 }
564
565 static final class NativeSslContext extends AbstractReferenceCounted {
566 private final long ctx;
567
568 NativeSslContext(long ctx) {
569 this.ctx = ctx;
570 }
571
572 long address() {
573 return ctx;
574 }
575
576 @Override
577 protected void deallocate() {
578 BoringSSL.SSLContext_free(ctx);
579 }
580
581 @Override
582 public ReferenceCounted touch(Object hint) {
583 return this;
584 }
585
586 @Override
587 public String toString() {
588 return "NativeSslContext{" +
589 "ctx=" + ctx +
590 '}';
591 }
592 }
593
594 private static final class BoringSSLAsyncPrivateKeyMethodAdapter implements BoringSSLPrivateKeyMethod {
595 private final QuicheQuicSslEngineMap engineMap;
596 private final BoringSSLAsyncPrivateKeyMethod privateKeyMethod;
597
598 BoringSSLAsyncPrivateKeyMethodAdapter(QuicheQuicSslEngineMap engineMap,
599 BoringSSLAsyncPrivateKeyMethod privateKeyMethod) {
600 this.engineMap = engineMap;
601 this.privateKeyMethod = privateKeyMethod;
602 }
603
604 @Override
605 public void sign(long ssl, int signatureAlgorithm, byte[] input, BiConsumer<byte[], Throwable> callback) {
606 final QuicheQuicSslEngine engine = engineMap.get(ssl);
607 if (engine == null) {
608
609 callback.accept(null, null);
610 } else {
611 privateKeyMethod.sign(engine, signatureAlgorithm, input).addListener(f -> {
612 Throwable cause = f.cause();
613 if (cause != null) {
614 callback.accept(null, cause);
615 } else {
616 callback.accept((byte[]) f.getNow(), null);
617 }
618 });
619 }
620 }
621
622 @Override
623 public void decrypt(long ssl, byte[] input, BiConsumer<byte[], Throwable> callback) {
624 final QuicheQuicSslEngine engine = engineMap.get(ssl);
625 if (engine == null) {
626
627 callback.accept(null, null);
628 } else {
629 privateKeyMethod.decrypt(engine, input).addListener(f -> {
630 Throwable cause = f.cause();
631 if (cause != null) {
632 callback.accept(null, cause);
633 } else {
634 callback.accept((byte[]) f.getNow(), null);
635 }
636 });
637 }
638 }
639 }
640 }