001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.api.ldap.codec.api; 021 022 023import javax.security.sasl.Sasl; 024import javax.security.sasl.SaslClient; 025import javax.security.sasl.SaslException; 026import javax.security.sasl.SaslServer; 027 028import org.apache.directory.api.ldap.model.constants.SaslQoP; 029import org.apache.mina.core.buffer.IoBuffer; 030import org.apache.mina.core.filterchain.IoFilterAdapter; 031import org.apache.mina.core.session.IoSession; 032import org.apache.mina.core.write.DefaultWriteRequest; 033import org.apache.mina.core.write.WriteRequest; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037 038/** 039 * An {@link IoFilterAdapter} that handles integrity and confidentiality protection 040 * for a SASL bound session. The SaslFilter must be constructed with a SASL 041 * context that has completed SASL negotiation. Some SASL mechanisms, such as 042 * CRAM-MD5, only support authentication and thus do not need this filter. DIGEST-MD5 043 * and GSSAPI do support message integrity and confidentiality and, therefore, 044 * do need this filter. 045 * 046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 047 */ 048public class SaslFilter extends IoFilterAdapter 049{ 050 private static final Logger LOG = LoggerFactory.getLogger( SaslFilter.class ); 051 052 /** 053 * A session attribute key that makes next one write request bypass 054 * this filter (not adding a security layer). This is a marker attribute, 055 * which means that you can put whatever as its value. ({@link Boolean#TRUE} 056 * is preferred.) The attribute is automatically removed from the session 057 * attribute map as soon as {@link IoSession#write(Object)} is invoked, 058 * and therefore should be put again if you want to make more messages 059 * bypass this filter. 060 */ 061 public static final String DISABLE_SECURITY_LAYER_ONCE = SaslFilter.class.getName() + ".DisableSecurityLayerOnce"; 062 063 /** 064 * A session attribute key that holds the received bytes of partially received 065 * SASL message. 066 */ 067 public static final String BYTES = SaslFilter.class.getName() + ".Buffer"; 068 069 /** 070 * A session attribute key that holds the offset of partially received 071 * SASL message. 072 */ 073 public static final String OFFSET = SaslFilter.class.getName() + ".Offset"; 074 075 /** The SASL client, only set if the filter is used at the client side. */ 076 private final SaslClient saslClient; 077 078 /** The SASL server, only set if the filter is used at the server side. */ 079 private final SaslServer saslServer; 080 081 /** True if a security layer has been negotiated */ 082 private boolean hasSecurityLayer; 083 084 /** The negotiated max buffer size */ 085 private int maxBufferSize; 086 087 /** 088 * Creates a new instance of SaslFilter. The SaslFilter must be constructed 089 * with a SASL client that has completed SASL negotiation. The SASL client 090 * will be used to provide message integrity and, optionally, message 091 * confidentiality. 092 * 093 * @param saslClient The initialized SASL client. 094 */ 095 public SaslFilter( SaslClient saslClient ) 096 { 097 if ( saslClient == null ) 098 { 099 throw new IllegalArgumentException(); 100 } 101 102 this.saslServer = null; 103 this.saslClient = saslClient; 104 initHasSecurityLayer( ( String ) saslClient.getNegotiatedProperty( Sasl.QOP ) ); 105 initMaxBuffer( ( String ) saslClient.getNegotiatedProperty( Sasl.MAX_BUFFER ) ); 106 } 107 108 109 /** 110 * Creates a new instance of SaslFilter. The SaslFilter must be constructed 111 * with a SASL server that has completed SASL negotiation. The SASL server 112 * will be used to provide message integrity and, optionally, message 113 * confidentiality. 114 * 115 * @param saslClient The initialized SASL server. 116 */ 117 public SaslFilter( SaslServer saslServer ) 118 { 119 if ( saslServer == null ) 120 { 121 throw new IllegalArgumentException(); 122 } 123 124 this.saslClient = null; 125 this.saslServer = saslServer; 126 initHasSecurityLayer( ( String ) saslServer.getNegotiatedProperty( Sasl.QOP ) ); 127 initMaxBuffer( ( String ) saslServer.getNegotiatedProperty( Sasl.MAX_BUFFER ) ); 128 } 129 130 131 private void initHasSecurityLayer( String qop ) 132 { 133 this.hasSecurityLayer = ( qop != null && ( qop.equals( SaslQoP.AUTH_INT.getValue() ) || qop 134 .equals( SaslQoP.AUTH_CONF.getValue() ) ) ); 135 } 136 137 138 private void initMaxBuffer( String maxBuffer ) 139 { 140 this.maxBufferSize = maxBuffer != null ? Integer.parseInt( maxBuffer ) : 65536; 141 } 142 143 144 @Override 145 public synchronized void messageReceived( NextFilter nextFilter, IoSession session, Object message ) 146 throws SaslException 147 { 148 LOG.debug( "Message received: {}", message ); 149 150 if ( !hasSecurityLayer ) 151 { 152 LOG.debug( "Will not use SASL on received message." ); 153 nextFilter.messageReceived( session, message ); 154 return; 155 } 156 157 /* 158 * Unwrap the data for mechanisms that support QoP (DIGEST-MD5, GSSAPI). 159 */ 160 IoBuffer buf = ( IoBuffer ) message; 161 while ( buf.hasRemaining() ) 162 { 163 /* 164 * Check for a previously received partial SASL message which is stored in the session. 165 * Otherwise read the first 4 bytes which is the length and allocate the bytes. 166 * Ensure the buffer size doesn't exceed the negotiated max buffer size. 167 */ 168 byte[] bytes = ( byte[] ) session.getAttribute( BYTES, null ); 169 int offset = ( int ) session.getAttribute( OFFSET, -1 ); 170 if ( bytes == null ) 171 { 172 int bufferSize = buf.getInt(); 173 if ( bufferSize > maxBufferSize ) 174 { 175 throw new IllegalStateException( 176 bufferSize + " exceeds the negotiated receive buffer size limit: " + maxBufferSize ); 177 } 178 bytes = new byte[bufferSize]; 179 offset = 0; 180 } 181 182 /* 183 * Get the buffer as bytes. Handle the case that only a part of the SASL message was received. 184 */ 185 int length = Math.min( bytes.length - offset, buf.remaining() ); 186 buf.get( bytes, offset, length ); 187 188 /* 189 * Check if the full SASL message was received. If not store the partially received data in 190 * the session so it can be resumed when the next message is received. 191 */ 192 offset += length; 193 if ( offset < bytes.length ) 194 { 195 LOG.debug( "Partial SASL message received: {}/{}", offset, bytes.length ); 196 session.setAttribute( BYTES, bytes ); 197 session.setAttribute( OFFSET, offset ); 198 break; 199 } 200 201 /* 202 * Unwrap the SASL message and forward it to the next filter. 203 */ 204 LOG.debug( "Will use SASL to unwrap received message of length: {}", bytes.length ); 205 byte[] token = unwrap( bytes, 0, bytes.length ); 206 nextFilter.messageReceived( session, IoBuffer.wrap( token ) ); 207 208 /* 209 * Finally clear the session attributes. 210 */ 211 session.removeAttribute( BYTES ); 212 session.removeAttribute( OFFSET ); 213 } 214 } 215 216 217 @Override 218 public synchronized void filterWrite( NextFilter nextFilter, IoSession session, WriteRequest writeRequest ) 219 throws SaslException 220 { 221 LOG.debug( "Filtering write request: {}", writeRequest ); 222 223 /* 224 * Check if security layer processing should be disabled once. 225 */ 226 if ( session.containsAttribute( DISABLE_SECURITY_LAYER_ONCE ) ) 227 { 228 // Remove the marker attribute because it is temporary. 229 LOG.debug( "Disabling SaslFilter once; will not use SASL on write request." ); 230 session.removeAttribute( DISABLE_SECURITY_LAYER_ONCE ); 231 nextFilter.filterWrite( session, writeRequest ); 232 return; 233 } 234 235 if ( !hasSecurityLayer ) 236 { 237 LOG.debug( "Will not use SASL on write request." ); 238 nextFilter.filterWrite( session, writeRequest ); 239 return; 240 } 241 242 /* 243 * Wrap the data for mechanisms that support QoP (DIGEST-MD5, GSSAPI). 244 */ 245 246 /* 247 * Get the buffer as bytes. 248 */ 249 IoBuffer buf = ( IoBuffer ) writeRequest.getMessage(); 250 int bufferLength = buf.remaining(); 251 byte[] bufferBytes = new byte[bufferLength]; 252 buf.get( bufferBytes ); 253 254 LOG.info( "Will use SASL to wrap message of length: {}", bufferLength ); 255 256 /* 257 * Ensure to not send larger SASL message than negotiated. 258 */ 259 int max = maxBufferSize - 200; 260 for ( int offset = 0; offset < bufferLength; offset += max ) 261 { 262 int length = Math.min( bufferLength - offset, max ); 263 byte[] saslLayer = wrap( bufferBytes, offset, length ); 264 265 /* 266 * Prepend 4 byte length. 267 */ 268 IoBuffer saslLayerBuffer = IoBuffer.allocate( 4 + saslLayer.length ); 269 saslLayerBuffer.putInt( saslLayer.length ); 270 saslLayerBuffer.put( saslLayer ); 271 saslLayerBuffer.position( 0 ); 272 saslLayerBuffer.limit( 4 + saslLayer.length ); 273 274 LOG.debug( "Sending encrypted token of length {}.", saslLayerBuffer.limit() ); 275 nextFilter.filterWrite( session, new DefaultWriteRequest( saslLayerBuffer, writeRequest.getFuture() ) ); 276 } 277 } 278 279 280 private byte[] wrap( byte[] buffer, int offset, int length ) throws SaslException 281 { 282 if ( saslClient != null ) 283 { 284 return saslClient.wrap( buffer, offset, length ); 285 } 286 else 287 { 288 return saslServer.wrap( buffer, offset, length ); 289 } 290 } 291 292 293 private byte[] unwrap( byte[] buffer, int offset, int length ) throws SaslException 294 { 295 if ( saslClient != null ) 296 { 297 return saslClient.unwrap( buffer, offset, length ); 298 } 299 else 300 { 301 return saslServer.unwrap( buffer, offset, length ); 302 } 303 } 304 305}