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 022 023import java.io.ByteArrayInputStream; 024import java.io.InputStream; 025import java.math.BigInteger; 026import java.net.InetAddress; 027import java.security.KeyFactory; 028import java.security.KeyPair; 029import java.security.KeyPairGenerator; 030import java.security.NoSuchAlgorithmException; 031import java.security.PrivateKey; 032import java.security.PublicKey; 033import java.security.Security; 034import java.security.cert.CertificateException; 035import java.security.cert.CertificateFactory; 036import java.security.cert.X509Certificate; 037import java.security.spec.EncodedKeySpec; 038import java.security.spec.InvalidKeySpecException; 039import java.security.spec.PKCS8EncodedKeySpec; 040import java.security.spec.X509EncodedKeySpec; 041import java.util.Date; 042 043import javax.security.auth.x500.X500Principal; 044 045import org.apache.directory.api.ldap.model.constants.SchemaConstants; 046import org.apache.directory.api.ldap.model.entry.Attribute; 047import org.apache.directory.api.ldap.model.entry.Entry; 048import org.apache.directory.api.ldap.model.exception.LdapException; 049import org.apache.directory.server.i18n.I18n; 050import org.bouncycastle.asn1.x509.BasicConstraints; 051import org.bouncycastle.asn1.x509.ExtendedKeyUsage; 052import org.bouncycastle.asn1.x509.Extension; 053import org.bouncycastle.asn1.x509.KeyPurposeId; 054import org.bouncycastle.jce.provider.BouncyCastleProvider; 055import org.bouncycastle.x509.X509V3CertificateGenerator; 056import org.slf4j.Logger; 057import org.slf4j.LoggerFactory; 058 059 060/** 061 * Generates the default RSA key pair for the server. 062 * 063 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 064 */ 065public final class TlsKeyGenerator 066{ 067 private TlsKeyGenerator() 068 { 069 } 070 071 private static final Logger LOG = LoggerFactory.getLogger( TlsKeyGenerator.class ); 072 073 public static final String TLS_KEY_INFO_OC = "tlsKeyInfo"; 074 public static final String PRIVATE_KEY_AT = "privateKey"; 075 public static final String PUBLIC_KEY_AT = "publicKey"; 076 public static final String KEY_ALGORITHM_AT = "keyAlgorithm"; 077 public static final String PRIVATE_KEY_FORMAT_AT = "privateKeyFormat"; 078 public static final String PUBLIC_KEY_FORMAT_AT = "publicKeyFormat"; 079 public static final String USER_CERTIFICATE_AT = "userCertificate"; 080 081 private static final String BASE_DN = "OU=Directory, O=ASF, C=US"; 082 083 public static final String CERTIFICATE_PRINCIPAL_DN = "CN=ApacheDS," + BASE_DN; 084 085 private static final String ALGORITHM = "RSA"; 086 087 /* 088 * Eventually we have to make several of these parameters configurable, 089 * however note to pass export restrictions we must use a key size of 090 * 512 or less here as the default. Users can configure this setting 091 * later based on their own legal situations. This is required to 092 * classify ApacheDS in the ECCN 5D002 category. Please see the following 093 * page for more information: 094 * 095 * http://www.apache.org/dev/crypto.html 096 * 097 * Also ApacheDS must be classified on the following page: 098 * 099 * http://www.apache.org/licenses/exports 100 */ 101 private static final int KEY_SIZE = 1024; 102 public static final long YEAR_MILLIS = 365L * 24L * 3600L * 1000L; 103 104 static 105 { 106 Security.addProvider( new BouncyCastleProvider() ); 107 } 108 109 110 /** 111 * Gets the certificate associated with the self signed TLS private/public 112 * key pair. 113 * 114 * @param entry the TLS key/cert entry 115 * @return the X509 certificate associated with that entry 116 * @throws org.apache.directory.api.ldap.model.exception.LdapException if there are problems accessing or decoding 117 */ 118 public static X509Certificate getCertificate( Entry entry ) throws LdapException 119 { 120 X509Certificate cert = null; 121 CertificateFactory certFactory = null; 122 123 try 124 { 125 certFactory = CertificateFactory.getInstance( "X.509", "BC" ); 126 } 127 catch ( Exception e ) 128 { 129 LdapException ne = new LdapException( I18n.err( I18n.ERR_286 ) ); 130 ne.initCause( e ); 131 throw ne; 132 } 133 134 byte[] certBytes = entry.get( USER_CERTIFICATE_AT ).getBytes(); 135 InputStream in = new ByteArrayInputStream( certBytes ); 136 137 try 138 { 139 cert = ( X509Certificate ) certFactory.generateCertificate( in ); 140 } 141 catch ( CertificateException e ) 142 { 143 LdapException ne = new LdapException( I18n.err( I18n.ERR_287 ) ); 144 ne.initCause( e ); 145 throw ne; 146 } 147 148 return cert; 149 } 150 151 152 /** 153 * Extracts the public private key pair from the tlsKeyInfo entry. 154 * 155 * @param entry an entry of the tlsKeyInfo objectClass 156 * @return the private and public key pair 157 * @throws LdapException if there are format or access issues 158 */ 159 public static KeyPair getKeyPair( Entry entry ) throws LdapException 160 { 161 PublicKey publicKey = null; 162 PrivateKey privateKey = null; 163 164 KeyFactory keyFactory = null; 165 try 166 { 167 keyFactory = KeyFactory.getInstance( ALGORITHM ); 168 } 169 catch ( Exception e ) 170 { 171 LdapException ne = new LdapException( I18n.err( I18n.ERR_288, ALGORITHM ) ); 172 ne.initCause( e ); 173 throw ne; 174 } 175 176 EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec( entry.get( PRIVATE_KEY_AT ).getBytes() ); 177 try 178 { 179 privateKey = keyFactory.generatePrivate( privateKeySpec ); 180 } 181 catch ( Exception e ) 182 { 183 LdapException ne = new LdapException( I18n.err( I18n.ERR_289 ) ); 184 ne.initCause( e ); 185 throw ne; 186 } 187 188 EncodedKeySpec publicKeySpec = new X509EncodedKeySpec( entry.get( PUBLIC_KEY_AT ).getBytes() ); 189 try 190 { 191 publicKey = keyFactory.generatePublic( publicKeySpec ); 192 } 193 catch ( InvalidKeySpecException e ) 194 { 195 LdapException ne = new LdapException( I18n.err( I18n.ERR_290 ) ); 196 ne.initCause( e ); 197 throw ne; 198 } 199 200 return new KeyPair( publicKey, privateKey ); 201 } 202 203 204 /** 205 * Adds a private key pair along with a self signed certificate to an 206 * entry making sure it contains the objectClasses and attributes needed 207 * to support the additions. This function is intended for creating a TLS 208 * key value pair and self signed certificate for use by the server to 209 * authenticate itself during SSL handshakes in the course of establishing 210 * an LDAPS connection or a secure LDAP connection using StartTLS. Usually 211 * this information is added to the administrator user's entry so the 212 * administrator (effectively the server) can manage these security 213 * concerns. 214 * 215 * @param entry the entry to add security attributes to 216 * @throws LdapException on problems generating the content in the entry 217 */ 218 public static void addKeyPair( Entry entry ) throws LdapException 219 { 220 String subjectDn = null; 221 try 222 { 223 String hostName = InetAddress.getLocalHost().getHostName(); 224 subjectDn = "CN=" + hostName + "," + BASE_DN; 225 } 226 catch ( Exception e ) 227 { 228 LOG.warn( "failed to create certificate subject name from host name", e ); 229 subjectDn = CERTIFICATE_PRINCIPAL_DN; 230 } 231 addKeyPair( entry, CERTIFICATE_PRINCIPAL_DN, subjectDn, ALGORITHM, KEY_SIZE ); 232 } 233 234 235 public static void addKeyPair( Entry entry, String issuerDN, String subjectDN, String keyAlgo ) throws LdapException 236 { 237 addKeyPair( entry, issuerDN, subjectDN, keyAlgo, KEY_SIZE ); 238 } 239 240 241 /** 242 * @see #addKeyPair(org.apache.directory.api.ldap.model.entry.Entry) 243 * 244 * @param entry The Entry to update 245 * @param issuerDN The issuer 246 * @param subjectDN The subject 247 * @param keyAlgo The algorithm 248 * @param keySize The key size 249 * @throws LdapException If the addition failed 250 */ 251 public static void addKeyPair( Entry entry, String issuerDN, String subjectDN, String keyAlgo, int keySize ) 252 throws LdapException 253 { 254 Date startDate = new Date(); 255 Date expiryDate = new Date( System.currentTimeMillis() + YEAR_MILLIS ); 256 addKeyPair( entry, issuerDN, subjectDN, startDate, expiryDate, keyAlgo, keySize, null ); 257 } 258 259 260 public static void addKeyPair( Entry entry, String issuerDN, String subjectDN, Date startDate, Date expiryDate, 261 String keyAlgo, int keySize, PrivateKey optionalSigningKey ) throws LdapException 262 { 263 Attribute objectClass = entry.get( SchemaConstants.OBJECT_CLASS_AT ); 264 265 if ( objectClass == null ) 266 { 267 entry.put( SchemaConstants.OBJECT_CLASS_AT, TLS_KEY_INFO_OC, SchemaConstants.INET_ORG_PERSON_OC ); 268 } 269 else 270 { 271 objectClass.add( TLS_KEY_INFO_OC, SchemaConstants.INET_ORG_PERSON_OC ); 272 } 273 274 KeyPairGenerator generator = null; 275 try 276 { 277 generator = KeyPairGenerator.getInstance( keyAlgo ); 278 } 279 catch ( NoSuchAlgorithmException e ) 280 { 281 LdapException ne = new LdapException( I18n.err( I18n.ERR_291 ) ); 282 ne.initCause( e ); 283 throw ne; 284 } 285 286 generator.initialize( keySize ); 287 KeyPair keypair = generator.genKeyPair(); 288 entry.put( KEY_ALGORITHM_AT, keyAlgo ); 289 290 // Generate the private key attributes 291 PrivateKey privateKey = keypair.getPrivate(); 292 entry.put( PRIVATE_KEY_AT, privateKey.getEncoded() ); 293 entry.put( PRIVATE_KEY_FORMAT_AT, privateKey.getFormat() ); 294 LOG.debug( "PrivateKey: {}", privateKey ); 295 296 PublicKey publicKey = keypair.getPublic(); 297 entry.put( PUBLIC_KEY_AT, publicKey.getEncoded() ); 298 entry.put( PUBLIC_KEY_FORMAT_AT, publicKey.getFormat() ); 299 LOG.debug( "PublicKey: {}", publicKey ); 300 301 // Generate the self-signed certificate 302 BigInteger serialNumber = BigInteger.valueOf( System.currentTimeMillis() ); 303 304 X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); 305 X500Principal issuerName = new X500Principal( issuerDN ); 306 X500Principal subjectName = new X500Principal( subjectDN ); 307 308 certGen.setSerialNumber( serialNumber ); 309 certGen.setIssuerDN( issuerName ); 310 certGen.setNotBefore( startDate ); 311 certGen.setNotAfter( expiryDate ); 312 certGen.setSubjectDN( subjectName ); 313 certGen.setPublicKey( publicKey ); 314 certGen.setSignatureAlgorithm( "SHA256With" + keyAlgo ); 315 certGen.addExtension( Extension.basicConstraints, false, new BasicConstraints( false ) ); 316 certGen.addExtension( Extension.extendedKeyUsage, true, new ExtendedKeyUsage( 317 new KeyPurposeId[] { KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth } ) ); 318 319 320 321 try 322 { 323 PrivateKey signingKey = optionalSigningKey != null ? optionalSigningKey : privateKey; 324 X509Certificate cert = certGen.generate( signingKey, "BC" ); 325 entry.put( USER_CERTIFICATE_AT, cert.getEncoded() ); 326 LOG.debug( "X509 Certificate: {}", cert ); 327 } 328 catch ( Exception e ) 329 { 330 LdapException ne = new LdapException( I18n.err( I18n.ERR_292 ) ); 331 ne.initCause( e ); 332 throw ne; 333 } 334 335 LOG.info( "Keys and self signed certificate successfully generated." ); 336 } 337 338}