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 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.FileOutputStream; 025import java.io.IOException; 026import java.io.InputStream; 027import java.math.BigInteger; 028import java.net.InetAddress; 029import java.nio.file.Files; 030import java.nio.file.Paths; 031import java.security.InvalidKeyException; 032import java.security.KeyPair; 033import java.security.KeyPairGenerator; 034import java.security.KeyStore; 035import java.security.KeyStoreException; 036import java.security.NoSuchAlgorithmException; 037import java.security.NoSuchProviderException; 038import java.security.SecureRandom; 039import java.security.Security; 040import java.security.SignatureException; 041import java.security.cert.CertificateException; 042import java.security.cert.X509Certificate; 043import java.util.Date; 044import java.util.Enumeration; 045 046import javax.net.ssl.KeyManagerFactory; 047 048import org.apache.directory.api.util.Strings; 049 050import sun.security.x509.AlgorithmId; 051import sun.security.x509.BasicConstraintsExtension; 052import sun.security.x509.CertificateAlgorithmId; 053import sun.security.x509.CertificateExtensions; 054import sun.security.x509.CertificateSerialNumber; 055import sun.security.x509.CertificateValidity; 056import sun.security.x509.CertificateVersion; 057import sun.security.x509.CertificateX509Key; 058import sun.security.x509.DNSName; 059import sun.security.x509.GeneralName; 060import sun.security.x509.GeneralNames; 061import sun.security.x509.IPAddressName; 062import sun.security.x509.SubjectAlternativeNameExtension; 063import sun.security.x509.X500Name; 064import sun.security.x509.X509CertImpl; 065import sun.security.x509.X509CertInfo; 066 067/** 068 * Helper class used to generate self-signed certificates, and load a KeyStore 069 * 070 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 071 */ 072@SuppressWarnings("restriction") 073public final class CertificateUtil 074{ 075 private static final boolean SELF_SIGNED = true; 076 private static final boolean CA_SIGNED = false; 077 private static final boolean CRITICAL = true; 078 079 private CertificateUtil() 080 { 081 // Nothing to do 082 } 083 084 085 private static void setInfo( X509CertInfo info, X500Name subject, X500Name issuer, KeyPair keyPair, int days, 086 String algoStr, boolean isCA ) 087 throws CertificateException, IOException, NoSuchAlgorithmException 088 { 089 Date from = new Date(); 090 Date to = new Date( from.getTime() + days * 86_400_000L ); 091 CertificateValidity interval = new CertificateValidity( from, to ); 092 093 // Feed the certificate info structure 094 // version [0] EXPLICIT Version DEFAULT v1 095 // Version ::= INTEGER { v1(0), v2(1), v3(2) } 096 info.set( X509CertInfo.VERSION, new CertificateVersion( CertificateVersion.V3 ) ); 097 098 // serialNumber CertificateSerialNumber 099 // 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}