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}