001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.extensions;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.util.ArrayList;
033    import java.util.List;
034    
035    import org.opends.server.admin.server.ConfigurationChangeListener;
036    import org.opends.server.admin.std.server.ExternalSASLMechanismHandlerCfg;
037    import org.opends.server.admin.std.server.SASLMechanismHandlerCfg;
038    import org.opends.server.api.CertificateMapper;
039    import org.opends.server.api.ClientConnection;
040    import org.opends.server.api.ConnectionSecurityProvider;
041    import org.opends.server.api.SASLMechanismHandler;
042    import org.opends.server.config.ConfigException;
043    import org.opends.server.core.BindOperation;
044    import org.opends.server.core.DirectoryServer;
045    import org.opends.server.protocols.asn1.ASN1OctetString;
046    import org.opends.server.types.Attribute;
047    import org.opends.server.types.AttributeType;
048    import org.opends.server.types.AttributeValue;
049    import org.opends.server.types.AuthenticationInfo;
050    import org.opends.server.types.ConfigChangeResult;
051    import org.opends.server.types.DirectoryException;
052    import org.opends.server.types.DN;
053    import org.opends.server.types.Entry;
054    import org.opends.server.types.InitializationException;
055    import org.opends.server.types.ResultCode;
056    
057    import static org.opends.server.config.ConfigConstants.*;
058    import static org.opends.server.loggers.debug.DebugLogger.*;
059    import org.opends.server.loggers.debug.DebugTracer;
060    import org.opends.server.types.DebugLogLevel;
061    import static org.opends.messages.ExtensionMessages.*;
062    
063    import static org.opends.server.util.ServerConstants.*;
064    import static org.opends.server.util.StaticUtils.*;
065    
066    
067    
068    /**
069     * This class provides an implementation of a SASL mechanism that relies on some
070     * form of authentication that has already been done outside the LDAP layer.  At
071     * the present time, this implementation only provides support for SSL-based
072     * clients that presented their own certificate to the Directory Server during
073     * the negotiation process.  Future implementations may be updated to look in
074     * other places to find and evaluate this external authentication information.
075     */
076    public class ExternalSASLMechanismHandler
077           extends SASLMechanismHandler<ExternalSASLMechanismHandlerCfg>
078           implements ConfigurationChangeListener<
079                           ExternalSASLMechanismHandlerCfg>
080    {
081      /**
082       * The tracer object for the debug logger.
083       */
084      private static final DebugTracer TRACER = getTracer();
085    
086      // The attribute type that should hold the certificates to use for the
087      // validation.
088      private AttributeType certificateAttributeType;
089    
090      // Indicates whether to attempt to validate the certificate presented by the
091      // client with a certificate in the user's entry.
092      private CertificateValidationPolicy validationPolicy;
093    
094      // The current configuration for this SASL mechanism handler.
095      private ExternalSASLMechanismHandlerCfg currentConfig;
096    
097    
098    
099      /**
100       * Creates a new instance of this SASL mechanism handler.  No initialization
101       * should be done in this method, as it should all be performed in the
102       * <CODE>initializeSASLMechanismHandler</CODE> method.
103       */
104      public ExternalSASLMechanismHandler()
105      {
106        super();
107      }
108    
109    
110    
111      /**
112       * {@inheritDoc}
113       */
114      @Override()
115      public void initializeSASLMechanismHandler(
116                       ExternalSASLMechanismHandlerCfg configuration)
117             throws ConfigException, InitializationException
118      {
119        configuration.addExternalChangeListener(this);
120        currentConfig = configuration;
121    
122        // See if we should attempt to validate client certificates against those in
123        // the corresponding user's entry.
124        switch (configuration.getCertificateValidationPolicy())
125        {
126          case NEVER:
127            validationPolicy = CertificateValidationPolicy.NEVER;
128            break;
129          case IFPRESENT:
130            validationPolicy = CertificateValidationPolicy.IFPRESENT;
131            break;
132          case ALWAYS:
133            validationPolicy = CertificateValidationPolicy.ALWAYS;
134            break;
135        }
136    
137    
138        // Get the attribute type to use for validating the certificates.  If none
139        // is provided, then default to the userCertificate type.
140        certificateAttributeType = configuration.getCertificateAttribute();
141        if (certificateAttributeType == null)
142        {
143          certificateAttributeType =
144               DirectoryServer.getAttributeType(DEFAULT_VALIDATION_CERT_ATTRIBUTE,
145                                                true);
146        }
147    
148    
149        DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_EXTERNAL, this);
150      }
151    
152    
153    
154      /**
155       * {@inheritDoc}
156       */
157      @Override()
158      public void finalizeSASLMechanismHandler()
159      {
160        currentConfig.removeExternalChangeListener(this);
161        DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_EXTERNAL);
162      }
163    
164    
165    
166    
167      /**
168       * {@inheritDoc}
169       */
170      @Override()
171      public void processSASLBind(BindOperation bindOperation)
172      {
173        ExternalSASLMechanismHandlerCfg config = currentConfig;
174        AttributeType certificateAttributeType = this.certificateAttributeType;
175        CertificateValidationPolicy validationPolicy = this.validationPolicy;
176    
177    
178        // Get the client connection used for the bind request, and get the
179        // security manager for that connection.  If either are null, then fail.
180        ClientConnection clientConnection = bindOperation.getClientConnection();
181        if (clientConnection == null)
182        {
183          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
184    
185          Message message = ERR_SASLEXTERNAL_NO_CLIENT_CONNECTION.get();
186          bindOperation.setAuthFailureReason(message);
187          return;
188        }
189    
190        ConnectionSecurityProvider securityProvider =
191             clientConnection.getConnectionSecurityProvider();
192        if (securityProvider == null)
193        {
194          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
195    
196          Message message = ERR_SASLEXTERNAL_NO_SECURITY_PROVIDER.get();
197          bindOperation.setAuthFailureReason(message);
198          return;
199        }
200    
201    
202        // Make sure that the client connection is using the TLS security provider.
203        // If not, then fail.
204        if (! (securityProvider instanceof TLSConnectionSecurityProvider))
205        {
206          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
207    
208          Message message = ERR_SASLEXTERNAL_CLIENT_NOT_USING_TLS_PROVIDER.get(
209                  securityProvider.getSecurityMechanismName());
210          bindOperation.setAuthFailureReason(message);
211          return;
212        }
213    
214        TLSConnectionSecurityProvider tlsSecurityProvider =
215             (TLSConnectionSecurityProvider) securityProvider;
216    
217    
218        // Get the certificate chain that the client presented to the server, if
219        // possible.  If there isn't one, then fail.
220        java.security.cert.Certificate[] clientCertChain =
221             tlsSecurityProvider.getClientCertificateChain();
222        if ((clientCertChain == null) || (clientCertChain.length == 0))
223        {
224          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
225    
226          Message message = ERR_SASLEXTERNAL_NO_CLIENT_CERT.get();
227          bindOperation.setAuthFailureReason(message);
228          return;
229        }
230    
231    
232        // Get the certificate mapper to use to map the certificate to a user entry.
233        DN certificateMapperDN = config.getCertificateMapperDN();
234        CertificateMapper<?> certificateMapper =
235             DirectoryServer.getCertificateMapper(certificateMapperDN);
236    
237    
238        // Use the Directory Server certificate mapper to map the client certificate
239        // chain to a single user DN.
240        Entry userEntry;
241        try
242        {
243          userEntry = certificateMapper.mapCertificateToUser(clientCertChain);
244        }
245        catch (DirectoryException de)
246        {
247          if (debugEnabled())
248          {
249            TRACER.debugCaught(DebugLogLevel.ERROR, de);
250          }
251    
252          bindOperation.setResponseData(de);
253          return;
254        }
255    
256    
257        // If the user DN is null, then we couldn't establish a mapping and
258        // therefore the authentication failed.
259        if (userEntry == null)
260        {
261          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
262    
263          Message message = ERR_SASLEXTERNAL_NO_MAPPING.get();
264          bindOperation.setAuthFailureReason(message);
265          return;
266        }
267        else
268        {
269          bindOperation.setSASLAuthUserEntry(userEntry);
270        }
271    
272    
273        // Get the userCertificate attribute from the user's entry for use in the
274        // validation process.
275        List<Attribute> certAttrList =
276             userEntry.getAttribute(certificateAttributeType);
277        switch (validationPolicy)
278        {
279          case ALWAYS:
280            if (certAttrList == null)
281            {
282              if (validationPolicy == CertificateValidationPolicy.ALWAYS)
283              {
284                bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
285    
286                Message message = ERR_SASLEXTERNAL_NO_CERT_IN_ENTRY.get(
287                        String.valueOf(userEntry.getDN()));
288                bindOperation.setAuthFailureReason(message);
289                return;
290              }
291            }
292            else
293            {
294              try
295              {
296                byte[] certBytes = clientCertChain[0].getEncoded();
297                AttributeValue v =
298                     new AttributeValue(certificateAttributeType,
299                                        new ASN1OctetString(certBytes));
300    
301                boolean found = false;
302                for (Attribute a : certAttrList)
303                {
304                  if (a.hasValue(v))
305                  {
306                    found = true;
307                    break;
308                  }
309                }
310    
311                if (! found)
312                {
313                  bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
314    
315                  Message message = ERR_SASLEXTERNAL_PEER_CERT_NOT_FOUND.get(
316                          String.valueOf(userEntry.getDN()));
317                  bindOperation.setAuthFailureReason(message);
318                  return;
319                }
320              }
321              catch (Exception e)
322              {
323                if (debugEnabled())
324                {
325                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
326                }
327    
328                bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
329    
330                Message message = ERR_SASLEXTERNAL_CANNOT_VALIDATE_CERT.get(
331                        String.valueOf(userEntry.getDN()),
332                        getExceptionMessage(e));
333                bindOperation.setAuthFailureReason(message);
334                return;
335              }
336            }
337            break;
338    
339          case IFPRESENT:
340            if (certAttrList != null)
341            {
342              try
343              {
344                byte[] certBytes = clientCertChain[0].getEncoded();
345                AttributeValue v =
346                     new AttributeValue(certificateAttributeType,
347                                        new ASN1OctetString(certBytes));
348    
349                boolean found = false;
350                for (Attribute a : certAttrList)
351                {
352                  if (a.hasValue(v))
353                  {
354                    found = true;
355                    break;
356                  }
357                }
358    
359                if (! found)
360                {
361                  bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
362    
363                  Message message = ERR_SASLEXTERNAL_PEER_CERT_NOT_FOUND.get(
364                          String.valueOf(userEntry.getDN()));
365                  bindOperation.setAuthFailureReason(message);
366                  return;
367                }
368              }
369              catch (Exception e)
370              {
371                if (debugEnabled())
372                {
373                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
374                }
375    
376                bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
377    
378                Message message = ERR_SASLEXTERNAL_CANNOT_VALIDATE_CERT.get(
379                        String.valueOf(userEntry.getDN()),
380                        getExceptionMessage(e));
381                bindOperation.setAuthFailureReason(message);
382                return;
383              }
384            }
385        }
386    
387    
388        AuthenticationInfo authInfo =
389             new AuthenticationInfo(userEntry, SASL_MECHANISM_EXTERNAL,
390                                    DirectoryServer.isRootDN(userEntry.getDN()));
391        bindOperation.setAuthenticationInfo(authInfo);
392        bindOperation.setResultCode(ResultCode.SUCCESS);
393      }
394    
395    
396    
397      /**
398       * {@inheritDoc}
399       */
400      @Override()
401      public boolean isPasswordBased(String mechanism)
402      {
403        // This is not a password-based mechanism.
404        return false;
405      }
406    
407    
408    
409      /**
410       * {@inheritDoc}
411       */
412      @Override()
413      public boolean isSecure(String mechanism)
414      {
415        // This may be considered a secure mechanism.
416        return true;
417      }
418    
419    
420    
421      /**
422       * {@inheritDoc}
423       */
424      @Override()
425      public boolean isConfigurationAcceptable(
426                          SASLMechanismHandlerCfg configuration,
427                          List<Message> unacceptableReasons)
428      {
429        ExternalSASLMechanismHandlerCfg config =
430             (ExternalSASLMechanismHandlerCfg) configuration;
431        return isConfigurationChangeAcceptable(config, unacceptableReasons);
432      }
433    
434    
435    
436      /**
437       * {@inheritDoc}
438       */
439      public boolean isConfigurationChangeAcceptable(
440                          ExternalSASLMechanismHandlerCfg configuration,
441                          List<Message> unacceptableReasons)
442      {
443        return true;
444      }
445    
446    
447    
448      /**
449       * {@inheritDoc}
450       */
451      public ConfigChangeResult applyConfigurationChange(
452                  ExternalSASLMechanismHandlerCfg configuration)
453      {
454        ResultCode        resultCode          = ResultCode.SUCCESS;
455        boolean           adminActionRequired = false;
456        ArrayList<Message> messages            = new ArrayList<Message>();
457    
458    
459        // See if we should attempt to validate client certificates against those in
460        // the corresponding user's entry.
461        CertificateValidationPolicy newValidationPolicy =
462             CertificateValidationPolicy.ALWAYS;
463        switch (configuration.getCertificateValidationPolicy())
464        {
465          case NEVER:
466            newValidationPolicy = CertificateValidationPolicy.NEVER;
467            break;
468          case IFPRESENT:
469            newValidationPolicy = CertificateValidationPolicy.IFPRESENT;
470            break;
471          case ALWAYS:
472            newValidationPolicy = CertificateValidationPolicy.ALWAYS;
473            break;
474        }
475    
476    
477        // Get the attribute type to use for validating the certificates.  If none
478        // is provided, then default to the userCertificate type.
479        AttributeType newCertificateType = configuration.getCertificateAttribute();
480        if (newCertificateType == null)
481        {
482          newCertificateType =
483               DirectoryServer.getAttributeType(DEFAULT_VALIDATION_CERT_ATTRIBUTE,
484                                                true);
485        }
486    
487    
488        if (resultCode == ResultCode.SUCCESS)
489        {
490          validationPolicy         = newValidationPolicy;
491          certificateAttributeType = newCertificateType;
492          currentConfig            = configuration;
493        }
494    
495    
496       return new ConfigChangeResult(resultCode, adminActionRequired, messages);
497      }
498    }
499