Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions entity/src/sbom_crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub struct Model {
pub asset_type: CryptoAssetType,
pub properties: serde_json::Value,
pub oid: Option<String>,
pub policy_verdict: Option<String>,
}

/// Possible types of the cryptographic assets within a CBOM
Expand Down
5 changes: 5 additions & 0 deletions etc/conforma/policy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
description: 'PQC Readiness Policy for trustify CBOM evaluation'
sources:
- name: PQC Readiness
policy:
- /policy
81 changes: 81 additions & 0 deletions etc/conforma/policy/policy.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package main

import future.keywords.contains
import future.keywords.if
import future.keywords.in

# Each entry in input.algorithms is:
# { "node_id": str, "sbom_id": str, "name": str, "oid": str|null, "properties": obj }
#
# Conforma strips extra fields from deny/warn results and only preserves "msg".
# We embed the node_id as a "[node_id:<uuid>]" prefix so trustify can parse it
# back out and match violations to individual AlgorithmPolicyResult rows.

deny contains result if {
algo := input.algorithms[_]
_is_non_compliant(algo)
result := {
"msg": sprintf("[node_id:%v] Algorithm '%v' is non-compliant (weak or broken)", [algo.node_id, algo.name]),
}
}

warn contains result if {
algo := input.algorithms[_]
not _is_pqc_safe(algo.name)
not _is_non_compliant(algo)
result := {
"msg": sprintf("[node_id:%v] Algorithm '%v' is not PQC-safe (classical — consider migration)", [algo.node_id, algo.name]),
}
}

# ── PQC-safe detection ────────────────────────────────────────────────────────

_is_pqc_safe(name) if {
normalized := upper(replace(name, "-", ""))
some pattern in {"MLKEM", "KYBER", "MLDSA", "DILITHIUM", "SLHDSA", "SPHINCS"}
contains(normalized, pattern)
}

# ── Non-compliant detection ───────────────────────────────────────────────────

_is_non_compliant(algo) if {
normalized := upper(replace(algo.name, "-", ""))
some pattern in {"MD5", "RC4", "RC2"}
contains(normalized, pattern)
}

_is_non_compliant(algo) if {
_is_sha1(algo.name)
}

# DES but not 3DES / Triple-DES / DESede
_is_non_compliant(algo) if {
normalized := upper(replace(algo.name, "-", ""))
contains(normalized, "DES")
not contains(normalized, "3DES")
not contains(normalized, "TDES")
not contains(normalized, "TRIPLE")
not contains(normalized, "DESEDE")
}

# RSA with key size <= 1024
_is_non_compliant(algo) if {
normalized := upper(replace(algo.name, "-", ""))
contains(normalized, "RSA")
to_number(algo.properties.algorithmProperties.parameterSetIdentifier) <= 1024
}

# DSA (exact match) with key size <= 1024
_is_non_compliant(algo) if {
upper(replace(algo.name, "-", "")) == "DSA"
to_number(algo.properties.algorithmProperties.parameterSetIdentifier) <= 1024
}

# ── SHA-1 helper ──────────────────────────────────────────────────────────────
# Matches SHA1 / SHA-1 but NOT SHA128, SHA256, SHA384, SHA512, etc.

_is_sha1(name) if {
normalized := upper(replace(name, "-", ""))
# Contains "SHA1" not immediately followed by a digit
regex.match(`.*SHA1([^0-9].*)?$`, normalized)
}
40 changes: 40 additions & 0 deletions etc/conforma/policy/pqc_strict.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# pqc_strict.rego
# Allowlist: ML-KEM, ML-DSA, SLH-DSA only (all parameter sets)
# Everything else is a FAIL.

package pqc.strict

import future.keywords.in
import future.keywords.contains
import future.keywords.if

pqc_allowed := {"ML-KEM", "ML-DSA", "SLH-DSA"}

crypto_algorithms := [c |
c := input.components[_]
c.type == "cryptographic-asset"
c.cryptoProperties.assetType == "algorithm"
]

violations contains v if {
c := crypto_algorithms[_]
family := c.cryptoProperties.algorithmProperties.algorithmFamily
not family in pqc_allowed

v := {
"rule": "NOT_PQC_APPROVED",
"severity": "FAIL",
"component": c.name,
"family": family,
"message": sprintf("'%v' (family: %v) is not in the approved PQC set {ML-KEM, ML-DSA, SLH-DSA}.", [c.name, family]),
}
}

compliant := count(violations) == 0

summary := {
"compliant": compliant,
"total_algorithms": count(crypto_algorithms),
"violation_count": count(violations),
"violations": violations,
}
14 changes: 14 additions & 0 deletions etc/deploy/compose/compose-conforma.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
services:
conforma:
image: quay.io/conforma/cli:latest
# Conforma binds to 127.0.0.1 inside the container; host networking is required
# for rootless podman + slirp4netns so the trustify server can reach it.
network_mode: host
volumes:
- ../../conforma:/policy:ro,Z
command: >
validate input
--server
--server-port 8085
--policy /policy/policy.yaml
restart: unless-stopped
Loading
Loading