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.net.InetAddress;
27  import java.security.KeyFactory;
28  import java.security.KeyPair;
29  import java.security.KeyPairGenerator;
30  import java.security.NoSuchAlgorithmException;
31  import java.security.PrivateKey;
32  import java.security.PublicKey;
33  import java.security.Security;
34  import java.security.cert.CertificateException;
35  import java.security.cert.CertificateFactory;
36  import java.security.cert.X509Certificate;
37  import java.security.spec.EncodedKeySpec;
38  import java.security.spec.InvalidKeySpecException;
39  import java.security.spec.PKCS8EncodedKeySpec;
40  import java.security.spec.X509EncodedKeySpec;
41  import java.util.Date;
42  
43  import javax.security.auth.x500.X500Principal;
44  
45  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
46  import org.apache.directory.api.ldap.model.entry.Attribute;
47  import org.apache.directory.api.ldap.model.entry.Entry;
48  import org.apache.directory.api.ldap.model.exception.LdapException;
49  import org.apache.directory.server.i18n.I18n;
50  import org.bouncycastle.jce.provider.BouncyCastleProvider;
51  import org.bouncycastle.x509.X509V1CertificateGenerator;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  
56  /**
57   * Generates the default RSA key pair for the server.
58   *
59   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
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      private static final String BASE_DN = "OU=Directory, O=ASF, C=US";
74  
75      public static final String CERTIFICATE_PRINCIPAL_DN = "CN=ApacheDS," + BASE_DN;
76  
77      private static final String ALGORITHM = "RSA";
78  
79      /* 
80       * Eventually we have to make several of these parameters configurable,
81       * however note to pass export restrictions we must use a key size of
82       * 512 or less here as the default.  Users can configure this setting
83       * later based on their own legal situations.  This is required to 
84       * classify ApacheDS in the ECCN 5D002 category.  Please see the following
85       * page for more information:
86       * 
87       *    http://www.apache.org/dev/crypto.html
88       * 
89       * Also ApacheDS must be classified on the following page:
90       * 
91       *    http://www.apache.org/licenses/exports
92       */
93      private static final int KEY_SIZE = 512;
94      private static final long YEAR_MILLIS = 365L * 24L * 3600L * 1000L;
95  
96      static
97      {
98          Security.addProvider( new BouncyCastleProvider() );
99      }
100 
101 
102     /**
103      * Gets the certificate associated with the self signed TLS private/public 
104      * key pair.
105      *
106      * @param entry the TLS key/cert entry
107      * @return the X509 certificate associated with that entry
108      * @throws org.apache.directory.api.ldap.model.exception.LdapException if there are problems accessing or decoding
109      */
110     public static X509Certificate getCertificate( Entry entry ) throws LdapException
111     {
112         X509Certificate cert = null;
113         CertificateFactory certFactory = null;
114 
115         try
116         {
117             certFactory = CertificateFactory.getInstance( "X.509", "BC" );
118         }
119         catch ( Exception e )
120         {
121             LdapException ne = new LdapException( I18n.err( I18n.ERR_286 ) );
122             ne.initCause( e );
123             throw ne;
124         }
125 
126         byte[] certBytes = entry.get( USER_CERTIFICATE_AT ).getBytes();
127         InputStream in = new ByteArrayInputStream( certBytes );
128 
129         try
130         {
131             cert = ( X509Certificate ) certFactory.generateCertificate( in );
132         }
133         catch ( CertificateException e )
134         {
135             LdapException ne = new LdapException( I18n.err( I18n.ERR_287 ) );
136             ne.initCause( e );
137             throw ne;
138         }
139 
140         return cert;
141     }
142 
143 
144     /**
145      * Extracts the public private key pair from the tlsKeyInfo entry.
146      *
147      * @param entry an entry of the tlsKeyInfo objectClass
148      * @return the private and public key pair
149      * @throws LdapException if there are format or access issues
150      */
151     public static KeyPair getKeyPair( Entry entry ) throws LdapException
152     {
153         PublicKey publicKey = null;
154         PrivateKey privateKey = null;
155 
156         KeyFactory keyFactory = null;
157         try
158         {
159             keyFactory = KeyFactory.getInstance( ALGORITHM );
160         }
161         catch ( Exception e )
162         {
163             LdapException ne = new LdapException( I18n.err( I18n.ERR_288, ALGORITHM ) );
164             ne.initCause( e );
165             throw ne;
166         }
167 
168         EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec( entry.get( PRIVATE_KEY_AT ).getBytes() );
169         try
170         {
171             privateKey = keyFactory.generatePrivate( privateKeySpec );
172         }
173         catch ( Exception e )
174         {
175             LdapException ne = new LdapException( I18n.err( I18n.ERR_289 ) );
176             ne.initCause( e );
177             throw ne;
178         }
179 
180         EncodedKeySpec publicKeySpec = new X509EncodedKeySpec( entry.get( PUBLIC_KEY_AT ).getBytes() );
181         try
182         {
183             publicKey = keyFactory.generatePublic( publicKeySpec );
184         }
185         catch ( InvalidKeySpecException e )
186         {
187             LdapException ne = new LdapException( I18n.err( I18n.ERR_290 ) );
188             ne.initCause( e );
189             throw ne;
190         }
191 
192         return new KeyPair( publicKey, privateKey );
193     }
194 
195 
196     /**
197      * Adds a private key pair along with a self signed certificate to an 
198      * entry making sure it contains the objectClasses and attributes needed
199      * to support the additions.  This function is intended for creating a TLS
200      * key value pair and self signed certificate for use by the server to 
201      * authenticate itself during SSL handshakes in the course of establishing
202      * an LDAPS connection or a secure LDAP connection using StartTLS. Usually
203      * this information is added to the administrator user's entry so the 
204      * administrator (effectively the server) can manage these security 
205      * concerns.
206      * 
207      * @param entry the entry to add security attributes to
208      * @throws LdapException on problems generating the content in the entry
209      */
210     public static void addKeyPair( Entry entry ) throws LdapException
211     {
212         Attribute objectClass = entry.get( SchemaConstants.OBJECT_CLASS_AT );
213 
214         if ( objectClass == null )
215         {
216             entry.put( SchemaConstants.OBJECT_CLASS_AT, TLS_KEY_INFO_OC, SchemaConstants.INET_ORG_PERSON_OC );
217         }
218         else if ( !objectClass.contains( SchemaConstants.INET_ORG_PERSON_OC ) )
219         {
220             objectClass.add( SchemaConstants.INET_ORG_PERSON_OC );
221         }
222         else if ( !objectClass.contains( TLS_KEY_INFO_OC ) )
223         {
224             objectClass.add( TLS_KEY_INFO_OC );
225         }
226 
227         KeyPairGenerator generator = null;
228         try
229         {
230             generator = KeyPairGenerator.getInstance( ALGORITHM );
231         }
232         catch ( NoSuchAlgorithmException e )
233         {
234             LdapException ne = new LdapException( I18n.err( I18n.ERR_291 ) );
235             ne.initCause( e );
236             throw ne;
237         }
238 
239         generator.initialize( KEY_SIZE );
240         KeyPair keypair = generator.genKeyPair();
241         entry.put( KEY_ALGORITHM_AT, ALGORITHM );
242 
243         // Generate the private key attributes 
244         PrivateKey privateKey = keypair.getPrivate();
245         entry.put( PRIVATE_KEY_AT, privateKey.getEncoded() );
246         entry.put( PRIVATE_KEY_FORMAT_AT, privateKey.getFormat() );
247         LOG.debug( "PrivateKey: {}", privateKey );
248 
249         PublicKey publicKey = keypair.getPublic();
250         entry.put( PUBLIC_KEY_AT, publicKey.getEncoded() );
251         entry.put( PUBLIC_KEY_FORMAT_AT, publicKey.getFormat() );
252         LOG.debug( "PublicKey: {}", publicKey );
253 
254         // Generate the self-signed certificate
255         Date startDate = new Date();
256         Date expiryDate = new Date( System.currentTimeMillis() + YEAR_MILLIS );
257         BigInteger serialNumber = BigInteger.valueOf( System.currentTimeMillis() );
258 
259         X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
260         X500Principal issuerDn = new X500Principal( CERTIFICATE_PRINCIPAL_DN );
261 
262         X500Principal subjectDn = null;
263 
264         try
265         {
266             String hostName = InetAddress.getLocalHost().getHostName();
267             subjectDn = new X500Principal( "CN=" + hostName + "," + BASE_DN );
268         }
269         catch ( Exception e )
270         {
271             LOG.warn( "failed to create certificate subject name from host name", e );
272             subjectDn = issuerDn;
273         }
274 
275         certGen.setSerialNumber( serialNumber );
276         certGen.setIssuerDN( issuerDn );
277         certGen.setNotBefore( startDate );
278         certGen.setNotAfter( expiryDate );
279         certGen.setSubjectDN( subjectDn );
280         certGen.setPublicKey( publicKey );
281         certGen.setSignatureAlgorithm( "SHA1With" + ALGORITHM );
282 
283         try
284         {
285             X509Certificate cert = certGen.generate( privateKey, "BC" );
286             entry.put( USER_CERTIFICATE_AT, cert.getEncoded() );
287             LOG.debug( "X509 Certificate: {}", cert );
288         }
289         catch ( Exception e )
290         {
291             LdapException ne = new LdapException( I18n.err( I18n.ERR_292 ) );
292             ne.initCause( e );
293             throw ne;
294         }
295 
296         LOG.info( "Keys and self signed certificate successfully generated." );
297     }
298 
299 
300     /**
301      * @see #addKeyPair(org.apache.directory.api.ldap.model.entry.Entry)
302      * 
303      * TODO the code is duplicate atm, will eliminate this redundancy after finding
304      * a better thought (an instant one is to call this method from the aboveaddKeyPair(entry) and remove the impl there)
305      */
306     public static void addKeyPair( Entry entry, String issuerDN, String subjectDN, String keyAlgo )
307         throws LdapException
308     {
309         Attribute objectClass = entry.get( SchemaConstants.OBJECT_CLASS_AT );
310 
311         if ( objectClass == null )
312         {
313             entry.put( SchemaConstants.OBJECT_CLASS_AT, TLS_KEY_INFO_OC, SchemaConstants.INET_ORG_PERSON_OC );
314         }
315         else
316         {
317             objectClass.add( TLS_KEY_INFO_OC, SchemaConstants.INET_ORG_PERSON_OC );
318         }
319 
320         KeyPairGenerator generator = null;
321         try
322         {
323             generator = KeyPairGenerator.getInstance( keyAlgo );
324         }
325         catch ( NoSuchAlgorithmException e )
326         {
327             LdapException ne = new LdapException( I18n.err( I18n.ERR_291 ) );
328             ne.initCause( e );
329             throw ne;
330         }
331 
332         generator.initialize( KEY_SIZE );
333         KeyPair keypair = generator.genKeyPair();
334         entry.put( KEY_ALGORITHM_AT, keyAlgo );
335 
336         // Generate the private key attributes 
337         PrivateKey privateKey = keypair.getPrivate();
338         entry.put( PRIVATE_KEY_AT, privateKey.getEncoded() );
339         entry.put( PRIVATE_KEY_FORMAT_AT, privateKey.getFormat() );
340         LOG.debug( "PrivateKey: {}", privateKey );
341 
342         PublicKey publicKey = keypair.getPublic();
343         entry.put( PUBLIC_KEY_AT, publicKey.getEncoded() );
344         entry.put( PUBLIC_KEY_FORMAT_AT, publicKey.getFormat() );
345         LOG.debug( "PublicKey: {}", publicKey );
346 
347         // Generate the self-signed certificate
348         Date startDate = new Date();
349         Date expiryDate = new Date( System.currentTimeMillis() + YEAR_MILLIS );
350         BigInteger serialNumber = BigInteger.valueOf( System.currentTimeMillis() );
351 
352         X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
353         X500Principal issuerName = new X500Principal( issuerDN );
354         X500Principal subjectName = new X500Principal( subjectDN );
355 
356         certGen.setSerialNumber( serialNumber );
357         certGen.setIssuerDN( issuerName );
358         certGen.setNotBefore( startDate );
359         certGen.setNotAfter( expiryDate );
360         certGen.setSubjectDN( subjectName );
361         certGen.setPublicKey( publicKey );
362         certGen.setSignatureAlgorithm( "SHA1With" + keyAlgo );
363 
364         try
365         {
366             X509Certificate cert = certGen.generate( privateKey, "BC" );
367             entry.put( USER_CERTIFICATE_AT, cert.getEncoded() );
368             LOG.debug( "X509 Certificate: {}", cert );
369         }
370         catch ( Exception e )
371         {
372             LdapException ne = new LdapException( I18n.err( I18n.ERR_292 ) );
373             ne.initCause( e );
374             throw ne;
375         }
376 
377         LOG.info( "Keys and self signed certificate successfully generated." );
378     }
379 
380 }