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.server.ldap.handlers.sasl;
021
022
023import javax.security.sasl.Sasl;
024import javax.security.sasl.SaslException;
025import javax.security.sasl.SaslServer;
026
027import org.apache.directory.api.ldap.model.constants.SaslQoP;
028import org.apache.mina.core.buffer.IoBuffer;
029import org.apache.mina.core.filterchain.IoFilterAdapter;
030import org.apache.mina.core.session.IoSession;
031import org.apache.mina.core.write.DefaultWriteRequest;
032import org.apache.mina.core.write.WriteRequest;
033import org.slf4j.Logger;
034import 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 */
047public class SaslFilter extends IoFilterAdapter
048{
049    private static final Logger LOG = LoggerFactory.getLogger( SaslFilter.class );
050
051    /**
052     * A session attribute key that makes next one write request bypass
053     * this filter (not adding a security layer).  This is a marker attribute,
054     * which means that you can put whatever as its value. ({@link Boolean#TRUE}
055     * is preferred.)  The attribute is automatically removed from the session
056     * attribute map as soon as {@link IoSession#write(Object)} is invoked,
057     * and therefore should be put again if you want to make more messages
058     * bypass this filter.
059     */
060    public static final String DISABLE_SECURITY_LAYER_ONCE = SaslFilter.class.getName() + ".DisableSecurityLayerOnce";
061
062    private SaslServer saslServer;
063
064
065    /**
066     * Creates a new instance of SaslFilter.  The SaslFilter must be constructed
067     * with a SASL context that has completed SASL negotiation.  The SASL context
068     * will be used to provide message integrity and, optionally, message
069     * confidentiality.
070     *
071     * @param saslServer The initialized SASL context.
072     */
073    public SaslFilter( SaslServer saslServer )
074    {
075        if ( saslServer == null )
076        {
077            throw new IllegalStateException();
078        }
079
080        this.saslServer = saslServer;
081    }
082
083
084    @Override
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.AUTH_INT.getValue() ) || qop
094            .equals( SaslQoP.AUTH_CONF.getValue() ) ) );
095
096        if ( hasSecurityLayer )
097        {
098            /*
099             * Get the buffer as bytes.  First 4 bytes are length as int.
100             */
101            IoBuffer buf = ( IoBuffer ) message;
102            int bufferLength = buf.getInt();
103            byte[] bufferBytes = new byte[bufferLength];
104            buf.get( bufferBytes );
105
106            LOG.debug( "Will use SASL to unwrap received message of length:  {}", bufferLength );
107            byte[] token = saslServer.unwrap( bufferBytes, 0, bufferBytes.length );
108            nextFilter.messageReceived( session, IoBuffer.wrap( token ) );
109        }
110        else
111        {
112            LOG.debug( "Will not use SASL on received message." );
113            nextFilter.messageReceived( session, message );
114        }
115    }
116
117
118    @Override
119    public void filterWrite( NextFilter nextFilter, IoSession session, WriteRequest writeRequest ) throws SaslException
120    {
121        LOG.debug( "Filtering write request:  {}", writeRequest );
122
123        /*
124         * Check if security layer processing should be disabled once.
125         */
126        if ( session.containsAttribute( DISABLE_SECURITY_LAYER_ONCE ) )
127        {
128            // Remove the marker attribute because it is temporary.
129            LOG.debug( "Disabling SaslFilter once; will not use SASL on write request." );
130            session.removeAttribute( DISABLE_SECURITY_LAYER_ONCE );
131            nextFilter.filterWrite( session, writeRequest );
132            return;
133        }
134
135        /*
136         * Wrap the data for mechanisms that support QoP (DIGEST-MD5, GSSAPI).
137         */
138        String qop = ( String ) saslServer.getNegotiatedProperty( Sasl.QOP );
139        boolean hasSecurityLayer = ( qop != null && ( qop.equals( SaslQoP.AUTH_INT.getValue() ) || qop
140            .equals( SaslQoP.AUTH_CONF.getValue() ) ) );
141
142        IoBuffer saslLayerBuffer = null;
143
144        if ( hasSecurityLayer )
145        {
146            /*
147             * Get the buffer as bytes.
148             */
149            IoBuffer buf = ( IoBuffer ) writeRequest.getMessage();
150            int bufferLength = buf.remaining();
151            byte[] bufferBytes = new byte[bufferLength];
152            buf.get( bufferBytes );
153
154            LOG.debug( "Will use SASL to wrap message of length:  {}", bufferLength );
155
156            byte[] saslLayer = saslServer.wrap( bufferBytes, 0, bufferBytes.length );
157
158            /*
159             * Prepend 4 byte length.
160             */
161            saslLayerBuffer = IoBuffer.allocate( 4 + saslLayer.length );
162            saslLayerBuffer.putInt( saslLayer.length );
163            saslLayerBuffer.put( saslLayer );
164            saslLayerBuffer.position( 0 );
165            saslLayerBuffer.limit( 4 + saslLayer.length );
166
167            LOG.debug( "Sending encrypted token of length {}.", saslLayerBuffer.limit() );
168            nextFilter.filterWrite( session, new DefaultWriteRequest( saslLayerBuffer, writeRequest.getFuture() ) );
169        }
170        else
171        {
172            LOG.debug( "Will not use SASL on write request." );
173            nextFilter.filterWrite( session, writeRequest );
174        }
175    }
176}