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}