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 */
020 package org.apache.directory.server.ldap.handlers.bind;
021
022
023 import javax.security.sasl.Sasl;
024 import javax.security.sasl.SaslException;
025 import javax.security.sasl.SaslServer;
026
027 import org.apache.directory.shared.ldap.constants.SaslQoP;
028 import org.apache.mina.core.buffer.IoBuffer;
029 import org.apache.mina.core.filterchain.IoFilterAdapter;
030 import org.apache.mina.core.session.IoSession;
031 import org.apache.mina.core.write.DefaultWriteRequest;
032 import org.apache.mina.core.write.WriteRequest;
033 import org.slf4j.Logger;
034 import org.slf4j.LoggerFactory;
035
036
037 /**
038 * An {@link IoFilterAdapter} that handles integrity and confidentiality protection
039 * for a SASL bound session. The SaslFilter must be constructed with a SASL
040 * context that has completed SASL negotiation. Some SASL mechanisms, such as
041 * CRAM-MD5, only support authentication and thus do not need this filter. DIGEST-MD5
042 * and GSSAPI do support message integrity and confidentiality and, therefore,
043 * do need this filter.
044 *
045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046 * @version $Rev$, $Date$
047 */
048 public 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 private SaslServer saslServer;
064
065
066 /**
067 * Creates a new instance of SaslFilter. The SaslFilter must be constructed
068 * with a SASL context that has completed SASL negotiation. The SASL context
069 * will be used to provide message integrity and, optionally, message
070 * confidentiality.
071 *
072 * @param context The initialized SASL context.
073 */
074 public SaslFilter( SaslServer saslServer )
075 {
076 if ( saslServer == null )
077 {
078 throw new IllegalStateException();
079 }
080
081 this.saslServer = saslServer;
082 }
083
084
085 public void messageReceived( NextFilter nextFilter, IoSession session, Object message ) throws SaslException
086 {
087 log.debug( "Message received: {}", message );
088
089 /*
090 * Unwrap the data for mechanisms that support QoP (DIGEST-MD5, GSSAPI).
091 */
092 String qop = ( String ) saslServer.getNegotiatedProperty( Sasl.QOP );
093 boolean hasSecurityLayer = ( qop != null && ( qop.equals( SaslQoP.QOP_AUTH_INT ) || qop.equals( SaslQoP.QOP_AUTH_CONF ) ) );
094
095 if ( hasSecurityLayer )
096 {
097 /*
098 * Get the buffer as bytes. First 4 bytes are length as int.
099 */
100 IoBuffer buf = ( IoBuffer ) message;
101 int bufferLength = buf.getInt();
102 byte[] bufferBytes = new byte[bufferLength];
103 buf.get( bufferBytes );
104
105 log.debug( "Will use SASL to unwrap received message of length: {}", bufferLength );
106 byte[] token = saslServer.unwrap( bufferBytes, 0, bufferBytes.length );
107 nextFilter.messageReceived( session, IoBuffer.wrap( token ) );
108 }
109 else
110 {
111 log.debug( "Will not use SASL on received message." );
112 nextFilter.messageReceived( session, message );
113 }
114 }
115
116
117 public void filterWrite( NextFilter nextFilter, IoSession session, WriteRequest writeRequest ) throws SaslException
118 {
119 log.debug( "Filtering write request: {}", writeRequest );
120
121 /*
122 * Check if security layer processing should be disabled once.
123 */
124 if ( session.containsAttribute( DISABLE_SECURITY_LAYER_ONCE ) )
125 {
126 // Remove the marker attribute because it is temporary.
127 log.debug( "Disabling SaslFilter once; will not use SASL on write request." );
128 session.removeAttribute( DISABLE_SECURITY_LAYER_ONCE );
129 nextFilter.filterWrite( session, writeRequest );
130 return;
131 }
132
133 /*
134 * Wrap the data for mechanisms that support QoP (DIGEST-MD5, GSSAPI).
135 */
136 String qop = ( String ) saslServer.getNegotiatedProperty( Sasl.QOP );
137 boolean hasSecurityLayer = ( qop != null && ( qop.equals( SaslQoP.QOP_AUTH_INT ) || qop.equals( SaslQoP.QOP_AUTH_CONF ) ) );
138
139 IoBuffer saslLayerBuffer = null;
140
141 if ( hasSecurityLayer )
142 {
143 /*
144 * Get the buffer as bytes.
145 */
146 IoBuffer buf = ( IoBuffer ) writeRequest.getMessage();
147 int bufferLength = buf.remaining();
148 byte[] bufferBytes = new byte[bufferLength];
149 buf.get( bufferBytes );
150
151 log.debug( "Will use SASL to wrap message of length: {}", bufferLength );
152
153 byte[] saslLayer = saslServer.wrap( bufferBytes, 0, bufferBytes.length );
154
155 /*
156 * Prepend 4 byte length.
157 */
158 saslLayerBuffer = IoBuffer.allocate( 4 + saslLayer.length );
159 saslLayerBuffer.putInt( saslLayer.length );
160 saslLayerBuffer.put( saslLayer );
161 saslLayerBuffer.position( 0 );
162 saslLayerBuffer.limit( 4 + saslLayer.length );
163
164 log.debug( "Sending encrypted token of length {}.", saslLayerBuffer.limit() );
165 nextFilter.filterWrite( session, new DefaultWriteRequest( saslLayerBuffer, writeRequest.getFuture() ) );
166 }
167 else
168 {
169 log.debug( "Will not use SASL on write request." );
170 nextFilter.filterWrite( session, writeRequest );
171 }
172 }
173 }