Chapter 25: Inventory first: CBOM
Parts II through IV implement four families of post-quantum primitives from scratch. Part V puts three schemes into production, and they come from two of those four: ML-KEM and ML-DSA from the lattice chapters of Part II, and SLH-DSA from the hash-based chapters of Part III. Those three are the ones NIST has standardized. The code-based and isogeny schemes Part IV built are not deployment targets anywhere in this Part, which is a gap Part IV’s closing section addresses rather than one this chapter papers over. No team can swap a classical algorithm for a post-quantum one without first naming what runs where.
Chapter 1 framed the urgency through harvest now, decrypt later, Mosca’s inequality (Mosca, 2018), and three sets of policy and guidance timelines. The UK guidance addresses organizations generally. The US instruments run on two tracks: one binds National Security Systems (NSS), and Executive Order 14412 with Office of Management and Budget (OMB) Memorandum M-26-15, both June 2026, binds civilian federal systems.
| Instrument | Milestone | Date |
|---|---|---|
| NCSC (UK National Cyber Security Centre, 2025) | Discovery and assessment done, initial plan written | 2028 |
| NCSC (UK National Cyber Security Centre, 2025) | Highest-priority migration activities done | 2031 |
| NCSC (UK National Cyber Security Centre, 2025) | Migration complete | 2035 |
| NSM-10 (US National Security Agency, 2024) | All NSS quantum-resistant | 2035 |
| CNSSP 15 (US National Security Agency, 2024) | CNSA 2.0 mandated for use | 31 Dec 2031 |
| CNSA 2.0 (US National Security Agency, 2022) | Exclusive use: software and firmware signing, networking equipment | 2030 |
| CNSA 2.0 (US National Security Agency, 2022) | Exclusive use: web and cloud, operating systems, large PKI | 2033 |
| Executive Order 14412 (The White House, 2026) | High Value Assets and high-impact systems: post-quantum key establishment | 31 Dec 2030 |
| Executive Order 14412 (The White House, 2026) | High Value Assets and high-impact systems: post-quantum signatures | 31 Dec 2031 |
| OMB M-26-15 (Office of Management and Budget, 2026) | Full migration of remaining civilian federal systems | 2035 |
The NCSC guidance is aimed primarily at large organizations, critical-national-infrastructure operators, and organizations with bespoke IT, though it states that the core timelines are relevant to all organizations (UK National Cyber Security Centre, 2025). The two CNSA 2.0 per-asset milestones straddle the CNSSP 15 mandate, at 2030 for software and firmware signing and networking equipment and 2033 for web and cloud, operating systems and large PKI, and both fall before the 2035 goal. For an NSS operator the binding date is the asset-class row rather than the headline year, and which asset class an estate sits in decides whether that row lands before or after 31 December 2031. For a civilian federal operator the Executive Order 14412 rows arrive first. OMB M-26-15 schedules the remaining estate through 2035. Chapter 25 turns that urgency into a concrete artifact.
A Cryptography Bill of Materials (CBOM) is a machine-readable inventory of the cryptographic assets within a defined discovery scope. The CycloneDX specification, which also defines the SBOM format, extends its schema with a cryptographic-asset component type and a cryptoProperties sub-object for crypto asset type, primitive, parameter-set identifier, execution environment, and security-level metadata (OWASP CycloneDX, 2025). The four-way migration status this chapter assigns (vulnerable, grover-only, quantum-safe, unknown) is not a native CycloneDX field; it is carried as an organization-specific encryptorium:quantum-status property.
The inventory anchors the rest of the migration program. Lifecycle metadata feeds Mosca’s : how long the protected data or trust decision must remain secure (Chapter 1). Dependency counts, engineering estimates, and rollout complexity feed , the time the migration itself takes. is the time until a cryptographically relevant quantum computer exists. Chapter 1 notes it has no consensus date. The external deadlines above do not estimate ; they cap the latest acceptable migration date for the sectors and systems they bind.
The chapter walks a small Python application with five cryptographic touchpoints and builds a CycloneDX 1.6 document over it by hand in standard-library Python. Each entry is classified against a deliberately simplified migration taxonomy derived from the NIST post-quantum standards at FIPS 203 (National Institute of Standards and Technology, 2024), FIPS 204 (National Institute of Standards and Technology, 2024b), and FIPS 205 (National Institute of Standards and Technology, 2024c). The standalone code package at solutions/ch25-cbom/ carries the same logic, with pytest tests that validate structural CycloneDX fields and the quantum-vulnerability propagation rule.
A five-touchpoint application
Section titled “A five-touchpoint application”Consider a small backend service with five cryptographic uses. The first four are a public TLS endpoint terminated at api.example.com, a JWT-based session token signed RS256, PBKDF2-HMAC-SHA256 password hashing on the user table, and HMAC-SHA256 authentication tags on outgoing webhooks. The fifth is an ECDSA-secp256k1 signing key the service uses to author Layer-1 blockchain transactions in its role as a chain operator. Each use has a different primitive, a different failure mode under quantum attack, and a different ownership trail. A useful inventory names all five.
Block 1 declares the five touchpoints as plain Python records. The fields follow a fixed schema. Two are not self-evident from the key name: families, a short list of algorithm-family labels that the vulnerability lookup consumes, and exposure, the adversarial-visibility label (public or internal) that the priority matrix in Figure 25.1 reads.
# Block 1: the five cryptographic touchpoints.TOUCHPOINTS = [ { "name": "tls_endpoint_api", "location": "edge/api.example.com", "algorithm": "ECDHE-ECDSA-AES256-GCM-SHA384", "primitive": "key-agree", "parameters": { "tls_version": "1.2", "curve": "P-256", "aead": "AES-256-GCM", "signature": "ECDSA-P-256", }, "families": ["ECDHE", "ECDSA", "AES", "SHA-384"], "exposure": "public", "deployed": "2022-06-15", "owner": "platform-team", }, { "name": "jwt_signing", "location": "auth/token-service", "algorithm": "RS256", "primitive": "signature", "parameters": {"rsa_modulus_bits": 2048, "hash": "SHA-256"}, "families": ["RSA", "SHA-256"], "exposure": "internal", "deployed": "2023-01-10", "owner": "auth-team", }, { "name": "password_hashing", "location": "auth/user-service", "algorithm": "PBKDF2-HMAC-SHA256", "primitive": "kdf", "parameters": { "iterations": 600_000, "salt_bytes": 16, "dk_bytes": 32, "hash": "SHA-256", }, "families": ["PBKDF2", "HMAC", "SHA-256"], "exposure": "internal", "deployed": "2024-03-01", "owner": "auth-team", }, { "name": "webhook_hmac", "location": "webhooks/outgoing-signer", "algorithm": "HMAC-SHA256", "primitive": "mac", "parameters": {"key_bytes": 32, "hash": "SHA-256"}, "families": ["HMAC", "SHA-256"], "exposure": "internal", "deployed": "2023-08-20", "owner": "integrations-team", }, { "name": "blockchain_validator_sig", "location": "chain/validator-keystore", "algorithm": "ECDSA-secp256k1", "primitive": "signature", "parameters": {"curve": "secp256k1", "hash": "SHA-256"}, "families": ["ECDSA", "SHA-256"], "exposure": "public", "deployed": "2024-01-15", "owner": "chain-ops", },]
print(len(TOUCHPOINTS), [t["primitive"] for t in TOUCHPOINTS])# ==> 5 ['key-agree', 'signature', 'kdf', 'mac', 'signature']Every Python block this chapter prints is also a standalone file in the companion repository, under chapter-code/ch25/, one file per block. Appendix C covers the clone and the environment they run on.
The five records cover four of the fifteen CycloneDX primitive labels, namely key-agree, signature, kdf, and mac. The JWT signer and the validator key are both signature primitives. The records span Shor-vulnerable and Grover-only algorithm families, which is enough to produce an ordered priority list for the entries. The exposure label is adversarial visibility, not key location. blockchain_validator_sig keeps its private key in an internal keystore but signs into a public consensus ecosystem, so it is labeled public; jwt_signing is reachable only inside the trust boundary, so it is internal.
The inventory axes
Section titled “The inventory axes”Any CBOM entry has to answer four questions at once.
| Axis | What the entry has to pin down |
|---|---|
| Identity | The algorithm and its parameters. RSA-2048 is a different entry from RSA-4096, and TLS 1.2 with a P-256 curve is a different entry from TLS 1.3 with X25519. |
| Location | Where the use sits in the architecture, so an owner can be assigned and the migration touches the right code. edge/api.example.com and auth/token-service are both software, but they belong to different teams on different release cadences. |
| Risk profile | Quantum vulnerability combined with exposure. What shape that risk takes depends on what the primitive protects, which is the next paragraph. |
| Lifecycle | When the entry went live, how long its keys have to stay confidential, and when it is scheduled for rotation or retirement. |
The risk axis is the one that repays care. Harvest now, decrypt later (HNDL, from Chapter 1) applies to confidentiality-bearing key-establishment and encryption: recorded ciphertext is decrypted later once the key exchange is broken. Public-facing entries have the most-exposed HNDL surface. Internal traffic is still harvestable by an attacker with east-west network access, so the threat is not bounded by the perimeter. Signature and authentication entries carry a different risk: a future quantum attacker forges signatures, impersonates a service, or invalidates long-term trust, with no recorded-traffic step required. A CBOM should let a reader separate confidentiality risk from authenticity and long-term-verification risk.
For this toy application, the quantum axis reduces to three cases. Shor’s algorithm gives quantum polynomial-time attacks on factoring and discrete log, which breaks RSA, DSA, and classical Diffie-Hellman directly (Shor, 1994). The same approach extends to elliptic-curve discrete log, so ECDSA and ECDH fall under the same bound. Grover’s algorithm gives a square-root speedup on unstructured search (from queries to ), which halves the exponent of the brute-force key search against a symmetric primitive (Grover, 1996). The migration targets are the first three finalized NIST post-quantum standards: ML-KEM (FIPS 203 (National Institute of Standards and Technology, 2024a)), ML-DSA (FIPS 204 (National Institute of Standards and Technology, 2024b)), and SLH-DSA (FIPS 205 (National Institute of Standards and Technology, 2024c)).
For US National Security Systems specifically, NSA CNSA 2.0 fixes the main parameter choices: ML-KEM-1024 for key establishment, ML-DSA-87 for general digital signatures, AES-256, and SHA-384 or SHA-512. For software and firmware signing, CNSA 2.0 also approves the stateful hash-based LMS and XMSS profiles from SP 800-208. NSA encouraged those early because validated implementations were available before ML-DSA was widely validated. SLH-DSA is standardized by NIST but is not part of CNSA 2.0 for NSS. CNSSP 15 sets 31 December 2031 as the mandatory-use deadline under NSM-10’s 2035 NSS quantum-resistant goal (US National Security Agency, 2024).
A short lookup is enough for the toy application. Production tooling also includes protocol-level tags such as TLS version and ML-KEM hybrid mode, and flags deprecated primitives separately. The pedagogical slice here names the families and returns one of four labels (vulnerable, grover-only, quantum-safe, or unknown). The family table is deliberately coarse. A production scanner classifies a hash function by use case (preimage resistance, collision resistance, MAC or KDF construction, password hashing, transcript hashing, signature-domain separation), because SHA-256 has no single universal security interpretation.
From source code to CBOM JSON
Section titled “From source code to CBOM JSON”The first step is the vulnerability lookup. Block 2 declares the three-family table, the per-family status function, and the worst-case propagation rule for a touchpoint that combines several families. The rule is that a touchpoint is only as strong as its weakest component. An ECDHE-ECDSA-AES cipher suite is vulnerable as a whole: an attacker who derives the session key offline owns the session no matter how strong the bulk cipher is.
# Block 2: the three-family quantum-vulnerability lookup.VULNERABLE = "vulnerable"GROVER_ONLY = "grover-only"QUANTUM_SAFE = "quantum-safe"UNKNOWN = "unknown"
FAMILIES = { "RSA": VULNERABLE, "DSA": VULNERABLE, "ECDSA": VULNERABLE, "ECDHE": VULNERABLE, "ECDH": VULNERABLE, "DH": VULNERABLE, "AES": GROVER_ONLY, "SHA-256": GROVER_ONLY, "SHA-384": GROVER_ONLY, "SHA-512": GROVER_ONLY, "HMAC": GROVER_ONLY, "PBKDF2": GROVER_ONLY, "ML-KEM": QUANTUM_SAFE, "ML-DSA": QUANTUM_SAFE, "SLH-DSA": QUANTUM_SAFE,}
def status(family): return FAMILIES.get(family, UNKNOWN)
def touchpoint_status(families): if not families: return UNKNOWN # an empty entry is a gap, not safe ordered = [status(f) for f in families] if VULNERABLE in ordered: return VULNERABLE if UNKNOWN in ordered: return UNKNOWN if GROVER_ONLY in ordered: return GROVER_ONLY return QUANTUM_SAFE
print(status("RSA"), status("HMAC"), status("ML-KEM"), status("Frobnitz"))# ==> vulnerable grover-only quantum-safe unknownprint(touchpoint_status(["ECDHE", "ECDSA", "AES", "SHA-384"]))# ==> vulnerableprint(touchpoint_status(["HMAC", "SHA-256"]))# ==> grover-onlyThe second step is the CBOM generator. Block 3 builds a single CycloneDX component for one touchpoint and prints it. Block 4 below is what wraps the components in a document. That envelope follows the CycloneDX specification (OWASP CycloneDX, 2025): bomFormat, specVersion, serialNumber, version, a metadata block, and a components array. Each component is of type cryptographic-asset with cryptoProperties.assetType = "algorithm" and algorithm details under cryptoProperties.algorithmProperties. Organization-specific fields go into a properties list of name-value pairs, the standard CycloneDX escape hatch.
# Block 3: build the CycloneDX component for one touchpoint.import json
VULNERABLE = "vulnerable"# Reduced table for this single JWT example; the full package uses# the shared three-family table from Block 2.FAMILIES = {"RSA": VULNERABLE, "SHA-256": "grover-only"}
def status(family): return FAMILIES.get(family, "unknown")
def touchpoint_status(families): if not families: return "unknown" ordered = [status(f) for f in families] if VULNERABLE in ordered: return VULNERABLE if "unknown" in ordered: return "unknown" return "grover-only"
def parameter_set(params): # Sort keys so the identifier is stable across versions and a # CBOM-to-CBOM diff is meaningful. return "; ".join(f"{k}={params[k]}" for k in sorted(params))
def component(touchpoint): return { "type": "cryptographic-asset", "bom-ref": f"crypto:{touchpoint['name']}", "name": touchpoint["algorithm"], "cryptoProperties": { "assetType": "algorithm", "algorithmProperties": { "primitive": touchpoint["primitive"], "parameterSetIdentifier": parameter_set(touchpoint["parameters"]), "executionEnvironment": "software-plain-ram", }, }, "properties": [ {"name": "encryptorium:location", "value": touchpoint["location"]}, {"name": "encryptorium:exposure", "value": touchpoint["exposure"]}, {"name": "encryptorium:owner", "value": touchpoint["owner"]}, {"name": "encryptorium:deployed", "value": touchpoint["deployed"]}, {"name": "encryptorium:quantum-status", "value": touchpoint_status(touchpoint["families"])}, {"name": "encryptorium:families", "value": ",".join(touchpoint["families"])}, ], }
jwt = { "name": "jwt_signing", "location": "auth/token-service", "algorithm": "RS256", "primitive": "signature", "parameters": {"rsa_modulus_bits": 2048, "hash": "SHA-256"}, "families": ["RSA", "SHA-256"], "exposure": "internal", "deployed": "2023-01-10", "owner": "auth-team",}print(json.dumps(component(jwt), indent=2))# ==> {# ==> "type": "cryptographic-asset",# ==> "bom-ref": "crypto:jwt_signing",# ==> "name": "RS256",# ==> "cryptoProperties": {# ==> "assetType": "algorithm",# ==> "algorithmProperties": {# ==> "primitive": "signature",# ==> "parameterSetIdentifier": "hash=SHA-256; rsa_modulus_bits=2048",# ==> "executionEnvironment": "software-plain-ram"# ==> }# ==> },# ==> "properties": [# ==> {# ==> "name": "encryptorium:location",# ==> "value": "auth/token-service"# ==> },# ==> {# ==> "name": "encryptorium:exposure",# ==> "value": "internal"# ==> },# ==> {# ==> "name": "encryptorium:owner",# ==> "value": "auth-team"# ==> },# ==> {# ==> "name": "encryptorium:deployed",# ==> "value": "2023-01-10"# ==> },# ==> {# ==> "name": "encryptorium:quantum-status",# ==> "value": "vulnerable"# ==> },# ==> {# ==> "name": "encryptorium:families",# ==> "value": "RSA,SHA-256"# ==> }# ==> ]# ==> }The third step wraps all five components in a CycloneDX envelope and prints a structural summary. Block 4 does not reproduce the full JSON output. The full document is in solutions/ch25-cbom/ and in the test suite. The summary lists each touchpoint, its primitive, its exposure, and its quantum status, which is what feeds the next step of the migration program.
# Block 4: assemble the full CBOM and print the inventory summary.TOUCHPOINTS = [ {"name": "tls_endpoint_api", "primitive": "key-agree", "exposure": "public", "families": ["ECDHE", "ECDSA", "AES", "SHA-384"]}, {"name": "jwt_signing", "primitive": "signature", "exposure": "internal", "families": ["RSA", "SHA-256"]}, {"name": "password_hashing", "primitive": "kdf", "exposure": "internal", "families": ["PBKDF2", "HMAC", "SHA-256"]}, {"name": "webhook_hmac", "primitive": "mac", "exposure": "internal", "families": ["HMAC", "SHA-256"]}, {"name": "blockchain_validator_sig", "primitive": "signature", "exposure": "public", "families": ["ECDSA", "SHA-256"]},]
FAMILIES = { "ECDHE": "vulnerable", "ECDSA": "vulnerable", "AES": "grover-only", "SHA-384": "grover-only", "RSA": "vulnerable", "SHA-256": "grover-only", "PBKDF2": "grover-only", "HMAC": "grover-only",}
def touchpoint_status(families): if not families: return "unknown" ordered = [FAMILIES.get(f, "unknown") for f in families] if "vulnerable" in ordered: return "vulnerable" if "unknown" in ordered: return "unknown" if "grover-only" in ordered: return "grover-only" return "quantum-safe"
print(f"{'touchpoint':<24} {'primitive':<12} {'exposure':<10} {'status'}")for t in TOUCHPOINTS: print(f"{t['name']:<24} {t['primitive']:<12} {t['exposure']:<10} " f"{touchpoint_status(t['families'])}")# ==> touchpoint primitive exposure status# ==> tls_endpoint_api key-agree public vulnerable# ==> jwt_signing signature internal vulnerable# ==> password_hashing kdf internal grover-only# ==> webhook_hmac mac internal grover-only# ==> blockchain_validator_sig signature public vulnerableThree of the five touchpoints are Shor-vulnerable and two carry only a Grover-reduced margin, but the two Grover-reduced readings are not the same. webhook_hmac has a clean symmetric reading: with a uniformly random 256-bit MAC key and full-length HMAC-SHA256 tags, Grover key search still leaves roughly a query margin. password_hashing is different. PBKDF2-HMAC-SHA256 uses only symmetric primitives, so Shor does not break it, but its real security is bounded by password entropy, salt handling, iteration count, and the rate-limiting and breach model around the verifier, not by the 32-byte derived-key length. Setting dk_bytes = 32 does not give a human password a 128-bit post-quantum margin. Collision resistance is a separate question (BHT gives approximately queries with quantum-accessible memory on the same scale), and the chapter does not claim a quantum margin for that case.
Figure 25.1 positions each touchpoint on a quantum-vulnerability axis against an exposure axis, with each of its six cells labeled by migration priority. Four labels cover the six cells, because LOW and DONE each appear twice. The HIGH-priority touchpoints carry forward into the blockchain Part: Ch 37 walks the L1 transaction signing migration, Ch 39 covers consensus and staking signatures, Ch 40 addresses the smart-contract verifier contracts that ZK rollups deploy.
The CBOM document that build_cbom returns in the ch25-cbom package under solutions/ carries the same six encryptorium: properties per component as Block 3. The test at tests/ch25/test_cbom_schema.py checks that every component has the required CycloneDX fields and all six property names.
Production tooling in the CycloneDX ecosystem handles a full schema, schema validation, and richer metadata. The cyclonedx-python-lib package provides data models, validators, and Vulnerability Exploitability eXchange (VEX) document modeling. BOM signing and full VEX lifecycle workflows are separate ecosystem tools (OWASP CycloneDX, 2025).
This chapter targets 1.6 rather than the current version. CycloneDX 1.7 was released on 2025-10-21, and specVersion is a per-document field, so a 1.6 document stays valid. Every field used here is defined identically in both. 1.7 adds algorithmFamily and ellipticCurve to algorithmProperties, and key-wrap to the primitive enum, none of which this generator emits.
What makes an inventory wrong
Section titled “What makes an inventory wrong”Four failure modes produce a CBOM document that looks complete while hiding risk. NIST SP 1800-38B, the NCCoE Quantum Readiness: Cryptographic Discovery practice guide, frames cryptographic discovery as a multifaceted problem and motivates these categories, particularly the gaps around library and framework dependencies (National Cybersecurity Center of Excellence (NCCoE), 2023). NIST IR 8547 is the companion transition-planning report (algorithm deprecation schedules and PQC replacements), not a discovery guide (Moody et al., 2024).
Shadow cryptography. Third-party libraries often bring their own cryptographic primitives. A JWT library may hash with SHA-1 internally for key-id derivation even while the signing algorithm is RS256. A logging dependency may use a weak PRF for request correlation. Source scanning limited to top-level code misses both. A useful CBOM program audits library versions against a curated database of known primitive uses, or instruments the runtime to log algorithm identifiers from inside the library stack.
Stale entries. Algorithms are deprecated on a cadence (3DES, SHA-1, RSA-1024, TLS 1.0), but deployed code is slow to follow. An entry that records “TLS 1.2” when the endpoint actually negotiates TLS 1.3 is stale. An entry that records “TLS 1.2” when the endpoint still accepts TLS 1.0 hides risk. Periodic regeneration from live configuration, not from a one-time manual survey, is the only reliable fix.
Parameter drift. An RSA-1024 key generated for a lab test five years ago can remain in a key vault long after anyone remembers why. The inventory must capture not only the algorithm but every parameter that affects strength: key length, curve choice, hash output size, iteration count, nonce size. A bare entry “RSA signing” is close to useless; an entry “RS256 / RSA-2048 / SHA-256” is auditable.
Out-of-band cryptography. HSM-managed keys, enclaves, and hardware security tokens do not always expose their algorithms to source scanning. A CBOM that covers only the application layer will omit operations performed inside the HSM. Filling this gap requires a second inventory pass against the HSM vendor’s key catalogue and a cross-reference back to the application CBOM by key identifier.
Tradeoffs: what a CBOM is and is not
Section titled “Tradeoffs: what a CBOM is and is not”A CBOM enables migration tracking through a versioned, machine-readable record. Its bom-ref and encryptorium:quantum-status fields survive an automated diff across versions of the document, which turns “what changed since last quarter” into a mechanical query rather than a manual review. The NCSC milestones for UK organizations (2028 identify assets, 2031 high-priority migration activities, 2035 complete migration) require a repeatable inventory process. A CBOM that regenerates after each migration step supplies the before-and-after record the 2028 asset-identification milestone assumes (UK National Cyber Security Centre, 2025).
Figure 25.2 traces the pipeline from source code to CBOM to risk score to migration roadmap and back to progress tracking.
A CBOM does not automate migration. It does not check whether an implementation is correct, whether a library version is affected by a CVE, or whether a deployment has rolled out safely. CBOM and CycloneDX VEX are designed to be paired: the CBOM inventories the assets, a VEX document tracks which known vulnerabilities are actually exploitable in context. Side-channel resistance is not captured in a CBOM (timing leaks, Rowhammer, EM emanations all live outside the schema). A CBOM is the input artifact for those checks, not a substitute.
At the start of a migration program the choice is coverage against completeness. The NCSC 2028 asset-identification milestone requires a named inventory. Waiting for 100% estate coverage before filing one misses the deadline. A CBOM with explicit “not yet inventoried” entries for the remaining estate is auditable; a gap that is silently omitted is not. The NCSC guidance and NSA CNSA 2.0 timelines both assume rolling inventories rather than one-shot surveys (UK National Cyber Security Centre, 2025; US National Security Agency, 2024).
One final caveat on the toy generator. The output of build_cbom in the ch25-cbom package under solutions/ is a minimal CycloneDX 1.6 subset: bomFormat, specVersion, version, serialNumber, metadata, and a components array of cryptographic-asset entries. It omits VEX linkages, external-reference bundles, licensing fields, signatures on the BOM itself, and the full vendor / hash / evidence tree that a production toolchain populates. The TLS row also collapses a protocol profile into one teaching entry: a production CBOM models the cipher-suite configuration and the underlying key-agreement, signature, AEAD, and hash assets as separate cryptographic-asset components linked by bom-ref. The tests/ch25/test_cbom_schema.py suite confirms the structural subset is correct. The document is not a complete description of the application.
Exercises
Section titled “Exercises”-
Extend the toy application with a sixth touchpoint for ML-KEM-768 key encapsulation at a new endpoint
api.example.com/v2/kem. UpdateBlock 1with the new record and the vulnerability table so the generator tags the entry asquantum-safe. Print the resulting CBOM summary. -
Write a filter function
only_vulnerable(cbom)that returns the list of components whoseencryptorium:quantum-statusproperty equals"vulnerable". Run it against the five-touchpoint CBOM and confirm the three expected entries. -
Define a migration-priority score. A reasonable rule: a vulnerable touchpoint that is public-facing scores HIGH; a vulnerable touchpoint that is internal scores MEDIUM; a grover-only touchpoint of any exposure scores LOW; a quantum-safe touchpoint scores DONE. The
exposurefield is already on eachBlock 1record and is emitted as theencryptorium:exposureproperty. Implement the scorer over (encryptorium:quantum-status,encryptorium:exposure) and print the priority for all five entries. -
Given two CBOM documents (the state before and after migrating
jwt_signingfrom RS256 to ML-DSA-65), write a diff function that reports each entry whoseencryptorium:quantum-statuschanged and what it changed to. -
Name one cryptographic use the source-only inventory described in this chapter would miss, and propose one mitigation that does not require rewriting the application. Indicate which of the four failure modes in Section “What makes an inventory wrong” the mitigation addresses, and whether it overlaps with the discovery challenges NIST SP 1800-38B names (National Cybersecurity Center of Excellence (NCCoE), 2023).
Worked solutions and editorial notes for these exercises are in Appendix D, Chapter 25. A separate track, for rebuilding rather than reading. Chapter 25 prints the vulnerability lookup and the per-touchpoint component builder in full, so the package exercises/ch25-cbom hands you both and stubs only build_cbom, the CycloneDX envelope this chapter describes but never prints. Grade your version against the suite that proves the reference one with PQC_IMPL=exercises pytest tests/ch25.
References
Section titled “References”Last updated: