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