| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.server.core.security; |
| |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.InputStream; |
| import java.math.BigInteger; |
| import java.security.KeyFactory; |
| import java.security.KeyPair; |
| import java.security.KeyPairGenerator; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.PrivateKey; |
| import java.security.PublicKey; |
| import java.security.Security; |
| import java.util.Date; |
| |
| import javax.naming.NamingException; |
| import javax.security.auth.x500.X500Principal; |
| |
| import java.security.cert.CertificateException; |
| import java.security.cert.CertificateFactory; |
| import java.security.cert.X509Certificate; |
| import java.security.spec.EncodedKeySpec; |
| import java.security.spec.InvalidKeySpecException; |
| import java.security.spec.PKCS8EncodedKeySpec; |
| import java.security.spec.X509EncodedKeySpec; |
| |
| import org.apache.directory.server.core.entry.ServerEntry; |
| import org.apache.directory.shared.ldap.constants.SchemaConstants; |
| import org.apache.directory.shared.ldap.entry.EntryAttribute; |
| import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| import org.bouncycastle.x509.X509V1CertificateGenerator; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * Generates the default RSA key pair for the server. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| * @version $Rev$, $Date$ |
| */ |
| public class TlsKeyGenerator |
| { |
| private static final Logger LOG = LoggerFactory.getLogger( TlsKeyGenerator.class ); |
| |
| public static final String TLS_KEY_INFO_OC = "tlsKeyInfo"; |
| public static final String PRIVATE_KEY_AT = "privateKey"; |
| public static final String PUBLIC_KEY_AT = "publicKey"; |
| public static final String KEY_ALGORITHM_AT = "keyAlgorithm"; |
| public static final String PRIVATE_KEY_FORMAT_AT = "privateKeyFormat"; |
| public static final String PUBLIC_KEY_FORMAT_AT = "publicKeyFormat"; |
| public static final String USER_CERTIFICATE_AT = "userCertificate"; |
| |
| public static final String CERTIFICATE_PRINCIPAL_DN = |
| "CN=ApacheDS, OU=Directory, O=ASF, C=US"; |
| private static final String ALGORITHM = "RSA"; |
| |
| /* |
| * Eventually we have to make several of these parameters configurable, |
| * however note to pass export restrictions we must use a key size of |
| * 512 or less here as the default. Users can configure this setting |
| * later based on their own legal situations. This is required to |
| * classify ApacheDS in the ECCN 5D002 category. Please see the following |
| * page for more information: |
| * |
| * http://www.apache.org/dev/crypto.html |
| * |
| * Also ApacheDS must be classified on the following page: |
| * |
| * http://www.apache.org/licenses/exports |
| */ |
| private static final int KEY_SIZE = 512; |
| private static final long YEAR_MILLIS = 365*24*3600*1000; |
| |
| |
| static |
| { |
| Security.addProvider( new BouncyCastleProvider() ); |
| } |
| |
| |
| /** |
| * Gets the certificate associated with the self signed TLS private/public |
| * key pair. |
| * |
| * @param entry the TLS key/cert entry |
| * @return the X509 certificate associated with that entry |
| * @throws NamingException if there are problems accessing or decoding |
| */ |
| public static X509Certificate getCertificate( ServerEntry entry ) throws NamingException |
| { |
| X509Certificate cert = null; |
| CertificateFactory certFactory = null; |
| |
| try |
| { |
| certFactory = CertificateFactory.getInstance( "X.509", "BC" ); |
| } |
| catch ( Exception e ) |
| { |
| NamingException ne = new NamingException( "Failed to get BC Certificate factory for algorithm: X.509" ); |
| ne.setRootCause( e ); |
| throw ne; |
| } |
| |
| byte[] certBytes = entry.get( USER_CERTIFICATE_AT ).getBytes(); |
| InputStream in = new ByteArrayInputStream( certBytes ); |
| |
| try |
| { |
| cert = ( X509Certificate ) certFactory.generateCertificate( in ); |
| } |
| catch ( CertificateException e ) |
| { |
| NamingException ne = new NamingException( "Bad certificate format." ); |
| ne.setRootCause( e ); |
| throw ne; |
| } |
| |
| return cert; |
| } |
| |
| |
| /** |
| * Extracts the public private key pair from the tlsKeyInfo entry. |
| * |
| * @param entry an entry of the tlsKeyInfo objectClass |
| * @return the private and public key pair |
| * @throws NamingException if there are format or access issues |
| */ |
| public static KeyPair getKeyPair( ServerEntry entry ) throws NamingException |
| { |
| PublicKey publicKey = null; |
| PrivateKey privateKey = null; |
| |
| KeyFactory keyFactory = null; |
| try |
| { |
| keyFactory = KeyFactory.getInstance( ALGORITHM ); |
| } |
| catch ( Exception e ) |
| { |
| NamingException ne = new NamingException( "Failed to get key factory for algorithm: " + ALGORITHM ); |
| ne.setRootCause( e ); |
| throw ne; |
| } |
| |
| EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec( entry.get( PRIVATE_KEY_AT ).getBytes() ); |
| try |
| { |
| privateKey = keyFactory.generatePrivate( privateKeySpec ); |
| } |
| catch ( Exception e ) |
| { |
| NamingException ne = new NamingException( "Bad private key format." ); |
| ne.setRootCause( e ); |
| throw ne; |
| } |
| |
| EncodedKeySpec publicKeySpec = new X509EncodedKeySpec( entry.get( PUBLIC_KEY_AT ).getBytes() ); |
| try |
| { |
| publicKey = keyFactory.generatePublic( publicKeySpec ); |
| } |
| catch ( InvalidKeySpecException e ) |
| { |
| NamingException ne = new NamingException( "Bad public key format." ); |
| ne.setRootCause( e ); |
| throw ne; |
| } |
| |
| return new KeyPair( publicKey, privateKey ); |
| } |
| |
| |
| /** |
| * Adds a private key pair along with a self signed certificate to an |
| * entry making sure it contains the objectClasses and attributes needed |
| * to support the additions. This function is intended for creating a TLS |
| * key value pair and self signed certificate for use by the server to |
| * authenticate itself during SSL handshakes in the course of establishing |
| * an LDAPS connection or a secure LDAP connection using StartTLS. Usually |
| * this information is added to the administrator user's entry so the |
| * administrator (effectively the server) can manage these security |
| * concerns. |
| * |
| * @param entry the entry to add security attributes to |
| * @throws NamingException on problems generating the content in the entry |
| */ |
| public static void addKeyPair( ServerEntry entry ) throws NamingException |
| { |
| EntryAttribute objectClass = entry.get( SchemaConstants.OBJECT_CLASS_AT ); |
| |
| if ( objectClass == null ) |
| { |
| entry.put( SchemaConstants.OBJECT_CLASS_AT, TLS_KEY_INFO_OC, SchemaConstants.INET_ORG_PERSON_OC ); |
| } |
| else |
| { |
| objectClass.add( TLS_KEY_INFO_OC, SchemaConstants.INET_ORG_PERSON_OC ); |
| } |
| |
| KeyPairGenerator generator = null; |
| try |
| { |
| generator = KeyPairGenerator.getInstance( ALGORITHM ); |
| } |
| catch ( NoSuchAlgorithmException e ) |
| { |
| NamingException ne = new NamingException( "Cannot generate key pair for TLS" ); |
| ne.setRootCause( e ); |
| throw ne; |
| } |
| |
| generator.initialize( KEY_SIZE ); |
| KeyPair keypair = generator.genKeyPair(); |
| entry.put( KEY_ALGORITHM_AT, ALGORITHM ); |
| |
| // Generate the private key attributes |
| PrivateKey privateKey = keypair.getPrivate(); |
| entry.put( PRIVATE_KEY_AT, privateKey.getEncoded() ); |
| entry.put( PRIVATE_KEY_FORMAT_AT, privateKey.getFormat() ); |
| LOG.debug( "PrivateKey: {}", privateKey ); |
| |
| PublicKey publicKey = keypair.getPublic(); |
| entry.put( PUBLIC_KEY_AT, publicKey.getEncoded() ); |
| entry.put( PUBLIC_KEY_FORMAT_AT, publicKey.getFormat() ); |
| LOG.debug( "PublicKey: {}", publicKey ); |
| |
| // Generate the self-signed certificate |
| Date startDate = new Date(); |
| Date expiryDate = new Date( System.currentTimeMillis() + YEAR_MILLIS ); |
| BigInteger serialNumber = BigInteger.valueOf( System.currentTimeMillis() ); |
| |
| X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); |
| X500Principal dnName = new X500Principal( CERTIFICATE_PRINCIPAL_DN ); |
| |
| certGen.setSerialNumber( serialNumber ); |
| certGen.setIssuerDN( dnName ); |
| certGen.setNotBefore( startDate ); |
| certGen.setNotAfter( expiryDate ); |
| certGen.setSubjectDN( dnName ); |
| certGen.setPublicKey( publicKey ); |
| certGen.setSignatureAlgorithm( "SHA1With" + ALGORITHM ); |
| |
| try |
| { |
| X509Certificate cert = certGen.generate( privateKey, "BC" ); |
| entry.put( USER_CERTIFICATE_AT, cert.getEncoded() ); |
| LOG.debug( "X509 Certificate: {}", cert ); |
| } |
| catch ( Exception e ) |
| { |
| NamingException ne = new NamingException( "Cannot generate self signed certificate." ); |
| ne.setRootCause( e ); |
| throw ne; |
| } |
| |
| LOG.info( "Keys and self signed certificate successfully generated." ); |
| } |
| } |