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