/* * Copyright (C) 2014 Intel Corporation * All rights reserved. */ package com.intel.mtwilson.tag; import com.intel.dcsg.cpg.io.UUID; import com.intel.dcsg.cpg.validation.Fault; import com.intel.mountwilson.as.common.ASException; import com.intel.mtwilson.My; import com.intel.mtwilson.api.ApiException; import com.intel.mtwilson.datatypes.AssetTagCertCreateRequest; import com.intel.mtwilson.datatypes.TxtHostRecord; import com.intel.mtwilson.tag.common.Global; import com.intel.mtwilson.tag.common.X509AttrBuilder; import com.intel.mtwilson.tag.dao.TagJdbi; import com.intel.mtwilson.tag.dao.jdbi.*; import com.intel.mtwilson.tag.model.*; import com.intel.mtwilson.tag.model.x509.UTF8NameValueMicroformat; import com.intel.mtwilson.tag.selection.SelectionBuilder; import com.intel.mtwilson.tag.selection.SelectionUtil; import com.intel.mtwilson.tag.selection.xml.*; import java.io.IOException; import java.security.PrivateKey; import java.security.SignatureException; import java.security.cert.X509Certificate; import java.sql.SQLException; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import java.util.concurrent.TimeUnit; import org.bouncycastle.asn1.ASN1Object; import org.bouncycastle.asn1.ASN1ObjectIdentifier; /** * Creates tag certificates for existing certificate requests in accordance with * the configured policy. * * When used in the provisioning service, this class is the "internal CA" for * automatic approval of certificate requests. * * Input: TagCertificateRequest Output: TagCertificate * * The only required parameter in the certificate request is the identity of the * subject (hardware uuid) being certified. * * @author jbuhacoff */ public class TagCertificateAuthority { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TagCertificateAuthority.class); private TagConfiguration configuration; public TagCertificateAuthority(TagConfiguration configuration) { this.configuration = configuration; } /** * Looks up hardware uuid of host in mtwilson; host already be registered. * * @param ip address or hostname * @return */ public String findSubjectHardwareUuid(String ip) throws IOException, ApiException, SignatureException { log.debug("Querying host {} in Mt Wilson", ip); List<TxtHostRecord> hostList = Global.mtwilson().queryForHosts(ip, true); if (hostList == null || hostList.isEmpty()) { log.debug("host uuid lookup didn't return back any results"); //throw new ASException(new Exception("No host records found, please verify your host is in mtwilson or provide a hardware uuid in the subject field.")); // log.warn("No host records found for {}, please verify your host is in mtwilson or provide a hardware uuid in the subject field", ip); return null; } log.debug("get host uuid returned " + hostList.get(0).Hardware_Uuid); return hostList.get(0).Hardware_Uuid; } /** * Looks up kv attributes by selection name and prepares a SelectionType * object corresponding to <selection name="name"/> in the selection xml * with all the attributes populated. * * NOTE: currently this only populates kv attributes. * */ protected SelectionType findSelectionByName(String name) throws SQLException { try (SelectionDAO selectionDao = TagJdbi.selectionDao()) { Selection selection = selectionDao.findByName(name); if (selection != null) { try (SelectionKvAttributeDAO selectionKvAttributeDAO = TagJdbi.selectionKvAttributeDao()) { List<SelectionKvAttribute> kvAttributes = selectionKvAttributeDAO.findBySelectionIdWithValues(selection.getId()); SelectionBuilder builder = SelectionBuilder.factory().selection(); for (SelectionKvAttribute kvAttribute : kvAttributes) { builder.textAttributeKV(kvAttribute.getKvAttributeName(), kvAttribute.getKvAttributeValue()); } return builder.build().getSelection().get(0); } } } return null; } protected SelectionType findSelectionById(String id) throws SQLException { try (SelectionDAO selectionDao = TagJdbi.selectionDao()) { Selection selection = selectionDao.findById(UUID.valueOf(id)); if (selection != null) { try (SelectionKvAttributeDAO selectionKvAttributeDAO = TagJdbi.selectionKvAttributeDao()) { SelectionBuilder builder = SelectionBuilder.factory().selection(); List<SelectionKvAttribute> kvAttributes = selectionKvAttributeDAO.findBySelectionIdWithValues(selection.getId()); for (SelectionKvAttribute kvAttribute : kvAttributes) { builder.textAttributeKV(kvAttribute.getKvAttributeName(), kvAttribute.getKvAttributeValue()); } return builder.build().getSelection().get(0); } } } return null; } protected SelectionType getInlineOrLookupSelection(SelectionType selection) throws SQLException { if (selection.getAttribute().isEmpty()) { log.debug("Selection does not have inline attributes"); if (selection.getId() != null) { SelectionType found = findSelectionById(selection.getId()); if (found != null) { log.debug("Found selection by id {}", selection.getId()); return found; } } if (selection.getName() != null) { SelectionType found = findSelectionByName(selection.getName()); if (found != null) { log.debug("Found selection by name {}", selection.getName()); return found; } } // if there are no inline <attribute>...</attribute> tags then either <selection name="..."/> or <selection id="..."/> xml attributes must be specified so we can look up the selected attributes in the database if( selection.getId() == null && selection.getName() == null ) { throw new IllegalArgumentException("Empty selection with no id or name"); } // if id or name attributes were specified but we didn't find them in the database, it's also an error throw new IllegalArgumentException("Cannot find selection by id or name"); } // if it's not empty we use the included attributes and do not need to look anything up on the server return selection; } /** * If there is a currently valid (notBefore<today<notAfter) selection that * mentions the subject, that selection will be used. Otherwise, if there is * a default selection (does not mention any subjects) that selection will * be used. Otherwise, if the server is configured with a default selection, * that selection will be used. Otherwise, an error will be returned because * a selection could not be found. * */ public SelectionType findCurrentSelectionForSubject(UUID targetSubject, SelectionsType selections) throws SQLException, IOException, ApiException, SignatureException { log.debug("findSelectionForSubject {}", targetSubject.toString()); SelectionsType currentSelections = SelectionUtil.copySelectionsValidOn(selections, new Date()); // first search by host uuid for (SelectionType selection : currentSelections.getSelection()) { for (SubjectType subject : selection.getSubject()) { if (subject.getUuid() != null) { log.debug("comparing to selection subject uuid {}", subject.getUuid().getValue().toLowerCase()); if (targetSubject.toString().equalsIgnoreCase(subject.getUuid().getValue().toLowerCase())) { // found a selection with the target subject uuid return getInlineOrLookupSelection(selection); } } } } // second search by host ip or host name for (SelectionType selection : currentSelections.getSelection()) { for (SubjectType subject : selection.getSubject()) { if (subject.getIp() != null) { log.debug("looking up uuid for host with ip {}", subject.getIp().getValue()); String uuid = findSubjectHardwareUuid(subject.getIp().getValue()); if (uuid != null) { log.debug("comparing to found selection subject uuid {}", uuid); if (targetSubject.toString().equalsIgnoreCase(uuid.toLowerCase())) { // found a selection with the target subject uuid return getInlineOrLookupSelection(selection); } } } } } // third search for default selection (no subjects declared) return the first one found if (currentSelections.getDefault() != null) { for (SelectionType selection : currentSelections.getDefault().getSelection()) { log.debug("Using first currently valid default selection"); return getInlineOrLookupSelection(selection); // get first default } } /* // fourth look for a server default selection - disabling this for now because it may be confusing to customers to have a server default behind their own selections file default because if they choose not to supply a default in their file they may still get one from the server. String defaultSelectionName = configuration.getTagProvisionSelectionDefault(); if (defaultSelectionName != null && !defaultSelectionName.isEmpty()) { return findSelectionByName(defaultSelectionName); } */ //throw new IllegalArgumentException("No matching selection"); return null; // no matching selection - let the caller decide if it's an error or not } /** * Finds the best matching selection for the give subject in the provided * selections object, based on validity dates and specified subjects and * using the default selections if provided and no better match can be * found. * * @param subject * @param selections representing an entire "xml selections file" * @return * @throws Exception */ public byte[] createTagCertificate(UUID subject, SelectionsType selections) throws SQLException, IOException, ApiException, SignatureException { SelectionType selection = findCurrentSelectionForSubject(subject, selections); if( selection == null ) { throw new IllegalArgumentException("No matching selection"); } return createTagCertificate(subject, selection); } /** * Does not attempt to match the subject to the selection. Do not call * directly unless you have already verified that you want to create a * certificate for the given subject with the given selection with no * further checks. * * @param subject * @param selection element representing a set of host attributes by * reference via the selection uuid or selection name or inline via the * attribute elements * @return * @throws Exception */ public byte[] createTagCertificate(UUID subject, SelectionType selection) throws IOException { // check if we have a private key to use for signing PrivateKey cakey = Global.cakey(); X509Certificate cakeyCert = Global.cakeyCert(); if (cakey == null || cakeyCert == null) { throw new IllegalStateException("Missing tag certificate authority key"); } X509AttrBuilder builder = X509AttrBuilder.factory() .issuerName(cakeyCert) .issuerPrivateKey(cakey) .dateSerial() .subjectUuid(subject) .expires(configuration.getTagValiditySeconds(), TimeUnit.SECONDS); for (AttributeType attribute : selection.getAttribute()) { X509AttrBuilder.Attribute oidAndValue = Util.toAttributeOidValue(attribute); builder.attribute(oidAndValue.oid, oidAndValue.value); } byte[] attributeCertificateBytes = builder.build(); if (attributeCertificateBytes == null) { log.error("Cannot build attribute certificate"); for (Fault fault : builder.getFaults()) { log.error(String.format("%s%s", fault.toString(), fault.getCause() == null ? "" : ": " + fault.getCause().getMessage())); } throw new IllegalArgumentException("Cannot build attribute certificate"); } // if auto-import to mtwilson is enabled, do it here, but if there is an exception we only log it try { log.debug("Tag certificate auto-import enabled: {}", configuration.isTagProvisionAutoImport()); if (configuration.isTagProvisionAutoImport()) { String url = My.configuration().getAssetTagMtWilsonBaseUrl(); log.debug("Mt Wilson URL: {}", url); if (url != null && !url.isEmpty()) { AssetTagCertCreateRequest request = new AssetTagCertCreateRequest(); request.setCertificate(attributeCertificateBytes); log.debug("Importing tag certificate to Mt Wilson"); Global.mtwilson().importAssetTagCertificate(request); } } } catch (IOException | ApiException | SignatureException e) { log.error("Failed to auto-import tag certificate to Mt Wilson", e); } return attributeCertificateBytes; } }