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 2006-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.concurrent.locks.Lock;
035    
036    import org.opends.messages.Message;
037    import org.opends.server.admin.std.server.SubjectEqualsDNCertificateMapperCfg;
038    import org.opends.server.api.CertificateMapper;
039    import org.opends.server.config.ConfigException;
040    import org.opends.server.core.DirectoryServer;
041    import org.opends.server.loggers.debug.DebugTracer;
042    import org.opends.server.types.DebugLogLevel;
043    import org.opends.server.types.DirectoryException;
044    import org.opends.server.types.DN;
045    import org.opends.server.types.Entry;
046    import org.opends.server.types.InitializationException;
047    import org.opends.server.types.LockManager;
048    import org.opends.server.types.ResultCode;
049    
050    import static org.opends.server.loggers.debug.DebugLogger.*;
051    import static org.opends.messages.ExtensionMessages.*;
052    import static org.opends.server.util.StaticUtils.*;
053    
054    
055    
056    /**
057     * This class implements a very simple Directory Server certificate mapper that
058     * will map a certificate to a user only if the subject of the peer certificate
059     * exactly matches the DN of a user in the Directory Server.
060     */
061    public class SubjectEqualsDNCertificateMapper
062           extends CertificateMapper<SubjectEqualsDNCertificateMapperCfg>
063    {
064      /**
065       * The tracer object for the debug logger.
066       */
067      private static final DebugTracer TRACER = getTracer();
068    
069      /**
070       * Creates a new instance of this certificate mapper.  Note that all actual
071       * initialization should be done in the
072       * <CODE>initializeCertificateMapper</CODE> method.
073       */
074      public SubjectEqualsDNCertificateMapper()
075      {
076        super();
077    
078      }
079    
080    
081    
082      /**
083       * {@inheritDoc}
084       */
085      public void initializeCertificateMapper(SubjectEqualsDNCertificateMapperCfg
086                                                   configuration)
087             throws ConfigException, InitializationException
088      {
089        // No initialization is required.
090      }
091    
092    
093    
094      /**
095       * Establishes a mapping between the information in the provided certificate
096       * chain to the DN of a single user in the Directory Server.
097       *
098       * @param  certificateChain  The certificate chain presented by the client
099       *                           during SSL negotiation.  The peer certificate
100       *                           will be listed first, followed by the ordered
101       *                           issuer chain as appropriate.
102       *
103       * @return  The DN of the one user to whom the mapping was established, or
104       *          <CODE>null</CODE> if no mapping was established and no special
105       *         message is required to send back to the client.
106       *
107       * @throws  DirectoryException  If a problem occurred while attempting to
108       *                              establish the mapping.  This may include
109       *                              internal failures, a mapping which matches
110       *                              multiple users, or any other case in which an
111       *                              error message should be returned to the
112       *                              client.
113       */
114      public Entry mapCertificateToUser(Certificate[] certificateChain)
115             throws DirectoryException
116      {
117        // Make sure that a peer certificate was provided.
118        if ((certificateChain == null) || (certificateChain.length == 0))
119        {
120          Message message = ERR_SEDCM_NO_PEER_CERTIFICATE.get();
121          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message);
122        }
123    
124    
125        // Get the first certificate in the chain.  It must be an X.509 certificate.
126        X509Certificate peerCertificate;
127        try
128        {
129          peerCertificate = (X509Certificate) certificateChain[0];
130        }
131        catch (Exception e)
132        {
133          if (debugEnabled())
134          {
135            TRACER.debugCaught(DebugLogLevel.ERROR, e);
136          }
137    
138          Message message = ERR_SEDCM_PEER_CERT_NOT_X509.get(
139              String.valueOf(certificateChain[0].getType()));
140          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message);
141        }
142    
143    
144        // Get the subject from the peer certificate and decode it as a DN.
145        X500Principal peerPrincipal = peerCertificate.getSubjectX500Principal();
146        DN subjectDN;
147        try
148        {
149          subjectDN = DN.decode(peerPrincipal.getName(X500Principal.RFC2253));
150        }
151        catch (Exception e)
152        {
153          if (debugEnabled())
154          {
155            TRACER.debugCaught(DebugLogLevel.ERROR, e);
156          }
157    
158          Message message = ERR_SEDCM_CANNOT_DECODE_SUBJECT_AS_DN.get(
159              String.valueOf(peerPrincipal), getExceptionMessage(e));
160          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message);
161        }
162    
163    
164        // Acquire a read lock on the user entry.  If this fails, then so will the
165        // certificate mapping.
166        Lock readLock = null;
167        for (int i=0; i < 3; i++)
168        {
169          readLock = LockManager.lockRead(subjectDN);
170          if (readLock != null)
171          {
172            break;
173          }
174        }
175    
176        if (readLock == null)
177        {
178          Message message =
179              ERR_SEDCM_CANNOT_LOCK_ENTRY.get(String.valueOf(subjectDN));
180          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message);
181        }
182    
183    
184        // Retrieve the entry with the specified DN from the directory.
185        Entry userEntry;
186        try
187        {
188          userEntry = DirectoryServer.getEntry(subjectDN);
189        }
190        catch (DirectoryException de)
191        {
192          if (debugEnabled())
193          {
194            TRACER.debugCaught(DebugLogLevel.ERROR, de);
195          }
196    
197          Message message = ERR_SEDCM_CANNOT_GET_ENTRY.get(
198              String.valueOf(subjectDN), de.getMessageObject());
199          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
200                                       de);
201        }
202        catch (Exception e)
203        {
204          if (debugEnabled())
205          {
206            TRACER.debugCaught(DebugLogLevel.ERROR, e);
207          }
208    
209          Message message = ERR_SEDCM_CANNOT_GET_ENTRY.get(
210              String.valueOf(subjectDN), getExceptionMessage(e));
211          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message,
212                                       e);
213        }
214        finally
215        {
216          LockManager.unlock(subjectDN, readLock);
217        }
218    
219    
220        if (userEntry == null)
221        {
222          Message message = ERR_SEDCM_NO_USER_FOR_DN.get(String.valueOf(subjectDN));
223          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, message);
224        }
225        else
226        {
227          return userEntry;
228        }
229      }
230    }
231