aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/sec_certs/constants.py7
-rw-r--r--src/sec_certs/dataset/cc.py23
-rw-r--r--src/sec_certs/sample/cc.py58
-rw-r--r--src/sec_certs/sample/cc_scheme.py216
-rw-r--r--src/sec_certs/utils/sanitization.py9
5 files changed, 153 insertions, 160 deletions
diff --git a/src/sec_certs/constants.py b/src/sec_certs/constants.py
index e9b6d882..69f0c6cd 100644
--- a/src/sec_certs/constants.py
+++ b/src/sec_certs/constants.py
@@ -145,7 +145,6 @@ CC_SWEDEN_INEVAL_URL = CC_SWEDEN_BASE_URL + "/verksamhet/ovrig-verksamhet/csec/p
CC_SWEDEN_ARCHIVED_URL = CC_SWEDEN_BASE_URL + "/verksamhet/ovrig-verksamhet/csec/arkiverade-certifikat-aldre-an-5-ar/"
CC_TURKEY_ARCHIVED_URL = "https://statik.tse.org.tr/upload/tr/dosya/icerikyonetimi/3300/03112021143434-2.pdf"
CC_USA_BASE_URL = "https://www.niap-ccevs.org"
-CC_USA_PRODUCT_URL = CC_USA_BASE_URL + "/Product/"
-CC_USA_CERTIFIED_URL = CC_USA_BASE_URL + "/Product/PCL.cfm"
-CC_USA_INEVAL_URL = CC_USA_BASE_URL + "/Product/PINE.cfm"
-CC_USA_ARCHIVED_URL = CC_USA_BASE_URL + "/Product/Archived.cfm"
+CC_USA_PRODUCTS_URL = CC_USA_BASE_URL + "/api/project/product/pcl_products/"
+CC_USA_FILES_URL = CC_USA_BASE_URL + "/api/file/get_pcl_files/"
+CC_USA_GETFILE_URL = CC_USA_BASE_URL + "/api/file/get_public_file/"
diff --git a/src/sec_certs/dataset/cc.py b/src/sec_certs/dataset/cc.py
index 8e6ecebb..ebffd912 100644
--- a/src/sec_certs/dataset/cc.py
+++ b/src/sec_certs/dataset/cc.py
@@ -34,7 +34,7 @@ from sec_certs.sample.cc_maintenance_update import CCMaintenanceUpdate
from sec_certs.sample.cc_scheme import EntryType
from sec_certs.sample.protection_profile import ProtectionProfile
from sec_certs.serialization.json import ComplexSerializableType, serialize
-from sec_certs.utils import helpers
+from sec_certs.utils import helpers, sanitization
from sec_certs.utils import parallel_processing as cert_processing
from sec_certs.utils.profiling import staged
@@ -368,7 +368,14 @@ class CCDataset(Dataset[CCCertificate, CCAuxiliaryDatasets], ComplexSerializable
return CCDataset.BASE_URL + relative_path
def _get_primary_key_str(row: Tag):
- return row["category"] + row["cert_name"] + row["report_link"]
+ return "|".join(
+ [
+ row["category"],
+ row["cert_name"],
+ sanitization.sanitize_link_fname(row["report_link"]) or "None",
+ sanitization.sanitize_link_fname(row["st_link"]) or "None",
+ ]
+ )
cert_status = "active" if "active" in str(file) else "archived"
@@ -408,11 +415,15 @@ class CCDataset(Dataset[CCCertificate, CCAuxiliaryDatasets], ComplexSerializable
df_base = df.loc[~df.is_maintenance].copy()
df_main = df.loc[df.is_maintenance].copy()
- df_base.report_link = df_base.report_link.map(map_ip_to_hostname)
- df_base.st_link = df_base.st_link.map(map_ip_to_hostname)
+ df_base.report_link = df_base.report_link.map(map_ip_to_hostname).map(sanitization.sanitize_link)
+ df_base.st_link = df_base.st_link.map(map_ip_to_hostname).map(sanitization.sanitize_link)
- df_main.maintenance_report_link = df_main.maintenance_report_link.map(map_ip_to_hostname)
- df_main.maintenance_st_link = df_main.maintenance_st_link.map(map_ip_to_hostname)
+ df_main.maintenance_report_link = df_main.maintenance_report_link.map(map_ip_to_hostname).map(
+ sanitization.sanitize_link
+ )
+ df_main.maintenance_st_link = df_main.maintenance_st_link.map(map_ip_to_hostname).map(
+ sanitization.sanitize_link
+ )
n_all = len(df_base)
n_deduplicated = len(df_base.drop_duplicates(subset=["dgst"]))
diff --git a/src/sec_certs/sample/cc.py b/src/sec_certs/sample/cc.py
index 9f8ba2bd..5354ab3b 100644
--- a/src/sec_certs/sample/cc.py
+++ b/src/sec_certs/sample/cc.py
@@ -15,7 +15,6 @@ from bs4 import Tag
import sec_certs.utils.extract
import sec_certs.utils.pdf
-import sec_certs.utils.sanitization
from sec_certs import constants
from sec_certs.cert_rules import SARS_IMPLIED_FROM_EAL, cc_rules, rules, security_level_csv_scan
from sec_certs.configuration import config
@@ -27,7 +26,7 @@ from sec_certs.sample.protection_profile import ProtectionProfile
from sec_certs.sample.sar import SAR
from sec_certs.serialization.json import ComplexSerializableType
from sec_certs.serialization.pandas import PandasSerializableType
-from sec_certs.utils import helpers
+from sec_certs.utils import helpers, sanitization
from sec_certs.utils.extract import normalize_match_string, scheme_frontpage_functions
@@ -57,16 +56,10 @@ class CCCertificate(
maintenance_st_link: str | None
def __post_init__(self):
- super().__setattr__(
- "maintenance_report_link", sec_certs.utils.sanitization.sanitize_cc_link(self.maintenance_report_link)
- )
- super().__setattr__(
- "maintenance_st_link", sec_certs.utils.sanitization.sanitize_cc_link(self.maintenance_st_link)
- )
- super().__setattr__(
- "maintenance_title", sec_certs.utils.sanitization.sanitize_string(self.maintenance_title)
- )
- super().__setattr__("maintenance_date", sec_certs.utils.sanitization.sanitize_date(self.maintenance_date))
+ super().__setattr__("maintenance_report_link", sanitization.sanitize_link(self.maintenance_report_link))
+ super().__setattr__("maintenance_st_link", sanitization.sanitize_link(self.maintenance_st_link))
+ super().__setattr__("maintenance_title", sanitization.sanitize_string(self.maintenance_title))
+ super().__setattr__("maintenance_date", sanitization.sanitize_date(self.maintenance_date))
@classmethod
def from_dict(cls, dct: dict) -> CCCertificate.MaintenanceReport:
@@ -420,20 +413,20 @@ class CCCertificate(
self.status = status
self.category = category
- self.name = sec_certs.utils.sanitization.sanitize_string(name)
+ self.name = sanitization.sanitize_string(name)
self.manufacturer = None
if manufacturer:
- self.manufacturer = sec_certs.utils.sanitization.sanitize_string(manufacturer)
+ self.manufacturer = sanitization.sanitize_string(manufacturer)
self.scheme = scheme
- self.security_level = sec_certs.utils.sanitization.sanitize_security_levels(security_level)
- self.not_valid_before = sec_certs.utils.sanitization.sanitize_date(not_valid_before)
- self.not_valid_after = sec_certs.utils.sanitization.sanitize_date(not_valid_after)
- self.report_link = sec_certs.utils.sanitization.sanitize_cc_link(report_link)
- self.st_link = sec_certs.utils.sanitization.sanitize_cc_link(st_link)
- self.cert_link = sec_certs.utils.sanitization.sanitize_cc_link(cert_link)
- self.manufacturer_web = sec_certs.utils.sanitization.sanitize_link(manufacturer_web)
+ self.security_level = sanitization.sanitize_security_levels(security_level)
+ self.not_valid_before = sanitization.sanitize_date(not_valid_before)
+ self.not_valid_after = sanitization.sanitize_date(not_valid_after)
+ self.report_link = sanitization.sanitize_link(report_link)
+ self.st_link = sanitization.sanitize_link(st_link)
+ self.cert_link = sanitization.sanitize_link(cert_link)
+ self.manufacturer_web = sanitization.sanitize_link(manufacturer_web)
self.protection_profiles = protection_profiles
self.maintenance_updates = maintenance_updates
self.state = state if state else self.InternalState()
@@ -445,6 +438,29 @@ class CCCertificate(
"""
Computes the primary key of the sample using first 16 bytes of SHA-256 digest
"""
+ if not (self.name is not None and self.category is not None):
+ raise RuntimeError("Certificate digest can't be computed, because information is missing.")
+ return helpers.get_first_16_bytes_sha256(
+ "|".join(
+ [
+ self.category,
+ self.name,
+ sanitization.sanitize_link_fname(self.report_link) or "None",
+ sanitization.sanitize_link_fname(self.st_link) or "None",
+ ]
+ )
+ )
+
+ @property
+ def old_dgst(self) -> str:
+ if not (self.name is not None and self.report_link is not None and self.category is not None):
+ raise RuntimeError("Certificate digest can't be computed, because information is missing.")
+ return helpers.get_first_16_bytes_sha256(
+ self.category + self.name + sanitization.sanitize_cc_link(self.report_link) # type: ignore
+ )
+
+ @property
+ def older_dgst(self) -> str:
if not (self.name is not None and self.report_link is not None and self.category is not None):
raise RuntimeError("Certificate digest can't be computed, because information is missing.")
return helpers.get_first_16_bytes_sha256(self.category + self.name + self.report_link)
diff --git a/src/sec_certs/sample/cc_scheme.py b/src/sec_certs/sample/cc_scheme.py
index 1af70e76..2785551d 100644
--- a/src/sec_certs/sample/cc_scheme.py
+++ b/src/sec_certs/sample/cc_scheme.py
@@ -18,6 +18,7 @@ from urllib.parse import urljoin
import requests
import tabula
from bs4 import BeautifulSoup, NavigableString, Tag
+from dateutil.parser import isoparse
from requests import Response
from urllib3.connectionpool import InsecureRequestWarning
@@ -65,12 +66,13 @@ __all__ = [
]
-def _get(url: str, session, **kwargs) -> Response:
+def _getq(url: str, params, session=None, **kwargs) -> Response:
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=InsecureRequestWarning)
conn = session if session else requests
resp = conn.get(
url,
+ params=params,
headers={"User-Agent": "sec-certs.org"},
verify=False,
**kwargs,
@@ -80,6 +82,10 @@ def _get(url: str, session, **kwargs) -> Response:
return resp
+def _get(url: str, session=None, **kwargs) -> Response:
+ return _getq(url, None, session, **kwargs)
+
+
def _get_page(url: str, session=None) -> BeautifulSoup:
return BeautifulSoup(_get(url, session).content, "html5lib")
@@ -1306,7 +1312,7 @@ def get_spain_certified() -> list[dict[str, Any]]:
"product_link": urljoin(constants.CC_SPAIN_BASE_URL, tds[0].find("a")["href"]),
"category": sns(tds[1].text),
"manufacturer": sns(tds[2].text),
- "certification_date": sns(tds[3].find("td", class_="djc_value").text),
+ "certification_date": sns(tds[3].text),
}
results.append(cert)
return results
@@ -1444,6 +1450,78 @@ def get_turkey_certified() -> list[dict[str, Any]]:
return results
+def _get_usa(args, enhanced: bool, artifacts: bool): # noqa: C901
+ # TODO: There is more information in the API (like about PPs, etc.)
+ def map_cert(cert, files=None): # noqa: C901
+ result = {
+ "product": cert["product_name"],
+ "id": f"CCEVS-VR-VID{cert['product_id']}",
+ "url": constants.CC_USA_BASE_URL + f"/product/{cert['product_id']}",
+ "certification_date": cert["certification_date"],
+ "expiration_date": cert["sunset_date"],
+ "category": cert["tech_type"],
+ "vendor": cert["vendor_id_name"],
+ "evaluation_facility": cert["assigned_lab_name"],
+ "scheme": cert["submitting_country_id_code"],
+ }
+ if files:
+ for file in files["eval_files"]:
+ if file["file_label"] == "Validation Report":
+ dt = isoparse(file["uploaded_on"])
+ result["id"] += f"-{dt.year}"
+ result["report_link"] = constants.CC_USA_GETFILE_URL + f"?file_id={file['file_id']}"
+ if artifacts:
+ result["report_hash"] = _get_hash(result["report_link"]).hex()
+ elif file["file_label"] == "CC Certificate":
+ result["cert_link"] = constants.CC_USA_GETFILE_URL + f"?file_id={file['file_id']}"
+ if artifacts:
+ result["cert_hash"] = _get_hash(result["cert_link"]).hex()
+ elif file["file_label"] == "Security Target":
+ result["target_link"] = constants.CC_USA_GETFILE_URL + f"?file_id={file['file_id']}"
+ if artifacts:
+ result["target_hash"] = _get_hash(result["target_link"]).hex()
+ elif file["file_label"] == "Assurance Activity Report (AAR)":
+ result["aar_link"] = constants.CC_USA_GETFILE_URL + f"?file_id={file['file_id']}"
+ if artifacts:
+ result["aar_hash"] = _get_hash(result["aar_link"]).hex()
+ elif file["file_label"] == "Administrative Guide (AGD)":
+ result["agd_link"] = constants.CC_USA_GETFILE_URL + f"?file_id={file['file_id']}"
+ if artifacts:
+ result["agd_hash"] = _get_hash(result["agd_link"]).hex()
+
+ return result
+
+ session = requests.session()
+ results = []
+ offset = 0
+ got = 0
+ while True:
+ resp = _getq(
+ constants.CC_USA_PRODUCTS_URL,
+ {"limit": 100, "offset": offset, **args},
+ session,
+ )
+ json = resp.json()
+ count = json["count"]
+ for cert in json["results"]["products"]:
+ got += 1
+ if "from_cc_portal" in cert:
+ continue
+ files = None
+ if enhanced:
+ resp = _getq(
+ constants.CC_USA_FILES_URL,
+ {"product_id": cert["product_id"]},
+ session,
+ )
+ files = resp.json()
+ results.append(map_cert(cert, files))
+ offset += 100
+ if got >= count:
+ break
+ return results
+
+
def get_usa_certified( # noqa: C901
enhanced: bool = True, artifacts: bool = False
) -> list[dict[str, Any]]:
@@ -1454,83 +1532,11 @@ def get_usa_certified( # noqa: C901
:param artifacts: Whether to download and compute artifact hashes (way slower, even more data).
:return: The entries.
"""
- # TODO: Information could be expanded by following the cc_claims (has links to protection profiles).
- soup = _get_page(constants.CC_USA_CERTIFIED_URL)
- tbody = soup.find("table", class_="tablesorter").find("tbody")
- results = []
- for tr in tbody.find_all("tr"):
- tds = tr.find_all("td")
- vendor_span = tds[0].find("span", class_="b u")
- product_link = tds[0].find("a")
- scheme_img = tds[6].find("img")
- # Only return the US certifications.
- if scheme_img["title"] != "USA":
- continue
- cert: dict[str, Any] = {
- "product": sns(product_link.text),
- "vendor": sns(vendor_span.text),
- "product_link": urljoin(constants.CC_USA_PRODUCT_URL, product_link["href"]),
- "id": sns(tds[1].text),
- "cc_claim": sns(tds[2].text),
- "cert_lab": sns(tds[3].text),
- "certification_date": sns(tds[4].text),
- "assurance_maintenance_date": sns(tds[5].text),
- }
- if enhanced:
- e: dict[str, Any] = {}
- if not cert["product_link"]:
- continue
- cert_page = _get_page(cert["product_link"])
- details = cert_page.find("div", class_="txt2 lma")
- for span in details.find_all("span"):
- title = sns(span.text)
- if not title:
- continue
- sibling = span.next_sibling
- value = sns(sibling.text)
- if "Certificate Date" in title:
- e["certification_date"] = value
- elif "Product Type" in title:
- e["product_type"] = value
- elif "Conformance Claim" in title:
- e["cc_claim"] = value
- elif "Validation Report Number" in title:
- e["cert_id"] = value
- elif "PP Identifier" in title:
- e["protection_profile"] = sns(span.find_next_sibling("a").text)
- elif "CC Testing Lab" in title:
- e["evaluation_facility"] = sns(span.find_next_sibling("a").text)
- links = cert_page.find_all("a", class_="pseudobtn1")
- for link in links:
- name = sns(link.text)
- href = urljoin(constants.CC_USA_BASE_URL, sns(link["href"]))
- if not name:
- continue
- if "CC Certificate" in name:
- e["cert_link"] = href
- if artifacts:
- e["cert_hash"] = _get_hash(href).hex()
- elif "Security Target" in name:
- e["target_link"] = href
- if artifacts:
- e["target_hash"] = _get_hash(href).hex()
- elif "Validation Report" in name:
- e["report_link"] = href
- if artifacts:
- e["report_hash"] = _get_hash(href).hex()
- elif "Assurance Activity" in name:
- e["assurance_activity_link"] = href
- if artifacts:
- e["assurance_activity_hash"] = _get_hash(href).hex()
- elif "Administrative Guide" in name:
- guides = e.setdefault("administrative_guides", [])
- guide = {"link": href}
- guides.append(guide)
- if artifacts:
- guide["hash"] = _get_hash(href).hex()
- cert["enhanced"] = e
- results.append(cert)
- return results
+ return _get_usa(
+ {"certification_status": "Certified", "publish_status": "Published"},
+ enhanced,
+ artifacts,
+ )
def get_usa_in_evaluation() -> list[dict[str, Any]]:
@@ -1539,29 +1545,7 @@ def get_usa_in_evaluation() -> list[dict[str, Any]]:
:return: The entries.
"""
- # TODO: Information could be expanded by following the cc_claims (has links to protection profiles).
- soup = _get_page(constants.CC_USA_INEVAL_URL)
- tbody = soup.find("table", class_="tablesorter").find("tbody")
- results = []
- for tr in tbody.find_all("tr"):
- tds = tr.find_all("td")
- vendor_span = tds[0].find("span", class_="b u")
- product_name = None
- for child in tds[0].children:
- if isinstance(child, NavigableString):
- product_name = sns(child)
- break
- cert = {
- "vendor": sns(vendor_span.text),
- "id": sns(tds[1].text),
- "cc_claim": sns(tds[2].text),
- "cert_lab": sns(tds[3].text),
- "kickoff_date": sns(tds[4].text),
- }
- if product_name:
- cert["product"] = product_name
- results.append(cert)
- return results
+ return _get_usa({"status": "In Progress", "publish_status": "Published"}, False, False)
def get_usa_archived() -> list[dict[str, Any]]:
@@ -1570,33 +1554,7 @@ def get_usa_archived() -> list[dict[str, Any]]:
:return: The entries.
"""
- # TODO: Information could be expanded by following the cc_claims (has links to protection profiles).
- soup = _get_page(constants.CC_USA_ARCHIVED_URL)
- tbody = soup.find("table", class_="tablesorter").find("tbody")
- results = []
- for tr in tbody.find_all("tr"):
- tds = tr.find_all("td")
- scheme_img = tds[5].find("img")
- # Only return the US certifications.
- if scheme_img["title"] != "USA":
- continue
- vendor_span = tds[0].find("span", class_="b u")
- product_name = None
- for child in tds[0].children:
- if isinstance(child, NavigableString):
- product_name = sns(child)
- break
- cert = {
- "vendor": sns(vendor_span.text),
- "id": sns(tds[1].text),
- "cc_claim": sns(tds[2].text),
- "cert_lab": sns(tds[3].text),
- "certification_date": sns(tds[4].text),
- }
- if product_name:
- cert["product"] = product_name
- results.append(cert)
- return results
+ return _get_usa({"status": "Archived", "publish_status": "Published"}, False, False)
class EntryType(Enum):
diff --git a/src/sec_certs/utils/sanitization.py b/src/sec_certs/utils/sanitization.py
index fd477746..4a7326eb 100644
--- a/src/sec_certs/utils/sanitization.py
+++ b/src/sec_certs/utils/sanitization.py
@@ -3,6 +3,8 @@ from __future__ import annotations
import html
import logging
from datetime import date
+from pathlib import Path
+from urllib.parse import urlparse
import numpy as np
import pandas as pd
@@ -23,6 +25,13 @@ def sanitize_link(record: str | None) -> str | None:
return record.replace(":443", "").replace(" ", "%20").replace("http://", "https://")
+def sanitize_link_fname(record: str | None) -> str | None:
+ if not record:
+ return None
+ parsed = urlparse(record)
+ return Path(parsed.path).name
+
+
def sanitize_cc_link(record: str | None) -> str | None:
record = sanitize_link(record)
if not record: