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