/* * * Copyright (c) 2013 - 2017 Lijun Liao * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY * THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT * OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the XiPKI software without * disclosing the source code of your own applications. * * For more information, please contact Lijun Liao at this * address: lijun.liao@gmail.com */ package org.xipki.pki.ca.server.mgmt.api.conf; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.URL; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.validation.SchemaFactory; import org.bouncycastle.util.encoders.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xipki.commons.common.InvalidConfException; import org.xipki.commons.common.ObjectCreationException; import org.xipki.commons.common.util.IoUtil; import org.xipki.commons.common.util.ParamUtil; import org.xipki.commons.common.util.XmlUtil; import org.xipki.commons.security.ConcurrentContentSigner; import org.xipki.commons.security.SecurityFactory; import org.xipki.commons.security.SignerConf; import org.xipki.commons.security.exception.XiSecurityException; import org.xipki.commons.security.util.X509Util; import org.xipki.pki.ca.api.NameId; import org.xipki.pki.ca.api.profile.CertValidity; import org.xipki.pki.ca.server.mgmt.api.CaEntry; import org.xipki.pki.ca.server.mgmt.api.CaHasRequestorEntry; import org.xipki.pki.ca.server.mgmt.api.CaMgmtException; import org.xipki.pki.ca.server.mgmt.api.CaStatus; import org.xipki.pki.ca.server.mgmt.api.CertprofileEntry; import org.xipki.pki.ca.server.mgmt.api.CmpControlEntry; import org.xipki.pki.ca.server.mgmt.api.CmpRequestorEntry; import org.xipki.pki.ca.server.mgmt.api.CmpResponderEntry; import org.xipki.pki.ca.server.mgmt.api.PublisherEntry; import org.xipki.pki.ca.server.mgmt.api.ValidityMode; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.CAConfType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.CaHasRequestorType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.CaType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.CmpcontrolType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.CrlsignerType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.FileOrBinaryType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.FileOrValueType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.NameValueType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.ObjectFactory; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.ProfileType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.PublisherType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.RequestorType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.ResponderType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.StringsType; import org.xipki.pki.ca.server.mgmt.api.conf.jaxb.X509CaInfoType; import org.xipki.pki.ca.server.mgmt.api.x509.ScepEntry; import org.xipki.pki.ca.server.mgmt.api.x509.X509CaEntry; import org.xipki.pki.ca.server.mgmt.api.x509.X509CaUris; import org.xipki.pki.ca.server.mgmt.api.x509.X509CrlSignerEntry; import org.xml.sax.SAXException; /** * @author Lijun Liao * @since 2.1.0 */ public class CaConf { private static final Logger LOG = LoggerFactory.getLogger(CaConf.class); private final Map<String, String> properties = new HashMap<>(); private final Map<String, CmpControlEntry> cmpControls = new HashMap<>(); private final Map<String, CmpResponderEntry> responders = new HashMap<>(); private final Map<String, String> environments = new HashMap<>(); private final Map<String, X509CrlSignerEntry> crlSigners = new HashMap<>(); private final Map<String, CmpRequestorEntry> requestors = new HashMap<>(); private final Map<String, PublisherEntry> publishers = new HashMap<>(); private final Map<String, CertprofileEntry> certprofiles = new HashMap<>(); private final Map<String, SingleCaConf> cas = new HashMap<>(); private final Map<String, ScepEntry> sceps = new HashMap<>(); public CaConf(final String confFilename, final SecurityFactory securityFactory) throws IOException, InvalidConfException, CaMgmtException, JAXBException, SAXException { ParamUtil.requireNonBlank("confFilename", confFilename); ParamUtil.requireNonNull("securityFactory", securityFactory); int fileExtIndex = confFilename.lastIndexOf('.'); String fileExt = null; if (fileExtIndex != -1) { fileExt = confFilename.substring(fileExtIndex + 1); } File confFile = new File(confFilename); ZipFile zipFile = null; InputStream caConfStream = null; try { if ("xml".equalsIgnoreCase(fileExt)) { LOG.info("read the configuration file {} as an XML file", confFilename); caConfStream = new FileInputStream(confFile); } else if ("zip".equalsIgnoreCase(fileExt)) { LOG.info("read the configuration file {} as a ZIP file", confFilename); zipFile = new ZipFile(confFile); caConfStream = zipFile.getInputStream(zipFile.getEntry("caconf.xml")); } else { try { LOG.info("try to read the configuration file {} as a ZIP file", confFilename); zipFile = new ZipFile(confFile); caConfStream = zipFile.getInputStream(zipFile.getEntry("caconf.xml")); } catch (ZipException ex) { LOG.info("the configuration file {} is not a ZIP file, try as an XML file", confFilename); zipFile = null; caConfStream = new FileInputStream(confFile); } } String baseDir = (zipFile == null) ? null : confFile.getParentFile().getPath(); JAXBContext context = JAXBContext.newInstance(ObjectFactory.class); SchemaFactory schemaFact = SchemaFactory.newInstance( javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI); URL url = CaConf.class.getResource("/xsd/caconf.xsd"); Unmarshaller jaxbUnmarshaller = context.createUnmarshaller(); jaxbUnmarshaller.setSchema(schemaFact.newSchema(url)); CAConfType root = (CAConfType) ((JAXBElement<?>) jaxbUnmarshaller.unmarshal(caConfStream)).getValue(); init(root, baseDir, zipFile, securityFactory); } catch (JAXBException ex) { throw XmlUtil.convert(ex); } finally { if (caConfStream != null) { try { caConfStream.close(); } catch (IOException ex) { LOG.info("could not clonse caConfStream", ex.getMessage()); } } if (zipFile != null) { try { zipFile.close(); } catch (IOException ex) { LOG.info("could not clonse zipFile", ex.getMessage()); } } } } public static void marshal(final CAConfType jaxb, final OutputStream out) throws JAXBException, SAXException { ParamUtil.requireNonNull("jaxb", jaxb); ParamUtil.requireNonNull("out", out); try { JAXBContext context = JAXBContext.newInstance(ObjectFactory.class); SchemaFactory schemaFact = SchemaFactory.newInstance( javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI); URL url = CaConf.class.getResource("/xsd/caconf.xsd"); Marshaller jaxbMarshaller = context.createMarshaller(); jaxbMarshaller.setSchema(schemaFact.newSchema(url)); jaxbMarshaller.marshal(new ObjectFactory().createCAConf(jaxb), out); } catch (JAXBException ex) { throw XmlUtil.convert(ex); } } private void init(final CAConfType jaxb, final String baseDir, final ZipFile zipFile, final SecurityFactory securityFactory) throws IOException, InvalidConfException, CaMgmtException { // Properties if (baseDir != null) { properties.put("baseDir", baseDir); } if (jaxb.getProperties() != null) { for (NameValueType m : jaxb.getProperties().getProperty()) { String name = m.getName(); if (properties.containsKey(name)) { throw new InvalidConfException("Property " + name + " already defined"); } properties.put(name, m.getValue()); } } // CMP controls if (jaxb.getCmpcontrols() != null) { for (CmpcontrolType m : jaxb.getCmpcontrols().getCmpcontrol()) { CmpControlEntry en = new CmpControlEntry(m.getName(), getValue(m.getConf(), zipFile)); addCmpControl(en); } } // Responders if (jaxb.getResponders() != null) { for ( ResponderType m : jaxb.getResponders().getResponder()) { CmpResponderEntry en = new CmpResponderEntry(m.getName(), expandConf(m.getType()), getValue(m.getConf(), zipFile), getBase64Binary(m.getCert(), zipFile)); addResponder(en); } } // Environments if (jaxb.getEnvironments() != null) { for (NameValueType m : jaxb.getEnvironments().getEnvironment()) { addEnvironment(m.getName(), expandConf(m.getValue())); } } // CRL signers if (jaxb.getCrlsigners() != null) { for (CrlsignerType m : jaxb.getCrlsigners().getCrlsigner()) { X509CrlSignerEntry en = new X509CrlSignerEntry(m.getName(), expandConf(m.getSignerType()), getValue(m.getSignerConf(), zipFile), getBase64Binary(m.getSignerCert(), zipFile), expandConf(m.getCrlControl())); addCrlSigner(en); } } // Requesters if (jaxb.getRequestors() != null) { for (RequestorType m : jaxb.getRequestors().getRequestor()) { CmpRequestorEntry en = new CmpRequestorEntry(new NameId(null, m.getName()), getBase64Binary(m.getCert(), zipFile)); addRequestor(en); } } // Publishers if (jaxb.getPublishers() != null) { for (PublisherType m : jaxb.getPublishers().getPublisher()) { PublisherEntry en = new PublisherEntry(new NameId(null, m.getName()), expandConf(m.getType()), getValue(m.getConf(), zipFile)); addPublisher(en); } } // CertProfiles if (jaxb.getProfiles() != null) { for (ProfileType m : jaxb.getProfiles().getProfile()) { CertprofileEntry en = new CertprofileEntry(new NameId(null, m.getName()), expandConf(m.getType()), getValue(m.getConf(), zipFile)); addProfile(en); } } // CAs if (jaxb.getCas() != null) { for (CaType m : jaxb.getCas().getCa()) { String name = m.getName(); GenSelfIssued genSelfIssued = null; X509CaEntry caEntry = null; if (m.getCaInfo() != null) { X509CaInfoType ci = m.getCaInfo().getX509Ca(); if (ci.getGenSelfIssued() != null) { String certFilename = null; if (ci.getCert() != null) { if (ci.getCert().getFile() != null) { certFilename = expandConf(ci.getCert().getFile()); } else { throw new InvalidConfException("cert.file of CA " + name + " must not be null"); } } byte[] csr = getBinary(ci.getGenSelfIssued().getCsr(), zipFile); BigInteger serialNumber = null; String str = ci.getGenSelfIssued().getSerialNumber(); if (str != null) { str = str.toUpperCase(); if (str.startsWith("0X")) { serialNumber = new BigInteger(str.substring(2), 16); } else { serialNumber = new BigInteger(str); } } genSelfIssued = new GenSelfIssued(ci.getGenSelfIssued().getProfile(), csr, serialNumber, certFilename); } X509CaUris caUris = new X509CaUris(getStrings(ci.getCacertUris()), getStrings(ci.getOcspUris()), getStrings(ci.getCrlUris()), getStrings(ci.getDeltacrlUris())); int exprirationPeriod = (ci.getExpirationPeriod() == null) ? 365 : ci.getExpirationPeriod().intValue(); int numCrls = (ci.getNumCrls() == null) ? 30 : ci.getNumCrls().intValue(); caEntry = new X509CaEntry(new NameId(null, name), ci.getSnSize(), ci.getNextCrlNo(), expandConf(ci.getSignerType()), getValue(ci.getSignerConf(), zipFile), caUris, numCrls, exprirationPeriod); caEntry.setCmpControlName(ci.getCmpcontrolName()); caEntry.setCrlSignerName(ci.getCrlsignerName()); caEntry.setDuplicateKeyPermitted(ci.isDuplicateKey()); caEntry.setDuplicateSubjectPermitted(ci.isDuplicateSubject()); if (ci.getExtraControl() != null) { caEntry.setExtraControl(getValue(ci.getExtraControl(), zipFile)); } int keepExpiredCertDays = (ci.getKeepExpiredCertDays() == null) ? -1 : ci.getKeepExpiredCertDays().intValue(); caEntry.setKeepExpiredCertInDays(keepExpiredCertDays); caEntry.setMaxValidity(CertValidity.getInstance(ci.getMaxValidity())); caEntry.setPermission(ci.getPermission()); caEntry.setResponderName(ci.getResponderName()); caEntry.setSaveRequest(ci.isSaveReq()); caEntry.setStatus(CaStatus.forName(ci.getStatus())); if (ci.getValidityMode() != null) { caEntry.setValidityMode(ValidityMode.forName(ci.getValidityMode())); } if (ci.getGenSelfIssued() == null) { X509Certificate caCert; if (ci.getCert() != null) { byte[] bytes = getBinary(ci.getCert(), zipFile); try { caCert = X509Util.parseCert(bytes); } catch (CertificateException ex) { throw new InvalidConfException("invalid certificate of CA " + name, ex); } } else { // extract from the signer configuration ConcurrentContentSigner signer; try { List<String[]> signerConfs = CaEntry.splitCaSignerConfs( getValue(ci.getSignerConf(), zipFile)); SignerConf signerConf = new SignerConf(signerConfs.get(0)[1]); signer = securityFactory.createSigner( expandConf(ci.getSignerType()), signerConf, (X509Certificate) null); } catch (ObjectCreationException | XiSecurityException ex) { throw new InvalidConfException("could not create CA signer for CA " + name, ex); } caCert = signer.getCertificate(); } caEntry.setCertificate(caCert); } } List<CaHasRequestorEntry> caHasRequestors = null; if (m.getRequestors() != null) { caHasRequestors = new LinkedList<>(); for (CaHasRequestorType req : m.getRequestors().getRequestor()) { CaHasRequestorEntry en = new CaHasRequestorEntry( new NameId(null, req.getRequestorName())); en.setRa(req.isRa()); List<String> strs = getStrings(req.getProfiles()); if (strs != null) { en.setProfiles(new HashSet<>(strs)); } en.setPermission(req.getPermission()); caHasRequestors.add(en); } } List<String> aliases = getStrings(m.getAliases()); List<String> profileNames = getStrings(m.getProfiles()); List<String> publisherNames = getStrings(m.getPublishers()); SingleCaConf singleCa = new SingleCaConf(name, genSelfIssued, caEntry, aliases, profileNames, caHasRequestors, publisherNames); addSingleCa(singleCa); } } } public void addCmpControl(final CmpControlEntry cmpControl) { ParamUtil.requireNonNull("cmpControl", cmpControl); this.cmpControls.put(cmpControl.getName(), cmpControl); } public Set<String> getCmpControlNames() { return Collections.unmodifiableSet(cmpControls.keySet()); } public CmpControlEntry getCmpControl(final String name) { return cmpControls.get(ParamUtil.requireNonNull("name", name)); } public void addResponder(final CmpResponderEntry responder) { ParamUtil.requireNonNull("responder", responder); this.responders.put(responder.getName(), responder); } public Set<String> getResponderNames() { return Collections.unmodifiableSet(responders.keySet()); } public CmpResponderEntry getResponder(final String name) { return responders.get(ParamUtil.requireNonNull("name", name)); } public void addEnvironment(final String name, final String value) { ParamUtil.requireNonBlank("name", name); ParamUtil.requireNonBlank("value", value); this.environments.put(name, value); } public Set<String> getEnvironmentNames() { return Collections.unmodifiableSet(environments.keySet()); } public String getEnvironment(final String name) { return environments.get(ParamUtil.requireNonNull("name", name)); } public void addCrlSigner(final X509CrlSignerEntry crlSigner) { ParamUtil.requireNonNull("crlSigner", crlSigner); this.crlSigners.put(crlSigner.getName(), crlSigner); } public Set<String> getCrlSignerNames() { return Collections.unmodifiableSet(crlSigners.keySet()); } public X509CrlSignerEntry getCrlSigner(final String name) { return crlSigners.get(ParamUtil.requireNonNull("name", name)); } public void addRequestor(final CmpRequestorEntry requestor) { ParamUtil.requireNonNull("requestor", requestor); this.requestors.put(requestor.getIdent().getName(), requestor); } public Set<String> getRequestorNames() { return Collections.unmodifiableSet(requestors.keySet()); } public CmpRequestorEntry getRequestor(final String name) { return requestors.get(ParamUtil.requireNonNull("name", name)); } public void addPublisher(final PublisherEntry publisher) { ParamUtil.requireNonNull("publisher", publisher); this.publishers.put(publisher.getIdent().getName(), publisher); } public Set<String> getPublisherNames() { return Collections.unmodifiableSet(publishers.keySet()); } public PublisherEntry getPublisher(final String name) { return publishers.get(ParamUtil.requireNonNull("name", name)); } public void addProfile(final CertprofileEntry profile) { ParamUtil.requireNonNull("profile", profile); this.certprofiles.put(profile.getIdent().getName(), profile); } public Set<String> getCertProfileNames() { return Collections.unmodifiableSet(certprofiles.keySet()); } public CertprofileEntry getCertProfile(final String name) { return certprofiles.get(ParamUtil.requireNonNull("name", name)); } public void addSingleCa(final SingleCaConf singleCa) { ParamUtil.requireNonNull("singleCa", singleCa); this.cas.put(singleCa.getName(), singleCa); } public Set<String> getCaNames() { return Collections.unmodifiableSet(cas.keySet()); } public SingleCaConf getCa(final String name) { return cas.get(ParamUtil.requireNonNull("name", name)); } public void addScep(final ScepEntry scep) { ParamUtil.requireNonNull("scep", scep); this.sceps.put(scep.getCaIdent().getName(), scep); } public Set<String> getScepNames() { return Collections.unmodifiableSet(sceps.keySet()); } public ScepEntry getScep(final String name) { return sceps.get(ParamUtil.requireNonNull("name", name)); } private String getValue(final FileOrValueType fileOrValue, final ZipFile zipFile) throws IOException { if (fileOrValue == null) { return null; } if (fileOrValue.getValue() != null) { return expandConf(fileOrValue.getValue()); } String fileName = expandConf(fileOrValue.getFile()); InputStream is; if (zipFile != null) { is = zipFile.getInputStream(new ZipEntry(fileName)); if (is == null) { throw new IOException("could not find ZIP entry " + fileName); } } else { is = new FileInputStream(fileName); } byte[] binary = IoUtil.read(is); return expandConf(new String(binary, "UTF-8")); } private String getBase64Binary(final FileOrBinaryType fileOrBinary, final ZipFile zipFile) throws IOException { byte[] binary = getBinary(fileOrBinary, zipFile); return (binary == null) ? null : Base64.toBase64String(binary); } private byte[] getBinary(final FileOrBinaryType fileOrBinary, final ZipFile zipFile) throws IOException { if (fileOrBinary == null) { return null; } if (fileOrBinary.getBinary() != null) { return fileOrBinary.getBinary(); } String fileName = expandConf(fileOrBinary.getFile()); InputStream is; if (zipFile != null) { is = zipFile.getInputStream(new ZipEntry(fileName)); if (is == null) { throw new IOException("could not find ZIP entry " + fileName); } } else { is = new FileInputStream(fileName); } return IoUtil.read(is); } private List<String> getStrings(StringsType jaxb) { if (jaxb == null) { return null; } List<String> ret = new ArrayList<>(jaxb.getStr().size()); for (String m : jaxb.getStr()) { ret.add(expandConf(m)); } return ret; } private final String expandConf(String confStr) { if (confStr == null || !confStr.contains("${") || confStr.indexOf('}') == -1) { return confStr; } for (String name : properties.keySet()) { String placeHolder = "${" + name + "}"; while (confStr.contains(placeHolder)) { confStr = confStr.replace(placeHolder, properties.get(name)); } } return confStr; } }