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.ldap.ByteString; 027import org.forgerock.opendj.ldap.ResultCode; 028import org.forgerock.opendj.ldap.schema.AttributeType; 029import org.opends.server.admin.server.ConfigurationChangeListener; 030import org.opends.server.admin.std.server.ExternalSASLMechanismHandlerCfg; 031import org.opends.server.admin.std.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 = 111 DirectoryServer.getAttributeType(DEFAULT_VALIDATION_CERT_ATTRIBUTE); 112 } 113 114 115 DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_EXTERNAL, this); 116 } 117 118 private CertificateValidationPolicy toCertificateValidationPolicy(ExternalSASLMechanismHandlerCfg cfg) 119 { 120 switch (cfg.getCertificateValidationPolicy()) 121 { 122 case NEVER: 123 return CertificateValidationPolicy.NEVER; 124 case IFPRESENT: 125 return CertificateValidationPolicy.IFPRESENT; 126 default: 127 return CertificateValidationPolicy.ALWAYS; 128 } 129 } 130 131 @Override 132 public void finalizeSASLMechanismHandler() 133 { 134 currentConfig.removeExternalChangeListener(this); 135 DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_EXTERNAL); 136 } 137 138 @Override 139 public void processSASLBind(BindOperation bindOperation) 140 { 141 ExternalSASLMechanismHandlerCfg config = currentConfig; 142 AttributeType certificateAttributeType = this.certificateAttributeType; 143 CertificateValidationPolicy validationPolicy = this.validationPolicy; 144 145 146 // Get the client connection used for the bind request, and get the 147 // security manager for that connection. If either are null, then fail. 148 ClientConnection clientConnection = bindOperation.getClientConnection(); 149 if (clientConnection == null) { 150 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 151 LocalizableMessage message = ERR_SASLEXTERNAL_NO_CLIENT_CONNECTION.get(); 152 bindOperation.setAuthFailureReason(message); 153 return; 154 } 155 156 if(!(clientConnection instanceof LDAPClientConnection)) { 157 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 158 LocalizableMessage message = ERR_SASLEXTERNAL_NOT_LDAP_CLIENT_INSTANCE.get(); 159 bindOperation.setAuthFailureReason(message); 160 return; 161 } 162 LDAPClientConnection lc = (LDAPClientConnection) clientConnection; 163 Certificate[] clientCertChain = lc.getClientCertificateChain(); 164 if (clientCertChain == null || clientCertChain.length == 0) { 165 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 166 LocalizableMessage message = ERR_SASLEXTERNAL_NO_CLIENT_CERT.get(); 167 bindOperation.setAuthFailureReason(message); 168 return; 169 } 170 171 172 // Get the certificate mapper to use to map the certificate to a user entry. 173 DN certificateMapperDN = config.getCertificateMapperDN(); 174 CertificateMapper<?> certificateMapper = 175 DirectoryServer.getCertificateMapper(certificateMapperDN); 176 177 178 // Use the Directory Server certificate mapper to map the client certificate 179 // chain to a single user DN. 180 Entry userEntry; 181 try 182 { 183 userEntry = certificateMapper.mapCertificateToUser(clientCertChain); 184 } 185 catch (DirectoryException de) 186 { 187 logger.traceException(de); 188 189 bindOperation.setResponseData(de); 190 return; 191 } 192 193 194 // If the user DN is null, then we couldn't establish a mapping and 195 // therefore the authentication failed. 196 if (userEntry == null) 197 { 198 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 199 200 LocalizableMessage message = ERR_SASLEXTERNAL_NO_MAPPING.get(); 201 bindOperation.setAuthFailureReason(message); 202 return; 203 } 204 205 bindOperation.setSASLAuthUserEntry(userEntry); 206 207 208 // Get the userCertificate attribute from the user's entry for use in the 209 // validation process. 210 List<Attribute> certAttrList = userEntry.getAttribute(certificateAttributeType); 211 switch (validationPolicy) 212 { 213 case ALWAYS: 214 if (certAttrList.isEmpty()) 215 { 216 if (validationPolicy == CertificateValidationPolicy.ALWAYS) 217 { 218 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 219 220 LocalizableMessage message = ERR_SASLEXTERNAL_NO_CERT_IN_ENTRY.get(userEntry.getName()); 221 bindOperation.setAuthFailureReason(message); 222 return; 223 } 224 } 225 else 226 { 227 try 228 { 229 ByteString certBytes = ByteString.wrap(clientCertChain[0].getEncoded()); 230 if (!findAttributeValue(certAttrList, certBytes)) 231 { 232 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 233 234 LocalizableMessage message = ERR_SASLEXTERNAL_PEER_CERT_NOT_FOUND.get(userEntry.getName()); 235 bindOperation.setAuthFailureReason(message); 236 return; 237 } 238 } 239 catch (Exception e) 240 { 241 logger.traceException(e); 242 243 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 244 245 LocalizableMessage message = ERR_SASLEXTERNAL_CANNOT_VALIDATE_CERT.get( 246 userEntry.getName(), getExceptionMessage(e)); 247 bindOperation.setAuthFailureReason(message); 248 return; 249 } 250 } 251 break; 252 253 case IFPRESENT: 254 if (!certAttrList.isEmpty()) 255 { 256 try 257 { 258 ByteString certBytes = ByteString.wrap(clientCertChain[0].getEncoded()); 259 if (!findAttributeValue(certAttrList, certBytes)) 260 { 261 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 262 263 LocalizableMessage message = ERR_SASLEXTERNAL_PEER_CERT_NOT_FOUND.get(userEntry.getName()); 264 bindOperation.setAuthFailureReason(message); 265 return; 266 } 267 } 268 catch (Exception e) 269 { 270 logger.traceException(e); 271 272 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS); 273 274 LocalizableMessage message = ERR_SASLEXTERNAL_CANNOT_VALIDATE_CERT.get( 275 userEntry.getName(), getExceptionMessage(e)); 276 bindOperation.setAuthFailureReason(message); 277 return; 278 } 279 } 280 } 281 282 283 AuthenticationInfo authInfo = new AuthenticationInfo(userEntry, 284 SASL_MECHANISM_EXTERNAL, DirectoryServer.isRootDN(userEntry.getName())); 285 bindOperation.setAuthenticationInfo(authInfo); 286 bindOperation.setResultCode(ResultCode.SUCCESS); 287 } 288 289 private boolean findAttributeValue(List<Attribute> certAttrList, ByteString certBytes) 290 { 291 for (Attribute a : certAttrList) 292 { 293 if (a.contains(certBytes)) 294 { 295 return true; 296 } 297 } 298 return false; 299 } 300 301 @Override 302 public boolean isPasswordBased(String mechanism) 303 { 304 // This is not a password-based mechanism. 305 return false; 306 } 307 308 @Override 309 public boolean isSecure(String mechanism) 310 { 311 // This may be considered a secure mechanism. 312 return true; 313 } 314 315 @Override 316 public boolean isConfigurationAcceptable( 317 SASLMechanismHandlerCfg configuration, 318 List<LocalizableMessage> unacceptableReasons) 319 { 320 ExternalSASLMechanismHandlerCfg config = 321 (ExternalSASLMechanismHandlerCfg) configuration; 322 return isConfigurationChangeAcceptable(config, unacceptableReasons); 323 } 324 325 @Override 326 public boolean isConfigurationChangeAcceptable( 327 ExternalSASLMechanismHandlerCfg configuration, 328 List<LocalizableMessage> unacceptableReasons) 329 { 330 return true; 331 } 332 333 @Override 334 public ConfigChangeResult applyConfigurationChange( 335 ExternalSASLMechanismHandlerCfg configuration) 336 { 337 final ConfigChangeResult ccr = new ConfigChangeResult(); 338 339 340 // See if we should attempt to validate client certificates against those in 341 // the corresponding user's entry. 342 CertificateValidationPolicy newValidationPolicy = toCertificateValidationPolicy(configuration); 343 344 345 // Get the attribute type to use for validating the certificates. If none 346 // is provided, then default to the userCertificate type. 347 AttributeType newCertificateType = configuration.getCertificateAttribute(); 348 if (newCertificateType == null) 349 { 350 newCertificateType = 351 DirectoryServer.getAttributeType(DEFAULT_VALIDATION_CERT_ATTRIBUTE); 352 } 353 354 355 if (ccr.getResultCode() == ResultCode.SUCCESS) 356 { 357 validationPolicy = newValidationPolicy; 358 certificateAttributeType = newCertificateType; 359 currentConfig = configuration; 360 } 361 362 return ccr; 363 } 364}