001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2012-2016 ForgeRock AS.
016 */
017package org.opends.server.extensions;
018
019import java.io.IOException;
020import java.nio.ByteBuffer;
021import java.nio.channels.ByteChannel;
022import java.security.cert.Certificate;
023
024import org.opends.server.api.ClientConnection;
025
026/**
027 * This class implements a SASL byte channel that can be used during
028 * confidentiality and integrity.
029 */
030public final class SASLByteChannel implements ConnectionSecurityProvider
031{
032  /** Private implementation. */
033  private final class ByteChannelImpl implements ByteChannel
034  {
035    @Override
036    public void close() throws IOException
037    {
038      synchronized (readLock)
039      {
040        synchronized (writeLock)
041        {
042          saslContext.dispose();
043          channel.close();
044        }
045      }
046    }
047
048    @Override
049    public boolean isOpen()
050    {
051      return saslContext != null;
052    }
053
054    @Override
055    public int read(final ByteBuffer unwrappedData) throws IOException
056    {
057      synchronized (readLock)
058      {
059        // Only read and unwrap new data if needed.
060        if (!recvUnwrappedBuffer.hasRemaining())
061        {
062          final int read = doRecvAndUnwrap();
063          if (read <= 0)
064          {
065            // No data read or end of stream.
066            return read;
067          }
068        }
069
070        // Copy available data.
071        final int startPos = unwrappedData.position();
072        if (recvUnwrappedBuffer.remaining() > unwrappedData.remaining())
073        {
074          // Unwrapped data does not fit in client buffer so copy one byte at a
075          // time: it's annoying that there is no easy way to do this with
076          // ByteBuffers.
077          while (unwrappedData.hasRemaining())
078          {
079            unwrappedData.put(recvUnwrappedBuffer.get());
080          }
081        }
082        else
083        {
084          // Unwrapped data fits client buffer so block copy.
085          unwrappedData.put(recvUnwrappedBuffer);
086        }
087        return unwrappedData.position() - startPos;
088      }
089    }
090
091    @Override
092    public int write(final ByteBuffer unwrappedData) throws IOException
093    {
094      // This method will block until the entire message is sent.
095      final int bytesWritten = unwrappedData.remaining();
096
097      // Synchronized in order to prevent interleaving and reordering.
098      synchronized (writeLock)
099      {
100        // Write data in sendBufferSize segments.
101        while (unwrappedData.hasRemaining())
102        {
103          final int remaining = unwrappedData.remaining();
104          final int wrapSize = (remaining < sendUnwrappedBufferSize) ? remaining
105              : sendUnwrappedBufferSize;
106
107          final byte[] wrappedDataBytes;
108          if (unwrappedData.hasArray())
109          {
110            // Avoid extra copy if ByteBuffer is array based.
111            wrappedDataBytes = saslContext.wrap(unwrappedData.array(),
112                unwrappedData.arrayOffset() + unwrappedData.position(),
113                wrapSize);
114          }
115          else
116          {
117            // Non-array based ByteBuffer, so copy.
118            unwrappedData.get(sendUnwrappedBytes, 0, wrapSize);
119            wrappedDataBytes = saslContext
120                .wrap(sendUnwrappedBytes, 0, wrapSize);
121          }
122          unwrappedData.position(unwrappedData.position() + wrapSize);
123
124          // Encode SASL packet: 4 byte length + wrapped data.
125          if (sendWrappedBuffer.capacity() < wrappedDataBytes.length + 4)
126          {
127            // Resize the send buffer.
128            sendWrappedBuffer =
129                ByteBuffer.allocate(wrappedDataBytes.length + 4);
130          }
131          sendWrappedBuffer.clear();
132          sendWrappedBuffer.putInt(wrappedDataBytes.length);
133          sendWrappedBuffer.put(wrappedDataBytes);
134          sendWrappedBuffer.flip();
135
136          // Write the SASL packet: our IO stack will block until all the data
137          // is written.
138          channel.write(sendWrappedBuffer);
139        }
140      }
141
142      return bytesWritten;
143    }
144
145    /** Attempt to read and unwrap the next SASL packet. */
146    private int doRecvAndUnwrap() throws IOException
147    {
148      // Read SASL packets until some unwrapped data is produced or no more
149      // data is available on the underlying channel.
150      while (true)
151      {
152        // Read the wrapped packet length first.
153        if (recvWrappedLength < 0)
154        {
155          // The channel read may only partially fill the buffer due to
156          // buffering in the underlying channel layer (e.g. SSL layer), so
157          // repeatedly read until the length has been read or we are sure
158          // that we are unable to proceed.
159          while (recvWrappedLengthBuffer.hasRemaining())
160          {
161            final int read = channel.read(recvWrappedLengthBuffer);
162            if (read <= 0)
163            {
164              // Not enough data available or end of stream.
165              return read;
166            }
167          }
168
169          // Decode the length and reset the length buffer.
170          recvWrappedLengthBuffer.flip();
171          recvWrappedLength = recvWrappedLengthBuffer.getInt();
172          recvWrappedLengthBuffer.clear();
173
174          // Check that the length is valid.
175          if (recvWrappedLength > recvWrappedBufferMaximumSize)
176          {
177            throw new IOException(
178                "Client sent a SASL packet specifying a length "
179                    + recvWrappedLength
180                    + " which exceeds the negotiated limit of "
181                    + recvWrappedBufferMaximumSize);
182          }
183
184          if (recvWrappedLength < 0)
185          {
186            throw new IOException(
187                "Client sent a SASL packet specifying a negative length "
188                    + recvWrappedLength);
189          }
190
191          // Prepare the recv buffer for reading.
192          recvWrappedBuffer.clear();
193          recvWrappedBuffer.limit(recvWrappedLength);
194        }
195
196        // Read the wrapped packet data.
197
198        // The channel read may only partially fill the buffer due to
199        // buffering in the underlying channel layer (e.g. SSL layer), so
200        // repeatedly read until the data has been read or we are sure
201        // that we are unable to proceed.
202        while (recvWrappedBuffer.hasRemaining())
203        {
204          final int read = channel.read(recvWrappedBuffer);
205          if (read <= 0)
206          {
207            // Not enough data available or end of stream.
208            return read;
209          }
210        }
211
212        // The complete packet has been read, so unwrap it.
213        recvWrappedBuffer.flip();
214        final byte[] unwrappedDataBytes = saslContext.unwrap(
215            recvWrappedBuffer.array(), 0, recvWrappedLength);
216        recvWrappedLength = -1;
217
218        // Only return the unwrapped data if it was non-empty, otherwise try to
219        // read another SASL packet.
220        if (unwrappedDataBytes.length > 0)
221        {
222          recvUnwrappedBuffer = ByteBuffer.wrap(unwrappedDataBytes);
223          return recvUnwrappedBuffer.remaining();
224        }
225      }
226    }
227  }
228
229  /**
230   * Return a SASL byte channel instance created using the specified parameters.
231   *
232   * @param c
233   *          A client connection associated with the instance.
234   * @param name
235   *          The name of the instance (SASL mechanism name).
236   * @param context
237   *          A SASL context associated with the instance.
238   * @return A SASL byte channel.
239   */
240  public static SASLByteChannel getSASLByteChannel(final ClientConnection c,
241      final String name, final SASLContext context)
242  {
243    return new SASLByteChannel(c, name, context);
244  }
245
246  private final String name;
247  private final ByteChannel channel;
248  private final ByteChannelImpl pimpl = new ByteChannelImpl();
249  private final SASLContext saslContext;
250
251  private ByteBuffer recvUnwrappedBuffer;
252  private final ByteBuffer recvWrappedBuffer;
253  private final int recvWrappedBufferMaximumSize;
254  private int recvWrappedLength = -1;
255  private final ByteBuffer recvWrappedLengthBuffer = ByteBuffer.allocate(4);
256
257  private final int sendUnwrappedBufferSize;
258  private final byte[] sendUnwrappedBytes;
259  private ByteBuffer sendWrappedBuffer;
260
261  private final Object readLock = new Object();
262  private final Object writeLock = new Object();
263
264  /**
265   * Create a SASL byte channel with the specified parameters that is capable of
266   * processing a confidentiality/integrity SASL connection.
267   *
268   * @param connection
269   *          The client connection to read/write the bytes.
270   * @param name
271   *          The SASL mechanism name.
272   * @param saslContext
273   *          The SASL context to process the data through.
274   */
275  private SASLByteChannel(final ClientConnection connection, final String name,
276      final SASLContext saslContext)
277  {
278    this.name = name;
279    this.saslContext = saslContext;
280
281    channel = connection.getChannel();
282    recvWrappedBufferMaximumSize = saslContext.getMaxReceiveBufferSize();
283    sendUnwrappedBufferSize = saslContext.getMaxRawSendBufferSize();
284
285    recvWrappedBuffer = ByteBuffer.allocate(recvWrappedBufferMaximumSize);
286    recvUnwrappedBuffer = ByteBuffer.allocate(0);
287    sendUnwrappedBytes = new byte[sendUnwrappedBufferSize];
288    sendWrappedBuffer = ByteBuffer.allocate(sendUnwrappedBufferSize + 64);
289  }
290
291  @Override
292  public ByteChannel getChannel()
293  {
294    return pimpl;
295  }
296
297  @Override
298  public Certificate[] getClientCertificateChain()
299  {
300    return new Certificate[0];
301  }
302
303  @Override
304  public String getName()
305  {
306    return name;
307  }
308
309  @Override
310  public int getSSF()
311  {
312    return saslContext.getSSF();
313  }
314
315  @Override
316  public boolean isSecure()
317  {
318    return true;
319  }
320}