001    /*
002     *   Licensed to the Apache Software Foundation (ASF) under one
003     *   or more contributor license agreements.  See the NOTICE file
004     *   distributed with this work for additional information
005     *   regarding copyright ownership.  The ASF licenses this file
006     *   to you under the Apache License, Version 2.0 (the
007     *   "License"); you may not use this file except in compliance
008     *   with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *   Unless required by applicable law or agreed to in writing,
013     *   software distributed under the License is distributed on an
014     *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *   KIND, either express or implied.  See the License for the
016     *   specific language governing permissions and limitations
017     *   under the License.
018     *
019     */
020    package org.apache.directory.server.core.security;
021    
022    
023    import java.io.ByteArrayInputStream;
024    import java.io.InputStream;
025    import java.math.BigInteger;
026    import java.security.KeyFactory;
027    import java.security.KeyPair;
028    import java.security.KeyPairGenerator;
029    import java.security.NoSuchAlgorithmException;
030    import java.security.PrivateKey;
031    import java.security.PublicKey;
032    import java.security.Security;
033    import java.security.cert.CertificateException;
034    import java.security.cert.CertificateFactory;
035    import java.security.cert.X509Certificate;
036    import java.security.spec.EncodedKeySpec;
037    import java.security.spec.InvalidKeySpecException;
038    import java.security.spec.PKCS8EncodedKeySpec;
039    import java.security.spec.X509EncodedKeySpec;
040    import java.util.Date;
041    
042    import javax.security.auth.x500.X500Principal;
043    
044    import org.apache.directory.server.i18n.I18n;
045    import org.apache.directory.shared.ldap.constants.SchemaConstants;
046    import org.apache.directory.shared.ldap.entry.EntryAttribute;
047    import org.apache.directory.shared.ldap.entry.ServerEntry;
048    import org.apache.directory.shared.ldap.exception.LdapException;
049    import org.bouncycastle.jce.provider.BouncyCastleProvider;
050    import org.bouncycastle.x509.X509V1CertificateGenerator;
051    import org.slf4j.Logger;
052    import org.slf4j.LoggerFactory;
053    
054    
055    /**
056     * Generates the default RSA key pair for the server.
057     *
058     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
059     * @version $Rev$, $Date$
060     */
061    public class TlsKeyGenerator
062    {
063        private static final Logger LOG = LoggerFactory.getLogger( TlsKeyGenerator.class );
064        
065        public static final String TLS_KEY_INFO_OC = "tlsKeyInfo";
066        public static final String PRIVATE_KEY_AT = "privateKey";
067        public static final String PUBLIC_KEY_AT = "publicKey";
068        public static final String KEY_ALGORITHM_AT = "keyAlgorithm";
069        public static final String PRIVATE_KEY_FORMAT_AT = "privateKeyFormat";
070        public static final String PUBLIC_KEY_FORMAT_AT = "publicKeyFormat";
071        public static final String USER_CERTIFICATE_AT = "userCertificate";
072    
073        public static final String CERTIFICATE_PRINCIPAL_DN =
074            "CN=ApacheDS, OU=Directory, O=ASF, C=US";
075        private static final String ALGORITHM = "RSA";
076        
077        /* 
078         * Eventually we have to make several of these parameters configurable,
079         * however note to pass export restrictions we must use a key size of
080         * 512 or less here as the default.  Users can configure this setting
081         * later based on their own legal situations.  This is required to 
082         * classify ApacheDS in the ECCN 5D002 category.  Please see the following
083         * page for more information:
084         * 
085         *    http://www.apache.org/dev/crypto.html
086         * 
087         * Also ApacheDS must be classified on the following page:
088         * 
089         *    http://www.apache.org/licenses/exports
090         */ 
091        private static final int KEY_SIZE = 512;
092        private static final long YEAR_MILLIS = 365L * 24L * 3600L * 1000L;
093        
094    
095        static
096        {
097            Security.addProvider( new BouncyCastleProvider() );
098        }
099    
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 LdapException if there are problems accessing or decoding
108         */
109        public static X509Certificate getCertificate( ServerEntry entry ) throws LdapException
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                LdapException ne = new LdapException( I18n.err( I18n.ERR_286 ) );
121                ne.initCause( 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                LdapException ne = new LdapException( I18n.err( I18n.ERR_287 ) );
135                ne.initCause( 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 LdapException if there are format or access issues
149         */
150        public static KeyPair getKeyPair( ServerEntry entry ) throws LdapException
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                LdapException ne = new LdapException( I18n.err( I18n.ERR_288, ALGORITHM ) );
163                ne.initCause( 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                LdapException ne = new LdapException( I18n.err( I18n.ERR_289 ) );
175                ne.initCause( 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                LdapException ne = new LdapException( I18n.err( I18n.ERR_290 ) );
187                ne.initCause( 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 LdapException on problems generating the content in the entry
208         */
209        public static void addKeyPair( ServerEntry entry ) throws LdapException
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                LdapException ne = new LdapException( I18n.err( I18n.ERR_291 ) );
230                ne.initCause( 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                LdapException ne = new LdapException( I18n.err( I18n.ERR_292 ) );
274                ne.initCause( e );
275                throw ne;
276            }
277            
278            LOG.info( "Keys and self signed certificate successfully generated." );
279        }
280        
281    
282        /**
283         * @see #addKeyPair(ServerEntry)
284         * 
285         * TODO the code is duplicate atm, will eliminate this redundancy after finding
286         * a better thought (an instant one is to call this method from the aboveaddKeyPair(entry) and remove the impl there)
287         */
288        public static void addKeyPair( ServerEntry entry, String issuerDN, String subjectDN, String keyAlgo ) throws LdapException
289        {
290            EntryAttribute objectClass = entry.get( SchemaConstants.OBJECT_CLASS_AT );
291            
292            if ( objectClass == null )
293            {
294                entry.put( SchemaConstants.OBJECT_CLASS_AT, TLS_KEY_INFO_OC, SchemaConstants.INET_ORG_PERSON_OC );
295            }
296            else
297            {
298                objectClass.add( TLS_KEY_INFO_OC, SchemaConstants.INET_ORG_PERSON_OC );
299            }
300            
301            KeyPairGenerator generator = null;
302            try
303            {
304                generator = KeyPairGenerator.getInstance( keyAlgo );
305            }
306            catch ( NoSuchAlgorithmException e )
307            {
308                LdapException ne = new LdapException( I18n.err( I18n.ERR_291 ) );
309                ne.initCause( e );
310                throw ne;
311            }
312    
313            generator.initialize( KEY_SIZE );
314            KeyPair keypair = generator.genKeyPair();
315            entry.put( KEY_ALGORITHM_AT, keyAlgo );
316            
317            // Generate the private key attributes 
318            PrivateKey privateKey = keypair.getPrivate();
319            entry.put( PRIVATE_KEY_AT, privateKey.getEncoded() );
320            entry.put( PRIVATE_KEY_FORMAT_AT, privateKey.getFormat() );
321            LOG.debug( "PrivateKey: {}", privateKey );
322            
323            PublicKey publicKey = keypair.getPublic();
324            entry.put( PUBLIC_KEY_AT, publicKey.getEncoded() );
325            entry.put( PUBLIC_KEY_FORMAT_AT, publicKey.getFormat() );
326            LOG.debug( "PublicKey: {}", publicKey );
327            
328            // Generate the self-signed certificate
329            Date startDate = new Date(); 
330            Date expiryDate = new Date( System.currentTimeMillis() + YEAR_MILLIS ); 
331            BigInteger serialNumber = BigInteger.valueOf( System.currentTimeMillis() );
332    
333            X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
334            X500Principal issuerName = new X500Principal( issuerDN );
335            X500Principal subjectName = new X500Principal( subjectDN );
336            
337            certGen.setSerialNumber( serialNumber );
338            certGen.setIssuerDN( issuerName );
339            certGen.setNotBefore( startDate );
340            certGen.setNotAfter( expiryDate );
341            certGen.setSubjectDN( subjectName );
342            certGen.setPublicKey( publicKey );
343            certGen.setSignatureAlgorithm( "SHA1With" + keyAlgo );
344    
345            try
346            {
347                X509Certificate cert = certGen.generate( privateKey, "BC" );
348                entry.put( USER_CERTIFICATE_AT, cert.getEncoded() );
349                LOG.debug( "X509 Certificate: {}", cert );
350            }
351            catch ( Exception e )
352            {
353                LdapException ne = new LdapException( I18n.err( I18n.ERR_292 ) );
354                ne.initCause( e );
355                throw ne;
356            }
357            
358            LOG.info( "Keys and self signed certificate successfully generated." );
359        }
360        
361    }