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

com.metaeffekt.artifact.analysis.vulnerability.CommonEnumerationUtil Maven / Gradle / Ivy

The newest version!
/*
 * 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.vulnerability;

import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.artifact.enrichment.other.timeline.VulnerabilityTimeline;
import com.metaeffekt.mirror.contents.vulnerability.VulnerableSoftwareVersionRangeCpe;
import org.apache.commons.lang3.tuple.Pair;
import org.metaeffekt.core.inventory.processor.model.AbstractModelBase;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.model.Inventory;
import org.metaeffekt.core.inventory.processor.model.VulnerabilityMetaData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import us.springett.parsers.cpe.Cpe;
import us.springett.parsers.cpe.CpeParser;
import us.springett.parsers.cpe.exceptions.CpeEncodingException;
import us.springett.parsers.cpe.exceptions.CpeParsingException;
import us.springett.parsers.cpe.exceptions.CpeValidationException;
import us.springett.parsers.cpe.values.Part;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Utility class for CPE related tasks.
 * 
    *
  • * CPE 2.2 specification
    * cpe:/ {part} : {vendor} : {product} : {version} : {update} : {edition} : {language} *
  • *
  • * CPE 2.3 specification
    * cpe : {cpe_version} : {part} : {vendor} : {product} : {version} : {update} : {edition} : {language} : {sw_edition} : {target_sw} : {target_hw} : {other} *
  • *
*/ public class CommonEnumerationUtil { private final static Logger LOG = LoggerFactory.getLogger(CommonEnumerationUtil.class); public static String reduceCPEUri(String uri) { int colonIndex1 = uri.indexOf(":"); if (colonIndex1 == -1) return uri; int colonIndex2 = uri.substring(colonIndex1 + 1).indexOf(":"); if (colonIndex2 == -1) return uri; int colonIndex3 = uri.substring(colonIndex1 + colonIndex2 + 2).indexOf(":"); if (colonIndex3 == -1) return uri; int colonIndex4 = uri.substring(colonIndex1 + colonIndex2 + colonIndex3 + 3).indexOf(":"); if (colonIndex4 == -1) return uri; return uri.substring(0, colonIndex1 + colonIndex2 + colonIndex3 + colonIndex4 + 3); } public static List reduceCpeUrisToCommonParts(String... uris) throws CpeValidationException { if (uris == null) return Collections.emptyList(); final List cpeUris = Arrays.stream(uris) .map(CommonEnumerationUtil::parseCpe) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); final List commonCpeUris = new ArrayList<>(); for (Cpe uri : cpeUris) { boolean found = false; for (Cpe knownCpe : commonCpeUris) { if (compareCpePartWithoutWildcards(uri.getPart().getAbbreviation(), knownCpe.getPart().getAbbreviation()) && compareCpePartWithoutWildcards(uri.getVendor(), knownCpe.getVendor()) && compareCpePartWithoutWildcards(uri.getProduct(), knownCpe.getProduct())) { CpeBuilder commonCpePartsBuilder = CommonEnumerationUtil.builder().from(knownCpe); if (!compareCpePartWithoutWildcards(uri.getVersion(), knownCpe.getVersion())) { commonCpePartsBuilder.version("*"); } if (!compareCpePartWithoutWildcards(uri.getUpdate(), knownCpe.getUpdate())) { commonCpePartsBuilder.update("*"); } if (!compareCpePartWithoutWildcards(uri.getEdition(), knownCpe.getEdition())) { commonCpePartsBuilder.edition("*"); } if (!compareCpePartWithoutWildcards(uri.getLanguage(), knownCpe.getLanguage())) { commonCpePartsBuilder.language("*"); } if (!compareCpePartWithoutWildcards(uri.getSwEdition(), knownCpe.getSwEdition())) { commonCpePartsBuilder.swEdition("*"); } if (!compareCpePartWithoutWildcards(uri.getTargetSw(), knownCpe.getTargetSw())) { commonCpePartsBuilder.targetSw("*"); } if (!compareCpePartWithoutWildcards(uri.getTargetHw(), knownCpe.getTargetHw())) { commonCpePartsBuilder.targetHw("*"); } if (!compareCpePartWithoutWildcards(uri.getOther(), knownCpe.getOther())) { commonCpePartsBuilder.other("*"); } commonCpeUris.remove(knownCpe); commonCpeUris.add(commonCpePartsBuilder.build()); found = true; break; } } if (!found) { commonCpeUris.add(uri); } } return commonCpeUris; } private final static Function DISTINCT_AND_SORTED_WITH_WILDCARDS_MAPPER = old -> toCpe22UriOrFallbackToCpe23FS(distinctAndSortedWithWildcards(parseCpes(old))); public static void distinctAndSortedWithWildcards(Inventory inventory) { inventory.getArtifacts().forEach(CommonEnumerationUtil::distinctAndSortedWithWildcards); inventory.getVulnerabilityMetaData().forEach(CommonEnumerationUtil::distinctAndSortedWithWildcards); } public static void distinctAndSortedWithWildcards(Artifact artifact) { transformAbstractModelBaseAttribute(artifact, InventoryAttribute.INITIAL_CPE_URIS.getKey(), DISTINCT_AND_SORTED_WITH_WILDCARDS_MAPPER); transformAbstractModelBaseAttribute(artifact, InventoryAttribute.ADDITIONAL_CPE.getKey(), DISTINCT_AND_SORTED_WITH_WILDCARDS_MAPPER); transformAbstractModelBaseAttribute(artifact, InventoryAttribute.INAPPLICABLE_CPE.getKey(), DISTINCT_AND_SORTED_WITH_WILDCARDS_MAPPER); transformAbstractModelBaseAttribute(artifact, InventoryAttribute.DERIVED_CPE_URIS.getKey(), DISTINCT_AND_SORTED_WITH_WILDCARDS_MAPPER); } public static void distinctAndSortedWithWildcards(VulnerabilityMetaData vmd) { transformAbstractModelBaseAttribute(vmd, VulnerabilityMetaData.Attribute.PRODUCT_URIS.getKey(), DISTINCT_AND_SORTED_WITH_WILDCARDS_MAPPER); } private static void transformAbstractModelBaseAttribute(AbstractModelBase element, String attributeKey, Function transformation) { if (StringUtils.hasText(element.get(attributeKey))) { element.set(attributeKey, transformation.apply(element.get(attributeKey))); } } public static List parseCpe(Collection cpe) { return cpe.stream() .map(CommonEnumerationUtil::parseCpe) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); } public static List parseCpe(AbstractModelBase vmd, String attribute) { final String value = vmd.getComplete(attribute); if (value != null) { return parseCpe(Arrays.asList(value.split(", "))); } return Collections.emptyList(); } /** * Computes what CPEs are applicable on the given artifact. The different attributes are applied in this order: *
    *
  • CPE URIs: if set, these CPEs are parsed and returned directly without evaluating the other attributes
  • *
  • Derived CPE URIs: Add all CPEs if not exact match present
  • *
  • Additional CPE URIs: Add all CPEs if not exact match present
  • *
  • Inapplicable CPE URIs: Remove CPEs if wildcard match present
  • *
* The resulting list is sorted alphabetically. * * @param artifact The artifact to compute the CPEs for * @return The list of applicable CPEs, empty if none are applicable */ public static List parseEffectiveCpe(Artifact artifact) { final List initialCpes = parseCpe(artifact, InventoryAttribute.INITIAL_CPE_URIS.getKey()); if (initialCpes.size() > 0) { return initialCpes; } final List derivedCpes = parseCpe(artifact, InventoryAttribute.DERIVED_CPE_URIS.getKey()); final List additionalCpes = parseCpe(artifact, InventoryAttribute.ADDITIONAL_CPE.getKey()); final List inapplicableCpes = parseCpe(artifact, InventoryAttribute.INAPPLICABLE_CPE.getKey()); final ArrayList resultingCpes = new ArrayList<>(derivedCpes); for (Cpe cpe : additionalCpes) { addCpeIfExactMatchAbsent(resultingCpes, cpe); } for (Cpe remove : inapplicableCpes) { resultingCpes.removeIf(cpe -> compareCpeUsingWildcardsOneWay(cpe, remove)); } return distinctAndSortedWithoutWildcards(resultingCpes); } public static List distinctAndSortedWithoutWildcards(Collection cpe) { return cpe.stream() .distinct() .sorted(Cpe::compareTo) .collect(Collectors.toList()); } /** * Returns a distinct and sorted list of Cpe objects from the provided collection.
* The method ensures uniqueness by comparing Cpe objects using wildcards.
* The returned list is sorted according to the natural ordering of Cpe objects. * * @param cpe the collection of Cpe objects to be processed * @return a distinct and sorted list of Cpe objects */ public static List distinctAndSortedWithWildcards(Collection cpe) { final List list = new ArrayList<>(); final Set uniqueValues = new HashSet<>(); for (Cpe c : cpe) { if (uniqueValues.add(c)) { if (list.stream().noneMatch(c2 -> c != c2 && compareCpeUsingWildcards(c, c2))) { list.add(c); } } } list.sort(Cpe::compareTo); return list; } /** * Returns a distinct and sorted list of Cpe objects from the provided collections.
* This method accepts multiple collections and combines them into a single list.
* The method ensures uniqueness by comparing Cpe objects using wildcards.
* The returned list is sorted according to the natural ordering of Cpe objects. * * @param cpe the collections of Cpe objects to be processed * @return a distinct and sorted list of Cpe objects */ @SafeVarargs public static List distinctAndSortedWithWildcards(Collection... cpe) { return distinctAndSortedWithWildcards(Arrays.stream(cpe) .flatMap(Collection::stream) .collect(Collectors.toList())); } public static List parseEffectiveCpe(VulnerabilityMetaData vulnerability) { if (vulnerability == null) { return Collections.emptyList(); } if (vulnerability.has(InventoryAttribute.ADDITIONAL_CPE.getKey())) { LOG.warn("Key '{}' should only be used on artifacts, not on vulnerabilities", InventoryAttribute.ADDITIONAL_CPE.getKey()); } else if (vulnerability.has(InventoryAttribute.INAPPLICABLE_CPE.getKey())) { LOG.warn("Key '{}' should only be used on artifacts, not on vulnerabilities", InventoryAttribute.INAPPLICABLE_CPE.getKey()); } else if (vulnerability.has(InventoryAttribute.INITIAL_CPE_URIS.getKey())) { LOG.warn("Key '{}' should only be used on artifacts, not on vulnerabilities", InventoryAttribute.INITIAL_CPE_URIS.getKey()); } else if (vulnerability.has(InventoryAttribute.DERIVED_CPE_URIS.getKey())) { LOG.warn("Key '{}' should only be used on artifacts, not on vulnerabilities", InventoryAttribute.DERIVED_CPE_URIS.getKey()); } final List cpes = parseCpe(vulnerability, VulnerabilityMetaData.Attribute.PRODUCT_URIS.getKey()); cpes.sort(Cpe::compareTo); return cpes; } public static void addCpeIfExactMatchAbsent(Collection cpes, Cpe add) { if (cpes.stream().noneMatch(c -> compareCpeWithoutWildcards(c, add))) { cpes.add(add); } } public static List> getVendorProducts(Collection cpes) { return cpes.stream() .map(e -> Pair.of(e.getVendor(), e.getProduct())) .distinct() .collect(Collectors.toList()); } public static List> getVendorProductsFromVersionRanges(Collection cpes) { return cpes.stream() .map(e -> Pair.of(e.getCpe().getVendor(), e.getCpe().getProduct())) .distinct() .collect(Collectors.toList()); } public static List> getVendorProductsFromTimelines(Collection timelines) { return timelines.stream() .map(e -> Pair.of(e.getVendor(), e.getProduct())) .distinct() .collect(Collectors.toList()); } public static Optional parseCPESilent(String cpe) { return parseCpe(cpe, true); } public static Optional parseCpe(String cpe) { return parseCpe(cpe, false); } public static List parseCpes(String cpe) { if (cpe == null) return new ArrayList<>(); return parseCpe(Arrays.stream(cpe.split(", ")) .map(String::trim) .filter(StringUtils::hasText) .collect(Collectors.toList())); } private static Optional parseCpe(String cpe, boolean silent) { if (cpe == null) { return Optional.empty(); } final String trimmedCpe = cpe.trim(); try { return Optional.ofNullable(CpeParser.parse(trimmedCpe)); } catch (CpeParsingException e) { try { if (trimmedCpe.startsWith("cpe:/")) { final String[] parts = trimmedCpe.substring(5).split(":"); final StringJoiner sb = new StringJoiner(":"); for (String part : parts) { if (part.equals("*")) { sb.add("*"); } else { sb.add(URLEncoder.encode(part, "UTF-8")); } } final String encodedCpe = "cpe:/" + sb; return Optional.ofNullable(CpeParser.parse(encodedCpe)); } else if (trimmedCpe.startsWith("cpe:2.3")) { return Optional.ofNullable(CpeParser.parse(fillCpeComponents(trimmedCpe))); } } catch (CpeParsingException | UnsupportedEncodingException ignored) { } if (!silent) { LOG.warn("Could not parse CPE [{}]: {}", trimmedCpe, e.getMessage()); } } return Optional.empty(); } public static String fillCpeComponents(String cpe) { if (cpe.startsWith("cpe:2.3:")) { final String[] parts = cpe.substring(8).split("(? cpes) { if (cpes == null || cpes.isEmpty()) { return null; } return cpes.stream() .map(CommonEnumerationUtil::toCpe22UriOrFallbackToCpe23FS) .collect(Collectors.joining(", ")); } private final static String[] ESCAPE_CPE_22_CHARS = new String[]{ "_", "\\+", "\\.", "/", "-" }; private static String encodeValidCpe22Part(String part) throws UnsupportedEncodingException { if (StringUtils.isEmpty(part) || part.equals("*")) { return "*"; } for (String character : ESCAPE_CPE_22_CHARS) { part = part.replaceAll("(? keepOnlyPartVendorProduct(Cpe cpe) { try { // remove every part behind the product return Optional.of(CommonEnumerationUtil.builder().from(cpe).keepOnlyPartVendorProduct().build()); } catch (CpeValidationException e) { LOG.error("Unable to parse CPE [{}] provided by vendor/product: {}", CommonEnumerationUtil.toCpe22UriOrFallbackToCpe23FS(cpe), e.getMessage()); return Optional.empty(); } } public static CpeBuilder builder() { return new CommonEnumerationUtil.CpeBuilder(); } public static class CpeBuilder { private Part part; private String vendor = "*"; private String product = "*"; private String version = "*"; private String update = "*"; private String edition = "*"; private String language = "*"; private String swEdition = "*"; private String targetSw = "*"; private String targetHw = "*"; private String other = "*"; private CpeBuilder() { } public CpeBuilder from(Cpe cpe) { part = cpe.getPart(); vendor = cpe.getVendor(); product = cpe.getProduct(); version = cpe.getVersion(); update = cpe.getUpdate(); edition = cpe.getEdition(); language = cpe.getLanguage(); swEdition = cpe.getSwEdition(); targetSw = cpe.getTargetSw(); targetHw = cpe.getTargetHw(); other = cpe.getOther(); return this; } public CpeBuilder from(String cpe) throws CpeValidationException { return from(CommonEnumerationUtil.parseCpe(cpe).orElseThrow(() -> new CpeValidationException("Failed to parse CPE: " + cpe))); } public CpeBuilder part(Part part) { if (part == null) part = Part.ANY; this.part = part; return this; } public CpeBuilder vendor(String vendor) { this.vendor = transformNullToAny(vendor); return this; } public CpeBuilder product(String product) { this.product = transformNullToAny(product); return this; } public CpeBuilder version(String version) { this.version = transformNullToAny(version); return this; } public CpeBuilder update(String update) { this.update = transformNullToAny(update); return this; } public CpeBuilder edition(String edition) { this.edition = transformNullToAny(edition); return this; } public CpeBuilder language(String language) { this.language = transformNullToAny(language); return this; } public CpeBuilder swEdition(String swEdition) { this.swEdition = transformNullToAny(swEdition); return this; } public CpeBuilder targetSw(String targetSw) { this.targetSw = transformNullToAny(targetSw); return this; } public CpeBuilder targetHw(String targetHw) { this.targetHw = transformNullToAny(targetHw); return this; } public CpeBuilder other(String other) { this.other = transformNullToAny(other); return this; } public CpeBuilder keepOnlyPartVendorProduct() { version = "*"; update = "*"; edition = "*"; language = "*"; swEdition = "*"; targetSw = "*"; targetHw = "*"; other = "*"; return this; } private String transformNullToAny(String s) { if (s == null) return "*"; return s; } public Cpe build() throws CpeValidationException { return new Cpe(part, vendor, product, version, update, edition, language, swEdition, targetSw, targetHw, other); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy