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 2007-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.extensions; 028 029 030 031 import java.security.MessageDigest; 032 import java.security.cert.Certificate; 033 import java.security.cert.X509Certificate; 034 import javax.security.auth.x500.X500Principal; 035 import java.util.ArrayList; 036 import java.util.Collection; 037 import java.util.List; 038 import java.util.Set; 039 040 import org.opends.messages.Message; 041 import org.opends.server.admin.server.ConfigurationChangeListener; 042 import org.opends.server.admin.std.server.CertificateMapperCfg; 043 import org.opends.server.admin.std.server.FingerprintCertificateMapperCfg; 044 import org.opends.server.api.Backend; 045 import org.opends.server.api.CertificateMapper; 046 import org.opends.server.config.ConfigException; 047 import org.opends.server.core.DirectoryServer; 048 import org.opends.server.loggers.ErrorLogger; 049 import org.opends.server.loggers.debug.DebugTracer; 050 import org.opends.server.protocols.internal.InternalClientConnection; 051 import org.opends.server.protocols.internal.InternalSearchOperation; 052 import org.opends.server.types.DirectoryException; 053 import org.opends.server.types.AttributeType; 054 import org.opends.server.types.AttributeValue; 055 import org.opends.server.types.ConfigChangeResult; 056 import org.opends.server.types.DebugLogLevel; 057 import org.opends.server.types.DN; 058 import org.opends.server.types.Entry; 059 import org.opends.server.types.IndexType; 060 import org.opends.server.types.InitializationException; 061 import org.opends.server.types.ResultCode; 062 import org.opends.server.types.SearchFilter; 063 import org.opends.server.types.SearchResultEntry; 064 import org.opends.server.types.SearchScope; 065 066 import static org.opends.messages.ExtensionMessages.*; 067 import static org.opends.server.loggers.debug.DebugLogger.*; 068 import static org.opends.server.util.StaticUtils.*; 069 070 071 072 /** 073 * This class implements a very simple Directory Server certificate mapper that 074 * will map a certificate to a user only if that user's entry contains an 075 * attribute with the fingerprint of the client certificate. There must be 076 * exactly one matching user entry for the mapping to be successful. 077 */ 078 public class FingerprintCertificateMapper 079 extends CertificateMapper<FingerprintCertificateMapperCfg> 080 implements ConfigurationChangeListener< 081 FingerprintCertificateMapperCfg> 082 { 083 /** 084 * The tracer object for the debug logger. 085 */ 086 private static final DebugTracer TRACER = getTracer(); 087 088 089 090 // The DN of the configuration entry for this certificate mapper. 091 private DN configEntryDN; 092 093 // The current configuration for this certificate mapper. 094 private FingerprintCertificateMapperCfg currentConfig; 095 096 // The algorithm that will be used to generate the fingerprint. 097 private String fingerprintAlgorithm; 098 099 100 101 /** 102 * Creates a new instance of this certificate mapper. Note that all actual 103 * initialization should be done in the 104 * <CODE>initializeCertificateMapper</CODE> method. 105 */ 106 public FingerprintCertificateMapper() 107 { 108 super(); 109 } 110 111 112 113 /** 114 * {@inheritDoc} 115 */ 116 public void initializeCertificateMapper( 117 FingerprintCertificateMapperCfg configuration) 118 throws ConfigException, InitializationException 119 { 120 configuration.addFingerprintChangeListener(this); 121 122 currentConfig = configuration; 123 configEntryDN = configuration.dn(); 124 125 126 // Get the algorithm that will be used to generate the fingerprint. 127 switch (configuration.getFingerprintAlgorithm()) 128 { 129 case MD5: 130 fingerprintAlgorithm = "MD5"; 131 break; 132 case SHA1: 133 fingerprintAlgorithm = "SHA1"; 134 break; 135 } 136 137 138 // Make sure that the fingerprint attribute is configured for equality in 139 // all appropriate backends. 140 Set<DN> cfgBaseDNs = configuration.getUserBaseDN(); 141 if ((cfgBaseDNs == null) || cfgBaseDNs.isEmpty()) 142 { 143 cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 144 } 145 146 AttributeType t = configuration.getFingerprintAttribute(); 147 for (DN baseDN : cfgBaseDNs) 148 { 149 Backend b = DirectoryServer.getBackend(baseDN); 150 if ((b != null) && (! b.isIndexed(t, IndexType.EQUALITY))) 151 { 152 Message message = WARN_SATUACM_ATTR_UNINDEXED.get( 153 configuration.dn().toString(), 154 t.getNameOrOID(), b.getBackendID()); 155 ErrorLogger.logError(message); 156 } 157 } 158 } 159 160 161 162 /** 163 * {@inheritDoc} 164 */ 165 public void finalizeCertificateMapper() 166 { 167 currentConfig.removeFingerprintChangeListener(this); 168 } 169 170 171 172 /** 173 * {@inheritDoc} 174 */ 175 public Entry mapCertificateToUser(Certificate[] certificateChain) 176 throws DirectoryException 177 { 178 FingerprintCertificateMapperCfg config = currentConfig; 179 AttributeType fingerprintAttributeType = config.getFingerprintAttribute(); 180 String fingerprintAlgorithm = this.fingerprintAlgorithm; 181 182 // Make sure that a peer certificate was provided. 183 if ((certificateChain == null) || (certificateChain.length == 0)) 184 { 185 Message message = ERR_FCM_NO_PEER_CERTIFICATE.get(); 186 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message); 187 } 188 189 190 // Get the first certificate in the chain. It must be an X.509 certificate. 191 X509Certificate peerCertificate; 192 try 193 { 194 peerCertificate = (X509Certificate) certificateChain[0]; 195 } 196 catch (Exception e) 197 { 198 if (debugEnabled()) 199 { 200 TRACER.debugCaught(DebugLogLevel.ERROR, e); 201 } 202 203 Message message = ERR_FCM_PEER_CERT_NOT_X509.get( 204 String.valueOf(certificateChain[0].getType())); 205 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message); 206 } 207 208 209 // Get the signature from the peer certificate and create a digest of it 210 // using the configured algorithm. 211 String fingerprintString; 212 try 213 { 214 MessageDigest digest = MessageDigest.getInstance(fingerprintAlgorithm); 215 byte[] fingerprintBytes = digest.digest(peerCertificate.getEncoded()); 216 fingerprintString = bytesToColonDelimitedHex(fingerprintBytes); 217 } 218 catch (Exception e) 219 { 220 if (debugEnabled()) 221 { 222 TRACER.debugCaught(DebugLogLevel.ERROR, e); 223 } 224 225 String peerSubject = peerCertificate.getSubjectX500Principal().getName( 226 X500Principal.RFC2253); 227 228 Message message = ERR_FCM_CANNOT_CALCULATE_FINGERPRINT.get( 229 peerSubject, getExceptionMessage(e)); 230 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message); 231 } 232 233 234 // Create the search filter from the fingerprint. 235 AttributeValue value = 236 new AttributeValue(fingerprintAttributeType, fingerprintString); 237 SearchFilter filter = 238 SearchFilter.createEqualityFilter(fingerprintAttributeType, value); 239 240 241 // If we have an explicit set of base DNs, then use it. Otherwise, use the 242 // set of public naming contexts in the server. 243 Collection<DN> baseDNs = config.getUserBaseDN(); 244 if ((baseDNs == null) || baseDNs.isEmpty()) 245 { 246 baseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 247 } 248 249 250 // For each base DN, issue an internal search in an attempt to map the 251 // certificate. 252 Entry userEntry = null; 253 InternalClientConnection conn = 254 InternalClientConnection.getRootConnection(); 255 for (DN baseDN : baseDNs) 256 { 257 InternalSearchOperation searchOperation = 258 conn.processSearch(baseDN, SearchScope.WHOLE_SUBTREE, filter); 259 for (SearchResultEntry entry : searchOperation.getSearchEntries()) 260 { 261 if (userEntry == null) 262 { 263 userEntry = entry; 264 } 265 else 266 { 267 Message message = ERR_FCM_MULTIPLE_MATCHING_ENTRIES. 268 get(fingerprintString, String.valueOf(userEntry.getDN()), 269 String.valueOf(entry.getDN())); 270 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message); 271 } 272 } 273 } 274 275 276 // If we've gotten here, then we either found exactly one user entry or we 277 // didn't find any. Either way, return the entry or null to the caller. 278 return userEntry; 279 } 280 281 282 283 /** 284 * {@inheritDoc} 285 */ 286 @Override() 287 public boolean isConfigurationAcceptable(CertificateMapperCfg configuration, 288 List<Message> unacceptableReasons) 289 { 290 FingerprintCertificateMapperCfg config = 291 (FingerprintCertificateMapperCfg) configuration; 292 return isConfigurationChangeAcceptable(config, unacceptableReasons); 293 } 294 295 296 297 /** 298 * {@inheritDoc} 299 */ 300 public boolean isConfigurationChangeAcceptable( 301 FingerprintCertificateMapperCfg configuration, 302 List<Message> unacceptableReasons) 303 { 304 boolean configAcceptable = true; 305 306 return configAcceptable; 307 } 308 309 310 311 /** 312 * {@inheritDoc} 313 */ 314 public ConfigChangeResult applyConfigurationChange( 315 FingerprintCertificateMapperCfg configuration) 316 { 317 ResultCode resultCode = ResultCode.SUCCESS; 318 boolean adminActionRequired = false; 319 ArrayList<Message> messages = new ArrayList<Message>(); 320 321 322 // Get the algorithm that will be used to generate the fingerprint. 323 String newFingerprintAlgorithm = null; 324 switch (configuration.getFingerprintAlgorithm()) 325 { 326 case MD5: 327 newFingerprintAlgorithm = "MD5"; 328 break; 329 case SHA1: 330 newFingerprintAlgorithm = "SHA1"; 331 break; 332 } 333 334 335 if (resultCode == ResultCode.SUCCESS) 336 { 337 fingerprintAlgorithm = newFingerprintAlgorithm; 338 currentConfig = configuration; 339 } 340 341 // Make sure that the fingerprint attribute is configured for equality in 342 // all appropriate backends. 343 Set<DN> cfgBaseDNs = configuration.getUserBaseDN(); 344 if ((cfgBaseDNs == null) || cfgBaseDNs.isEmpty()) 345 { 346 cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 347 } 348 349 AttributeType t = configuration.getFingerprintAttribute(); 350 for (DN baseDN : cfgBaseDNs) 351 { 352 Backend b = DirectoryServer.getBackend(baseDN); 353 if ((b != null) && (! b.isIndexed(t, IndexType.EQUALITY))) 354 { 355 Message message = WARN_SATUACM_ATTR_UNINDEXED.get( 356 configuration.dn().toString(), 357 t.getNameOrOID(), b.getBackendID()); 358 messages.add(message); 359 ErrorLogger.logError(message); 360 } 361 } 362 363 return new ConfigChangeResult(resultCode, adminActionRequired, messages); 364 } 365 } 366