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