opends/src/server/org/opends/server/api/ClientConnection.java
@@ -23,13 +23,14 @@ * * * Copyright 2006-2009 Sun Microsystems, Inc. * Portions copyright 2011 ForgeRock AS * Portions copyright 2011-2012 ForgeRock AS */ package org.opends.server.api; import java.net.InetAddress; import java.nio.channels.ByteChannel; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Collection; @@ -47,7 +48,6 @@ import org.opends.server.core.PluginConfigManager; import org.opends.server.core.SearchOperation; import org.opends.server.core.networkgroups.NetworkGroup; import org.opends.server.extensions.RedirectingByteChannel; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeType; @@ -1327,7 +1327,7 @@ * * @return The lowest level channel associated with a connection. */ public RedirectingByteChannel getChannel() { public ByteChannel getChannel() { // By default, return null, which indicates that there should // be no channel. Subclasses should override this if // they want to support a channel. @@ -1351,21 +1351,6 @@ /** * Return the largest application buffer size that should be used * for a connection. * * @return The application buffer size. */ public int getAppBufferSize() { // By default, return 0, which indicates that there should // be no application buffer size. Subclasses should override //this if they want to support a application buffer size. return 0; } /** * Retrieves the size limit that will be enforced for searches * performed using this client connection. * opends/src/server/org/opends/server/extensions/ConnectionSecurityProvider.java
@@ -42,15 +42,6 @@ { /** * Return a buffer size of the byte channel. * * @return Integer representing the byte channel application buffer size. */ int getAppBufSize(); /** * Return a certificate chain array. * * @return A certificate chain array. @@ -87,12 +78,9 @@ /** * Factory method: creates a new security ByteChannel layer wrapping the * provided ByteChannel. * Returns the security provider's byte channel. * * @param channel * The byte channel to be wrapped. * @return A byte channel wrapping the specified byte channel. * @return The security provider's byte channel. */ ByteChannel wrapChannel(ByteChannel channel); ByteChannel getChannel(); } opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java
@@ -23,6 +23,7 @@ * * * Copyright 2006-2009 Sun Microsystems, Inc. * Portions copyright 2012 ForgeRock AS. */ package org.opends.server.extensions; @@ -163,18 +164,7 @@ (SASLContext) clientConn.getSASLAuthStateInfo(); if(saslContext == null) { try { //If the connection is secure already (i.e., TLS), then make the //receive buffers sizes match. if(clientConn.isSecure()) { HashMap<String, String>secProps = new HashMap<String,String>(saslProps); int maxBuf = clientConn.getAppBufferSize(); secProps.put(Sasl.MAX_BUFFER, Integer.toString(maxBuf)); saslContext = SASLContext.createSASLContext(secProps, serverFQDN, SASL_MECHANISM_DIGEST_MD5, identityMapper); } else saslContext = SASLContext.createSASLContext(saslProps, serverFQDN, saslContext = SASLContext.createSASLContext(saslProps, serverFQDN, SASL_MECHANISM_DIGEST_MD5, identityMapper); } catch (SaslException ex) { if (debugEnabled()) { opends/src/server/org/opends/server/extensions/GSSAPISASLMechanismHandler.java
@@ -23,7 +23,7 @@ * * * Copyright 2006-2009 Sun Microsystems, Inc. * Portions Copyright 2011 ForgeRock AS * Portions Copyright 2011-2012 ForgeRock AS */ package org.opends.server.extensions; @@ -398,19 +398,8 @@ SASLContext saslContext = (SASLContext) clientConn.getSASLAuthStateInfo(); if (saslContext == null) { try { //If the connection is secure already (i.e., TLS), then make the //receive buffers sizes match. if(clientConn.isSecure()) { HashMap<String, String>secProps = new HashMap<String,String>(saslProps); int maxBuf = clientConn.getAppBufferSize(); secProps.put(Sasl.MAX_BUFFER, Integer.toString(maxBuf)); saslContext = SASLContext.createSASLContext(secProps, serverFQDN, saslContext = SASLContext.createSASLContext(saslProps, serverFQDN, SASL_MECHANISM_GSSAPI, identityMapper); } else { saslContext = SASLContext.createSASLContext(saslProps, serverFQDN, SASL_MECHANISM_GSSAPI, identityMapper); } } catch (SaslException ex) { if (debugEnabled()) TRACER.debugCaught(DebugLogLevel.ERROR, ex); opends/src/server/org/opends/server/extensions/RedirectingByteChannel.java
@@ -143,7 +143,7 @@ */ public final void redirect(final ConnectionSecurityProvider provider) { redirect = provider.wrapChannel(child); redirect = provider.getChannel(); } opends/src/server/org/opends/server/extensions/SASLByteChannel.java
@@ -35,9 +35,6 @@ import java.nio.channels.ByteChannel; import java.security.cert.Certificate; import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; import org.opends.server.api.ClientConnection; @@ -46,10 +43,229 @@ * This class implements a SASL byte channel that can be used during * confidentiality and integrity. */ public class SASLByteChannel implements ByteChannel, ConnectionSecurityProvider public final class SASLByteChannel implements ConnectionSecurityProvider { /** * Private implementation. */ private final class ByteChannelImpl implements ByteChannel { /** * {@inheritDoc} */ @Override public void close() throws IOException { synchronized (readLock) { synchronized (writeLock) { saslContext.dispose(); channel.close(); } } } /** * {@inheritDoc} */ @Override public boolean isOpen() { return saslContext != null; } /** * {@inheritDoc} */ @Override public int read(final ByteBuffer unwrappedData) throws IOException { synchronized (readLock) { // Only read and unwrap new data if needed. if (!recvUnwrappedBuffer.hasRemaining()) { final int read = doRecvAndUnwrap(); if (read <= 0) { // No data read or end of stream. return read; } } // Copy available data. final int startPos = unwrappedData.position(); if (recvUnwrappedBuffer.remaining() > unwrappedData.remaining()) { // Unwrapped data does not fit in client buffer so copy one byte at a // time: it's annoying that there is no easy way to do this with // ByteBuffers. while (unwrappedData.hasRemaining()) { unwrappedData.put(recvUnwrappedBuffer.get()); } } else { // Unwrapped data fits client buffer so block copy. unwrappedData.put(recvUnwrappedBuffer); } return unwrappedData.position() - startPos; } } /** * {@inheritDoc} */ @Override public int write(final ByteBuffer unwrappedData) throws IOException { // This method will block until the entire message is sent. final int bytesWritten = unwrappedData.remaining(); // Synchronized in order to prevent interleaving and reordering. synchronized (writeLock) { // Write data in sendBufferSize segments. while (unwrappedData.hasRemaining()) { final int remaining = unwrappedData.remaining(); final int wrapSize = (remaining < sendUnwrappedBufferSize) ? remaining : sendUnwrappedBufferSize; final byte[] wrappedDataBytes; if (unwrappedData.hasArray()) { // Avoid extra copy if ByteBuffer is array based. wrappedDataBytes = saslContext.wrap(unwrappedData.array(), unwrappedData.arrayOffset(), wrapSize); unwrappedData.position(unwrappedData.position() + wrapSize); } else { // Non-array based ByteBuffer, so copy. unwrappedData.get(sendUnwrappedBytes, 0, wrapSize); wrappedDataBytes = saslContext .wrap(sendUnwrappedBytes, 0, wrapSize); unwrappedData.position(unwrappedData.position() + wrapSize); } // Encode SASL packet: 4 byte length + wrapped data. if (sendWrappedBuffer.capacity() < wrappedDataBytes.length + 4) { // Resize the send buffer. sendWrappedBuffer = ByteBuffer .allocate(wrappedDataBytes.length + 4); } sendWrappedBuffer.clear(); sendWrappedBuffer.putInt(wrappedDataBytes.length); sendWrappedBuffer.put(wrappedDataBytes); sendWrappedBuffer.flip(); // Write the SASL packet: our IO stack will block until all the data // is written. channel.write(sendWrappedBuffer); } } return bytesWritten; } // Attempt to read and unwrap the next SASL packet. private int doRecvAndUnwrap() throws IOException { // Read the encoded packet length first. if (recvWrappedLength < 0) { final int read = channel.read(recvWrappedLengthBuffer); if (read <= 0) { // No data read or end of stream. return read; } if (recvWrappedLengthBuffer.hasRemaining()) { // Unable to read the length, so no data available yet. return 0; } // Decode the length and reset the length buffer. recvWrappedLengthBuffer.flip(); recvWrappedLength = recvWrappedLengthBuffer.getInt(); recvWrappedLengthBuffer.clear(); // Check that the length is valid. if (recvWrappedLength > recvWrappedBufferMaximumSize) { throw new IOException( "Client sent a SASL packet specifying a length " + recvWrappedLength + " which exceeds the negotiated limit of " + recvWrappedBufferMaximumSize); } if (recvWrappedLength < 0) { throw new IOException( "Client sent a SASL packet specifying a negative length " + recvWrappedLength); } // Prepare the recv buffer for reading. recvWrappedBuffer.clear(); recvWrappedBuffer.limit(recvWrappedLength); } // Read wrapped data. final int read = channel.read(recvWrappedBuffer); if (read <= 0) { // No data read or end of stream. return read; } if (recvWrappedBuffer.hasRemaining()) { // Unable to read the full packet, so no data available yet. return 0; } // The complete packet has been read, so unwrap it. recvWrappedBuffer.flip(); final byte[] unwrappedDataBytes = saslContext.unwrap( recvWrappedBuffer.array(), 0, recvWrappedLength); recvWrappedLength = -1; if (recvUnwrappedBuffer.capacity() < unwrappedDataBytes.length) { // Resize the recv buffer (this shouldn't ever happen). recvUnwrappedBuffer = ByteBuffer.allocate(unwrappedDataBytes.length); } recvUnwrappedBuffer.clear(); recvUnwrappedBuffer.put(unwrappedDataBytes); recvUnwrappedBuffer.flip(); return recvUnwrappedBuffer.remaining(); } } /** * Return a SASL byte channel instance created using the specified parameters. * * @param c @@ -68,31 +284,23 @@ // The SASL context associated with the provider private SASLContext saslContext; // The byte channel associated with this provider. private final RedirectingByteChannel channel; // The number of bytes in the length buffer. private static final int lengthSize = 4; // Length of the buffer. private int bufLength; // The SASL mechanism name. private final String name; private final ByteChannel channel; private final ByteChannelImpl pimpl = new ByteChannelImpl(); private final SASLContext saslContext; // Buffers used in reading and decoding (unwrap) private final ByteBuffer readBuffer, decodeBuffer; private ByteBuffer recvUnwrappedBuffer; private final ByteBuffer recvWrappedBuffer; private final int recvWrappedBufferMaximumSize; private int recvWrappedLength = -1; private final ByteBuffer recvWrappedLengthBuffer = ByteBuffer.allocate(4); // How many bytes of the subsequent buffer is needed to complete a partially // read buffer. private int neededBytes = 0; private final int sendUnwrappedBufferSize; private final byte[] sendUnwrappedBytes; private ByteBuffer sendWrappedBuffer; // Used to not reset the buffer length size because the first 4 bytes of a // buffer are not size bytes. private boolean reading = false; private final Object readLock = new Object(); private final Object writeLock = new Object(); @@ -112,10 +320,16 @@ { this.name = name; this.saslContext = saslContext; this.channel = connection.getChannel(); this.readBuffer = ByteBuffer.allocate(connection.getAppBufferSize()); this.decodeBuffer = ByteBuffer.allocate(connection.getAppBufferSize() + lengthSize); channel = connection.getChannel(); recvWrappedBufferMaximumSize = saslContext.getMaxReceiveBufferSize(); sendUnwrappedBufferSize = saslContext.getMaxRawSendBufferSize(); recvWrappedBuffer = ByteBuffer.allocate(recvWrappedBufferMaximumSize); recvUnwrappedBuffer = ByteBuffer.allocate(recvWrappedBufferMaximumSize); recvUnwrappedBuffer.flip(); // Initially nothing has been received. sendUnwrappedBytes = new byte[sendUnwrappedBufferSize]; sendWrappedBuffer = ByteBuffer.allocate(sendUnwrappedBufferSize + 64); } @@ -124,21 +338,9 @@ * {@inheritDoc} */ @Override public synchronized void close() throws IOException public ByteChannel getChannel() { saslContext.dispose(); saslContext = null; } /** * {@inheritDoc} */ @Override public int getAppBufSize() { return saslContext.getBufSize(Sasl.MAX_BUFFER); return pimpl; } @@ -180,300 +382,9 @@ * {@inheritDoc} */ @Override public boolean isOpen() { return saslContext != null; } /** * {@inheritDoc} */ @Override public boolean isSecure() { return true; } /** * {@inheritDoc} */ @Override public synchronized int read(final ByteBuffer clearDst) throws IOException { int bytesToRead = lengthSize; if (reading) { bytesToRead = neededBytes; } final int readResult = readAll(readBuffer, bytesToRead); if (readResult == -1) { return -1; } // The previous buffer read was not complete, the current // buffer completes it. if (neededBytes > 0 && readResult > 0) { return (processPartial(readResult, clearDst)); } if (readResult == 0 && !reading) { return 0; } if (!reading) { bufLength = getBufLength(readBuffer); } reading = false; // The buffer length is greater than what is there, save what is there, // figure out how much more is needed and return. if (bufLength > readBuffer.position()) { neededBytes = bufLength - readBuffer.position() + lengthSize; readBuffer.flip(); decodeBuffer.put(readBuffer); readBuffer.clear(); return 0; } else { readBuffer.flip(); decodeBuffer.put(readBuffer); final byte[] inBytes = decodeBuffer.array(); final byte[] clearBytes = saslContext.unwrap(inBytes, lengthSize, bufLength); decodeBuffer.clear(); clearDst.put(clearBytes); readBuffer.clear(); return clearDst.position(); } } /** * {@inheritDoc} */ @Override public ByteChannel wrapChannel(final ByteChannel channel) { return this; } /** * {@inheritDoc} */ @Override public synchronized int write(final ByteBuffer clearSrc) throws IOException { final int sendBufSize = getAppBufSize(); final int srcLen = clearSrc.remaining(); final ByteBuffer sendBuffer = ByteBuffer.allocate(sendBufSize); if (srcLen > sendBufSize) { final int oldPos = clearSrc.position(); int curPos = oldPos; int curLimit = oldPos + sendBufSize; while (curPos < srcLen) { clearSrc.position(curPos); clearSrc.limit(curLimit); sendBuffer.put(clearSrc); writeChannel(wrap(sendBuffer.array(), clearSrc.remaining())); curPos = curLimit; curLimit = Math.min(srcLen, curPos + sendBufSize); } return srcLen; } else { sendBuffer.put(clearSrc); return writeChannel(wrap(sendBuffer.array(), srcLen)); } } /** * Return the clear buffer length as determined by processing the first 4 * bytes of the specified buffer. * * @param byteBuf * The buffer to examine the first 4 bytes of. * @return The size of the clear buffer. */ private int getBufLength(final ByteBuffer byteBuf) { int answer = 0; for (int i = 0; i < lengthSize; i++) { final byte b = byteBuf.get(i); answer <<= 8; answer |= (b & 0xff); } return answer; } /** * Finish processing a previous, partially read buffer using some, or, all of * the bytes of the current buffer. */ private int processPartial(final int readResult, final ByteBuffer clearDst) throws IOException { readBuffer.flip(); // Use all of the bytes of the current buffer and read some more. if (neededBytes > readResult) { neededBytes -= readResult; decodeBuffer.put(readBuffer); readBuffer.clear(); reading = false; return 0; } // Use a portion of the current buffer. for (; neededBytes > 0; neededBytes--) { decodeBuffer.put(readBuffer.get()); } // Unwrap the now completed buffer. final byte[] inBytes = decodeBuffer.array(); final byte[] clearBytes = saslContext .unwrap(inBytes, lengthSize, bufLength); clearDst.put(clearBytes); decodeBuffer.clear(); readBuffer.compact(); // If the read buffer has bytes, these are a new buffer. Reset the // buffer length to the new value. if (readBuffer.position() != 0) { bufLength = getBufLength(readBuffer); reading = true; } else { reading = false; } return clearDst.position(); } /** * Read from the socket channel into the specified byte buffer at least the * number of bytes specified in the total parameter. * * @param byteBuf * The byte buffer to put the bytes in. * @param total * The total number of bytes to read from the socket channel. * @return The number of bytes read, 0 or -1. * @throws IOException * If an error occurred reading the socket channel. */ private int readAll(final ByteBuffer byteBuf, int total) throws IOException { while (channel.isOpen() && total > 0) { final int count = channel.read(byteBuf); if (count == -1) { return -1; } if (count == 0) { return 0; } total -= count; } if (total > 0) { return -1; } else { return byteBuf.position(); } } /** * Creates a buffer suitable to send to the client using the specified clear * byte array and length of the bytes to wrap. * * @param clearBytes * The clear byte array to send to the client. * @param len * The length of the bytes to wrap in the byte array. * @throws SaslException * If the wrap of the bytes fails. */ private ByteBuffer wrap(final byte[] clearBytes, final int len) throws SaslException { final byte[] wrapBytes = saslContext.wrap(clearBytes, 0, len); final byte[] outBytes = new byte[wrapBytes.length + lengthSize]; writeBufLen(outBytes, wrapBytes.length); System.arraycopy(wrapBytes, 0, outBytes, lengthSize, wrapBytes.length); return ByteBuffer.wrap(outBytes); } /** * Writes the specified len parameter into the buffer in a form that can be * sent over a network to the client. * * @param buf * The buffer to hold the length bytes. * @param len * The length to encode. */ private void writeBufLen(final byte[] buf, int len) { for (int i = 3; i >= 0; i--) { buf[i] = (byte) (len & 0xff); len >>>= 8; } } /** * Write the specified byte buffer to the socket channel. * * @param buffer * The byte buffer to write to the socket channel. * @return {@code true} if the byte buffer was successfully written to the * socket channel, or, {@code false} if not. */ private int writeChannel(final ByteBuffer buffer) throws IOException { return channel.write(buffer); } } opends/src/server/org/opends/server/extensions/SASLContext.java
@@ -447,16 +447,65 @@ /** * Return the negotiated buffer size. * Returns the negotiated maximum size of protected data which can be received * from the client. * * @param prop * The buffer size property to return. * @return The value of the negotiated buffer size. * @return The negotiated maximum size of protected data which can be received * from the client. */ int getBufSize(final String prop) int getMaxReceiveBufferSize() { final String sizeStr = (String) saslServer.getNegotiatedProperty(prop); return Integer.parseInt(sizeStr); String str = (String) saslServer.getNegotiatedProperty(Sasl.MAX_BUFFER); if (str != null) { try { return Integer.parseInt(str); } catch (NumberFormatException e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } // Default buffer size if not specified according to Java SASL // documentation. return 65536; } /** * Returns the negotiated maximum size of raw data which can be sent to the * client. * * @return The negotiated maximum size of raw data which can be sent to the * client. */ int getMaxRawSendBufferSize() { String str = (String) saslServer.getNegotiatedProperty(Sasl.RAW_SEND_SIZE); if (str != null) { try { return Integer.parseInt(str); } catch (NumberFormatException e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } // Default buffer size if not specified according to Java SASL // documentation. return 65536; } opends/src/server/org/opends/server/extensions/TLSByteChannel.java
@@ -42,6 +42,7 @@ import java.util.Set; import javax.net.ssl.*; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import org.opends.server.admin.std.server.LDAPConnectionHandlerCfg; import org.opends.server.api.ClientConnection; @@ -53,50 +54,375 @@ /** * A class that provides a TLS byte channel implementation. */ public class TLSByteChannel implements ByteChannel, ConnectionSecurityProvider public final class TLSByteChannel implements ConnectionSecurityProvider { private static final DebugTracer TRACER = getTracer(); /** * Private implementation. */ private final class ByteChannelImpl implements ByteChannel { private final ByteChannel socketChannel; /** * {@inheritDoc} */ public void close() throws IOException { synchronized (readLock) { synchronized (writeLock) { final boolean isInitiator = !sslEngine.isInboundDone(); private final SSLEngine sslEngine; try { if (!sslEngine.isOutboundDone()) { sslEngine.closeOutbound(); while (doWrapAndSend(EMPTY_BUFFER) > 0) { // Write out any remaining SSL close notifications. } } } catch (final ClosedChannelException e) { // Ignore this so that close is idempotent. } finally { try { sslEngine.closeInbound(); } catch (final SSLException e) { // Not yet received peer's close notification. Ignore this if we // are the initiator. if (!isInitiator) { throw e; } } finally { channel.close(); } } } } } // read copy to buffer private final ByteBuffer appData; // read encrypted private final ByteBuffer appNetData; // Write encrypted private final ByteBuffer netData; private final ByteBuffer tempData; /** * {@inheritDoc} */ public boolean isOpen() { return !sslEngine.isOutboundDone() || !sslEngine.isInboundDone(); } private final int sslBufferSize; private final int appBufSize; private boolean reading = false; /** * {@inheritDoc} */ public int read(final ByteBuffer unwrappedData) throws IOException { synchronized (readLock) { // Repeat until there is some unwrapped data available or all available // data has been read from the underlying socket. if (!recvUnwrappedBuffer.hasRemaining()) { final int read = doRecvAndUnwrap(); if (read <= 0) { // No data read or end of stream. return read; } } // Copy available data. final int startPos = unwrappedData.position(); if (recvUnwrappedBuffer.remaining() > unwrappedData.remaining()) { // Unwrapped data does not fit in client buffer so copy one by at a // time: it's annoying that there is no easy way to do this with // ByteBuffers. while (unwrappedData.hasRemaining()) { unwrappedData.put(recvUnwrappedBuffer.get()); } } else { // Unwrapped data fits client buffer so block copy. unwrappedData.put(recvUnwrappedBuffer); } return unwrappedData.position() - startPos; } } /** * {@inheritDoc} */ public int write(final ByteBuffer unwrappedData) throws IOException { // This method will block until the entire message is sent. final int bytesWritten = unwrappedData.remaining(); // Synchronized in order to prevent interleaving and reordering. synchronized (writeLock) { // Repeat until the entire input data is written. while (unwrappedData.hasRemaining()) { // Wrap and send the data. doWrapAndSend(unwrappedData); // Perform handshake if needed. if (isHandshaking(sslEngine.getHandshakeStatus())) { doHandshake(false /* isReading */); } } } return bytesWritten; } private void doHandshake(final boolean isReading) throws IOException { // This lock is probably unnecessary since tasks can be run in parallel, // but it adds no additional overhead so there's little harm in having // it. synchronized (handshakeLock) { while (true) { switch (sslEngine.getHandshakeStatus()) { case NEED_TASK: Runnable runnable; while ((runnable = sslEngine.getDelegatedTask()) != null) { runnable.run(); } break; case NEED_UNWRAP: // Block for writes, but be non-blocking for reads. if (isReading) { // Let doRecvAndUnwrap() deal with this. return; } // Need to do an unwrap (read) while writing. if (doRecvAndUnwrap() < 0) { throw new ClosedChannelException(); } break; case NEED_WRAP: doWrapAndSend(EMPTY_BUFFER); break; default: // NOT_HANDSHAKING, FINISHED. return; } } } } // Attempt to read and unwrap the next SSL packet. private int doRecvAndUnwrap() throws IOException { // Synchronize SSL unwrap with channel reads. synchronized (unwrapLock) { // Repeat if there is underflow or overflow. boolean needRead = true; while (true) { // Read wrapped data if needed. if (needRead) { recvWrappedBuffer.compact(); // Prepare for append. final int read = channel.read(recvWrappedBuffer); recvWrappedBuffer.flip(); // Restore for read. if (read < 0) { // Peer abort? sslEngine.closeInbound(); return -1; } } else { needRead = true; } // Unwrap. recvUnwrappedBuffer.compact(); // Prepare for append. final SSLEngineResult result = sslEngine.unwrap(recvWrappedBuffer, recvUnwrappedBuffer); recvUnwrappedBuffer.flip(); // Restore for read. switch (result.getStatus()) { case BUFFER_OVERFLOW: // The unwrapped buffer is not big enough: resize and repeat. final int newAppSize = sslEngine.getSession() .getApplicationBufferSize(); final ByteBuffer newRecvUnwrappedBuffer = ByteBuffer .allocate(recvUnwrappedBuffer.limit() + newAppSize); newRecvUnwrappedBuffer.put(recvUnwrappedBuffer); newRecvUnwrappedBuffer.flip(); recvUnwrappedBuffer = newRecvUnwrappedBuffer; needRead = false; break; // Retry unwrap. case BUFFER_UNDERFLOW: // Not enough data was read. This either means that the inbound // buffer was too small, or not enough data is available. final int newPktSize = sslEngine.getSession().getPacketBufferSize(); if (newPktSize > recvWrappedBuffer.capacity()) { // Buffer needs resizing. final ByteBuffer newRecvWrappedBuffer = ByteBuffer .allocate(newPktSize); newRecvWrappedBuffer.put(recvWrappedBuffer); newRecvWrappedBuffer.flip(); recvWrappedBuffer = newRecvWrappedBuffer; break; } else { // Not enough data is available to read a complete SSL packet. return 0; } case CLOSED: // Peer sent SSL close notification. sslEngine.closeInbound(); return -1; default: // OK if (recvUnwrappedBuffer.hasRemaining()) { // Some application data was read so return it. return recvUnwrappedBuffer.remaining(); } else if (isHandshaking(result.getHandshakeStatus())) { // No application data was read, but if we are handshaking then // try to continue. if (result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP && !recvWrappedBuffer.hasRemaining()) { // Not enough data is available to continue handshake. return 0; } else { // Continue handshake. doHandshake(true /* isReading */); } } else { // No data available and not handshaking. return 0; } } } } } // Attempt to wrap and send the next SSL packet. private int doWrapAndSend(final ByteBuffer unwrappedData) throws IOException { // Synchronize SSL wrap with channel writes. synchronized (wrapLock) { // Repeat while there is overflow. while (true) { final SSLEngineResult result = sslEngine.wrap(unwrappedData, sendWrappedBuffer); switch (result.getStatus()) { case BUFFER_OVERFLOW: // The wrapped buffer is not big enough: resize and repeat. final int newSize = sslEngine.getSession().getPacketBufferSize(); final ByteBuffer newSendWrappedBuffer = ByteBuffer .allocate(sendWrappedBuffer.position() + newSize); sendWrappedBuffer.flip(); newSendWrappedBuffer.put(sendWrappedBuffer); sendWrappedBuffer = newSendWrappedBuffer; break; // Retry. case BUFFER_UNDERFLOW: // This should not happen for sends. throw new SSLException("Got unexpected underflow while wrapping"); case CLOSED: throw new ClosedChannelException(); default: // OK // Write the SSL packet: our IO stack will block until all the // data is written. sendWrappedBuffer.flip(); while (sendWrappedBuffer.hasRemaining()) { channel.write(sendWrappedBuffer); } final int written = sendWrappedBuffer.position(); sendWrappedBuffer.clear(); return written; } } } } private boolean isHandshaking(final HandshakeStatus status) { return status != HandshakeStatus.NOT_HANDSHAKING; } } // Map of cipher phrases to effective key size (bits). Taken from the // following RFCs: 5289, 4346, 3268,4132 and 4162. private static final Map<String, Integer> cipherMap; private static final Map<String, Integer> CIPHER_MAP; static { cipherMap = new LinkedHashMap<String, Integer>(); cipherMap.put("_WITH_AES_256_CBC_", new Integer(256)); cipherMap.put("_WITH_CAMELLIA_256_CBC_", new Integer(256)); cipherMap.put("_WITH_AES_256_GCM_", new Integer(256)); cipherMap.put("_WITH_3DES_EDE_CBC_", new Integer(112)); cipherMap.put("_WITH_AES_128_GCM_", new Integer(128)); cipherMap.put("_WITH_SEED_CBC_", new Integer(128)); cipherMap.put("_WITH_CAMELLIA_128_CBC_", new Integer(128)); cipherMap.put("_WITH_AES_128_CBC_", new Integer(128)); cipherMap.put("_WITH_IDEA_CBC_", new Integer(128)); cipherMap.put("_WITH_DES_CBC_", new Integer(56)); cipherMap.put("_WITH_RC2_CBC_40_", new Integer(40)); cipherMap.put("_WITH_RC4_40_", new Integer(40)); cipherMap.put("_WITH_DES40_CBC_", new Integer(40)); cipherMap.put("_WITH_NULL_", new Integer(0)); }; CIPHER_MAP = new LinkedHashMap<String, Integer>(); CIPHER_MAP.put("_WITH_AES_256_CBC_", new Integer(256)); CIPHER_MAP.put("_WITH_CAMELLIA_256_CBC_", new Integer(256)); CIPHER_MAP.put("_WITH_AES_256_GCM_", new Integer(256)); CIPHER_MAP.put("_WITH_3DES_EDE_CBC_", new Integer(112)); CIPHER_MAP.put("_WITH_AES_128_GCM_", new Integer(128)); CIPHER_MAP.put("_WITH_SEED_CBC_", new Integer(128)); CIPHER_MAP.put("_WITH_CAMELLIA_128_CBC_", new Integer(128)); CIPHER_MAP.put("_WITH_AES_128_CBC_", new Integer(128)); CIPHER_MAP.put("_WITH_IDEA_CBC_", new Integer(128)); CIPHER_MAP.put("_WITH_DES_CBC_", new Integer(56)); CIPHER_MAP.put("_WITH_RC2_CBC_40_", new Integer(40)); CIPHER_MAP.put("_WITH_RC4_40_", new Integer(40)); CIPHER_MAP.put("_WITH_DES40_CBC_", new Integer(40)); CIPHER_MAP.put("_WITH_NULL_", new Integer(0)); } private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0); private static final DebugTracer TRACER = getTracer(); @@ -124,12 +450,28 @@ private final ByteChannelImpl pimpl = new ByteChannelImpl(); private final ByteChannel channel; private final SSLEngine sslEngine; private ByteBuffer recvWrappedBuffer; private ByteBuffer recvUnwrappedBuffer; private ByteBuffer sendWrappedBuffer; private final Object handshakeLock = new Object(); private final Object unwrapLock = new Object(); private final Object wrapLock = new Object(); private final Object readLock = new Object(); private final Object writeLock = new Object(); private TLSByteChannel(final LDAPConnectionHandlerCfg config, final ClientConnection c, final ByteChannel socketChannel, final ClientConnection c, final ByteChannel channel, final SSLContext sslContext) { this.socketChannel = socketChannel; this.channel = channel; // getHostName could potentially be very expensive and could block // the connection handler for several minutes. (See issue 4229) @@ -137,11 +479,14 @@ // avoid blocking new connections. Just remove for now to prevent // potential DoS attacks. SSL sessions will not be reused and some // cipher suites (such as Kerberos) will not work. // String hostName = socketChannel.socket().getInetAddress().getHostName(); // int port = socketChannel.socket().getPort(); // sslEngine = sslContext.createSSLEngine(hostName, port); sslEngine = sslContext.createSSLEngine(); sslEngine.setUseClientMode(false); final Set<String> protocols = config.getSSLProtocol(); if (!protocols.isEmpty()) { @@ -171,15 +516,18 @@ break; } final SSLSession sslSession = sslEngine.getSession(); sslBufferSize = sslSession.getPacketBufferSize(); appBufSize = sslSession.getApplicationBufferSize(); // Allocate read/write buffers. final SSLSession session = sslEngine.getSession(); final int wrappedBufferSize = session.getPacketBufferSize(); final int unwrappedBufferSize = session.getApplicationBufferSize(); appNetData = ByteBuffer.allocate(sslBufferSize); netData = ByteBuffer.allocate(sslBufferSize); sendWrappedBuffer = ByteBuffer.allocate(wrappedBufferSize); recvWrappedBuffer = ByteBuffer.allocate(wrappedBufferSize); recvUnwrappedBuffer = ByteBuffer.allocate(unwrappedBufferSize); appData = ByteBuffer.allocate(sslSession.getApplicationBufferSize()); tempData = ByteBuffer.allocate(sslSession.getApplicationBufferSize()); // Initially nothing has been received. recvWrappedBuffer.flip(); recvUnwrappedBuffer.flip(); } @@ -187,27 +535,9 @@ /** * {@inheritDoc} */ public synchronized void close() throws IOException public ByteChannel getChannel() { sslEngine.closeInbound(); sslEngine.closeOutbound(); final SSLEngineResult.HandshakeStatus hsStatus = sslEngine .getHandshakeStatus(); if (hsStatus != SSLEngineResult.HandshakeStatus.FINISHED && hsStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { doHandshakeWrite(hsStatus); } } /** * {@inheritDoc} */ public int getAppBufSize() { return appBufSize; return pimpl; } @@ -248,31 +578,15 @@ */ public int getSSF() { int cipherKeySSF = 0; final String cipherString = sslEngine.getSession().getCipherSuite(); for (final Map.Entry<String, Integer> mapEntry : cipherMap.entrySet()) for (final Map.Entry<String, Integer> mapEntry : CIPHER_MAP.entrySet()) { if (cipherString.indexOf(mapEntry.getKey()) >= 0) { cipherKeySSF = mapEntry.getValue().intValue(); break; return mapEntry.getValue().intValue(); } } return cipherKeySSF; } /** * {@inheritDoc} */ public boolean isOpen() { if (sslEngine.isInboundDone() || sslEngine.isOutboundDone()) { return false; } return true; return 0; } @@ -285,269 +599,4 @@ return true; } /** * {@inheritDoc} */ public synchronized int read(final ByteBuffer clearBuffer) throws IOException { SSLEngineResult.HandshakeStatus hsStatus; if (!reading) { appNetData.clear(); } else { reading = false; } if (!socketChannel.isOpen()) { return -1; } if (sslEngine.isInboundDone()) { return -1; } do { final int wrappedBytes = socketChannel.read(appNetData); appNetData.flip(); if (wrappedBytes == -1) { return -1; } hsStatus = sslEngine.getHandshakeStatus(); if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) { doHandshakeRead(hsStatus); } if (wrappedBytes == 0) { return 0; } while (appNetData.hasRemaining()) { appData.clear(); final SSLEngineResult res = sslEngine.unwrap(appNetData, appData); appData.flip(); if (res.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) { appNetData.compact(); reading = true; break; } else if (res.getStatus() != SSLEngineResult.Status.OK) { return -1; } hsStatus = sslEngine.getHandshakeStatus(); if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) { doHandshakeOp(hsStatus); } clearBuffer.put(appData); } hsStatus = sslEngine.getHandshakeStatus(); } while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP); return clearBuffer.position(); } /** * {@inheritDoc} */ public ByteChannel wrapChannel(final ByteChannel channel) { return this; } /** * {@inheritDoc} */ public synchronized int write(final ByteBuffer clearData) throws IOException { if (!socketChannel.isOpen() || sslEngine.isOutboundDone()) { throw new ClosedChannelException(); } final int originalPosition = clearData.position(); final int originalLimit = clearData.limit(); final int length = originalLimit - originalPosition; if (length > sslBufferSize) { int pos = originalPosition; int lim = originalPosition + sslBufferSize; while (pos < originalLimit) { clearData.position(pos); clearData.limit(lim); writeInternal(clearData); pos = lim; lim = Math.min(originalLimit, pos + sslBufferSize); } return length; } else { return writeInternal(clearData); } } private void doHandshakeOp(SSLEngineResult.HandshakeStatus hsStatus) throws IOException { SSLEngineResult res; switch (hsStatus) { case NEED_TASK: hsStatus = doTasks(); break; case NEED_WRAP: tempData.clear(); netData.clear(); res = sslEngine.wrap(tempData, netData); hsStatus = res.getHandshakeStatus(); netData.flip(); while (netData.hasRemaining()) { socketChannel.write(netData); } hsStatus = sslEngine.getHandshakeStatus(); return; default: return; } } private void doHandshakeRead(SSLEngineResult.HandshakeStatus hsStatus) throws IOException { do { doHandshakeOp(hsStatus); hsStatus = sslEngine.getHandshakeStatus(); } while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP || hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK); } private void doHandshakeUnwrap() throws IOException { netData.clear(); tempData.clear(); final int bytesRead = socketChannel.read(netData); if (bytesRead <= 0) { throw new ClosedChannelException(); } else { sslEngine.unwrap(netData, tempData); } } private void doHandshakeWrite(SSLEngineResult.HandshakeStatus hsStatus) throws IOException { do { if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { doHandshakeUnwrap(); } else { doHandshakeOp(hsStatus); } hsStatus = sslEngine.getHandshakeStatus(); } while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP || hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP); } private SSLEngineResult.HandshakeStatus doTasks() { Runnable task; while ((task = sslEngine.getDelegatedTask()) != null) { task.run(); } return sslEngine.getHandshakeStatus(); } private int writeInternal(final ByteBuffer clearData) throws IOException { int totBytesSent = 0; SSLEngineResult.HandshakeStatus hsStatus; hsStatus = sslEngine.getHandshakeStatus(); if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { doHandshakeWrite(hsStatus); } while (clearData.hasRemaining()) { netData.clear(); final SSLEngineResult res = sslEngine.wrap(clearData, netData); netData.flip(); if (netData.remaining() == 0) { // wrap didn't produce any data from our clear buffer. // Throw exception to prevent looping. throw new SSLException("SSLEngine.wrap produced 0 bytes"); } if (res.getStatus() != SSLEngineResult.Status.OK) { throw new ClosedChannelException(); } if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { doHandshakeWrite(hsStatus); } totBytesSent += socketChannel.write(netData); } return totBytesSent; } } opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java
@@ -23,7 +23,7 @@ * * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2010-2011 ForgeRock AS. * Portions Copyright 2010-2012 ForgeRock AS. */ package org.opends.server.protocols.ldap; @@ -360,7 +360,7 @@ private ASN1ByteChannelReader asn1Reader; private int APPLICATION_BUFFER_SIZE = 4096; private final int bufferSize; private final RedirectingByteChannel saslChannel; private final RedirectingByteChannel tlsChannel; @@ -420,7 +420,7 @@ this.useNanoTime=DirectoryServer.getUseNanoTime(); } APPLICATION_BUFFER_SIZE = connectionHandler.getBufferSize(); bufferSize = connectionHandler.getBufferSize(); tlsChannel = RedirectingByteChannel.getRedirectingByteChannel( @@ -428,7 +428,7 @@ saslChannel = RedirectingByteChannel.getRedirectingByteChannel(tlsChannel); this.asn1Reader = ASN1.getReader(saslChannel, APPLICATION_BUFFER_SIZE, connectionHandler ASN1.getReader(saslChannel, bufferSize, connectionHandler .getMaxRequestSize()); writeLock = new ReentrantLock(); @@ -939,16 +939,8 @@ { if (asn1Writer == null) { if (isSecure()) { int appBufSize = activeProvider.getAppBufSize(); asn1Writer = ASN1.getWriter(saslChannel, writeLock, appBufSize); } else { asn1Writer = ASN1.getWriter(saslChannel, writeLock, APPLICATION_BUFFER_SIZE); } asn1Writer = ASN1.getWriter(saslChannel, writeLock, bufferSize); asn1WriterMap.put(currentThread, asn1Writer); } @@ -2590,9 +2582,6 @@ */ public void enableTLS() { this.asn1Reader = ASN1.getReader(saslChannel, tlsPendingProvider.getAppBufSize(), connectionHandler.getMaxRequestSize()); activeProvider = tlsPendingProvider; tlsChannel.redirect(tlsPendingProvider); tlsPendingProvider = null; @@ -2608,9 +2597,6 @@ */ public void enableSSL(ConnectionSecurityProvider sslProvider) { this.asn1Reader = ASN1.getReader(saslChannel, sslProvider.getAppBufSize(), connectionHandler.getMaxRequestSize()); activeProvider = sslProvider; tlsChannel.redirect(sslProvider); } @@ -2624,10 +2610,6 @@ { activeProvider = saslPendingProvider; saslChannel.redirect(saslPendingProvider); asn1Reader = ASN1.getReader(saslChannel, saslPendingProvider.getAppBufSize(), connectionHandler .getMaxRequestSize()); saslPendingProvider = null; } @@ -2657,7 +2639,7 @@ * @return The TLS redirecting byte channel. */ @Override public RedirectingByteChannel getChannel() { public ByteChannel getChannel() { return this.tlsChannel; } @@ -2678,21 +2660,6 @@ /** * Retrieves the application buffer size used in a LDAP client connection. * If a active security provider is being used, then the application buffer * size of that provider is returned. * * @return The application buffer size. */ @Override public int getAppBufferSize() { if(activeProvider != null) return activeProvider.getAppBufSize(); else return APPLICATION_BUFFER_SIZE; } /** * {@inheritDoc} */ @Override