package com.grendelscan.proxy.ssl; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStore.PasswordProtection; import java.security.KeyStore.PrivateKeyEntry; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.x509.X509V3CertificateGenerator; import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.grendelscan.commons.ConfigurationManager; public class CertificateAuthority { private static final Logger LOGGER = LoggerFactory.getLogger(CertificateAuthority.class); public synchronized static CertificateAuthority getCertificateAuthority() throws GeneralSecurityException { if (certificateAuthority == null) { certificateAuthority = new CertificateAuthority(); } return certificateAuthority; } public synchronized static void regenerateCA() throws GeneralSecurityException { LOGGER.info("Regenerating CA"); try { getCertificateAuthority().createCAStore(); } catch (IOException e) { LOGGER.error("Error regenerating CA: " + e.toString(), e); throw new GeneralSecurityException(e); } } private final char[] keyPassword; private final String caName; private final String rootStoreFileName; private static final String CA_ALIAS = "CA"; private X509Certificate caCert; private final String certsDir; private KeyStore caStore; private final HashMap<String, KeyStore> keyStores; private final PasswordProtection passwordProtection; private static CertificateAuthority certificateAuthority; private final X500Principal issuerDN; private final SecureRandom randomSource; private final KeyPairGenerator keyGenerator; /** * Private constructors only */ private CertificateAuthority() throws GeneralSecurityException { LOGGER.trace("Creating certificate authority"); keyPassword = ConfigurationManager.getString("ca.keypassword", "password").toCharArray(); caName = ConfigurationManager.getString("ca.ca_name", "Grendel-Scan CA"); // rootStoreFileName = ConfigurationManager.getString("ca.rootstore_filename", "conf/ca.pfx"); rootStoreFileName = ConfigurationManager.getString("ca.rootstore_filename", "conf/ca.jks"); certsDir = ConfigurationManager.getString("ca.certs_directory", "conf/certs/"); passwordProtection = new KeyStore.PasswordProtection(keyPassword); keyStores = new HashMap<String, KeyStore>(2); Security.addProvider(new BouncyCastleProvider()); try { // issuerDN = new X500Principal("CN=" + caName); issuerDN = new X500Principal("C=GI;ST=Gibraltar;L=Gibraltar;O=Ongame Network Ltd;CN=p5-client.ongamenetwork.com;emailAddress=registry@ongame.com"); keyGenerator = KeyPairGenerator.getInstance("RSA"); keyGenerator.initialize(1024); randomSource = SecureRandom.getInstance("SHA1PRNG"); randomSource.setSeed(new Date().getTime()); caStore = getKeyStore(null); caCert = (X509Certificate) caStore.getCertificate(CA_ALIAS); } catch (Exception e) { String message = "An error occurred when loading or creating the CA: " + e.toString(); LOGGER.error(message, e); throw new GeneralSecurityException(message, e); } } private synchronized void createCAStore() throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException { createKeyStore(rootStoreFileName, null); } private KeyStore createKeyStore(final String filename, final String hostname) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, InvalidKeyException, NoSuchProviderException, SignatureException, UnrecoverableKeyException { X509Certificate[] chain; if (hostname == null) // is a CA { LOGGER.trace("Creating CA key store at " + filename); chain = new X509Certificate[1]; } else // is a host { LOGGER.trace("Creating host key store at " + filename + " for " + hostname); chain = new X509Certificate[2]; chain[1] = caCert; } KeyPair keyPair = keyGenerator.generateKeyPair(); // KeyStore outKeyStore = KeyStore.getInstance("PKCS12"); KeyStore outKeyStore = KeyStore.getInstance("JKS"); outKeyStore.load(null); if (hostname == null) // is a CA { caCert = genX509Cert(hostname, 3650, keyPair.getPublic(), keyPair.getPrivate(), null, null); // self-signed, null signer chain[0] = caCert; outKeyStore.setEntry(CA_ALIAS, new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), chain), passwordProtection); } else // is a host { PublicKey caPubKey = chain[1].getPublicKey(); PrivateKey caPrivKey = (PrivateKey) caStore.getKey(CA_ALIAS, getKeyPassword()); chain[0] = genX509Cert(hostname, 365, keyPair.getPublic(), keyPair.getPrivate(), caPubKey, caPrivKey); PrivateKey privateKey = keyPair.getPrivate(); PrivateKeyEntry pke = new KeyStore.PrivateKeyEntry(privateKey, chain); outKeyStore.setEntry(hostname, pke, passwordProtection); } outKeyStore.store(new FileOutputStream(filename), keyPassword); return outKeyStore; } private X509Certificate genX509Cert(final String commonName, final int validityDays, final PublicKey certPubKey, final PrivateKey certPrivKey, PublicKey caPubKey, PrivateKey caPrivKey) throws NoSuchAlgorithmException, InvalidKeyException, CertificateParsingException, CertificateEncodingException, NoSuchProviderException, SignatureException { LOGGER.trace("Creating x509 cert for " + commonName); boolean isCA = caPubKey == null; if (isCA) { caPubKey = certPubKey; caPrivKey = certPrivKey; } byte[] bSerialNumber = new byte[8]; randomSource.nextBytes(bSerialNumber); Calendar expiry = Calendar.getInstance(); expiry.add(Calendar.DAY_OF_YEAR, validityDays); Date startDate = new Date(); // time from which certificate is valid Date expiryDate = expiry.getTime(); // time after which certificate is not valid BigInteger serialNumber = new BigInteger(bSerialNumber).abs(); // serial number for certificate X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator(); certGenerator.setIssuerDN(issuerDN); certGenerator.setSignatureAlgorithm("SHA1withRSA"); // X500Principal subjectName = new X500Principal("CN=" + commonName); X500Principal subjectName = new X500Principal("C=GI;ST=Gibraltar;L=Gibraltar;O=Ongame Network Ltd;CN=p5-client.ongamenetwork.com;emailAddress=registry@ongame.com"); certGenerator.setSerialNumber(serialNumber); // certGenerator.setSerialNumber(new BigInteger("1")); certGenerator.setNotBefore(startDate); certGenerator.setNotAfter(expiryDate); certGenerator.setSubjectDN(subjectName); certGenerator.setPublicKey(certPubKey); certGenerator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caPubKey)); certGenerator.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(certPubKey)); certGenerator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(isCA)); return certGenerator.generate(caPrivKey, "BC"); } public char[] getKeyPassword() { return keyPassword; } public synchronized KeyStore getKeyStore(final String hostname) throws NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, InvalidKeyException, NoSuchProviderException, SignatureException, KeyStoreException, UnrecoverableKeyException { KeyStore keyStore = null; String filename; if (hostname == null) // is a ca { LOGGER.trace("Getting key store for CA"); filename = rootStoreFileName; } else // is a host { LOGGER.trace("Getting key store for host " + hostname); if (keyStores.containsKey(hostname)) { return keyStores.get(hostname); } // filename = certsDir + hostname + ".pfx"; filename = certsDir + hostname + ".jks"; } if (new File(".", filename).exists()) { LOGGER.trace(filename + " exists, loading key store"); // keyStore = KeyStore.getInstance("PKCS12"); keyStore = KeyStore.getInstance("JKS"); try { keyStore.load(new FileInputStream(filename), keyPassword); } catch (Exception e) { LOGGER.error("Failed to read key store at " + filename, e); } } if (keyStore == null) { keyStore = createKeyStore(filename, hostname); } keyStores.put(hostname, keyStore); return keyStore; } public String getRootStoreFileName() { return rootStoreFileName; } }