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.sasl.external.certificate;
21  
22  
23  import org.apache.commons.lang3.exception.ExceptionUtils;
24  import org.apache.directory.api.ldap.model.constants.AuthenticationLevel;
25  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
26  import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
27  import org.apache.directory.api.ldap.model.entry.Entry;
28  import org.apache.directory.api.ldap.model.entry.Value;
29  import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
30  import org.apache.directory.api.ldap.model.filter.EqualityNode;
31  import org.apache.directory.api.ldap.model.message.BindRequest;
32  import org.apache.directory.api.ldap.model.message.SearchScope;
33  import org.apache.directory.api.util.Strings;
34  import org.apache.directory.server.core.api.CoreSession;
35  import org.apache.directory.server.core.api.DirectoryService;
36  import org.apache.directory.server.core.api.LdapPrincipal;
37  import org.apache.directory.server.core.api.OperationEnum;
38  import org.apache.directory.server.core.api.OperationManager;
39  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
40  import org.apache.directory.server.core.api.interceptor.context.BindOperationContext;
41  import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
42  import org.apache.directory.server.ldap.LdapServer;
43  import org.apache.directory.server.ldap.LdapSession;
44  import org.apache.directory.server.ldap.handlers.sasl.AbstractSaslServer;
45  import org.apache.directory.server.ldap.handlers.sasl.SaslConstants;
46  import org.apache.mina.filter.ssl.SslFilter;
47  
48  import javax.naming.Context;
49  import javax.net.ssl.SSLSession;
50  import javax.security.sasl.SaslException;
51  import java.security.cert.Certificate;
52  
53  
54  /**
55   * A SaslServer implementation for certificate based SASL EXTERNAL mechanism.
56   *
57   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
58   */
59  public final class ExternalSaslServer extends AbstractSaslServer
60  {
61      /**
62       * The possible states for the negotiation of a EXTERNAL mechanism.
63       */
64      private enum NegotiationState
65      {
66          INITIALIZED, // Negotiation has just started
67          COMPLETED // The user/password have been received
68      }
69  
70      /** The current negotiation state */
71      private NegotiationState state;
72  
73      /**
74       *
75       * Creates a new instance of ExternalSaslServer.
76       *
77       * @param ldapSession The associated LdapSession instance
78       * @param adminSession The Administrator session
79       * @param bindRequest The associated BindRequest object
80       */
81      ExternalSaslServer( LdapSession ldapSession, CoreSession adminSession, BindRequest bindRequest )
82      {
83          super( ldapSession, adminSession, bindRequest );
84          state = NegotiationState.INITIALIZED;
85      }
86  
87  
88      /**
89       * {@inheritDoc}
90       */
91      public String getMechanismName()
92      {
93          return SupportedSaslMechanisms.EXTERNAL;
94      }
95  
96  
97      /**
98       * {@inheritDoc}
99       */
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/interceptor/context/SearchOperationContext.html#SearchOperationContext">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/api/interceptor/context/BindOperationContext.html#BindOperationContext">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 }