You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2015/06/18 19:13:41 UTC

svn commit: r1686279 [2/2] - /tomcat/trunk/java/org/apache/tomcat/util/net/openssl/

Added: tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java?rev=1686279&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java Thu Jun 18 17:13:40 2015
@@ -0,0 +1,1312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net.openssl;
+
+import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionBindingEvent;
+import javax.net.ssl.SSLSessionBindingListener;
+import javax.net.ssl.SSLSessionContext;
+import javax.security.cert.CertificateException;
+import javax.security.cert.X509Certificate;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jni.Buffer;
+import org.apache.tomcat.jni.Pool;
+import org.apache.tomcat.jni.SSL;
+import org.apache.tomcat.jni.SSLContext;
+import org.apache.tomcat.util.buf.ByteBufferUtils;
+import org.apache.tomcat.util.net.Constants;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Implements a {@link SSLEngine} using
+ * <a href="https://www.openssl.org/docs/crypto/BIO_s_bio.html#EXAMPLE">OpenSSL
+ * BIO abstractions</a>.
+ */
+public final class OpenSSLEngine extends SSLEngine {
+
+    private static final Log logger = LogFactory.getLog(OpenSSLEngine.class);
+    private static final StringManager sm = StringManager.getManager(OpenSSLEngine.class);
+
+    private static final Certificate[] EMPTY_CERTIFICATES = new Certificate[0];
+    private static final SSLException ENGINE_CLOSED = new SSLException(sm.getString("engine.engineClosed"));
+    private static final SSLException RENEGOTIATION_UNSUPPORTED = new SSLException(sm.getString("engine.renegociationUnsupported"));
+    private static final SSLException ENCRYPTED_PACKET_OVERSIZED = new SSLException(sm.getString("engine.oversizedPacket"));
+
+    private static final Set<String> AVAILABLE_CIPHER_SUITES;
+
+    static {
+        final Set<String> availableCipherSuites = new LinkedHashSet<String>(128);
+        final long aprPool = Pool.create(0);
+        try {
+            final long sslCtx = SSLContext.make(aprPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
+            try {
+                SSLContext.setOptions(sslCtx, SSL.SSL_OP_ALL);
+                SSLContext.setCipherSuite(sslCtx, "ALL");
+                final long ssl = SSL.newSSL(sslCtx, true);
+                try {
+                    for (String c: SSL.getCiphers(ssl)) {
+                        // Filter out bad input.
+                        if (c == null || c.length() == 0 || availableCipherSuites.contains(c)) {
+                            continue;
+                        }
+                        availableCipherSuites.add(CipherSuiteConverter.toJava(c, "ALL"));
+                    }
+                } finally {
+                    SSL.freeSSL(ssl);
+                }
+            } finally {
+                SSLContext.free(sslCtx);
+            }
+        } catch (Exception e) {
+            logger.warn(sm.getString("engine.ciphersFailure"), e);
+        } finally {
+            Pool.destroy(aprPool);
+        }
+        AVAILABLE_CIPHER_SUITES = Collections.unmodifiableSet(availableCipherSuites);
+    }
+
+    static {
+        ENGINE_CLOSED.setStackTrace(new StackTraceElement[0]);
+        RENEGOTIATION_UNSUPPORTED.setStackTrace(new StackTraceElement[0]);
+        ENCRYPTED_PACKET_OVERSIZED.setStackTrace(new StackTraceElement[0]);
+        DESTROYED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(OpenSSLEngine.class, "destroyed");
+        SESSION_UPDATER = AtomicReferenceFieldUpdater.newUpdater(OpenSSLEngine.class, SSLSession.class, "session");
+    }
+
+    private static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14
+    private static final int MAX_COMPRESSED_LENGTH = MAX_PLAINTEXT_LENGTH + 1024;
+    private static final int MAX_CIPHERTEXT_LENGTH = MAX_COMPRESSED_LENGTH + 1024;
+
+    // Protocols
+    protected static final int VERIFY_DEPTH = 10;
+
+    private static final String[] SUPPORTED_PROTOCOLS = {
+        Constants.SSL_PROTO_SSLv2Hello,
+        Constants.SSL_PROTO_SSLv2,
+        Constants.SSL_PROTO_SSLv3,
+        Constants.SSL_PROTO_TLSv1,
+        Constants.SSL_PROTO_TLSv1_1,
+        Constants.SSL_PROTO_TLSv1_2
+    };
+    private static final Set<String> SUPPORTED_PROTOCOLS_SET = new HashSet<String>(Arrays.asList(SUPPORTED_PROTOCOLS));
+
+    // Header (5) + Data (2^14) + Compression (1024) + Encryption (1024) + MAC (20) + Padding (256)
+    static final int MAX_ENCRYPTED_PACKET_LENGTH = MAX_CIPHERTEXT_LENGTH + 5 + 20 + 256;
+
+    static final int MAX_ENCRYPTION_OVERHEAD_LENGTH = MAX_ENCRYPTED_PACKET_LENGTH - MAX_PLAINTEXT_LENGTH;
+
+    enum ClientAuthMode {
+        NONE,
+        OPTIONAL,
+        REQUIRE,
+    }
+
+    private static final AtomicIntegerFieldUpdater<OpenSSLEngine> DESTROYED_UPDATER;
+    private static final AtomicReferenceFieldUpdater<OpenSSLEngine, SSLSession> SESSION_UPDATER;
+
+    private static final String INVALID_CIPHER = "SSL_NULL_WITH_NULL_NULL";
+
+    private static final long EMPTY_ADDR = Buffer.address(ByteBuffer.allocate(0));
+
+    // OpenSSL state
+    private long ssl;
+    private long networkBIO;
+
+    /**
+     * 0 - not accepted, 1 - accepted implicitly via wrap()/unwrap(), 2 -
+     * accepted explicitly via beginHandshake() call
+     */
+    private int accepted;
+    private boolean handshakeFinished;
+    private boolean receivedShutdown;
+    @SuppressWarnings("UnusedDeclaration")
+    private volatile int destroyed;
+
+    // Use an invalid cipherSuite until the handshake is completed
+    // See http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html#getSession()
+    private volatile String cipher;
+    private volatile String applicationProtocol;
+
+    // We store this outside of the SslSession so we not need to create an instance during verifyCertificates(...)
+    private volatile Certificate[] peerCerts;
+    private volatile ClientAuthMode clientAuth = ClientAuthMode.NONE;
+
+    // SSL Engine status variables
+    private boolean isInboundDone;
+    private boolean isOutboundDone;
+    private boolean engineClosed;
+
+    private final boolean clientMode;
+    private final String fallbackApplicationProtocol;
+    private final OpenSSLSessionContext sessionContext;
+
+    @SuppressWarnings("unused")
+    private volatile SSLSession session;
+
+    /**
+     * Creates a new instance
+     *
+     * @param sslCtx an OpenSSL {@code SSL_CTX} object
+     * @param alloc the {@link ByteBufAllocator} that will be used by this
+     * engine
+     * @param clientMode {@code true} if this is used for clients, {@code false}
+     * otherwise
+     * @param sessionContext the {@link OpenSslSessionContext} this
+     * {@link SSLEngine} belongs to.
+     */
+    OpenSSLEngine(long sslCtx, String fallbackApplicationProtocol,
+            boolean clientMode, OpenSSLSessionContext sessionContext) {
+        if (sslCtx == 0) {
+            throw new IllegalArgumentException(sm.getString("engine.noSSLContext"));
+        }
+        ssl = SSL.newSSL(sslCtx, !clientMode);
+        networkBIO = SSL.makeNetworkBIO(ssl);
+        this.fallbackApplicationProtocol = fallbackApplicationProtocol;
+        this.clientMode = clientMode;
+        this.sessionContext = sessionContext;
+    }
+
+    /**
+     * Destroys this engine.
+     */
+    public synchronized void shutdown() {
+        if (DESTROYED_UPDATER.compareAndSet(this, 0, 1)) {
+            SSL.freeSSL(ssl);
+            SSL.freeBIO(networkBIO);
+            ssl = networkBIO = 0;
+
+            // internal errors can cause shutdown without marking the engine closed
+            isInboundDone = isOutboundDone = engineClosed = true;
+        }
+    }
+
+    /**
+     * Write plaintext data to the OpenSSL internal BIO
+     *
+     * Calling this function with src.remaining == 0 is undefined.
+     */
+    private int writePlaintextData(final ByteBuffer src) {
+        final int pos = src.position();
+        final int limit = src.limit();
+        final int len = Math.min(limit - pos, MAX_PLAINTEXT_LENGTH);
+        final int sslWrote;
+
+        if (src.isDirect()) {
+            final long addr = Buffer.address(src) + pos;
+            sslWrote = SSL.writeToSSL(ssl, addr, len);
+            if (sslWrote > 0) {
+                src.position(pos + sslWrote);
+                return sslWrote;
+            }
+        } else {
+            ByteBuffer buf = ByteBuffer.allocateDirect(len);
+            try {
+                final long addr = memoryAddress(buf);
+
+                src.limit(pos + len);
+
+                buf.put(src);
+                src.limit(limit);
+
+                sslWrote = SSL.writeToSSL(ssl, addr, len);
+                if (sslWrote > 0) {
+                    src.position(pos + sslWrote);
+                    return sslWrote;
+                } else {
+                    src.position(pos);
+                }
+            } finally {
+                buf.clear();
+                ByteBufferUtils.cleanDirectBuffer(buf);
+            }
+        }
+
+        throw new IllegalStateException(sm.getString("engine.writeToSSLFailed", sslWrote));
+    }
+
+    /**
+     * Write encrypted data to the OpenSSL network BIO.
+     */
+    private int writeEncryptedData(final ByteBuffer src) {
+        final int pos = src.position();
+        final int len = src.remaining();
+        if (src.isDirect()) {
+            final long addr = Buffer.address(src) + pos;
+            final int netWrote = SSL.writeToBIO(networkBIO, addr, len);
+            if (netWrote >= 0) {
+                src.position(pos + netWrote);
+                return netWrote;
+            }
+        } else {
+            ByteBuffer buf = ByteBuffer.allocateDirect(len);
+            try {
+                final long addr = memoryAddress(buf);
+
+                buf.put(src);
+
+                final int netWrote = SSL.writeToBIO(networkBIO, addr, len);
+                if (netWrote >= 0) {
+                    src.position(pos + netWrote);
+                    return netWrote;
+                } else {
+                    src.position(pos);
+                }
+            } finally {
+                buf.clear();
+                ByteBufferUtils.cleanDirectBuffer(buf);
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Read plaintext data from the OpenSSL internal BIO
+     */
+    private int readPlaintextData(final ByteBuffer dst) {
+        if (dst.isDirect()) {
+            final int pos = dst.position();
+            final long addr = Buffer.address(dst) + pos;
+            final int len = dst.limit() - pos;
+            final int sslRead = SSL.readFromSSL(ssl, addr, len);
+            if (sslRead > 0) {
+                dst.position(pos + sslRead);
+                return sslRead;
+            }
+        } else {
+            final int pos = dst.position();
+            final int limit = dst.limit();
+            final int len = Math.min(MAX_ENCRYPTED_PACKET_LENGTH, limit - pos);
+            final ByteBuffer buf = ByteBuffer.allocateDirect(len);
+            try {
+                final long addr = memoryAddress(buf);
+
+                final int sslRead = SSL.readFromSSL(ssl, addr, len);
+                if (sslRead > 0) {
+                    buf.limit(sslRead);
+                    dst.limit(pos + sslRead);
+                    dst.put(buf);
+                    dst.limit(limit);
+                    return sslRead;
+                }
+            } finally {
+                buf.clear();
+                ByteBufferUtils.cleanDirectBuffer(buf);
+            }
+        }
+
+        return 0;
+    }
+
+    /**
+     * Read encrypted data from the OpenSSL network BIO
+     */
+    private int readEncryptedData(final ByteBuffer dst, final int pending) {
+        if (dst.isDirect() && dst.remaining() >= pending) {
+            final int pos = dst.position();
+            final long addr = Buffer.address(dst) + pos;
+            final int bioRead = SSL.readFromBIO(networkBIO, addr, pending);
+            if (bioRead > 0) {
+                dst.position(pos + bioRead);
+                return bioRead;
+            }
+        } else {
+            final ByteBuffer buf = ByteBuffer.allocateDirect(pending);
+            try {
+                final long addr = memoryAddress(buf);
+
+                final int bioRead = SSL.readFromBIO(networkBIO, addr, pending);
+                if (bioRead > 0) {
+                    buf.limit(bioRead);
+                    int oldLimit = dst.limit();
+                    dst.limit(dst.position() + bioRead);
+                    dst.put(buf);
+                    dst.limit(oldLimit);
+                    return bioRead;
+                }
+            } finally {
+                buf.clear();
+                ByteBufferUtils.cleanDirectBuffer(buf);
+            }
+        }
+
+        return 0;
+    }
+
+    @Override
+    public synchronized SSLEngineResult wrap(final ByteBuffer[] srcs, final int offset, final int length, final ByteBuffer dst) throws SSLException {
+
+        // Check to make sure the engine has not been closed
+        if (destroyed != 0) {
+            return new SSLEngineResult(SSLEngineResult.Status.CLOSED, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, 0);
+        }
+
+        // Throw required runtime exceptions
+        if (srcs == null) {
+            throw new IllegalArgumentException(sm.getString("engine.nullBuffer"));
+        }
+        if (dst == null) {
+            throw new IllegalArgumentException(sm.getString("engine.nullBuffer"));
+        }
+
+        if (offset >= srcs.length || offset + length > srcs.length) {
+            throw new IndexOutOfBoundsException(sm.getString("engine.invalidBufferArray", offset, length, srcs.length));
+        }
+
+        if (dst.isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
+
+        // Prepare OpenSSL to work in server mode and receive handshake
+        if (accepted == 0) {
+            beginHandshakeImplicitly();
+        }
+
+        // In handshake or close_notify stages, check if call to wrap was made
+        // without regard to the handshake status.
+        SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
+
+        if ((!handshakeFinished || engineClosed) && handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
+            return new SSLEngineResult(getEngineStatus(), SSLEngineResult.HandshakeStatus.NEED_UNWRAP, 0, 0);
+        }
+
+        int bytesProduced = 0;
+        int pendingNet;
+
+        // Check for pending data in the network BIO
+        pendingNet = SSL.pendingWrittenBytesInBIO(networkBIO);
+        if (pendingNet > 0) {
+            // Do we have enough room in dst to write encrypted data?
+            int capacity = dst.remaining();
+            if (capacity < pendingNet) {
+                return new SSLEngineResult(SSLEngineResult.Status.BUFFER_OVERFLOW, handshakeStatus, 0, bytesProduced);
+            }
+
+            // Write the pending data from the network BIO into the dst buffer
+            try {
+                bytesProduced += readEncryptedData(dst, pendingNet);
+            } catch (Exception e) {
+                throw new SSLException(e);
+            }
+
+            // If isOuboundDone is set, then the data from the network BIO
+            // was the close_notify message -- we are not required to wait
+            // for the receipt the peer's close_notify message -- shutdown.
+            if (isOutboundDone) {
+                shutdown();
+            }
+
+            return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), 0, bytesProduced);
+        }
+
+        // There was no pending data in the network BIO -- encrypt any application data
+        int bytesConsumed = 0;
+        int endOffset = offset + length;
+        for (int i = offset; i < endOffset; ++i) {
+            final ByteBuffer src = srcs[i];
+            if (src == null) {
+                throw new IllegalArgumentException(sm.getString("engine.nullBufferInArray"));
+            }
+            while (src.hasRemaining()) {
+
+                // Write plaintext application data to the SSL engine
+                try {
+                    bytesConsumed += writePlaintextData(src);
+                } catch (Exception e) {
+                    throw new SSLException(e);
+                }
+
+                // Check to see if the engine wrote data into the network BIO
+                pendingNet = SSL.pendingWrittenBytesInBIO(networkBIO);
+                if (pendingNet > 0) {
+                    // Do we have enough room in dst to write encrypted data?
+                    int capacity = dst.remaining();
+                    if (capacity < pendingNet) {
+                        return new SSLEngineResult(
+                                SSLEngineResult.Status.BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced);
+                    }
+
+                    // Write the pending data from the network BIO into the dst buffer
+                    try {
+                        bytesProduced += readEncryptedData(dst, pendingNet);
+                    } catch (Exception e) {
+                        throw new SSLException(e);
+                    }
+
+                    return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
+                }
+            }
+        }
+        return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
+    }
+
+    @Override
+    public synchronized SSLEngineResult unwrap(final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException {
+        // Check to make sure the engine has not been closed
+        if (destroyed != 0) {
+            return new SSLEngineResult(SSLEngineResult.Status.CLOSED, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, 0);
+        }
+
+        // Throw requried runtime exceptions
+        if (src == null) {
+            throw new IllegalArgumentException(sm.getString("engine.nullBuffer"));
+        }
+        if (dsts == null) {
+            throw new IllegalArgumentException(sm.getString("engine.nullBuffer"));
+        }
+        if (offset >= dsts.length || offset + length > dsts.length) {
+            throw new IndexOutOfBoundsException(sm.getString("engine.invalidBufferArray", offset, length, dsts.length));
+        }
+
+        int capacity = 0;
+        final int endOffset = offset + length;
+        for (int i = offset; i < endOffset; i++) {
+            ByteBuffer dst = dsts[i];
+            if (dst == null) {
+                throw new IllegalArgumentException(sm.getString("engine.nullBufferInArray"));
+            }
+            if (dst.isReadOnly()) {
+                throw new ReadOnlyBufferException();
+            }
+            capacity += dst.remaining();
+        }
+
+        // Prepare OpenSSL to work in server mode and receive handshake
+        if (accepted == 0) {
+            beginHandshakeImplicitly();
+        }
+
+        // In handshake or close_notify stages, check if call to unwrap was made
+        // without regard to the handshake status.
+        SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
+        if ((!handshakeFinished || engineClosed) && handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
+            return new SSLEngineResult(getEngineStatus(), SSLEngineResult.HandshakeStatus.NEED_WRAP, 0, 0);
+        }
+
+        int len = src.remaining();
+
+        // protect against protocol overflow attack vector
+        if (len > MAX_ENCRYPTED_PACKET_LENGTH) {
+            isInboundDone = true;
+            isOutboundDone = true;
+            engineClosed = true;
+            shutdown();
+            throw ENCRYPTED_PACKET_OVERSIZED;
+        }
+
+        // Write encrypted data to network BIO
+        int bytesConsumed = -1;
+        try {
+            int written = writeEncryptedData(src);
+            if (written >= 0) {
+                if (bytesConsumed == -1) {
+                    bytesConsumed = written;
+                } else {
+                    bytesConsumed += written;
+                }
+            }
+        } catch (Exception e) {
+            throw new SSLException(e);
+        }
+        if (bytesConsumed >= 0) {
+            int lastPrimingReadResult = SSL.readFromSSL(ssl, EMPTY_ADDR, 0); // priming read
+            // check if SSL_read returned <= 0. In this case we need to check the error and see if it was something
+            // fatal.
+            if (lastPrimingReadResult <= 0) {
+                // Check for OpenSSL errors caused by the priming read
+                long error = SSL.getLastErrorNumber();
+                if (error != SSL.SSL_ERROR_NONE) {
+                    String err = SSL.getErrorString(error);
+                    if (logger.isDebugEnabled()) {
+                        logger.debug(sm.getString("engine.readFromSSLFailed", error, lastPrimingReadResult, err));
+                    }
+                    // There was an internal error -- shutdown
+                    shutdown();
+                    throw new SSLException(err);
+                }
+            }
+        } else {
+            // Reset to 0 as -1 is used to signal that nothing was written and no priming read needs to be done
+            bytesConsumed = 0;
+        }
+
+        // There won't be any application data until we're done handshaking
+        //
+        // We first check handshakeFinished to eliminate the overhead of extra JNI call if possible.
+        int pendingApp = (handshakeFinished || SSL.isInInit(ssl) == 0) ? SSL.pendingReadableBytesInSSL(ssl) : 0;
+        int bytesProduced = 0;
+
+        if (pendingApp > 0) {
+            // Do we have enough room in dsts to write decrypted data?
+            if (capacity < pendingApp) {
+                return new SSLEngineResult(SSLEngineResult.Status.BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, 0);
+            }
+
+            // Write decrypted data to dsts buffers
+            int idx = offset;
+            while (idx < endOffset) {
+                ByteBuffer dst = dsts[idx];
+                if (!dst.hasRemaining()) {
+                    idx++;
+                    continue;
+                }
+
+                if (pendingApp <= 0) {
+                    break;
+                }
+
+                int bytesRead;
+                try {
+                    bytesRead = readPlaintextData(dst);
+                } catch (Exception e) {
+                    throw new SSLException(e);
+                }
+
+                if (bytesRead == 0) {
+                    break;
+                }
+
+                bytesProduced += bytesRead;
+                pendingApp -= bytesRead;
+
+                if (!dst.hasRemaining()) {
+                    idx++;
+                }
+            }
+        }
+
+        // Check to see if we received a close_notify message from the peer
+        if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) {
+            receivedShutdown = true;
+            closeOutbound();
+            closeInbound();
+        }
+        if (bytesProduced == 0 && bytesConsumed == 0) {
+            return new SSLEngineResult(SSLEngineResult.Status.BUFFER_UNDERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced);
+        } else {
+            return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
+        }
+    }
+
+    @Override
+    public Runnable getDelegatedTask() {
+        // Currently, we do not delegate SSL computation tasks
+        // TODO: in the future, possibly create tasks to do encrypt / decrypt async
+        return null;
+    }
+
+    @Override
+    public synchronized void closeInbound() throws SSLException {
+        if (isInboundDone) {
+            return;
+        }
+
+        isInboundDone = true;
+        engineClosed = true;
+
+        shutdown();
+
+        if (accepted != 0 && !receivedShutdown) {
+            throw new SSLException(sm.getString("engine.inboundClose"));
+        }
+    }
+
+    @Override
+    public synchronized boolean isInboundDone() {
+        return isInboundDone || engineClosed;
+    }
+
+    @Override
+    public synchronized void closeOutbound() {
+        if (isOutboundDone) {
+            return;
+        }
+
+        isOutboundDone = true;
+        engineClosed = true;
+
+        if (accepted != 0 && destroyed == 0) {
+            int mode = SSL.getShutdown(ssl);
+            if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) {
+                SSL.shutdownSSL(ssl);
+            }
+        } else {
+            // engine closing before initial handshake
+            shutdown();
+        }
+    }
+
+    @Override
+    public synchronized boolean isOutboundDone() {
+        return isOutboundDone;
+    }
+
+    @Override
+    public String[] getSupportedCipherSuites() {
+        Set<String> availableCipherSuites = AVAILABLE_CIPHER_SUITES;
+        return availableCipherSuites.toArray(new String[availableCipherSuites.size()]);
+    }
+
+    @Override
+    public String[] getEnabledCipherSuites() {
+        String[] enabled = SSL.getCiphers(ssl);
+        if (enabled == null) {
+            return new String[0];
+        } else {
+            for (int i = 0; i < enabled.length; i++) {
+                String mapped = toJavaCipherSuite(enabled[i]);
+                if (mapped != null) {
+                    enabled[i] = mapped;
+                }
+            }
+            return enabled;
+        }
+    }
+
+    @Override
+    public void setEnabledCipherSuites(String[] cipherSuites) {
+        if (cipherSuites == null) {
+            throw new IllegalArgumentException(sm.getString("engine.nullCipherSuite"));
+        }
+        final StringBuilder buf = new StringBuilder();
+        for (String cipherSuite : cipherSuites) {
+            if (cipherSuite == null) {
+                break;
+            }
+            String converted = CipherSuiteConverter.toOpenSsl(cipherSuite);
+            if (converted != null) {
+                cipherSuite = converted;
+            }
+            if (!AVAILABLE_CIPHER_SUITES.contains(cipherSuite)) {
+                logger.debug(sm.getString("engine.unsupportedCipher", cipherSuite, converted));
+            }
+
+            buf.append(cipherSuite);
+            buf.append(':');
+        }
+
+        if (buf.length() == 0) {
+            throw new IllegalArgumentException(sm.getString("engine.emptyCipherSuite"));
+        }
+        buf.setLength(buf.length() - 1);
+
+        final String cipherSuiteSpec = buf.toString();
+        try {
+            SSL.setCipherSuites(ssl, cipherSuiteSpec);
+        } catch (Exception e) {
+            throw new IllegalStateException(sm.getString("engine.failedCipherSuite", cipherSuiteSpec), e);
+        }
+    }
+
+    @Override
+    public String[] getSupportedProtocols() {
+        return SUPPORTED_PROTOCOLS.clone();
+    }
+
+    @Override
+    public String[] getEnabledProtocols() {
+        List<String> enabled = new ArrayList<String>();
+        // Seems like there is no way to explict disable SSLv2Hello in openssl so it is always enabled
+        enabled.add(Constants.SSL_PROTO_SSLv2Hello);
+        int opts = SSL.getOptions(ssl);
+        if ((opts & SSL.SSL_OP_NO_TLSv1) == 0) {
+            enabled.add(Constants.SSL_PROTO_TLSv1);
+        }
+        if ((opts & SSL.SSL_OP_NO_TLSv1_1) == 0) {
+            enabled.add(Constants.SSL_PROTO_TLSv1_1);
+        }
+        if ((opts & SSL.SSL_OP_NO_TLSv1_2) == 0) {
+            enabled.add(Constants.SSL_PROTO_TLSv1_2);
+        }
+        if ((opts & SSL.SSL_OP_NO_SSLv2) == 0) {
+            enabled.add(Constants.SSL_PROTO_SSLv2);
+        }
+        if ((opts & SSL.SSL_OP_NO_SSLv3) == 0) {
+            enabled.add(Constants.SSL_PROTO_SSLv3);
+        }
+        int size = enabled.size();
+        if (size == 0) {
+            return new String[0];
+        } else {
+            return enabled.toArray(new String[size]);
+        }
+    }
+
+    @Override
+    public void setEnabledProtocols(String[] protocols) {
+        if (protocols == null) {
+            // This is correct from the API docs
+            throw new IllegalArgumentException();
+        }
+        boolean sslv2 = false;
+        boolean sslv3 = false;
+        boolean tlsv1 = false;
+        boolean tlsv1_1 = false;
+        boolean tlsv1_2 = false;
+        for (String p : protocols) {
+            if (!SUPPORTED_PROTOCOLS_SET.contains(p)) {
+                throw new IllegalArgumentException(sm.getString("engine.unsupportedProtocol", p));
+            }
+            if (p.equals(Constants.SSL_PROTO_SSLv2)) {
+                sslv2 = true;
+            } else if (p.equals(Constants.SSL_PROTO_SSLv3)) {
+                sslv3 = true;
+            } else if (p.equals(Constants.SSL_PROTO_TLSv1)) {
+                tlsv1 = true;
+            } else if (p.equals(Constants.SSL_PROTO_TLSv1_1)) {
+                tlsv1_1 = true;
+            } else if (p.equals(Constants.SSL_PROTO_TLSv1_2)) {
+                tlsv1_2 = true;
+            }
+        }
+        // Enable all and then disable what we not want
+        SSL.setOptions(ssl, SSL.SSL_OP_ALL);
+
+        if (!sslv2) {
+            SSL.setOptions(ssl, SSL.SSL_OP_NO_SSLv2);
+        }
+        if (!sslv3) {
+            SSL.setOptions(ssl, SSL.SSL_OP_NO_SSLv3);
+        }
+        if (!tlsv1) {
+            SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1);
+        }
+        if (!tlsv1_1) {
+            SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1_1);
+        }
+        if (!tlsv1_2) {
+            SSL.setOptions(ssl, SSL.SSL_OP_NO_TLSv1_2);
+        }
+    }
+
+    private Certificate[] initPeerCertChain() throws SSLPeerUnverifiedException {
+        byte[][] chain = SSL.getPeerCertChain(ssl);
+        byte[] clientCert;
+        if (!clientMode) {
+            // if used on the server side SSL_get_peer_cert_chain(...) will not include the remote peer certificate.
+            // We use SSL_get_peer_certificate to get it in this case and add it to our array later.
+            //
+            // See https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html
+            clientCert = SSL.getPeerCertificate(ssl);
+        } else {
+            clientCert = null;
+        }
+
+        if (chain == null && clientCert == null) {
+            throw new SSLPeerUnverifiedException(sm.getString("engine.unverifiedPeer"));
+        }
+        int len = 0;
+        if (chain != null) {
+            len += chain.length;
+        }
+
+        int i = 0;
+        Certificate[] peerCerts;
+        if (clientCert != null) {
+            len++;
+            peerCerts = new Certificate[len];
+            peerCerts[i++] = new OpenSslX509Certificate(clientCert);
+        } else {
+            peerCerts = new Certificate[len];
+        }
+        if (chain != null) {
+            int a = 0;
+            for (; i < peerCerts.length; i++) {
+                peerCerts[i] = new OpenSslX509Certificate(chain[a++]);
+            }
+        }
+        return peerCerts;
+    }
+
+    @Override
+    public SSLSession getSession() {
+        // A other methods on SSLEngine are thread-safe we also need to make this thread-safe...
+        SSLSession session = this.session;
+        if (session == null) {
+            session = new SSLSession() {
+                // SSLSession implementation seems to not need to be thread-safe so no need for volatile etc.
+                private X509Certificate[] x509PeerCerts;
+
+                // lazy init for memory reasons
+                private Map<String, Object> values;
+
+                @Override
+                public byte[] getId() {
+                    // We don't cache that to keep memory usage to a minimum.
+                    byte[] id = SSL.getSessionId(ssl);
+                    if (id == null) {
+                        // The id should never be null, if it was null then the SESSION itself was not valid.
+                        throw new IllegalStateException(sm.getString("engine.noSession"));
+                    }
+                    return id;
+                }
+
+                @Override
+                public SSLSessionContext getSessionContext() {
+                    return sessionContext;
+                }
+
+                @Override
+                public long getCreationTime() {
+                    // We need ot multiple by 1000 as openssl uses seconds and we need milli-seconds.
+                    return SSL.getTime(ssl) * 1000L;
+                }
+
+                @Override
+                public long getLastAccessedTime() {
+                    // TODO: Add proper implementation
+                    return getCreationTime();
+                }
+
+                @Override
+                public void invalidate() {
+                    // NOOP
+                }
+
+                @Override
+                public boolean isValid() {
+                    return false;
+                }
+
+                @Override
+                public void putValue(String name, Object value) {
+                    if (name == null) {
+                        throw new IllegalArgumentException(sm.getString("engine.nullName"));
+                    }
+                    if (value == null) {
+                        throw new IllegalArgumentException(sm.getString("engine.nullValue"));
+                    }
+                    Map<String, Object> values = this.values;
+                    if (values == null) {
+                        // Use size of 2 to keep the memory overhead small
+                        values = this.values = new HashMap<String, Object>(2);
+                    }
+                    Object old = values.put(name, value);
+                    if (value instanceof SSLSessionBindingListener) {
+                        ((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name));
+                    }
+                    notifyUnbound(old, name);
+                }
+
+                @Override
+                public Object getValue(String name) {
+                    if (name == null) {
+                        throw new IllegalArgumentException(sm.getString("engine.nullName"));
+                    }
+                    if (values == null) {
+                        return null;
+                    }
+                    return values.get(name);
+                }
+
+                @Override
+                public void removeValue(String name) {
+                    if (name == null) {
+                        throw new IllegalArgumentException(sm.getString("engine.nullName"));
+                    }
+                    Map<String, Object> values = this.values;
+                    if (values == null) {
+                        return;
+                    }
+                    Object old = values.remove(name);
+                    notifyUnbound(old, name);
+                }
+
+                @Override
+                public String[] getValueNames() {
+                    Map<String, Object> values = this.values;
+                    if (values == null || values.isEmpty()) {
+                        return new String[0];
+                    }
+                    return values.keySet().toArray(new String[values.size()]);
+                }
+
+                private void notifyUnbound(Object value, String name) {
+                    if (value instanceof SSLSessionBindingListener) {
+                        ((SSLSessionBindingListener) value).valueUnbound(new SSLSessionBindingEvent(this, name));
+                    }
+                }
+
+                @Override
+                public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+                    // these are lazy created to reduce memory overhead
+                    Certificate[] c = peerCerts;
+                    if (c == null) {
+                        if (SSL.isInInit(ssl) != 0) {
+                            throw new SSLPeerUnverifiedException(sm.getString("engine.unverifiedPeer"));
+                        }
+                        c = peerCerts = initPeerCertChain();
+                    }
+                    return c;
+                }
+
+                @Override
+                public Certificate[] getLocalCertificates() {
+                    // TODO: Find out how to get these
+                    return EMPTY_CERTIFICATES;
+                }
+
+                @Override
+                public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
+                    // these are lazy created to reduce memory overhead
+                    X509Certificate[] c = x509PeerCerts;
+                    if (c == null) {
+                        if (SSL.isInInit(ssl) != 0) {
+                            throw new SSLPeerUnverifiedException(sm.getString("engine.unverifiedPeer"));
+                        }
+                        byte[][] chain = SSL.getPeerCertChain(ssl);
+                        if (chain == null) {
+                            throw new SSLPeerUnverifiedException(sm.getString("engine.unverifiedPeer"));
+                        }
+                        X509Certificate[] peerCerts = new X509Certificate[chain.length];
+                        for (int i = 0; i < peerCerts.length; i++) {
+                            try {
+                                peerCerts[i] = X509Certificate.getInstance(chain[i]);
+                            } catch (CertificateException e) {
+                                throw new IllegalStateException(e);
+                            }
+                        }
+                        c = x509PeerCerts = peerCerts;
+                    }
+                    return c;
+                }
+
+                @Override
+                public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+                    Certificate[] peer = getPeerCertificates();
+                    if (peer == null || peer.length == 0) {
+                        return null;
+                    }
+                    return principal(peer);
+                }
+
+                @Override
+                public Principal getLocalPrincipal() {
+                    Certificate[] local = getLocalCertificates();
+                    if (local == null || local.length == 0) {
+                        return null;
+                    }
+                    return principal(local);
+                }
+
+                private Principal principal(Certificate[] certs) {
+                    return ((java.security.cert.X509Certificate) certs[0]).getIssuerX500Principal();
+                }
+
+                @Override
+                public String getCipherSuite() {
+                    if (!handshakeFinished) {
+                        return INVALID_CIPHER;
+                    }
+                    if (cipher == null) {
+                        String c = toJavaCipherSuite(SSL.getCipherForSSL(ssl));
+                        if (c != null) {
+                            cipher = c;
+                        }
+                    }
+                    return cipher;
+                }
+
+                @Override
+                public String getProtocol() {
+                    String applicationProtocol = OpenSSLEngine.this.applicationProtocol;
+                    if (applicationProtocol == null) {
+                        applicationProtocol = SSL.getNextProtoNegotiated(ssl);
+                        if (applicationProtocol == null) {
+                            applicationProtocol = fallbackApplicationProtocol;
+                        }
+                        if (applicationProtocol != null) {
+                            OpenSSLEngine.this.applicationProtocol = applicationProtocol.replace(':', '_');
+                        } else {
+                            OpenSSLEngine.this.applicationProtocol = applicationProtocol = "";
+                        }
+                    }
+                    String version = SSL.getVersion(ssl);
+                    if (applicationProtocol.isEmpty()) {
+                        return version;
+                    } else {
+                        return version + ':' + applicationProtocol;
+                    }
+                }
+
+                @Override
+                public String getPeerHost() {
+                    return null;
+                }
+
+                @Override
+                public int getPeerPort() {
+                    return 0;
+                }
+
+                @Override
+                public int getPacketBufferSize() {
+                    return MAX_ENCRYPTED_PACKET_LENGTH;
+                }
+
+                @Override
+                public int getApplicationBufferSize() {
+                    return MAX_PLAINTEXT_LENGTH;
+                }
+            };
+
+            if (!SESSION_UPDATER.compareAndSet(this, null, session)) {
+                // Was lazy created in the meantime so get the current reference.
+                session = this.session;
+            }
+        }
+
+        return session;
+    }
+
+    @Override
+    public synchronized void beginHandshake() throws SSLException {
+        if (engineClosed || destroyed != 0) {
+            throw ENGINE_CLOSED;
+        }
+        switch (accepted) {
+            case 0:
+                handshake();
+                accepted = 2;
+                break;
+            case 1:
+                // A user did not start handshake by calling this method by him/herself,
+                // but handshake has been started already by wrap() or unwrap() implicitly.
+                // Because it's the user's first time to call this method, it is unfair to
+                // raise an exception.  From the user's standpoint, he or she never asked
+                // for renegotiation.
+
+                accepted = 2; // Next time this method is invoked by the user, we should raise an exception.
+                break;
+            case 2:
+                throw RENEGOTIATION_UNSUPPORTED;
+            default:
+                throw new Error();
+        }
+    }
+
+    private void beginHandshakeImplicitly() throws SSLException {
+        if (engineClosed || destroyed != 0) {
+            throw ENGINE_CLOSED;
+        }
+
+        if (accepted == 0) {
+            handshake();
+            accepted = 1;
+        }
+    }
+
+    private void handshake() throws SSLException {
+        int code = SSL.doHandshake(ssl);
+        if (code <= 0) {
+            // Check for OpenSSL errors caused by the handshake
+            long error = SSL.getLastErrorNumber();
+            if (error != SSL.SSL_ERROR_NONE) {
+                String err = SSL.getErrorString(error);
+                if (logger.isDebugEnabled()) {
+                    logger.debug(sm.getString("engine.handshakeFailure", err));
+                }
+                // There was an internal error -- shutdown
+                shutdown();
+                throw new SSLException(err);
+            }
+        } else {
+            // if SSL_do_handshake returns > 0 it means the handshake was finished. This means we can update
+            // handshakeFinished directly and so eliminate uncessary calls to SSL.isInInit(...)
+            handshakeFinished = true;
+        }
+    }
+
+    private static long memoryAddress(ByteBuffer buf) {
+        return Buffer.address(buf);
+    }
+
+    private SSLEngineResult.Status getEngineStatus() {
+        return engineClosed ? SSLEngineResult.Status.CLOSED : SSLEngineResult.Status.OK;
+    }
+
+    @Override
+    public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
+        if (accepted == 0 || destroyed != 0) {
+            return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
+        }
+
+        // Check if we are in the initial handshake phase
+        if (!handshakeFinished) {
+            // There is pending data in the network BIO -- call wrap
+            if (SSL.pendingWrittenBytesInBIO(networkBIO) != 0) {
+                return SSLEngineResult.HandshakeStatus.NEED_WRAP;
+            }
+
+            // No pending data to be sent to the peer
+            // Check to see if we have finished handshaking
+            if (SSL.isInInit(ssl) == 0) {
+                handshakeFinished = true;
+                return SSLEngineResult.HandshakeStatus.FINISHED;
+            }
+
+            // No pending data and still handshaking
+            // Must be waiting on the peer to send more data
+            return SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
+        }
+
+        // Check if we are in the shutdown phase
+        if (engineClosed) {
+            // Waiting to send the close_notify message
+            if (SSL.pendingWrittenBytesInBIO(networkBIO) != 0) {
+                return SSLEngineResult.HandshakeStatus.NEED_WRAP;
+            }
+
+            // Must be waiting to receive the close_notify message
+            return SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
+        }
+
+        return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
+    }
+
+    /**
+     * Converts the specified OpenSSL cipher suite to the Java cipher suite.
+     */
+    private String toJavaCipherSuite(String openSslCipherSuite) {
+        if (openSslCipherSuite == null) {
+            return null;
+        }
+
+        String prefix = toJavaCipherSuitePrefix(SSL.getVersion(ssl));
+        return CipherSuiteConverter.toJava(openSslCipherSuite, prefix);
+    }
+
+    /**
+     * Converts the protocol version string returned by
+     * {@link SSL#getVersion(long)} to protocol family string.
+     */
+    private static String toJavaCipherSuitePrefix(String protocolVersion) {
+        final char c;
+        if (protocolVersion == null || protocolVersion.length() == 0) {
+            c = 0;
+        } else {
+            c = protocolVersion.charAt(0);
+        }
+
+        switch (c) {
+            case 'T':
+                return "TLS";
+            case 'S':
+                return "SSL";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    @Override
+    public void setUseClientMode(boolean clientMode) {
+        if (clientMode != this.clientMode) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    @Override
+    public boolean getUseClientMode() {
+        return clientMode;
+    }
+
+    @Override
+    public void setNeedClientAuth(boolean b) {
+        setClientAuth(b ? ClientAuthMode.REQUIRE : ClientAuthMode.NONE);
+    }
+
+    @Override
+    public boolean getNeedClientAuth() {
+        return clientAuth == ClientAuthMode.REQUIRE;
+    }
+
+    @Override
+    public void setWantClientAuth(boolean b) {
+        setClientAuth(b ? ClientAuthMode.OPTIONAL : ClientAuthMode.NONE);
+    }
+
+    @Override
+    public boolean getWantClientAuth() {
+        return clientAuth == ClientAuthMode.OPTIONAL;
+    }
+
+    private void setClientAuth(ClientAuthMode mode) {
+        if (clientMode) {
+            return;
+        }
+        synchronized (this) {
+            if (clientAuth == mode) {
+                // No need to issue any JNI calls if the mode is the same
+                return;
+            }
+            switch (mode) {
+                case NONE:
+                    SSL.setVerify(ssl, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
+                    break;
+                case REQUIRE:
+                    SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRE, VERIFY_DEPTH);
+                    break;
+                case OPTIONAL:
+                    SSL.setVerify(ssl, SSL.SSL_CVERIFY_OPTIONAL, VERIFY_DEPTH);
+                    break;
+            }
+            clientAuth = mode;
+        }
+    }
+
+    @Override
+    public void setEnableSessionCreation(boolean b) {
+        if (b) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    @Override
+    public boolean getEnableSessionCreation() {
+        return false;
+    }
+
+    @Override
+    @SuppressWarnings("FinalizeDeclaration")
+    protected void finalize() throws Throwable {
+        super.finalize();
+        // Call shutdown as the user may have created the OpenSslEngine and not used it at all.
+        shutdown();
+    }
+}

Added: tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLImplementation.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLImplementation.java?rev=1686279&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLImplementation.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLImplementation.java Thu Jun 18 17:13:40 2015
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net.openssl;
+
+import javax.net.ssl.SSLSession;
+
+import org.apache.tomcat.util.net.SSLHostConfig;
+import org.apache.tomcat.util.net.SSLImplementation;
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.SSLUtil;
+import org.apache.tomcat.util.net.jsse.JSSESupport;
+
+public class OpenSSLImplementation extends SSLImplementation {
+
+    public static final String IMPLEMENTATION_NAME = "org.apache.tomcat.util.net.openssl.OpenSSLImplementation";
+
+    @Override
+    public String getImplementationName() {
+        return "OpenSSl";
+    }
+
+    @Override
+    public SSLSupport getSSLSupport(SSLSession session) {
+        return new JSSESupport(session);
+    }
+
+    @Override
+    public SSLUtil getSSLUtil(SSLHostConfig sslHostConfig) {
+        return new OpenSSLUtil(sslHostConfig);
+    }
+
+}

Added: tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLKeyManager.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLKeyManager.java?rev=1686279&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLKeyManager.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLKeyManager.java Thu Jun 18 17:13:40 2015
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net.openssl;
+
+import java.io.File;
+
+import javax.net.ssl.KeyManager;
+
+import org.apache.tomcat.util.res.StringManager;
+
+public class OpenSSLKeyManager implements KeyManager{
+
+    private static final StringManager sm = StringManager.getManager(OpenSSLKeyManager.class);
+
+    private File certificateChain;
+    public File getCertificateChain() { return certificateChain; }
+    public void setCertificateChain(File certificateChain) { this.certificateChain = certificateChain; }
+
+    private File privateKey;
+    public File getPrivateKey() { return privateKey; }
+    public void setPrivateKey(File privateKey) { this.privateKey = privateKey; }
+
+    OpenSSLKeyManager(String certChainFile, String keyFile) {
+        if (certChainFile == null) {
+            throw new IllegalArgumentException(sm.getString("keyManager.nullCertificateChain"));
+        }
+        if (keyFile == null) {
+            throw new IllegalArgumentException(sm.getString("keyManager.nullPrivateKey"));
+        }
+        this.certificateChain = new File(certChainFile);
+        this.privateKey = new File(keyFile);
+    }
+
+}

Added: tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLProtocols.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLProtocols.java?rev=1686279&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLProtocols.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLProtocols.java Thu Jun 18 17:13:40 2015
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net.openssl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.tomcat.util.net.Constants;
+
+/**
+ * Get SSL protocols in the right preference order
+ */
+public class OpenSSLProtocols {
+
+    private List<String> openSSLProtocols = new ArrayList<>();
+
+    public OpenSSLProtocols(String preferredJSSEProto) {
+        Collections.addAll(openSSLProtocols, Constants.SSL_PROTO_TLSv1_2,
+                Constants.SSL_PROTO_TLSv1_1, Constants.SSL_PROTO_TLSv1,
+                Constants.SSL_PROTO_SSLv3, Constants.SSL_PROTO_SSLv2);
+        if(openSSLProtocols.contains(preferredJSSEProto)) {
+            openSSLProtocols.remove(preferredJSSEProto);
+            openSSLProtocols.add(0, preferredJSSEProto);
+        }
+    }
+
+    public String[] getProtocols() {
+        return openSSLProtocols.toArray(new String[openSSLProtocols.size()]);
+    }
+}

Added: tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLServerSessionContext.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLServerSessionContext.java?rev=1686279&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLServerSessionContext.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLServerSessionContext.java Thu Jun 18 17:13:40 2015
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net.openssl;
+
+import org.apache.tomcat.jni.SSL;
+import org.apache.tomcat.jni.SSLContext;
+
+
+/**
+ * {@link OpenSslSessionContext} implementation which offers extra methods which are only useful for the server-side.
+ */
+public final class OpenSSLServerSessionContext extends OpenSSLSessionContext {
+    OpenSSLServerSessionContext(long context) {
+        super(context);
+    }
+
+    @Override
+    public void setSessionTimeout(int seconds) {
+        if (seconds < 0) {
+            throw new IllegalArgumentException();
+        }
+        SSLContext.setSessionCacheTimeout(context, seconds);
+    }
+
+    @Override
+    public int getSessionTimeout() {
+        return (int) SSLContext.getSessionCacheTimeout(context);
+    }
+
+    @Override
+    public void setSessionCacheSize(int size) {
+        if (size < 0) {
+            throw new IllegalArgumentException();
+        }
+        SSLContext.setSessionCacheSize(context, size);
+    }
+
+    @Override
+    public int getSessionCacheSize() {
+        return (int) SSLContext.getSessionCacheSize(context);
+    }
+
+    @Override
+    public void setSessionCacheEnabled(boolean enabled) {
+        long mode = enabled ? SSL.SSL_SESS_CACHE_SERVER : SSL.SSL_SESS_CACHE_OFF;
+        SSLContext.setSessionCacheMode(context, mode);
+    }
+
+    @Override
+    public boolean isSessionCacheEnabled() {
+        return SSLContext.getSessionCacheMode(context) == SSL.SSL_SESS_CACHE_SERVER;
+    }
+
+    /**
+     * Set the context within which session be reused (server side only)
+     * See <a href="http://www.openssl.org/docs/ssl/SSL_CTX_set_session_id_context.html">
+     *     man SSL_CTX_set_session_id_context</a>
+     *
+     * @param sidCtx can be any kind of binary data, it is therefore possible to use e.g. the name
+     *               of the application and/or the hostname and/or service name
+     * @return {@code true} if success, {@code false} otherwise.
+     */
+    public boolean setSessionIdContext(byte[] sidCtx) {
+        return SSLContext.setSessionIdContext(context, sidCtx);
+    }
+}

Added: tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLSessionContext.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLSessionContext.java?rev=1686279&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLSessionContext.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLSessionContext.java Thu Jun 18 17:13:40 2015
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net.openssl;
+
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+
+import org.apache.tomcat.jni.SSLContext;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * OpenSSL specific {@link SSLSessionContext} implementation.
+ */
+public abstract class OpenSSLSessionContext implements SSLSessionContext {
+    private static final StringManager sm = StringManager.getManager(OpenSSLSessionContext.class);
+    private static final Enumeration<byte[]> EMPTY = new EmptyEnumeration();
+
+    private final OpenSSLSessionStats stats;
+    final long context;
+
+    OpenSSLSessionContext(long context) {
+        this.context = context;
+        stats = new OpenSSLSessionStats(context);
+    }
+
+    @Override
+    public SSLSession getSession(byte[] bytes) {
+        return null;
+    }
+
+    @Override
+    public Enumeration<byte[]> getIds() {
+        return EMPTY;
+    }
+
+    /**
+     * Sets the SSL session ticket keys of this context.
+     */
+    public void setTicketKeys(byte[] keys) {
+        if (keys == null) {
+            throw new IllegalArgumentException(sm.getString("sessionContext.nullTicketKeys"));
+        }
+        SSLContext.setSessionTicketKeys(context, keys);
+    }
+
+    /**
+     * Enable or disable caching of SSL sessions.
+     */
+    public abstract void setSessionCacheEnabled(boolean enabled);
+
+    /**
+     * Return {@code true} if caching of SSL sessions is enabled, {@code false} otherwise.
+     */
+    public abstract boolean isSessionCacheEnabled();
+
+    /**
+     * Returns the stats of this context.
+     */
+    public OpenSSLSessionStats stats() {
+        return stats;
+    }
+
+    private static final class EmptyEnumeration implements Enumeration<byte[]> {
+        @Override
+        public boolean hasMoreElements() {
+            return false;
+        }
+
+        @Override
+        public byte[] nextElement() {
+            throw new NoSuchElementException();
+        }
+    }
+}

Added: tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLSessionStats.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLSessionStats.java?rev=1686279&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLSessionStats.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLSessionStats.java Thu Jun 18 17:13:40 2015
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net.openssl;
+
+import org.apache.tomcat.jni.SSLContext;
+
+/**
+ * Stats exposed by an OpenSSL session context.
+ *
+ * @see <a href="https://www.openssl.org/docs/ssl/SSL_CTX_sess_number.html"><code>SSL_CTX_sess_number</code></a>
+ */
+public final class OpenSSLSessionStats {
+
+    private final long context;
+
+    OpenSSLSessionStats(long context) {
+        this.context = context;
+    }
+
+    /**
+     * Returns the current number of sessions in the internal session cache.
+     */
+    public long number() {
+        return SSLContext.sessionNumber(context);
+    }
+
+    /**
+     * Returns the number of started SSL/TLS handshakes in client mode.
+     */
+    public long connect() {
+        return SSLContext.sessionConnect(context);
+    }
+
+    /**
+     * Returns the number of successfully established SSL/TLS sessions in client mode.
+     */
+    public long connectGood() {
+        return SSLContext.sessionConnectGood(context);
+    }
+
+    /**
+     * Returns the number of start renegotiations in client mode.
+     */
+    public long connectRenegotiate() {
+        return SSLContext.sessionConnectRenegotiate(context);
+    }
+
+    /**
+     * Returns the number of started SSL/TLS handshakes in server mode.
+     */
+    public long accept() {
+        return SSLContext.sessionAccept(context);
+    }
+
+    /**
+     * Returns the number of successfully established SSL/TLS sessions in server mode.
+     */
+    public long acceptGood() {
+        return SSLContext.sessionAcceptGood(context);
+    }
+
+    /**
+     * Returns the number of start renegotiations in server mode.
+     */
+    public long acceptRenegotiate() {
+        return SSLContext.sessionAcceptRenegotiate(context);
+    }
+
+    /**
+     * Returns the number of successfully reused sessions. In client mode, a session set with {@code SSL_set_session}
+     * successfully reused is counted as a hit. In server mode, a session successfully retrieved from internal or
+     * external cache is counted as a hit.
+     */
+    public long hits() {
+        return SSLContext.sessionHits(context);
+    }
+
+    /**
+     * Returns the number of successfully retrieved sessions from the external session cache in server mode.
+     */
+    public long cbHits() {
+        return SSLContext.sessionCbHits(context);
+    }
+
+    /**
+     * Returns the number of sessions proposed by clients that were not found in the internal session cache
+     * in server mode.
+     */
+    public long misses() {
+        return SSLContext.sessionMisses(context);
+    }
+
+    /**
+     * Returns the number of sessions proposed by clients and either found in the internal or external session cache
+     * in server mode, but that were invalid due to timeout. These sessions are not included in the {@link #hits()}
+     * count.
+     */
+    public long timeouts() {
+        return SSLContext.sessionTimeouts(context);
+    }
+
+    /**
+     * Returns the number of sessions that were removed because the maximum session cache size was exceeded.
+     */
+    public long cacheFull() {
+        return SSLContext.sessionCacheFull(context);
+    }
+}

Added: tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLUtil.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLUtil.java?rev=1686279&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLUtil.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLUtil.java Thu Jun 18 17:13:40 2015
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net.openssl;
+
+import java.util.List;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.TrustManager;
+
+import org.apache.tomcat.util.net.SSLContext;
+import org.apache.tomcat.util.net.SSLHostConfig;
+import org.apache.tomcat.util.net.SSLUtil;
+
+public class OpenSSLUtil implements SSLUtil {
+
+    private final SSLHostConfig sslHostConfig;
+
+    private String[] enabledProtocols = null;
+    private String[] enabledCiphers = null;
+
+    public OpenSSLUtil(SSLHostConfig sslHostConfig) {
+        this.sslHostConfig = sslHostConfig;
+    }
+
+    @Override
+    public SSLContext createSSLContext() throws Exception {
+        return new OpenSSLContext(sslHostConfig);
+    }
+
+    @Override
+    public KeyManager[] getKeyManagers() throws Exception {
+        KeyManager[] managers = {
+                new OpenSSLKeyManager(SSLHostConfig.adjustRelativePath(sslHostConfig.getCertificateFile()),
+                        SSLHostConfig.adjustRelativePath(sslHostConfig.getCertificateKeyFile()))
+                };
+        return managers;
+    }
+
+    @Override
+    public TrustManager[] getTrustManagers() throws Exception {
+        return null;
+    }
+
+    @Override
+    public void configureSessionContext(SSLSessionContext sslSessionContext) {
+        // do nothing. configuration is done in the init phase
+    }
+
+    @Override
+    public String[] getEnableableCiphers(SSLContext context) {
+        if (enabledCiphers == null) {
+            List<String> enabledCiphersList = ((OpenSSLContext) context).getCiphers();
+            enabledCiphers = enabledCiphersList.toArray(new String[enabledCiphersList.size()]);
+        }
+        return enabledCiphers;
+    }
+
+    @Override
+    public String[] getEnableableProtocols(SSLContext context) {
+        if (enabledProtocols == null) {
+            enabledProtocols = new OpenSSLProtocols(((OpenSSLContext) context).getEnabledProtocol()).getProtocols();
+        }
+        return enabledProtocols;
+    }
+
+}

Added: tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLX509Certificate.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLX509Certificate.java?rev=1686279&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLX509Certificate.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/util/net/openssl/OpenSSLX509Certificate.java Thu Jun 18 17:13:40 2015
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net.openssl;
+
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Set;
+
+final class OpenSslX509Certificate extends X509Certificate {
+
+    private final byte[] bytes;
+    private X509Certificate wrapped;
+
+    public OpenSslX509Certificate(byte[] bytes) {
+        this.bytes = bytes;
+    }
+
+    @Override
+    public void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException {
+        unwrap().checkValidity();
+    }
+
+    @Override
+    public void checkValidity(Date date) throws CertificateExpiredException, CertificateNotYetValidException {
+        unwrap().checkValidity(date);
+    }
+
+    @Override
+    public int getVersion() {
+        return unwrap().getVersion();
+    }
+
+    @Override
+    public BigInteger getSerialNumber() {
+        return unwrap().getSerialNumber();
+    }
+
+    @Override
+    public Principal getIssuerDN() {
+        return unwrap().getIssuerDN();
+    }
+
+    @Override
+    public Principal getSubjectDN() {
+        return unwrap().getSubjectDN();
+    }
+
+    @Override
+    public Date getNotBefore() {
+        return unwrap().getNotBefore();
+    }
+
+    @Override
+    public Date getNotAfter() {
+        return unwrap().getNotAfter();
+    }
+
+    @Override
+    public byte[] getTBSCertificate() throws CertificateEncodingException {
+        return unwrap().getTBSCertificate();
+    }
+
+    @Override
+    public byte[] getSignature() {
+        return unwrap().getSignature();
+    }
+
+    @Override
+    public String getSigAlgName() {
+        return unwrap().getSigAlgName();
+    }
+
+    @Override
+    public String getSigAlgOID() {
+        return unwrap().getSigAlgOID();
+    }
+
+    @Override
+    public byte[] getSigAlgParams() {
+        return unwrap().getSigAlgParams();
+    }
+
+    @Override
+    public boolean[] getIssuerUniqueID() {
+        return unwrap().getIssuerUniqueID();
+    }
+
+    @Override
+    public boolean[] getSubjectUniqueID() {
+        return unwrap().getSubjectUniqueID();
+    }
+
+    @Override
+    public boolean[] getKeyUsage() {
+        return unwrap().getKeyUsage();
+    }
+
+    @Override
+    public int getBasicConstraints() {
+        return unwrap().getBasicConstraints();
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        return bytes.clone();
+    }
+
+    @Override
+    public void verify(PublicKey key)
+            throws CertificateException, NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException {
+        unwrap().verify(key);
+    }
+
+    @Override
+    public void verify(PublicKey key, String sigProvider)
+            throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+            NoSuchProviderException, SignatureException {
+        unwrap().verify(key, sigProvider);
+    }
+
+    @Override
+    public String toString() {
+        return unwrap().toString();
+    }
+
+    @Override
+    public PublicKey getPublicKey() {
+        return unwrap().getPublicKey();
+    }
+
+    @Override
+    public boolean hasUnsupportedCriticalExtension() {
+        return unwrap().hasUnsupportedCriticalExtension();
+    }
+
+    @Override
+    public Set<String> getCriticalExtensionOIDs() {
+        return unwrap().getCriticalExtensionOIDs();
+    }
+
+    @Override
+    public Set<String> getNonCriticalExtensionOIDs() {
+        return unwrap().getNonCriticalExtensionOIDs();
+    }
+
+    @Override
+    public byte[] getExtensionValue(String oid) {
+        return unwrap().getExtensionValue(oid);
+    }
+
+    private X509Certificate unwrap() {
+        X509Certificate wrapped = this.wrapped;
+        if (wrapped == null) {
+            try {
+                wrapped = this.wrapped = (X509Certificate) OpenSSLContext.X509_CERT_FACTORY.generateCertificate(
+                        new ByteArrayInputStream(bytes));
+            } catch (CertificateException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+        return wrapped;
+    }
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org