aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Janovsky2022-05-10 17:02:56 +0200
committerAdam Janovsky2022-05-10 17:02:56 +0200
commitd10334b7aaab83c8aa5e0897c93c6465c85d4874 (patch)
treeb83d5ca882a83084f06de9b119a226e99e2bc900
parentc1a28a8b8bad1c8026860e69045e35f6b1bd77a8 (diff)
downloadsec-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.py175
-rw-r--r--sec_certs/dataset/common_criteria.py28
-rw-r--r--sec_certs/helpers.py11
-rw-r--r--sec_certs/sample/common_criteria.py37
-rw-r--r--tests/data/test_cc_heuristics/dependency_dataset.json38
-rw-r--r--tests/data/test_cc_heuristics/vulnerable_dataset.json99
-rw-r--r--tests/data/test_cc_oop/fictional_cert.json2
-rw-r--r--tests/data/test_cc_oop/toy_dataset.json4
-rw-r--r--tests/test_cc_heuristics.py15
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))