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.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      // Use default that is supported in java 11 and earlier and also in OpenSSL / BoringSSL.
68      // See https://github.com/netty/netty-tcnative/issues/567
69      // See https://www.java.com/en/configure_crypto.html for ordering
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          // Call Quic.isAvailable() first to ensure native lib is loaded.
83          // See https://github.com/netty/netty-incubator-codec-quic/issues/759
84          if (Quic.isAvailable()) {
85              final long sslCtx = BoringSSL.SSLContext_new();
86              try {
87                  // Let's filter out any group that is not supported from the default.
88                  Iterator<String> defaultGroupsIter = defaultConvertedNamedGroups.iterator();
89                  while (defaultGroupsIter.hasNext()) {
90                      if (BoringSSL.SSLContext_set1_groups_list(sslCtx, defaultGroupsIter.next()) == 0) {
91                          // Not supported, let's remove it. This could for example be the case if we use
92                          // fips and the configure group is not supported when using FIPS.
93                          // See https://github.com/netty/netty-tcnative/issues/883
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                         // Will return 0 on failure.
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                     // We have some more details about why the operations failed, include these into the message.
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                     // We have some more details about why the operations failed, include these into the message.
259                     msg += ". " + lastError;
260                 }
261                 throw new IllegalStateException(msg);
262             }
263 
264             apn = new QuicheQuicApplicationProtocolNegotiator(applicationProtocols);
265             if (this.sessionCache != null) {
266                 // Cache is handled via our own implementation.
267                 this.sessionCache.setSessionCacheSize((int) sessionCacheSize);
268                 this.sessionCache.setSessionTimeout((int) sessionTimeout);
269             } else {
270                 // Cache is handled by BoringSSL internally
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             // We retained before but as we don't create a QuicheQuicConnection and transfer ownership we need to
345             // explict call release again here.
346             nativeSslContext.release();
347             return null;
348         }
349         // The connection will call nativeSslContext.release() once it is freed.
350         return new QuicheQuicConnection(connection, ssl, engine, nativeSslContext);
351     }
352 
353     /**
354      * Add the given engine to this context
355      *
356      * @param engine    the engine to add.
357      * @return          the pointer address of this context.
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      * Remove the given engine from this context.
368      *
369      * @param engine    the engine to remove.
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                 // May be null if it was destroyed in the meantime.
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                 // May be null if it was destroyed in the meantime.
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 }