aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJán Jančár2022-07-13 23:41:50 +0200
committerGitHub2022-07-13 23:41:50 +0200
commit74787f2f641f25aa28f617ada22fdb993526597c (patch)
tree31b11f9458a5fc7655366ece238a96ce524533a9
parentbf94b0ea4c245af115f011a9fedf1f9aac8ad706 (diff)
parent99c2451b77a29f3c2b9b171ed41b09cb634a1d70 (diff)
downloadsec-certs-74787f2f641f25aa28f617ada22fdb993526597c.tar.gz
sec-certs-74787f2f641f25aa28f617ada22fdb993526597c.tar.zst
sec-certs-74787f2f641f25aa28f617ada22fdb993526597c.zip
Merge pull request #247 from crocs-muni/feat/scheme-pages
Add download of certificate lists from scheme pages.
-rw-r--r--sec_certs/constants.py48
-rw-r--r--sec_certs/dataset/common_criteria.py774
-rw-r--r--tests/test_cc_schemes.py72
3 files changed, 893 insertions, 1 deletions
diff --git a/sec_certs/constants.py b/sec_certs/constants.py
index 72b12d08..f6da500c 100644
--- a/sec_certs/constants.py
+++ b/sec_certs/constants.py
@@ -57,3 +57,51 @@ GARBAGE_SIZE_THRESHOLD = 1000
GARBAGE_AVG_LLEN_THRESHOLD = 10
GARBAGE_EVERY_SECOND_CHAR_THRESHOLD = 15
GARBAGE_ALPHA_CHARS_THRESHOLD = 0.5
+
+CC_AUSTRALIA_BASE_URL = "https://www.cyber.gov.au"
+CC_AUSTRALIA_CERTIFIED_URL = (
+ CC_AUSTRALIA_BASE_URL + "/acsc/view-all-content/programs/australian-information-security-evaluation-program"
+)
+CC_CANADA_CERTIFIED_URL = "https://www.cyber.gc.ca/en/tools-services/common-criteria/certified-products"
+CC_CANADA_INEVAL_URL = "https://www.cyber.gc.ca/en/tools-services/common-criteria/products-evaluation"
+CC_ANSSI_BASE_URL = "https://www.ssi.gouv.fr"
+CC_ANSSI_CERTIFIED_URL = CC_ANSSI_BASE_URL + "/en/products/certified-products/"
+CC_BSI_BASE_URL = "https://www.bsi.bund.de/"
+CC_BSI_CERTIFIED_URL = CC_BSI_BASE_URL + "EN/Topics/Certification/certified_products/certified_products_node.html"
+CC_INDIA_CERTIFIED_URL = "https://www.commoncriteria-india.gov.in/product-certified"
+CC_INDIA_ARCHIVED_URL = "https://www.commoncriteria-india.gov.in/archived-prod-cer"
+CC_ITALY_BASE_URL = "https://ocsi.isticom.it"
+CC_ITALY_CERTIFIED_URL = CC_ITALY_BASE_URL + "/index.php/elenchi-certificazioni/prodotti-certificati"
+CC_ITALY_INEVAL_URL = CC_ITALY_BASE_URL + "/index.php/elenchi-certificazioni/in-corso-di-valutazione"
+CC_JAPAN_CERTIFIED_URL = "https://www.ipa.go.jp/security/jisec/jisec_e/certified_products/certfy_list_e31.html"
+CC_JAPAN_ARCHIVED_URL = "https://www.ipa.go.jp/security/jisec/jisec_e/certified_products/certfy_list_e_archive.html"
+CC_JAPAN_INEVAL_URL = "https://www.ipa.go.jp/security/jisec/jisec_e/prdct_in_eval.html"
+CC_MALAYSIA_BASE_URL = "https://www.cybersecurity.my/mycc"
+CC_MALAYSIA_CERTIFIED_URL = CC_MALAYSIA_BASE_URL + "/mycprA.html"
+CC_MALAYSIA_INEVAL_URL = CC_MALAYSIA_BASE_URL + "/mycprC.html"
+CC_NETHERLANDS_BASE_URL = "https://www.tuv-nederland.nl/common-criteria"
+CC_NETHERLANDS_CERTIFIED_URL = CC_NETHERLANDS_BASE_URL + "/certificates.html"
+CC_NETHERLANDS_INEVAL_URL = CC_NETHERLANDS_BASE_URL + "/ongoing-certifications.html"
+CC_NORWAY_CERTIFIED_URL = "https://sertit.no/certified-products/category1919.html"
+CC_NORWAY_ARCHIVED_URL = "https://sertit.no/certified-products/product-archive/"
+CC_KOREA_EN_URL = "https://itscc.kr/main/main.do?accessMode=home_en"
+CC_KOREA_CERTIFIED_URL = "https://itscc.kr/certprod/list.do"
+CC_KOREA_PRODUCT_URL = "https://itscc.kr/certprod/view.do?product_id={}&product_class=1"
+CC_SINGAPORE_BASE_URL = "https://www.csa.gov.sg"
+CC_SINGAPORE_CERTIFIED_URL = (
+ CC_SINGAPORE_BASE_URL + "/Programmes/certification-and-labelling-schemes/csa-common-criteria/product-list"
+)
+CC_SINGAPORE_ARCHIVED_URL = (
+ CC_SINGAPORE_BASE_URL + "/Programmes/certification-and-labelling-schemes/csa-common-criteria/product-archives"
+)
+CC_SPAIN_BASE_URL = "https://oc.ccn.cni.es"
+CC_SPAIN_CERTIFIED_URL = CC_SPAIN_BASE_URL + "/en/certified-products/certified-products"
+CC_SWEDEN_BASE_URL = "https://www.fmv.se"
+CC_SWEDEN_CERTIFIED_URL = CC_SWEDEN_BASE_URL + "/verksamhet/ovrig-verksamhet/csec/certifikat-utgivna-av-csec/"
+CC_SWEDEN_INEVAL_URL = CC_SWEDEN_BASE_URL + "/verksamhet/ovrig-verksamhet/csec/pagaende-certifieringar/"
+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_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"
diff --git a/sec_certs/dataset/common_criteria.py b/sec_certs/dataset/common_criteria.py
index dfeb28bf..24d8bc41 100644
--- a/sec_certs/dataset/common_criteria.py
+++ b/sec_certs/dataset/common_criteria.py
@@ -13,9 +13,12 @@ from typing import Any, Callable, ClassVar, Dict, Iterator, List, Optional, Set,
import numpy as np
import pandas as pd
-from bs4 import BeautifulSoup, Tag
+import requests
+import tabula
+from bs4 import BeautifulSoup, NavigableString, Tag
import sec_certs.utils.sanitization
+from sec_certs import constants
from sec_certs.config.configuration import config
from sec_certs.dataset.dataset import Dataset, logger
from sec_certs.dataset.protection_profile import ProtectionProfileDataset
@@ -991,3 +994,772 @@ class CCDatasetMaintenanceUpdates(CCDataset, ComplexSerializableType):
main_dates.maintenance_date = main_dates.maintenance_date.map(lambda x: [x])
main_dates.index.name = "dgst"
return main_dates.groupby("related_cert_digest").maintenance_date.agg("sum").rename("maintenance_dates")
+
+
+class CCSchemeDataset:
+ @staticmethod
+ def _download_page(url, session=None):
+ if session:
+ conn = session
+ else:
+ conn = requests
+ resp = conn.get(url, headers={"User-Agent": "seccerts.org"})
+ if resp.status_code != requests.codes.ok:
+ raise ValueError(f"Unable to download: status={resp.status_code}")
+ return BeautifulSoup(resp.content, "html.parser")
+
+ @staticmethod
+ def get_australia_in_evaluation():
+ # TODO: Information could be expanded by following url.
+ soup = CCSchemeDataset._download_page(constants.CC_AUSTRALIA_CERTIFIED_URL)
+ header = soup.find("h2", text="Products in evaluation")
+ table = header.find_next_sibling("table")
+ results = []
+ for tr in table.find_all("tr"):
+ tds = tr.find_all("td")
+ if not tds:
+ continue
+ cert = {
+ "vendor": str(tds[0].text),
+ "product": str(tds[1].text),
+ "url": constants.CC_AUSTRALIA_BASE_URL + str(tds[1].find("a")["href"]),
+ "level": str(tds[2].text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_canada_certified():
+ soup = CCSchemeDataset._download_page(constants.CC_CANADA_CERTIFIED_URL)
+ tbody = soup.find("table").find("tbody")
+ results = []
+ for tr in tbody.find_all("tr"):
+ tds = tr.find_all("td")
+ if not tds:
+ continue
+ cert = {
+ "product": str(tds[0].text),
+ "vendor": str(tds[1].text),
+ "level": str(tds[2].text),
+ "certification_date": str(tds[3].text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_canada_in_evaluation():
+ soup = CCSchemeDataset._download_page(constants.CC_CANADA_INEVAL_URL)
+ tbody = soup.find("table").find("tbody")
+ results = []
+ for tr in tbody.find_all("tr"):
+ tds = tr.find_all("td")
+ if not tds:
+ continue
+ cert = {
+ "product": str(tds[0].text),
+ "vendor": str(tds[1].text),
+ "level": str(tds[2].text),
+ "cert_lab": str(tds[3].text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_france_certified():
+ # TODO: Information could be expanded by following product link.
+ base_soup = CCSchemeDataset._download_page(constants.CC_ANSSI_CERTIFIED_URL)
+ category_nav = base_soup.find("ul", class_="nav-categories")
+ results = []
+ for li in category_nav.find_all("li"):
+ a = li.find("a")
+ url = a["href"]
+ category_name = str(a.text)
+ soup = CCSchemeDataset._download_page(constants.CC_ANSSI_BASE_URL + url)
+ table = soup.find("table", class_="produits-liste cc")
+ if not table:
+ continue
+ tbody = table.find("tbody")
+ for tr in tbody.find_all("tr"):
+ tds = tr.find_all("td")
+ if not tds:
+ continue
+ cert = {
+ "product": str(tds[0].text),
+ "vendor": str(tds[1].text),
+ "level": str(tds[2].text),
+ "id": str(tds[3].text),
+ "certification_date": str(tds[4].text),
+ "category": category_name,
+ "url": constants.CC_ANSSI_BASE_URL + str(tds[0].find("a")["href"]),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_germany_certified():
+ # TODO: Information could be expanded by following url.
+ base_soup = CCSchemeDataset._download_page(constants.CC_BSI_CERTIFIED_URL)
+ category_nav = base_soup.find("ul", class_="no-bullet row")
+ results = []
+ for li in category_nav.find_all("li"):
+ a = li.find("a")
+ url = a["href"]
+ category_name = str(a.text)
+ soup = CCSchemeDataset._download_page(constants.CC_BSI_BASE_URL + url)
+ content = soup.find("div", class_="content").find("div", class_="column")
+ for table in content.find_all("table"):
+ tbody = table.find("tbody")
+ header = table.find_parent("div", class_="wrapperTable").find_previous_sibling("h2")
+ for tr in tbody.find_all("tr"):
+ tds = tr.find_all("td")
+ if len(tds) != 4:
+ continue
+ cert = {
+ "cert_id": str(tds[0].text),
+ "product": str(tds[1].text),
+ "vendor": str(tds[2].text),
+ "certification_date": str(tds[3].text),
+ "category": category_name,
+ "url": constants.CC_BSI_BASE_URL + str(tds[0].find("a")["href"]),
+ }
+ if header is not None:
+ cert["subcategory"] = str(header.text)
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_india_certified():
+ pages = {0}
+ seen_pages = set()
+ results = []
+ while pages:
+ page = pages.pop()
+ seen_pages.add(page)
+ url = constants.CC_INDIA_CERTIFIED_URL + f"?page={page}"
+ soup = CCSchemeDataset._download_page(url)
+
+ # Update pages
+ pager = soup.find("ul", class_="pager")
+ for li in pager.find_all("li"):
+ try:
+ new_page = int(li.text)
+ except Exception:
+ continue
+ if new_page not in seen_pages:
+ pages.add(new_page)
+
+ # Parse table
+ tbody = soup.find("div", class_="content").find("table").find("tbody")
+ for tr in tbody.find_all("tr"):
+ tds = tr.find_all("td")
+ if not tds:
+ continue
+ report_a = tds[5].find("a")
+ target_a = tds[6].find("a")
+ cert_a = tds[7].find("a")
+ cert = {
+ "serial_number": str(tds[0].text),
+ "product": str(tds[1].text),
+ "sponsor": str(tds[2].text),
+ "developer": str(tds[3].text),
+ "level": str(tds[4].text),
+ "report_link": report_a["href"],
+ "report_name": str(report_a.text),
+ "target_link": target_a["href"],
+ "target_name": str(target_a.text),
+ "cert_link": cert_a["href"],
+ "cert_name": str(cert_a.text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_india_archived():
+ pages = {0}
+ seen_pages = set()
+ results = []
+ while pages:
+ page = pages.pop()
+ seen_pages.add(page)
+ url = constants.CC_INDIA_ARCHIVED_URL + f"?page={page}"
+ soup = CCSchemeDataset._download_page(url)
+
+ # Update pages
+ pager = soup.find("ul", class_="pager")
+ for li in pager.find_all("li"):
+ try:
+ new_page = int(li.text)
+ except Exception:
+ continue
+ if new_page not in seen_pages:
+ pages.add(new_page)
+
+ # Parse table
+ tbody = soup.find("div", class_="content").find("table").find("tbody")
+ for tr in tbody.find_all("tr"):
+ tds = tr.find_all("td")
+ if not tds:
+ continue
+ report_a = tds[5].find("a")
+ target_a = tds[6].find("a")
+ cert_a = tds[7].find("a")
+ cert = {
+ "serial_number": str(tds[0].text),
+ "product": str(tds[1].text),
+ "sponsor": str(tds[2].text),
+ "developer": str(tds[3].text),
+ "level": str(tds[4].text),
+ "report_link": report_a["href"],
+ "report_name": str(report_a.text),
+ "target_link": target_a["href"],
+ "target_name": str(target_a.text),
+ "cert_link": cert_a["href"],
+ "cert_name": str(cert_a.text),
+ "certification_date": str(tds[8].text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_italy_certified(): # noqa: C901
+ soup = CCSchemeDataset._download_page(constants.CC_ITALY_CERTIFIED_URL)
+ div = soup.find("div", class_="certificati")
+ results = []
+ for cert_div in div.find_all("div", recursive=False):
+ title = cert_div.find("h3").text
+ data_div = cert_div.find("div", class_="collapse")
+ cert = {"title": title}
+ for data_p in data_div.find_all("p"):
+ p_text = data_p.text
+ if ":" not in p_text:
+ continue
+ p_name, p_data = p_text.split(":")
+ p_data = p_data.strip()
+ p_link = data_p.find("a")
+ if "Fornitore" in p_name:
+ cert["supplier"] = p_data
+ elif "Livello di garanzia" in p_name:
+ cert["level"] = p_data
+ elif "Data emissione certificato" in p_name:
+ cert["certification_date"] = p_data
+ elif "Data revisione" in p_name:
+ cert["revision_date"] = p_data
+ elif "Rapporto di Certificazione" in p_name and p_link:
+ cert["report_link_it"] = constants.CC_ITALY_BASE_URL + p_link["href"]
+ elif "Certification Report" in p_name and p_link:
+ cert["report_link_en"] = constants.CC_ITALY_BASE_URL + p_link["href"]
+ elif "Traguardo di Sicurezza" in p_name and p_link:
+ cert["target_link"] = constants.CC_ITALY_BASE_URL + p_link["href"]
+ elif "Nota su" in p_name and p_link:
+ cert["vulnerability_note_link"] = constants.CC_ITALY_BASE_URL + p_link["href"]
+ elif "Nota di chiarimento" in p_name and p_link:
+ cert["clarification_note_link"] = constants.CC_ITALY_BASE_URL + p_link["href"]
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_italy_in_evaluation():
+ soup = CCSchemeDataset._download_page(constants.CC_ITALY_INEVAL_URL)
+ div = soup.find("div", class_="valutazioni")
+ results = []
+ for cert_div in div.find_all("div", recursive=False):
+ title = cert_div.find("h3").text
+ data_div = cert_div.find("div", class_="collapse")
+ cert = {"title": title}
+ for data_p in data_div.find_all("p"):
+ p_text = data_p.text
+ if ":" not in p_text:
+ continue
+ p_name, p_data = p_text.split(":")
+ p_data = p_data.strip()
+ if "Committente" in p_name:
+ cert["client"] = p_data
+ elif "Livello di garanzia" in p_name:
+ cert["level"] = p_data
+ elif "Tipologia prodotto" in p_name:
+ cert["product_type"] = p_data
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_japan_certified():
+ # TODO: Information could be expanded by following toe link.
+ soup = CCSchemeDataset._download_page(constants.CC_JAPAN_CERTIFIED_URL)
+ table = soup.find("div", id="cert_list").find("table")
+ results = []
+ trs = list(table.find_all("tr"))
+ for tr in trs:
+ tds = tr.find_all("td")
+ if not tds:
+ continue
+ if len(tds) == 6:
+ cert = {
+ "cert_id": str(tds[0].text),
+ "supplier": str(tds[1].text),
+ "toe_overseas_name": str(tds[2].text),
+ "certification_date": str(tds[3].text),
+ "claim": str(tds[4].text),
+ }
+ toe_a = tds[2].find("a")
+ if toe_a and "href" in toe_a.attrs:
+ cert["toe_overseas_link"] = toe_a["href"]
+ results.append(cert)
+ if len(tds) == 1:
+ cert = results[-1]
+ cert["toe_japan_name"] = str(tds[0].text)
+ toe_a = tds[0].find("a")
+ if toe_a and "href" in toe_a.attrs:
+ cert["toe_japan_link"] = toe_a["href"]
+ return results
+
+ @staticmethod
+ def get_japan_archived():
+ # TODO: Information could be expanded by following toe link.
+ soup = CCSchemeDataset._download_page(constants.CC_JAPAN_ARCHIVED_URL)
+ table = soup.find("table")
+ results = []
+ trs = list(table.find_all("tr"))
+ for tr in trs:
+ tds = tr.find_all("td")
+ if not tds:
+ continue
+ if len(tds) == 6:
+ cert = {
+ "cert_id": str(tds[0].text),
+ "supplier": str(tds[1].text),
+ "toe_overseas_name": str(tds[2].text),
+ "certification_date": str(tds[3].text),
+ "claim": str(tds[4].text),
+ }
+ toe_a = tds[2].find("a")
+ if toe_a and "href" in toe_a.attrs:
+ cert["toe_overseas_link"] = toe_a["href"]
+ results.append(cert)
+ if len(tds) == 1:
+ cert = results[-1]
+ cert["toe_japan_name"] = str(tds[0].text)
+ toe_a = tds[0].find("a")
+ if toe_a and "href" in toe_a.attrs:
+ cert["toe_japan_link"] = toe_a["href"]
+ return results
+
+ @staticmethod
+ def get_japan_in_evaluation():
+ # TODO: Information could be expanded by following toe link.
+ soup = CCSchemeDataset._download_page(constants.CC_JAPAN_INEVAL_URL)
+ table = soup.find("table")
+ results = []
+ for tr in table.find_all("tr"):
+ tds = tr.find_all("td")
+ if not tds:
+ continue
+ toe_a = tds[1].find("a")
+ cert = {
+ "supplier": str(tds[0].text),
+ "toe_name": str(toe_a.text),
+ "toe_link": toe_a["href"],
+ "claim": str(tds[2].text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_malaysia_certified():
+ soup = CCSchemeDataset._download_page(constants.CC_MALAYSIA_CERTIFIED_URL)
+ main_table = soup.find("table").find("table")
+ cert_table = main_table.find_all("table")[1].find("table")
+ tables = cert_table.find_all(lambda x: x.name == "table" and x.find_parent("table") == cert_table)
+ category_name = None
+ archive = False
+ results = []
+ for table in tables:
+ if table.find("table") is None:
+ name_td = table.find_all("td")[1]
+ if "Archive" in name_td.text:
+ category_name = str(name_td.text).split("Archive")[1]
+ archive = True
+ else:
+ category_name = str(name_td.text)
+ archive = False
+ continue
+ data_table = table.find("table")
+ tr = data_table.find_all("tr")[1]
+ tds = tr.find_all("td")
+ if len(tds) != 6:
+ continue
+ cert = {
+ "category": category_name,
+ "archived": archive,
+ "level": str(tds[0].text),
+ "cert_id": str(tds[1].text),
+ "certification_date": str(tds[2].text),
+ "product": str(tds[3].text),
+ "developer": str(tds[4].text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_malaysia_in_evalution():
+ soup = CCSchemeDataset._download_page(constants.CC_MALAYSIA_INEVAL_URL)
+ cert_table = soup.find("table").find("table").find_all("table")[1]
+ tables = cert_table.find_all(lambda x: x.name == "table" and x.find_parent("table") == cert_table)
+ results = []
+ for table in tables:
+ cat_p = table.find_previous_sibling("p")
+ data_table = table.find("table")
+ tr = data_table.find_all("tr")[1]
+ tds = tr.find_all("td")
+ if len(tds) != 5:
+ continue
+ cert = {
+ "category": str(cat_p.text),
+ "level": str(tds[0].text),
+ "project_id": str(tds[1].text),
+ "toe_name": str(tds[2].text),
+ "developer": str(tds[3].text),
+ "expected_completion_date": str(tds[4].text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_netherlands_certified():
+ soup = CCSchemeDataset._download_page(constants.CC_NETHERLANDS_CERTIFIED_URL)
+ main_div = soup.select("body > main > div > div > div > div:nth-child(2) > div.col-lg-9 > div:nth-child(3)")[0]
+ rows = main_div.find_all("div", class_="row", recursive=False)
+ modals = main_div.find_all("div", class_="modal", recursive=False)
+ results = []
+ for row, modal in zip(rows, modals):
+ row_entries = row.find_all("a")
+ modal_trs = modal.find_all("tr")
+ cert = {
+ "manufacturer": str(row_entries[0].text),
+ "product": str(row_entries[1].text),
+ "scheme": str(row_entries[2].text),
+ "cert_id": str(row_entries[3].text),
+ }
+ for tr in modal_trs:
+ th_text = tr.find("th").text
+ td = tr.find("td")
+ if "Manufacturer website" in th_text:
+ cert["manufacturer_link"] = td.find("a")["href"]
+ elif "Assurancelevel" in th_text:
+ cert["level"] = str(td.text)
+ elif "Certificate" in th_text:
+ cert["cert_link"] = constants.CC_NETHERLANDS_BASE_URL + td.find("a")["href"]
+ elif "Certificationreport" in th_text:
+ cert["report_link"] = constants.CC_NETHERLANDS_BASE_URL + td.find("a")["href"]
+ elif "Securitytarget" in th_text:
+ cert["target_link"] = constants.CC_NETHERLANDS_BASE_URL + td.find("a")["href"]
+ elif "Maintenance report" in th_text:
+ cert["maintenance_link"] = constants.CC_NETHERLANDS_BASE_URL + td.find("a")["href"]
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_netherlands_in_evaluation():
+ soup = CCSchemeDataset._download_page(constants.CC_NETHERLANDS_INEVAL_URL)
+ table = soup.find("table")
+ results = []
+ for tr in table.find_all("tr")[1:]:
+ tds = tr.find_all("td")
+ cert = {
+ "developer": str(tds[0].text),
+ "product": str(tds[1].text),
+ "category": str(tds[2].text),
+ "level": str(tds[3].text),
+ "certification_id": str(tds[4].text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def _get_norway(url):
+ # TODO: Information could be expanded by following product link.
+ soup = CCSchemeDataset._download_page(url)
+ results = []
+ for tr in soup.find_all("tr", class_="certified-product"):
+ tds = tr.find_all("td")
+ cert = {
+ "product": str(tds[0].text).strip(),
+ "product_link": tds[0].find("a")["href"],
+ "category": str(tds[1].find("p", class_="value").text),
+ "developer": str(tds[2].find("p", class_="value").text),
+ "certification_date": str(tds[3].find("time").text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_norway_certified():
+ return CCSchemeDataset._get_norway(constants.CC_NORWAY_CERTIFIED_URL)
+
+ @staticmethod
+ def get_norway_archived():
+ return CCSchemeDataset._get_norway(constants.CC_NORWAY_ARCHIVED_URL)
+
+ @staticmethod
+ def _get_korea(product_class):
+ # TODO: Information could be expanded by following product link.
+ session = requests.session()
+ session.get(constants.CC_KOREA_EN_URL)
+ # Get base page
+ url = constants.CC_KOREA_CERTIFIED_URL + f"?product_class={product_class}"
+ soup = CCSchemeDataset._download_page(url, session=session)
+ seen_pages = set()
+ pages = {1}
+ results = []
+ while pages:
+ page = pages.pop()
+ csrf = soup.find("form", id="fm").find("input", attrs={"name": "csrf"})["value"]
+ resp = session.post(url, data={"csrf": csrf, "selectPage": page, "product_class": product_class})
+ soup = BeautifulSoup(resp.content, "html.parser")
+ tbody = soup.find("table", class_="cpl").find("tbody")
+ for tr in tbody.find_all("tr"):
+ tds = tr.find_all("td")
+ if len(tds) != 6:
+ continue
+ link = tds[0].find("a")
+ id = link["id"].split("-")[1]
+ cert = {
+ "product": str(tds[0].text),
+ "cert_id": str(tds[1].text),
+ "product_link": constants.CC_KOREA_PRODUCT_URL.format(id),
+ "vendor": str(tds[2].text),
+ "level": str(tds[3].text),
+ "category": str(tds[4].text),
+ "certification_date": str(tds[5].text),
+ }
+ results.append(cert)
+ seen_pages.add(page)
+ page_links = soup.find("div", class_="paginate").find_all("a", class_="number_off")
+ for page_link in page_links:
+ try:
+ new_page = int(page_link.text)
+ if new_page not in seen_pages:
+ pages.add(new_page)
+ except Exception:
+ pass
+ return results
+
+ @staticmethod
+ def get_korea_certified():
+ return CCSchemeDataset._get_korea(product_class=1)
+
+ @staticmethod
+ def get_korea_suspended():
+ return CCSchemeDataset._get_korea(product_class=2)
+
+ @staticmethod
+ def get_korea_archived():
+ return CCSchemeDataset._get_korea(product_class=4)
+
+ @staticmethod
+ def _get_singapore(url):
+ soup = CCSchemeDataset._download_page(url)
+ table = soup.find("table")
+ skip = False
+ results = []
+ category_name = None
+ for tr in table.find_all("tr"):
+ if skip:
+ skip = False
+ continue
+ tds = tr.find_all("td")
+ if len(tds) == 1:
+ category_name = str(tds[0].text)
+ skip = True
+ continue
+
+ cert = {
+ "product": str(tds[0].text.split()[0]),
+ "vendor": str(tds[1].text),
+ "level": str(tds[2].text),
+ "certification_date": str(tds[3].text),
+ "expiration_date": str(tds[4].text),
+ "category": category_name,
+ }
+ for link in tds[0].find_all("a"):
+ link_text = link.text.strip()
+ if link_text == "Certificate":
+ cert["cert_link"] = constants.CC_SINGAPORE_BASE_URL + link["href"]
+ elif link_text in ("Certificate Report", "Certification Report"):
+ cert["report_link"] = constants.CC_SINGAPORE_BASE_URL + link["href"]
+ elif link_text == "Security Target":
+ cert["target_link"] = constants.CC_SINGAPORE_BASE_URL + link["href"]
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_singapore_certified():
+ return CCSchemeDataset._get_singapore(constants.CC_SINGAPORE_CERTIFIED_URL)
+
+ @staticmethod
+ def get_singapore_in_evaluation():
+ soup = CCSchemeDataset._download_page(constants.CC_SINGAPORE_CERTIFIED_URL)
+ header = soup.find(lambda x: x.name == "h3" and x.text == "In Evaluation")
+ table = header.find_next("table")
+ results = []
+ for tr in table.find_all("tr")[1:]:
+ tds = tr.find_all("td")
+ cert = {"name": str(tds[0].text), "vendor": str(tds[1].text), "level": str(tds[2].text)}
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_singapore_archived():
+ return CCSchemeDataset._get_singapore(constants.CC_SINGAPORE_ARCHIVED_URL)
+
+ @staticmethod
+ def get_spain_certified():
+ soup = CCSchemeDataset._download_page(constants.CC_SPAIN_CERTIFIED_URL)
+ tbody = soup.find("table", class_="djc_items_table").find("tbody")
+ results = []
+ for tr in tbody.find_all("tr", recursive=False):
+ tds = tr.find_all("td")
+ cert = {
+ "product": str(tds[0].text),
+ "product_link": constants.CC_SPAIN_BASE_URL + tds[0].find("a")["href"],
+ "category": str(tds[1].text),
+ "manufacturer": str(tds[2].text),
+ "certification_date": str(tds[3].text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def _get_sweden(url):
+ # TODO: Information could be expanded by following product link.
+ soup = CCSchemeDataset._download_page(url)
+ nav = soup.find("main").find("nav", class_="component-nav-box__list")
+ results = []
+ for link in nav.find_all("a"):
+ cert = {"product": str(link.text), "product_link": link["href"]}
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_sweden_certified():
+ return CCSchemeDataset._get_sweden(constants.CC_SWEDEN_CERTIFIED_URL)
+
+ @staticmethod
+ def get_sweden_in_evalution():
+ return CCSchemeDataset._get_sweden(constants.CC_SWEDEN_INEVAL_URL)
+
+ @staticmethod
+ def get_sweden_archived():
+ return CCSchemeDataset._get_sweden(constants.CC_SWEDEN_ARCHIVED_URL)
+
+ @staticmethod
+ def get_turkey_certified():
+ results = []
+ with tempfile.TemporaryDirectory() as tmpdir:
+ pdf_path = Path(tmpdir) / "turkey.pdf"
+ resp = requests.get(constants.CC_TURKEY_ARCHIVED_URL)
+ if resp.status_code != requests.codes.ok:
+ raise ValueError(f"Unable to download: status={resp.status_code}")
+ with pdf_path.open("wb") as f:
+ f.write(resp.content)
+ dfs = tabula.read_pdf(str(pdf_path), pages="all")
+ for df in dfs:
+ for line in df.values:
+ cert = {
+ # TODO: Split item number and generate several dicts for a range they include.
+ "item_no": line[0],
+ "developer": line[1],
+ "product": line[2],
+ "cc_version": line[3],
+ "level": line[4],
+ "cert_lab": line[5],
+ "certification_date": line[6],
+ "expiration_date": line[7],
+ # TODO: Parse "Ongoing Evaluation" out of this field as well.
+ "archived": isinstance(line[9], str) and "Archived" in line[9],
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_usa_certified():
+ # TODO: Information could be expanded by following product link.
+ # TODO: Information could be expanded by following the cc_claims (has links to protection profiles).
+ soup = CCSchemeDataset._download_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 = {
+ "product": str(product_link.text),
+ "vendor": str(vendor_span.text),
+ "product_link": product_link["href"],
+ "id": str(tds[1].text),
+ "cc_claim": str(tds[2].text),
+ "cert_lab": str(tds[3].text),
+ "certification_date": str(tds[4].text),
+ "assurance_maintenance_date": str(tds[5].text),
+ }
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_usa_in_evaluation():
+ # TODO: Information could be expanded by following the cc_claims (has links to protection profiles).
+ soup = CCSchemeDataset._download_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 = str(child).strip()
+ break
+ cert = {
+ "vendor": str(vendor_span.text),
+ "id": str(tds[1].text),
+ "cc_claim": str(tds[2].text),
+ "cert_lab": str(tds[3].text),
+ "kickoff_date": str(tds[4].text),
+ }
+ if product_name:
+ cert["product"] = product_name
+ results.append(cert)
+ return results
+
+ @staticmethod
+ def get_usa_archived():
+ # TODO: Information could be expanded by following the cc_claims (has links to protection profiles).
+ soup = CCSchemeDataset._download_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 = str(child).strip()
+ break
+ cert = {
+ "vendor": str(vendor_span.text),
+ "id": str(tds[1].text),
+ "cc_claim": str(tds[2].text),
+ "cert_lab": str(tds[3].text),
+ "certification_date": str(tds[4].text),
+ }
+ if product_name:
+ cert["product"] = product_name
+ results.append(cert)
+ return results
diff --git a/tests/test_cc_schemes.py b/tests/test_cc_schemes.py
new file mode 100644
index 00000000..be840edb
--- /dev/null
+++ b/tests/test_cc_schemes.py
@@ -0,0 +1,72 @@
+from unittest import TestCase
+
+import pytest
+
+from sec_certs.dataset.common_criteria import CCSchemeDataset
+
+
+class TestCCSchemes(TestCase):
+ def test_australia(self):
+ assert len(CCSchemeDataset.get_australia_in_evaluation()) != 0
+
+ def test_canada(self):
+ assert len(CCSchemeDataset.get_canada_certified()) != 0
+ assert len(CCSchemeDataset.get_canada_in_evaluation()) != 0
+
+ def test_anssi(self):
+ assert len(CCSchemeDataset.get_france_certified()) != 0
+
+ def test_bsi(self):
+ assert len(CCSchemeDataset.get_germany_certified()) != 0
+
+ @pytest.mark.xfail
+ def test_india(self):
+ assert len(CCSchemeDataset.get_india_certified()) != 0
+ assert len(CCSchemeDataset.get_india_archived()) != 0
+
+ def test_italy(self):
+ assert len(CCSchemeDataset.get_italy_certified()) != 0
+ assert len(CCSchemeDataset.get_italy_in_evaluation()) != 0
+
+ def test_japan(self):
+ assert len(CCSchemeDataset.get_japan_certified()) != 0
+ assert len(CCSchemeDataset.get_japan_archived()) != 0
+ assert len(CCSchemeDataset.get_japan_in_evaluation()) != 0
+
+ def test_malaysia(self):
+ assert len(CCSchemeDataset.get_malaysia_certified()) != 0
+ assert len(CCSchemeDataset.get_malaysia_in_evalution()) != 0
+
+ def test_netherlands(self):
+ assert len(CCSchemeDataset.get_netherlands_certified()) != 0
+ assert len(CCSchemeDataset.get_netherlands_in_evaluation()) != 0
+
+ def test_norway(self):
+ assert len(CCSchemeDataset.get_norway_certified()) != 0
+ assert len(CCSchemeDataset.get_norway_archived()) != 0
+
+ def test_korea(self):
+ assert len(CCSchemeDataset.get_korea_certified()) != 0
+ CCSchemeDataset.get_korea_suspended()
+ assert len(CCSchemeDataset.get_korea_archived()) != 0
+
+ def test_singapore(self):
+ assert len(CCSchemeDataset.get_singapore_certified()) != 0
+ assert len(CCSchemeDataset.get_singapore_archived()) != 0
+ assert len(CCSchemeDataset.get_singapore_in_evaluation()) != 0
+
+ def test_spain(self):
+ assert len(CCSchemeDataset.get_spain_certified()) != 0
+
+ def test_sweden(self):
+ assert len(CCSchemeDataset.get_sweden_certified()) != 0
+ assert len(CCSchemeDataset.get_sweden_in_evalution()) != 0
+ assert len(CCSchemeDataset.get_sweden_archived()) != 0
+
+ def test_turkey(self):
+ assert len(CCSchemeDataset.get_turkey_certified()) != 0
+
+ def test_usa(self):
+ assert len(CCSchemeDataset.get_usa_certified()) != 0
+ assert len(CCSchemeDataset.get_usa_in_evaluation()) != 0
+ assert len(CCSchemeDataset.get_usa_archived()) != 0