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 */
020package org.apache.directory.server.core.security;
021
022
023import java.io.ByteArrayInputStream;
024import java.io.InputStream;
025import java.math.BigInteger;
026import java.net.InetAddress;
027import java.security.KeyFactory;
028import java.security.KeyPair;
029import java.security.KeyPairGenerator;
030import java.security.NoSuchAlgorithmException;
031import java.security.PrivateKey;
032import java.security.PublicKey;
033import java.security.Security;
034import java.security.cert.CertificateException;
035import java.security.cert.CertificateFactory;
036import java.security.cert.X509Certificate;
037import java.security.spec.EncodedKeySpec;
038import java.security.spec.InvalidKeySpecException;
039import java.security.spec.PKCS8EncodedKeySpec;
040import java.security.spec.X509EncodedKeySpec;
041import java.util.Date;
042
043import javax.security.auth.x500.X500Principal;
044
045import org.apache.directory.api.ldap.model.constants.SchemaConstants;
046import org.apache.directory.api.ldap.model.entry.Attribute;
047import org.apache.directory.api.ldap.model.entry.Entry;
048import org.apache.directory.api.ldap.model.exception.LdapException;
049import org.apache.directory.server.i18n.I18n;
050import org.bouncycastle.asn1.x509.BasicConstraints;
051import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
052import org.bouncycastle.asn1.x509.Extension;
053import org.bouncycastle.asn1.x509.KeyPurposeId;
054import org.bouncycastle.jce.provider.BouncyCastleProvider;
055import org.bouncycastle.x509.X509V3CertificateGenerator;
056import org.slf4j.Logger;
057import org.slf4j.LoggerFactory;
058
059
060/**
061 * Generates the default RSA key pair for the server.
062 *
063 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
064 */
065public final class TlsKeyGenerator
066{
067    private TlsKeyGenerator()
068    {
069    }
070
071    private static final Logger LOG = LoggerFactory.getLogger( TlsKeyGenerator.class );
072
073    public static final String TLS_KEY_INFO_OC = "tlsKeyInfo";
074    public static final String PRIVATE_KEY_AT = "privateKey";
075    public static final String PUBLIC_KEY_AT = "publicKey";
076    public static final String KEY_ALGORITHM_AT = "keyAlgorithm";
077    public static final String PRIVATE_KEY_FORMAT_AT = "privateKeyFormat";
078    public static final String PUBLIC_KEY_FORMAT_AT = "publicKeyFormat";
079    public static final String USER_CERTIFICATE_AT = "userCertificate";
080
081    private static final String BASE_DN = "OU=Directory, O=ASF, C=US";
082
083    public static final String CERTIFICATE_PRINCIPAL_DN = "CN=ApacheDS," + BASE_DN;
084
085    private static final String ALGORITHM = "RSA";
086
087    /* 
088     * Eventually we have to make several of these parameters configurable,
089     * however note to pass export restrictions we must use a key size of
090     * 512 or less here as the default.  Users can configure this setting
091     * later based on their own legal situations.  This is required to 
092     * classify ApacheDS in the ECCN 5D002 category.  Please see the following
093     * page for more information:
094     * 
095     *    http://www.apache.org/dev/crypto.html
096     * 
097     * Also ApacheDS must be classified on the following page:
098     * 
099     *    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}