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                          @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                     // We have some more details about why the operations failed, include these into the message.
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                     // We have some more details about why the operations failed, include these into the message.
267                     msg += ". " + lastError;
268                 }
269                 throw new IllegalStateException(msg);
270             }
271 
272             apn = new QuicheQuicApplicationProtocolNegotiator(applicationProtocols);
273             if (this.sessionCache != null) {
274                 // Cache is handled via our own implementation.
275                 this.sessionCache.setSessionCacheSize((int) sessionCacheSize);
276                 this.sessionCache.setSessionTimeout((int) sessionTimeout);
277             } else {
278                 // Cache is handled by BoringSSL internally
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             // We retained before but as we don't create a QuicheQuicConnection and transfer ownership we need to
353             // explict call release again here.
354             nativeSslContext.release();
355             return null;
356         }
357         // The connection will call nativeSslContext.release() once it is freed.
358         return new QuicheQuicConnection(connection, ssl, engine, nativeSslContext);
359     }
360 
361     /**
362      * Add the given engine to this context
363      *
364      * @param engine    the engine to add.
365      * @return          the pointer address of this context.
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      * Remove the given engine from this context.
376      *
377      * @param engine    the engine to remove.
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                 // May be null if it was destroyed in the meantime.
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                 // May be null if it was destroyed in the meantime.
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 }