blob: be1e40f38992a09b03a564127f1a4ea81b896361 [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.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Enumeration;
import javax.net.ssl.KeyManagerFactory;
import org.apache.directory.api.util.Strings;
import sun.security.x509.AlgorithmId;
import sun.security.x509.BasicConstraintsExtension;
import sun.security.x509.CertificateAlgorithmId;
import sun.security.x509.CertificateExtensions;
import sun.security.x509.CertificateSerialNumber;
import sun.security.x509.CertificateValidity;
import sun.security.x509.CertificateVersion;
import sun.security.x509.CertificateX509Key;
import sun.security.x509.DNSName;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.IPAddressName;
import sun.security.x509.SubjectAlternativeNameExtension;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;
/**
* Helper class used to generate self-signed certificates, and load a KeyStore
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
@SuppressWarnings("restriction")
public final class CertificateUtil
{
private static final boolean SELF_SIGNED = true;
private static final boolean CA_SIGNED = false;
private static final boolean CRITICAL = true;
private CertificateUtil()
{
// Nothing to do
}
private static void setInfo( X509CertInfo info, X500Name subject, X500Name issuer, KeyPair keyPair, int days,
String algoStr, boolean isCA )
throws CertificateException, IOException, NoSuchAlgorithmException
{
Date from = new Date();
Date to = new Date( from.getTime() + days * 86_400_000L );
CertificateValidity interval = new CertificateValidity( from, to );
// Feed the certificate info structure
// version [0] EXPLICIT Version DEFAULT v1
// Version ::= INTEGER { v1(0), v2(1), v3(2) }
info.set( X509CertInfo.VERSION, new CertificateVersion( CertificateVersion.V3 ) );
// serialNumber CertificateSerialNumber
// CertificateSerialNumber ::= INTEGER
BigInteger serialNumber = new BigInteger( 64, new SecureRandom() );
info.set( X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber( serialNumber ) );
// signature AlgorithmIdentifier
AlgorithmId algo = AlgorithmId.get( algoStr );
info.set( X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId( algo ) );
// issuer Name
// Name ::= CHOICE {
// RDNSequence }
// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
// RelativeDistinguishedName ::=
// SET OF AttributeTypeAndValue
// AttributeTypeAndValue ::= SEQUENCE {
// type AttributeType,
// value AttributeValue }
// AttributeType ::= OBJECT IDENTIFIER
// AttributeValue ::= ANY DEFINED BY AttributeType
info.set( X509CertInfo.ISSUER, issuer );
// validity Validity,
// Validity ::= SEQUENCE {
// notBefore Time,
// notAfter Time }
info.set( X509CertInfo.VALIDITY, interval );
// subject Name
// Name ::= CHOICE {
// RDNSequence }
// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
// RelativeDistinguishedName ::=
// SET OF AttributeTypeAndValue
// AttributeTypeAndValue ::= SEQUENCE {
// type AttributeType,
// value AttributeValue }
// AttributeType ::= OBJECT IDENTIFIER
// AttributeValue ::= ANY DEFINED BY AttributeType
info.set( X509CertInfo.SUBJECT, subject );
// subjectPublicKeyInfo SubjectPublicKeyInfo,
// SubjectPublicKeyInfo ::= SEQUENCE {
// algorithm AlgorithmIdentifier,
// subjectPublicKey BIT STRING }
info.set( X509CertInfo.KEY, new CertificateX509Key( keyPair.getPublic() ) );
// Extensions. Basically, a subjectAltName and a Basic-Constraint
CertificateExtensions extensions = new CertificateExtensions();
// SubjectAltName
GeneralNames names = new GeneralNames();
names.add( new GeneralName( new DNSName( InetAddress.getLocalHost().getHostName() ) ) );
String ipAddress = InetAddress.getLocalHost().getHostAddress();
names.add( new GeneralName( new IPAddressName( ipAddress ) ) );
// A wildcard
//names.add( new GeneralName(
// new DNSName(
// new DerValue(
// DerValue.tag_IA5String, "*.apache.org" ) ) ) );
SubjectAlternativeNameExtension subjectAltName = new SubjectAlternativeNameExtension( names );
extensions.set( subjectAltName.getExtensionId().toString(), subjectAltName );
// The Basic-Constraint,
BasicConstraintsExtension basicConstraint = new BasicConstraintsExtension( CRITICAL, isCA, -1 );
extensions.set( basicConstraint.getExtensionId().toString(), basicConstraint );
// Inject the extensions into the cert
info.set( X509CertInfo.EXTENSIONS, extensions );
}
/**
* Create a self signed certificate
*
* @param issuer The Issuer (which is the same as the subject
* @param keyPair The asymmetric keyPair
* @param days Validity number of days
* @param algoStr Algorithm
* @return A self signed CA certificate
* @throws CertificateException If the info store din the certificate is invalid
* @throws IOException If we can't store some info in the certificate
* @throws NoSuchAlgorithmException If the algorithm does not exist
* @throws SignatureException If the certificate cannot be signed
* @throws NoSuchProviderException If we don't have a security provider
* @throws InvalidKeyException If the KeyPair is invalid
*/
public static X509Certificate generateSelfSignedCertificate( X500Name issuer, KeyPair keyPair, int days, String algoStr )
throws CertificateException, IOException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException
{
// Create the certificate info
X509CertInfo info = new X509CertInfo();
// Set the common certificate info
setInfo( info, issuer, issuer, keyPair, days, algoStr, SELF_SIGNED );
// Sign the cert to identify the algorithm that's used.
X509CertImpl certificate = new X509CertImpl( info );
certificate.sign( keyPair.getPrivate(), algoStr );
return certificate;
}
/**
* Generate a Certificate signed by a CA certificate
*
* @param issuer The Issuer (which is the same as the subject
* @param keyPair The asymmetric keyPair
* @param days Validity number of days
* @param algoStr Algorithm
* @return A self signed CA certificate
* @throws CertificateException If the info store din the certificate is invalid
* @throws IOException If we can't store some info in the certificate
* @throws NoSuchAlgorithmException If the algorithm does not exist
* @throws SignatureException If the certificate cannot be signed
* @throws NoSuchProviderException If we don't have a security provider
* @throws InvalidKeyException If the KeyPair is invalid
*/
public static X509Certificate generateCertificate( X500Name subject, X500Name issuer, KeyPair keyPair, int days, String algoStr )
throws CertificateException, IOException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException
{
// Create the certificate info
X509CertInfo info = new X509CertInfo();
// Set the common certificate info
setInfo( info, subject, issuer, keyPair, days, algoStr, CA_SIGNED );
// Sign the cert to identify the algorithm that's used.
X509CertImpl certificate = new X509CertImpl( info );
certificate.sign( keyPair.getPrivate(), algoStr );
return certificate;
}
/**
* Loads the digital certificate from a keystore file
*
* @param keyStoreFile The KeyStore file to load
* @param keyStorePasswordStr The KeyStore password
* @return The KeyManager factory it created
* @throws Exception If the KeyStore can't be loaded
*/
public static KeyManagerFactory loadKeyStore( String keyStoreFile, String keyStorePasswordStr ) throws Exception
{
char[] keyStorePassword = Strings.isEmpty( keyStorePasswordStr ) ? null : keyStorePasswordStr.toCharArray();
if ( !Strings.isEmpty( keyStoreFile ) )
{
// We have a provided KeyStore file: read it
KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
try ( InputStream is = Files.newInputStream( Paths.get( keyStoreFile ) ) )
{
keyStore.load( is, keyStorePassword );
}
/*
* Verify key store:
* * Must only contain one entry which must be a key entry
* * Must contain a certificate chain
* * The private key must be recoverable by the key store password
*/
Enumeration<String> aliases = keyStore.aliases();
if ( !aliases.hasMoreElements() )
{
throw new KeyStoreException( "Key store is empty" );
}
String alias = aliases.nextElement();
if ( aliases.hasMoreElements() )
{
throw new KeyStoreException( "Key store contains more than one entry" );
}
if ( !keyStore.isKeyEntry( alias ) )
{
throw new KeyStoreException( "Key store must contain a key entry" );
}
if ( keyStore.getCertificateChain( alias ) == null )
{
throw new KeyStoreException( "Key store must contain a certificate chain" );
}
if ( keyStore.getKey( alias, keyStorePassword ) == null )
{
throw new KeyStoreException( "Private key must be recoverable by the key store password" );
}
// Set up key manager factory to use our key store
String algorithm = Security.getProperty( "ssl.KeyManagerFactory.algorithm" );
if ( algorithm == null )
{
algorithm = KeyManagerFactory.getDefaultAlgorithm();
}
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( algorithm );
keyManagerFactory.init( keyStore, keyStorePassword );
return keyManagerFactory;
}
else
{
return null;
}
}
public static File createTempKeyStore( String keyStoreName, char[] keyStorePassword ) throws IOException, KeyStoreException,
NoSuchAlgorithmException, CertificateException, InvalidKeyException, NoSuchProviderException, SignatureException
{
// Create a temporary keystore, be sure to remove it when exiting the test
File keyStoreFile = Files.createTempFile( keyStoreName, "ks" ).toFile();
keyStoreFile.deleteOnExit();
KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
try ( InputStream keyStoreData = new FileInputStream( keyStoreFile ) )
{
keyStore.load( null, keyStorePassword );
}
// Generate the asymmetric keys, using EC algorithm
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( "EC" );
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// Generate the subject's name
@SuppressWarnings("restriction")
X500Name owner = new X500Name( "apacheds", "directory", "apache", "US" );
// Create the self-signed certificate
X509Certificate certificate = CertificateUtil.generateSelfSignedCertificate( owner, keyPair, 365, "SHA256WithECDSA" );
keyStore.setKeyEntry( "apachedsKey", keyPair.getPrivate(), keyStorePassword, new X509Certificate[] { certificate } );
try ( FileOutputStream out = new FileOutputStream( keyStoreFile ) )
{
keyStore.store( out, keyStorePassword );
}
return keyStoreFile;
}
}