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  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      // Use default that is supported in java 11 and earlier and also in OpenSSL / BoringSSL.
69      // See https://github.com/netty/netty-tcnative/issues/567
70      // See https://www.java.com/en/configure_crypto.html for ordering
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          // Call Quic.isAvailable() first to ensure native lib is loaded.
84          // See https://github.com/netty/netty-incubator-codec-quic/issues/759
85          if (Quic.isAvailable()) {
86              final long sslCtx = BoringSSL.SSLContext_new();
87              try {
88                  // Let's filter out any group that is not supported from the default.
89                  Iterator<String> defaultGroupsIter = defaultConvertedNamedGroups.iterator();
90                  while (defaultGroupsIter.hasNext()) {
91                      if (BoringSSL.SSLContext_set1_groups_list(sslCtx, defaultGroupsIter.next()) == 0) {
92                          // Not supported, let's remove it. This could for example be the case if we use
93                          // fips and the configure group is not supported when using FIPS.
94                          // See https://github.com/netty/netty-tcnative/issues/883
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                         // Will return 0 on failure.
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                          X509Certificate @Nullable [] trustCertCollection,
162                          @Nullable KeyManagerFactory keyManagerFactory,
163                          X509Certificate @Nullable [] keyCertChain, String password,
164                          @Nullable Mapping<? super String, ? extends QuicSslContext> mapping,
165                          @Nullable Boolean earlyData, @Nullable BoringSSLKeylog keylog,
166                          String[] applicationProtocols, String endpointIdentificationAlgorithm,
167                          Map.Entry<SslContextOption<?>, Object>... ctxOptions) {
168         Quic.ensureAvailability();
169         this.server = server;
170         this.endpointIdentificationAlgorithm = endpointIdentificationAlgorithm;
171         this.clientAuth = server ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE;
172         final X509TrustManager trustManager;
173         final boolean hasExternalTrustManagerFactorySpecified = trustManagerFactory != null;
174         if (trustManagerFactory == null) {
175             try {
176                 trustManagerFactory =
177                         TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
178                 trustManagerFactory.init((KeyStore) null);
179                 trustManager = chooseTrustManager(trustManagerFactory);
180             } catch (Exception e) {
181                 throw new IllegalStateException(e);
182             }
183         } else {
184             trustManager = chooseTrustManager(trustManagerFactory);
185         }
186         if (this.endpointIdentificationAlgorithm != null && !this.endpointIdentificationAlgorithm.isEmpty() &&
187             !(trustManager instanceof X509ExtendedTrustManager)) {
188             throw new UnsupportedOperationException(
189                 "Endpoint identification algorithm '" + this.endpointIdentificationAlgorithm + "' is " +
190                     "configured but the trust manager does not support extended trust manager verification. " +
191                     "Please provide an X509ExtendedTrustManager.");
192         }
193         final X509ExtendedKeyManager keyManager;
194         if (keyManagerFactory == null) {
195             if (server) {
196                 throw new IllegalArgumentException("No KeyManagerFactory");
197             }
198             keyManager = null;
199         } else {
200             keyManager = chooseKeyManager(keyManagerFactory);
201         }
202         String[] groups = NAMED_GROUPS;
203         String[] sigalgs = EmptyArrays.EMPTY_STRINGS;
204         Map<String, String> serverKeyTypes = null;
205         Set<String> clientKeyTypes = null;
206 
207         if (ctxOptions != null) {
208             for (Map.Entry<SslContextOption<?>, Object> ctxOpt : ctxOptions) {
209                 SslContextOption<?> option = ctxOpt.getKey();
210 
211                 if (option == BoringSSLContextOption.GROUPS) {
212                     String[] groupsArray = (String[]) ctxOpt.getValue();
213                     Set<String> groupsSet = new LinkedHashSet<String>(groupsArray.length);
214                     for (String group : groupsArray) {
215                         groupsSet.add(GroupsConverter.toBoringSSL(group));
216                     }
217                     groups = groupsSet.toArray(EmptyArrays.EMPTY_STRINGS);
218                 } else if (option == BoringSSLContextOption.SIGNATURE_ALGORITHMS) {
219                     String[] sigalgsArray = (String[]) ctxOpt.getValue();
220                     Set<String> sigalgsSet = new LinkedHashSet<String>(sigalgsArray.length);
221                     for (String sigalg : sigalgsArray) {
222                         sigalgsSet.add(sigalg);
223                     }
224                     sigalgs = sigalgsSet.toArray(EmptyArrays.EMPTY_STRINGS);
225                 } else if (option == BoringSSLContextOption.CLIENT_KEY_TYPES) {
226                     clientKeyTypes = (Set<String>) ctxOpt.getValue();
227                 } else if (option == BoringSSLContextOption.SERVER_KEY_TYPES) {
228                     serverKeyTypes = (Map<String, String>) ctxOpt.getValue();
229                 } else {
230                     LOGGER.debug("Skipping unsupported " + SslContextOption.class.getSimpleName()
231                             + ": " + ctxOpt.getKey());
232                 }
233             }
234         }
235         final BoringSSLPrivateKeyMethod privateKeyMethod;
236         if (keyManagerFactory instanceof  BoringSSLKeylessManagerFactory) {
237             privateKeyMethod = new BoringSSLAsyncPrivateKeyMethodAdapter(engineMap,
238                     ((BoringSSLKeylessManagerFactory) keyManagerFactory).privateKeyMethod);
239         } else {
240             privateKeyMethod = null;
241         }
242         sessionCache = server ? null : new QuicClientSessionCache();
243         int verifyMode = server ? boringSSLVerifyModeForServer(this.clientAuth) : BoringSSL.SSL_VERIFY_PEER;
244         nativeSslContext = new NativeSslContext(BoringSSL.SSLContext_new(server, applicationProtocols,
245                 new BoringSSLHandshakeCompleteCallback(engineMap),
246                 new BoringSSLCertificateCallback(engineMap, keyManager, password, serverKeyTypes, clientKeyTypes),
247                 new BoringSSLCertificateVerifyCallback(engineMap, trustManager),
248                 mapping == null ? null : new BoringSSLTlsextServernameCallback(engineMap, mapping),
249                 keylog == null ? null : new BoringSSLKeylogCallback(engineMap, keylog),
250                 server ? null : new BoringSSLSessionCallback(engineMap, sessionCache), privateKeyMethod,
251                 sessionTicketCallback, verifyMode,
252                 BoringSSL.subjectNames(trustManager.getAcceptedIssuers())));
253         boolean success = false;
254         try {
255             if (groups.length > 0 && BoringSSL.SSLContext_set1_groups_list(nativeSslContext.ctx, groups) == 0) {
256                 String msg = "failed to set curves / groups list: " + Arrays.toString(groups);
257                 String lastError = BoringSSL.ERR_last_error();
258                 if (lastError != null) {
259                     // We have some more details about why the operations failed, include these into the message.
260                     msg += ". " + lastError;
261                 }
262                 throw new IllegalStateException(msg);
263             }
264 
265             if (sigalgs.length > 0 && BoringSSL.SSLContext_set1_sigalgs_list(nativeSslContext.ctx, sigalgs) == 0) {
266                 String msg = "failed to set signature algorithm list: " + Arrays.toString(sigalgs);
267                 String lastError = BoringSSL.ERR_last_error();
268                 if (lastError != null) {
269                     // We have some more details about why the operations failed, include these into the message.
270                     msg += ". " + lastError;
271                 }
272                 throw new IllegalStateException(msg);
273             }
274 
275             apn = new QuicheQuicApplicationProtocolNegotiator(applicationProtocols);
276             if (this.sessionCache != null) {
277                 // Cache is handled via our own implementation.
278                 this.sessionCache.setSessionCacheSize((int) sessionCacheSize);
279                 this.sessionCache.setSessionTimeout((int) sessionTimeout);
280             } else {
281                 // Cache is handled by BoringSSL internally
282                 BoringSSL.SSLContext_setSessionCacheSize(
283                         nativeSslContext.address(), sessionCacheSize);
284                 this.sessionCacheSize = sessionCacheSize;
285 
286                 BoringSSL.SSLContext_setSessionCacheTimeout(
287                         nativeSslContext.address(), sessionTimeout);
288                 this.sessionTimeout = sessionTimeout;
289 
290                 // Derive the session_id_context from what distinguishes this context, so sessions can not be
291                 // resumed across QuicSslContext instances that differ in their ClientAuth mode or trust / key
292                 // configuration, even if session-ticket keys or a session cache are shared between them.
293                 // The trust and key material can only be digested if it was handed to us as plain certificates.
294                 // Anything backed by a TrustManagerFactory / KeyManagerFactory we did not create ourselves is
295                 // opaque to us, so we can not tell if two of them behave the same.
296                 boolean opaqueConfiguration = keyCertChain == null ||
297                         (trustCertCollection == null && hasExternalTrustManagerFactorySpecified);
298                 if (!BoringSSL.SSLContext_setSessionIdContext(nativeSslContext.address(),
299                         deriveSessionIdContext(this.clientAuth, keyCertChain, trustCertCollection,
300                                 opaqueConfiguration))) {
301                     // Without a session_id_context we must not continue as resumption would not be scoped.
302                     String msg = "failed to set session id context";
303                     String lastError = BoringSSL.ERR_last_error();
304                     if (lastError != null) {
305                         msg += ": " + lastError;
306                     }
307                     throw new IllegalStateException(msg);
308                 }
309             }
310             if (server && this.clientAuth == ClientAuth.REQUIRE) {
311                 // A client certificate is not re-requested when a session is resumed, and the peer certificates are
312                 // only available once the handshake completed. Early data would hence be handed to the application
313                 // before we are able to verify that the client authenticated itself, so never accept it here.
314                 BoringSSL.SSLContext_set_early_data_enabled(nativeSslContext.address(), false);
315             } else if (earlyData != null) {
316                 BoringSSL.SSLContext_set_early_data_enabled(nativeSslContext.address(), earlyData);
317             }
318             sessionCtx = new QuicheQuicSslSessionContext(this);
319             success = true;
320         } finally {
321             if (!success) {
322                 nativeSslContext.release();
323             }
324         }
325     }
326 
327     private X509ExtendedKeyManager chooseKeyManager(KeyManagerFactory keyManagerFactory) {
328         for (KeyManager manager: keyManagerFactory.getKeyManagers()) {
329             if (manager instanceof X509ExtendedKeyManager) {
330                 return (X509ExtendedKeyManager) manager;
331             }
332         }
333         throw new IllegalArgumentException("No X509ExtendedKeyManager included");
334     }
335 
336     private static X509TrustManager chooseTrustManager(TrustManagerFactory trustManagerFactory) {
337         for (TrustManager manager: trustManagerFactory.getTrustManagers()) {
338             if (manager instanceof X509TrustManager) {
339                 return (X509TrustManager) manager;
340             }
341         }
342         throw new IllegalArgumentException("No X509TrustManager included");
343     }
344 
345      static X509Certificate @Nullable [] toX509Certificates0(@Nullable File file) throws CertificateException {
346         return toX509Certificates(file);
347     }
348 
349     static PrivateKey toPrivateKey0(@Nullable File keyFile, @Nullable String keyPassword) throws Exception {
350         return toPrivateKey(keyFile, keyPassword);
351     }
352 
353     static TrustManagerFactory buildTrustManagerFactory0(
354             X509Certificate @Nullable [] certCollection)
355             throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
356         return buildTrustManagerFactory(certCollection, null, null);
357     }
358 
359     private static int boringSSLVerifyModeForServer(ClientAuth mode) {
360         switch (mode) {
361             case NONE:
362                 return BoringSSL.SSL_VERIFY_NONE;
363             case REQUIRE:
364                 return BoringSSL.SSL_VERIFY_PEER | BoringSSL.SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
365             case OPTIONAL:
366                 return BoringSSL.SSL_VERIFY_PEER;
367             default:
368                 throw new Error("Unexpected mode: " + mode);
369         }
370     }
371 
372     @Nullable
373     QuicheQuicConnection createConnection(LongFunction<Long> connectionCreator, QuicheQuicSslEngine engine) {
374         nativeSslContext.retain();
375         long ssl = BoringSSL.SSL_new(nativeSslContext.address(), isServer(), engine.tlsHostName);
376         engineMap.put(ssl, engine);
377         long connection = connectionCreator.apply(ssl);
378         if (connection == -1) {
379             engineMap.remove(ssl);
380             // We retained before but as we don't create a QuicheQuicConnection and transfer ownership we need to
381             // explict call release again here.
382             nativeSslContext.release();
383             return null;
384         }
385         // The connection will call nativeSslContext.release() once it is freed.
386         return new QuicheQuicConnection(connection, ssl, engine, nativeSslContext);
387     }
388 
389     /**
390      * Add the given engine to this context
391      *
392      * @param engine    the engine to add.
393      * @return          the pointer address of this context.
394      */
395     long add(QuicheQuicSslEngine engine) {
396         nativeSslContext.retain();
397         engine.connection.reattach(nativeSslContext);
398         engineMap.put(engine.connection.ssl, engine);
399         return nativeSslContext.address();
400     }
401 
402     /**
403      * Remove the given engine from this context.
404      *
405      * @param engine    the engine to remove.
406      */
407     void remove(QuicheQuicSslEngine engine) {
408         QuicheQuicSslEngine removed = engineMap.remove(engine.connection.ssl);
409         assert removed == null || removed == engine;
410         engine.removeSessionFromCacheIfInvalid();
411     }
412 
413     @Nullable
414     QuicClientSessionCache getSessionCache() {
415         return sessionCache;
416     }
417 
418     @Override
419     public boolean isClient() {
420         return !server;
421     }
422 
423     @Override
424     public List<String> cipherSuites() {
425         return Arrays.asList("TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384");
426     }
427 
428     @Override
429     public long sessionCacheSize() {
430         if (sessionCache != null) {
431             return sessionCache.getSessionCacheSize();
432         } else {
433             synchronized (this) {
434                 return sessionCacheSize;
435             }
436         }
437     }
438 
439     @Override
440     public long sessionTimeout() {
441         if (sessionCache != null) {
442             return sessionCache.getSessionTimeout();
443         } else {
444             synchronized (this) {
445                 return sessionTimeout;
446             }
447         }
448     }
449 
450     @Override
451     public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
452         return apn;
453     }
454 
455     @Override
456     public QuicSslEngine newEngine(ByteBufAllocator alloc) {
457         return new QuicheQuicSslEngine(this, null, -1, endpointIdentificationAlgorithm);
458     }
459 
460     @Override
461     public QuicSslEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
462         return new QuicheQuicSslEngine(this, peerHost, peerPort, endpointIdentificationAlgorithm);
463     }
464 
465     @Override
466     public QuicSslSessionContext sessionContext() {
467         return sessionCtx;
468     }
469 
470     @Override
471     protected SslHandler newHandler(ByteBufAllocator alloc, boolean startTls) {
472         throw new UnsupportedOperationException();
473     }
474 
475     @Override
476     public SslHandler newHandler(ByteBufAllocator alloc, Executor delegatedTaskExecutor) {
477         throw new UnsupportedOperationException();
478     }
479 
480     @Override
481     protected SslHandler newHandler(ByteBufAllocator alloc, boolean startTls, Executor executor) {
482         throw new UnsupportedOperationException();
483     }
484 
485     @Override
486     protected SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort, boolean startTls) {
487         throw new UnsupportedOperationException();
488     }
489 
490     @Override
491     public SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort,
492                                  Executor delegatedTaskExecutor) {
493         throw new UnsupportedOperationException();
494     }
495 
496     @Override
497     protected SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort,
498                                     boolean startTls, Executor delegatedTaskExecutor) {
499         throw new UnsupportedOperationException();
500     }
501 
502     @Override
503     protected void finalize() throws Throwable {
504         try {
505             nativeSslContext.release();
506         } finally {
507             super.finalize();
508         }
509     }
510 
511     void setSessionTimeout(int seconds) throws IllegalArgumentException {
512         if (sessionCache != null) {
513             sessionCache.setSessionTimeout(seconds);
514         } else {
515             BoringSSL.SSLContext_setSessionCacheTimeout(nativeSslContext.address(), seconds);
516             this.sessionTimeout = seconds;
517         }
518     }
519 
520     void setSessionCacheSize(int size) throws IllegalArgumentException {
521         if (sessionCache != null) {
522             sessionCache.setSessionCacheSize(size);
523         } else {
524             BoringSSL.SSLContext_setSessionCacheSize(nativeSslContext.address(), size);
525             sessionCacheSize = size;
526         }
527     }
528 
529     void setSessionTicketKeys(SslSessionTicketKey @Nullable [] ticketKeys) {
530         sessionTicketCallback.setSessionTicketKeys(ticketKeys);
531         BoringSSL.SSLContext_setSessionTicketKeys(
532                 nativeSslContext.address(), ticketKeys != null && ticketKeys.length != 0);
533     }
534 
535     @SuppressWarnings("deprecation")
536     private static final class QuicheQuicApplicationProtocolNegotiator implements ApplicationProtocolNegotiator {
537         private final List<String> protocols;
538 
539         QuicheQuicApplicationProtocolNegotiator(String @Nullable ... protocols) {
540             if (protocols == null) {
541                 this.protocols = Collections.emptyList();
542             } else {
543                 this.protocols = Collections.unmodifiableList(Arrays.asList(protocols));
544             }
545         }
546 
547         @Override
548         public List<String> protocols() {
549             return protocols;
550         }
551     }
552 
553     private static final class QuicheQuicSslSessionContext implements QuicSslSessionContext {
554         private final QuicheQuicSslContext context;
555 
556         QuicheQuicSslSessionContext(QuicheQuicSslContext context) {
557             this.context = context;
558         }
559 
560         @Override
561         @Nullable
562         public SSLSession getSession(byte[] sessionId) {
563             return null;
564         }
565 
566         @Override
567         public Enumeration<byte[]> getIds() {
568             return new Enumeration<byte[]>() {
569                 @Override
570                 public boolean hasMoreElements() {
571                     return false;
572                 }
573 
574                 @Override
575                 public byte[] nextElement() {
576                     throw new NoSuchElementException();
577                 }
578             };
579         }
580 
581         @Override
582         public void setSessionTimeout(int seconds) throws IllegalArgumentException {
583             context.setSessionTimeout(seconds);
584         }
585 
586         @Override
587         public int getSessionTimeout() {
588             return (int) context.sessionTimeout();
589         }
590 
591         @Override
592         public void setSessionCacheSize(int size) throws IllegalArgumentException {
593             context.setSessionCacheSize(size);
594         }
595 
596         @Override
597         public int getSessionCacheSize() {
598             return (int) context.sessionCacheSize();
599         }
600 
601         @Override
602         public void setTicketKeys(SslSessionTicketKey @Nullable ... keys) {
603             context.setSessionTicketKeys(keys);
604         }
605     }
606 
607     static final class NativeSslContext extends AbstractReferenceCounted {
608         private final long ctx;
609 
610         NativeSslContext(long ctx) {
611             this.ctx = ctx;
612         }
613 
614         long address() {
615             return ctx;
616         }
617 
618         @Override
619         protected void deallocate() {
620             BoringSSL.SSLContext_free(ctx);
621         }
622 
623         @Override
624         public ReferenceCounted touch(Object hint) {
625             return this;
626         }
627 
628         @Override
629         public String toString() {
630             return "NativeSslContext{" +
631                     "ctx=" + ctx +
632                     '}';
633         }
634     }
635 
636     private static final class BoringSSLAsyncPrivateKeyMethodAdapter implements BoringSSLPrivateKeyMethod {
637         private final QuicheQuicSslEngineMap engineMap;
638         private final BoringSSLAsyncPrivateKeyMethod privateKeyMethod;
639 
640         BoringSSLAsyncPrivateKeyMethodAdapter(QuicheQuicSslEngineMap engineMap,
641                                               BoringSSLAsyncPrivateKeyMethod privateKeyMethod) {
642             this.engineMap = engineMap;
643             this.privateKeyMethod = privateKeyMethod;
644         }
645 
646         @Override
647         public void sign(long ssl, int signatureAlgorithm, byte[] input, BiConsumer<byte[], Throwable> callback) {
648             final QuicheQuicSslEngine engine = engineMap.get(ssl);
649             if (engine == null) {
650                 // May be null if it was destroyed in the meantime.
651                 callback.accept(null, null);
652             } else {
653                 privateKeyMethod.sign(engine, signatureAlgorithm, input).addListener(f -> {
654                     Throwable cause = f.cause();
655                     if (cause != null) {
656                         callback.accept(null, cause);
657                     } else {
658                         callback.accept((byte[]) f.getNow(), null);
659                     }
660                 });
661             }
662         }
663 
664         @Override
665         public void decrypt(long ssl, byte[] input, BiConsumer<byte[], Throwable> callback) {
666             final QuicheQuicSslEngine engine = engineMap.get(ssl);
667             if (engine == null) {
668                 // May be null if it was destroyed in the meantime.
669                 callback.accept(null, null);
670             } else {
671                 privateKeyMethod.decrypt(engine, input).addListener(f -> {
672                     Throwable cause = f.cause();
673                     if (cause != null) {
674                         callback.accept(null, cause);
675                     } else {
676                         callback.accept((byte[]) f.getNow(), null);
677                     }
678                 });
679             }
680         }
681     }
682 }