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