aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJán Jančár2024-02-06 19:21:53 +0100
committerGitHub2024-02-06 19:21:53 +0100
commit2fc72fc0e3636cec4cc1cfb2809c050f787146dc (patch)
tree44442c00ab10629444b8bd424f2b2c4cd077aade
parentd3c7491d5afa8b66780e1b87ec3b72ca6c2dbb8f (diff)
parentec0162c8f82b00e29c1551109b3a3a61d37adbac (diff)
downloadsec-certs-2fc72fc0e3636cec4cc1cfb2809c050f787146dc.tar.gz
sec-certs-2fc72fc0e3636cec4cc1cfb2809c050f787146dc.tar.zst
sec-certs-2fc72fc0e3636cec4cc1cfb2809c050f787146dc.zip
Merge pull request #386 from crocs-muni/feat/cert-id-cleanup
Cleanup cert-id rules
-rw-r--r--src/sec_certs/rules.yaml185
-rw-r--r--src/sec_certs/sample/cc.py17
-rw-r--r--src/sec_certs/sample/cc_certificate_id.py324
-rw-r--r--tests/cc/test_cc_misc.py75
-rw-r--r--tests/test_common.py2
5 files changed, 422 insertions, 181 deletions
diff --git a/src/sec_certs/rules.yaml b/src/sec_certs/rules.yaml
index 3cce1ab8..4305b8cd 100644
--- a/src/sec_certs/rules.yaml
+++ b/src/sec_certs/rules.yaml
@@ -5,75 +5,158 @@
#####
cc_cert_id:
DE:
- - "BSI-DSZ-CC-[0-9]+?-[0-9]+"
- - "BSI-DSZ-CC-[0-9]+?-(?:V|v)[0-9]+-[0-9]+"
- - "BSI-DSZ-CC-[0-9]+?-(?:V|v)[0-9]+"
- - "BSI-DSZ-CC-[0-9]+-(?:V|v)[0-9]+(-[0-9]{4})*" # German BSI (number + version + year or without year)
- - "BSI-DSZ-CC-[0-9]+-[0-9]{4}" # German BSI (number + year, no version)
- - "BSI-DSZ-CC-[0-9]+-(?:V|v)[0-9]+(?!-)" # German BSI (number + version but no year => no - after version)
- # - "BSI-DSZ-CC-[0-9]+" # Maybe?
+ - "BSI-DSZ-CC-(?:(?P<s>S)-)?(?P<counter>[0-9]{3,5})-?(?:(?P<version>[vV][0-9])-)?(?P<year>[0-9]{4})?(?:-(?P<doc>(?:RA|MA)(?:-[0-9]+)?))?"
+ # Examples:
+ # BSI-DSZ-CC-1004
+ # BSI-DSZ-CC-0973-2016
+ # BSI-DSZ-CC-0831-V4-2021
+ # BSI-DSZ-CC-0837-V2-2014-MA-01
+ # BSI-DSZ-CC-S-0192-2021
FR:
- - "ANSS[Ii](?:-|-CC-|-CC )[0-9]{4}/[0-9]+(v[1-9])?" # French
- - "ANSS[Ii]-CC[ -][0-9]{4}[/-_][0-9][0-9]+(?!-M|-S|-R)" # French (/two or more digits then NOT -M or -S)
- - "ANSS[Ii]-CC[ -][0-9]{4}[/-_][0-9]+(?:v[0-9])?[_/-][MSR][0-9]+" # French, maintenance or surveillance report (ANSSI-CC-2014_46_M01)
- # 'ANSSI-CC-CER-F-.+?', # French
- - "DCSS[Ii]-[0-9]+/[0-9]+" # French (DCSSI-2009/07)
- - "Certification Report [0-9]+/[0-9]+" # French or Australia! Solved because we limit ourselves to scheme when doing heuristics.
- - "Rapport de certification [0-9]+/[0-9]+" # French
+ - "DCSS[Ii]-(?P<year>[0-9]{2,4})/(?P<counter>[0-9]+)([vV](?P<version>[0-9]))?"
+ - "Rapport de certification (?P<year>[0-9]{2,4})/(?P<counter>[0-9]+)([vV](?P<version>[0-9]))?"
+ - "Certification Report (?P<year>[0-9]{2,4})/(?P<counter>[0-9]+)([vV](?P<version>[0-9]))?"
+ - "ANSS[Ii](?:-CC)?[ -](?P<year>[0-9]{2,4})[/_-](?P<counter>[0-9]+)(?:-(?P<doc>(?:[MSR][0-9]+)))?([vV](?P<version>[0-9]))?"
+ # Examples:
+ # DCSSI-2009/07
+ # ANSSI-CC 2001/02-R01
+ # Rapport de certification 2001/02v2
+ # Certification Report 2003/20
NL:
- - "NSCIB-CC-[0-9]{4}.+?" # Netherlands
- - "NSCIB-CC-[0-9]{4}[0-9]*-CR" # Netherlands
- - "NSCIB-CC-[0-9][0-9]-[0-9]+?-CR[0-9]+?" # Netherlands
- - "NSCIB-CC-[0-9][0-9]-[0-9]+(-CR[0-9]+)*" # Netherlands (old number NSCIB-CC-05-6609 or NSCIB-CC-05-6609-CR)
- - "NSCIB-CC-[0-9]+-CR[0-9]*" # Netherlands (new number NSCIB-CC-111441-CR NSCIB-CC-111441-CR1)
- - "NSCIB-CC-[0-9]+-MA[0-9]*" # Netherlands (new number NSCIB-CC-222073-MA NSCIB-CC-200716-MA2)
- - "NSCIB-CC-[0-9][0-9]-[0-9]+" # Netherlands (old number NSCIB-CC-05-6609)
- - "NSCIB-CC-[0-9][0-9]-[0-9]+-CR[0-9]+" # Netherlands (NSCIB-CC-year2digits-number-CR)
+ - "(?:NSCIB-|CC-|NSCIB-CC-)(?P<core>((?P<year>[0-9]{2})-)?(?:-?[0-9]+)+)(?:-?(?P<doc>(?:CR|MA|MR)[0-9]*))?"
+ # Examples:
+ # NSCIB-CC-22-0428888-CR2 (with year=22 and CR2)
+ # NSCIB-CC-228723-CR (no year)
+ # CC-16-31801-CR4 (no NSCIB)
+ # NSCIB-CC-98209 (no year, no CR)
"NO":
- - "SERTIT-[0-9]+" # Norway
+ - "SERTIT-(?P<counter>[0-9]+)"
+ # Examples:
+ # SERTIT-101
US:
- - "CCEVS-VR-(?:CC-|VID)?[0-9]+-[0-9]+[a-z]?(?:-[0-9]+)?" # US NSA (CCEVS-VR-10884-2018 CCEVS-VR-VID10877-2018)
+ - "CCEVS-VR-(?:(?P<cc>CC)-)?(?:(?P<VID>VID)-?)?(?P<year>[0-9]{2})-(?P<counter>[0-9]+)"
+ - "CCEVS-VR-(?:(?P<cc>CC)-)?(?:(?P<VID>VID)-?)?(?P<counter>[0-9]{4,5})-(?P<year>[0-9]{4})?"
+ # Examples:
+ # CCEVS-VR-VID10015-2008
+ # CCEVS-VR-10880-2018
+ # CCEVS-VR-04-0082
CA:
- # '[0-9][0-9\-]+?-CR', # Canada
- - "[0-9][0-9][0-9]-[347]-[0-9][0-9][0-9]?(?:-CR|P)?" # Canada xxx-{347}-xxx (383-4-438, 383-4-82-CR, 383-4-422P)
- - "[0-9][0-9][0-9][ -](?:EWA|LSS|CCS)(?:[ -]20[0-9][0-9])?" # Canada (522-EWA-2020, 524 LSS 2020, 503-LSS)
- - "[0-9][0-9][0-9](?:%20|-)(?:EWA|LSS|CCS)(?:%20|-)(?:20[0-9][0-9]%20|)CR%20v[0-9]\\.[0-9]" # Canada filename with space (518-LSS%20CR%20v1.0)
+ - "(?P<number1>[0-9]+)[ -](?P<digit>[0-9])[ -](?P<number2>[0-9]+)(?:-CR|P)?"
+ - "(?P<number>[0-9]+)[ -](?P<lab>EWA|LSS|CCS)(?:[ -](?P<year>[0-9]+))?"
+ # Examples:
+ # 383-4-123-CR
+ # 383-4-123P
+ # 522 EWA 2020
+ # Filename rule:
+ #- "[0-9][0-9][0-9](?:%20|-)(?:EWA|LSS|CCS)(?:%20|-)(?:20[0-9][0-9]%20|)CR%20v[0-9]\\.[0-9]" # Canada filename with space (518-LSS%20CR%20v1.0)
UK:
- - "CRP[0-9]+[A-Z]?" # UK CESG
- - "CERTIFICATION REPORT No. P[0-9]+[A-Z]?" # UK CESG
+ - "CRP(?P<counter>[0-9]+[A-Z]?)"
+ - "CERTIFICATION REPORT No. P(?P<counter>[0-9]+[A-Z]?)"
+ # Examples:
+ # CRP208
+ # CERTIFICATION REPORT No. P123A
ES:
- - "20[0-9][0-9][-‐][0-9]+[-‐]INF[-‐][0-9]+([-‐]?[ -‐](?:V|v)[0-9]+)?" # Spain ("2006-4-INF-98 v2" or "2006-4-INF-98-v2" or "2020-34-INF-3784- v1")
+ - "(?P<year>[0-9]{4})[-‐](?P<project>[0-9]+)[-‐]INF[-‐](?P<counter>[0-9]+)[ -‐]{1,2}[vV](?P<version>[0-9])"
+ # Examples:
+ # 2006-4-INF-98 v2
+ # 2020-34-INF-3784- v1
+ # 2019-20-INF-3379-v1
KR:
- # Korea
+ - "KECS[-‐](?P<word>ISIS|NISS|CISS)[-‐](?P<counter>[0-9]{2,4})[-‐](?P<year>[0-9]{4})"
# XXX: Do not use KECS-CR as those refer to the certificate report and do not represent the certificate id.
- # - "KECS[-‐]CR[-‐][0-9]+[-‐][0-9]+" # Korea KECS-CR-20-61
- - "KECS[-‐](?:ISIS|NISS|CISS)[-‐][0-9]+[-‐][0-9]{4}" # Korea KECS-ISIS-1234-2011
+ # Examples:
+ # KECS-ISIS-0579-2015
+ # KECS-NISS-0792-2017
+ # KECS-CISS-1210-2023
JP:
- - "(?:CRP|ACR)-C[0-9]+-[0-9]+" # Japan (CRP-C0595-01 ACR-C0417-03)
- - "JISEC-CC-CRP-C[0-9]+-[0-9]+-[0-9]+" # Japan (JISEC-CC-CRP-C0689-01-2020)
- - "Certification No. [cC][0-9]+" # Japan (Certification No. C0090)
+ - "(?:CRP|ACR)-C(?P<counter>[0-9]+)-(?P<digit>[0-9]+)"
+ - "JISEC-CC-CRP-C(?P<counter>[0-9]+)-(?P<digit>[0-9]+)-(?P<year>[0-9]{4})"
+ - "Certification No. [cC](?P<counter>[0-9]+)"
+ # Examples:
+ # CRP-C0595-01
+ # JISEC-CC-CRP-C0689-01-2020
+ # Certification No. C0090
MY:
- - "ISCB-[0-9]+-(?:RPT|FRM)-[CM][0-9]+[A-Z]?-(?:CR|AMR)(?:-[0-9])?-[vV][0-9](?:\\.[0-9])?[a-z]?" # Malaysia (ISCB-3-RPT-C092-CR-v1, ISCB-3-RPT-C068-CR-1-v1)
+ - "ISCB-(?P<digit>[0-9])-RPT-C(?P<counter>[0-9]{3})-CR(?:-[0-9])?-(?P<version>[vV][0-9][a-z]?)"
+ # Examples:
+ # ISCB-3-RPT-C068-CR-1-v1
+ # ISCB-5-RPT-C075-CR-v2
+ # ISCB-5-RPT-C046-CR-V1a
IT:
- - "OCSI/CERT/.+?" # Italy
- - "OCSI/CERT/.+?/20[0-9]+(?:\\w|/RC)" # Italy (OCSI/CERT/ATS/01/2018/RC)
+ - "OCSI/CERT/(?:(?P<lab>[A-Z]{3})/)?(?P<counter>[0-9]{2,3})/(?P<year>[0-9]{4})/RC"
+ # Examples:
+ # OCSI/CERT/SYS/04/2018/RC
+ # OCSI/CERT/CCL/10/2022/RC
+ # OCSI/CERT/TEC/09/2017/RC
+ # OCSI/CERT/ATS/06/2020/RC
TR:
- - "[0-9\\.]+?/TSE-CCCS-[0-9]+" # Turkish CCCS (21.0.0sc/TSE-CCCS-75)
- - "(?:[0-9]{1,2}\\.){2}[0-9]{1,2}/[0-9]{1,4}-[0-9]{3}" # 21.0.01/13-028
+ - "(?P<prefix>[0-9\\.]+)/TSE-CCCS-(?P<number>[0-9]+)"
+ # XXX: The report numbers are like "21.0.01/13-028"
+ # Examples:
+ # 21.0.03.0.00.00/TSE-CCCS-85
+ # 21.0.03/TSE-CCCS-33
SE:
- - "CSEC ?[0-9]{6,7}" # Sweden (CSEC2019015)
+ - "CSEC ?(?P<year>[0-9]{4})(?P<counter>[0-9]{2,3})"
+ # Examples:
+ # CSEC2019015
+ # CSEC 2019012
IN:
- # India (IC3S/DEL01/VALIANT/EAL1/0317/0007/CR STQC/CC/14-15/12/ETR/0017 IC3S/MUM01/CISCO/cPP/0119/0016/CR)
- # will miss STQC/CC/14-15/12/ETR/0017
- - "(?:IC3S|STQC/CC)/[^ ]+? ?/CR"
+ - "IC3S/(?P<lab>[A-Z]+[0-9]+)/(?P<vendor>[a-zA-Z_]+)/(?P<level>[a-zA-Z0-9]+)/(?P<number1>[0-9]+)/(?P<number2>[0-9]+) ?(?:/CR)?"
+ # XXX: The cert IDs are often present only in the certificate and not in the report.
+ # The report often only has the report id, of the format "STQC/CC/1617/18/CR"
+ # Examples:
+ # IC3S/BG01/HALTDOS/EAL2/0317/0008/CR
+ # IC3S/KOL01/ADVA/EAL2/0520/0021/CR
+ # IC3S/MUM01/Symantec/NDcPP/0722/0032/CR
SG:
- - "CSA_CC_[0-9]+" # Singapore (CSA_CC_19001)
+ - "CSA_CC_(?P<year>[0-9]{2})(?P<counter>[0-9]{3})"
+ # Examples:
+ # CSA_CC_19001
AU:
- # Australia (EFS-T048 ETR 1.0, EFS-T056-ETR 1.0, DXC-EFC-T092-ETR 1.0)
+ - "(?:Certificate Number:|Certification Report) (?P<year>[0-9]{2,4})/(?P<counter>[0-9]+)"
# XXX: Do not use Australian ETR numbers, they are not certificate id.
- # - "(?:EFS|EFT|DXC-EFC)-T[0-9]+(?: |-)ETR [0-9]+.[0-9]+"
- - "Certificate Number: [0-9]{1,4}/[0-9]{1,4}"
- - "Certification Report [0-9]+/[0-9]+"
+ # Examples:
+ # Certification Report 2007/06
+ # Certificate Number: 2010/67
+ # Certificate Number: 37/2006 !mistake
+ # Certification Report 97/76 !short year
+
+#####
+# Common Criteria certificate IDs as they appear in report filenames, grouped by scheme (Alpha-2 ISO country code).
+#####
+cc_filename_cert_id:
+ DE:
+ #- "(?P<counter>[0-9]{3,5})(?:(?P<version>[vV][0-9]))?a?(?:_pdf)?"
+ - "(?P<year>[0-9]{4})(?P<month>[0-9]{2})(?P<day>[0-9]{2})_(?P<counter>[0-9]{3,5})(?:(?P<version>[vV][0-9]))?a?(?:_pdf)?"
+ FR:
+ - "(?P<year>[0-9]{4})[_-](?P<counter>[0-9]{2})([vV](?P<version>[0-9]))?"
+ #- "(?P<year>[0-9]{2})(?P<counter>[0-9]{2})([vV](?P<version>[0-9]))?"
+ NL:
+ - "(?:NSCIB-|CC-|NSCIB-CC-)(?P<core>((?P<year>[0-9]{2})-)?(?:-?[0-9]+)+)(?:-?(?P<doc>(?:CR|MA|MR)[0-9]*))?"
+ "NO":
+ - "SERTIT-(?P<counter>[0-9]+)"
+ US:
+ CA:
+ - "(?P<number1>[0-9]+)[ -](?P<digit>[0-9])[ -](?P<number2>[0-9]+)(?:-CR|P)?"
+ - "(?P<number>[0-9]+)[ -](?P<lab>EWA|LSS|CCS)(?:[ -](?P<year>[0-9]+))?"
+ UK:
+ - "CRP(?P<counter>[0-9]+[A-Z]?)"
+ ES:
+ - "(?P<year>[0-9]{4})[-‐](?P<project>[0-9]+)[-‐]INF[-‐](?P<counter>[0-9]+)[ -‐_]{1,2}[vV](?P<version>[0-9])"
+ KR:
+ - "(?P<word>ISIS|NISS|CISS)[-‐](?P<counter>[0-9]{2,4})(?:[-‐](?P<year>[0-9]{4}))?"
+ JP:
+ - "[cC](?P<counter>[0-9]+)"
+ MY:
+ - "ISCB-(?P<digit>[0-9])-RPT-C(?P<counter>[0-9]{3})-CR(?:-[0-9])?-(?P<version>[vV][0-9][a-z]?)"
+ IT:
+ TR:
+ SE:
+ - "CR(?P<year>[0-9]{4})(?P<counter>[0-9]{2,3})"
+ IN:
+ SG:
+ AU:
+ - "(?P<year>[0-9]{2,4})_(?P<counter>[0-9]+)"
#####
# Common Criteria protection profile IDs, grouped by certification body (e.g. BSI)
diff --git a/src/sec_certs/sample/cc.py b/src/sec_certs/sample/cc.py
index 6ee2bd1b..ef1ff984 100644
--- a/src/sec_certs/sample/cc.py
+++ b/src/sec_certs/sample/cc.py
@@ -19,7 +19,7 @@ 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.sample.cc_certificate_id import canonicalize
+from sec_certs.sample.cc_certificate_id import canonicalize, schemes
from sec_certs.sample.certificate import Certificate, References, logger
from sec_certs.sample.certificate import Heuristics as BaseHeuristics
from sec_certs.sample.certificate import PdfData as BasePdfData
@@ -337,13 +337,20 @@ class CCCertificate(
"""
if not self.report_filename:
return {}
- scheme_rules = rules["cc_cert_id"][scheme]
+ scheme_filename_rules = rules["cc_filename_cert_id"][scheme]
+ if not scheme_filename_rules:
+ return {}
+ scheme_meta = schemes[scheme]
matches: Counter = Counter()
- for rule in scheme_rules:
+ for rule in scheme_filename_rules:
match = re.search(rule, self.report_filename)
if match:
- cert_id = normalize_match_string(match.group())
- matches[cert_id] += 1
+ try:
+ meta = match.groupdict()
+ cert_id = scheme_meta(meta)
+ matches[cert_id] += 1
+ except Exception:
+ continue
if not matches:
return {}
total = max(matches.values())
diff --git a/src/sec_certs/sample/cc_certificate_id.py b/src/sec_certs/sample/cc_certificate_id.py
index 254c8e05..1fb7b046 100644
--- a/src/sec_certs/sample/cc_certificate_id.py
+++ b/src/sec_certs/sample/cc_certificate_id.py
@@ -2,133 +2,223 @@ from __future__ import annotations
import re
from dataclasses import dataclass
+from functools import cached_property
+from sec_certs.cert_rules import rules
-@dataclass(eq=True, frozen=True)
-class CertificateId:
- """
- A Common Criteria certificate id.
- """
- scheme: str
- raw: str
+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")
+ cert_id = f"ANSSI-CC-{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:
+ year = _parse_year(meta["year"])
+ counter = meta["counter"]
+ cc = meta.get("cc")
+ vid = meta.get("VID")
+ cert_id = "CCEVS-VR"
+ if cc:
+ cert_id += f"-{cc}"
+ if vid:
+ cert_id += f"-{vid}"
+ cert_id += f"-{counter}"
+ 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 _canonical_fr(self) -> str:
- def pad_last_segment_with_zero(id_str: str) -> str:
- splitted = id_str.split("/")
- if len(splitted) > 1:
- num = splitted[-1].zfill(2)
- return f"{''.join(splitted[:-1])}/{num}"
- return id_str
- new_cert_id = self.clean
- rules = [
- "(?:Rapport de certification|Certification Report) ([0-9]+[/-_][0-9]+(?:[vV][1-9])?(?:[_/-][MSR][0-9]+)?)",
- "(?:ANSS[Ii]|DCSSI)(?:-CC)?[- ]([0-9]+[/-_][0-9]+(?:[vV][1-9])?(?:[_/-][MSR][0-9]+)?)",
- "([0-9]+[/-_][0-9]+(?:[vV][1-9])?(?:[_/-][MSR][0-9]+)?)",
- ]
- for rule in rules:
- if match := re.match(rule, new_cert_id):
- return pad_last_segment_with_zero("ANSSI-CC-" + match.group(1).replace("_", "/").replace("V", "v"))
+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}"
- return new_cert_id
- def _canonical_de(self) -> str:
- def extract_parts(bsi_parts: list[str]) -> tuple:
- cert_num = None
- cert_version = None
- cert_year = None
+def SE(meta) -> str:
+ year = _parse_year(meta["year"])
+ counter = int(meta["counter"])
+ return f"CSEC{year}{counter:03}"
- if len(bsi_parts) > 3:
- cert_num = bsi_parts[3]
- if len(bsi_parts) > 4:
- if bsi_parts[4].startswith("V") or bsi_parts[4].startswith("v"):
- cert_version = bsi_parts[4].upper() # get version in uppercase
- else:
- cert_year = bsi_parts[4]
- if len(bsi_parts) > 5:
- cert_year = bsi_parts[5]
- return cert_num, cert_version, cert_year
+def UK(meta) -> str:
+ counter = meta["counter"]
+ return f"CRP{counter}"
- bsi_parts = self.clean.split("-")
- cert_num, cert_version, cert_year = extract_parts(bsi_parts)
+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}"
- # reconstruct BSI number again
- new_cert_id = "BSI-DSZ-CC"
- if cert_num is not None:
- new_cert_id += "-" + cert_num
- if cert_version is not None:
- new_cert_id += "-" + cert_version
- if cert_year is not None:
- new_cert_id += "-" + cert_year
- return new_cert_id
+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 _canonical_es(self) -> str:
- cert_id = self.clean
- spain_parts = cert_id.split("-")
- cert_year = spain_parts[0]
- cert_batch = spain_parts[1].lstrip("0")
- cert_num = spain_parts[3].lstrip("0")
- if "v" in cert_num:
- cert_num = cert_num[: cert_num.find("v")]
- if "V" in cert_num:
- cert_num = cert_num[: cert_num.find("V")]
+def KR(meta) -> str:
+ word = meta["word"]
+ counter = int(meta["counter"])
+ year = _parse_year(meta["year"])
+ return f"KECS-{word}-{counter:04}-{year}"
- new_cert_id = f"{cert_year}-{cert_batch}-INF-{cert_num.strip()}" # drop version # TODO: Maybe do not drop?
- return new_cert_id
+def TR(meta) -> str:
+ prefix = meta["prefix"]
+ number = meta["number"]
+ return f"{prefix}/TSE-CCCS-{number}"
- def _canonical_it(self):
- new_cert_id = self.clean
- if not new_cert_id.endswith("/RC"):
- new_cert_id = new_cert_id + "/RC"
- return new_cert_id
+def NO(meta) -> str:
+ counter = int(meta["counter"])
+ return f"SERTIT-{counter:03}"
- def _canonical_in(self):
- return self.clean.replace(" ", "")
- def _canonical_se(self):
- return self.clean.replace(" ", "")
+def NL(meta) -> str:
+ core = meta["core"]
+ doc = meta.get("doc")
+ if doc is None:
+ doc = "CR"
+ return f"NSCIB-CC-{core}-{doc}"
- def _canonical_uk(self):
- new_cert_id = self.clean
- if match := re.match("CERTIFICATION REPORT No. P([0-9]+[A-Z]?)", new_cert_id):
- new_cert_id = "CRP" + match.group(1)
- return new_cert_id
- def _canonical_ca(self):
- new_cert_id = self.clean
- if new_cert_id.endswith("-CR"):
- new_cert_id = new_cert_id[:-3]
- if new_cert_id.endswith("P"):
- new_cert_id = new_cert_id[:-1]
- return new_cert_id.replace(" ", "-")
+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 _canonical_jp(self):
- new_cert_id = self.clean
- if match := re.match("Certification No. (C[0-9]+)", new_cert_id):
- return match.group(1)
- if match := re.search("CRP-(C[0-9]+)-", new_cert_id):
- return match.group(1)
- return new_cert_id
- def _canonical_no(self):
- new_cert_id = self.clean
- cert_num = int(new_cert_id.split("-")[1])
- return f"SERTIT-{cert_num:03}"
+def SG(meta) -> str:
+ year = meta["year"]
+ counter = meta["counter"]
+ return f"CSA_CC_{year}{counter}"
- def _canonical_nl(self):
- new_cert_id = self.clean
- if new_cert_id.startswith("CC-"):
- new_cert_id = f"NSCIB-{new_cert_id}"
- if not new_cert_id.endswith("-CR"):
- new_cert_id = f"{new_cert_id}-CR"
- return new_cert_id
+
+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
+
+
+# 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,
+}
+
+
+@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:
@@ -142,25 +232,25 @@ class CertificateId:
"""
The canonical version of this certificate id.
"""
- # We have rules for some schemes to make canonical cert_ids.
- schemes = {
- "FR": self._canonical_fr,
- "DE": self._canonical_de,
- "ES": self._canonical_es,
- "IT": self._canonical_it,
- "IN": self._canonical_in,
- "SE": self._canonical_se,
- "UK": self._canonical_uk,
- "CA": self._canonical_ca,
- "JP": self._canonical_jp,
- "NO": self._canonical_no,
- "NL": self._canonical_nl,
- }
+ clean = self.clean
if self.scheme in schemes:
- return schemes[self.scheme]()
+ return schemes[self.scheme](self.meta)
else:
- return self.clean
+ 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:
diff --git a/tests/cc/test_cc_misc.py b/tests/cc/test_cc_misc.py
index b0932482..bf880f95 100644
--- a/tests/cc/test_cc_misc.py
+++ b/tests/cc/test_cc_misc.py
@@ -1,30 +1,59 @@
-from sec_certs.sample.cc_certificate_id import canonicalize
+from sec_certs.sample.cc_certificate_id import CertificateId, canonicalize
+
+
+def test_meta_parse():
+ i = CertificateId("FR", "Rapport de certification 2001/02v2")
+ assert "year" in i.meta
+ assert i.meta["year"] == "2001"
+ assert i.meta["counter"] == "02"
+ assert i.meta["version"] == "2"
def test_canonicalize_fr():
assert canonicalize("Rapport de certification 2001/02v2", "FR") == "ANSSI-CC-2001/02v2"
assert canonicalize("ANSSI-CC 2001/02-R01", "FR") == "ANSSI-CC-2001/02-R01"
+ assert canonicalize("ANSSI-CC 2001_02-M01", "FR") == "ANSSI-CC-2001/02-M01"
def test_canonicalize_de():
assert canonicalize("BSI-DSZ-CC-0420-2007", "DE") == "BSI-DSZ-CC-0420-2007"
+ assert canonicalize("BSI-DSZ-CC-1004", "DE") == "BSI-DSZ-CC-1004"
+ assert canonicalize("BSI-DSZ-CC-0831-V4-2021", "DE") == "BSI-DSZ-CC-0831-V4-2021"
+ assert canonicalize("BSI-DSZ-CC-0837-V2-2014-MA-01", "DE") == "BSI-DSZ-CC-0837-V2-2014-MA-01"
+
+
+def test_canonicalize_us():
+ assert canonicalize("CCEVS-VR-VID10015-2008", "US") == "CCEVS-VR-VID-10015-2008"
+ assert canonicalize("CCEVS-VR-10880-2018", "US") == "CCEVS-VR-10880-2018"
+ assert canonicalize("CCEVS-VR-04-0082", "US") == "CCEVS-VR-0082-2004"
+
+
+def test_canonicalize_my():
+ assert canonicalize("ISCB-5-RPT-C075-CR-v2", "MY") == "ISCB-5-RPT-C075-CR-v2"
+ assert canonicalize("ISCB-5-RPT-C046-CR-V1a", "MY") == "ISCB-5-RPT-C046-CR-v1a"
+ assert canonicalize("ISCB-3-RPT-C068-CR-1-v1", "MY") == "ISCB-3-RPT-C068-CR-v1"
def test_canonicalize_es():
assert canonicalize("2011-14-INF-1095-v1", "ES") == "2011-14-INF-1095"
-def test_canonicalize_it():
- assert canonicalize("OCSI/CERT/SYS/10/2016", "IT") == "OCSI/CERT/SYS/10/2016/RC"
+def test_canonicalize_sg():
+ assert canonicalize("CSA_CC_21005", "SG") == "CSA_CC_21005"
def test_canonicalize_in():
- assert canonicalize("IC3S/KOL01/ADVA/EAL2/0520/0021 /CR", "IN") == "IC3S/KOL01/ADVA/EAL2/0520/0021/CR"
+ assert canonicalize("IC3S/KOL01/ADVA/EAL2/0520/0021 /CR", "IN") == "IC3S/KOL01/ADVA/EAL2/0520/0021"
+
+
+def test_canonicalize_it():
+ assert canonicalize("OCSI/CERT/TEC/02/2009/RC", "IT") == "OCSI/CERT/TEC/02/2009/RC"
def test_canonicalize_se():
assert canonicalize("CSEC2017020", "SE") == "CSEC2017020"
assert canonicalize("CSEC 2017020", "SE") == "CSEC2017020"
+ assert canonicalize("CSEC201003", "SE") == "CSEC2010003"
def test_canonicalize_uk():
@@ -32,16 +61,48 @@ def test_canonicalize_uk():
assert canonicalize("CRP123A", "UK") == "CRP123A"
+def test_canonicalize_au():
+ assert canonicalize("Certification Report 2007/02", "AU") == "Certificate Number: 2007/02"
+ assert canonicalize("Certificate Number: 37/2006", "AU") == "Certificate Number: 2006/37"
+ assert canonicalize("Certificate Number: 2011/73", "AU") == "Certificate Number: 2011/73"
+ assert canonicalize("Certification Report 97/76", "AU") == "Certificate Number: 1997/76"
+
+
def test_canonicalize_ca():
assert canonicalize("383-4-123-CR", "CA") == "383-4-123"
assert canonicalize("383-4-123P", "CA") == "383-4-123"
+ assert canonicalize("522 EWA 2020", "CA") == "522-EWA-2020"
def test_canonicalize_jp():
- assert canonicalize("Certification No. C01234", "JP") == "C01234"
- assert canonicalize("CRP-C01234-01", "JP") == "C01234"
- assert canonicalize("JISEC-CC-CRP-C0689-01-2020", "JP") == "C0689"
+ assert canonicalize("Certification No. C01234", "JP") == "JISEC-CC-CRP-C01234"
+ assert canonicalize("CRP-C01234-01", "JP") == "JISEC-CC-CRP-C01234-01"
+ assert canonicalize("JISEC-CC-CRP-C0689-01-2020", "JP") == "JISEC-CC-CRP-C0689-01-2020"
+
+
+def test_canonicalize_kr():
+ assert canonicalize("KECS-ISIS-0579-2015", "KR") == "KECS-ISIS-0579-2015"
+ assert canonicalize("KECS-CISS-10-2023", "KR") == "KECS-CISS-0010-2023"
def test_canonicalize_no():
assert canonicalize("SERTIT-12", "NO") == "SERTIT-012"
+
+
+def test_canonicalize_tr():
+ assert canonicalize("21.0.03.0.00.00/TSE-CCCS-85", "TR") == "21.0.03.0.00.00/TSE-CCCS-85"
+ assert canonicalize("21.0.03/TSE-CCCS-33", "TR") == "21.0.03/TSE-CCCS-33"
+
+
+def test_canonicalize_nl():
+ assert canonicalize("NSCIB-CC-22-0428888-CR2", "NL") == "NSCIB-CC-22-0428888-CR2"
+ assert canonicalize("NSCIB-CC-22-0428888", "NL") == "NSCIB-CC-22-0428888-CR"
+ assert canonicalize("CC-22-0428888", "NL") == "NSCIB-CC-22-0428888-CR"
+
+
+def test_certid_compare():
+ cid1 = CertificateId("AU", "Certification Report 2007/02")
+ cid2 = CertificateId("AU", "Certificate Number: 02/2007")
+ cid3 = CertificateId("AU", "Certificate Number: 05/2007")
+ assert cid1 == cid2
+ assert cid1 != cid3
diff --git a/tests/test_common.py b/tests/test_common.py
index fa7a3775..7b29dd8d 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -5,5 +5,5 @@ def test_rules():
assert "cc_cert_id" in cc_rules
assert "fips_cert_id" in fips_rules
for rule_group in rules:
- if rule_group not in ("cc_rules", "fips_rules"):
+ if rule_group not in ("cc_rules", "fips_rules", "cc_filename_cert_id"):
assert rule_group in cc_rules or rule_group in fips_rules