package org.bubblecloud.ilves.security; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang.time.DateUtils; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x500.X500NameBuilder; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bubblecloud.ilves.util.PropertiesUtil; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.math.BigInteger; import java.security.*; import java.security.cert.X509Certificate; import java.util.Date; /** * Utility class for certificate management. * * @author Tommi S.E. Laukkanen */ public class CertificateUtil { /** The logger. */ private static final Logger LOGGER = Logger.getLogger(CertificateUtil.class); /** * Ensure that BouncyCastle provider is loaded. */ static { if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } } /** * The security provider. */ public static final String PROVIDER = "BC"; /** * The keystore type. */ public static final String KEY_STORE_TYPE = "BKS"; /** * The certificate asymmetric encryption algorithm. */ public static final String CERTIFICATE_ENCRYPTION_ALGORITHM = "RSA"; /** * The certificate asymmetric encryption key size. */ public static final int CERTIFICATE_KEY_SIZE = Integer.parseInt( PropertiesUtil.getProperty("site", "server-certificate-self-signed-key-size")); /** * The certificate signature algorithm. */ public static final String CERTIFICATE_SIGNATURE_ALGORITHM = "SHA256WithRSAEncryption"; /** * Checks server certificate exists and if it does not then generates self signed certificate it. * * @param certificateCommonName the certificate common name * @param ipAddress the certificate subject alternative name IP address or null * @param certificateAlias the certificate alias * @param certificatePrivateKeyPassword the certificate private key password * @param keyStorePath the key store path * @param keyStorePassword the key store password */ public static void ensureServerCertificateExists(final String certificateCommonName, final String ipAddress, final String certificateAlias, final String certificatePrivateKeyPassword, final String keyStorePath, final String keyStorePassword) { if (!hasCertificate(certificateAlias, keyStorePath, keyStorePassword)) { generateSelfSignedCertificate(certificateAlias, certificateCommonName, ipAddress, keyStorePath, keyStorePassword, certificatePrivateKeyPassword); } } /** * Checks whether key store contains given certificate. * @param certificateAlias the certificate alias * @param keyStorePath the key store path * @param keyStorePassword the key store password * @return TRUE if certificate exists. */ public static boolean hasCertificate(final String certificateAlias, final String keyStorePath, final String keyStorePassword) { try { final KeyStore keyStore = loadKeyStore(keyStorePath, keyStorePassword); return keyStore.containsAlias(certificateAlias); } catch (final Exception e) { throw new SecurityException("Error checking if certificate exists '" + certificateAlias + "' in key store: " + keyStorePath, e); } } /** * Gets certificate from key store. * @param certificateAlias the certificate alias * @param keyStorePath the key store path * @param keyStorePassword the key store password * @return the certificate or null if certificate does not exist. */ public static X509Certificate getCertificate(final String certificateAlias, final String keyStorePath, final String keyStorePassword) { try { final KeyStore keyStore = loadKeyStore(keyStorePath, keyStorePassword); return (X509Certificate) keyStore.getCertificate(certificateAlias); } catch (final Exception e) { throw new SecurityException("Error loading certificate '" + certificateAlias + "' from key store: " + keyStorePath, e); } } /** * Saves certificate to key store. * @param certificateAlias the certificate alias * @param keyStorePath the key store path * @param keyStorePassword the key store password * @param certificate the certificate */ public static void saveCertificate(final String certificateAlias, final String keyStorePath, final String keyStorePassword, final X509Certificate certificate) { try { final KeyStore keyStore = loadKeyStore(keyStorePath, keyStorePassword); keyStore.setCertificateEntry(certificateAlias, certificate); saveKeyStore(keyStore, keyStorePath, keyStorePassword); } catch (final Exception e) { throw new SecurityException("Error saving certificate '" + certificateAlias + "' to key store: " + keyStorePath, e); } } /** * Gets certificate private key from key store. * @param certificateAlias the certificate alias * @param keyStorePath the key store path * @param keyStorePassword the key store password * @param keyEntryPassword the key entry password * @return the certificate or null if certificate does not exist. */ public static PrivateKey getPrivateKey(final String certificateAlias, final String keyStorePath, final String keyStorePassword, final String keyEntryPassword) { try { final KeyStore keyStore = loadKeyStore(keyStorePath, keyStorePassword); return (PrivateKey) keyStore.getKey(certificateAlias, keyEntryPassword.toCharArray()); } catch (final Exception e) { throw new SecurityException("Error loading private key '" + certificateAlias + "' from key store: " + keyStorePath, e); } } /** * Removes certificate from key store. * @param certificateAlias the certificate alias * @param keyStorePath the key store path * @param keyStorePassword the key store password */ public static void removeCertificate(final String certificateAlias, final String keyStorePath, final String keyStorePassword) { try { final KeyStore keyStore = loadKeyStore(keyStorePath, keyStorePassword); keyStore.deleteEntry(certificateAlias); saveKeyStore(keyStore, keyStorePath, keyStorePassword); } catch (final Exception e) { throw new SecurityException("Error removing certificate '" + certificateAlias + "' from key store: " + keyStorePath, e); } } /** * Generates and self signed certificate and saves it to key store. * @param alias the certificate alias * @param commonName the certificate common name * @param ipAddress the subject alternative name IP address or null * @param keyStorePath the key store path * @param keyStorePassword the key store password * @param keyEntryPassword the key entry password */ private static void generateSelfSignedCertificate(final String alias, final String commonName, final String ipAddress, final String keyStorePath, final String keyStorePassword, final String keyEntryPassword) { try { final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(CERTIFICATE_ENCRYPTION_ALGORITHM, PROVIDER); keyGen.initialize(CERTIFICATE_KEY_SIZE); final KeyPair keyPair = keyGen.generateKeyPair(); final X509Certificate certificate = buildCertificate(commonName,ipAddress, keyPair); LOGGER.info("Generated self signed certificate: " + certificate); final KeyStore keyStore = loadKeyStore(keyStorePath, keyStorePassword); keyStore.setKeyEntry(alias, (Key) keyPair.getPrivate(), keyEntryPassword.toCharArray(), new X509Certificate[]{certificate}); saveKeyStore(keyStore, keyStorePath, keyStorePassword); } catch (final Exception e) { throw new RuntimeException("Unable to generate self signed certificate.", e); } } /** * Generates and self signed certificate and saves it to key store with fingerprint as alias. * * @param commonName the certificate common name * @param ipAddress the subject alternative name IP address or null * @param keyStorePath the key store path * @param keyStorePassword the key store password * @param keyEntryPassword the key entry password * @return fingerprint e.q. alias of the generated certificate. */ public static String generateSelfSignedCertificate(final String commonName, final String ipAddress, final String keyStorePath, final String keyStorePassword, final String keyEntryPassword) { try { final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(CERTIFICATE_ENCRYPTION_ALGORITHM, PROVIDER); keyGen.initialize(CERTIFICATE_KEY_SIZE); final KeyPair keyPair = keyGen.generateKeyPair(); final X509Certificate certificate = buildCertificate(commonName,ipAddress, keyPair); final String alias = DigestUtils.sha256Hex(certificate.getEncoded()); LOGGER.info("Generated self signed certificate: " + certificate); final KeyStore keyStore = loadKeyStore(keyStorePath, keyStorePassword); keyStore.setKeyEntry(alias, (Key) keyPair.getPrivate(), keyEntryPassword.toCharArray(), new X509Certificate[]{certificate}); saveKeyStore(keyStore, keyStorePath, keyStorePassword); return alias; } catch (final Exception e) { throw new RuntimeException("Unable to generate self signed certificate.", e); } } /** * Loads key store. * @param keyStorePath the key store path * @param keyStorePassword the key store password * @return the key store */ public static KeyStore loadKeyStore(final String keyStorePath, final String keyStorePassword) { try { final File keyStoreFile = new File(keyStorePath); if (keyStoreFile.exists()) { final FileInputStream keyStoreInputStream = new FileInputStream(keyStoreFile); final KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE, PROVIDER); keyStore.load(keyStoreInputStream, keyStorePassword.toCharArray()); keyStoreInputStream.close(); return keyStore; } else { final KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE, PROVIDER); keyStore.load(null, keyStorePassword.toCharArray()); return keyStore; } } catch (final Exception e) { throw new SecurityException("Unable to load key store: " + keyStorePath, e); } } /** * Saves key store. * @param keyStore the key store * @param keyStorePath the key store path * @param keyStorePassword the key store password */ public static void saveKeyStore(final KeyStore keyStore, final String keyStorePath, final String keyStorePassword) { try { final FileOutputStream keyStoreOutputStream = new FileOutputStream(keyStorePath, false); keyStore.store(keyStoreOutputStream, keyStorePassword.toCharArray()); keyStoreOutputStream.flush(); keyStoreOutputStream.close(); } catch (final Exception e) { throw new SecurityException("Unable to save key store: " + keyStorePath, e); } } /** * Build self signed certificate from key pair. * @param commonName the certificate common name * @param ipAddress the subject alternative name IP address or null * @param keyPair the key pair. * @return the certificate * @throws Exception if error occurs in certificate generation process. */ private static X509Certificate buildCertificate(final String commonName, final String ipAddress, KeyPair keyPair) throws Exception { final Date notBefore = new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24); final Date notAfter = DateUtils.addYears(notBefore, 100); final BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); final X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); nameBuilder.addRDN(BCStyle.CN, commonName); final SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo( ASN1Sequence.getInstance(keyPair.getPublic().getEncoded())); final X509v3CertificateBuilder certGen = new X509v3CertificateBuilder(nameBuilder.build(), serial, notBefore, notAfter, nameBuilder.build(), subjectPublicKeyInfo); if (ipAddress != null) { certGen.addExtension(Extension.subjectAlternativeName, false, new GeneralNames( new GeneralName(GeneralName.iPAddress, ipAddress))); } final ContentSigner sigGen = new JcaContentSignerBuilder(CERTIFICATE_SIGNATURE_ALGORITHM) .setProvider(PROVIDER).build(keyPair.getPrivate()); final X509Certificate cert = new JcaX509CertificateConverter().setProvider(PROVIDER) .getCertificate(certGen.build(sigGen)); return cert; } }