/*
* Copyright (C) 2012 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.as.ca;
import com.intel.mountwilson.as.common.ASConfig;
import com.intel.mtwilson.as.controller.MwKeystoreJpaController;
import com.intel.mtwilson.as.controller.exceptions.ASDataException;
import com.intel.mtwilson.as.controller.exceptions.NonexistentEntityException;
import com.intel.mtwilson.as.data.MwKeystore;
import com.intel.dcsg.cpg.crypto.CryptographyException;
import com.intel.dcsg.cpg.crypto.RsaCredentialX509;
import com.intel.dcsg.cpg.crypto.RsaUtil;
import com.intel.dcsg.cpg.crypto.SimpleKeystore;
import com.intel.dcsg.cpg.io.ByteArrayResource;
import com.intel.dcsg.cpg.x500.DN;
import com.intel.mtwilson.My;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Date;
import org.apache.commons.configuration.Configuration;
import com.intel.dcsg.cpg.x509.X509Util;
import java.security.cert.CertificateException;
/**
* The Trust Agent Certificate Authority is an RSA key pair that signs SSL
* certificates during Trust Agent installs.
*
* The required components are:
* 1. RSA key pair (self-signed or higher CA signed)
* 2. Password for the RSA private key
*
* The RSA key pair is stored in a Java keystore file in the Mt Wilson database.
* The password is provided through the Mt Wilson configuration.
*
* Call the setup() method to ensure that a keystore and CA certificate exist
* in the database before trying to use the Trust Agent CA.
*
* @author jbuhacoff
*/
public class TrustAgentCertificateAuthority {
public static final String KEYSTORE_NAME = "Mt Wilson CA";
public static final String KEYSTORE_PROVIDER = "JKS";
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrustAgentCertificateAuthority.class);
private Configuration config = ASConfig.getConfiguration();
private MwKeystoreJpaController keystoreJpa; //new MwKeystoreJpaController(getEntityManagerFactory());
private MwKeystore mwKeystore = null;
private ByteArrayResource mwKeystoreResource = null;
private SimpleKeystore keystore = null;
private X509Certificate cacert = null;
private String keystorePassword = null;
private String keyAlias = null;
private String keyPassword = null;
public TrustAgentCertificateAuthority() throws IOException {
keystoreJpa = My.jpa().mwKeystore();
}
public TrustAgentCertificateAuthority(Configuration config) {
this.config = config;
}
/**
* Preconditions:
* Configuration contains the following keys:
* mtwilson.ca.keystore.password
* mtwilson.ca.key.alias
* mtwilson.ca.key.password
*
* Behavior:
* If the keystore exists in the database and contains a CA key, nothing happens.
* If the keystore does not exist in the database, it is created.
* If the keystore exists but does not have a CA key, it is created.
* If the keystore exists but cannot be opened because the password is incorrect -- will throw a CryptographyException.
* If the keystore exists but the CA key cannot be accessed because the password is incorrect -- will throw a CryptographyException
*
* The general contract of setup() is that it returns if everything is ok, and
* throws an Exception if there is any error. See preconditions for
* required inputs.
*
* @throws CryptographyException
* @throws GeneralSecurityException
* @throws IOException
* @throws NonexistentEntityException
* @throws Exception
*/
public void setup() throws CryptographyException, GeneralSecurityException, IOException, NonexistentEntityException, KeyStoreException, NoSuchAlgorithmException, CertificateException, ASDataException {
setupConfiguration();
setupKeystore();
setupCACert();
}
/**
* @deprecated see code in setup-console
*
* For simplicity, the CSR format is simply a self-signed certificate with
* the details that the requestor would like to use in the CA-signed certificate.
*
* Currently the only self-signed details that are honored are the Common Name
* and the IP Address Alternative Name.
* The rest of the output details are determined by the nature of this method,
* which is to sign Trust Agent SSL certificates.
*
* The output certificate should be valid for SSL but not for other functions.
*
* Preconditions:
* Configuration contains the following keys:
* mtwilson.ca.keystore.password
* mtwilson.ca.key.alias
*
* @param csr should be a self-signed X509Certificate with Subject Name and Alternative Name (IP Address)
* @param authorizationPassword required in order to sign the certificate. (mtwilson.ca.key.password)
* @return
*/
public X509Certificate signSslCertificate(X509Certificate csr, String authorizationPassword) throws CryptographyException, FileNotFoundException {
RsaCredentialX509 ca = getCA(authorizationPassword);
String subjectName = csr.getSubjectX500Principal().getName(); // this is a string like CN=abc, O=xyz, C=US
DN dn = new DN(subjectName);
String subjectCommonName = dn.getCommonName() != null ? dn.getCommonName() : subjectName;
String alternativeName = X509Util.ipAddressAlternativeName(csr);
if( alternativeName != null ) {
alternativeName = "ip:" + alternativeName;
}
int days = 3650;
try {
X509Certificate cert = RsaUtil.createX509CertificateWithIssuer(csr.getPublicKey(), subjectCommonName, alternativeName, days, ca.getPrivateKey(), ca.getCertificate());
return cert;
}
catch(IOException e) {
throw new CryptographyException("Cannot create X509 certificate:", e);
}
}
private void setupConfiguration() {
keystorePassword = config.getString("mtwilson.ca.keystore.password");
keyAlias = config.getString("mtwilson.ca.key.alias");
keyPassword = config.getString("mtwilson.ca.key.password");
if( keystorePassword == null || keyAlias == null || keyPassword == null ) {
throw new IllegalStateException("One or more required configuration settings missing: mtwilson.ca.keystore.password, mtwilson.ca.key.alias, mtwilson.ca.key.password");
}
}
/**
* Precondition: setupConfiguration()
*
* @throws CryptographyException
*/
private void setupKeystore() throws CryptographyException {
if( !isKeystoreCreated() ) {
createKeystoreWithPassword();
}
}
/**
* Precondition: setupKeystore()
*
* @throws CryptographyException
* @throws GeneralSecurityException
* @throws IOException
* @throws NonexistentEntityException
* @throws Exception
*/
private void setupCACert() throws CryptographyException, GeneralSecurityException, IOException, NonexistentEntityException, KeyStoreException,
NoSuchAlgorithmException, CertificateException, ASDataException {
if( !isCACertCreated() ) {
createCA();
saveKeystore();
}
}
/**
* Precondition: setupConfiguration()
*
* If this function returns true, then the keystore is non-null
*
* @return true if the CA keystore is available
*/
private boolean isKeystoreCreated() {
if( mwKeystore != null && keystore != null ) {
return true;
}
if( mwKeystore == null ) {
mwKeystore = keystoreJpa.findMwKeystoreByName(KEYSTORE_NAME);
}
if( mwKeystore != null && mwKeystore.getKeystore() != null ) {
try {
openKeystoreWithPassword();
}
catch(CryptographyException e) {
log.error("Cannot open keystore", e);
keystore = null;
}
}
return mwKeystore != null && keystore != null;
}
/**
* Precondition: isKeystoreCreated() == true
*
* If this function returns true, then the cacert is non-null
*
* @return true if the CA certificate is available
*/
private boolean isCACertCreated() {
if( cacert != null ) {
return true;
}
try {
try {
cacert = getCACert();
if( cacert != null ) {
return true;
}
}
catch(FileNotFoundException e) {
return false;
}
return false;
}
catch(CryptographyException e) {
return false;
}
}
/**
* Precondition: setupConfiguration()
*
* @param password
* @throws CryptographyException
*/
private void createKeystoreWithPassword() throws CryptographyException {
try {
mwKeystoreResource = new ByteArrayResource();
keystore = new SimpleKeystore(mwKeystoreResource, keystorePassword);
}
catch(KeyManagementException e) {
throw new CryptographyException("Cannot create new keystore", e);
}
}
/**
* Precondition: setupConfiguration()
*
* @throws CryptographyException if cannot open the keystore
*/
private void openKeystoreWithPassword() throws CryptographyException {
try {
mwKeystoreResource = new ByteArrayResource(mwKeystore.getKeystore());
keystore = new SimpleKeystore(mwKeystoreResource, keystorePassword);
}
catch(KeyManagementException e) {
throw new CryptographyException("Cannot open existing keystore", e);
}
}
private void saveKeystore() throws NonexistentEntityException, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,NonexistentEntityException, ASDataException {
if( keystore == null ) { throw new IllegalStateException("Keystore is null"); }
keystore.save(); // makes the keystore available in mwKeystoreResource.toByteArray()
if( mwKeystore == null ) {
mwKeystore = new MwKeystore();
mwKeystore.setName(KEYSTORE_NAME);
mwKeystore.setComment(String.format("Automatically created on %s", new Date().toString()));
mwKeystore.setProvider(KEYSTORE_PROVIDER);
mwKeystore.setKeystore(mwKeystoreResource.toByteArray());
keystoreJpa.create(mwKeystore);
}
else {
mwKeystore.setKeystore(mwKeystoreResource.toByteArray());
keystoreJpa.edit(mwKeystore);
}
}
private RsaCredentialX509 getCA(String password) throws CryptographyException, FileNotFoundException {
try {
RsaCredentialX509 credential = keystore.getRsaCredentialX509(keyAlias, password);
return credential;
}
catch(NoSuchAlgorithmException e) {
throw new CryptographyException("Cannot load CA Cert from keystore", e);
}
catch(UnrecoverableEntryException e) {
throw new CryptographyException("Cannot load CA Cert from keystore", e);
}
catch(KeyStoreException e) {
throw new CryptographyException("Cannot load CA Cert from keystore", e);
}
catch(CertificateEncodingException e) {
throw new CryptographyException("Cannot load CA Cert from keystore", e);
}
}
private RsaCredentialX509 getCA() throws CryptographyException, FileNotFoundException {
return getCA(keyPassword);
}
private X509Certificate getCACert() throws CryptographyException, FileNotFoundException {
return getCA().getCertificate();
}
private void createCA() throws CryptographyException, GeneralSecurityException, IOException {
try {
KeyPair keypair = RsaUtil.generateRsaKeyPair(RsaUtil.MINIMUM_RSA_KEY_SIZE);
cacert = RsaUtil.generateX509Certificate("CN=Mt Wilson CA", keypair, 3650);
keystore.addKeyPairX509(keypair.getPrivate(), cacert, keyAlias, keyPassword);
} catch (NoSuchAlgorithmException e) {
throw new CryptographyException("Cannot create new CA keypair", e);
}
}
}