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}