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

com.metaeffekt.mirror.contents.advisory.CertFrAdvisorEntry 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.mirror.contents.advisory;

import com.metaeffekt.artifact.analysis.utils.FileUtils;
import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.utils.TimeUtils;
import com.metaeffekt.mirror.contents.base.DescriptionParagraph;
import com.metaeffekt.mirror.contents.base.Reference;
import com.metaeffekt.mirror.contents.store.AdvisoryTypeStore;
import com.metaeffekt.mirror.contents.store.VulnerabilityTypeStore;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.lucene.document.Document;
import org.json.JSONArray;
import org.json.JSONObject;
import org.metaeffekt.core.inventory.processor.model.AdvisoryMetaData;
import org.metaeffekt.core.inventory.processor.report.model.AdvisoryUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class CertFrAdvisorEntry extends AdvisoryEntry {

    private final static Logger LOG = LoggerFactory.getLogger(CertFrAdvisorEntry.class);

    protected final static Set CONVERSION_KEYS_AMB = new HashSet(AdvisoryEntry.CONVERSION_KEYS_AMB) {{
    }};

    protected final static Set CONVERSION_KEYS_MAP = new HashSet(AdvisoryEntry.CONVERSION_KEYS_MAP) {{
    }};


    protected final static String CERT_FR_BASE_URL = "https://www.cert.ssi.gouv.fr/";

    public CertFrAdvisorEntry() {
        super(AdvisoryTypeStore.CERT_FR);
    }

    public CertFrAdvisorEntry(String id) {
        super(AdvisoryTypeStore.CERT_FR, id);
    }

    protected String getBaseType() {
        if (id == null) return null;

        final String[] parts = id.split("-");
        if (parts.length < 4) return null;

        switch (parts[2]) {
            case "AVI":
                return "avis";
            case "ALE":
                return "alerte";
            case "IOC":
                return "ioc";
            case "DUR":
                return "dur";
            case "ACT":
                return "actualite";
            case "CTI":
                return "cti";
            case "REC":
            case "INF":
                return "information";
            default:
                return null;
        }
    }

    public String getType() {
        return AdvisoryUtils.normalizeType(getBaseType());
    }

    @Override
    public String getUrl() {
        if (id == null) return CERT_FR_BASE_URL;

        final String[] parts = id.split("-");
        if (parts.length < 4) return CERT_FR_BASE_URL;

        final StringJoiner onlineCertFr = new StringJoiner("-");
        for (int i = 0; i < parts.length; i++) {
            if (i < 4) {
                if (i == 3 && parts[i].matches("\\d+") && parts[i].length() == 2) {
                    parts[i] = "0" + parts[i];
                }
                onlineCertFr.add(parts[i]);
            }
        }

        final String type = getBaseType();
        if (type == null) return CERT_FR_BASE_URL;
        return CERT_FR_BASE_URL + type + "/" + onlineCertFr;
    }

    /* TYPE CONVERSION METHODS */

    @Override
    protected Set conversionKeysAmb() {
        return CONVERSION_KEYS_AMB;
    }

    @Override
    protected Set conversionKeysMap() {
        return CONVERSION_KEYS_MAP;
    }

    @Override
    public CertFrAdvisorEntry constructDataClass() {
        return new CertFrAdvisorEntry();
    }

    public static CertFrAdvisorEntry fromAdvisoryMetaData(AdvisoryMetaData amd) {
        return AdvisoryEntry.fromAdvisoryMetaData(amd, CertFrAdvisorEntry::new);
    }

    public static CertFrAdvisorEntry fromInputMap(Map map) {
        return AdvisoryEntry.fromInputMap(map, CertFrAdvisorEntry::new);
    }

    public static CertFrAdvisorEntry fromJson(JSONObject json) {
        return AdvisoryEntry.fromJson(json, CertFrAdvisorEntry::new);
    }

    public static CertFrAdvisorEntry fromJson(File file) throws IOException {
        return AdvisoryEntry.fromJson(file, CertFrAdvisorEntry::new);
    }

    public static CertFrAdvisorEntry fromDocument(Document document) {
        return AdvisoryEntry.fromDocument(document, CertFrAdvisorEntry::new);
    }

    @Override
    public void appendFromBaseModel(AdvisoryMetaData amd) {
        super.appendFromBaseModel(amd);
    }

    @Override
    public void appendToBaseModel(AdvisoryMetaData amd) {
        super.appendToBaseModel(amd);
    }

    @Override
    public void appendFromMap(Map map) {
        super.appendFromMap(map);
    }

    @Override
    public void appendToJson(JSONObject json) {
        super.appendToJson(json);
    }

    @Override
    public void appendFromDocument(Document document) {
        super.appendFromDocument(document);
    }

    @Override
    public void appendToDocument(Document document) {
        super.appendToDocument(document);
    }

    /**
     * The CERT-FR download contains its data in an unstructured text format.
* These lines do not contain any content, but are rather the header/footer contents of the PFD the text has been * extracted from. */ private final static Pattern[] CERT_FR_IGNORE_LINE_PATTERNS = new Pattern[]{ Pattern.compile(".*BULLETIN D'ACTUALITÉ DU CERT-FR.*"), Pattern.compile(".*PREMIER MINISTRE.*"), Pattern.compile(".*Paris, le.*"), Pattern.compile(".*N°.*"), Pattern.compile(".*S \\. G \\. D \\. S \\. N.*"), Pattern.compile(".*Agence nationale.*"), Pattern.compile(".*de la sécurité des.*"), Pattern.compile(".*systèmes d'information.*"), Pattern.compile(".*Affaire suivie par.*"), Pattern.compile(".*AVIS DU CERT-FR.*"), Pattern.compile(".*Une gestion de version détaillée se trouve à la fin de ce document\\..*"), Pattern.compile("\\d{5,10}"), Pattern.compile("Secrétariat général de la défense et de la sécurité nationale – ANSSI – CERT-FR"), Pattern.compile("51, bd de La Tour-Maubourg"), Pattern.compile("75700 Paris 07 SP"), Pattern.compile("Tél\\.: {2}\\+33 1 71 75 84 68"), Pattern.compile("Fax: {2}\\+33 1 84 82 40 70"), Pattern.compile("Web: {2}https://www.cert.ssi.gouv.fr"), Pattern.compile("Mél: {2}[email protected]"), Pattern.compile("Page \\d+ / \\d+"), Pattern.compile("Ce bulletin d’actualité du CERT-FR revient sur les vulnérabilités.+"), Pattern.compile("Toutes les vulnérabilités évoquées dans les avis.+"), Pattern.compile("Veuillez-vous référer aux avis des éditeurs pour obtenir les correctifs.+") }; /** * Paragraph headers that should be extracted from the CERT-FR advisory text.
* The first element of each array element is the official title, the others are alternative titles that get merged * into the official title.
* If the last value is null, the title is not being normalized. */ private final static String[][] CERT_FR_PARAGRAPH_HEADERS = new String[][]{ {"Systèmes affectés", "Affected systems"}, {"Résumé", "Summary"}, {"Risque\\(s\\)", "Risques", "Risque", "Risk"}, {"Solution", "Solutions", "Solution"}, {"Recommandations", "Recommendations"}, {"Documentation", "Documentations", "Documentation"}, {"Contournement provisoire", "Temporary bypass"}, {"Description", "Description"}, {"Rappel des avis émis", "Rappel des avis et des mises à jour émis", "Rappel des avis et mises à jour émis", "Reminder of notices issued"}, {"^\\d{1,2} .+", null}, {"^##+ .+", null}, }; public static CertFrAdvisorEntry fromDownloadText(List lines) { final CertFrAdvisorEntry entry = new CertFrAdvisorEntry(); if (lines.isEmpty()) throw new IllegalArgumentException("No data found in file"); // find lines that should be processed // ignore empty lines and lines without important content lines = lines.stream() .map(l -> l.replace("\f", "")) .map(l -> l.replace("(cid:160)", "")) .map(String::trim) .filter(l -> Arrays.stream(CERT_FR_IGNORE_LINE_PATTERNS).noneMatch(ignoreLine -> ignoreLine.matcher(l).matches())) .collect(Collectors.toList()); // remove double newlines final List reducedMappedLines = new ArrayList<>(); boolean lastLineEmpty = false; for (String line : lines) { if (lastLineEmpty && line.isEmpty()) { continue; } reducedMappedLines.add(line); lastLineEmpty = line.isEmpty(); } final Map furtherContent = new HashMap<>(); int maxHeaderNumber = 0; // has to be a fori loop, as lines are being skipped and lookaheads are performed inside the loop for (int i = 0; i < reducedMappedLines.size(); i++) { String line = reducedMappedLines.get(i); // depending on the archive and year, the format can be different, therefore the different sections of the // document need to be found first before they can be processed if (line.startsWith("Objet:")) { // topic entry.setSummary(line.replace("Objet: ", "")); } else if (line.equals("Gestion du document")) { // document data in table final ArrayList tableContent = new ArrayList<>(); i++; line = reducedMappedLines.get(i); // continue collecting table entries until end of table is reached while (!(line.contains("Tableau") || line.contains("TAB")) || line.contains("Electric")) { tableContent.add(line); i++; if (i >= reducedMappedLines.size()) break; line = reducedMappedLines.get(i); } certFrExtractContentsFromTable(entry, tableContent); } else if (line.equals("Gestion détaillée du document")) { // document history at the bottom of the document, the following lines do not contain important content any more break; } else if (certFrIsHeader(line, maxHeaderNumber, reducedMappedLines, i)) { // if a header is found, collect the following lines until either another header is found or the end of the // document (Gestion détaillée du document) is found. final String header = certFrNormalizeHeaderText(line); maxHeaderNumber = certFrExtractHeaderNumber(line, maxHeaderNumber); final List contents = new ArrayList<>(); // skip to next line and continue adding lines until header or end of document is reached i++; if (i >= lines.size()) break; line = reducedMappedLines.get(i); while (!certFrIsHeader(line, maxHeaderNumber, reducedMappedLines, i) && !line.equals("Gestion détaillée du document")) { contents.add(line); i++; if (i >= reducedMappedLines.size()) break; line = reducedMappedLines.get(i); } // add the paragraph to the further details furtherContent.computeIfAbsent(header, h -> new StringJoiner("\n")).add(String.join("\n", contents)); i--; } } final Map trimmedFurtherContent = furtherContent.entrySet() .stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> trimNewlines(e.getValue().toString().trim()).trim())); // the further content map may contain a field named "Summary". This field may become very long however (see // CERTFR-2021-ALE-022), which is why it cannot be used for the summary attribute of the AdvisorEntry, which is // reserved for a short description. final StringJoiner recommendationBuilder = new StringJoiner("\n\n"); if (trimmedFurtherContent.containsKey("Recommendations")) { recommendationBuilder.add(trimmedFurtherContent.remove("Recommendations")); } if (trimmedFurtherContent.containsKey("Solution")) { recommendationBuilder.add(trimmedFurtherContent.remove("Solution")); } if (recommendationBuilder.length() > 0) { entry.setRecommendations(recommendationBuilder.toString()); } entry.setThreat(trimmedFurtherContent.remove("Risk")); entry.setWorkarounds(trimmedFurtherContent.remove("Temporary bypass")); if (trimmedFurtherContent.containsKey("Documentation")) { final String[] documentationLines = trimmedFurtherContent.remove("Documentation").split("\n"); for (int i = 0; i < documentationLines.length - 1; i += 2) { final Reference ref = Reference.fromTitleAndUrl(documentationLines[i], documentationLines[i + 1]); entry.getReferences().add(ref); } entry.clearDescription(); } else { final String sourcesDescription = entry.getTextDescription(); entry.setDescription(DescriptionParagraph.fromTitleAndContent("Sources", sourcesDescription)); } if (trimmedFurtherContent.containsKey("Description")) { entry.addDescription(DescriptionParagraph.fromTitleAndContent("Description", trimmedFurtherContent.remove("Description"))); } // merge all further contents into a single description for (String header : trimmedFurtherContent.keySet()) { entry.addDescription(DescriptionParagraph.fromTitleAndContent(header, trimmedFurtherContent.get(header))); } final String inputAsOneLine = String.join("", reducedMappedLines); extractReferenceIdsFromStringIfPresent(inputAsOneLine, entry, AdvisoryTypeStore.CERT_FR); extractReferenceIdsFromStringIfPresent(inputAsOneLine, entry, VulnerabilityTypeStore.CVE); if (!StringUtils.hasText(entry.getId())) { LOG.error("Unable to detect CERT-FR identifier in document: {}", entry.toJson()); } else { entry.removeReferencedSecurityAdvisory(AdvisoryTypeStore.CERT_FR, entry.getId()); } if (entry.getCreateDate() == null) { LOG.warn("Missing create date on {}", entry.getId()); } if (entry.getUpdateDate() == null) { LOG.warn("Missing update date on {}", entry.getId()); } return entry; } private static void certFrExtractContentsFromTable(CertFrAdvisorEntry entry, List tableContent) { entry.setId(null); entry.setCreateDate(null); entry.setUpdateDate(null); final StringBuilder title = new StringBuilder(); final StringBuilder sources = new StringBuilder(); boolean updateAndCreateDateFound = false; // find the relevant data in the table lines. this is a rather convoluted process since the table entries and // row identifiers can appear in (almost) any order in the given lines. for (int i = 0, tableSize = tableContent.size(); i < tableSize; i++) { String line = tableContent.get(i); if (line.isEmpty()) { continue; } if (line.matches("(?:\\d+)?(?:CERTFR|CERTA)-.+")) { // fixes 00CERTFR-2022-ALE-008 if (line.matches("\\d+.+")) { line = line.replaceAll("\\d+(.+)", "$1"); } line = line.toUpperCase(Locale.ENGLISH).trim(); entry.setId(line); } else if (line.matches("Date de la première version .+")) { entry.setCreateDate(TimeUtils.tryParse(line.replaceAll("Date de la première version (.+)", "$1"))); } else if (line.matches("Date de la dernière version .+")) { entry.setUpdateDate(TimeUtils.tryParse(line.replaceAll("Date de la dernière version (.+)", "$1"))); } else if (entry.getCreateDate() == null && line.matches("\\d{1,2} [^ ]+ \\d{4}(?: à \\d{2}h\\d{2})?")) { // if line matches a date and date of the first released version has not been set yet entry.setCreateDate(TimeUtils.tryParse(line)); } else if (entry.getUpdateDate() == null && line.matches("\\d{1,2} [^ ]+ \\d{4}(?: à \\d{2}h\\d{2})?")) { // if line matches a date and date of the first & latest released version has not been set yet entry.setUpdateDate(TimeUtils.tryParse(line)); } else if (certFrIsNotTableRowIdentifier(line) && (title.length() == 0 || !updateAndCreateDateFound)) { // if the document title and the version dates have not been found yet, it is the document title title.append(" ").append(line.trim()); } else if (certFrIsNotTableRowIdentifier(line) && sources.length() == 0) { // otherwise and if the sources have not been found yet, it is the sources boolean first = true; final StringBuilder sourcesBuilder = new StringBuilder(); do { if (!first) { if (sources.length() > 0 || sourcesBuilder.length() > 0) { sourcesBuilder.append("\n"); } sourcesBuilder.append(line); } else { first = false; } line = tableContent.get(i); i++; // sources can be multiple lines, continue until row identifier is found or table end is reached } while (certFrIsNotTableRowIdentifier(line) && i < tableSize && !line.equals("Aucune(s)") && !line.equals("Aucune")); sources.append(certFrPrepareSources(sourcesBuilder.toString())); if (i >= tableSize) break; i--; } // if both version dates have been found, the source can be found next since the title has been passed already if (entry.getCreateDate() != null && entry.getUpdateDate() != null) updateAndCreateDateFound = true; } // title is unused, as it appears as 'Objet:' in the title above the table // it still has to be collected as marker for the sources // the sources might be ditched later on, as the description further contents may contain the same contents in // the 'Documentation' section, but if not, it will keep the ones from this section if (sources.length() > 0) { entry.setDescription(DescriptionParagraph.fromContent(sources.toString())); } } private static boolean certFrIsNotTableRowIdentifier(String s) { return !s.contains("version") && !s.contains("Date de la") && !s.equals("Référence") && !s.equals("Titre") && !s.equals("Source(s)") && !s.equals("Pièce(s) jointe(s)"); } private static boolean certFrIsHeader(String line, int largestHeader, List allLines, int i) { for (String[] headers : CERT_FR_PARAGRAPH_HEADERS) { for (String header : headers) { if (header != null && line.matches(header)) { if (line.matches("^\\d+ .+")) { // number may be at most +2 of the largest header number found int headerNumber = certFrExtractHeaderNumber(line, largestHeader); if (headerNumber <= largestHeader + 2) { // next and previous line must be empty if (i > 0 && allLines.get(i - 1).isEmpty() && i < allLines.size() - 1 && allLines.get(i + 1).isEmpty()) { // may not contain -.1 or -.1.2, ... behind the first number if (!line.matches("^\\d+ - ?\\.\\d+.+")) { return true; } else { LOG.debug("Skipping header [{}] because it has -. behind it", line); return false; } } else { LOG.debug("Skipping header [{}] because it is not surrounded by empty lines", line); return false; } } else { LOG.debug("Skipping header [{}] because it is too large [{} | {}]", line, headerNumber, largestHeader); return false; } } return true; } } } return false; } private static int certFrExtractHeaderNumber(String line, int defaultValue) { if (line == null) return defaultValue; if (line.matches("^\\d+ .+")) { return Integer.parseInt(line.replaceAll("^(\\d+) .+", "$1")); } return defaultValue; } private static String certFrNormalizeHeaderText(String line) { for (String[] headers : CERT_FR_PARAGRAPH_HEADERS) { for (String header : headers) { if (header != null && line.matches(header)) { if (headers[headers.length - 1] == null) { return certFrNormalizeHeaderText(line.replaceAll("^(?:\\d{1,2}|##+) (.+)$", "$1") .replace("– ", "")); } return headers[headers.length - 1]; } } } return line; } private static String certFrPrepareSources(String sources) { return sources.replaceAll("(JANVIER|FÉVRIER|MARSAVRIL|MAI|JUIN|JUILLET|AOÛT|SEPTEMBRE|OCTOBRE|NOVEMBRE|DÉCEMBRE|janvier|février|mars|avril|mai|juin|juillet|août|septembre|octobre|novembre|décembre)\n", " $1 ") .replaceAll("((? lines = Arrays.asList(contentField.split("\n")); final Map furtherContent = new HashMap<>(); for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); if (certFrIsHeader(line, 1, lines, i)) { // if a header is found, collect the following lines until either another header is found or the end of the // document (Gestion détaillée du document) is found. final String header = certFrNormalizeHeaderText(line); final List contents = new ArrayList<>(); // skip to next line and continue adding lines until header or end of document is reached i++; if (i >= lines.size()) break; line = lines.get(i); while (!certFrIsHeader(line, 1, lines, i) && !line.equals("Gestion détaillée du document")) { contents.add(line); i++; if (i >= lines.size()) break; line = lines.get(i); } // add the paragraph to the further details furtherContent.computeIfAbsent(header, h -> new StringJoiner("\n")).add(String.join("\n", contents)); i--; } } final Map trimmedFurtherContent = furtherContent.entrySet() .stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> trimNewlines(e.getValue().toString().trim()).trim())); if (trimmedFurtherContent.containsKey("Temporary bypass")) { entry.setWorkarounds(trimmedFurtherContent.remove("Temporary bypass")); } if (trimmedFurtherContent.containsKey("Solution") || trimmedFurtherContent.containsKey("Recommendations")) { final StringJoiner recommendationsBuilder = new StringJoiner("\n\n"); if (trimmedFurtherContent.containsKey("Solution")) { recommendationsBuilder.add(trimmedFurtherContent.remove("Solution")); } if (trimmedFurtherContent.containsKey("Recommendations")) { recommendationsBuilder.add(trimmedFurtherContent.remove("Recommendations")); } entry.setRecommendations(recommendationsBuilder.toString()); } if (trimmedFurtherContent.containsKey("Documentation")) { final String[] documentationLines = trimmedFurtherContent.remove("Documentation").split("\n"); for (int i = 0; i < documentationLines.length - 1; i += 2) { final Reference ref = Reference.fromTitleAndUrl(documentationLines[i], documentationLines[i + 1]); entry.getReferences().add(ref); } } for (String header : trimmedFurtherContent.keySet()) { entry.addDescription(DescriptionParagraph.fromTitleAndContent(header, trimmedFurtherContent.get(header))); } } if (StringUtils.hasText(documentJson.optString("summary", null))) { entry.addDescription(DescriptionParagraph.fromTitleAndContent("Summary", documentJson.getString("summary").replace("\n\n\u00a0", "").replaceAll("\n+$", ""))); } final JSONArray revisions = documentJson.optJSONArray("revisions"); if (revisions != null && !revisions.isEmpty()) { final JSONObject firstRevision = revisions.getJSONObject(0); entry.setCreateDate(TimeUtils.tryParse(firstRevision.optString("revision_date", null))); final JSONObject lastRevision = revisions.getJSONObject(revisions.length() - 1); entry.setUpdateDate(TimeUtils.tryParse(lastRevision.optString("revision_date", null))); } final JSONArray risks = documentJson.optJSONArray("risks"); if (risks != null && !risks.isEmpty()) { final StringJoiner riskJoiner = new StringJoiner("\n"); for (int i = 0; i < risks.length(); i++) { final String risk = risks.getJSONObject(i).optString("description", null); if (risk != null) riskJoiner.add(risk); } entry.setThreat(riskJoiner.toString()); } final List affectedSystems = new ArrayList<>(); final JSONArray affectedSystemsArray = documentJson.optJSONArray("affected_systems"); if (affectedSystemsArray != null) { for (int i = 0; i < affectedSystemsArray.length(); i++) { final JSONObject affectedSystem = affectedSystemsArray.getJSONObject(i); final String affectedSystemDescription = affectedSystem.getString("description"); if (affectedSystem.optJSONObject("product") != null) { final String productName = affectedSystem.getJSONObject("product").getString("name"); if (StringUtils.hasText(productName) && !productName.equals("N/A")) { affectedSystems.add(productName + ": " + affectedSystemDescription); } else { affectedSystems.add(affectedSystemDescription); } } else { affectedSystems.add(affectedSystemDescription); } } } if (documentJson.optString("affected_systems_content", null) != null) { affectedSystems.add(documentJson.getString("affected_systems_content")); } if (!affectedSystems.isEmpty()) { entry.addDescription("Affected systems", affectedSystems.stream().sorted().collect(Collectors.joining("\n"))); } final JSONArray links = documentJson.optJSONArray("links"); if (links != null && !links.isEmpty()) { for (int i = 0; i < links.length(); i++) { final JSONObject link = links.optJSONObject(i); if (link != null) { entry.addReference(Reference.fromTitleAndUrl(link.getString("title"), link.getString("url"))); } } } final JSONArray cves = documentJson.optJSONArray("cves"); if (cves != null && !cves.isEmpty()) { for (int i = 0; i < cves.length(); i++) { final JSONObject cve = cves.optJSONObject(i); if (cve != null) { entry.addReferencedVulnerability(VulnerabilityTypeStore.CVE, cve.getString("name")); } } } final JSONArray vendorAdvisories = documentJson.optJSONArray("vendor_advisories"); // add as references if (vendorAdvisories != null && !vendorAdvisories.isEmpty()) { for (int i = 0; i < vendorAdvisories.length(); i++) { final JSONObject vendorAdvisory = vendorAdvisories.optJSONObject(i); if (vendorAdvisory != null) { final String url = vendorAdvisory.optString("url", null); if (url != null) { final Reference reference = Reference.fromTitleAndUrl(vendorAdvisory.optString("title", null), url); if (vendorAdvisory.optString("published_at", null) != null) { reference.addTag(vendorAdvisory.getString("published_at")); } entry.addReference(reference); } } } } AdvisoryEntry.extractReferenceIdsFromStringIfPresent(entry.getTextDescription(), entry, AdvisoryTypeStore.CERT_FR); AdvisoryEntry.extractReferenceIdsFromStringIfPresent(entry.getTextDescription(), entry, VulnerabilityTypeStore.CVE); AdvisoryEntry.extractReferenceIdsFromStringIfPresent(entry.getReferences().stream().map(Reference::toString).collect(Collectors.joining(", ")), entry, AdvisoryTypeStore.CERT_FR); return entry; } public static CertFrAdvisorEntry fromApiJson(File jsonFile) throws IOException { return fromApiJson(new JSONObject(FileUtils.readFileToString(jsonFile, StandardCharsets.UTF_8))); } /** * This is the most cursed stuff ever, I do not want to touch this ever again pls thx * * @return the text format of the CERT-FR advisory */ public String toCertFrArchiveLegacyTextFormat() { final StringJoiner textFormatJoiner = new StringJoiner("\n"); textFormatJoiner.add("PREMIER MINISTRE") .add("") .add("Paris, le 07 juin 2021") .add("N° " + id).add("") .add("S . G . D . S . N") .add("Agence nationale") .add("de la sécurité des").add("") .add("systèmes d'information").add("") .add("Affaire suivie par: CERT-FR").add("") .add("BULLETIN D'ALERTE DU CERT-FR").add("") .add("Objet: " + summary).add("") .add("Gestion du document") .add("") .add("Référence").add("") .add("Titre").add("") .add(id).add("") .add(summary).add("") .add("Date de la première version " + (createDate != null ? createDate.getTime() : "")).add("") .add("Date de la dernière version " + (updateDate != null ? updateDate.getTime() : "")).add("") .add("Source(s)").add("") .add("Aucune(s)").add("") .add("Pièce(s) jointe(s)").add("") .add("Aucune(s)").add("") .add("Tableau 1: Gestion du document").add("") .add("Une gestion de version détaillée se trouve à la fin de ce document.").add(""); if (threat != null) { textFormatJoiner .add("Risque(s)").add("") .add(threat); } textFormatJoiner.add(""); if (this.getDescription("Affected systems") != null) { textFormatJoiner.add("Systèmes affectés").add(""); textFormatJoiner.add(this.getDescription("Affected systems").getContent()).add(""); } if (this.getDescription("Summary") != null) { textFormatJoiner.add("Résumé").add(""); textFormatJoiner.add(this.getDescription("Summary").getContent()).add(""); } final String temporaryBypass = ObjectUtils.firstNonNull(this.getDescriptionContent("Temporary bypass"), this.getWorkarounds()); if (temporaryBypass != null) { textFormatJoiner.add("Contournement provisoire").add(""); textFormatJoiner.add(temporaryBypass).add(""); } final String recommendations = ObjectUtils.firstNonNull(this.getDescriptionContent("Solution"), this.getRecommendations()); if (recommendations != null) { textFormatJoiner.add("Solution").add(""); textFormatJoiner.add(recommendations).add(""); } for (DescriptionParagraph description : this.getDescription()) { if (description.getHeader() != null && !description.getHeader().isEmpty() && !description.getHeader().equals("Summary") && !description.getHeader().equals("Affected systems") && !description.getHeader().equals("Temporary bypass") && !description.getHeader().equals("Solution")) { textFormatJoiner.add(description.getHeader()).add(""); textFormatJoiner.add(description.getContent()).add(""); } } if (!references.isEmpty()) { textFormatJoiner.add("Documentation").add(""); for (Reference reference : references) { // first line is title and tags, second line is URL if (reference.getTitle() != null) { final String title; if (reference.getTitle().startsWith("CERTFR-") || reference.getTitle().startsWith("CERTA-")) { title = "; " + reference.getTitle(); } else { title = reference.getTitle(); } textFormatJoiner.add(title + (reference.getTags().isEmpty() ? "" : " (" + String.join(", ", reference.getTags()) + ")")) .add(reference.getUrl()); } else { textFormatJoiner.add("Reference").add(reference.getUrl()); } } textFormatJoiner.add(""); } return textFormatJoiner.toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy