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.nio.channels.ClosedChannelException;
023import java.security.cert.Certificate;
024import java.util.Collections;
025import java.util.LinkedHashMap;
026import java.util.Map;
027
028import javax.net.ssl.SSLEngine;
029import javax.net.ssl.SSLEngineResult;
030import javax.net.ssl.SSLEngineResult.HandshakeStatus;
031import javax.net.ssl.SSLException;
032import javax.net.ssl.SSLPeerUnverifiedException;
033import javax.net.ssl.SSLSession;
034
035import org.forgerock.i18n.slf4j.LocalizedLogger;
036
037/** A class that provides a TLS byte channel implementation. */
038public final class TLSByteChannel implements ConnectionSecurityProvider
039{
040  /** Private implementation. */
041  private final class ByteChannelImpl implements ByteChannel
042  {
043    @Override
044    public void close() throws IOException
045    {
046      synchronized (readLock)
047      {
048        synchronized (writeLock)
049        {
050          final boolean isInitiator = !sslEngine.isInboundDone();
051
052          try
053          {
054            if (!sslEngine.isOutboundDone())
055            {
056              sslEngine.closeOutbound();
057              while (doWrapAndSend(EMPTY_BUFFER) > 0)
058              {
059                // Write out any remaining SSL close notifications.
060              }
061            }
062          }
063          catch (final ClosedChannelException e)
064          {
065            // Ignore this so that close is idempotent.
066          }
067          finally
068          {
069            try
070            {
071              sslEngine.closeInbound();
072            }
073            catch (final SSLException e)
074            {
075              // Not yet received peer's close notification. Ignore this if we
076              // are the initiator.
077              if (!isInitiator)
078              {
079                throw e;
080              }
081            }
082            finally
083            {
084              channel.close();
085            }
086          }
087        }
088      }
089    }
090
091    @Override
092    public boolean isOpen()
093    {
094      return !sslEngine.isOutboundDone() || !sslEngine.isInboundDone();
095    }
096
097    @Override
098    public int read(final ByteBuffer unwrappedData) throws IOException
099    {
100      synchronized (readLock)
101      {
102        // Only read and unwrap new data if needed.
103        if (!recvUnwrappedBuffer.hasRemaining())
104        {
105          final int read = doRecvAndUnwrap();
106          if (read <= 0)
107          {
108            // No data read or end of stream.
109            return read;
110          }
111        }
112
113        // Copy available data.
114        final int startPos = unwrappedData.position();
115        if (recvUnwrappedBuffer.remaining() > unwrappedData.remaining())
116        {
117          // Unwrapped data does not fit in client buffer so copy one byte at a
118          // time: it's annoying that there is no easy way to do this with
119          // ByteBuffers.
120          while (unwrappedData.hasRemaining())
121          {
122            unwrappedData.put(recvUnwrappedBuffer.get());
123          }
124        }
125        else
126        {
127          // Unwrapped data fits client buffer so block copy.
128          unwrappedData.put(recvUnwrappedBuffer);
129        }
130        return unwrappedData.position() - startPos;
131      }
132    }
133
134    @Override
135    public int write(final ByteBuffer unwrappedData) throws IOException
136    {
137      // This method will block until the entire message is sent.
138      final int bytesWritten = unwrappedData.remaining();
139
140      // Synchronized in order to prevent interleaving and reordering.
141      synchronized (writeLock)
142      {
143        // Repeat until the entire input data is written.
144        while (unwrappedData.hasRemaining())
145        {
146          // Wrap and send the data.
147          doWrapAndSend(unwrappedData);
148
149          // Perform handshake if needed.
150          if (isHandshaking(sslEngine.getHandshakeStatus()))
151          {
152            doHandshake(false /* isReading */);
153          }
154        }
155      }
156
157      return bytesWritten;
158    }
159
160    /**
161     * It seems that the SSL engine does not remember if an error has already
162     * occurred so we must cache it here and rethrow. See OPENDJ-652.
163     */
164    private void abortOnSSLException() throws IOException
165    {
166      if (sslException != null)
167      {
168        throw sslException;
169      }
170    }
171
172    private void doHandshake(final boolean isReading) throws IOException
173    {
174      // This lock is probably unnecessary since tasks can be run in parallel,
175      // but it adds no additional overhead so there's little harm in having
176      // it.
177      synchronized (handshakeLock)
178      {
179        while (true)
180        {
181          switch (sslEngine.getHandshakeStatus())
182          {
183          case NEED_TASK:
184            Runnable runnable;
185            while ((runnable = sslEngine.getDelegatedTask()) != null)
186            {
187              runnable.run();
188            }
189            break;
190          case NEED_UNWRAP:
191            // Block for writes, but be non-blocking for reads.
192            if (isReading)
193            {
194              // Let doRecvAndUnwrap() deal with this.
195              return;
196            }
197
198            // Need to do an unwrap (read) while writing.
199            if (doRecvAndUnwrap() < 0)
200            {
201              throw new ClosedChannelException();
202            }
203            break;
204          case NEED_WRAP:
205            doWrapAndSend(EMPTY_BUFFER);
206            break;
207          default: // NOT_HANDSHAKING, FINISHED.
208            return;
209          }
210        }
211      }
212    }
213
214    /** Attempt to read and unwrap the next SSL packet. */
215    private int doRecvAndUnwrap() throws IOException
216    {
217      // Synchronize SSL unwrap with channel reads.
218      synchronized (unwrapLock)
219      {
220        // Read SSL packets until some unwrapped data is produced or no more
221        // data is available on the underlying channel.
222        while (true)
223        {
224          // Unwrap any remaining data in the buffer.
225          abortOnSSLException();
226          recvUnwrappedBuffer.compact(); // Prepare for append.
227          final SSLEngineResult result;
228          try
229          {
230            result = sslEngine.unwrap(recvWrappedBuffer, recvUnwrappedBuffer);
231          }
232          catch (final SSLException e)
233          {
234            // Save the error - see abortOnSSLException().
235            sslException = e;
236            throw e;
237          }
238          finally
239          {
240            recvUnwrappedBuffer.flip(); // Restore for read.
241          }
242
243          switch (result.getStatus())
244          {
245          case BUFFER_OVERFLOW:
246            // The unwrapped buffer is not big enough: resize and repeat.
247            final int newAppSize = sslEngine.getSession()
248                .getApplicationBufferSize();
249            final ByteBuffer newRecvUnwrappedBuffer = ByteBuffer
250                .allocate(recvUnwrappedBuffer.limit() + newAppSize);
251            newRecvUnwrappedBuffer.put(recvUnwrappedBuffer);
252            newRecvUnwrappedBuffer.flip();
253            recvUnwrappedBuffer = newRecvUnwrappedBuffer;
254            break; // Retry unwrap.
255          case BUFFER_UNDERFLOW:
256            // Not enough data was read. This either means that the inbound
257            // buffer was too small, or not enough data was read.
258            final int newPktSize = sslEngine.getSession().getPacketBufferSize();
259            if (newPktSize > recvWrappedBuffer.capacity())
260            {
261              // Increase the buffer size.
262              final ByteBuffer newRecvWrappedBuffer = ByteBuffer
263                  .allocate(newPktSize);
264              newRecvWrappedBuffer.put(recvWrappedBuffer);
265              newRecvWrappedBuffer.flip();
266              recvWrappedBuffer = newRecvWrappedBuffer;
267            }
268            // Read wrapped data from underlying channel.
269            recvWrappedBuffer.compact(); // Prepare for append.
270            final int read = channel.read(recvWrappedBuffer);
271            recvWrappedBuffer.flip(); // Restore for read.
272            if (read <= 0)
273            {
274              // Not enough data is available to read a complete SSL packet, or
275              // channel closed.
276              return read;
277            }
278            // Loop and unwrap.
279            break;
280          case CLOSED:
281            // Peer sent SSL close notification.
282            return -1;
283          default: // OK
284            if (recvUnwrappedBuffer.hasRemaining())
285            {
286              // Some application data was read so return it.
287              return recvUnwrappedBuffer.remaining();
288            }
289            else if (isHandshaking(result.getHandshakeStatus()))
290            {
291              // No application data was read, but if we are handshaking then
292              // try to continue.
293              doHandshake(true /* isReading */);
294            }
295            break;
296          }
297        }
298      }
299    }
300
301    /** Attempt to wrap and send the next SSL packet. */
302    private int doWrapAndSend(final ByteBuffer unwrappedData)
303        throws IOException
304    {
305      // Synchronize SSL wrap with channel writes.
306      synchronized (wrapLock)
307      {
308        // Repeat while there is overflow.
309        while (true)
310        {
311          abortOnSSLException();
312          final SSLEngineResult result;
313          try
314          {
315            result = sslEngine.wrap(unwrappedData, sendWrappedBuffer);
316          }
317          catch (SSLException e)
318          {
319            // Save the error - see abortOnSSLException().
320            sslException = e;
321            throw e;
322          }
323
324          switch (result.getStatus())
325          {
326          case BUFFER_OVERFLOW:
327            // The wrapped buffer is not big enough: resize and repeat.
328            final int newSize = sslEngine.getSession().getPacketBufferSize();
329            final ByteBuffer newSendWrappedBuffer = ByteBuffer
330                .allocate(sendWrappedBuffer.position() + newSize);
331            sendWrappedBuffer.flip();
332            newSendWrappedBuffer.put(sendWrappedBuffer);
333            sendWrappedBuffer = newSendWrappedBuffer;
334            break; // Retry.
335          case BUFFER_UNDERFLOW:
336            // This should not happen for sends.
337            sslException =
338              new SSLException("Got unexpected underflow while wrapping");
339            throw sslException;
340          case CLOSED:
341            throw new ClosedChannelException();
342          default: // OK
343            // Write the SSL packet: our IO stack will block until all the
344            // data is written.
345            sendWrappedBuffer.flip();
346            while (sendWrappedBuffer.hasRemaining())
347            {
348              channel.write(sendWrappedBuffer);
349            }
350            final int written = sendWrappedBuffer.position();
351            sendWrappedBuffer.clear();
352            return written;
353          }
354        }
355      }
356    }
357
358    private boolean isHandshaking(final HandshakeStatus status)
359    {
360      return status != HandshakeStatus.NOT_HANDSHAKING;
361    }
362  }
363
364  /**
365   * Map of cipher phrases to effective key size (bits). Taken from the
366   * following RFCs: 5289, 4346, 3268,4132 and 4162 and the IANA Transport Layer
367   * Security (TLS) Parameters.
368   *
369   * @see <a
370   * href="http://www.iana.org/assignments/tls-parameters/tls-parameters.xml">
371   * Transport Layer Security (TLS) Parameters, TLS Cipher Suite Registry</a>
372   */
373  static final Map<String, Integer> CIPHER_MAP;
374  static
375  {
376    final Map<String, Integer> map = new LinkedHashMap<>();
377    map.put("_WITH_AES_256_", 256);
378    map.put("_WITH_ARIA_256_", 256);
379    map.put("_WITH_CAMELLIA_256_", 256);
380    map.put("_WITH_AES_128_", 128);
381    map.put("_WITH_ARIA_128_", 128);
382    map.put("_WITH_SEED_", 128);
383    map.put("_WITH_CAMELLIA_128_", 128);
384    map.put("_WITH_IDEA_", 128);
385    map.put("_WITH_RC4_128_", 128);
386    map.put("_WITH_3DES_EDE_", 112);
387    map.put("_WITH_FORTEZZA_", 96);
388    map.put("_WITH_RC4_56_", 56);
389    map.put("_WITH_DES_CBC_40_", 40);
390    map.put("_WITH_RC2_CBC_40_", 40);
391    map.put("_WITH_RC4_40_", 40);
392    map.put("_WITH_DES40_", 40);
393    map.put("_WITH_DES_", 56);
394    map.put("_WITH_NULL_", 0);
395    CIPHER_MAP = Collections.unmodifiableMap(map);
396  }
397
398  private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
399  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
400
401  private final ByteChannelImpl pimpl = new ByteChannelImpl();
402  private final ByteChannel channel;
403  private final SSLEngine sslEngine;
404
405  private volatile SSLException sslException;
406  private ByteBuffer recvWrappedBuffer;
407  private ByteBuffer recvUnwrappedBuffer;
408  private ByteBuffer sendWrappedBuffer;
409
410  private final Object handshakeLock = new Object();
411  private final Object unwrapLock = new Object();
412  private final Object wrapLock = new Object();
413  private final Object readLock = new Object();
414  private final Object writeLock = new Object();
415
416  /**
417   * Creates an TLS byte channel instance using the specified LDAP connection
418   * configuration, client connection, SSL context and socket channel
419   * parameters.
420   *
421   * @param channel
422   *          The underlying channel.
423   * @param sslEngine
424   *          The SSL engine to use.
425   */
426  public TLSByteChannel(final ByteChannel channel, final SSLEngine sslEngine)
427  {
428    this.channel = channel;
429    this.sslEngine = sslEngine;
430
431    // Allocate read/write buffers.
432    final SSLSession session = sslEngine.getSession();
433    final int wrappedBufferSize = session.getPacketBufferSize();
434    final int unwrappedBufferSize = session.getApplicationBufferSize();
435
436    sendWrappedBuffer = ByteBuffer.allocate(wrappedBufferSize);
437    recvWrappedBuffer = ByteBuffer.allocate(wrappedBufferSize);
438    recvUnwrappedBuffer = ByteBuffer.allocate(unwrappedBufferSize);
439
440    // Initially nothing has been received.
441    recvWrappedBuffer.flip();
442    recvUnwrappedBuffer.flip();
443  }
444
445  @Override
446  public ByteChannel getChannel()
447  {
448    return pimpl;
449  }
450
451  @Override
452  public Certificate[] getClientCertificateChain()
453  {
454    try
455    {
456      return sslEngine.getSession().getPeerCertificates();
457    }
458    catch (final SSLPeerUnverifiedException e)
459    {
460      logger.traceException(e);
461      return new Certificate[0];
462    }
463  }
464
465  @Override
466  public String getName()
467  {
468    return "TLS";
469  }
470
471  @Override
472  public int getSSF()
473  {
474    final Integer ssf = getSSF(sslEngine.getSession().getCipherSuite());
475    if (ssf != null)
476    {
477      return ssf.intValue();
478    }
479    return 0;
480  }
481
482  /**
483   * Returns the Security Strength Factor corresponding to the supplied cipher
484   * string.
485   *
486   * @param cipherString
487   *          the cipher to test for SSF
488   * @return the Security Strength Factor corresponding to the supplied cipher
489   *         string, null if the cipher cannot be recognized.
490   */
491  static Integer getSSF(final String cipherString)
492  {
493    for (final Map.Entry<String, Integer> mapEntry : CIPHER_MAP.entrySet())
494    {
495      if (cipherString.contains(mapEntry.getKey()))
496      {
497        return mapEntry.getValue();
498      }
499    }
500    return null;
501  }
502
503  @Override
504  public boolean isSecure()
505  {
506    return true;
507  }
508}