/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2008-2009 Sun Microsystems, Inc. * Portions Copyright 2012 ForgeRock AS */ package org.opends.server.extensions; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.security.cert.Certificate; import org.opends.server.api.ClientConnection; /** * This class implements a SASL byte channel that can be used during * confidentiality and integrity. */ 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() + 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 SASL packets until some unwrapped data is produced or no more // data is available on the underlying channel. while (true) { // Read the wrapped packet length first. if (recvWrappedLength < 0) { // The channel read may only partially fill the buffer due to // buffering in the underlying channel layer (e.g. SSL layer), so // repeatedly read until the length has been read or we are sure // that we are unable to proceed. while (recvWrappedLengthBuffer.hasRemaining()) { final int read = channel.read(recvWrappedLengthBuffer); if (read <= 0) { // Not enough data available or end of stream. return read; } } // 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 the wrapped packet data. // The channel read may only partially fill the buffer due to // buffering in the underlying channel layer (e.g. SSL layer), so // repeatedly read until the data has been read or we are sure // that we are unable to proceed. while (recvWrappedBuffer.hasRemaining()) { final int read = channel.read(recvWrappedBuffer); if (read <= 0) { // Not enough data available or end of stream. return read; } } // The complete packet has been read, so unwrap it. recvWrappedBuffer.flip(); final byte[] unwrappedDataBytes = saslContext.unwrap( recvWrappedBuffer.array(), 0, recvWrappedLength); recvWrappedLength = -1; // Only return the unwrapped data if it was non-empty, otherwise try to // read another SASL packet. if (unwrappedDataBytes.length > 0) { recvUnwrappedBuffer = ByteBuffer.wrap(unwrappedDataBytes); return recvUnwrappedBuffer.remaining(); } } } } /** * Return a SASL byte channel instance created using the specified parameters. * * @param c * A client connection associated with the instance. * @param name * The name of the instance (SASL mechanism name). * @param context * A SASL context associated with the instance. * @return A SASL byte channel. */ public static SASLByteChannel getSASLByteChannel(final ClientConnection c, final String name, final SASLContext context) { return new SASLByteChannel(c, name, context); } private final String name; private final ByteChannel channel; private final ByteChannelImpl pimpl = new ByteChannelImpl(); private final SASLContext saslContext; private ByteBuffer recvUnwrappedBuffer; private final ByteBuffer recvWrappedBuffer; private final int recvWrappedBufferMaximumSize; private int recvWrappedLength = -1; private final ByteBuffer recvWrappedLengthBuffer = ByteBuffer.allocate(4); private final int sendUnwrappedBufferSize; private final byte[] sendUnwrappedBytes; private ByteBuffer sendWrappedBuffer; private final Object readLock = new Object(); private final Object writeLock = new Object(); /** * Create a SASL byte channel with the specified parameters that is capable of * processing a confidentiality/integrity SASL connection. * * @param connection * The client connection to read/write the bytes. * @param name * The SASL mechanism name. * @param saslContext * The SASL context to process the data through. */ private SASLByteChannel(final ClientConnection connection, final String name, final SASLContext saslContext) { this.name = name; this.saslContext = saslContext; channel = connection.getChannel(); recvWrappedBufferMaximumSize = saslContext.getMaxReceiveBufferSize(); sendUnwrappedBufferSize = saslContext.getMaxRawSendBufferSize(); recvWrappedBuffer = ByteBuffer.allocate(recvWrappedBufferMaximumSize); recvUnwrappedBuffer = ByteBuffer.allocate(0); sendUnwrappedBytes = new byte[sendUnwrappedBufferSize]; sendWrappedBuffer = ByteBuffer.allocate(sendUnwrappedBufferSize + 64); } /** * {@inheritDoc} */ @Override public ByteChannel getChannel() { return pimpl; } /** * {@inheritDoc} */ @Override public Certificate[] getClientCertificateChain() { return new Certificate[0]; } /** * {@inheritDoc} */ @Override public String getName() { return name; } /** * {@inheritDoc} */ @Override public int getSSF() { return saslContext.getSSF(); } /** * {@inheritDoc} */ @Override public boolean isSecure() { return true; } }