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 }