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