org.jboss.netty.handler.ssl.OpenSslEngine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hello-world-plugin Show documentation
Show all versions of hello-world-plugin Show documentation
Kill Bill Hello World plugin
The newest version!
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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.jboss.netty.handler.ssl;
import org.apache.tomcat.jni.Buffer;
import org.apache.tomcat.jni.SSL;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.util.internal.EmptyArrays;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.security.cert.X509Certificate;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.security.Principal;
import java.security.cert.Certificate;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
import static javax.net.ssl.SSLEngineResult.Status.*;
/**
* Implements a {@link SSLEngine} using
* OpenSSL BIO abstractions.
*/
public final class OpenSslEngine extends SSLEngine {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslEngine.class);
private static final Certificate[] EMPTY_CERTIFICATES = new Certificate[0];
private static final X509Certificate[] EMPTY_X509_CERTIFICATES = new X509Certificate[0];
private static final SSLException ENGINE_CLOSED = new SSLException("engine closed");
private static final SSLException RENEGOTIATION_UNSUPPORTED = new SSLException("renegotiation unsupported");
private static final SSLException ENCRYPTED_PACKET_OVERSIZED = new SSLException("encrypted packet oversized");
static {
ENGINE_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
RENEGOTIATION_UNSUPPORTED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
ENCRYPTED_PACKET_OVERSIZED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
}
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;
// 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;
private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed");
// 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;
private String cipher;
private volatile String applicationProtocol;
// SSL Engine status variables
private boolean isInboundDone;
private boolean isOutboundDone;
private boolean engineClosed;
private int lastPrimingReadResult;
private final SslBufferPool bufPool;
private final String fallbackApplicationProtocol;
private SSLSession session;
/**
* Creates a new instance
*
* @param sslCtx an OpenSSL {@code SSL_CTX} object
* @param bufPool the {@link SslBufferPool} that will be used by this engine
*/
public OpenSslEngine(long sslCtx, SslBufferPool bufPool, String fallbackApplicationProtocol) {
OpenSsl.ensureAvailability();
if (sslCtx == 0) {
throw new NullPointerException("sslContext");
}
if (bufPool == null) {
throw new NullPointerException("bufPool");
}
this.bufPool = bufPool;
ssl = SSL.newSSL(sslCtx, true);
networkBIO = SSL.makeNetworkBIO(ssl);
this.fallbackApplicationProtocol = fallbackApplicationProtocol;
}
/**
* 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 {
final ByteBuffer buf = bufPool.acquireBuffer();
try {
assert buf.isDirect();
assert len <= buf.capacity() : "buffer pool write overflow";
final long addr = Buffer.address(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 {
bufPool.releaseBuffer(buf);
}
}
throw new IllegalStateException("SSL.writeToSSL() returned a non-positive value: " + 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);
lastPrimingReadResult = SSL.readFromSSL(ssl, addr, 0); // priming read
return netWrote;
}
} else {
final ByteBuffer buf = bufPool.acquireBuffer();
try {
assert buf.isDirect();
assert len <= buf.capacity();
final long addr = Buffer.address(buf);
buf.put(src);
final int netWrote = SSL.writeToBIO(networkBIO, addr, len);
if (netWrote >= 0) {
src.position(pos + netWrote);
lastPrimingReadResult = SSL.readFromSSL(ssl, addr, 0); // priming read
return netWrote;
} else {
src.position(pos);
}
} finally {
bufPool.releaseBuffer(buf);
}
}
return 0;
}
/**
* 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 ByteBuffer buf = bufPool.acquireBuffer();
try {
assert buf.isDirect();
final long addr = Buffer.address(buf);
final int len = Math.min(buf.capacity(), dst.remaining());
buf.limit(len);
final int sslRead = SSL.readFromSSL(ssl, addr, len);
if (sslRead > 0) {
buf.limit(sslRead);
dst.put(buf);
return sslRead;
}
} finally {
bufPool.releaseBuffer(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 = bufPool.acquireBuffer();
try {
assert buf.isDirect();
final long addr = Buffer.address(buf);
assert buf.capacity() >= pending :
"network BIO read overflow (pending: " + pending + ", capacity: " + buf.capacity() + ')';
final int bioRead = SSL.readFromBIO(networkBIO, addr, pending);
if (bioRead > 0) {
buf.limit(bioRead);
dst.put(buf);
return bioRead;
}
} finally {
bufPool.releaseBuffer(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(CLOSED, NOT_HANDSHAKING, 0, 0);
}
// Throw required runtime exceptions
if (srcs == null) {
throw new NullPointerException("srcs");
}
if (dst == null) {
throw new NullPointerException("dst");
}
if (offset >= srcs.length || offset + length > srcs.length) {
throw new IndexOutOfBoundsException(
"offset: " + offset + ", length: " + length +
" (expected: offset <= offset + length <= srcs.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 == NEED_UNWRAP) {
return new SSLEngineResult(getEngineStatus(), 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(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;
for (int i = offset; i < length; ++ i) {
final ByteBuffer src = srcs[i];
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(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(CLOSED, NOT_HANDSHAKING, 0, 0);
}
// Throw requried runtime exceptions
if (src == null) {
throw new NullPointerException("src");
}
if (dsts == null) {
throw new NullPointerException("dsts");
}
if (offset >= dsts.length || offset + length > dsts.length) {
throw new IndexOutOfBoundsException(
"offset: " + offset + ", length: " + length +
" (expected: offset <= offset + length <= dsts.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();
}
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 == NEED_WRAP) {
return new SSLEngineResult(getEngineStatus(), NEED_WRAP, 0, 0);
}
// protect against protocol overflow attack vector
if (src.remaining() > MAX_ENCRYPTED_PACKET_LENGTH) {
isInboundDone = true;
isOutboundDone = true;
engineClosed = true;
shutdown();
throw ENCRYPTED_PACKET_OVERSIZED;
}
// Write encrypted data to network BIO
int bytesConsumed = 0;
lastPrimingReadResult = 0;
try {
bytesConsumed += writeEncryptedData(src);
} catch (Exception e) {
throw new SSLException(e);
}
// Check for OpenSSL errors caused by the priming read
String error = SSL.getLastError();
if (error != null && !error.startsWith(OpenSsl.IGNORABLE_ERROR_PREFIX)) {
if (logger.isInfoEnabled()) {
logger.info(
"SSL_read failed: primingReadResult: " + lastPrimingReadResult +
"; OpenSSL error: '" + error + '\'');
}
// There was an internal error -- shutdown
shutdown();
throw new SSLException(error);
}
// There won't be any application data until we're done handshaking
int pendingApp = SSL.isInInit(ssl) == 0 ? SSL.pendingReadableBytesInSSL(ssl) : 0;
// Do we have enough room in dsts to write decrypted data?
if (capacity < pendingApp) {
return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, 0);
}
// Write decrypted data to dsts buffers
int bytesProduced = 0;
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();
}
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;
if (accepted != 0) {
if (!receivedShutdown) {
shutdown();
throw new SSLException("close_notify has not been received");
}
} else {
// engine closing before initial handshake
shutdown();
}
}
@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() {
return EmptyArrays.EMPTY_STRINGS;
}
@Override
public String[] getEnabledCipherSuites() {
return EmptyArrays.EMPTY_STRINGS;
}
@Override
public void setEnabledCipherSuites(String[] strings) {
throw new UnsupportedOperationException();
}
@Override
public String[] getSupportedProtocols() {
return EmptyArrays.EMPTY_STRINGS;
}
@Override
public String[] getEnabledProtocols() {
return EmptyArrays.EMPTY_STRINGS;
}
@Override
public void setEnabledProtocols(String[] strings) {
throw new UnsupportedOperationException();
}
@Override
public SSLSession getSession() {
SSLSession session = this.session;
if (session == null) {
this.session = session = new SSLSession() {
public byte[] getId() {
return String.valueOf(ssl).getBytes();
}
public SSLSessionContext getSessionContext() {
return null;
}
public long getCreationTime() {
return 0;
}
public long getLastAccessedTime() {
return 0;
}
public void invalidate() {
}
public boolean isValid() {
return false;
}
public void putValue(String s, Object o) {
}
public Object getValue(String s) {
return null;
}
public void removeValue(String s) {
}
public String[] getValueNames() {
return EmptyArrays.EMPTY_STRINGS;
}
public Certificate[] getPeerCertificates() {
return EMPTY_CERTIFICATES;
}
public Certificate[] getLocalCertificates() {
return EMPTY_CERTIFICATES;
}
public X509Certificate[] getPeerCertificateChain() {
return EMPTY_X509_CERTIFICATES;
}
public Principal getPeerPrincipal() {
return null;
}
public Principal getLocalPrincipal() {
return null;
}
public String getCipherSuite() {
return cipher;
}
public String getProtocol() {
// TODO: Figure out how to get the current protocol.
String applicationProtocol = OpenSslEngine.this.applicationProtocol;
if (applicationProtocol == null) {
return "unknown";
} else {
return "unknown:" + applicationProtocol;
}
}
public String getPeerHost() {
return null;
}
public int getPeerPort() {
return 0;
}
public int getPacketBufferSize() {
return MAX_ENCRYPTED_PACKET_LENGTH;
}
public int getApplicationBufferSize() {
return MAX_PLAINTEXT_LENGTH;
}
};
}
return session;
}
@Override
public synchronized void beginHandshake() throws SSLException {
if (engineClosed) {
throw ENGINE_CLOSED;
}
switch (accepted) {
case 0:
SSL.doHandshake(ssl);
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 synchronized void beginHandshakeImplicitly() throws SSLException {
if (engineClosed) {
throw ENGINE_CLOSED;
}
if (accepted == 0) {
SSL.doHandshake(ssl);
accepted = 1;
}
}
private SSLEngineResult.Status getEngineStatus() {
return engineClosed? CLOSED : OK;
}
@Override
public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
if (accepted == 0 || destroyed != 0) {
return 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 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;
cipher = SSL.getCipherForSSL(ssl);
String applicationProtocol = SSL.getNextProtoNegotiated(ssl);
if (applicationProtocol == null) {
applicationProtocol = fallbackApplicationProtocol;
}
if (applicationProtocol != null) {
this.applicationProtocol = applicationProtocol.replace(':', '_');
} else {
this.applicationProtocol = null;
}
return FINISHED;
}
// No pending data and still handshaking
// Must be waiting on the peer to send more data
return 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 NEED_WRAP;
}
// Must be waiting to receive the close_notify message
return NEED_UNWRAP;
}
return NOT_HANDSHAKING;
}
@Override
public void setUseClientMode(boolean clientMode) {
if (clientMode) {
throw new UnsupportedOperationException();
}
}
@Override
public boolean getUseClientMode() {
return false;
}
@Override
public void setNeedClientAuth(boolean b) {
if (b) {
throw new UnsupportedOperationException();
}
}
@Override
public boolean getNeedClientAuth() {
return false;
}
@Override
public void setWantClientAuth(boolean b) {
if (b) {
throw new UnsupportedOperationException();
}
}
@Override
public boolean getWantClientAuth() {
return false;
}
@Override
public void setEnableSessionCreation(boolean b) {
if (b) {
throw new UnsupportedOperationException();
}
}
@Override
public boolean getEnableSessionCreation() {
return false;
}
}