import glob import os from typing import Optional from functools import lru_cache import yaml import platform import helpers class Scheme: def __init__(self): self.type = None self.name = None self.implementations = [] def path(self, base='..'): return os.path.join(base, 'crypto_' + self.type, self.name) def namespace_prefix(self): return 'PQCLEAN_{}_'.format(self.name.upper()).replace('-', '') # only useful for Falcon def padded_namespace_prefix(self): if self.name.startswith('falcon-'): return 'PQCLEAN_{}PADDED{}_'.format(*self.name.upper().split('-')) else: # return a dummy value return self.namespace_prefix() @staticmethod @lru_cache(maxsize=None) def by_name(scheme_name): for scheme in Scheme.all_schemes(): if scheme.name == scheme_name: return scheme raise KeyError(f"No scheme for {scheme_name}") @staticmethod @lru_cache(maxsize=1) def all_schemes(): schemes = [] schemes.extend(Scheme.all_schemes_of_type('kem')) schemes.extend(Scheme.all_schemes_of_type('sign')) return schemes @staticmethod @lru_cache(maxsize=1) def all_implementations(): implementations = [] for scheme in Scheme.all_schemes(): implementations.extend(scheme.implementations) return implementations @staticmethod @lru_cache(maxsize=1) def all_supported_implementations(): return [impl for impl in Scheme.all_implementations() if impl.supported_on_current_platform()] @staticmethod @lru_cache(maxsize=32) def all_schemes_of_type(type: str) -> list: schemes = [] p = os.path.join('..', 'crypto_' + type) if os.path.isdir(p): for d in os.listdir(p): if os.path.isdir(os.path.join(p, d)): if type == 'kem': schemes.append(KEM(d)) elif type == 'sign': schemes.append(Signature(d)) else: assert('Unknown type') return schemes @lru_cache(maxsize=None) def metadata(self): metafile = os.path.join(self.path(), 'META.yml') try: with open(metafile, encoding='utf-8') as f: metadata = yaml.safe_load(f) return metadata except Exception as e: print("Can't open {}: {}".format(metafile, e)) return None def __repr__(self): return "<{}({})>".format(self.type.title(), self.name) class Implementation: def __init__(self, scheme, name): self.scheme = scheme self.name = name @lru_cache(maxsize=None) def metadata(self): for i in self.scheme.metadata()['implementations']: if i['name'] == self.name: return i def path(self, base='..') -> str: return os.path.join(self.scheme.path(base=base), self.name) def libname(self) -> str: if os.name == 'nt': return "lib{}_{}.lib".format(self.scheme.name, self.name) return "lib{}_{}.a".format(self.scheme.name, self.name) def cfiles(self) -> [str]: return glob.glob(os.path.join(self.path(), '*.c')) def hfiles(self) -> [str]: return glob.glob(os.path.join(self.path(), '*.h')) def ofiles(self) -> [str]: return glob.glob(os.path.join(self.path(), '*.o' if os.name != 'nt' else '*.obj')) @staticmethod @lru_cache(maxsize=None) def by_name(scheme_name, implementation_name): scheme = Scheme.by_name(scheme_name) for implementation in scheme.implementations: if implementation.name == implementation_name: return implementation raise KeyError(f"No implementation for {scheme_name} - {implementation_name}") @staticmethod @lru_cache(maxsize=None) def all_implementations(scheme: Scheme) -> list: implementations = [] for d in os.listdir(scheme.path()): if os.path.isdir(os.path.join(scheme.path(), d)): implementations.append(Implementation(scheme, d)) return implementations @staticmethod def all_supported_implementations(scheme: Scheme) -> list: return [impl for impl in Implementation.all_implementations(scheme) if impl.supported_on_current_platform()] def namespace_prefix(self): return '{}{}_'.format(self.scheme.namespace_prefix(), self.name.upper()).replace('-', '') # only useful for Falcon def padded_namespace_prefix(self): return '{}{}_'.format(self.scheme.padded_namespace_prefix(), self.name.upper()).replace('-', '') def supported_on_os(self, os: Optional[str] = None) -> bool: """Check if we support the OS If no OS is specified, then we run on the current OS """ if os is None: os = platform.system() for platform_ in self.metadata().get('supported_platforms', []): if 'operating_systems' in platform_: if os not in platform_['operating_systems']: return False return True @lru_cache(maxsize=10000) def supported_on_current_platform(self) -> bool: if 'supported_platforms' not in self.metadata(): return True if platform.machine() == 'ppc': return False if not self.supported_on_os(): return False cpuinfo = helpers.get_cpu_info() for platform_ in self.metadata()['supported_platforms']: if platform_['architecture'] == cpuinfo['arch'].lower(): # Detect actually running on emulated i386 if platform_['architecture'] == 'x86_64': if platform.architecture()[0] == '32bit': continue if (platform.system() == "Windows" and os.environ.get("PLATFORM") == "x86"): continue if all(flag in cpuinfo['flags'] for flag in platform_['required_flags']): return True return False def __str__(self): return "{} implementation of {}".format(self.name, self.scheme.name) def __repr__(self): return "".format(self.scheme.name, self.name) class KEM(Scheme): def __init__(self, name: str): self.type = 'kem' self.name = name self.implementations = Implementation.all_implementations(self) @staticmethod def all_kems() -> list: return Scheme.all_schemes_of_type('kem') class Signature(Scheme): def __init__(self, name: str): self.type = 'sign' self.name = name self.implementations = Implementation.all_implementations(self) @staticmethod def all_sigs(): return Scheme.all_schemes_of_type('sign')