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