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