View Javadoc

1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.server.core.security;
21  
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.InputStream;
25  import java.math.BigInteger;
26  import java.security.KeyFactory;
27  import java.security.KeyPair;
28  import java.security.KeyPairGenerator;
29  import java.security.NoSuchAlgorithmException;
30  import java.security.PrivateKey;
31  import java.security.PublicKey;
32  import java.security.Security;
33  import java.util.Date;
34  
35  import javax.naming.NamingException;
36  import javax.security.auth.x500.X500Principal;
37  
38  import java.security.cert.CertificateException;
39  import java.security.cert.CertificateFactory;
40  import java.security.cert.X509Certificate;
41  import java.security.spec.EncodedKeySpec;
42  import java.security.spec.InvalidKeySpecException;
43  import java.security.spec.PKCS8EncodedKeySpec;
44  import java.security.spec.X509EncodedKeySpec;
45  
46  import org.apache.directory.server.core.entry.ServerEntry;
47  import org.apache.directory.shared.ldap.constants.SchemaConstants;
48  import org.apache.directory.shared.ldap.entry.EntryAttribute;
49  import org.bouncycastle.jce.provider.BouncyCastleProvider;
50  import org.bouncycastle.x509.X509V1CertificateGenerator;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  
55  /**
56   * Generates the default RSA key pair for the server.
57   *
58   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
59   * @version $Rev$, $Date$
60   */
61  public class TlsKeyGenerator
62  {
63      private static final Logger LOG = LoggerFactory.getLogger( TlsKeyGenerator.class );
64      
65      public static final String TLS_KEY_INFO_OC = "tlsKeyInfo";
66      public static final String PRIVATE_KEY_AT = "privateKey";
67      public static final String PUBLIC_KEY_AT = "publicKey";
68      public static final String KEY_ALGORITHM_AT = "keyAlgorithm";
69      public static final String PRIVATE_KEY_FORMAT_AT = "privateKeyFormat";
70      public static final String PUBLIC_KEY_FORMAT_AT = "publicKeyFormat";
71      public static final String USER_CERTIFICATE_AT = "userCertificate";
72  
73      public static final String CERTIFICATE_PRINCIPAL_DN =
74          "CN=ApacheDS, OU=Directory, O=ASF, C=US";
75      private static final String ALGORITHM = "RSA";
76      
77      /* 
78       * Eventually we have to make several of these parameters configurable,
79       * however note to pass export restrictions we must use a key size of
80       * 512 or less here as the default.  Users can configure this setting
81       * later based on their own legal situations.  This is required to 
82       * classify ApacheDS in the ECCN 5D002 category.  Please see the following
83       * page for more information:
84       * 
85       *    http://www.apache.org/dev/crypto.html
86       * 
87       * Also ApacheDS must be classified on the following page:
88       * 
89       *    http://www.apache.org/licenses/exports
90       */ 
91      private static final int KEY_SIZE = 512;
92      private static final long YEAR_MILLIS = 365*24*3600*1000;
93      
94  
95      static
96      {
97          Security.addProvider( new BouncyCastleProvider() );
98      }
99  
100     
101     /**
102      * Gets the certificate associated with the self signed TLS private/public 
103      * key pair.
104      *
105      * @param entry the TLS key/cert entry
106      * @return the X509 certificate associated with that entry
107      * @throws NamingException if there are problems accessing or decoding
108      */
109     public static X509Certificate getCertificate( ServerEntry entry ) throws NamingException
110     {
111         X509Certificate cert = null;
112         CertificateFactory certFactory = null;
113         
114         try
115         {
116             certFactory = CertificateFactory.getInstance( "X.509", "BC" );
117         }
118         catch ( Exception e )
119         {
120             NamingException ne = new NamingException( "Failed to get BC Certificate factory for algorithm: X.509" );
121             ne.setRootCause( e );
122             throw ne;
123         }
124 
125         byte[] certBytes = entry.get( USER_CERTIFICATE_AT ).getBytes();
126         InputStream in = new ByteArrayInputStream( certBytes );
127 
128         try
129         {
130             cert = ( X509Certificate ) certFactory.generateCertificate( in );
131         }
132         catch ( CertificateException e )
133         {
134             NamingException ne = new NamingException( "Bad certificate format." );
135             ne.setRootCause( e );
136             throw ne;
137         }
138         
139         return cert;
140     }
141     
142     
143     /**
144      * Extracts the public private key pair from the tlsKeyInfo entry.
145      *
146      * @param entry an entry of the tlsKeyInfo objectClass
147      * @return the private and public key pair
148      * @throws NamingException if there are format or access issues
149      */
150     public static KeyPair getKeyPair( ServerEntry entry ) throws NamingException
151     {
152         PublicKey publicKey = null;
153         PrivateKey privateKey = null;
154         
155         KeyFactory keyFactory = null;
156         try
157         {
158             keyFactory = KeyFactory.getInstance( ALGORITHM );
159         }
160         catch ( Exception e )
161         {
162             NamingException ne = new NamingException( "Failed to get key factory for algorithm: " + ALGORITHM );
163             ne.setRootCause( e );
164             throw ne;
165         }
166         
167         EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec( entry.get( PRIVATE_KEY_AT ).getBytes() );
168         try
169         {
170             privateKey = keyFactory.generatePrivate( privateKeySpec );
171         }
172         catch ( Exception e )
173         {
174             NamingException ne = new NamingException( "Bad private key format." );
175             ne.setRootCause( e );
176             throw ne;
177         }
178     
179         EncodedKeySpec publicKeySpec = new X509EncodedKeySpec( entry.get( PUBLIC_KEY_AT ).getBytes() );
180         try
181         {
182             publicKey = keyFactory.generatePublic( publicKeySpec );
183         }
184         catch ( InvalidKeySpecException e )
185         {
186             NamingException ne = new NamingException( "Bad public key format." );
187             ne.setRootCause( e );
188             throw ne;
189         }
190         
191         return new KeyPair( publicKey, privateKey );
192     }
193     
194 
195     /**
196      * Adds a private key pair along with a self signed certificate to an 
197      * entry making sure it contains the objectClasses and attributes needed
198      * to support the additions.  This function is intended for creating a TLS
199      * key value pair and self signed certificate for use by the server to 
200      * authenticate itself during SSL handshakes in the course of establishing
201      * an LDAPS connection or a secure LDAP connection using StartTLS. Usually
202      * this information is added to the administrator user's entry so the 
203      * administrator (effectively the server) can manage these security 
204      * concerns.
205      * 
206      * @param entry the entry to add security attributes to
207      * @throws NamingException on problems generating the content in the entry
208      */
209     public static void addKeyPair( ServerEntry entry ) throws NamingException
210     {
211         EntryAttribute objectClass = entry.get( SchemaConstants.OBJECT_CLASS_AT );
212         
213         if ( objectClass == null )
214         {
215             entry.put( SchemaConstants.OBJECT_CLASS_AT, TLS_KEY_INFO_OC, SchemaConstants.INET_ORG_PERSON_OC );
216         }
217         else
218         {
219             objectClass.add( TLS_KEY_INFO_OC, SchemaConstants.INET_ORG_PERSON_OC );
220         }
221         
222         KeyPairGenerator generator = null;
223         try
224         {
225             generator = KeyPairGenerator.getInstance( ALGORITHM );
226         }
227         catch ( NoSuchAlgorithmException e )
228         {
229             NamingException ne = new NamingException( "Cannot generate key pair for TLS" );
230             ne.setRootCause( e );
231             throw ne;
232         }
233 
234         generator.initialize( KEY_SIZE );
235         KeyPair keypair = generator.genKeyPair();
236         entry.put( KEY_ALGORITHM_AT, ALGORITHM );
237         
238         // Generate the private key attributes 
239         PrivateKey privateKey = keypair.getPrivate();
240         entry.put( PRIVATE_KEY_AT, privateKey.getEncoded() );
241         entry.put( PRIVATE_KEY_FORMAT_AT, privateKey.getFormat() );
242         LOG.debug( "PrivateKey: {}", privateKey );
243         
244         PublicKey publicKey = keypair.getPublic();
245         entry.put( PUBLIC_KEY_AT, publicKey.getEncoded() );
246         entry.put( PUBLIC_KEY_FORMAT_AT, publicKey.getFormat() );
247         LOG.debug( "PublicKey: {}", publicKey );
248         
249         // Generate the self-signed certificate
250         Date startDate = new Date(); 
251         Date expiryDate = new Date( System.currentTimeMillis() + YEAR_MILLIS ); 
252         BigInteger serialNumber = BigInteger.valueOf( System.currentTimeMillis() );
253 
254         X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
255         X500Principal dnName = new X500Principal( CERTIFICATE_PRINCIPAL_DN );
256 
257         certGen.setSerialNumber( serialNumber );
258         certGen.setIssuerDN( dnName );
259         certGen.setNotBefore( startDate );
260         certGen.setNotAfter( expiryDate );
261         certGen.setSubjectDN( dnName );
262         certGen.setPublicKey( publicKey );
263         certGen.setSignatureAlgorithm( "SHA1With" + ALGORITHM );
264 
265         try
266         {
267             X509Certificate cert = certGen.generate( privateKey, "BC" );
268             entry.put( USER_CERTIFICATE_AT, cert.getEncoded() );
269             LOG.debug( "X509 Certificate: {}", cert );
270         }
271         catch ( Exception e )
272         {
273             NamingException ne = new NamingException( "Cannot generate self signed certificate." );
274             ne.setRootCause( e );
275             throw ne;
276         }
277         
278         LOG.info( "Keys and self signed certificate successfully generated." );
279     }
280 }