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