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.cert.Certificate; 032 import java.security.cert.X509Certificate; 033 import javax.security.auth.x500.X500Principal; 034 import java.util.Collection; 035 import java.util.List; 036 import java.util.Set; 037 038 import org.opends.messages.Message; 039 import org.opends.server.admin.server.ConfigurationChangeListener; 040 import org.opends.server.admin.std.server.CertificateMapperCfg; 041 import org.opends.server.admin.std.server. 042 SubjectDNToUserAttributeCertificateMapperCfg; 043 import org.opends.server.api.Backend; 044 import org.opends.server.api.CertificateMapper; 045 import org.opends.server.config.ConfigException; 046 import org.opends.server.core.DirectoryServer; 047 import org.opends.server.loggers.ErrorLogger; 048 import org.opends.server.loggers.debug.DebugTracer; 049 import org.opends.server.protocols.internal.InternalClientConnection; 050 import org.opends.server.protocols.internal.InternalSearchOperation; 051 import org.opends.server.types.DirectoryException; 052 import org.opends.server.types.AttributeType; 053 import org.opends.server.types.AttributeValue; 054 import org.opends.server.types.ConfigChangeResult; 055 import org.opends.server.types.DebugLogLevel; 056 import org.opends.server.types.DN; 057 import org.opends.server.types.Entry; 058 import org.opends.server.types.IndexType; 059 import org.opends.server.types.InitializationException; 060 import org.opends.server.types.ResultCode; 061 import org.opends.server.types.SearchFilter; 062 import org.opends.server.types.SearchResultEntry; 063 import org.opends.server.types.SearchScope; 064 065 import static org.opends.messages.ExtensionMessages.*; 066 import static org.opends.server.loggers.debug.DebugLogger.*; 067 068 069 070 /** 071 * This class implements a very simple Directory Server certificate mapper that 072 * will map a certificate to a user only if that user's entry contains an 073 * attribute with the subject of the client certificate. There must be exactly 074 * one matching user entry for the mapping to be successful. 075 */ 076 public class SubjectDNToUserAttributeCertificateMapper 077 extends CertificateMapper< 078 SubjectDNToUserAttributeCertificateMapperCfg> 079 implements ConfigurationChangeListener< 080 SubjectDNToUserAttributeCertificateMapperCfg> 081 { 082 /** 083 * The tracer object for the debug logger. 084 */ 085 private static final DebugTracer TRACER = getTracer(); 086 087 // The DN of the configuration entry for this certificate mapper. 088 private DN configEntryDN; 089 090 // The current configuration for this certificate mapper. 091 private SubjectDNToUserAttributeCertificateMapperCfg currentConfig; 092 093 094 095 /** 096 * Creates a new instance of this certificate mapper. Note that all actual 097 * initialization should be done in the 098 * <CODE>initializeCertificateMapper</CODE> method. 099 */ 100 public SubjectDNToUserAttributeCertificateMapper() 101 { 102 super(); 103 } 104 105 106 107 /** 108 * {@inheritDoc} 109 */ 110 public void initializeCertificateMapper( 111 SubjectDNToUserAttributeCertificateMapperCfg 112 configuration) 113 throws ConfigException, InitializationException 114 { 115 configuration.addSubjectDNToUserAttributeChangeListener(this); 116 117 currentConfig = configuration; 118 configEntryDN = configuration.dn(); 119 120 121 // Make sure that the subject attribute is configured for equality in all 122 // appropriate backends. 123 Set<DN> cfgBaseDNs = configuration.getUserBaseDN(); 124 if ((cfgBaseDNs == null) || cfgBaseDNs.isEmpty()) 125 { 126 cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 127 } 128 129 AttributeType t = configuration.getSubjectAttribute(); 130 for (DN baseDN : cfgBaseDNs) 131 { 132 Backend b = DirectoryServer.getBackend(baseDN); 133 if ((b != null) && (! b.isIndexed(t, IndexType.EQUALITY))) 134 { 135 Message message = WARN_SATUACM_ATTR_UNINDEXED.get( 136 configuration.dn().toString(), 137 t.getNameOrOID(), b.getBackendID()); 138 ErrorLogger.logError(message); 139 } 140 } 141 } 142 143 144 145 /** 146 * {@inheritDoc} 147 */ 148 public void finalizeCertificateMapper() 149 { 150 currentConfig.removeSubjectDNToUserAttributeChangeListener(this); 151 } 152 153 154 155 /** 156 * {@inheritDoc} 157 */ 158 public Entry mapCertificateToUser(Certificate[] certificateChain) 159 throws DirectoryException 160 { 161 SubjectDNToUserAttributeCertificateMapperCfg config = 162 currentConfig; 163 AttributeType subjectAttributeType = config.getSubjectAttribute(); 164 165 166 // Make sure that a peer certificate was provided. 167 if ((certificateChain == null) || (certificateChain.length == 0)) 168 { 169 Message message = ERR_SDTUACM_NO_PEER_CERTIFICATE.get(); 170 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message); 171 } 172 173 174 // Get the first certificate in the chain. It must be an X.509 certificate. 175 X509Certificate peerCertificate; 176 try 177 { 178 peerCertificate = (X509Certificate) certificateChain[0]; 179 } 180 catch (Exception e) 181 { 182 if (debugEnabled()) 183 { 184 TRACER.debugCaught(DebugLogLevel.ERROR, e); 185 } 186 187 Message message = ERR_SDTUACM_PEER_CERT_NOT_X509.get( 188 String.valueOf(certificateChain[0].getType())); 189 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message); 190 } 191 192 193 // Get the subject from the peer certificate and use it to create a search 194 // filter. 195 X500Principal peerPrincipal = peerCertificate.getSubjectX500Principal(); 196 String peerName = peerPrincipal.getName(X500Principal.RFC2253); 197 AttributeValue value = new AttributeValue(subjectAttributeType, peerName); 198 SearchFilter filter = 199 SearchFilter.createEqualityFilter(subjectAttributeType, value); 200 201 202 // If we have an explicit set of base DNs, then use it. Otherwise, use the 203 // set of public naming contexts in the server. 204 Collection<DN> baseDNs = config.getUserBaseDN(); 205 if ((baseDNs == null) || baseDNs.isEmpty()) 206 { 207 baseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 208 } 209 210 211 // For each base DN, issue an internal search in an attempt to map the 212 // certificate. 213 Entry userEntry = null; 214 InternalClientConnection conn = 215 InternalClientConnection.getRootConnection(); 216 for (DN baseDN : baseDNs) 217 { 218 InternalSearchOperation searchOperation = 219 conn.processSearch(baseDN, SearchScope.WHOLE_SUBTREE, filter); 220 for (SearchResultEntry entry : searchOperation.getSearchEntries()) 221 { 222 if (userEntry == null) 223 { 224 userEntry = entry; 225 } 226 else 227 { 228 Message message = ERR_SDTUACM_MULTIPLE_MATCHING_ENTRIES. 229 get(peerName, String.valueOf(userEntry.getDN()), 230 String.valueOf(entry.getDN())); 231 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message); 232 } 233 } 234 } 235 236 237 // If we've gotten here, then we either found exactly one user entry or we 238 // didn't find any. Either way, return the entry or null to the caller. 239 return userEntry; 240 } 241 242 243 244 /** 245 * {@inheritDoc} 246 */ 247 @Override() 248 public boolean isConfigurationAcceptable(CertificateMapperCfg configuration, 249 List<Message> unacceptableReasons) 250 { 251 SubjectDNToUserAttributeCertificateMapperCfg config = 252 (SubjectDNToUserAttributeCertificateMapperCfg) configuration; 253 return isConfigurationChangeAcceptable(config, unacceptableReasons); 254 } 255 256 257 258 /** 259 * {@inheritDoc} 260 */ 261 public boolean isConfigurationChangeAcceptable( 262 SubjectDNToUserAttributeCertificateMapperCfg 263 configuration, 264 List<Message> unacceptableReasons) 265 { 266 boolean configAcceptable = true; 267 return configAcceptable; 268 } 269 270 271 272 /** 273 * {@inheritDoc} 274 */ 275 public ConfigChangeResult applyConfigurationChange( 276 SubjectDNToUserAttributeCertificateMapperCfg 277 configuration) 278 { 279 currentConfig = configuration; 280 return new ConfigChangeResult(ResultCode.SUCCESS, false); 281 } 282 } 283