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