View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.server.ldap.handlers.bind;
21  
22  
23  import javax.security.sasl.Sasl;
24  import javax.security.sasl.SaslException;
25  import javax.security.sasl.SaslServer;
26  
27  import org.apache.directory.shared.ldap.constants.SaslQoP;
28  import org.apache.mina.common.ByteBuffer;
29  import org.apache.mina.common.IoFilterAdapter;
30  import org.apache.mina.common.IoSession;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  
35  /**
36   * An {@link IoFilterAdapter} that handles integrity and confidentiality protection
37   * for a SASL bound session.  The SaslFilter must be constructed with a SASL
38   * context that has completed SASL negotiation.  Some SASL mechanisms, such as
39   * CRAM-MD5, only support authentication and thus do not need this filter.  DIGEST-MD5
40   * and GSSAPI do support message integrity and confidentiality and, therefore,
41   * do need this filter.
42   * 
43   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
44   * @version $Rev$, $Date$
45   */
46  public class SaslFilter extends IoFilterAdapter
47  {
48      private static final Logger log = LoggerFactory.getLogger( SaslFilter.class );
49  
50      /**
51       * A session attribute key that makes next one write request bypass
52       * this filter (not adding a security layer).  This is a marker attribute,
53       * which means that you can put whatever as its value. ({@link Boolean#TRUE}
54       * is preferred.)  The attribute is automatically removed from the session
55       * attribute map as soon as {@link IoSession#write(Object)} is invoked,
56       * and therefore should be put again if you want to make more messages
57       * bypass this filter.
58       */
59      public static final String DISABLE_SECURITY_LAYER_ONCE = SaslFilter.class.getName() + ".DisableSecurityLayerOnce";
60  
61      private SaslServer saslServer;
62  
63  
64      /**
65       * Creates a new instance of SaslFilter.  The SaslFilter must be constructed
66       * with a SASL context that has completed SASL negotiation.  The SASL context
67       * will be used to provide message integrity and, optionally, message
68       * confidentiality.
69       *
70       * @param context The initialized SASL context.
71       */
72      public SaslFilter( SaslServer saslServer )
73      {
74          if ( saslServer == null )
75          {
76              throw new IllegalStateException();
77          }
78  
79          this.saslServer = saslServer;
80      }
81  
82  
83      public void messageReceived( NextFilter nextFilter, IoSession session, Object message ) throws SaslException
84      {
85          log.debug( "Message received:  {}", message );
86  
87          /*
88           * Unwrap the data for mechanisms that support QoP (DIGEST-MD5, GSSAPI).
89           */
90          String qop = ( String ) saslServer.getNegotiatedProperty( Sasl.QOP );
91          boolean hasSecurityLayer = ( qop != null && ( qop.equals( SaslQoP.QOP_AUTH_INT ) || qop.equals( SaslQoP.QOP_AUTH_CONF ) ) );
92  
93          if ( hasSecurityLayer )
94          {
95              /*
96               * Get the buffer as bytes.  First 4 bytes are length as int.
97               */
98              ByteBuffer buf = ( ByteBuffer ) message;
99              int bufferLength = buf.getInt();
100             byte[] bufferBytes = new byte[bufferLength];
101             buf.get( bufferBytes );
102 
103             log.debug( "Will use SASL to unwrap received message of length:  {}", bufferLength );
104             byte[] token = saslServer.unwrap( bufferBytes, 0, bufferBytes.length );
105             nextFilter.messageReceived( session, ByteBuffer.wrap( token ) );
106         }
107         else
108         {
109             log.debug( "Will not use SASL on received message." );
110             nextFilter.messageReceived( session, message );
111         }
112     }
113 
114 
115     public void filterWrite( NextFilter nextFilter, IoSession session, WriteRequest writeRequest ) throws SaslException
116     {
117         log.debug( "Filtering write request:  {}", writeRequest );
118 
119         /*
120          * Check if security layer processing should be disabled once.
121          */
122         if ( session.containsAttribute( DISABLE_SECURITY_LAYER_ONCE ) )
123         {
124             // Remove the marker attribute because it is temporary.
125             log.debug( "Disabling SaslFilter once; will not use SASL on write request." );
126             session.removeAttribute( DISABLE_SECURITY_LAYER_ONCE );
127             nextFilter.filterWrite( session, writeRequest );
128             return;
129         }
130 
131         /*
132          * Wrap the data for mechanisms that support QoP (DIGEST-MD5, GSSAPI).
133          */
134         String qop = ( String ) saslServer.getNegotiatedProperty( Sasl.QOP );
135         boolean hasSecurityLayer = ( qop != null && ( qop.equals( SaslQoP.QOP_AUTH_INT ) || qop.equals( SaslQoP.QOP_AUTH_CONF ) ) );
136 
137         ByteBuffer saslLayerBuffer = null;
138 
139         if ( hasSecurityLayer )
140         {
141             /*
142              * Get the buffer as bytes.
143              */
144             ByteBuffer buf = ( ByteBuffer ) writeRequest.getMessage();
145             int bufferLength = buf.remaining();
146             byte[] bufferBytes = new byte[bufferLength];
147             buf.get( bufferBytes );
148 
149             log.debug( "Will use SASL to wrap message of length:  {}", bufferLength );
150 
151             byte[] saslLayer = saslServer.wrap( bufferBytes, 0, bufferBytes.length );
152 
153             /*
154              * Prepend 4 byte length.
155              */
156             saslLayerBuffer = ByteBuffer.allocate( 4 + saslLayer.length );
157             saslLayerBuffer.putInt( saslLayer.length );
158             saslLayerBuffer.put( saslLayer );
159             saslLayerBuffer.position( 0 );
160             saslLayerBuffer.limit( 4 + saslLayer.length );
161 
162             log.debug( "Sending encrypted token of length {}.", saslLayerBuffer.limit() );
163             nextFilter.filterWrite( session, new WriteRequest( saslLayerBuffer, writeRequest.getFuture() ) );
164         }
165         else
166         {
167             log.debug( "Will not use SASL on write request." );
168             nextFilter.filterWrite( session, writeRequest );
169         }
170     }
171 }