blob: ec5d23b73a4ad68b1b035c9f26dc56ddcc71c9f9 [file] [log] [blame]
/*
* 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.net.InetAddress;
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.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 java.util.Date;
import javax.security.auth.x500.X500Principal;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.server.i18n.I18n;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;
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>
*/
public final class TlsKeyGenerator
{
private 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";
private static final String BASE_DN = "OU=Directory, O=ASF, C=US";
public static final String CERTIFICATE_PRINCIPAL_DN = "CN=ApacheDS," + BASE_DN;
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 = 1024;
public static final long YEAR_MILLIS = 365L * 24L * 3600L * 1000L;
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 org.apache.directory.api.ldap.model.exception.LdapException if there are problems accessing or decoding
*/
public static X509Certificate getCertificate( Entry entry ) throws LdapException
{
X509Certificate cert = null;
CertificateFactory certFactory = null;
try
{
certFactory = CertificateFactory.getInstance( "X.509", "BC" );
}
catch ( Exception e )
{
LdapException ne = new LdapException( I18n.err( I18n.ERR_286 ) );
ne.initCause( 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 )
{
LdapException ne = new LdapException( I18n.err( I18n.ERR_287 ) );
ne.initCause( 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 LdapException if there are format or access issues
*/
public static KeyPair getKeyPair( Entry entry ) throws LdapException
{
PublicKey publicKey = null;
PrivateKey privateKey = null;
KeyFactory keyFactory = null;
try
{
keyFactory = KeyFactory.getInstance( ALGORITHM );
}
catch ( Exception e )
{
LdapException ne = new LdapException( I18n.err( I18n.ERR_288, ALGORITHM ) );
ne.initCause( e );
throw ne;
}
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec( entry.get( PRIVATE_KEY_AT ).getBytes() );
try
{
privateKey = keyFactory.generatePrivate( privateKeySpec );
}
catch ( Exception e )
{
LdapException ne = new LdapException( I18n.err( I18n.ERR_289 ) );
ne.initCause( e );
throw ne;
}
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec( entry.get( PUBLIC_KEY_AT ).getBytes() );
try
{
publicKey = keyFactory.generatePublic( publicKeySpec );
}
catch ( InvalidKeySpecException e )
{
LdapException ne = new LdapException( I18n.err( I18n.ERR_290 ) );
ne.initCause( 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 LdapException on problems generating the content in the entry
*/
public static void addKeyPair( Entry entry ) throws LdapException
{
String subjectDn = null;
try
{
String hostName = InetAddress.getLocalHost().getHostName();
subjectDn = "CN=" + hostName + "," + BASE_DN;
}
catch ( Exception e )
{
LOG.warn( "failed to create certificate subject name from host name", e );
subjectDn = CERTIFICATE_PRINCIPAL_DN;
}
addKeyPair( entry, CERTIFICATE_PRINCIPAL_DN, subjectDn, ALGORITHM, KEY_SIZE );
}
public static void addKeyPair( Entry entry, String issuerDN, String subjectDN, String keyAlgo ) throws LdapException
{
addKeyPair( entry, issuerDN, subjectDN, keyAlgo, KEY_SIZE );
}
/**
* @see #addKeyPair(org.apache.directory.api.ldap.model.entry.Entry)
*
* @param entry The Entry to update
* @param issuerDN The issuer
* @param subjectDN The subject
* @param keyAlgo The algorithm
* @param keySize The key size
* @throws LdapException If the addition failed
*/
public static void addKeyPair( Entry entry, String issuerDN, String subjectDN, String keyAlgo, int keySize )
throws LdapException
{
Date startDate = new Date();
Date expiryDate = new Date( System.currentTimeMillis() + YEAR_MILLIS );
addKeyPair( entry, issuerDN, subjectDN, startDate, expiryDate, keyAlgo, keySize, null );
}
public static void addKeyPair( Entry entry, String issuerDN, String subjectDN, Date startDate, Date expiryDate,
String keyAlgo, int keySize, PrivateKey optionalSigningKey ) throws LdapException
{
Attribute 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( keyAlgo );
}
catch ( NoSuchAlgorithmException e )
{
LdapException ne = new LdapException( I18n.err( I18n.ERR_291 ) );
ne.initCause( e );
throw ne;
}
generator.initialize( keySize );
KeyPair keypair = generator.genKeyPair();
entry.put( KEY_ALGORITHM_AT, keyAlgo );
// 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
BigInteger serialNumber = BigInteger.valueOf( System.currentTimeMillis() );
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
X500Principal issuerName = new X500Principal( issuerDN );
X500Principal subjectName = new X500Principal( subjectDN );
certGen.setSerialNumber( serialNumber );
certGen.setIssuerDN( issuerName );
certGen.setNotBefore( startDate );
certGen.setNotAfter( expiryDate );
certGen.setSubjectDN( subjectName );
certGen.setPublicKey( publicKey );
certGen.setSignatureAlgorithm( "SHA256With" + keyAlgo );
certGen.addExtension( Extension.basicConstraints, false, new BasicConstraints( false ) );
certGen.addExtension( Extension.extendedKeyUsage, true, new ExtendedKeyUsage(
new KeyPurposeId[] { KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth } ) );
try
{
PrivateKey signingKey = optionalSigningKey != null ? optionalSigningKey : privateKey;
X509Certificate cert = certGen.generate( signingKey, "BC" );
entry.put( USER_CERTIFICATE_AT, cert.getEncoded() );
LOG.debug( "X509 Certificate: {}", cert );
}
catch ( Exception e )
{
LdapException ne = new LdapException( I18n.err( I18n.ERR_292 ) );
ne.initCause( e );
throw ne;
}
LOG.info( "Keys and self signed certificate successfully generated." );
}
}