All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.metaeffekt.artifact.analysis.converter.InventoryToCycloneDxBom Maven / Gradle / Ivy

/*
 * Copyright 2021-2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.metaeffekt.artifact.analysis.converter;

import com.fasterxml.jackson.databind.JsonNode;
import com.metaeffekt.artifact.analysis.spdxbom.LicenseStringConverter;
import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.vulnerability.CommonEnumerationUtil;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.artifact.terms.model.NormalizationMetaData;
import org.cyclonedx.Version;
import org.cyclonedx.generators.BomGeneratorFactory;
import org.cyclonedx.generators.json.BomJsonGenerator;
import org.cyclonedx.generators.xml.BomXmlGenerator;
import org.cyclonedx.model.*;
import org.cyclonedx.model.license.Expression;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.model.Inventory;
import us.springett.parsers.cpe.Cpe;

import javax.xml.parsers.ParserConfigurationException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * This class converts a given inventory into CycloneDx Bom.
* Set the Bom version using {@link #setVersion(Version)} or {@link #setVersion(String)}.
* Attributes that are converted: *
    *
  • Name/Id
  • *
  • Component
  • *
  • Version
  • *
  • Group Id
  • *
  • Description
  • *
  • Checksum
  • *
  • Cpe
  • *
  • Other vulnerability scanning relevant data
  • *
*/ public class InventoryToCycloneDxBom { public static final String KEY_COMPONENT_SPECIFIED_LICENSE = "Component Specified License"; private Version version = Version.VERSION_16; private final LicenseStringConverter licenseStringConverter; private boolean prettyPrint = true; /** * @param normalizationMetaData The normalization metadata has to be passed in order for the licenses to be * converted. */ public InventoryToCycloneDxBom(NormalizationMetaData normalizationMetaData) { this(normalizationMetaData, null); } public InventoryToCycloneDxBom(NormalizationMetaData normalizationMetaData, Map spdxStringAssessments) { licenseStringConverter = new LicenseStringConverter(normalizationMetaData, spdxStringAssessments); } public void setVersion(Version version) { this.version = version; } public void setPrettyPrint(boolean prettyPrint) { this.prettyPrint = prettyPrint; } public void setVersion(String versionString) { Version version = Arrays.stream(Version.values()) .filter(v -> v.getVersionString().equals(versionString)) .findFirst().orElseThrow(() -> new IllegalStateException("Unknown version " + versionString + ".")); setVersion(version); } private String generatedXmlBom = null, generatedJsonBom = null; /** * Converts the inventory into a JSON and XML bom and stores the result.
* Use {@link #getGeneratedJsonBom()} and {@link #getGeneratedXmlBom()} to get the generated boms. * * @param inventory The inventory to convert. * @throws ParserConfigurationException If the XML bom could not be generated. */ public void convert(Inventory inventory) throws ParserConfigurationException { Bom bom = convertInventoryToBom(inventory); BomXmlGenerator xml = BomGeneratorFactory.createXml(version, bom); xml.generate(); generatedXmlBom = xml.toString(); BomJsonGenerator json = BomGeneratorFactory.createJson(version, bom); JsonNode generate = json.toJsonNode(); if (generate == null) { throw new ParserConfigurationException("Error generating JSON output."); } generatedJsonBom = prettyPrint ? generate.toPrettyString() : generate.toString(); } /** * Run {@link #convert(Inventory)} first. * * @return The generated JSON Bom. */ public String getGeneratedJsonBom() { return generatedJsonBom; } /** * Run {@link #convert(Inventory)} first. * * @return The generated JSON Bom. */ public String getGeneratedXmlBom() { return generatedXmlBom; } private Bom convertInventoryToBom(Inventory inventory) { final Bom bom = new Bom(); inventory.getArtifacts().stream() .map(this::createComponentFromArtifact) .forEach(bom::addComponent); return bom; } /** * Converts the following field values of an {@link Artifact} into properties of a {@link Component}: *
    *
  • {@link Component#setName(String)} from Id
  • *
  • {@link Component#setPublisher(String)} from Component
  • *
  • {@link Component#setVersion(String)} from Version
  • *
  • {@link Component#setGroup(String)} from Group Id
  • *
  • {@link Component#setType(Component.Type)} to {@link Component.Type#LIBRARY}
  • *
  • * {@link Component#setDescription(String)} from the first non-null of: Description, * Summary, Comment *
  • *
  • * {@link Component#setCpe(String)}: *
      *
    • Uses {@link CommonEnumerationUtil#parseEffectiveCpe(Artifact)} to find effective CPEs on artifact
    • *
    • If none are found or the only CPE is cpe:/a:none:none, skips setting property
    • *
    • Formats the string as CPE 2.2 or 2.3 as fallback
    • *
    *
  • *
  • {@link Component#setPurl(String)} from PURL
  • *
  • * {@link Component#setLicenseChoice(LicenseChoice)}: *
      *
    • * Uses {@link InventoryToCycloneDxBom#createLicenseExpression(String, boolean)} to map the artifact * Licenses to {@link License} instances *
    • *
    *
  • *
  • * {@link Component#setHashes(List)}: collect to list from every algorithm in {@link Hash.Algorithm}: *
      *
    • MD5: Checksum
    • *
    • other: Checksum ($algorithm)
    • *
    *
  • *
  • * {@link Component#setProperties(List)} from these fields if available: *
      *
    • MS Product ID
    • *
    • MS Knowledge Base ID
    • *
    • Addon CVEs
    • *
    • Inapplicable CVE
    • *
    • CPE URIs
    • *
    • Derived CPE URIs
    • *
    • Additional CPE URIs
    • *
    • URL
    • *
    • Comment
    • *
    *
  • *
* * @param artifact The artifact to create the component from. * @return The converted component. */ private Component createComponentFromArtifact(Artifact artifact) { final Component component = new Component(); component.setName(artifact.getId()); component.setPublisher(artifact.getComponent()); component.setVersion(artifact.getVersion()); component.setGroup(artifact.getGroupId()); component.setType(Component.Type.LIBRARY); component.setDescription(StringUtils.nonNull(artifact.get("Description"), artifact.get("Summary"), artifact.get(Artifact.Attribute.COMMENT))); final List effectiveArtifactCpes = CommonEnumerationUtil.parseEffectiveCpe(artifact); if (!effectiveArtifactCpes.isEmpty() && !effectiveArtifactCpes.get(0).getVendor().equals("none") && !effectiveArtifactCpes.get(0).getProduct().equals("none")) { final String cpeString = CommonEnumerationUtil.toCpe22UriOrFallbackToCpe23FS(effectiveArtifactCpes.get(0)); component.setCpe(cpeString); } component.setPurl(artifact.get(InventoryAttribute.PURL.getKey())); component.setLicenses(createLicenseChoice(artifact)); final List hashes = new ArrayList<>(); if (component.getHashes() != null) { hashes.addAll(component.getHashes()); } if (artifact.getChecksum() != null) { hashes.add(new Hash(Hash.Algorithm.MD5, artifact.getChecksum())); } for (Hash.Algorithm algorithm : Hash.Algorithm.values()) { final String spec = algorithm.getSpec(); { // legacy; remove once obsolete final String value = artifact.get(Artifact.Attribute.CHECKSUM.getKey() + " (" + spec + ")"); if (value != null) { hashes.add(new Hash(algorithm, value)); } } { final String value = artifact.get("Hash (" + spec + ")"); if (value != null) { hashes.add(new Hash(algorithm, value)); } } } component.setHashes(hashes); final List properties = new ArrayList<>(); checkForProperty(artifact, InventoryAttribute.MS_PRODUCT_ID.getKey(), properties); checkForProperty(artifact, InventoryAttribute.MS_KB_IDENTIFIER.getKey(), properties); checkForProperty(artifact, InventoryAttribute.ADDON_CVES.getKey(), properties); checkForProperty(artifact, InventoryAttribute.INAPPLICABLE_CVE.getKey(), properties); checkForProperty(artifact, InventoryAttribute.INITIAL_CPE_URIS.getKey(), properties); checkForProperty(artifact, InventoryAttribute.DERIVED_CPE_URIS.getKey(), properties); checkForProperty(artifact, InventoryAttribute.ADDITIONAL_CPE.getKey(), properties); checkForProperty(artifact, Artifact.Attribute.URL.getKey(), properties); checkForProperty(artifact, Artifact.Attribute.COMMENT.getKey(), properties); if (properties.size() > 0) component.setProperties(properties); return component; } private LicenseChoice createLicenseChoice(Artifact artifact) { final String curatedAssociatedLicenses = artifact.getLicense(); final String originalLicenses = artifact.get(KEY_COMPONENT_SPECIFIED_LICENSE); if (StringUtils.hasText(curatedAssociatedLicenses)) { final LicenseChoice licenseChoice = createLicenseChoice(curatedAssociatedLicenses, true); if (licenseChoice != null) return licenseChoice; } else if (StringUtils.hasText(originalLicenses)) { final LicenseChoice licenseChoice = createLicenseChoice(originalLicenses, false); if (licenseChoice != null) return licenseChoice; } return null; } private LicenseChoice createLicenseChoice(String licenses, boolean toSpdx) { final Expression licenseExpression = createLicenseExpression(licenses, toSpdx); if (licenseExpression != null) { final LicenseChoice licenseChoice = new LicenseChoice(); licenseChoice.setExpression(licenseExpression); return licenseChoice; } return null; } private void checkForProperty(Artifact artifact, String key, List properties) { if (artifact.get(key) != null) { Property property = new Property(); property.setName(key); property.setValue(artifact.get(key)); properties.add(property); } } /** * Creates an Spdx License Expression from an artifact license string. * * @param artifactLicense An artifact license string to be converted into an spdx expression. * @param toSpdx Whether to convert to an SPDX identified (required TMD). * * @return Returns a converted Spdx expression string. */ private Expression createLicenseExpression(String artifactLicense, boolean toSpdx) { if (artifactLicense != null) { if (toSpdx) { final LicenseStringConverter.ToSpdxResult toSpdxResult = licenseStringConverter.licenseStringToSpdxExpression(artifactLicense); if (!StringUtils.isEmpty(toSpdxResult.getExpression())) { return new Expression(toSpdxResult.getExpression()); } } else { return new Expression(artifactLicense); } } return null; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy