View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.server.core.security;
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.math.BigInteger;
28  import java.net.InetAddress;
29  import java.nio.file.Files;
30  import java.nio.file.Paths;
31  import java.security.InvalidKeyException;
32  import java.security.KeyPair;
33  import java.security.KeyPairGenerator;
34  import java.security.KeyStore;
35  import java.security.KeyStoreException;
36  import java.security.NoSuchAlgorithmException;
37  import java.security.NoSuchProviderException;
38  import java.security.SecureRandom;
39  import java.security.Security;
40  import java.security.SignatureException;
41  import java.security.cert.CertificateException;
42  import java.security.cert.X509Certificate;
43  import java.util.Date;
44  import java.util.Enumeration;
45  
46  import javax.net.ssl.KeyManagerFactory;
47  
48  import org.apache.directory.api.util.Strings;
49  
50  import sun.security.x509.AlgorithmId;
51  import sun.security.x509.BasicConstraintsExtension;
52  import sun.security.x509.CertificateAlgorithmId;
53  import sun.security.x509.CertificateExtensions;
54  import sun.security.x509.CertificateSerialNumber;
55  import sun.security.x509.CertificateValidity;
56  import sun.security.x509.CertificateVersion;
57  import sun.security.x509.CertificateX509Key;
58  import sun.security.x509.DNSName;
59  import sun.security.x509.GeneralName;
60  import sun.security.x509.GeneralNames;
61  import sun.security.x509.IPAddressName;
62  import sun.security.x509.SubjectAlternativeNameExtension;
63  import sun.security.x509.X500Name;
64  import sun.security.x509.X509CertImpl;
65  import sun.security.x509.X509CertInfo;
66  
67  /**
68   * Helper class used to generate self-signed certificates, and load a KeyStore
69   * 
70   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
71   */
72  @SuppressWarnings("restriction")
73  public final class CertificateUtil
74  {
75      private static final boolean SELF_SIGNED = true;
76      private static final boolean CA_SIGNED = false;
77      private static final boolean CRITICAL = true;
78          
79      private CertificateUtil()
80      {
81          // Nothing to do
82      }
83      
84      
85      private static void setInfo( X509CertInfo info, X500Name subject, X500Name issuer, KeyPair keyPair, int days, 
86          String algoStr, boolean isCA ) 
87          throws CertificateException, IOException, NoSuchAlgorithmException
88      {
89          Date from = new Date();
90          Date to = new Date( from.getTime() + days * 86_400_000L );
91          CertificateValidity interval = new CertificateValidity( from, to );
92  
93          // Feed the certificate info structure
94          // version         [0]  EXPLICIT Version DEFAULT v1
95          // Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }
96          info.set( X509CertInfo.VERSION, new CertificateVersion( CertificateVersion.V3 ) );
97          
98          // serialNumber         CertificateSerialNumber
99          // CertificateSerialNumber  ::=  INTEGER
100         BigInteger serialNumber = new BigInteger( 64, new SecureRandom() );
101         info.set( X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber( serialNumber ) );
102 
103         // signature            AlgorithmIdentifier
104         AlgorithmId algo = AlgorithmId.get( algoStr );
105         info.set( X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId( algo ) );
106 
107         // issuer               Name
108         // Name ::= CHOICE {
109         //          RDNSequence }
110         // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
111         // RelativeDistinguishedName ::=
112         //          SET OF AttributeTypeAndValue
113         // AttributeTypeAndValue ::= SEQUENCE {
114         //          type     AttributeType,
115         //          value    AttributeValue }
116         // AttributeType ::= OBJECT IDENTIFIER
117         // AttributeValue ::= ANY DEFINED BY AttributeType
118         info.set( X509CertInfo.ISSUER, issuer );
119         
120         // validity             Validity,
121         // Validity ::= SEQUENCE {
122         //          notBefore      Time,
123         //          notAfter       Time }
124         info.set( X509CertInfo.VALIDITY, interval );
125         
126         // subject              Name
127         // Name ::= CHOICE {
128         //          RDNSequence }
129         // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
130         // RelativeDistinguishedName ::=
131         //          SET OF AttributeTypeAndValue
132         // AttributeTypeAndValue ::= SEQUENCE {
133         //          type     AttributeType,
134         //          value    AttributeValue }
135         // AttributeType ::= OBJECT IDENTIFIER
136         // AttributeValue ::= ANY DEFINED BY AttributeType
137         info.set( X509CertInfo.SUBJECT, subject );
138         
139         // subjectPublicKeyInfo SubjectPublicKeyInfo,
140         // SubjectPublicKeyInfo  ::=  SEQUENCE  {
141         //          algorithm            AlgorithmIdentifier,
142         //          subjectPublicKey     BIT STRING  }
143         info.set( X509CertInfo.KEY, new CertificateX509Key( keyPair.getPublic() ) );
144 
145         // Extensions. Basically, a subjectAltName and a Basic-Constraint 
146         CertificateExtensions extensions = new CertificateExtensions();
147 
148         // SubjectAltName
149         GeneralNames names = new GeneralNames();
150         names.add( new GeneralName( new DNSName( InetAddress.getLocalHost().getHostName() ) ) );
151         String ipAddress = InetAddress.getLocalHost().getHostAddress();
152         names.add( new GeneralName( new IPAddressName( ipAddress ) ) );
153         
154         // A wildcard
155         //names.add( new GeneralName( 
156         //    new DNSName( 
157         //        new DerValue( 
158         //            DerValue.tag_IA5String, "*.apache.org" ) ) ) );
159         SubjectAlternativeNameExtension subjectAltName = new SubjectAlternativeNameExtension( names );
160         
161         extensions.set( subjectAltName.getExtensionId().toString(), subjectAltName );
162 
163         // The Basic-Constraint,
164         BasicConstraintsExtension basicConstraint = new BasicConstraintsExtension( CRITICAL, isCA, -1 );
165         extensions.set( basicConstraint.getExtensionId().toString(), basicConstraint );
166 
167         // Inject the extensions into the cert
168         info.set( X509CertInfo.EXTENSIONS, extensions );
169     }
170     
171     
172     /**
173      * Create a self signed certificate
174      * 
175      * @param issuer The Issuer (which is the same as the subject
176      * @param keyPair The asymmetric keyPair
177      * @param days Validity number of days
178      * @param algoStr Algorithm
179      * @return A self signed CA certificate
180      * @throws CertificateException If the info store din the certificate is invalid
181      * @throws IOException If we can't store some info in the certificate
182      * @throws NoSuchAlgorithmException If the algorithm does not exist
183      * @throws SignatureException If the certificate cannot be signed
184      * @throws NoSuchProviderException  If we don't have a security provider
185      * @throws InvalidKeyException  If the KeyPair is invalid
186      */
187     public static X509Certificate generateSelfSignedCertificate( X500Name issuer, KeyPair keyPair,  int days, String algoStr ) 
188         throws CertificateException, IOException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException
189     {
190         // Create the certificate info
191         X509CertInfo info = new X509CertInfo();
192         
193         // Set the common certificate info
194         setInfo( info, issuer, issuer, keyPair, days, algoStr, SELF_SIGNED );
195         
196         // Sign the cert to identify the algorithm that's used.
197         X509CertImpl certificate = new X509CertImpl( info );
198         certificate.sign( keyPair.getPrivate(), algoStr );
199 
200         return certificate;
201     }
202     
203     
204     /**
205      * Generate a Certificate signed by a CA certificate
206      * 
207      * @param issuer The Issuer (which is the same as the subject
208      * @param keyPair The asymmetric keyPair
209      * @param days Validity number of days
210      * @param algoStr Algorithm
211      * @return A self signed CA certificate
212      * @throws CertificateException If the info store din the certificate is invalid
213      * @throws IOException If we can't store some info in the certificate
214      * @throws NoSuchAlgorithmException If the algorithm does not exist
215      * @throws SignatureException If the certificate cannot be signed
216      * @throws NoSuchProviderException  If we don't have a security provider
217      * @throws InvalidKeyException  If the KeyPair is invalid
218      */
219     public static X509Certificate generateCertificate( X500Name subject, X500Name issuer, KeyPair keyPair,  int days, String algoStr ) 
220         throws CertificateException, IOException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException
221     {
222         // Create the certificate info
223         X509CertInfo info = new X509CertInfo();
224         
225         // Set the common certificate info
226         setInfo( info, subject, issuer, keyPair, days, algoStr, CA_SIGNED );
227          
228         // Sign the cert to identify the algorithm that's used.
229         X509CertImpl certificate = new X509CertImpl( info );
230         certificate.sign( keyPair.getPrivate(), algoStr );
231 
232         return certificate;
233     }
234     
235     
236     /**
237      * Loads the digital certificate from a keystore file
238      *
239      * @param keyStoreFile The KeyStore file to load
240      * @param keyStorePasswordStr The KeyStore password
241      * @return The KeyManager factory it created 
242      * @throws Exception If the KeyStore can't be loaded
243      */
244     public static KeyManagerFactory loadKeyStore( String keyStoreFile, String keyStorePasswordStr ) throws Exception
245     {
246         char[] keyStorePassword = Strings.isEmpty( keyStorePasswordStr ) ? null : keyStorePasswordStr.toCharArray();
247 
248         if ( !Strings.isEmpty( keyStoreFile ) )
249         {
250             // We have a provided KeyStore file: read it
251             KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
252 
253             try ( InputStream is = Files.newInputStream( Paths.get( keyStoreFile ) ) )
254             {
255                 keyStore.load( is, keyStorePassword );
256             }
257     
258             /*
259              * Verify key store:
260              * * Must only contain one entry which must be a key entry
261              * * Must contain a certificate chain
262              * * The private key must be recoverable by the key store password
263              */
264             Enumeration<String> aliases = keyStore.aliases();
265             
266             if ( !aliases.hasMoreElements() )
267             {
268                 throw new KeyStoreException( "Key store is empty" );
269             }
270             
271             String alias = aliases.nextElement();
272             
273             if ( aliases.hasMoreElements() )
274             {
275                 throw new KeyStoreException( "Key store contains more than one entry" );
276             }
277             
278             if ( !keyStore.isKeyEntry( alias ) )
279             {
280                 throw new KeyStoreException( "Key store must contain a key entry" );
281             }
282             
283             if ( keyStore.getCertificateChain( alias ) == null )
284             {
285                 throw new KeyStoreException( "Key store must contain a certificate chain" );
286             }
287             
288             if ( keyStore.getKey( alias, keyStorePassword ) == null )
289             {
290                 throw new KeyStoreException( "Private key must be recoverable by the key store password" );
291             }
292     
293             // Set up key manager factory to use our key store
294             String algorithm = Security.getProperty( "ssl.KeyManagerFactory.algorithm" );
295     
296             if ( algorithm == null )
297             {
298                 algorithm = KeyManagerFactory.getDefaultAlgorithm();
299             }
300     
301             KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( algorithm );
302     
303             keyManagerFactory.init( keyStore, keyStorePassword );
304             
305             return keyManagerFactory;
306         }
307         else
308         {
309             return null;
310         }
311     }
312     
313     
314     public static File createTempKeyStore( String keyStoreName, char[] keyStorePassword ) throws IOException, KeyStoreException,
315         NoSuchAlgorithmException, CertificateException, InvalidKeyException, NoSuchProviderException, SignatureException
316     {
317         // Create a temporary keystore, be sure to remove it when exiting the test
318         File keyStoreFile = Files.createTempFile( keyStoreName, "ks" ).toFile();
319         keyStoreFile.deleteOnExit();
320         
321         KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
322         
323         try ( InputStream keyStoreData = new FileInputStream( keyStoreFile ) )
324         {
325             keyStore.load( null, keyStorePassword );
326         }
327 
328         // Generate the asymmetric keys, using EC algorithm
329         KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( "EC" );
330         KeyPair keyPair = keyPairGenerator.generateKeyPair();
331         
332         // Generate the subject's name
333         @SuppressWarnings("restriction")
334         X500Name owner = new X500Name( "apacheds", "directory", "apache", "US" );
335 
336         // Create the self-signed certificate
337         X509Certificate certificate = CertificateUtil.generateSelfSignedCertificate( owner, keyPair, 365, "SHA256WithECDSA" );
338         
339         keyStore.setKeyEntry( "apachedsKey", keyPair.getPrivate(), keyStorePassword, new X509Certificate[] { certificate } );
340         
341         try ( FileOutputStream out = new FileOutputStream( keyStoreFile ) )
342         {
343             keyStore.store( out, keyStorePassword );
344         }
345         
346         return keyStoreFile;
347     }
348 }