diff options
| author | Adam Janovsky | 2022-05-10 17:02:56 +0200 |
|---|---|---|
| committer | Adam Janovsky | 2022-05-10 17:02:56 +0200 |
| commit | d10334b7aaab83c8aa5e0897c93c6465c85d4874 (patch) | |
| tree | b83d5ca882a83084f06de9b119a226e99e2bc900 | |
| parent | c1a28a8b8bad1c8026860e69045e35f6b1bd77a8 (diff) | |
| download | sec-certs-d10334b7aaab83c8aa5e0897c93c6465c85d4874.tar.gz sec-certs-d10334b7aaab83c8aa5e0897c93c6465c85d4874.tar.zst sec-certs-d10334b7aaab83c8aa5e0897c93c6465c85d4874.zip | |
implement EAL-implied SARs inference
| -rw-r--r-- | sec_certs/cert_rules.py | 175 | ||||
| -rw-r--r-- | sec_certs/dataset/common_criteria.py | 28 | ||||
| -rw-r--r-- | sec_certs/helpers.py | 11 | ||||
| -rw-r--r-- | sec_certs/sample/common_criteria.py | 37 | ||||
| -rw-r--r-- | tests/data/test_cc_heuristics/dependency_dataset.json | 38 | ||||
| -rw-r--r-- | tests/data/test_cc_heuristics/vulnerable_dataset.json | 99 | ||||
| -rw-r--r-- | tests/data/test_cc_oop/fictional_cert.json | 2 | ||||
| -rw-r--r-- | tests/data/test_cc_oop/toy_dataset.json | 4 | ||||
| -rw-r--r-- | tests/test_cc_heuristics.py | 15 |
9 files changed, 300 insertions, 109 deletions
diff --git a/sec_certs/cert_rules.py b/sec_certs/cert_rules.py index 399cafc5..914e6b3e 100644 --- a/sec_certs/cert_rules.py +++ b/sec_certs/cert_rules.py @@ -1,9 +1,180 @@ import copy import re -from typing import Dict, List, Pattern +from typing import Dict, List, Pattern, Set, Tuple REGEXEC_SEP = r"[ ,;\]”)(]" +# This ignores ACM and AMA SARs that are present in CC version 2 +SARS_IMPLIED_FROM_EAL: Dict[str, Set[Tuple[str, int]]] = { + "EAL1": { + ("ADV_FSP", 1), + ("AGD_OPE", 1), + ("AGD_PRE", 1), + ("ALC_CMC", 1), + ("ALC_CMS", 1), + ("ASE_CCL", 1), + ("ASE_ECD", 1), + ("ASE_INT", 1), + ("ASE_OBJ", 1), + ("ASE_REQ", 1), + ("ASE_TSS", 1), + ("ATE_IND", 1), + ("AVA_VAN", 1), + }, + "EAL2": { + ("ADV_ARC", 1), + ("ADV_TDS", 1), + ("AGD_OPE", 1), + ("AGD_PRE", 1), + ("ALC_CMC", 2), + ("ALC_CMS", 2), + ("ALC_DEL", 1), + ("ASE_CCL", 1), + ("ASE_ECD", 1), + ("ASE_INT", 1), + ("ASE_OBJ", 2), + ("ASE_REQ", 2), + ("ASE_SPD", 1), + ("ASE_TSS", 1), + ("ATE_COV", 1), + ("ATE_FUN", 1), + ("ATE_IND", 2), + ("AVA_VAN", 2), + }, + "EAL3": { + ("ADV_ARC", 1), + ("ADV_FSP", 3), + ("ADV_TDS", 2), + ("AGD_PRE", 1), + ("ALC_CMC", 3), + ("ALC_CMS", 3), + ("ALC_DEL", 1), + ("ALC_DVS", 1), + ("ALC_LCD", 1), + ("ASE_CCL", 1), + ("ASE_ECD", 1), + ("ASE_INT", 1), + ("ASE_OBJ", 2), + ("ASE_REQ", 2), + ("ASE_SPD", 1), + ("ASE_TSS", 1), + ("ATE_COV", 2), + ("ATE_DPT", 1), + ("ATE_FUN", 1), + ("ATE_IND", 2), + ("AVA_VAN", 2), + }, + "EAL4": { + ("ADV_ARC", 1), + ("ADV_FSP", 4), + ("ADV_IMP", 1), + ("ADV_TDS", 3), + ("AGD_OPE", 1), + ("AGD_PRE", 1), + ("ALC_CMC", 4), + ("ALC_CMS", 4), + ("ALC_DEL", 1), + ("ALC_DVS", 1), + ("ALC_LCD", 1), + ("ALC_TAT", 1), + ("ASE_CCL", 1), + ("ASE_ECD", 1), + ("ASE_INT", 1), + ("ASE_OBJ", 2), + ("ASE_REQ", 2), + ("ASE_SPD", 1), + ("ASE_TSS", 1), + ("ATE_COV", 2), + ("ATE_DPT", 1), + ("ATE_FUN", 1), + ("ATE_IND", 2), + ("AVA_VAN", 3), + }, + "EAL5": { + ("ADV_ARC", 1), + ("ADV_FSP", 5), + ("ADV_IMP", 1), + ("ADV_INT", 2), + ("ADV_TDS", 4), + ("AGD_OPE", 1), + ("AGD_PRE", 1), + ("ALC_CMC", 4), + ("ALC_CMS", 5), + ("ALC_DEL", 1), + ("ALC_DVS", 1), + ("ALC_LCD", 1), + ("ALC_TAT", 2), + ("ASE_CCL", 1), + ("ASE_ECD", 1), + ("ASE_INT", 1), + ("ASE_OBJ", 2), + ("ASE_REQ", 2), + ("ASE_SPD", 1), + ("ASE_TSS", 1), + ("ATE_COV", 2), + ("ATE_DPT", 3), + ("ATE_FUN", 1), + ("ATE_IND", 2), + ("AVA_VAN", 4), + }, + "EAL6": { + ("ADV_ARC", 1), + ("ADV_FSP", 5), + ("ADV_IMP", 2), + ("ADV_INT", 3), + ("ADV_SPM", 1), + ("ADV_TDS", 5), + ("AGD_OPE", 1), + ("AGD_PRE", 1), + ("ALC_CMC", 5), + ("ALC_CMS", 5), + ("ALC_DEL", 1), + ("ALC_DVS", 2), + ("ALC_LCD", 1), + ("ALC_TAT", 3), + ("ASE_CCL", 1), + ("ASE_ECD", 1), + ("ASE_INT", 1), + ("ASE_OBJ", 2), + ("ASE_REQ", 2), + ("ASE_SPD", 1), + ("ASE_TSS", 1), + ("ATE_COV", 3), + ("ATE_DPT", 3), + ("ATE_FUN", 2), + ("ATE_IND", 2), + ("AVA_VAN", 5), + }, + "EAL7": { + ("ADV_ARC", 1), + ("ADV_FSP", 6), + ("ADV_IMP", 2), + ("ADV_INT", 3), + ("ADV_SPM", 1), + ("ADV_TDS", 6), + ("AGD_OPE", 1), + ("AGD_PRE", 1), + ("ALC_CMC", 5), + ("ALC_CMS", 5), + ("ALC_DEL", 1), + ("ALC_DVS", 2), + ("ALC_LCD", 2), + ("ALC_TAT", 3), + ("ASE_CCL", 1), + ("ASE_ECD", 1), + ("ASE_INT", 1), + ("ASE_OBJ", 2), + ("ASE_REQ", 2), + ("ASE_SPD", 1), + ("ASE_TSS", 1), + ("ATE_COV", 3), + ("ATE_DPT", 4), + ("ATE_FUN", 2), + ("ATE_IND", 3), + ("AVA_VAN", 5), + }, +} + rules_cert_id = [ "BSI-DSZ-CC-[0-9]+?-[0-9]+", # German BSI "BSI-DSZ-CC-[0-9]+?-(?:V|v)[0-9]+-[0-9]+", # German BSI @@ -129,6 +300,8 @@ rules_security_level = [ "ITSEC[ ]*E[1-9]*.+?", ] +security_level_csv_scan = r"EAL[1-7]\+?" + rules_security_assurance_components = [ r"ACE(?:_[A-Z]{3,4}){1,2}(?:\.[0-9]|\.[0-9]\.[0-9]|)", r"ACM(?:_[A-Z]{3,4}){1,2}(?:\.[0-9]|\.[0-9]\.[0-9]|)", diff --git a/sec_certs/dataset/common_criteria.py b/sec_certs/dataset/common_criteria.py index 93caf40a..e9af7b7a 100644 --- a/sec_certs/dataset/common_criteria.py +++ b/sec_certs/dataset/common_criteria.py @@ -69,29 +69,9 @@ class CCDataset(Dataset[CommonCriteriaCert], ComplexSerializableType): ~df.manufacturer.isnull() ] # Manually delete one certificate with None manufacturer (seems to have many blank fields) - # Introduce highest security level EAL variable - eals = [ - "EAL1", - "EAL1+", - "EAL2", - "EAL2+", - "EAL3", - "EAL3+", - "EAL4", - "EAL4+", - "EAL5", - "EAL5+", - "EAL6+", - "EAL7", - "EAL7+", - ] - df["highest_security_level"] = df.security_level.map( - lambda all_levels: [eal for eal in all_levels if eal.startswith("EAL")] if all_levels else np.nan - ) - df.highest_security_level = df.highest_security_level.map( - lambda x: x[0] if x and isinstance(x, list) else np.nan - ) - df.highest_security_level = pd.Categorical(df.highest_security_level, categories=eals, ordered=True) + # Categorize EAL + df.eal = df.eal.fillna(value=np.nan) + df.eal = pd.Categorical(df.eal, categories=sorted(df.eal.dropna().unique().tolist()), ordered=True) # Introduce year when cert got valid df["year_from"] = pd.DatetimeIndex(df.not_valid_before).year @@ -754,7 +734,7 @@ class CCDataset(Dataset[CommonCriteriaCert], ComplexSerializableType): logger.info("Computing SARs") transformer = SARTransformer().fit(self.certs.values()) for cert in self: - cert.heuristics.sars = transformer.transform_single_cert(cert) + cert.heuristics.extracted_sars = transformer.transform_single_cert(cert) def _compute_dependencies(self) -> None: def ref_lookup(kw_attr): diff --git a/sec_certs/helpers.py b/sec_certs/helpers.py index e1148a36..20e6578e 100644 --- a/sec_certs/helpers.py +++ b/sec_certs/helpers.py @@ -139,16 +139,7 @@ def sanitize_string(record: str) -> str: def sanitize_security_levels(record: Union[str, Set[str]]) -> Set[str]: if isinstance(record, str): record = set(record.split(",")) - - if "PP\xa0Compliant" in record: - record.remove("PP\xa0Compliant") - - if "None" in record: - record.remove("None") - - # TODO: There may be 'basic' in the levels. Delete as well? - - return record + return record - {"Basic", "ND-PP", "PP\xa0Compliant", "None"} def sanitize_protection_profiles(record: str) -> list: diff --git a/sec_certs/sample/common_criteria.py b/sec_certs/sample/common_criteria.py index c3c0a624..29789d2a 100644 --- a/sec_certs/sample/common_criteria.py +++ b/sec_certs/sample/common_criteria.py @@ -1,5 +1,6 @@ import copy import operator +import re from dataclasses import dataclass, field from datetime import date, datetime from enum import Enum @@ -12,6 +13,7 @@ from bs4 import Tag from sec_certs import constants as constants from sec_certs import helpers +from sec_certs.cert_rules import SARS_IMPLIED_FROM_EAL, security_level_csv_scan from sec_certs.model.cpe_matching import CPEClassifier from sec_certs.model.dependency_finder import References from sec_certs.sample.certificate import Certificate, Heuristics, logger @@ -248,7 +250,7 @@ class CommonCriteriaCert( cert_id: Optional[str] = field(default=None) st_references: References = field(default_factory=References) report_references: References = field(default_factory=References) - sars: Optional[Set[SAR]] = field(default=None) + extracted_sars: Optional[Set[SAR]] = field(default=None) direct_dependency_cves: Optional[Set[str]] = field(default=None) indirect_dependency_cves: Optional[Set[str]] = field(default=None) @@ -265,6 +267,7 @@ class CommonCriteriaCert( "manufacturer", "scheme", "security_level", + "eal", "not_valid_before", "not_valid_after", "report_link", @@ -278,7 +281,7 @@ class CommonCriteriaCert( "indirectly_referenced_by", "directly_referencing", "indirectly_referencing", - "sars", + "extracted_sars", ] def __init__( @@ -335,6 +338,33 @@ class CommonCriteriaCert( return helpers.get_first_16_bytes_sha256(self.category + self.name + self.report_link) @property + def eal(self) -> Optional[str]: + res = [x for x in self.security_level if re.match(security_level_csv_scan, x)] + if not res: + return None + + if not len(res) == 1: + raise ValueError(f"Expected single EAL in security_level field, got: {res}") + return res[0] + + @property + def actual_sars(self) -> Optional[Set[SAR]]: + """ + Computes actual SARs. First, SARs implied by EAL are computed. Then, these are augmented with heuristically extracted SARs + :return Optional[Set[SAR]]: Set of actual SARs of a certificate, None if empty + """ + sars = dict() + if self.eal: + sars = {x[0]: SAR(x[0], x[1]) for x in SARS_IMPLIED_FROM_EAL[self.eal[:4]]} + + if self.heuristics.extracted_sars: + for sar in self.heuristics.extracted_sars: + if sar not in sars or sar.level > sars[sar.family].level: + sars[sar.family] = sar + + return set(sars.values()) if sars else None + + @property def label_studio_title(self) -> str: return self.name @@ -349,6 +379,7 @@ class CommonCriteriaCert( self.manufacturer, self.scheme, self.security_level, + self.eal, self.not_valid_before, self.not_valid_after, self.report_link, @@ -362,7 +393,7 @@ class CommonCriteriaCert( self.heuristics.report_references.indirectly_referenced_by, self.heuristics.report_references.directly_referencing, self.heuristics.report_references.indirectly_referencing, - self.heuristics.sars, + self.heuristics.extracted_sars, ) def __str__(self) -> str: diff --git a/tests/data/test_cc_heuristics/dependency_dataset.json b/tests/data/test_cc_heuristics/dependency_dataset.json index 7e226577..44e61512 100644 --- a/tests/data/test_cc_heuristics/dependency_dataset.json +++ b/tests/data/test_cc_heuristics/dependency_dataset.json @@ -12,8 +12,7 @@ "name": "test dataset", "description": "test dataset for testing dependencies", "n_certs": 3, - "certs": [ - { + "certs": [{ "_type": "CommonCriteriaCert", "dgst": "c30de3192d2e8ec2", "status": "archived", @@ -21,10 +20,13 @@ "name": "Océ Digital Access Controller (DAC) R10.1.5 for use in the Océ VarioPrint 1055, 1055 BC, 1055 DP, 1065, 1075, 2062, 2075, 2075 DP printer/copier/scanner products", "manufacturer": "Océ N.V.", "scheme": "DE", - "security_level": [ - "ALC_FLR.1", - "EAL2+" - ], + "security_level": { + "_type": "Set", + "elements": [ + "ALC_FLR.1", + "EAL2+" + ] + }, "not_valid_before": "2009-02-20", "not_valid_after": "2019-09-01", "report_link": "https://www.commoncriteriaportal.org/files/epfiles/0517a.pdf", @@ -341,10 +343,13 @@ "name": "Océ Digital Access Controller (DAC) R9.1.6", "manufacturer": "Océ N.V.", "scheme": "DE", - "security_level": [ - "ALC_FLR.1", - "EAL2+" - ], + "security_level": { + "_type": "Set", + "elements": [ + "ALC_FLR.1", + "EAL2+" + ] + }, "not_valid_before": "2006-10-26", "not_valid_after": "2019-09-01", "report_link": "https://www.commoncriteriaportal.org/files/epfiles/0370a.pdf", @@ -691,10 +696,13 @@ "name": "Océ Digital Access Controller R8.1.10", "manufacturer": "Océ N.V.", "scheme": "DE", - "security_level": [ - "ALC_FLR.1", - "EAL2+" - ], + "security_level": { + "_type": "Set", + "elements": [ + "ALC_FLR.1", + "EAL2+" + ] + }, "not_valid_before": "2006-01-27", "not_valid_after": "2019-09-01", "report_link": "https://www.commoncriteriaportal.org/files/epfiles/0325a.pdf", @@ -1035,4 +1043,4 @@ } } ] -} +}
\ No newline at end of file diff --git a/tests/data/test_cc_heuristics/vulnerable_dataset.json b/tests/data/test_cc_heuristics/vulnerable_dataset.json index c14c8142..f6fc18bb 100644 --- a/tests/data/test_cc_heuristics/vulnerable_dataset.json +++ b/tests/data/test_cc_heuristics/vulnerable_dataset.json @@ -12,57 +12,58 @@ "name": "cc_full_dataset", "description": "sample dataset description", "n_certs": 1, - "certs": [ - { - "_type": "CommonCriteriaCert", - "dgst": "c01e5375331b25dc", - "status": "active", - "category": "Access Control Devices and Systems", - "name": "IBM Security Access Manager for Enterprise Single Sign-On Version 8.2", - "manufacturer": "IBM Corporation", - "scheme": "DE", - "security_level": [ + "certs": [{ + "_type": "CommonCriteriaCert", + "dgst": "c01e5375331b25dc", + "status": "active", + "category": "Access Control Devices and Systems", + "name": "IBM Security Access Manager for Enterprise Single Sign-On Version 8.2", + "manufacturer": "IBM Corporation", + "scheme": "DE", + "security_level": { + "_type": "Set", + "elements": [ "ALC_FLR.1", "EAL3+" + ] + }, + "not_valid_before": "2014-12-05", + "not_valid_after": null, + "report_link": "http://www.commoncriteriaportal.org/files/epfiles/0683a_pdf.pdf", + "st_link": "http://www.commoncriteriaportal.org/files/epfiles/0683b_pdf.pdf", + "cert_link": null, + "manufacturer_web": "http://www.ibm.com", + "protection_profiles": [], + "maintenance_updates": [], + "state": { + "_type": "InternalState", + "st_download_ok": true, + "report_download_ok": true, + "st_convert_ok": true, + "report_convert_ok": true, + "st_extract_ok": true, + "report_extract_ok": true, + "errors": [] + }, + "pdf_data": { + "_type": "PdfData", + "report_metadata": null, + "st_metadata": null, + "report_frontpage": null, + "st_frontpage": null, + "report_keywords": null, + "st_keywords": null + }, + "heuristics": { + "_type": "CCHeuristics", + "extracted_versions": [ + "8.2" ], - "not_valid_before": "2014-12-05", - "not_valid_after": null, - "report_link": "http://www.commoncriteriaportal.org/files/epfiles/0683a_pdf.pdf", - "st_link": "http://www.commoncriteriaportal.org/files/epfiles/0683b_pdf.pdf", - "cert_link": null, - "manufacturer_web": "http://www.ibm.com", - "protection_profiles": [], - "maintenance_updates": [], - "state": { - "_type": "InternalState", - "st_download_ok": true, - "report_download_ok": true, - "st_convert_ok": true, - "report_convert_ok": true, - "st_extract_ok": true, - "report_extract_ok": true, - "errors": [] - }, - "pdf_data": { - "_type": "PdfData", - "report_metadata": null, - "st_metadata": null, - "report_frontpage": null, - "st_frontpage": null, - "report_keywords": null, - "st_keywords": null - }, - "heuristics": { - "_type": "CCHeuristics", - "extracted_versions": [ - "8.2" - ], - "cpe_matches": null, - "verified_cpe_matches": null, - "related_cves": null, - "cert_lab": null, - "cert_id": null - } + "cpe_matches": null, + "verified_cpe_matches": null, + "related_cves": null, + "cert_lab": null, + "cert_id": null } - ] + }] }
\ No newline at end of file diff --git a/tests/data/test_cc_oop/fictional_cert.json b/tests/data/test_cc_oop/fictional_cert.json index 633157e1..441ae7d2 100644 --- a/tests/data/test_cc_oop/fictional_cert.json +++ b/tests/data/test_cc_oop/fictional_cert.json @@ -61,7 +61,7 @@ "related_cves": null, "cert_lab": null, "cert_id": null, - "sars": null, + "extracted_sars": null, "direct_dependency_cves": null, "indirect_dependency_cves": null, "report_references": { diff --git a/tests/data/test_cc_oop/toy_dataset.json b/tests/data/test_cc_oop/toy_dataset.json index 3aebf682..740ddf1b 100644 --- a/tests/data/test_cc_oop/toy_dataset.json +++ b/tests/data/test_cc_oop/toy_dataset.json @@ -68,7 +68,7 @@ "related_cves": null, "cert_lab": null, "cert_id": null, - "sars": null, + "extracted_sars": null, "direct_dependency_cves": null, "indirect_dependency_cves": null, "report_references": { @@ -145,7 +145,7 @@ "related_cves": null, "cert_lab": null, "cert_id": null, - "sars": null, + "extracted_sars": null, "direct_dependency_cves": null, "indirect_dependency_cves": null, "report_references": { diff --git a/tests/test_cc_heuristics.py b/tests/test_cc_heuristics.py index b034f841..5f4946ed 100644 --- a/tests/test_cc_heuristics.py +++ b/tests/test_cc_heuristics.py @@ -6,6 +6,7 @@ from unittest import TestCase import tests.data.test_cc_heuristics from sec_certs import constants +from sec_certs.cert_rules import SARS_IMPLIED_FROM_EAL from sec_certs.dataset.common_criteria import CCDataset from sec_certs.dataset.cpe import CPEDataset from sec_certs.dataset.cve import CVEDataset @@ -317,9 +318,15 @@ class TestCommonCriteriaHeuristics(TestCase): test_cert = self.cc_dset["ebd276cca70fd723"] # This one should be taken from security level and not overwritten by stronger SARs in ST - self.assertTrue(SAR("ALC_FLR", 1) in test_cert.heuristics.sars) - self.assertTrue(SAR("ALC_FLR", 2) not in test_cert.heuristics.sars) + self.assertTrue(SAR("ALC_FLR", 1) in test_cert.heuristics.extracted_sars) + self.assertTrue(SAR("ALC_FLR", 2) not in test_cert.heuristics.extracted_sars) # This one should be taken from ST and not overwritten by stronger SAR in report - self.assertTrue(SAR("ADV_FSP", 3) in test_cert.heuristics.sars) - self.assertTrue(SAR("ADV_FSP", 6) not in test_cert.heuristics.sars) + self.assertTrue(SAR("ADV_FSP", 3) in test_cert.heuristics.extracted_sars) + self.assertTrue(SAR("ADV_FSP", 6) not in test_cert.heuristics.extracted_sars) + + def test_eal_implied_sar_inference(self): + test_cert = self.cc_dset["ebd276cca70fd723"] + actual_sars = test_cert.actual_sars + eal_3_sars = {SAR(x[0], x[1]) for x in SARS_IMPLIED_FROM_EAL["EAL3"]} + self.assertTrue(eal_3_sars.issubset(actual_sars)) |
