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 legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2013-2015 ForgeRock AS 026 */ 027package org.opends.server.extensions; 028 029import java.security.cert.Certificate; 030import java.util.List; 031 032import org.forgerock.i18n.LocalizableMessage; 033import org.opends.server.admin.server.ConfigurationChangeListener; 034import org.opends.server.admin.std.server.ExternalSASLMechanismHandlerCfg; 035import org.opends.server.admin.std.server.SASLMechanismHandlerCfg; 036import org.opends.server.api.CertificateMapper; 037import org.opends.server.api.ClientConnection; 038import org.opends.server.api.SASLMechanismHandler; 039import org.forgerock.opendj.config.server.ConfigChangeResult; 040import org.forgerock.opendj.config.server.ConfigException; 041import org.opends.server.core.BindOperation; 042import org.opends.server.core.DirectoryServer; 043import org.forgerock.i18n.slf4j.LocalizedLogger; 044import org.opends.server.protocols.ldap.LDAPClientConnection; 045import org.opends.server.types.*; 046import org.forgerock.opendj.ldap.ResultCode; 047import org.forgerock.opendj.ldap.ByteString; 048import static org.opends.messages.ExtensionMessages.*; 049import static org.opends.server.config.ConfigConstants.*; 050import static org.opends.server.util.ServerConstants.*; 051import static org.opends.server.util.StaticUtils.*; 052 053/** 054 * This class provides an implementation of a SASL mechanism that relies on some 055 * form of authentication that has already been done outside the LDAP layer. At 056 * the present time, this implementation only provides support for SSL-based 057 * clients that presented their own certificate to the Directory Server during 058 * the negotiation process. Future implementations may be updated to look in 059 * other places to find and evaluate this external authentication information. 060 */ 061public class ExternalSASLMechanismHandler 062 extends SASLMechanismHandler<ExternalSASLMechanismHandlerCfg> 063 implements ConfigurationChangeListener< 064 ExternalSASLMechanismHandlerCfg> 065{ 066 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 067 068 /** 069 * The attribute type that should hold the certificates to use for the 070 * validation. 071 */ 072 private AttributeType certificateAttributeType; 073 074 /** 075 * Indicates whether to attempt to validate the certificate presented by the 076 * client with a certificate in the user's entry. 077 */ 078 private CertificateValidationPolicy validationPolicy; 079 080 /** The current configuration for this SASL mechanism handler. */ 081 private ExternalSASLMechanismHandlerCfg currentConfig; 082 083 084 085 /** 086 * Creates a new instance of this SASL mechanism handler. No initialization 087 * should be done in this method, as it should all be performed in the 088 * <CODE>initializeSASLMechanismHandler</CODE> method. 089 */ 090 public ExternalSASLMechanismHandler() 091 { 092 super(); 093 } 094 095 096 097 /** {@inheritDoc} */ 098 @Override 099 public void initializeSASLMechanismHandler( 100 ExternalSASLMechanismHandlerCfg configuration) 101 throws ConfigException, InitializationException 102 { 103 configuration.addExternalChangeListener(this); 104 currentConfig = configuration; 105 106 // See if we should attempt to validate client certificates against those in 107 // the corresponding user's entry. 108 switch (configuration.getCertificateValidationPolicy()) 109 { 110 case NEVER: 111 validationPolicy = CertificateValidationPolicy.NEVER; 112 break; 113 case IFPRESENT: 114 validationPolicy = CertificateValidationPolicy.IFPRESENT; 115 break; 116 case ALWAYS: 117 validationPolicy = CertificateValidationPolicy.ALWAYS; 118 break; 119 } 120 121 122 // Get the attribute type to use for validating the certificates. If none 123 // is provided, then default to the userCertificate type. 124 certificateAttributeType = configuration.getCertificateAttribute(); 125 if (certificateAttributeType == null) 126 { 127 certificateAttributeType = 128 DirectoryServer.getAttributeTypeOrDefault(DEFAULT_VALIDATION_CERT_ATTRIBUTE); 129 } 130 131 132 DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_EXTERNAL, this); 133 } 134 135 136 137 /** {@inheritDoc} */ 138 @Override 139 public void finalizeSASLMechanismHandler() 140 { 141 currentConfig.removeExternalChangeListener(this); 142 DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_EXTERNAL); 143 } 144 145 146 147 148 /** {@inheritDoc} */ 149 @Override 150 public void processSASLBind(BindOperation bindOperation) 151 { 152 ExternalSASLMechanismHandlerCfg config = currentConfig; 153 AttributeType certificateAttributeType = this.certificateAttributeType; 154 CertificateValidationPolicy validationPolicy = this.validationPolicy; 155 156 157 // Get the client connection used for the bind request, and get the 158 // security manager for that connection. If either are null, then fail. 159 ClientConnection clientConnection = bindOperation.getClientConnection(); 160 if (clientConnection == null) { 161 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 162 LocalizableMessage message = ERR_SASLEXTERNAL_NO_CLIENT_CONNECTION.get(); 163 bindOperation.setAuthFailureReason(message); 164 return; 165 } 166 167 if(!(clientConnection instanceof LDAPClientConnection)) { 168 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 169 LocalizableMessage message = ERR_SASLEXTERNAL_NOT_LDAP_CLIENT_INSTANCE.get(); 170 bindOperation.setAuthFailureReason(message); 171 return; 172 } 173 LDAPClientConnection lc = (LDAPClientConnection) clientConnection; 174 Certificate[] clientCertChain = lc.getClientCertificateChain(); 175 if (clientCertChain == null || clientCertChain.length == 0) { 176 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 177 LocalizableMessage message = ERR_SASLEXTERNAL_NO_CLIENT_CERT.get(); 178 bindOperation.setAuthFailureReason(message); 179 return; 180 } 181 182 183 // Get the certificate mapper to use to map the certificate to a user entry. 184 DN certificateMapperDN = config.getCertificateMapperDN(); 185 CertificateMapper<?> certificateMapper = 186 DirectoryServer.getCertificateMapper(certificateMapperDN); 187 188 189 // Use the Directory Server certificate mapper to map the client certificate 190 // chain to a single user DN. 191 Entry userEntry; 192 try 193 { 194 userEntry = certificateMapper.mapCertificateToUser(clientCertChain); 195 } 196 catch (DirectoryException de) 197 { 198 logger.traceException(de); 199 200 bindOperation.setResponseData(de); 201 return; 202 } 203 204 205 // If the user DN is null, then we couldn't establish a mapping and 206 // therefore the authentication failed. 207 if (userEntry == null) 208 { 209 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 210 211 LocalizableMessage message = ERR_SASLEXTERNAL_NO_MAPPING.get(); 212 bindOperation.setAuthFailureReason(message); 213 return; 214 } 215 else 216 { 217 bindOperation.setSASLAuthUserEntry(userEntry); 218 } 219 220 221 // Get the userCertificate attribute from the user's entry for use in the 222 // validation process. 223 List<Attribute> certAttrList = 224 userEntry.getAttribute(certificateAttributeType); 225 switch (validationPolicy) 226 { 227 case ALWAYS: 228 if (certAttrList == null) 229 { 230 if (validationPolicy == CertificateValidationPolicy.ALWAYS) 231 { 232 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 233 234 LocalizableMessage message = ERR_SASLEXTERNAL_NO_CERT_IN_ENTRY.get(userEntry.getName()); 235 bindOperation.setAuthFailureReason(message); 236 return; 237 } 238 } 239 else 240 { 241 try 242 { 243 ByteString certBytes = ByteString.wrap(clientCertChain[0].getEncoded()); 244 if (!find(certAttrList, certBytes)) 245 { 246 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 247 248 LocalizableMessage message = ERR_SASLEXTERNAL_PEER_CERT_NOT_FOUND.get(userEntry.getName()); 249 bindOperation.setAuthFailureReason(message); 250 return; 251 } 252 } 253 catch (Exception e) 254 { 255 logger.traceException(e); 256 257 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 258 259 LocalizableMessage message = ERR_SASLEXTERNAL_CANNOT_VALIDATE_CERT.get( 260 userEntry.getName(), getExceptionMessage(e)); 261 bindOperation.setAuthFailureReason(message); 262 return; 263 } 264 } 265 break; 266 267 case IFPRESENT: 268 if (certAttrList != null) 269 { 270 try 271 { 272 ByteString certBytes = ByteString.wrap(clientCertChain[0].getEncoded()); 273 if (!find(certAttrList, certBytes)) 274 { 275 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 276 277 LocalizableMessage message = ERR_SASLEXTERNAL_PEER_CERT_NOT_FOUND.get(userEntry.getName()); 278 bindOperation.setAuthFailureReason(message); 279 return; 280 } 281 } 282 catch (Exception e) 283 { 284 logger.traceException(e); 285 286 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 287 288 LocalizableMessage message = ERR_SASLEXTERNAL_CANNOT_VALIDATE_CERT.get( 289 userEntry.getName(), getExceptionMessage(e)); 290 bindOperation.setAuthFailureReason(message); 291 return; 292 } 293 } 294 } 295 296 297 AuthenticationInfo authInfo = new AuthenticationInfo(userEntry, 298 SASL_MECHANISM_EXTERNAL, DirectoryServer.isRootDN(userEntry.getName())); 299 bindOperation.setAuthenticationInfo(authInfo); 300 bindOperation.setResultCode(ResultCode.SUCCESS); 301 } 302 303 304 305 private boolean find(List<Attribute> certAttrList, ByteString certBytes) 306 { 307 for (Attribute a : certAttrList) 308 { 309 if (a.contains(certBytes)) 310 { 311 return true; 312 } 313 } 314 return false; 315 } 316 317 318 319 /** {@inheritDoc} */ 320 @Override 321 public boolean isPasswordBased(String mechanism) 322 { 323 // This is not a password-based mechanism. 324 return false; 325 } 326 327 328 329 /** {@inheritDoc} */ 330 @Override 331 public boolean isSecure(String mechanism) 332 { 333 // This may be considered a secure mechanism. 334 return true; 335 } 336 337 338 339 /** {@inheritDoc} */ 340 @Override 341 public boolean isConfigurationAcceptable( 342 SASLMechanismHandlerCfg configuration, 343 List<LocalizableMessage> unacceptableReasons) 344 { 345 ExternalSASLMechanismHandlerCfg config = 346 (ExternalSASLMechanismHandlerCfg) configuration; 347 return isConfigurationChangeAcceptable(config, unacceptableReasons); 348 } 349 350 351 352 /** {@inheritDoc} */ 353 public boolean isConfigurationChangeAcceptable( 354 ExternalSASLMechanismHandlerCfg configuration, 355 List<LocalizableMessage> unacceptableReasons) 356 { 357 return true; 358 } 359 360 361 362 /** {@inheritDoc} */ 363 public ConfigChangeResult applyConfigurationChange( 364 ExternalSASLMechanismHandlerCfg configuration) 365 { 366 final ConfigChangeResult ccr = new ConfigChangeResult(); 367 368 369 // See if we should attempt to validate client certificates against those in 370 // the corresponding user's entry. 371 CertificateValidationPolicy newValidationPolicy = 372 CertificateValidationPolicy.ALWAYS; 373 switch (configuration.getCertificateValidationPolicy()) 374 { 375 case NEVER: 376 newValidationPolicy = CertificateValidationPolicy.NEVER; 377 break; 378 case IFPRESENT: 379 newValidationPolicy = CertificateValidationPolicy.IFPRESENT; 380 break; 381 case ALWAYS: 382 newValidationPolicy = CertificateValidationPolicy.ALWAYS; 383 break; 384 } 385 386 387 // Get the attribute type to use for validating the certificates. If none 388 // is provided, then default to the userCertificate type. 389 AttributeType newCertificateType = configuration.getCertificateAttribute(); 390 if (newCertificateType == null) 391 { 392 newCertificateType = 393 DirectoryServer.getAttributeTypeOrDefault(DEFAULT_VALIDATION_CERT_ATTRIBUTE); 394 } 395 396 397 if (ccr.getResultCode() == ResultCode.SUCCESS) 398 { 399 validationPolicy = newValidationPolicy; 400 certificateAttributeType = newCertificateType; 401 currentConfig = configuration; 402 } 403 404 return ccr; 405 } 406} 407