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