/* * Copyright (C) 2013 Intel Corporation * All rights reserved. */ package com.intel.mtwilson.setup.tasks; 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.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.security.KeyPair; import java.security.KeyStore; import java.security.PrivateKey; 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 CreateTlsCertificate extends LocalSetupTask { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CreateTlsCertificate.class); private String tlsDistinguishedName = "CN=mtwilson-tls,OU=mtwilson"; private String ipAlternativeName = null; private String dnsAlternativeName = null; // private File tlsKeystoreFile = null; // maybe the path would be a configuration item, currently it's hardcoded to be "mtwilson-tls.jks" under MTWILSON_CONF private String tlsKeystorePassword = null; public String getTlsDistinguishedName() { return tlsDistinguishedName; } public void setTlsDistinguishedName(String tlsDistinguishedName) { this.tlsDistinguishedName = tlsDistinguishedName; } public String getIpAlternativeName() { return ipAlternativeName; } public void setIpAlternativeName(String ipAlternativeName) { this.ipAlternativeName = ipAlternativeName; } public String getDnsAlternativeName() { return dnsAlternativeName; } public void setDnsAlternativeName(String dnsAlternativeName) { this.dnsAlternativeName = dnsAlternativeName; } public String getTlsKeystorePassword() { return tlsKeystorePassword; } public void setTlsKeystorePassword(String tlsKeystorePassword) { this.tlsKeystorePassword = tlsKeystorePassword; } @Override protected void configure() throws Exception { if(tlsDistinguishedName == null) { configuration("TLS distinguished name is not configured"); } if( ipAlternativeName == null && dnsAlternativeName == null ) { configuration("No alternative name is configured; set IP, DNS, or both"); } tlsKeystorePassword = My.configuration().getTlsKeystorePassword(); if( tlsKeystorePassword == null || tlsKeystorePassword.isEmpty() ) { tlsKeystorePassword = RandomUtil.randomBase64String(16); My.configuration().update("mtwilson.tls.keystore.password", tlsKeystorePassword); My.reset(); } // 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 TLS setup need to // log them as configuration issues here byte[] combinedPrivateKeyAndCertPemBytes; try (FileInputStream cakeyIn = new FileInputStream(My.configuration().getCaKeystoreFile())) { combinedPrivateKeyAndCertPemBytes = IOUtils.toByteArray(cakeyIn); } try { PrivateKey cakey = RsaUtil.decodePemPrivateKey(new String(combinedPrivateKeyAndCertPemBytes)); log.debug("Read cakey {} from {}", cakey.getAlgorithm(), My.configuration().getCaKeystoreFile().getAbsolutePath()); } catch(Exception 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(Exception 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 { File tlsKeystoreFile = My.configuration().getTlsKeystoreFile(); if( !tlsKeystoreFile.exists() ) { validation("TLS keystore is missing"); } // keystore exists, look for the private key and cert if( tlsKeystorePassword == null ) { configuration("TLS keystore password is not configured"); return; } SimpleKeystore keystore = new SimpleKeystore(tlsKeystoreFile, tlsKeystorePassword); for(String alias : keystore.aliases()) { log.debug("Keystore alias: {}", alias); // make sure it has a TLS private key and certificate inside try { RsaCredentialX509 credential = keystore.getRsaCredentialX509(alias, tlsKeystorePassword); log.debug("TLS certificate: {}", credential.getCertificate().getSubjectX500Principal().getName()); } catch(Exception e) { log.debug("Cannot read TLS key from keystore", e); // validation("Cannot read TLS 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; try (FileInputStream cakeyIn = new FileInputStream(My.configuration().getCaKeystoreFile())) { combinedPrivateKeyAndCertPemBytes = IOUtils.toByteArray(cakeyIn); } PrivateKey cakey = RsaUtil.decodePemPrivateKey(new String(combinedPrivateKeyAndCertPemBytes)); X509Certificate cacert = X509Util.decodePemCertificate(new String(combinedPrivateKeyAndCertPemBytes)); // create a new key pair for TLS KeyPair tlskey = RsaUtil.generateRsaKeyPair(2048); X509Builder builder = X509Builder.factory(); // builder.selfSigned(tlsDistinguishedName, tlskey); builder.issuerName(cacert); builder.issuerPrivateKey(cakey); builder.subjectName(tlsDistinguishedName); builder.subjectPublicKey(tlskey.getPublic()); if( dnsAlternativeName != null ) { builder.dnsAlternativeName(dnsAlternativeName); } if( ipAlternativeName != null ) { builder.ipAlternativeName(ipAlternativeName); } X509Certificate tlscert = 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 TLS certificate"); } File tlsKeystoreFile = My.configuration().getTlsKeystoreFile(); SimpleKeystore keystore = new SimpleKeystore(tlsKeystoreFile, tlsKeystorePassword); // keystore.addTrustedCaCertificate(cacert, cacert.getIssuerX500Principal().getName()); keystore.addKeyPairX509(tlskey.getPrivate(), tlscert, tlsDistinguishedName, tlsKeystorePassword, 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(); } }