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.external.certificate; 021 022 023import org.apache.commons.lang3.exception.ExceptionUtils; 024import org.apache.directory.api.ldap.model.constants.AuthenticationLevel; 025import org.apache.directory.api.ldap.model.constants.SchemaConstants; 026import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms; 027import org.apache.directory.api.ldap.model.entry.Entry; 028import org.apache.directory.api.ldap.model.entry.Value; 029import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException; 030import org.apache.directory.api.ldap.model.filter.EqualityNode; 031import org.apache.directory.api.ldap.model.message.BindRequest; 032import org.apache.directory.api.ldap.model.message.SearchScope; 033import org.apache.directory.api.util.Strings; 034import org.apache.directory.server.core.api.CoreSession; 035import org.apache.directory.server.core.api.DirectoryService; 036import org.apache.directory.server.core.api.LdapPrincipal; 037import org.apache.directory.server.core.api.OperationEnum; 038import org.apache.directory.server.core.api.OperationManager; 039import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 040import org.apache.directory.server.core.api.interceptor.context.BindOperationContext; 041import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 042import org.apache.directory.server.ldap.LdapServer; 043import org.apache.directory.server.ldap.LdapSession; 044import org.apache.directory.server.ldap.handlers.sasl.AbstractSaslServer; 045import org.apache.directory.server.ldap.handlers.sasl.SaslConstants; 046import org.apache.mina.filter.ssl.SslFilter; 047 048import javax.naming.Context; 049import javax.net.ssl.SSLSession; 050import javax.security.sasl.SaslException; 051import java.security.cert.Certificate; 052 053 054/** 055 * A SaslServer implementation for certificate based SASL EXTERNAL mechanism. 056 * 057 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 058 */ 059public final class ExternalSaslServer extends AbstractSaslServer 060{ 061 /** 062 * The possible states for the negotiation of a EXTERNAL mechanism. 063 */ 064 private enum NegotiationState 065 { 066 INITIALIZED, // Negotiation has just started 067 COMPLETED // The user/password have been received 068 } 069 070 /** The current negotiation state */ 071 private NegotiationState state; 072 073 /** 074 * 075 * Creates a new instance of ExternalSaslServer. 076 * 077 * @param ldapSession The associated LdapSession instance 078 * @param adminSession The Administrator session 079 * @param bindRequest The associated BindRequest object 080 */ 081 ExternalSaslServer( LdapSession ldapSession, CoreSession adminSession, BindRequest bindRequest ) 082 { 083 super( ldapSession, adminSession, bindRequest ); 084 state = NegotiationState.INITIALIZED; 085 } 086 087 088 /** 089 * {@inheritDoc} 090 */ 091 public String getMechanismName() 092 { 093 return SupportedSaslMechanisms.EXTERNAL; 094 } 095 096 097 /** 098 * {@inheritDoc} 099 */ 100 public byte[] evaluateResponse( byte[] initialResponse ) throws SaslException 101 { 102 try 103 { 104 SSLSession sslSession = ( SSLSession ) getLdapSession().getIoSession().getAttribute( SslFilter.SSL_SESSION ); 105 Certificate[] peerCertificates = sslSession.getPeerCertificates(); 106 107 if ( null == peerCertificates || 1 > peerCertificates.length ) 108 { 109 throw new SaslException( "No peer certificate provided - cancel bind." ); 110 } 111 112 getLdapSession().setCoreSession( authenticate( peerCertificates[0] ) ); 113 state = NegotiationState.COMPLETED; 114 } 115 catch ( Exception e ) 116 { 117 throw new SaslException( "Error authentication using client certificate: " + ExceptionUtils.getStackTrace( e ), e ); 118 } 119 120 return Strings.EMPTY_BYTES; 121 } 122 123 124 /** 125 * Provides {@code true} if negationstate is {@link NegotiationState#COMPLETED} 126 * 127 * @return {@code true} if completed, otherwise {@code false} 128 */ 129 public boolean isComplete() 130 { 131 return state == NegotiationState.COMPLETED; 132 } 133 134 135 /** 136 * Try to authenticate the user against the underlying LDAP server. 137 * We identify the user using the provided peercertificate. 138 */ 139 private CoreSession authenticate( Certificate peerCertificate ) throws Exception 140 { 141 LdapSession ldapSession = getLdapSession(); 142 CoreSession adminSession = getAdminSession(); 143 DirectoryService directoryService = adminSession.getDirectoryService(); 144 LdapServer ldapServer = ldapSession.getLdapServer(); 145 OperationManager operationManager = directoryService.getOperationManager(); 146 147 // find user by userCertificate 148 EqualityNode<String> filter = new EqualityNode<>( 149 directoryService.getSchemaManager().getAttributeType( SchemaConstants.USER_CERTIFICATE_AT ), 150 new Value( peerCertificate.getEncoded() ) ); 151 152 SearchOperationContext searchContext = new SearchOperationContext( directoryService.getAdminSession() ); 153 searchContext.setDn( directoryService.getDnFactory().create( ldapServer.getSearchBaseDn() ) ); 154 searchContext.setScope( SearchScope.SUBTREE ); 155 searchContext.setFilter( filter ); 156 searchContext.setSizeLimit( 1 ); 157 searchContext.setNoAttributes( true ); 158 159 try ( EntryFilteringCursor cursor = operationManager.search( searchContext ) ) 160 { 161 if ( cursor.next() ) 162 { 163 Entry entry = cursor.get(); 164 165 BindOperationContext bindContext = new BindOperationContext( ldapSession.getCoreSession() ); 166 bindContext.setDn( entry.getDn() ); 167 bindContext.setSaslMechanism( getMechanismName() ); 168 bindContext.setSaslAuthId( getBindRequest().getName() ); 169 bindContext.setIoSession( ldapSession.getIoSession() ); 170 bindContext.setInterceptors( directoryService.getInterceptors( OperationEnum.BIND ) ); 171 172 operationManager.bind( bindContext ); 173 174 ldapSession.putSaslProperty( SaslConstants.SASL_AUTHENT_USER, new LdapPrincipal( directoryService.getSchemaManager(), 175 entry.getDn(), AuthenticationLevel.STRONG ) ); 176 getLdapSession().putSaslProperty( Context.SECURITY_PRINCIPAL, getBindRequest().getName() ); 177 178 return bindContext.getSession(); 179 } 180 181 throw new LdapAuthenticationException( "Cannot authenticate user cert=" + peerCertificate ); 182 } 183 } 184}