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