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