from __future__ import annotations import re from dataclasses import dataclass from functools import cached_property from sec_certs.cert_rules import rules def _parse_year(year: str | None) -> int | None: if year is None: return None y = int(year) if y < 50: return y + 2000 elif y < 100: return y + 1900 else: return y def FR(meta) -> str: year = _parse_year(meta["year"]) counter = meta["counter"] doc = meta.get("doc") version = meta.get("version") type = meta.get("type") cert_id = "ANSSI-CC-" if type: cert_id += f"{type}-" cert_id += f"{year}/{counter}" if doc: cert_id += f"-{doc}" if version: cert_id += f"v{version}" return cert_id def DE(meta) -> str: s = meta.get("s") counter = meta["counter"] version = meta.get("version") year = _parse_year(meta.get("year")) doc = meta.get("doc") cert_id = "BSI-DSZ-CC" if s: cert_id += f"-{s}" cert_id += f"-{counter}" if version: cert_id += f"-{version.upper()}" if year: cert_id += f"-{year}" if doc: cert_id += f"-{doc}" return cert_id def US(meta) -> str: counter = meta["counter"] cc = meta.get("cc") vid = meta.get("VID") year = _parse_year(meta.get("year")) cert_id = "CCEVS-VR" if cc: cert_id += f"-{cc}" if vid: cert_id += f"-{vid}" cert_id += f"-{counter}" if year: cert_id += f"-{year}" return cert_id def MY(meta) -> str: digit = meta["digit"] counter = meta["counter"] version = meta["version"] return f"ISCB-{digit}-RPT-C{counter}-CR-{version.lower()}" def ES(meta) -> str: year = _parse_year(meta["year"]) project = meta["project"] counter = meta["counter"] # Version is intentionally cut here, as it seems to refer to an internal version of the report. # version = groups["version"] return f"{year}-{project}-INF-{counter}" def IN(meta) -> str: lab = meta["lab"] vendor = meta["vendor"] level = meta["level"] number1, number2 = meta["number1"], meta["number2"] return f"IC3S/{lab}/{vendor}/{level}/{number1}/{number2}" def SE(meta) -> str: year = _parse_year(meta["year"]) counter = int(meta["counter"]) return f"CSEC{year}{counter:03}" def UK(meta) -> str: counter = meta["counter"] return f"CRP{counter}" def CA(meta) -> str: if "lab" in meta: year = _parse_year(meta.get("year")) number = meta["number"] lab = meta["lab"] cert_id = f"{number}-{lab}" if year: cert_id += f"-{year}" return cert_id else: number1 = meta["number1"] digit = meta["digit"] number2 = meta["number2"] return f"{number1}-{digit}-{number2}" def JP(meta) -> str: counter = meta["counter"] digit = meta.get("digit") year = _parse_year(meta.get("year")) cert_id = f"JISEC-CC-CRP-C{counter}" if digit: cert_id += f"-{digit}" if year: cert_id += f"-{year}" return cert_id def KR(meta) -> str: word = meta["word"] counter = int(meta["counter"]) year = _parse_year(meta["year"]) return f"KECS-{word}-{counter:04}-{year}" def TR(meta) -> str: prefix = meta["prefix"] number = meta["number"] return f"{prefix}/TSE-CCCS-{number}" def NO(meta) -> str: counter = int(meta["counter"]) return f"SERTIT-{counter:03}" def NL(meta) -> str: core = meta["core"] doc = meta.get("doc") if doc is None: doc = "CR" return f"NSCIB-CC-{core}-{doc}" def AU(meta) -> str: counter = meta["counter"] year_s = meta["year"] if len(year_s) < len(counter): # Hack for some mistakes in their ordering year_s, counter = counter, year_s year = _parse_year(year_s) return f"Certificate Number: {year}/{counter}" def SG(meta) -> str: year = meta["year"] counter = meta["counter"] return f"CSA_CC_{year}{counter}" def IT(meta) -> str: lab = meta.get("lab") counter = meta["counter"] year = _parse_year(meta["year"]) cert_id = "OCSI/CERT/" if lab: cert_id += f"{lab}/" cert_id += f"{counter}/{year}/RC" return cert_id def PL(meta) -> str: number = meta["number"] ac = meta.get("ac") year = meta["year"] cert_id = f"{number}/PC1/" if ac: cert_id += f"{ac}/" cert_id += f"{year}" return cert_id # We have rules for some schemes to make canonical cert_ids. schemes = { "FR": FR, "DE": DE, "US": US, "MY": MY, "ES": ES, "IN": IN, "SE": SE, "UK": UK, "CA": CA, "JP": JP, "NO": NO, "NL": NL, "AU": AU, "KR": KR, "TR": TR, "SG": SG, "IT": IT, "PL": PL, } @dataclass(frozen=True) class CertificateId: """ A Common Criteria certificate id. """ scheme: str raw: str @cached_property def meta(self): for rule in rules["cc_cert_id"][self.scheme]: if match := re.match(rule, self.clean): return match.groupdict() return {} @property def clean(self) -> str: """ The clean version of this certificate id. """ return self.raw.replace("\N{HYPHEN}", "-").strip() @property def canonical(self) -> str: """ The canonical version of this certificate id. """ clean = self.clean if self.scheme in schemes: return schemes[self.scheme](self.meta) else: return clean def __str__(self): return self.canonical def __hash__(self): return hash((self.scheme, self.raw)) def __eq__(self, other): if isinstance(other, str): return self.canonical == other if not isinstance(other, CertificateId): return False return self.canonical == other.canonical and self.scheme == other.scheme def canonicalize(cert_id_str: str, scheme: str) -> str: return CertificateId(scheme, cert_id_str).canonical