View Javadoc
1   /*
2    * Copyright 2016 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    *   http://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.ssl;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufAllocator;
20  import io.netty.internal.tcnative.CertificateVerifier;
21  import io.netty.internal.tcnative.SSL;
22  import io.netty.internal.tcnative.SSLContext;
23  import io.netty.internal.tcnative.SSLPrivateKeyMethod;
24  import io.netty.util.AbstractReferenceCounted;
25  import io.netty.util.ReferenceCounted;
26  import io.netty.util.ResourceLeakDetector;
27  import io.netty.util.ResourceLeakDetectorFactory;
28  import io.netty.util.ResourceLeakTracker;
29  import io.netty.util.internal.ObjectUtil;
30  import io.netty.util.internal.PlatformDependent;
31  import io.netty.util.internal.StringUtil;
32  import io.netty.util.internal.SuppressJava6Requirement;
33  import io.netty.util.internal.SystemPropertyUtil;
34  import io.netty.util.internal.UnstableApi;
35  import io.netty.util.internal.logging.InternalLogger;
36  import io.netty.util.internal.logging.InternalLoggerFactory;
37  
38  import java.security.PrivateKey;
39  import java.security.SignatureException;
40  import java.security.cert.CertPathValidatorException;
41  import java.security.cert.Certificate;
42  import java.security.cert.CertificateExpiredException;
43  import java.security.cert.CertificateNotYetValidException;
44  import java.security.cert.CertificateRevokedException;
45  import java.security.cert.X509Certificate;
46  import java.util.Arrays;
47  import java.util.Collections;
48  import java.util.List;
49  import java.util.Map;
50  import java.util.concurrent.Executor;
51  import java.util.concurrent.locks.Lock;
52  import java.util.concurrent.locks.ReadWriteLock;
53  import java.util.concurrent.locks.ReentrantReadWriteLock;
54  
55  import javax.net.ssl.KeyManager;
56  import javax.net.ssl.KeyManagerFactory;
57  import javax.net.ssl.SSLEngine;
58  import javax.net.ssl.SSLException;
59  import javax.net.ssl.SSLHandshakeException;
60  import javax.net.ssl.TrustManager;
61  import javax.net.ssl.X509ExtendedTrustManager;
62  import javax.net.ssl.X509KeyManager;
63  import javax.net.ssl.X509TrustManager;
64  
65  import static io.netty.handler.ssl.OpenSsl.DEFAULT_CIPHERS;
66  import static io.netty.handler.ssl.OpenSsl.availableJavaCipherSuites;
67  import static io.netty.util.internal.ObjectUtil.checkNotNull;
68  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
69  
70  /**
71   * An implementation of {@link SslContext} which works with libraries that support the
72   * <a href="https://www.openssl.org/">OpenSsl</a> C library API.
73   * <p>Instances of this class must be {@link #release() released} or else native memory will leak!
74   *
75   * <p>Instances of this class <strong>must not</strong> be released before any {@link ReferenceCountedOpenSslEngine}
76   * which depends upon the instance of this class is released. Otherwise if any method of
77   * {@link ReferenceCountedOpenSslEngine} is called which uses this class's JNI resources the JVM may crash.
78   */
79  public abstract class ReferenceCountedOpenSslContext extends SslContext implements ReferenceCounted {
80      private static final InternalLogger logger =
81              InternalLoggerFactory.getInstance(ReferenceCountedOpenSslContext.class);
82  
83      private static final int DEFAULT_BIO_NON_APPLICATION_BUFFER_SIZE = Math.max(1,
84              SystemPropertyUtil.getInt("io.netty.handler.ssl.openssl.bioNonApplicationBufferSize",
85                      2048));
86      static final boolean USE_TASKS =
87              SystemPropertyUtil.getBoolean("io.netty.handler.ssl.openssl.useTasks", false);
88      private static final Integer DH_KEY_LENGTH;
89      private static final ResourceLeakDetector<ReferenceCountedOpenSslContext> leakDetector =
90              ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslContext.class);
91  
92      // TODO: Maybe make configurable ?
93      protected static final int VERIFY_DEPTH = 10;
94  
95      /**
96       * The OpenSSL SSL_CTX object.
97       *
98       * <strong>{@link #ctxLock} must be hold while using ctx!</strong>
99       */
100     protected long ctx;
101     private final List<String> unmodifiableCiphers;
102     private final long sessionCacheSize;
103     private final long sessionTimeout;
104     private final OpenSslApplicationProtocolNegotiator apn;
105     private final int mode;
106 
107     // Reference Counting
108     private final ResourceLeakTracker<ReferenceCountedOpenSslContext> leak;
109     private final AbstractReferenceCounted refCnt = new AbstractReferenceCounted() {
110         @Override
111         public ReferenceCounted touch(Object hint) {
112             if (leak != null) {
113                 leak.record(hint);
114             }
115 
116             return ReferenceCountedOpenSslContext.this;
117         }
118 
119         @Override
120         protected void deallocate() {
121             destroy();
122             if (leak != null) {
123                 boolean closed = leak.close(ReferenceCountedOpenSslContext.this);
124                 assert closed;
125             }
126         }
127     };
128 
129     final Certificate[] keyCertChain;
130     final ClientAuth clientAuth;
131     final String[] protocols;
132     final boolean enableOcsp;
133     final OpenSslEngineMap engineMap = new DefaultOpenSslEngineMap();
134     final ReadWriteLock ctxLock = new ReentrantReadWriteLock();
135 
136     private volatile int bioNonApplicationBufferSize = DEFAULT_BIO_NON_APPLICATION_BUFFER_SIZE;
137 
138     @SuppressWarnings("deprecation")
139     static final OpenSslApplicationProtocolNegotiator NONE_PROTOCOL_NEGOTIATOR =
140             new OpenSslApplicationProtocolNegotiator() {
141                 @Override
142                 public ApplicationProtocolConfig.Protocol protocol() {
143                     return ApplicationProtocolConfig.Protocol.NONE;
144                 }
145 
146                 @Override
147                 public List<String> protocols() {
148                     return Collections.emptyList();
149                 }
150 
151                 @Override
152                 public ApplicationProtocolConfig.SelectorFailureBehavior selectorFailureBehavior() {
153                     return ApplicationProtocolConfig.SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL;
154                 }
155 
156                 @Override
157                 public ApplicationProtocolConfig.SelectedListenerFailureBehavior selectedListenerFailureBehavior() {
158                     return ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT;
159                 }
160             };
161 
162     static {
163         Integer dhLen = null;
164 
165         try {
166             String dhKeySize = SystemPropertyUtil.get("jdk.tls.ephemeralDHKeySize");
167             if (dhKeySize != null) {
168                 try {
169                     dhLen = Integer.valueOf(dhKeySize);
170                 } catch (NumberFormatException e) {
171                     logger.debug("ReferenceCountedOpenSslContext supports -Djdk.tls.ephemeralDHKeySize={int}, but got: "
172                             + dhKeySize);
173                 }
174             }
175         } catch (Throwable ignore) {
176             // ignore
177         }
178         DH_KEY_LENGTH = dhLen;
179     }
180 
181     ReferenceCountedOpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
182                                    ApplicationProtocolConfig apnCfg, long sessionCacheSize, long sessionTimeout,
183                                    int mode, Certificate[] keyCertChain, ClientAuth clientAuth, String[] protocols,
184                                    boolean startTls, boolean enableOcsp, boolean leakDetection) throws SSLException {
185         this(ciphers, cipherFilter, toNegotiator(apnCfg), sessionCacheSize, sessionTimeout, mode, keyCertChain,
186                 clientAuth, protocols, startTls, enableOcsp, leakDetection);
187     }
188 
189     ReferenceCountedOpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
190                                    OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize,
191                                    long sessionTimeout, int mode, Certificate[] keyCertChain,
192                                    ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp,
193                                    boolean leakDetection) throws SSLException {
194         super(startTls);
195 
196         OpenSsl.ensureAvailability();
197 
198         if (enableOcsp && !OpenSsl.isOcspSupported()) {
199             throw new IllegalStateException("OCSP is not supported.");
200         }
201 
202         if (mode != SSL.SSL_MODE_SERVER && mode != SSL.SSL_MODE_CLIENT) {
203             throw new IllegalArgumentException("mode most be either SSL.SSL_MODE_SERVER or SSL.SSL_MODE_CLIENT");
204         }
205         leak = leakDetection ? leakDetector.track(this) : null;
206         this.mode = mode;
207         this.clientAuth = isServer() ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE;
208         this.protocols = protocols;
209         this.enableOcsp = enableOcsp;
210 
211         this.keyCertChain = keyCertChain == null ? null : keyCertChain.clone();
212 
213         unmodifiableCiphers = Arrays.asList(checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites(
214                 ciphers, DEFAULT_CIPHERS, availableJavaCipherSuites()));
215 
216         this.apn = checkNotNull(apn, "apn");
217 
218         // Create a new SSL_CTX and configure it.
219         boolean success = false;
220         try {
221             try {
222                 int protocolOpts = SSL.SSL_PROTOCOL_SSLV3 | SSL.SSL_PROTOCOL_TLSV1 |
223                                    SSL.SSL_PROTOCOL_TLSV1_1 | SSL.SSL_PROTOCOL_TLSV1_2;
224                 if (OpenSsl.isTlsv13Supported()) {
225                     protocolOpts |= SSL.SSL_PROTOCOL_TLSV1_3;
226                 }
227                 ctx = SSLContext.make(protocolOpts, mode);
228             } catch (Exception e) {
229                 throw new SSLException("failed to create an SSL_CTX", e);
230             }
231 
232             boolean tlsv13Supported = OpenSsl.isTlsv13Supported();
233             StringBuilder cipherBuilder = new StringBuilder();
234             StringBuilder cipherTLSv13Builder = new StringBuilder();
235 
236             /* List the ciphers that are permitted to negotiate. */
237             try {
238                 if (unmodifiableCiphers.isEmpty()) {
239                     // Set non TLSv1.3 ciphers.
240                     SSLContext.setCipherSuite(ctx, StringUtil.EMPTY_STRING, false);
241                     if (tlsv13Supported) {
242                         // Set TLSv1.3 ciphers.
243                         SSLContext.setCipherSuite(ctx, StringUtil.EMPTY_STRING, true);
244                     }
245                 } else {
246                     CipherSuiteConverter.convertToCipherStrings(
247                             unmodifiableCiphers, cipherBuilder, cipherTLSv13Builder, OpenSsl.isBoringSSL());
248 
249                     // Set non TLSv1.3 ciphers.
250                     SSLContext.setCipherSuite(ctx, cipherBuilder.toString(), false);
251                     if (tlsv13Supported) {
252                         // Set TLSv1.3 ciphers.
253                         SSLContext.setCipherSuite(ctx, cipherTLSv13Builder.toString(), true);
254                     }
255                 }
256             } catch (SSLException e) {
257                 throw e;
258             } catch (Exception e) {
259                 throw new SSLException("failed to set cipher suite: " + unmodifiableCiphers, e);
260             }
261 
262             int options = SSLContext.getOptions(ctx) |
263                           SSL.SSL_OP_NO_SSLv2 |
264                           SSL.SSL_OP_NO_SSLv3 |
265                           // Disable TLSv1.3 by default for now. Even if TLSv1.3 is not supported this will
266                           // work fine as in this case SSL_OP_NO_TLSv1_3 will be 0.
267                           SSL.SSL_OP_NO_TLSv1_3 |
268 
269                           SSL.SSL_OP_CIPHER_SERVER_PREFERENCE |
270 
271                           // We do not support compression at the moment so we should explicitly disable it.
272                           SSL.SSL_OP_NO_COMPRESSION |
273 
274                           // Disable ticket support by default to be more inline with SSLEngineImpl of the JDK.
275                           // This also let SSLSession.getId() work the same way for the JDK implementation and the
276                           // OpenSSLEngine. If tickets are supported SSLSession.getId() will only return an ID on the
277                           // server-side if it could make use of tickets.
278                           SSL.SSL_OP_NO_TICKET;
279 
280             if (cipherBuilder.length() == 0) {
281                 // No ciphers that are compatible with SSLv2 / SSLv3 / TLSv1 / TLSv1.1 / TLSv1.2
282                 options |= SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1
283                            | SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2;
284             }
285 
286             SSLContext.setOptions(ctx, options);
287 
288             // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address may change between
289             // calling OpenSSLEngine.wrap(...).
290             // See https://github.com/netty/netty-tcnative/issues/100
291             SSLContext.setMode(ctx, SSLContext.getMode(ctx) | SSL.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
292 
293             if (DH_KEY_LENGTH != null) {
294                 SSLContext.setTmpDHLength(ctx, DH_KEY_LENGTH);
295             }
296 
297             List<String> nextProtoList = apn.protocols();
298                 /* Set next protocols for next protocol negotiation extension, if specified */
299             if (!nextProtoList.isEmpty()) {
300                 String[] appProtocols = nextProtoList.toArray(new String[0]);
301                 int selectorBehavior = opensslSelectorFailureBehavior(apn.selectorFailureBehavior());
302 
303                 switch (apn.protocol()) {
304                     case NPN:
305                         SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior);
306                         break;
307                     case ALPN:
308                         SSLContext.setAlpnProtos(ctx, appProtocols, selectorBehavior);
309                         break;
310                     case NPN_AND_ALPN:
311                         SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior);
312                         SSLContext.setAlpnProtos(ctx, appProtocols, selectorBehavior);
313                         break;
314                     default:
315                         throw new Error();
316                 }
317             }
318 
319             /* Set session cache size, if specified */
320             if (sessionCacheSize <= 0) {
321                 // Get the default session cache size using SSLContext.setSessionCacheSize()
322                 sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480);
323             }
324             this.sessionCacheSize = sessionCacheSize;
325             SSLContext.setSessionCacheSize(ctx, sessionCacheSize);
326 
327             /* Set session timeout, if specified */
328             if (sessionTimeout <= 0) {
329                 // Get the default session timeout using SSLContext.setSessionCacheTimeout()
330                 sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300);
331             }
332             this.sessionTimeout = sessionTimeout;
333             SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
334 
335             if (enableOcsp) {
336                 SSLContext.enableOcsp(ctx, isClient());
337             }
338 
339             SSLContext.setUseTasks(ctx, USE_TASKS);
340             success = true;
341         } finally {
342             if (!success) {
343                 release();
344             }
345         }
346     }
347 
348     private static int opensslSelectorFailureBehavior(ApplicationProtocolConfig.SelectorFailureBehavior behavior) {
349         switch (behavior) {
350             case NO_ADVERTISE:
351                 return SSL.SSL_SELECTOR_FAILURE_NO_ADVERTISE;
352             case CHOOSE_MY_LAST_PROTOCOL:
353                 return SSL.SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL;
354             default:
355                 throw new Error();
356         }
357     }
358 
359     @Override
360     public final List<String> cipherSuites() {
361         return unmodifiableCiphers;
362     }
363 
364     @Override
365     public final long sessionCacheSize() {
366         return sessionCacheSize;
367     }
368 
369     @Override
370     public final long sessionTimeout() {
371         return sessionTimeout;
372     }
373 
374     @Override
375     public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
376         return apn;
377     }
378 
379     @Override
380     public final boolean isClient() {
381         return mode == SSL.SSL_MODE_CLIENT;
382     }
383 
384     @Override
385     public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
386         return newEngine0(alloc, peerHost, peerPort, true);
387     }
388 
389     @Override
390     protected final SslHandler newHandler(ByteBufAllocator alloc, boolean startTls) {
391         return new SslHandler(newEngine0(alloc, null, -1, false), startTls);
392     }
393 
394     @Override
395     protected final SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort, boolean startTls) {
396         return new SslHandler(newEngine0(alloc, peerHost, peerPort, false), startTls);
397     }
398 
399     @Override
400     protected SslHandler newHandler(ByteBufAllocator alloc, boolean startTls, Executor executor) {
401         return new SslHandler(newEngine0(alloc, null, -1, false), startTls, executor);
402     }
403 
404     @Override
405     protected SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort,
406                                     boolean startTls, Executor executor) {
407         return new SslHandler(newEngine0(alloc, peerHost, peerPort, false), executor);
408     }
409 
410     SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort, boolean jdkCompatibilityMode) {
411         return new ReferenceCountedOpenSslEngine(this, alloc, peerHost, peerPort, jdkCompatibilityMode, true);
412     }
413 
414     /**
415      * Returns a new server-side {@link SSLEngine} with the current configuration.
416      */
417     @Override
418     public final SSLEngine newEngine(ByteBufAllocator alloc) {
419         return newEngine(alloc, null, -1);
420     }
421 
422     /**
423      * Returns the pointer to the {@code SSL_CTX} object for this {@link ReferenceCountedOpenSslContext}.
424      * Be aware that it is freed as soon as the {@link #finalize()}  method is called.
425      * At this point {@code 0} will be returned.
426      *
427      * @deprecated this method is considered unsafe as the returned pointer may be released later. Dont use it!
428      */
429     @Deprecated
430     public final long context() {
431         return sslCtxPointer();
432     }
433 
434     /**
435      * Returns the stats of this context.
436      *
437      * @deprecated use {@link #sessionContext#stats()}
438      */
439     @Deprecated
440     public final OpenSslSessionStats stats() {
441         return sessionContext().stats();
442     }
443 
444     /**
445      * {@deprecated Renegotiation is not supported}
446      * Specify if remote initiated renegotiation is supported or not. If not supported and the remote side tries
447      * to initiate a renegotiation a {@link SSLHandshakeException} will be thrown during decoding.
448      */
449     @Deprecated
450     public void setRejectRemoteInitiatedRenegotiation(boolean rejectRemoteInitiatedRenegotiation) {
451         if (!rejectRemoteInitiatedRenegotiation) {
452             throw new UnsupportedOperationException("Renegotiation is not supported");
453         }
454     }
455 
456     /**
457      * {@deprecated Renegotiation is not supported}
458      * @return {@code true} because renegotiation is not supported.
459      */
460     @Deprecated
461     public boolean getRejectRemoteInitiatedRenegotiation() {
462         return true;
463     }
464 
465     /**
466      * Set the size of the buffer used by the BIO for non-application based writes
467      * (e.g. handshake, renegotiation, etc...).
468      */
469     public void setBioNonApplicationBufferSize(int bioNonApplicationBufferSize) {
470         this.bioNonApplicationBufferSize =
471                 checkPositiveOrZero(bioNonApplicationBufferSize, "bioNonApplicationBufferSize");
472     }
473 
474     /**
475      * Returns the size of the buffer used by the BIO for non-application based writes
476      */
477     public int getBioNonApplicationBufferSize() {
478         return bioNonApplicationBufferSize;
479     }
480 
481     /**
482      * Sets the SSL session ticket keys of this context.
483      *
484      * @deprecated use {@link OpenSslSessionContext#setTicketKeys(byte[])}
485      */
486     @Deprecated
487     public final void setTicketKeys(byte[] keys) {
488         sessionContext().setTicketKeys(keys);
489     }
490 
491     @Override
492     public abstract OpenSslSessionContext sessionContext();
493 
494     /**
495      * Returns the pointer to the {@code SSL_CTX} object for this {@link ReferenceCountedOpenSslContext}.
496      * Be aware that it is freed as soon as the {@link #release()} method is called.
497      * At this point {@code 0} will be returned.
498      *
499      * @deprecated this method is considered unsafe as the returned pointer may be released later. Dont use it!
500      */
501     @Deprecated
502     public final long sslCtxPointer() {
503         Lock readerLock = ctxLock.readLock();
504         readerLock.lock();
505         try {
506             return SSLContext.getSslCtx(ctx);
507         } finally {
508             readerLock.unlock();
509         }
510     }
511 
512     /**
513      * Set the {@link OpenSslPrivateKeyMethod} to use. This allows to offload private-key operations
514      * if needed.
515      *
516      * This method is currently only supported when {@code BoringSSL} is used.
517      *
518      * @param method method to use.
519      */
520     @UnstableApi
521     public final void setPrivateKeyMethod(OpenSslPrivateKeyMethod method) {
522         ObjectUtil.checkNotNull(method, "method");
523         Lock writerLock = ctxLock.writeLock();
524         writerLock.lock();
525         try {
526             SSLContext.setPrivateKeyMethod(ctx, new PrivateKeyMethod(engineMap, method));
527         } finally {
528             writerLock.unlock();
529         }
530     }
531 
532     public final void setUseTasks(boolean useTasks) {
533         Lock writerLock = ctxLock.writeLock();
534         writerLock.lock();
535         try {
536             SSLContext.setUseTasks(ctx, useTasks);
537         } finally {
538             writerLock.unlock();
539         }
540     }
541 
542     // IMPORTANT: This method must only be called from either the constructor or the finalizer as a user MUST never
543     //            get access to an OpenSslSessionContext after this method was called to prevent the user from
544     //            producing a segfault.
545     private void destroy() {
546         Lock writerLock = ctxLock.writeLock();
547         writerLock.lock();
548         try {
549             if (ctx != 0) {
550                 if (enableOcsp) {
551                     SSLContext.disableOcsp(ctx);
552                 }
553 
554                 SSLContext.free(ctx);
555                 ctx = 0;
556 
557                 OpenSslSessionContext context = sessionContext();
558                 if (context != null) {
559                     context.destroy();
560                 }
561             }
562         } finally {
563             writerLock.unlock();
564         }
565     }
566 
567     protected static X509Certificate[] certificates(byte[][] chain) {
568         X509Certificate[] peerCerts = new X509Certificate[chain.length];
569         for (int i = 0; i < peerCerts.length; i++) {
570             peerCerts[i] = new OpenSslX509Certificate(chain[i]);
571         }
572         return peerCerts;
573     }
574 
575     protected static X509TrustManager chooseTrustManager(TrustManager[] managers) {
576         for (TrustManager m : managers) {
577             if (m instanceof X509TrustManager) {
578                 if (PlatformDependent.javaVersion() >= 7) {
579                     return OpenSslX509TrustManagerWrapper.wrapIfNeeded((X509TrustManager) m);
580                 }
581                 return (X509TrustManager) m;
582             }
583         }
584         throw new IllegalStateException("no X509TrustManager found");
585     }
586 
587     protected static X509KeyManager chooseX509KeyManager(KeyManager[] kms) {
588         for (KeyManager km : kms) {
589             if (km instanceof X509KeyManager) {
590                 return (X509KeyManager) km;
591             }
592         }
593         throw new IllegalStateException("no X509KeyManager found");
594     }
595 
596     /**
597      * Translate a {@link ApplicationProtocolConfig} object to a
598      * {@link OpenSslApplicationProtocolNegotiator} object.
599      *
600      * @param config The configuration which defines the translation
601      * @return The results of the translation
602      */
603     @SuppressWarnings("deprecation")
604     static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config) {
605         if (config == null) {
606             return NONE_PROTOCOL_NEGOTIATOR;
607         }
608 
609         switch (config.protocol()) {
610             case NONE:
611                 return NONE_PROTOCOL_NEGOTIATOR;
612             case ALPN:
613             case NPN:
614             case NPN_AND_ALPN:
615                 switch (config.selectedListenerFailureBehavior()) {
616                     case CHOOSE_MY_LAST_PROTOCOL:
617                     case ACCEPT:
618                         switch (config.selectorFailureBehavior()) {
619                             case CHOOSE_MY_LAST_PROTOCOL:
620                             case NO_ADVERTISE:
621                                 return new OpenSslDefaultApplicationProtocolNegotiator(
622                                         config);
623                             default:
624                                 throw new UnsupportedOperationException(
625                                         new StringBuilder("OpenSSL provider does not support ")
626                                                 .append(config.selectorFailureBehavior())
627                                                 .append(" behavior").toString());
628                         }
629                     default:
630                         throw new UnsupportedOperationException(
631                                 new StringBuilder("OpenSSL provider does not support ")
632                                         .append(config.selectedListenerFailureBehavior())
633                                         .append(" behavior").toString());
634                 }
635             default:
636                 throw new Error();
637         }
638     }
639 
640     @SuppressJava6Requirement(reason = "Guarded by java version check")
641     static boolean useExtendedTrustManager(X509TrustManager trustManager) {
642         return PlatformDependent.javaVersion() >= 7 && trustManager instanceof X509ExtendedTrustManager;
643     }
644 
645     @Override
646     public final int refCnt() {
647         return refCnt.refCnt();
648     }
649 
650     @Override
651     public final ReferenceCounted retain() {
652         refCnt.retain();
653         return this;
654     }
655 
656     @Override
657     public final ReferenceCounted retain(int increment) {
658         refCnt.retain(increment);
659         return this;
660     }
661 
662     @Override
663     public final ReferenceCounted touch() {
664         refCnt.touch();
665         return this;
666     }
667 
668     @Override
669     public final ReferenceCounted touch(Object hint) {
670         refCnt.touch(hint);
671         return this;
672     }
673 
674     @Override
675     public final boolean release() {
676         return refCnt.release();
677     }
678 
679     @Override
680     public final boolean release(int decrement) {
681         return refCnt.release(decrement);
682     }
683 
684     abstract static class AbstractCertificateVerifier extends CertificateVerifier {
685         private final OpenSslEngineMap engineMap;
686 
687         AbstractCertificateVerifier(OpenSslEngineMap engineMap) {
688             this.engineMap = engineMap;
689         }
690 
691         @Override
692         public final int verify(long ssl, byte[][] chain, String auth) {
693             final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl);
694             if (engine == null) {
695                 // May be null if it was destroyed in the meantime.
696                 return CertificateVerifier.X509_V_ERR_UNSPECIFIED;
697             }
698             X509Certificate[] peerCerts = certificates(chain);
699             try {
700                 verify(engine, peerCerts, auth);
701                 return CertificateVerifier.X509_V_OK;
702             } catch (Throwable cause) {
703                 logger.debug("verification of certificate failed", cause);
704                 engine.initHandshakeException(cause);
705 
706                 // Try to extract the correct error code that should be used.
707                 if (cause instanceof OpenSslCertificateException) {
708                     // This will never return a negative error code as its validated when constructing the
709                     // OpenSslCertificateException.
710                     return ((OpenSslCertificateException) cause).errorCode();
711                 }
712                 if (cause instanceof CertificateExpiredException) {
713                     return CertificateVerifier.X509_V_ERR_CERT_HAS_EXPIRED;
714                 }
715                 if (cause instanceof CertificateNotYetValidException) {
716                     return CertificateVerifier.X509_V_ERR_CERT_NOT_YET_VALID;
717                 }
718                 if (PlatformDependent.javaVersion() >= 7) {
719                     return translateToError(cause);
720                 }
721 
722                 // Could not detect a specific error code to use, so fallback to a default code.
723                 return CertificateVerifier.X509_V_ERR_UNSPECIFIED;
724             }
725         }
726 
727         @SuppressJava6Requirement(reason = "Usage guarded by java version check")
728         private static int translateToError(Throwable cause) {
729             if (cause instanceof CertificateRevokedException) {
730                 return CertificateVerifier.X509_V_ERR_CERT_REVOKED;
731             }
732 
733             // The X509TrustManagerImpl uses a Validator which wraps a CertPathValidatorException into
734             // an CertificateException. So we need to handle the wrapped CertPathValidatorException to be
735             // able to send the correct alert.
736             Throwable wrapped = cause.getCause();
737             while (wrapped != null) {
738                 if (wrapped instanceof CertPathValidatorException) {
739                     CertPathValidatorException ex = (CertPathValidatorException) wrapped;
740                     CertPathValidatorException.Reason reason = ex.getReason();
741                     if (reason == CertPathValidatorException.BasicReason.EXPIRED) {
742                         return CertificateVerifier.X509_V_ERR_CERT_HAS_EXPIRED;
743                     }
744                     if (reason == CertPathValidatorException.BasicReason.NOT_YET_VALID) {
745                         return CertificateVerifier.X509_V_ERR_CERT_NOT_YET_VALID;
746                     }
747                     if (reason == CertPathValidatorException.BasicReason.REVOKED) {
748                         return CertificateVerifier.X509_V_ERR_CERT_REVOKED;
749                     }
750                 }
751                 wrapped = wrapped.getCause();
752             }
753             return CertificateVerifier.X509_V_ERR_UNSPECIFIED;
754         }
755 
756         abstract void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts,
757                              String auth) throws Exception;
758     }
759 
760     private static final class DefaultOpenSslEngineMap implements OpenSslEngineMap {
761         private final Map<Long, ReferenceCountedOpenSslEngine> engines = PlatformDependent.newConcurrentHashMap();
762 
763         @Override
764         public ReferenceCountedOpenSslEngine remove(long ssl) {
765             return engines.remove(ssl);
766         }
767 
768         @Override
769         public void add(ReferenceCountedOpenSslEngine engine) {
770             engines.put(engine.sslPointer(), engine);
771         }
772 
773         @Override
774         public ReferenceCountedOpenSslEngine get(long ssl) {
775             return engines.get(ssl);
776         }
777     }
778 
779     static void setKeyMaterial(long ctx, X509Certificate[] keyCertChain, PrivateKey key, String keyPassword)
780             throws SSLException {
781          /* Load the certificate file and private key. */
782         long keyBio = 0;
783         long keyCertChainBio = 0;
784         long keyCertChainBio2 = 0;
785         PemEncoded encoded = null;
786         try {
787             // Only encode one time
788             encoded = PemX509Certificate.toPEM(ByteBufAllocator.DEFAULT, true, keyCertChain);
789             keyCertChainBio = toBIO(ByteBufAllocator.DEFAULT, encoded.retain());
790             keyCertChainBio2 = toBIO(ByteBufAllocator.DEFAULT, encoded.retain());
791 
792             if (key != null) {
793                 keyBio = toBIO(ByteBufAllocator.DEFAULT, key);
794             }
795 
796             SSLContext.setCertificateBio(
797                     ctx, keyCertChainBio, keyBio,
798                     keyPassword == null ? StringUtil.EMPTY_STRING : keyPassword);
799             // We may have more then one cert in the chain so add all of them now.
800             SSLContext.setCertificateChainBio(ctx, keyCertChainBio2, true);
801         } catch (SSLException e) {
802             throw e;
803         } catch (Exception e) {
804             throw new SSLException("failed to set certificate and key", e);
805         } finally {
806             freeBio(keyBio);
807             freeBio(keyCertChainBio);
808             freeBio(keyCertChainBio2);
809             if (encoded != null) {
810                 encoded.release();
811             }
812         }
813     }
814 
815     static void freeBio(long bio) {
816         if (bio != 0) {
817             SSL.freeBIO(bio);
818         }
819     }
820 
821     /**
822      * Return the pointer to a <a href="https://www.openssl.org/docs/crypto/BIO_get_mem_ptr.html">in-memory BIO</a>
823      * or {@code 0} if the {@code key} is {@code null}. The BIO contains the content of the {@code key}.
824      */
825     static long toBIO(ByteBufAllocator allocator, PrivateKey key) throws Exception {
826         if (key == null) {
827             return 0;
828         }
829 
830         PemEncoded pem = PemPrivateKey.toPEM(allocator, true, key);
831         try {
832             return toBIO(allocator, pem.retain());
833         } finally {
834             pem.release();
835         }
836     }
837 
838     /**
839      * Return the pointer to a <a href="https://www.openssl.org/docs/crypto/BIO_get_mem_ptr.html">in-memory BIO</a>
840      * or {@code 0} if the {@code certChain} is {@code null}. The BIO contains the content of the {@code certChain}.
841      */
842     static long toBIO(ByteBufAllocator allocator, X509Certificate... certChain) throws Exception {
843         if (certChain == null) {
844             return 0;
845         }
846 
847         if (certChain.length == 0) {
848             throw new IllegalArgumentException("certChain can't be empty");
849         }
850 
851         PemEncoded pem = PemX509Certificate.toPEM(allocator, true, certChain);
852         try {
853             return toBIO(allocator, pem.retain());
854         } finally {
855             pem.release();
856         }
857     }
858 
859     static long toBIO(ByteBufAllocator allocator, PemEncoded pem) throws Exception {
860         try {
861             // We can turn direct buffers straight into BIOs. No need to
862             // make a yet another copy.
863             ByteBuf content = pem.content();
864 
865             if (content.isDirect()) {
866                 return newBIO(content.retainedSlice());
867             }
868 
869             ByteBuf buffer = allocator.directBuffer(content.readableBytes());
870             try {
871                 buffer.writeBytes(content, content.readerIndex(), content.readableBytes());
872                 return newBIO(buffer.retainedSlice());
873             } finally {
874                 try {
875                     // If the contents of the ByteBuf is sensitive (e.g. a PrivateKey) we
876                     // need to zero out the bytes of the copy before we're releasing it.
877                     if (pem.isSensitive()) {
878                         SslUtils.zeroout(buffer);
879                     }
880                 } finally {
881                     buffer.release();
882                 }
883             }
884         } finally {
885             pem.release();
886         }
887     }
888 
889     private static long newBIO(ByteBuf buffer) throws Exception {
890         try {
891             long bio = SSL.newMemBIO();
892             int readable = buffer.readableBytes();
893             if (SSL.bioWrite(bio, OpenSsl.memoryAddress(buffer) + buffer.readerIndex(), readable) != readable) {
894                 SSL.freeBIO(bio);
895                 throw new IllegalStateException("Could not write data to memory BIO");
896             }
897             return bio;
898         } finally {
899             buffer.release();
900         }
901     }
902 
903     /**
904      * Returns the {@link OpenSslKeyMaterialProvider} that should be used for OpenSSL. Depending on the given
905      * {@link KeyManagerFactory} this may cache the {@link OpenSslKeyMaterial} for better performance if it can
906      * ensure that the same material is always returned for the same alias.
907      */
908     static OpenSslKeyMaterialProvider providerFor(KeyManagerFactory factory, String password) {
909         if (factory instanceof OpenSslX509KeyManagerFactory) {
910             return ((OpenSslX509KeyManagerFactory) factory).newProvider();
911         }
912 
913         if (factory instanceof OpenSslCachingX509KeyManagerFactory) {
914             // The user explicit used OpenSslCachingX509KeyManagerFactory which signals us that its fine to cache.
915             return ((OpenSslCachingX509KeyManagerFactory) factory).newProvider(password);
916         }
917         // We can not be sure if the material may change at runtime so we will not cache it.
918         return new OpenSslKeyMaterialProvider(chooseX509KeyManager(factory.getKeyManagers()), password);
919     }
920 
921     private static final class PrivateKeyMethod implements SSLPrivateKeyMethod {
922 
923         private final OpenSslEngineMap engineMap;
924         private final OpenSslPrivateKeyMethod keyMethod;
925         PrivateKeyMethod(OpenSslEngineMap engineMap, OpenSslPrivateKeyMethod keyMethod) {
926             this.engineMap = engineMap;
927             this.keyMethod = keyMethod;
928         }
929 
930         private ReferenceCountedOpenSslEngine retrieveEngine(long ssl) throws SSLException {
931             ReferenceCountedOpenSslEngine engine = engineMap.get(ssl);
932             if (engine == null) {
933                 throw new SSLException("Could not find a " +
934                         StringUtil.simpleClassName(ReferenceCountedOpenSslEngine.class) + " for sslPointer " + ssl);
935             }
936             return engine;
937         }
938 
939         @Override
940         public byte[] sign(long ssl, int signatureAlgorithm, byte[] digest) throws Exception {
941             ReferenceCountedOpenSslEngine engine = retrieveEngine(ssl);
942             try {
943                 return verifyResult(keyMethod.sign(engine, signatureAlgorithm, digest));
944             } catch (Exception e) {
945                 engine.initHandshakeException(e);
946                 throw e;
947             }
948         }
949 
950         @Override
951         public byte[] decrypt(long ssl, byte[] input) throws Exception {
952             ReferenceCountedOpenSslEngine engine = retrieveEngine(ssl);
953             try {
954                 return verifyResult(keyMethod.decrypt(engine, input));
955             } catch (Exception e) {
956                 engine.initHandshakeException(e);
957                 throw e;
958             }
959         }
960 
961         private static byte[] verifyResult(byte[] result) throws SignatureException {
962             if (result == null) {
963                 throw new SignatureException();
964             }
965             return result;
966         }
967     }
968 }