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