2025-01-17 18:27:46 +03:00

225 lines
8.3 KiB
Python

# SPDX-License-Identifier: MIT
# This script generates a Cryptography Bill of Material (CBOM)
# according to https://github.com/IBM/CBOM/blob/main/bom-1.4-cbom-1.0.schema.json
# apt-get install npm python3-git
import argparse
import glob
import yaml
import os
import json
import git
import uuid
import datetime
import copy
cbom_json_file = "cbom.json"
def load_yaml(filename, encoding='utf-8'):
with open(filename, mode='r', encoding=encoding) as fh:
return yaml.safe_load(fh.read())
def file_get_contents(filename, encoding=None):
with open(filename, mode='r', encoding=encoding) as fh:
return fh.read()
def out_write(out, str):
out.write(str)
kem_yamls = []
sig_yamls = []
cbom_components = []
bom_algs_bomrefs = []
bom_algs_use_dependencies = {}
## Common crypto components: aes, sha3
common_crypto_component_aes = {
"type": "cryptographic-asset",
"bom-ref": "alg:aes",
"name": "aes",
"cryptoProperties": {
"assetType": "algorithm",
"algorithmProperties": {
"primitive": "block-cipher",
"executionEnvironment": "software-plain-ram"
}
}
}
common_crypto_component_sha3 = {
"type": "cryptographic-asset",
"bom-ref": "alg:sha3",
"name": "sha3",
"cryptoProperties": {
"assetType": "algorithm",
"algorithmProperties": {
"primitive": "hash",
"executionEnvironment": "software-plain-ram"
}
}
}
def add_cbom_component(out, kem_yaml, parameter_set):
primitive = kem_yaml['type']
component = {}
component['type'] = "cryptographic-asset"
component['bom-ref'] = "alg:" + parameter_set['name']
component['name'] = kem_yaml['name']
algorithmProperties = {}
algorithmProperties['parameterSetIdentifier'] = parameter_set['name']
algorithmProperties['primitive'] = primitive
algorithmProperties['executionEnvironment'] = "software-plain-ram"
if primitive == 'kem':
algorithmProperties['cryptoFunctions'] = ["keygen", "encapsulate", "decapsulate"]
elif primitive == 'signature':
algorithmProperties['cryptoFunctions'] = ["keygen", "sign", "verify"]
algorithmProperties['nistQuantumSecurityLevel'] = parameter_set['claimed-nist-level']
cryptoProperties = {}
cryptoProperties['assetType'] = "algorithm"
cryptoProperties['algorithmProperties'] = algorithmProperties
component['cryptoProperties'] = cryptoProperties
for impl in parameter_set['implementations']:
dic = {
"all": "generic",
"x86_64": "x86_64",
"ARM64_V8": "armv8-a"
}
dep = []
if 'common-crypto' in impl:
for a in impl['common-crypto']:
if "SHA3" in a:
dep.append(common_crypto_component_sha3['bom-ref'])
elif "AES" in a:
dep.append(common_crypto_component_aes['bom-ref'])
if impl['supported-platforms'] == "all":
algorithmProperties['implementationPlatform'] = dic[impl['supported-platforms']]
component_cpy = copy.deepcopy(component)
component_cpy['bom-ref'] += ":" + algorithmProperties['implementationPlatform']
cbom_components.append(component_cpy)
bom_algs_bomrefs.append(component_cpy['bom-ref'])
if (dep):
bom_algs_use_dependencies.update({
component_cpy['bom-ref'] : dep
})
else:
for plat in impl['supported-platforms']:
if plat['architecture'] in dic.keys():
algorithmProperties['implementationPlatform'] = dic[plat['architecture']]
component_cpy = copy.deepcopy(component)
if 'upstream' in impl and impl['upstream'] == 'libjade':
tag = ":jasmin:"
if any('required_flags' in i for i in impl['supported-platforms']):
tag += impl['upstream-id'] + ':'
component_cpy['bom-ref'] += tag + algorithmProperties['implementationPlatform']
else:
component_cpy['bom-ref'] += ":" + algorithmProperties['implementationPlatform']
cbom_components.append(component_cpy)
bom_algs_bomrefs.append(component_cpy['bom-ref'])
if dep:
bom_algs_use_dependencies.update({
component_cpy['bom-ref'] : dep
})
def build_cbom(liboqs_root, liboqs_version):
## Add KEM components
for kem_yaml_path in sorted(glob.glob(os.path.join(liboqs_root, 'docs', 'algorithms', 'kem', '*.yml'))):
kem_yaml = load_yaml(kem_yaml_path)
kem_yamls.append(kem_yaml)
kem_name = os.path.splitext(os.path.basename(kem_yaml_path))[0]
name = kem_yaml['name']
for parameter_set in kem_yaml['parameter-sets']:
add_cbom_component(None, kem_yaml, parameter_set)
## Add Sig components
for sig_yaml_path in sorted(glob.glob(os.path.join(liboqs_root, 'docs', 'algorithms', 'sig', '*.yml'))):
sig_yaml = load_yaml(sig_yaml_path)
sig_yamls.append(sig_yaml)
sig_name = os.path.splitext(os.path.basename(sig_yaml_path))[0]
for parameter_set in sig_yaml['parameter-sets']:
add_cbom_component(None, sig_yaml, parameter_set)
## liboqs component
liboqs_component = {}
version = liboqs_version
if version == "git":
repo = git.Repo(search_parent_directories=True, odbt=git.GitDB)
version = repo.head.object.hexsha
liboqs_component['type'] = "library"
liboqs_component['bom-ref'] = "pkg:github/open-quantum-safe/liboqs@" + version
liboqs_component['name'] = "liboqs"
liboqs_component['version'] = version
cbom_components.insert(0, liboqs_component)
metadata = {}
metadata['timestamp'] = datetime.datetime.now(datetime.timezone.utc).isoformat()
metadata['component'] = liboqs_component
## Dependencies
dependencies = []
dependencies.append({
"ref": liboqs_component['bom-ref'],
"provides": bom_algs_bomrefs
})
for usedep in bom_algs_use_dependencies.keys():
dependencies.append({
"ref": usedep,
"dependsOn": bom_algs_use_dependencies[usedep]
})
## CBOM
cbom = {}
cbom['$schema'] = "https://raw.githubusercontent.com/CycloneDX/specification/1.6/schema/bom-1.6.schema.json"
cbom['bomFormat'] = "CycloneDX"
cbom['specVersion'] = "1.6"
cbom['serialNumber'] = "urn:uuid:" + str(uuid.uuid4())
cbom['version'] = 1
cbom['metadata'] = metadata
cbom['components'] = cbom_components + [common_crypto_component_aes, common_crypto_component_sha3]
cbom['dependencies'] = dependencies
return cbom
def algorithms_changed(cbom, cbom_path):
if os.path.isfile(cbom_path):
with open(cbom_path, mode='r', encoding='utf-8') as c:
existing_cbom = json.load(c)
existing_cbom['serialNumber'] = cbom['serialNumber']
existing_cbom['metadata']['timestamp'] = cbom['metadata']['timestamp']
existing_cbom['metadata']['component']['bom-ref'] = cbom['metadata']['component']['bom-ref']
existing_cbom['metadata']['component']['version'] = cbom['metadata']['component']['version']
existing_cbom['components'][0]['bom-ref'] = cbom['components'][0]['bom-ref']
existing_cbom['components'][0]['version'] = cbom['components'][0]['version']
existing_cbom['dependencies'][0]['ref'] = cbom['dependencies'][0]['ref']
update_cbom = existing_cbom != cbom
c.close()
return update_cbom
else:
return True
def update_cbom_if_algs_not_changed(liboqs_root, liboqs_version):
cbom_path = os.path.join(liboqs_root, 'docs', cbom_json_file)
cbom = build_cbom(liboqs_root, liboqs_version)
if algorithms_changed(cbom, cbom_path):
with open(cbom_path, mode='w', encoding='utf-8') as out_md:
out_md.write(json.dumps(cbom, indent=2))
out_md.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("--liboqs-root", default=".")
parser.add_argument("--liboqs-version", default="git")
args = parser.parse_args()
update_cbom_if_algs_not_changed(args.liboqs_root, args.liboqs_version)