One profile against two formats
The profile is defined independently of any format. A mapping locates each attribute, and the interface count, within a concrete document. CycloneDX provides a native cryptographic model. SPDX 3.0.1 does not, so a CBOM is currently expressed in CycloneDX and referenced from an SPDX SBOM. The SPDX community has indicated that a cryptographic object model is planned, so this position is expected to change; the working group has not verified the current state at field level, and the SPDX column below is hedged accordingly.
Because both formats permit the same information to be encoded in more than one valid way, the mapping also serves to normalize encoding: it defines a single, canonical location for each attribute. A consumer that relies on the mapping extracts the required information from one place rather than accommodating every valid variant, which reduces parsing complexity. See Challenges.
Two valid encodings of the same fact
The encoding problem is easier to see with a concrete case. Take a single fact from the worked example: the HTTPS interface uses X25519 for key exchange. CycloneDX allows a producer to record this in at least two ways, both correct.
Neither encoding is wrong, and a producer may use both together. The consequence is that a consumer cannot assume a single location. The validator in this example reads both, merging the cipher-suite algorithm list with the flat reference array before it evaluates any rule, because a CBOM that omits the key exchange and one that records it in the less common place are otherwise indistinguishable.
The same pattern appears for authentication. A TLS interface usually reaches its signature algorithm through the certificate, using certificateProperties.signatureAlgorithmRef. An SSH interface has no certificate, so the host key algorithm is referenced directly by the interface. Both satisfy the profile requirement for an authentication algorithm, and a consumer has to handle each. The mapping records which form applies in which case, so a consumer knows what to expect instead of establishing it by experiment.
Beyond locating attributes, then, the mapping removes a class of errors in which a CBOM is complete but a consumer reports it as deficient, only because the producer chose a different valid structure.
Representation of an interface
An interface, meaning a cryptographic relationship, is represented as a cryptographic-asset component with assetType: protocol. It is worth setting out how this works, because the profile's model and the format's model do not align at this point.
The format treats the protocol as an asset, in the same category as an algorithm or a certificate. The profile treats the interface as a relationship between endpoints. The mapping bridges the two by using the protocol asset as the carrier for the relationship and attaching the relationship's attributes to it as properties. CycloneDX supports this today: it has a cryptographic-asset component with an assetType of protocol to serve as the carrier. SPDX 3.0.1 has neither, which is why the SPDX column reaches the same attributes through a linked CycloneDX document rather than natively. In both cases the attachment of relationship attributes to the carrier is a convention agreed in the mapping, not a structure either format enforces.
This has several consequences for an implementation:
- The set of interfaces is derived, not declared. A consumer enumerates the components with
assetType: protocoland counts them. Product rules P1 and P2 are evaluated against that derived set, including the count of components carryinginterfaceType = management. - Nothing in the format prevents a protocol asset from being used for something other than an interface. A producer might record a protocol that a product implements generally, without it corresponding to a deployed interface. The
interfaceTypeproperty is what distinguishes the two, which is why the profile requires it on every interface. - An interface is identified by the producer's own
interfaceId, which the profile does not constrain. Consumers should treat it as a label, not as a stable cross-organization identifier. - The algorithms belonging to an interface are reached by reference, through the encodings described above, rather than being contained within it. A broken or missing reference presents as an absent attribute.
- Where one deployed interface is described by more than one component, for example when a product and an operator each report the same connection, correlation happens in the inventory rather than in the document. See Inventory.
If a format introduces a first-class object for the relationship, this part of the mapping changes and the profile's requirements do not.
Mapping table
| # | Requirement | CycloneDX 1.7 (illustrative) | SPDX 3.0.1 |
|---|---|---|---|
| P1 | ≥1 interface | count of assetType:protocol components carrying interfaceType | unresolved |
| P2 | ≥1 management | count where interfaceType==management | unresolved |
| I1 | protocol | protocolProperties.type | via linked CBOM |
| I2 | protocolVersion | protocolProperties.version | via linked CBOM |
| I3 | keyExchange | algorithm primitive=key-agree/kem | via linked CBOM |
| I4 | encryption | algorithm primitive=ae | via linked CBOM |
| I5 | authentication | certificate signatureAlgorithmRef or host key | via linked CBOM |
| I6 | endpointRoles | properties[endpointRole:*] (no edge model) | unresolved |
| I7 | interfaceType | properties[interfaceType] (no native field) | unresolved |
| I8 | lifecycleStage | properties[lifecycleStage] | unresolved |
| G1 | capabilityStatus, blockedBy, roadmapRef (per purpose) | properties[capabilityByPurpose:<purpose>:<attribute>] | unresolved |
| — | interfaceId (carried, not constrained) | properties[interfaceId], defaulting to bom-ref | unresolved |
| I9 | implementationPurl | purl on library component | packageUrl on a Package |
Interpretation of the columns
In CycloneDX, the cryptographic assets (I1–I5 and the provider in I9) are represented in native fields, which is an area of strength for the format. The interface-level classifiers on which the profile depends — interfaceType (I7), endpoint roles (I6), and lifecycle stage (I8) — have no native field and are carried in component.properties under the pkic:profile: namespace. The interfaceType attribute is what makes product rule P2 evaluable.
In SPDX, the absence of a cryptographic model in 3.0.1 means that most detail is obtained through the linked CycloneDX CBOM. The area of strength for SPDX is provider identity (I9): it identifies the OpenSSL and OpenSSH packages by packageUrl, which is the value cross-referenced against vulnerability feeds — the join described in Vulnerabilities. As SPDX gains a cryptographic object model, entries in this column will move from the linked CBOM to native SPDX fields. The requirement numbers stay the same, which is the point of expressing the profile independently of the format.
The edge gap
Normalization
Normalization covers two separate questions: where a value is written in the document, and how the value itself is expressed. Both have to be settled before CBOMs from different producers can be compared. The second causes more difficulty in practice.
The mapping normalizes identity as well as encoding. Naming a canonical location settles where a value is read. It does not establish whether two documents that carry that value describe the same component, certificate, key, or interface. Where an identifier scheme exists the mapping can require it, as requirement I9 does for the implementing library and as the registry does for algorithms. Where no scheme exists, and for the producer-chosen interfaceId in particular, a mapping cannot supply an identity the formats do not define. Identity normalization is therefore the harder of the two questions, and the residual correlation is performed in the inventory. See Challenges.
Why it matters
Every automated use of a CBOM depends on matching a value against something, whether a policy, a deprecation list, a vulnerability feed, or the same attribute in another CBOM. Those matches are exact. A value that means the right thing but is written differently will not match, and nothing in the process reports an error. A policy engine looking for AES-256-GCM that encounters AES256 finds no match, and depending on how the rule is written it treats the interface either as compliant or as having no encryption at all. Both results are incorrect, and both look like ordinary output.
The problem grows with the size of the estate. A single supplier using an unusual spelling can be handled by hand. The same variance across many suppliers and a large number of interfaces makes estate-wide questions impractical to answer, such as how many interfaces still use a deprecated algorithm, or which services an advisory affects. Inconsistent naming seen in real deployments is the reason the registry was introduced.
What the registry solves
CycloneDX 1.7 introduces the Cryptography Registry, which assigns stable identifiers to algorithm families and their parameters. Elliptic curve names show the problem clearly. The same curve is commonly written three ways, depending on which standards body or library the producer follows:
| As written by the producer | Origin | Refers to |
|---|---|---|
P-256 | NIST | The same curve. The three strings have no form in common, so a text comparison treats them as unrelated. |
secp256r1 | SECG | |
prime256v1 | ANSI, used by OpenSSL |
Compared as text these are three distinct values; compared as registry identifiers they are one. Symmetric algorithms raise a related difficulty: AES256, AES-256-GCM, and AES_256_GCM may or may not denote the same thing, because the first omits the mode that the others state. The registry also separates the algorithm from its parameters, so a query for deprecated key sizes does not depend on parsing a name.
For requirements I3, I4, and I5 a registry identifier is preferred wherever one exists. CBOMs authored to version 1.6 carry free-text names and are normalized at the adapter using a versioned name map, so that rules only ever see canonical values. See Versioning.
Protocols need normalizing too
Algorithm naming receives most of the attention, but protocol identity presents the same difficulty. The protocol and its version are recorded as free text in protocolProperties, and producers write them inconsistently: TLS, tls, and TLSv1.3; a version given as 1.3, TLS 1.3, or 0x0304; SSH as 2.0 or SSH-2.0. Some protocols have more than one accepted name, such as MACsec and IEEE 802.1AE.
There are several practical reasons to normalize this.
- Profiles gate on protocol version. A rule requiring TLS 1.2 or higher has to compare versions, and comparison needs a defined ordering. Strings do not provide one:
1.10sorts before1.9as text, andTLSv1.3does not parse as a number at all. - Deprecation is expressed per protocol version. Guidance withdraws TLS 1.0 and 1.1, not TLS generally, so a policy cannot be applied unless the version is canonical.
- The protocol determines which algorithm roles exist. TLS authenticates with a certificate signature, SSH with a host key, IPsec through IKE negotiation. A consumer that misreads the protocol looks for the wrong attribute and reports it as missing, as the authentication case above illustrates.
- Aggregation depends on it. Counting how many interfaces across an estate still run a given protocol version is a routine migration question, and it cannot be answered by grouping on inconsistent strings.
- Sector vocabularies differ. The same interface may be named by a 3GPP reference point in one document and by its IETF protocol in another. Without a canonical protocol identity these do not reconcile.
The mapping therefore specifies the expected form for protocol type and version, and the adapter normalizes what it receives before any rule is evaluated. Where a registry provides protocol identifiers, those are preferred for the same reasons they are preferred for algorithms.