/* * Copyright (C) 2013 Intel Corporation * All rights reserved. */ package com.intel.mtwilson.setup.tasks; import com.intel.dcsg.cpg.crypto.CryptographyException; import com.intel.dcsg.cpg.crypto.RandomUtil; 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.pem.Pem; import com.intel.dcsg.cpg.validation.Fault; import com.intel.dcsg.cpg.x509.X509Builder; import com.intel.dcsg.cpg.x509.X509Util; import com.intel.mtwilson.My; import com.intel.mtwilson.setup.LocalSetupTask; import com.intel.mtwilson.setup.SetupException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyPair; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.UnrecoverableEntryException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.List; import org.apache.commons.io.IOUtils; /** * Depends on CreateCertificateAuthorityKey to create the cakey first * * @author jbuhacoff */ public class CreateSamlCertificate extends LocalSetupTask { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CreateSamlCertificate.class); public static final String SAML_CERTIFICATE_DN = "saml.certificate.dn"; public static final String SAML_KEYSTORE_FILE = "saml.keystore.file"; public static final String SAML_KEYSTORE_PASSWORD = "saml.keystore.password"; public static final String SAML_KEY_ALIAS = "saml.key.alias"; public static final String SAML_KEY_PASSWORD = "saml.key.password"; public String getSamlKeystoreFile() { return getConfiguration().getString(SAML_KEYSTORE_FILE, My.filesystem().getConfigurationPath() + File.separator + "mtwilson-saml.jks"); } public void setSamlKeystoreFile(String samlKeystoreFile) { getConfiguration().setString(SAML_KEYSTORE_FILE, samlKeystoreFile); } public String getSamlKeystorePassword() { return getConfiguration().getString(SAML_KEYSTORE_PASSWORD); // no default here, will return null if not configured: only the configure() method will generate a new random password if necessary } public void setSamlKeystorePassword(String samlKeystorePassword) { getConfiguration().setString(SAML_KEYSTORE_PASSWORD, samlKeystorePassword); } public String getSamlKeyAlias() { return getConfiguration().getString(SAML_KEY_ALIAS, "mtwilson-saml"); } public void setSamlKeyAlias(String samlKeyAlias) { getConfiguration().setString(SAML_KEY_ALIAS, samlKeyAlias); } public String getSamlKeyPassword() { return getConfiguration().getString(SAML_KEY_PASSWORD); // no default here, will return null if not configured: only the configure() method will generate a new random password if necessary } public void setSamlKeyPassword(String samlKeyPassword) { getConfiguration().setString(SAML_KEY_PASSWORD, samlKeyPassword); } public String getSamlCertificateDistinguishedName() { return getConfiguration().getString(SAML_CERTIFICATE_DN, "CN=mtwilson-saml,OU=mtwilson"); } public void setSamlCertificateDistinguishedName(String samlDistinguishedName) { getConfiguration().setString(SAML_CERTIFICATE_DN, samlDistinguishedName); } @Override protected void configure() throws Exception { // File samlKeystoreFile = new File(getSamlKeystoreFile()); String samlKeystorePassword = getSamlKeystorePassword(); if (samlKeystorePassword == null || samlKeystorePassword.isEmpty()) { setSamlKeystorePassword(RandomUtil.randomBase64String(16)); setSamlKeyPassword(getSamlKeystorePassword()); } // this section about checkign the ca key availability // is in configuration because it must be ready before the // setup task can even run // it's copied from the validate() method of CreateCertificateAuthorityKe // and probably this code needs to be refactored so we don't repeat it; // the challenge is whether the exception handling with configuration/validation // fault logging can be refactored because the CA setup needs to log them // as validation issues while dependent setups such as this SAML setup need to // log them as configuration issues here byte[] combinedPrivateKeyAndCertPemBytes; try (FileInputStream cakeyIn = new FileInputStream(My.configuration().getCaKeystoreFile())) { // // throws FileNotFoundException, IOException combinedPrivateKeyAndCertPemBytes = IOUtils.toByteArray(cakeyIn); // throws IOException } catch (IOException e) { log.debug("Cannot read saml cakey from {}", My.configuration().getCaKeystoreFile()); configuration("Cannot read saml ca key from: %s", My.configuration().getCaKeystoreFile()); return; } try { PrivateKey cakey = RsaUtil.decodePemPrivateKey(new String(combinedPrivateKeyAndCertPemBytes)); log.debug("Read cakey {} from {}", cakey.getAlgorithm(), My.configuration().getCaKeystoreFile().getAbsolutePath()); } catch (CryptographyException e) { log.debug("Cannot read private key from {}", My.configuration().getCaKeystoreFile().getAbsolutePath(), e); configuration("Cannot read private key from: %s", My.configuration().getCaKeystoreFile().getAbsolutePath()); } try { X509Certificate cacert = X509Util.decodePemCertificate(new String(combinedPrivateKeyAndCertPemBytes)); log.debug("Read cacert {} from {}", cacert.getSubjectX500Principal().getName(), My.configuration().getCaKeystoreFile().getAbsolutePath()); } catch (CertificateException e) { log.debug("Cannot read certificate from {}", My.configuration().getCaKeystoreFile().getAbsolutePath(), e); configuration("Cannot read certificate from: %s", My.configuration().getCaKeystoreFile().getAbsolutePath()); } } @Override protected void validate() throws Exception { if (getSamlKeystorePassword() == null) { configuration("SAML keystore password is not configured"); } if (getSamlKeyPassword() == null) { configuration("SAML key password is not configured"); } if (!getConfigurationFaults().isEmpty()) { return; } // File samlKeystoreFile = My.configuration().getSamlKeystoreFile(); File samlKeystoreFile = new File(getSamlKeystoreFile()); if (!samlKeystoreFile.exists()) { validation("SAML keystore file is missing"); } // keystore exists, look for the private key and cert SimpleKeystore keystore = new SimpleKeystore(samlKeystoreFile, getSamlKeystorePassword()); for (String alias : keystore.aliases()) { log.debug("Keystore alias: {}", alias); // make sure it has a SAML private key and certificate inside try { RsaCredentialX509 credential = keystore.getRsaCredentialX509(alias, getSamlKeystorePassword()); log.debug("SAML certificate: {}", credential.getCertificate().getSubjectX500Principal().getName()); } catch (FileNotFoundException | KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException | CertificateEncodingException | CryptographyException e) { log.debug("Cannot read SAML key from keystore", e); // validation("Cannot read SAML key from keystore"); // we are assuming the keystore only has one private key entry ... } } } @Override protected void execute() throws Exception { // load the ca key - same code as in configure() but without exception // handling byte[] combinedPrivateKeyAndCertPemBytes; PrivateKey cakey; X509Certificate cacert; try (FileInputStream cakeyIn = new FileInputStream(My.configuration().getCaKeystoreFile())) { // ; // throws FileNotFoundException, IOException combinedPrivateKeyAndCertPemBytes = IOUtils.toByteArray(cakeyIn); // throws IOException cakey = RsaUtil.decodePemPrivateKey(new String(combinedPrivateKeyAndCertPemBytes)); cacert = X509Util.decodePemCertificate(new String(combinedPrivateKeyAndCertPemBytes)); } // create a new key pair for SAML KeyPair samlkey = RsaUtil.generateRsaKeyPair(2048); X509Builder builder = X509Builder.factory(); // builder.selfSigned(samlDistinguishedName, samlkey); builder.issuerName(cacert); builder.issuerPrivateKey(cakey); builder.subjectName(getSamlCertificateDistinguishedName()); builder.subjectPublicKey(samlkey.getPublic()); X509Certificate samlcert = builder.build(); if (cacert == null) { // log.error("Failed to create certificate"); // no need to print this, if the build failed there are guaranteed to be faults to print... List<Fault> faults = builder.getFaults(); for (Fault fault : faults) { log.error(String.format("%s%s", fault.toString(), fault.getCause() == null ? "" : ": " + fault.getCause().getMessage())); validation(fault); } throw new SetupException("Cannot generate SAML certificate"); } File samlKeystoreFile = new File(getSamlKeystoreFile()); SimpleKeystore keystore = new SimpleKeystore(samlKeystoreFile, getSamlKeystorePassword()); // keystore.addTrustedCaCertificate(cacert, cacert.getIssuerX500Principal().getName()); keystore.addKeyPairX509(samlkey.getPrivate(), samlcert, getSamlCertificateDistinguishedName(), getSamlKeystorePassword(), cacert); // we have to provide the issuer chain since it's not self-signed, otherwise we'll get an exception from the KeyStore provider keystore.save(); Pem samlCert = new Pem("CERTIFICATE", samlcert.getEncoded()); Pem samlCaCert = new Pem("CERTIFICATE", cacert.getEncoded()); String certificateChainPem = String.format("%s\n%s", samlCert.toString(), samlCaCert.toString()); File certificateChainPemFile = new File(My.filesystem().getConfigurationPath() + File.separator + "saml.crt.pem"); try (FileOutputStream certificateChainPemFileOut = new FileOutputStream(certificateChainPemFile)) { IOUtils.write(certificateChainPem, certificateChainPemFileOut); } catch (IOException e) { validation(e, "Cannot write saml.crt.pem"); } // getConfiguration().setString("saml.keystore.file", samlKeystoreFile.getAbsolutePath()); // getConfiguration().setString("saml.keystore.password", samlKeystorePassword); // getConfiguration().setString("saml.key.alias", samlDistinguishedName); // getConfiguration().setString("saml.key.password", samlKeystorePassword); } }