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