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