cobalt-sbom 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cobalt_sbom-0.1.0/PKG-INFO +113 -0
- cobalt_sbom-0.1.0/README.md +91 -0
- cobalt_sbom-0.1.0/cobalt_sbom/__init__.py +2 -0
- cobalt_sbom-0.1.0/cobalt_sbom/cli.py +194 -0
- cobalt_sbom-0.1.0/cobalt_sbom/cyclonedx.py +190 -0
- cobalt_sbom-0.1.0/cobalt_sbom/detectors.py +390 -0
- cobalt_sbom-0.1.0/cobalt_sbom/signer.py +63 -0
- cobalt_sbom-0.1.0/cobalt_sbom.egg-info/PKG-INFO +113 -0
- cobalt_sbom-0.1.0/cobalt_sbom.egg-info/SOURCES.txt +13 -0
- cobalt_sbom-0.1.0/cobalt_sbom.egg-info/dependency_links.txt +1 -0
- cobalt_sbom-0.1.0/cobalt_sbom.egg-info/entry_points.txt +2 -0
- cobalt_sbom-0.1.0/cobalt_sbom.egg-info/requires.txt +3 -0
- cobalt_sbom-0.1.0/cobalt_sbom.egg-info/top_level.txt +1 -0
- cobalt_sbom-0.1.0/pyproject.toml +38 -0
- cobalt_sbom-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cobalt-sbom
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cryptographic Bill of Materials generator — CycloneDX 1.5, ML-DSA signed
|
|
5
|
+
Author-email: QreativeLab / OMEGA <dominik@qreativelab.io>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dom-omg/cobalt-sbom
|
|
8
|
+
Project-URL: Repository, https://github.com/dom-omg/cobalt-sbom
|
|
9
|
+
Keywords: cbom,sbom,pqc,cryptography,cyclonedx,quantum
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Information Technology
|
|
13
|
+
Classifier: Topic :: Security :: Cryptography
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: rich>=13.0
|
|
20
|
+
Requires-Dist: requests>=2.31
|
|
21
|
+
Requires-Dist: click>=8.1
|
|
22
|
+
|
|
23
|
+
# COBALT SBOM
|
|
24
|
+
|
|
25
|
+
**Cryptographic Bill of Materials generator — CycloneDX 1.6, ML-DSA-65 signed.**
|
|
26
|
+
|
|
27
|
+
Scan any codebase in seconds. Find every cryptographic algorithm. Know your quantum exposure.
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
30
|
+
|
|
31
|
+
- Detects 30+ crypto primitives across Python, TypeScript, JavaScript, Go, C/C++, Java, Rust
|
|
32
|
+
- Outputs a signed **CycloneDX 1.6 CBOM** (Cryptographic Bill of Materials)
|
|
33
|
+
- Scores your **quantum readiness (0-100)**
|
|
34
|
+
- Flags broken algorithms (DES, MD5, RC4) and deprecated ones (SHA-1, TLS-1.0)
|
|
35
|
+
- Integrates into CI/CD via GitHub Actions — gate PRs on quantum safety
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install cobalt-sbom
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Scan a repo
|
|
47
|
+
cobalt-sbom scan ./myrepo --output cbom.cdx.json
|
|
48
|
+
|
|
49
|
+
# Scan + sign with ML-DSA-65 (via AXIOM)
|
|
50
|
+
cobalt-sbom scan ./myrepo --output cbom.cdx.json --sign
|
|
51
|
+
|
|
52
|
+
# CI mode — fail if quantum-unsafe algorithms present
|
|
53
|
+
cobalt-sbom scan ./myrepo --ci
|
|
54
|
+
|
|
55
|
+
# Fail if quantum readiness score below 80
|
|
56
|
+
cobalt-sbom scan ./myrepo --fail-score 80
|
|
57
|
+
|
|
58
|
+
# Display an existing CBOM
|
|
59
|
+
cobalt-sbom show cbom.cdx.json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## GitHub Actions
|
|
63
|
+
|
|
64
|
+
Add to `.github/workflows/cbom.yml`:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
- name: Install cobalt-sbom
|
|
68
|
+
run: pip install cobalt-sbom
|
|
69
|
+
|
|
70
|
+
- name: Scan cryptographic assets
|
|
71
|
+
run: cobalt-sbom scan . --output cbom.cdx.json --sign --fail-score 60
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Output format
|
|
75
|
+
|
|
76
|
+
CycloneDX 1.6 JSON — compatible with all CBOM/SBOM toolchains:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"bomFormat": "CycloneDX",
|
|
81
|
+
"specVersion": "1.6",
|
|
82
|
+
"components": [
|
|
83
|
+
{
|
|
84
|
+
"type": "cryptographic-asset",
|
|
85
|
+
"name": "ML-DSA-65",
|
|
86
|
+
"cryptoProperties": {
|
|
87
|
+
"assetType": "algorithm",
|
|
88
|
+
"algorithmProperties": {
|
|
89
|
+
"primitive": "sign",
|
|
90
|
+
"nistQuantumSecurityLevel": 3,
|
|
91
|
+
"cryptoFunctions": ["sign", "verify"]
|
|
92
|
+
},
|
|
93
|
+
"quantumSafety": "quantum-safe"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"summary": {
|
|
98
|
+
"quantum_readiness_score": 72,
|
|
99
|
+
"quantum_unsafe": 15,
|
|
100
|
+
"quantum_safe": 16,
|
|
101
|
+
"broken_algorithms": ["DES", "SHA-1"]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Why CBOM
|
|
107
|
+
|
|
108
|
+
Governments (USA EO 14028, EU CRA, NIST IR 8547) are mandating cryptographic inventories. The CBOM Working Group (CycloneDX) is standardizing the format. **First movers own the toolchain.**
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
Built on [OMEGA](https://omega-sovereign.fly.dev) — sovereign intelligence stack.
|
|
113
|
+
Signing powered by [AXIOM](https://axiom-trust.fly.dev) — ML-DSA-65 post-quantum certificates.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# COBALT SBOM
|
|
2
|
+
|
|
3
|
+
**Cryptographic Bill of Materials generator — CycloneDX 1.6, ML-DSA-65 signed.**
|
|
4
|
+
|
|
5
|
+
Scan any codebase in seconds. Find every cryptographic algorithm. Know your quantum exposure.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Detects 30+ crypto primitives across Python, TypeScript, JavaScript, Go, C/C++, Java, Rust
|
|
10
|
+
- Outputs a signed **CycloneDX 1.6 CBOM** (Cryptographic Bill of Materials)
|
|
11
|
+
- Scores your **quantum readiness (0-100)**
|
|
12
|
+
- Flags broken algorithms (DES, MD5, RC4) and deprecated ones (SHA-1, TLS-1.0)
|
|
13
|
+
- Integrates into CI/CD via GitHub Actions — gate PRs on quantum safety
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install cobalt-sbom
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Scan a repo
|
|
25
|
+
cobalt-sbom scan ./myrepo --output cbom.cdx.json
|
|
26
|
+
|
|
27
|
+
# Scan + sign with ML-DSA-65 (via AXIOM)
|
|
28
|
+
cobalt-sbom scan ./myrepo --output cbom.cdx.json --sign
|
|
29
|
+
|
|
30
|
+
# CI mode — fail if quantum-unsafe algorithms present
|
|
31
|
+
cobalt-sbom scan ./myrepo --ci
|
|
32
|
+
|
|
33
|
+
# Fail if quantum readiness score below 80
|
|
34
|
+
cobalt-sbom scan ./myrepo --fail-score 80
|
|
35
|
+
|
|
36
|
+
# Display an existing CBOM
|
|
37
|
+
cobalt-sbom show cbom.cdx.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## GitHub Actions
|
|
41
|
+
|
|
42
|
+
Add to `.github/workflows/cbom.yml`:
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
- name: Install cobalt-sbom
|
|
46
|
+
run: pip install cobalt-sbom
|
|
47
|
+
|
|
48
|
+
- name: Scan cryptographic assets
|
|
49
|
+
run: cobalt-sbom scan . --output cbom.cdx.json --sign --fail-score 60
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Output format
|
|
53
|
+
|
|
54
|
+
CycloneDX 1.6 JSON — compatible with all CBOM/SBOM toolchains:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"bomFormat": "CycloneDX",
|
|
59
|
+
"specVersion": "1.6",
|
|
60
|
+
"components": [
|
|
61
|
+
{
|
|
62
|
+
"type": "cryptographic-asset",
|
|
63
|
+
"name": "ML-DSA-65",
|
|
64
|
+
"cryptoProperties": {
|
|
65
|
+
"assetType": "algorithm",
|
|
66
|
+
"algorithmProperties": {
|
|
67
|
+
"primitive": "sign",
|
|
68
|
+
"nistQuantumSecurityLevel": 3,
|
|
69
|
+
"cryptoFunctions": ["sign", "verify"]
|
|
70
|
+
},
|
|
71
|
+
"quantumSafety": "quantum-safe"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"summary": {
|
|
76
|
+
"quantum_readiness_score": 72,
|
|
77
|
+
"quantum_unsafe": 15,
|
|
78
|
+
"quantum_safe": 16,
|
|
79
|
+
"broken_algorithms": ["DES", "SHA-1"]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Why CBOM
|
|
85
|
+
|
|
86
|
+
Governments (USA EO 14028, EU CRA, NIST IR 8547) are mandating cryptographic inventories. The CBOM Working Group (CycloneDX) is standardizing the format. **First movers own the toolchain.**
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
Built on [OMEGA](https://omega-sovereign.fly.dev) — sovereign intelligence stack.
|
|
91
|
+
Signing powered by [AXIOM](https://axiom-trust.fly.dev) — ML-DSA-65 post-quantum certificates.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""COBALT SBOM CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich import box
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.text import Text
|
|
14
|
+
|
|
15
|
+
from .detectors import scan_repo, CryptoAsset
|
|
16
|
+
from .cyclonedx import build_cbom
|
|
17
|
+
from .signer import sign_cbom
|
|
18
|
+
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
QUANTUM_SAFE_ALGOS = {"ML-KEM", "ML-DSA", "FN-DSA", "SLH-DSA", "AES", "ChaCha20", "SHA-256", "SHA-384", "SHA-512", "SHA3-256", "SHA3-512", "HMAC-SHA256", "HMAC-SHA384", "HMAC-SHA512"}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _safety_color(safety: str) -> str:
|
|
25
|
+
return {"quantum-safe": "green", "quantum-unsafe": "red", "hybrid": "yellow"}.get(safety, "dim")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.group()
|
|
29
|
+
def main() -> None:
|
|
30
|
+
"""COBALT SBOM — Cryptographic Bill of Materials generator."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@main.command()
|
|
34
|
+
@click.argument("target", type=click.Path(exists=True, file_okay=False))
|
|
35
|
+
@click.option("--output", "-o", default="cbom.cdx.json", help="Output file path")
|
|
36
|
+
@click.option("--sign", is_flag=True, default=False, help="Sign with ML-DSA-65 via AXIOM")
|
|
37
|
+
@click.option("--include-tests", is_flag=True, default=False, help="Include test files")
|
|
38
|
+
@click.option("--ci", is_flag=True, default=False, help="CI mode: exit 1 if quantum-unsafe algorithms detected")
|
|
39
|
+
@click.option("--fail-score", default=0, type=int, help="CI: fail if quantum readiness score below threshold (0-100)")
|
|
40
|
+
@click.option("--json-only", is_flag=True, default=False, help="Suppress terminal output, write JSON only")
|
|
41
|
+
def scan(
|
|
42
|
+
target: str,
|
|
43
|
+
output: str,
|
|
44
|
+
sign: bool,
|
|
45
|
+
include_tests: bool,
|
|
46
|
+
ci: bool,
|
|
47
|
+
fail_score: int,
|
|
48
|
+
json_only: bool,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Scan TARGET repo and generate a CycloneDX 1.6 CBOM."""
|
|
51
|
+
target_path = Path(target)
|
|
52
|
+
target = target_path # type: ignore[assignment]
|
|
53
|
+
if not json_only:
|
|
54
|
+
console.print(Panel(
|
|
55
|
+
f"[bold cyan]COBALT SBOM[/bold cyan] Cryptographic Bill of Materials\n"
|
|
56
|
+
f"Target: [cyan]{target.resolve()}[/cyan]",
|
|
57
|
+
box=box.ROUNDED,
|
|
58
|
+
))
|
|
59
|
+
|
|
60
|
+
assets: list[CryptoAsset] = scan_repo(target, include_tests=include_tests)
|
|
61
|
+
|
|
62
|
+
if not json_only:
|
|
63
|
+
console.print(f" Scanned: [bold]{len(list(target.rglob('*')))}[/bold] files — "
|
|
64
|
+
f"[bold]{len(assets)}[/bold] crypto usages found\n")
|
|
65
|
+
|
|
66
|
+
signature = None
|
|
67
|
+
if sign:
|
|
68
|
+
if not json_only:
|
|
69
|
+
console.print(" Signing with ML-DSA-65 via AXIOM...", end=" ")
|
|
70
|
+
signature = sign_cbom({})
|
|
71
|
+
if not json_only:
|
|
72
|
+
console.print("[green]✓[/green]" if signature else "[yellow]⚠ AXIOM unreachable, unsigned[/yellow]")
|
|
73
|
+
|
|
74
|
+
cbom = build_cbom(assets, target, signature)
|
|
75
|
+
out_path = Path(output)
|
|
76
|
+
out_path.write_text(json.dumps(cbom, indent=2))
|
|
77
|
+
|
|
78
|
+
if not json_only:
|
|
79
|
+
_print_summary(cbom, assets)
|
|
80
|
+
|
|
81
|
+
if not json_only:
|
|
82
|
+
console.print(f"\n Output: [cyan]{out_path.resolve()}[/cyan]")
|
|
83
|
+
|
|
84
|
+
score: int = cbom["summary"]["quantum_readiness_score"]
|
|
85
|
+
|
|
86
|
+
if ci:
|
|
87
|
+
unsafe_algos = cbom["summary"]["quantum_unsafe"]
|
|
88
|
+
if unsafe_algos > 0:
|
|
89
|
+
console.print(f"\n[red]CI FAIL[/red] — {unsafe_algos} quantum-unsafe algorithm(s) detected", err=True)
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
|
|
92
|
+
if fail_score and score < fail_score:
|
|
93
|
+
console.print(f"\n[red]CI FAIL[/red] — quantum readiness score {score} < threshold {fail_score}", err=True)
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@main.command()
|
|
98
|
+
@click.argument("cbom_file", type=click.Path(exists=True))
|
|
99
|
+
def show(cbom_file: str) -> None:
|
|
100
|
+
"""Display a CBOM file in a human-readable table."""
|
|
101
|
+
cbom = json.loads(Path(cbom_file).read_text())
|
|
102
|
+
assets_raw = cbom.get("components", [])
|
|
103
|
+
|
|
104
|
+
table = Table(show_header=True, header_style="bold cyan", box=box.SIMPLE_HEAVY)
|
|
105
|
+
table.add_column("Algorithm", style="bold")
|
|
106
|
+
table.add_column("Type")
|
|
107
|
+
table.add_column("Primitive")
|
|
108
|
+
table.add_column("Library")
|
|
109
|
+
table.add_column("Quantum")
|
|
110
|
+
table.add_column("Bits")
|
|
111
|
+
table.add_column("NIST PQC Level")
|
|
112
|
+
|
|
113
|
+
for comp in assets_raw:
|
|
114
|
+
cp = comp.get("cryptoProperties", {})
|
|
115
|
+
ap = cp.get("algorithmProperties", {})
|
|
116
|
+
safety = cp.get("quantumSafety", "unknown")
|
|
117
|
+
color = _safety_color(safety)
|
|
118
|
+
table.add_row(
|
|
119
|
+
comp.get("name", ""),
|
|
120
|
+
cp.get("assetType", ""),
|
|
121
|
+
ap.get("primitive", ""),
|
|
122
|
+
ap.get("implementationPlatform", ""),
|
|
123
|
+
f"[{color}]{safety}[/{color}]",
|
|
124
|
+
str(ap.get("classicalSecurityLevel", "—")),
|
|
125
|
+
str(ap.get("nistQuantumSecurityLevel", "—")),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
console.print(table)
|
|
129
|
+
|
|
130
|
+
summary = cbom.get("summary", {})
|
|
131
|
+
score = summary.get("quantum_readiness_score", 0)
|
|
132
|
+
score_color = "green" if score >= 80 else ("yellow" if score >= 50 else "red")
|
|
133
|
+
console.print(f"\nQuantum Readiness Score: [{score_color}]{score}/100[/{score_color}]")
|
|
134
|
+
|
|
135
|
+
broken = summary.get("broken_algorithms", [])
|
|
136
|
+
if broken:
|
|
137
|
+
console.print(f"[red]Broken algorithms (immediate risk):[/red] {', '.join(broken)}")
|
|
138
|
+
|
|
139
|
+
deprecated = summary.get("deprecated_algorithms", [])
|
|
140
|
+
if deprecated:
|
|
141
|
+
console.print(f"[yellow]Deprecated algorithms:[/yellow] {', '.join(deprecated)}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _print_summary(cbom: dict, assets: list[CryptoAsset]) -> None:
|
|
145
|
+
summary = cbom["summary"]
|
|
146
|
+
score: int = summary["quantum_readiness_score"]
|
|
147
|
+
score_color = "green" if score >= 80 else ("yellow" if score >= 50 else "red")
|
|
148
|
+
|
|
149
|
+
table = Table(show_header=True, header_style="bold", box=box.SIMPLE_HEAVY)
|
|
150
|
+
table.add_column("Algorithm", style="bold")
|
|
151
|
+
table.add_column("Primitive")
|
|
152
|
+
table.add_column("Library")
|
|
153
|
+
table.add_column("Quantum Safety")
|
|
154
|
+
table.add_column("Classical Bits")
|
|
155
|
+
table.add_column("NIST PQC Level")
|
|
156
|
+
table.add_column("Occurrences")
|
|
157
|
+
|
|
158
|
+
from collections import Counter
|
|
159
|
+
counts: Counter[str] = Counter(a.name for a in assets)
|
|
160
|
+
|
|
161
|
+
seen: set[str] = set()
|
|
162
|
+
for a in assets:
|
|
163
|
+
if a.name in seen:
|
|
164
|
+
continue
|
|
165
|
+
seen.add(a.name)
|
|
166
|
+
color = _safety_color(a.quantum_safety)
|
|
167
|
+
table.add_row(
|
|
168
|
+
a.name,
|
|
169
|
+
a.primitive,
|
|
170
|
+
a.library,
|
|
171
|
+
f"[{color}]{a.quantum_safety}[/{color}]",
|
|
172
|
+
str(a.classical_bits) if a.classical_bits else "—",
|
|
173
|
+
str(a.nist_quantum_level) if a.nist_quantum_level else "—",
|
|
174
|
+
str(counts[a.name]),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
console.print(table)
|
|
178
|
+
|
|
179
|
+
broken = summary.get("broken_algorithms", [])
|
|
180
|
+
deprecated = summary.get("deprecated_algorithms", [])
|
|
181
|
+
|
|
182
|
+
score_text = Text(f" Quantum Readiness Score: {score}/100", style=f"bold {score_color}")
|
|
183
|
+
console.print(score_text)
|
|
184
|
+
|
|
185
|
+
if broken:
|
|
186
|
+
console.print(f" [bold red]CRITICAL — Broken algorithms:[/bold red] {', '.join(broken)}")
|
|
187
|
+
if deprecated:
|
|
188
|
+
console.print(f" [bold yellow]WARNING — Deprecated algorithms:[/bold yellow] {', '.join(deprecated)}")
|
|
189
|
+
|
|
190
|
+
console.print(
|
|
191
|
+
f"\n Quantum-safe: [green]{summary['quantum_safe']}[/green] "
|
|
192
|
+
f"Quantum-unsafe: [red]{summary['quantum_unsafe']}[/red] "
|
|
193
|
+
f"Total unique: [bold]{summary['unique_algorithms']}[/bold]"
|
|
194
|
+
)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""CycloneDX 1.5 CBOM serializer — spec-compliant."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
import uuid
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .detectors import CryptoAsset
|
|
13
|
+
|
|
14
|
+
# CycloneDX 1.5 spec: cryptoFunctions valid values
|
|
15
|
+
_CRYPTO_FUNCTIONS: dict[str, list[str]] = {
|
|
16
|
+
"ae": ["encrypt", "decrypt"],
|
|
17
|
+
"hash": ["digest"],
|
|
18
|
+
"ecc": ["sign", "verify"],
|
|
19
|
+
"dsa": ["sign", "verify"],
|
|
20
|
+
"rsa": ["sign", "verify", "encrypt", "decrypt"],
|
|
21
|
+
"ecDH": ["keyDerive"],
|
|
22
|
+
"ff-dh": ["keyDerive"],
|
|
23
|
+
"post-quantum": ["encapsulate", "decapsulate", "sign", "verify"],
|
|
24
|
+
"mac": ["generate", "verify"],
|
|
25
|
+
"kdf": ["derive"],
|
|
26
|
+
"drbg": ["generate"],
|
|
27
|
+
"stream-cipher": ["encrypt", "decrypt"],
|
|
28
|
+
"other": [],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# CycloneDX 1.5 spec valid values for executionEnvironment
|
|
32
|
+
_EXEC_ENV = "software-plain-ram"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _aggregate(assets: list[CryptoAsset]) -> list[dict]:
|
|
36
|
+
"""Group by algorithm name, aggregate all occurrences and libraries."""
|
|
37
|
+
groups: dict[str, dict] = {}
|
|
38
|
+
|
|
39
|
+
for a in assets:
|
|
40
|
+
key = a.name
|
|
41
|
+
if key not in groups:
|
|
42
|
+
groups[key] = {
|
|
43
|
+
"asset": a,
|
|
44
|
+
"occurrences": [],
|
|
45
|
+
"libraries": set(),
|
|
46
|
+
}
|
|
47
|
+
groups[key]["occurrences"].append({
|
|
48
|
+
"location": a.location,
|
|
49
|
+
"line": a.line,
|
|
50
|
+
"symbol": a.evidence[:100] if a.evidence else "",
|
|
51
|
+
})
|
|
52
|
+
if a.library and a.library != "unknown":
|
|
53
|
+
groups[key]["libraries"].add(a.library)
|
|
54
|
+
|
|
55
|
+
return list(groups.values())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _component(group: dict, idx: int) -> dict:
|
|
59
|
+
a: CryptoAsset = group["asset"]
|
|
60
|
+
occurrences: list[dict] = group["occurrences"]
|
|
61
|
+
libraries: set[str] = group["libraries"]
|
|
62
|
+
|
|
63
|
+
crypto_funcs = _CRYPTO_FUNCTIONS.get(a.primitive, [])
|
|
64
|
+
# Post-quantum: distinguish KEM vs signature by name
|
|
65
|
+
if a.primitive == "post-quantum":
|
|
66
|
+
name_up = a.name.upper()
|
|
67
|
+
if "KEM" in name_up or "KYBER" in name_up:
|
|
68
|
+
crypto_funcs = ["encapsulate", "decapsulate"]
|
|
69
|
+
else:
|
|
70
|
+
crypto_funcs = ["sign", "verify"]
|
|
71
|
+
|
|
72
|
+
algo: dict = {
|
|
73
|
+
"primitive": a.primitive,
|
|
74
|
+
"executionEnvironment": _EXEC_ENV,
|
|
75
|
+
"cryptoFunctions": crypto_funcs,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if a.param_set:
|
|
79
|
+
algo["parameterSetIdentifier"] = a.param_set
|
|
80
|
+
if a.curve:
|
|
81
|
+
algo["curve"] = a.curve
|
|
82
|
+
if a.mode:
|
|
83
|
+
algo["mode"] = a.mode
|
|
84
|
+
if a.classical_bits:
|
|
85
|
+
algo["classicalSecurityLevel"] = a.classical_bits
|
|
86
|
+
if a.nist_quantum_level:
|
|
87
|
+
algo["nistQuantumSecurityLevel"] = a.nist_quantum_level
|
|
88
|
+
|
|
89
|
+
crypto_props: dict = {
|
|
90
|
+
"assetType": a.asset_type,
|
|
91
|
+
"algorithmProperties": algo,
|
|
92
|
+
}
|
|
93
|
+
if a.oid:
|
|
94
|
+
crypto_props["oid"] = a.oid
|
|
95
|
+
|
|
96
|
+
# Custom properties: quantum safety + library (not in spec, use properties array)
|
|
97
|
+
props = [
|
|
98
|
+
{"name": "cobalt:quantumSafety", "value": a.quantum_safety},
|
|
99
|
+
]
|
|
100
|
+
if libraries:
|
|
101
|
+
props.append({"name": "cobalt:library", "value": ", ".join(sorted(libraries))})
|
|
102
|
+
props.append({"name": "cobalt:occurrenceCount", "value": str(len(occurrences))})
|
|
103
|
+
|
|
104
|
+
comp: dict = {
|
|
105
|
+
"type": "cryptographic-asset",
|
|
106
|
+
"bom-ref": f"crypto-{idx}",
|
|
107
|
+
"name": a.name,
|
|
108
|
+
"cryptoProperties": crypto_props,
|
|
109
|
+
# CycloneDX 1.5 spec: occurrences go in evidence.occurrences
|
|
110
|
+
"evidence": {
|
|
111
|
+
"occurrences": occurrences,
|
|
112
|
+
},
|
|
113
|
+
"properties": props,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return comp
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _metadata(root: Path) -> dict:
|
|
120
|
+
return {
|
|
121
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
122
|
+
"tools": [
|
|
123
|
+
{
|
|
124
|
+
"type": "application",
|
|
125
|
+
"vendor": "OMEGA / QreativeLab",
|
|
126
|
+
"name": "COBALT SBOM",
|
|
127
|
+
"version": "0.1.0",
|
|
128
|
+
"externalReferences": [
|
|
129
|
+
{"type": "website", "url": "https://axiom-trust.fly.dev"},
|
|
130
|
+
],
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
"component": {
|
|
134
|
+
"type": "library",
|
|
135
|
+
"name": root.name,
|
|
136
|
+
"bom-ref": "root-component",
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def build_cbom(
|
|
142
|
+
assets: list[CryptoAsset],
|
|
143
|
+
root: Path,
|
|
144
|
+
signature: dict | None = None,
|
|
145
|
+
) -> dict:
|
|
146
|
+
groups = _aggregate(assets)
|
|
147
|
+
serial = str(uuid.uuid4())
|
|
148
|
+
|
|
149
|
+
cbom: dict = {
|
|
150
|
+
"bomFormat": "CycloneDX",
|
|
151
|
+
"specVersion": "1.5",
|
|
152
|
+
"serialNumber": f"urn:uuid:{serial}",
|
|
153
|
+
"version": 1,
|
|
154
|
+
"metadata": _metadata(root),
|
|
155
|
+
"components": [_component(g, i) for i, g in enumerate(groups)],
|
|
156
|
+
"summary": _summary(assets),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if signature:
|
|
160
|
+
cbom["signature"] = signature
|
|
161
|
+
|
|
162
|
+
return cbom
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _summary(assets: list[CryptoAsset]) -> dict:
|
|
166
|
+
unsafe = {a.name for a in assets if a.quantum_safety == "quantum-unsafe"}
|
|
167
|
+
safe = {a.name for a in assets if a.quantum_safety == "quantum-safe"}
|
|
168
|
+
unique_names = {a.name for a in assets}
|
|
169
|
+
broken = {a.name for a in assets if 0 < a.classical_bits < 112}
|
|
170
|
+
deprecated = {"MD5", "SHA-1", "DES", "RC4", "3DES", "TLS-1.0", "TLS-1.2"}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
"total_findings": len(assets),
|
|
174
|
+
"unique_algorithms": len(unique_names),
|
|
175
|
+
"quantum_unsafe": len(unsafe),
|
|
176
|
+
"quantum_safe": len(safe),
|
|
177
|
+
"broken_algorithms": sorted(broken),
|
|
178
|
+
"deprecated_algorithms": sorted(deprecated & unique_names),
|
|
179
|
+
"quantum_readiness_score": _readiness_score(assets),
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _readiness_score(assets: list[CryptoAsset]) -> int:
|
|
184
|
+
if not assets:
|
|
185
|
+
return 100
|
|
186
|
+
total = len(assets)
|
|
187
|
+
unsafe = sum(1 for a in assets if a.quantum_safety == "quantum-unsafe")
|
|
188
|
+
broken = sum(1 for a in assets if 0 < a.classical_bits < 112)
|
|
189
|
+
score = max(0, 100 - int((unsafe / total) * 70) - int((broken / total) * 30))
|
|
190
|
+
return score
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""Multi-language cryptographic asset detectors for CBOM generation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import re
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
AssetType = Literal["algorithm", "protocol", "related-crypto-material", "certificate"]
|
|
10
|
+
# CycloneDX 1.5 CBOM primitive values
|
|
11
|
+
Primitive = Literal[
|
|
12
|
+
"ae", "dsa", "ecc", "ecDH", "ecMQV", "factoring", "ff-dh",
|
|
13
|
+
"hash", "integer-factorization", "kdf", "lattice", "mac",
|
|
14
|
+
"nestedEncryption", "other", "pke", "post-quantum", "rsa",
|
|
15
|
+
"stream-cipher", "xor", "drbg",
|
|
16
|
+
]
|
|
17
|
+
QuantumSafety = Literal["quantum-safe", "quantum-unsafe", "hybrid", "unknown"]
|
|
18
|
+
|
|
19
|
+
NIST_QUANTUM_LEVELS: dict[str, int] = {
|
|
20
|
+
"ml-kem-512": 1, "kyber512": 1,
|
|
21
|
+
"ml-kem-768": 3, "kyber768": 3,
|
|
22
|
+
"ml-kem-1024": 5, "kyber1024": 5,
|
|
23
|
+
"ml-dsa-44": 2, "dilithium2": 2,
|
|
24
|
+
"ml-dsa-65": 3, "dilithium3": 3,
|
|
25
|
+
"ml-dsa-87": 5, "dilithium5": 5,
|
|
26
|
+
"slh-dsa-shake-128s": 1,
|
|
27
|
+
"slh-dsa-shake-192s": 3,
|
|
28
|
+
"slh-dsa-shake-256s": 5,
|
|
29
|
+
"sphincs+-shake-128s": 1,
|
|
30
|
+
"fn-dsa-512": 1, "falcon512": 1,
|
|
31
|
+
"fn-dsa-1024": 5, "falcon1024": 5,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
CLASSICAL_SECURITY_BITS: dict[str, int] = {
|
|
35
|
+
"rsa-1024": 80, "rsa-2048": 112, "rsa-3072": 128, "rsa-4096": 140,
|
|
36
|
+
"ecdsa-p-256": 128, "ecdsa-p-384": 192, "ecdsa-p-521": 260,
|
|
37
|
+
"ecdsa-p256": 128, "ecdsa-p384": 192, "ecdsa-p521": 260,
|
|
38
|
+
"ecdsa-secp256k1": 128,
|
|
39
|
+
"ed25519": 128, "ed448": 224,
|
|
40
|
+
"ecdh-p-256": 128, "ecdh-p-384": 192, "ecdh-p-521": 260,
|
|
41
|
+
"ecdh-p256": 128, "ecdh-p384": 192,
|
|
42
|
+
"x25519": 128, "x448": 224,
|
|
43
|
+
"aes-128": 128, "aes-192": 192, "aes-256": 256,
|
|
44
|
+
"chacha20": 256, "chacha20-poly1305": 256,
|
|
45
|
+
"sha-1": 80, "sha-256": 128, "sha-384": 192, "sha-512": 256,
|
|
46
|
+
"sha3-256": 128, "sha3-512": 256,
|
|
47
|
+
"hmac-sha256": 128, "hmac-sha384": 192, "hmac-sha512": 256,
|
|
48
|
+
"3des": 112, "des": 56, "rc4": 0, "md5": 0,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class CryptoAsset:
|
|
54
|
+
name: str
|
|
55
|
+
asset_type: AssetType
|
|
56
|
+
primitive: Primitive
|
|
57
|
+
location: str
|
|
58
|
+
line: int
|
|
59
|
+
library: str
|
|
60
|
+
mode: str = ""
|
|
61
|
+
curve: str = ""
|
|
62
|
+
param_set: str = ""
|
|
63
|
+
classical_bits: int = 0
|
|
64
|
+
nist_quantum_level: int = 0
|
|
65
|
+
quantum_safety: QuantumSafety = "unknown"
|
|
66
|
+
oid: str = ""
|
|
67
|
+
evidence: str = ""
|
|
68
|
+
|
|
69
|
+
def normalize_name(self) -> str:
|
|
70
|
+
return self.name.lower().replace("_", "-")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
ALGO_PATTERNS: list[dict] = [
|
|
74
|
+
# ── RSA ──────────────────────────────────────────────────────────────────
|
|
75
|
+
{"pattern": r"RSA[_\-]?(\d{3,4})|rsa\.generate\s*\(\s*(\d{3,4})|genrsa.*?(\d{3,4})|RSA\.generate\s*\((\d{3,4})",
|
|
76
|
+
"name_fn": lambda m: f"RSA-{m.group(1) or m.group(2) or m.group(3) or m.group(4)}",
|
|
77
|
+
"primitive": "rsa", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
78
|
+
"oid": "1.2.840.113549.1.1.1"},
|
|
79
|
+
|
|
80
|
+
# ── ECDSA ─────────────────────────────────────────────────────────────────
|
|
81
|
+
{"pattern": r"(?:ECDSA|ecdsa)[_\-]?(P-?256|P-?384|P-?521|secp256k1|secp384r1|secp521r1)?|EC_KEY_new|ec\.generate_private_key\s*\(\s*(?:ec\.\s*)?(\w+)",
|
|
82
|
+
"name_fn": lambda m: f"ECDSA-{(m.group(1) or m.group(2) or 'P-256').upper()}",
|
|
83
|
+
"primitive": "ecc", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
84
|
+
"oid": "1.2.840.10045.4.3.2"},
|
|
85
|
+
|
|
86
|
+
# ── ECDH ──────────────────────────────────────────────────────────────────
|
|
87
|
+
{"pattern": r"(?:ECDH|ecdh)[_\-]?(P-?256|P-?384|P-?521|X25519|X448)?|ec\.ECDH\(",
|
|
88
|
+
"name_fn": lambda m: f"ECDH-{(m.group(1) or 'P-256').upper()}",
|
|
89
|
+
"primitive": "ecDH", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
90
|
+
"oid": "1.3.132.1.12"},
|
|
91
|
+
|
|
92
|
+
# ── Ed25519 / Ed448 ───────────────────────────────────────────────────────
|
|
93
|
+
{"pattern": r"Ed25519|ed25519|edwards25519|ed25519_dalek|ed25519-dalek",
|
|
94
|
+
"name_fn": lambda m: "Ed25519",
|
|
95
|
+
"primitive": "ecc", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
96
|
+
"oid": "1.3.101.112"},
|
|
97
|
+
|
|
98
|
+
{"pattern": r"Ed448|ed448",
|
|
99
|
+
"name_fn": lambda m: "Ed448",
|
|
100
|
+
"primitive": "ecc", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
101
|
+
"oid": "1.3.101.113"},
|
|
102
|
+
|
|
103
|
+
# ── X25519 / X448 ─────────────────────────────────────────────────────────
|
|
104
|
+
{"pattern": r"X25519|x25519|curve25519|Curve25519",
|
|
105
|
+
"name_fn": lambda m: "X25519",
|
|
106
|
+
"primitive": "ecDH", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
107
|
+
"oid": "1.3.101.110"},
|
|
108
|
+
|
|
109
|
+
{"pattern": r"X448|x448",
|
|
110
|
+
"name_fn": lambda m: "X448",
|
|
111
|
+
"primitive": "ecDH", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
112
|
+
"oid": "1.3.101.111"},
|
|
113
|
+
|
|
114
|
+
# ── AES ───────────────────────────────────────────────────────────────────
|
|
115
|
+
{"pattern": r"AES[_\-]?(128|192|256)?[_\-]?(GCM|CBC|CTR|CCM|SIV|ECB|CFB|OFB|XTS)?|AES\.new\(|EVP_aes_(\d+)_(\w+)|createCipheriv\(['\"]aes[_\-](\d+)[_\-](\w+)",
|
|
116
|
+
"name_fn": lambda m: f"AES-{m.group(1) or m.group(3) or m.group(5) or '256'}-{(m.group(2) or m.group(4) or m.group(6) or 'GCM').upper()}",
|
|
117
|
+
"primitive": "ae", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
118
|
+
"oid": "2.16.840.1.101.3.4.1"},
|
|
119
|
+
|
|
120
|
+
# ── ChaCha20-Poly1305 ─────────────────────────────────────────────────────
|
|
121
|
+
{"pattern": r"ChaCha20[_\-]?Poly1305|chacha20[_\-]poly1305|chacha20_poly1305",
|
|
122
|
+
"name_fn": lambda m: "ChaCha20-Poly1305",
|
|
123
|
+
"primitive": "ae", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
124
|
+
"oid": "1.2.840.113549.1.9.16.3.18"},
|
|
125
|
+
|
|
126
|
+
# ── SHA ───────────────────────────────────────────────────────────────────
|
|
127
|
+
{"pattern": r"SHA[_\-]?1\b|sha1\b|SHA1\b|hashlib\.sha1",
|
|
128
|
+
"name_fn": lambda m: "SHA-1",
|
|
129
|
+
"primitive": "hash", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
130
|
+
"oid": "1.3.14.3.2.26"},
|
|
131
|
+
|
|
132
|
+
{"pattern": r"SHA[_\-]?256\b|sha256\b|SHA256\b|hashlib\.sha256|EVP_sha256",
|
|
133
|
+
"name_fn": lambda m: "SHA-256",
|
|
134
|
+
"primitive": "hash", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
135
|
+
"oid": "2.16.840.1.101.3.4.2.1"},
|
|
136
|
+
|
|
137
|
+
{"pattern": r"SHA[_\-]?384\b|sha384\b|SHA384\b|hashlib\.sha384",
|
|
138
|
+
"name_fn": lambda m: "SHA-384",
|
|
139
|
+
"primitive": "hash", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
140
|
+
"oid": "2.16.840.1.101.3.4.2.2"},
|
|
141
|
+
|
|
142
|
+
{"pattern": r"SHA[_\-]?512\b|sha512\b|SHA512\b|hashlib\.sha512",
|
|
143
|
+
"name_fn": lambda m: "SHA-512",
|
|
144
|
+
"primitive": "hash", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
145
|
+
"oid": "2.16.840.1.101.3.4.2.3"},
|
|
146
|
+
|
|
147
|
+
{"pattern": r"SHA3[_\-]?256\b|sha3_256\b|SHA3_256\b|sha3[_\-]256",
|
|
148
|
+
"name_fn": lambda m: "SHA3-256",
|
|
149
|
+
"primitive": "hash", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
150
|
+
"oid": "2.16.840.1.101.3.4.2.8"},
|
|
151
|
+
|
|
152
|
+
{"pattern": r"SHA3[_\-]?512\b|sha3_512\b|SHA3_512\b|sha3[_\-]512",
|
|
153
|
+
"name_fn": lambda m: "SHA3-512",
|
|
154
|
+
"primitive": "hash", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
155
|
+
"oid": "2.16.840.1.101.3.4.2.10"},
|
|
156
|
+
|
|
157
|
+
{"pattern": r"\bMD5\b|hashlib\.md5\b|EVP_md5\b",
|
|
158
|
+
"name_fn": lambda m: "MD5",
|
|
159
|
+
"primitive": "hash", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
160
|
+
"oid": "1.2.840.113549.2.5"},
|
|
161
|
+
|
|
162
|
+
# ── HMAC ──────────────────────────────────────────────────────────────────
|
|
163
|
+
{"pattern": r"HMAC[_\-]?SHA[_\-]?(256|384|512)|hmac\.new\(|createHmac\(['\"]sha(256|384|512)",
|
|
164
|
+
"name_fn": lambda m: f"HMAC-SHA{m.group(1) or m.group(2) or '256'}",
|
|
165
|
+
"primitive": "mac", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
166
|
+
"oid": "1.2.840.113549.2.9"},
|
|
167
|
+
|
|
168
|
+
# ── 3DES / DES ────────────────────────────────────────────────────────────
|
|
169
|
+
{"pattern": r"3DES|TripleDES|DES3|des3\b|EVP_des_ede3",
|
|
170
|
+
"name_fn": lambda m: "3DES",
|
|
171
|
+
"primitive": "ae", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
172
|
+
"oid": "1.2.840.113549.3.7"},
|
|
173
|
+
|
|
174
|
+
{"pattern": r"\bDES\b(?!3)|EVP_des_cbc\b",
|
|
175
|
+
"name_fn": lambda m: "DES",
|
|
176
|
+
"primitive": "ae", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
177
|
+
"oid": "1.3.14.3.2.7"},
|
|
178
|
+
|
|
179
|
+
# ── RC4 ───────────────────────────────────────────────────────────────────
|
|
180
|
+
{"pattern": r"\bRC4\b|arcfour|EVP_rc4\b",
|
|
181
|
+
"name_fn": lambda m: "RC4",
|
|
182
|
+
"primitive": "stream-cipher", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
183
|
+
"oid": "1.2.840.113549.3.4"},
|
|
184
|
+
|
|
185
|
+
# ── DH ────────────────────────────────────────────────────────────────────
|
|
186
|
+
{"pattern": r"\bDH\b|DHE\b|dh\.generate_parameters|EVP_PKEY_DH|DiffieHellman\(",
|
|
187
|
+
"name_fn": lambda m: "DH",
|
|
188
|
+
"primitive": "ff-dh", "asset_type": "algorithm", "quantum_safety": "quantum-unsafe",
|
|
189
|
+
"oid": "1.2.840.113549.1.3.1"},
|
|
190
|
+
|
|
191
|
+
# ── PQC: ML-KEM ───────────────────────────────────────────────────────────
|
|
192
|
+
{"pattern": r"ML[_\-]KEM[_\-]?(512|768|1024)?|Kyber(?:512|768|1024)?|kyber(?:512|768|1024)?|ml_kem|mlkem",
|
|
193
|
+
"name_fn": lambda m: f"ML-KEM-{m.group(1) or '768'}",
|
|
194
|
+
"primitive": "post-quantum", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
195
|
+
"oid": "1.3.6.1.4.1.22554.5.6.1"},
|
|
196
|
+
|
|
197
|
+
# ── PQC: ML-DSA ───────────────────────────────────────────────────────────
|
|
198
|
+
{"pattern": r"ML[_\-]DSA[_\-]?(44|65|87)?|Dilithium(?:2|3|5)?|dilithium(?:2|3|5)?|ml_dsa|mldsa",
|
|
199
|
+
"name_fn": lambda m: f"ML-DSA-{m.group(1) or '65'}",
|
|
200
|
+
"primitive": "post-quantum", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
201
|
+
"oid": "1.3.6.1.4.1.22554.5.6.2"},
|
|
202
|
+
|
|
203
|
+
# ── PQC: FN-DSA ───────────────────────────────────────────────────────────
|
|
204
|
+
{"pattern": r"FN[_\-]DSA[_\-]?(512|1024)?|Falcon(?:512|1024)?|falcon(?:512|1024)?|fn_dsa",
|
|
205
|
+
"name_fn": lambda m: f"FN-DSA-{m.group(1) or '512'}",
|
|
206
|
+
"primitive": "post-quantum", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
207
|
+
"oid": "1.3.6.1.4.1.22554.5.6.3"},
|
|
208
|
+
|
|
209
|
+
# ── PQC: SLH-DSA ──────────────────────────────────────────────────────────
|
|
210
|
+
{"pattern": r"SLH[_\-]DSA|SPHINCS\+?|sphincs_plus|slh_dsa",
|
|
211
|
+
"name_fn": lambda m: "SLH-DSA-SHAKE-128s",
|
|
212
|
+
"primitive": "post-quantum", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
213
|
+
"oid": "1.3.6.1.4.1.22554.5.6.4"},
|
|
214
|
+
|
|
215
|
+
# ── TLS ───────────────────────────────────────────────────────────────────
|
|
216
|
+
{"pattern": r"TLS[_\s]?1[_\.]?0\b|SSL[_\s]?3\b|TLSv1\b|SSLv3\b",
|
|
217
|
+
"name_fn": lambda m: "TLS-1.0",
|
|
218
|
+
"primitive": "other", "asset_type": "protocol", "quantum_safety": "quantum-unsafe",
|
|
219
|
+
"oid": ""},
|
|
220
|
+
|
|
221
|
+
{"pattern": r"TLS[_\s]?1[_\.]?2\b|TLSv1_2\b|TLS\.TLSv1_2",
|
|
222
|
+
"name_fn": lambda m: "TLS-1.2",
|
|
223
|
+
"primitive": "other", "asset_type": "protocol", "quantum_safety": "quantum-unsafe",
|
|
224
|
+
"oid": ""},
|
|
225
|
+
|
|
226
|
+
{"pattern": r"TLS[_\s]?1[_\.]?3\b|TLSv1_3\b|ssl\.PROTOCOL_TLS",
|
|
227
|
+
"name_fn": lambda m: "TLS-1.3",
|
|
228
|
+
"primitive": "other", "asset_type": "protocol", "quantum_safety": "quantum-unsafe",
|
|
229
|
+
"oid": ""},
|
|
230
|
+
|
|
231
|
+
# ── JWT ───────────────────────────────────────────────────────────────────
|
|
232
|
+
{"pattern": r"['\"]HS256['\"]|['\"]RS256['\"]|['\"]ES256['\"]|['\"]PS256['\"]|jwt\.sign\(|jwt\.verify\(|jose\.",
|
|
233
|
+
"name_fn": lambda m: f"JWT-{m.group(0).strip(chr(39)).strip(chr(34)) if m.group(0).startswith((chr(39), chr(34))) else 'RS256'}",
|
|
234
|
+
"primitive": "other", "asset_type": "protocol", "quantum_safety": "quantum-unsafe",
|
|
235
|
+
"oid": ""},
|
|
236
|
+
|
|
237
|
+
# ── KDF ───────────────────────────────────────────────────────────────────
|
|
238
|
+
{"pattern": r"PBKDF2|pbkdf2_hmac|bcrypt|argon2|scrypt",
|
|
239
|
+
"name_fn": lambda m: m.group(0).upper().replace("_HMAC", ""),
|
|
240
|
+
"primitive": "kdf", "asset_type": "algorithm", "quantum_safety": "quantum-safe",
|
|
241
|
+
"oid": "1.2.840.113549.1.5.12"},
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
LIBRARY_PATTERNS: list[tuple[re.Pattern, str]] = [
|
|
245
|
+
(re.compile(r"#include\s+[<\"]openssl/|import\s+openssl"), "openssl"),
|
|
246
|
+
(re.compile(r"from\s+cryptography|import\s+cryptography"), "cryptography-py"),
|
|
247
|
+
(re.compile(r"require\(['\"]crypto['\"]\)|from\s+['\"]crypto['\"]|import\s+crypto\s+from"), "node-crypto"),
|
|
248
|
+
(re.compile(r"['\"]jose['\"]|['\"]jsonwebtoken['\"]|node-jose"), "jose"),
|
|
249
|
+
(re.compile(r"#include\s+[<\"]wolfssl/|import\s+wolfssl"), "wolfssl"),
|
|
250
|
+
(re.compile(r"mbedtls/|psa/crypto"), "mbedtls"),
|
|
251
|
+
(re.compile(r"liboqs|oqs\."), "liboqs"),
|
|
252
|
+
(re.compile(r"use\s+ring::|extern\s+crate\s+ring"), "ring-rust"),
|
|
253
|
+
(re.compile(r"ed25519[_\-]dalek|p256::|rsa::"), "rust-crypto"),
|
|
254
|
+
(re.compile(r"BoringSSL|boringssl"), "boringssl"),
|
|
255
|
+
(re.compile(r"import\s+javax\.crypto|import\s+java\.security"), "java-jce"),
|
|
256
|
+
(re.compile(r"\"crypto/rsa\"|\"crypto/ecdsa\"|\"x/crypto"), "go-stdlib"),
|
|
257
|
+
(re.compile(r"tweetnacl|libsodium|sodium\.random"), "libsodium"),
|
|
258
|
+
(re.compile(r"org\.bouncycastle"), "bouncycastle"),
|
|
259
|
+
(re.compile(r"aws-lc|aws_lc"), "aws-lc"),
|
|
260
|
+
(re.compile(r"nss\.|mozilla.*nss"), "nss"),
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
LANG_EXTENSIONS: dict[str, list[str]] = {
|
|
264
|
+
"python": [".py"],
|
|
265
|
+
"typescript": [".ts", ".tsx"],
|
|
266
|
+
"javascript": [".js", ".jsx", ".mjs", ".cjs"],
|
|
267
|
+
"go": [".go"],
|
|
268
|
+
"c": [".c", ".h"],
|
|
269
|
+
"cpp": [".cpp", ".cc", ".cxx", ".hpp", ".hh"],
|
|
270
|
+
"java": [".java"],
|
|
271
|
+
"rust": [".rs"],
|
|
272
|
+
"kotlin": [".kt"],
|
|
273
|
+
"swift": [".swift"],
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
SKIP_DIRS: set[str] = {
|
|
277
|
+
"node_modules", ".git", ".next", "dist", "build", "__pycache__",
|
|
278
|
+
".venv", "venv", "vendor", "target", ".cargo", "coverage",
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _detect_libraries(source: str) -> list[str]:
|
|
283
|
+
"""Return all detected crypto libraries in a source file."""
|
|
284
|
+
found = [lib for pat, lib in LIBRARY_PATTERNS if pat.search(source)]
|
|
285
|
+
return found if found else []
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _extract_curve(name: str) -> str:
|
|
289
|
+
n = name.upper()
|
|
290
|
+
if "P-256" in n or "P256" in n or "SECP256R1" in n: return "P-256"
|
|
291
|
+
if "P-384" in n or "P384" in n or "SECP384R1" in n: return "P-384"
|
|
292
|
+
if "P-521" in n or "P521" in n or "SECP521R1" in n: return "P-521"
|
|
293
|
+
if "SECP256K1" in n: return "secp256k1"
|
|
294
|
+
if "25519" in name: return "Curve25519"
|
|
295
|
+
if "448" in name: return "Curve448"
|
|
296
|
+
return ""
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _extract_param_set(name: str) -> str:
|
|
300
|
+
"""Extract key/parameter size from algorithm name."""
|
|
301
|
+
import re as _re
|
|
302
|
+
m = _re.search(r"[-_](\d{2,4})(?:[-_]|$)", name)
|
|
303
|
+
return m.group(1) if m else ""
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _extract_mode(name: str) -> str:
|
|
307
|
+
n = name.upper()
|
|
308
|
+
for mode in ("GCM", "CBC", "CTR", "CCM", "SIV", "ECB", "CFB", "OFB", "XTS"):
|
|
309
|
+
if mode in n:
|
|
310
|
+
return mode.lower()
|
|
311
|
+
return ""
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def scan_file(path: Path) -> list[CryptoAsset]:
|
|
315
|
+
try:
|
|
316
|
+
source = path.read_text(encoding="utf-8", errors="ignore")
|
|
317
|
+
except (OSError, PermissionError):
|
|
318
|
+
return []
|
|
319
|
+
|
|
320
|
+
libraries = _detect_libraries(source)
|
|
321
|
+
primary_lib = libraries[0] if libraries else "unknown"
|
|
322
|
+
lines = source.splitlines()
|
|
323
|
+
assets: list[CryptoAsset] = []
|
|
324
|
+
seen: set[tuple[str, int]] = set()
|
|
325
|
+
|
|
326
|
+
for detector in ALGO_PATTERNS:
|
|
327
|
+
pat = re.compile(detector["pattern"], re.IGNORECASE | re.MULTILINE)
|
|
328
|
+
for m in pat.finditer(source):
|
|
329
|
+
try:
|
|
330
|
+
name = detector["name_fn"](m)
|
|
331
|
+
except (IndexError, AttributeError):
|
|
332
|
+
continue
|
|
333
|
+
|
|
334
|
+
line_no = source[: m.start()].count("\n") + 1
|
|
335
|
+
key = (name.lower(), line_no)
|
|
336
|
+
if key in seen:
|
|
337
|
+
continue
|
|
338
|
+
seen.add(key)
|
|
339
|
+
|
|
340
|
+
norm = name.lower().replace("_", "-")
|
|
341
|
+
# Try exact match, then prefix match for classical_bits
|
|
342
|
+
classical_bits = CLASSICAL_SECURITY_BITS.get(norm, 0)
|
|
343
|
+
if not classical_bits:
|
|
344
|
+
for prefix in ("aes-128", "aes-192", "aes-256"):
|
|
345
|
+
if norm.startswith(prefix):
|
|
346
|
+
classical_bits = CLASSICAL_SECURITY_BITS[prefix]
|
|
347
|
+
break
|
|
348
|
+
|
|
349
|
+
nist_level = NIST_QUANTUM_LEVELS.get(norm, 0)
|
|
350
|
+
evidence = lines[line_no - 1].strip()[:120] if line_no <= len(lines) else ""
|
|
351
|
+
|
|
352
|
+
assets.append(CryptoAsset(
|
|
353
|
+
name=name,
|
|
354
|
+
asset_type=detector["asset_type"],
|
|
355
|
+
primitive=detector["primitive"],
|
|
356
|
+
location=str(path),
|
|
357
|
+
line=line_no,
|
|
358
|
+
library=primary_lib,
|
|
359
|
+
mode=_extract_mode(name),
|
|
360
|
+
curve=_extract_curve(name),
|
|
361
|
+
param_set=_extract_param_set(name),
|
|
362
|
+
classical_bits=classical_bits,
|
|
363
|
+
nist_quantum_level=nist_level,
|
|
364
|
+
quantum_safety=detector["quantum_safety"],
|
|
365
|
+
oid=detector.get("oid", ""),
|
|
366
|
+
evidence=evidence,
|
|
367
|
+
))
|
|
368
|
+
|
|
369
|
+
return assets
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def scan_repo(root: Path, include_tests: bool = False) -> list[CryptoAsset]:
|
|
373
|
+
all_assets: list[CryptoAsset] = []
|
|
374
|
+
extensions = {ext for exts in LANG_EXTENSIONS.values() for ext in exts}
|
|
375
|
+
|
|
376
|
+
for file_path in root.rglob("*"):
|
|
377
|
+
if not file_path.is_file():
|
|
378
|
+
continue
|
|
379
|
+
if any(part in SKIP_DIRS for part in file_path.parts):
|
|
380
|
+
continue
|
|
381
|
+
if not include_tests and any(
|
|
382
|
+
p in ("test", "tests", "spec", "__tests__", "__test__") for p in file_path.parts
|
|
383
|
+
):
|
|
384
|
+
continue
|
|
385
|
+
if file_path.suffix.lower() not in extensions:
|
|
386
|
+
continue
|
|
387
|
+
|
|
388
|
+
all_assets.extend(scan_file(file_path))
|
|
389
|
+
|
|
390
|
+
return all_assets
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""ML-DSA-65 signing via AXIOM API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import requests
|
|
10
|
+
HAS_REQUESTS = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
HAS_REQUESTS = False
|
|
13
|
+
|
|
14
|
+
AXIOM_URL = "https://axiom-trust.fly.dev"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def sign_cbom(cbom: dict) -> dict | None:
|
|
18
|
+
"""Sign the CBOM payload via AXIOM ML-DSA-65 endpoint. Returns signature block or None."""
|
|
19
|
+
if not HAS_REQUESTS:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
# Sign the actual CBOM content, not an empty dict
|
|
23
|
+
cbom_without_sig = {k: v for k, v in cbom.items() if k != "signature"}
|
|
24
|
+
payload_bytes = json.dumps(cbom_without_sig, separators=(",", ":"), sort_keys=True).encode()
|
|
25
|
+
digest = hashlib.sha3_256(payload_bytes).hexdigest()
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
resp = requests.post(
|
|
29
|
+
f"{AXIOM_URL}/api/sign",
|
|
30
|
+
json={"payload": digest, "algorithm": "ML-DSA-65"},
|
|
31
|
+
timeout=10,
|
|
32
|
+
)
|
|
33
|
+
if resp.status_code == 200:
|
|
34
|
+
data = resp.json()
|
|
35
|
+
return {
|
|
36
|
+
"algorithm": "ML-DSA-65",
|
|
37
|
+
"publicKey": data.get("publicKey", ""),
|
|
38
|
+
"value": data.get("signature", ""),
|
|
39
|
+
"digest": digest,
|
|
40
|
+
"certRef": data.get("certRef", ""),
|
|
41
|
+
}
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def verify_cbom(cbom: dict, signature_block: dict) -> bool:
|
|
48
|
+
if not HAS_REQUESTS:
|
|
49
|
+
return False
|
|
50
|
+
try:
|
|
51
|
+
resp = requests.post(
|
|
52
|
+
f"{AXIOM_URL}/api/verify",
|
|
53
|
+
json={
|
|
54
|
+
"digest": signature_block["digest"],
|
|
55
|
+
"signature": signature_block["value"],
|
|
56
|
+
"publicKey": signature_block["publicKey"],
|
|
57
|
+
"algorithm": "ML-DSA-65",
|
|
58
|
+
},
|
|
59
|
+
timeout=10,
|
|
60
|
+
)
|
|
61
|
+
return resp.status_code == 200 and resp.json().get("valid") is True
|
|
62
|
+
except Exception:
|
|
63
|
+
return False
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cobalt-sbom
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cryptographic Bill of Materials generator — CycloneDX 1.5, ML-DSA signed
|
|
5
|
+
Author-email: QreativeLab / OMEGA <dominik@qreativelab.io>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dom-omg/cobalt-sbom
|
|
8
|
+
Project-URL: Repository, https://github.com/dom-omg/cobalt-sbom
|
|
9
|
+
Keywords: cbom,sbom,pqc,cryptography,cyclonedx,quantum
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Information Technology
|
|
13
|
+
Classifier: Topic :: Security :: Cryptography
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: rich>=13.0
|
|
20
|
+
Requires-Dist: requests>=2.31
|
|
21
|
+
Requires-Dist: click>=8.1
|
|
22
|
+
|
|
23
|
+
# COBALT SBOM
|
|
24
|
+
|
|
25
|
+
**Cryptographic Bill of Materials generator — CycloneDX 1.6, ML-DSA-65 signed.**
|
|
26
|
+
|
|
27
|
+
Scan any codebase in seconds. Find every cryptographic algorithm. Know your quantum exposure.
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
30
|
+
|
|
31
|
+
- Detects 30+ crypto primitives across Python, TypeScript, JavaScript, Go, C/C++, Java, Rust
|
|
32
|
+
- Outputs a signed **CycloneDX 1.6 CBOM** (Cryptographic Bill of Materials)
|
|
33
|
+
- Scores your **quantum readiness (0-100)**
|
|
34
|
+
- Flags broken algorithms (DES, MD5, RC4) and deprecated ones (SHA-1, TLS-1.0)
|
|
35
|
+
- Integrates into CI/CD via GitHub Actions — gate PRs on quantum safety
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install cobalt-sbom
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Scan a repo
|
|
47
|
+
cobalt-sbom scan ./myrepo --output cbom.cdx.json
|
|
48
|
+
|
|
49
|
+
# Scan + sign with ML-DSA-65 (via AXIOM)
|
|
50
|
+
cobalt-sbom scan ./myrepo --output cbom.cdx.json --sign
|
|
51
|
+
|
|
52
|
+
# CI mode — fail if quantum-unsafe algorithms present
|
|
53
|
+
cobalt-sbom scan ./myrepo --ci
|
|
54
|
+
|
|
55
|
+
# Fail if quantum readiness score below 80
|
|
56
|
+
cobalt-sbom scan ./myrepo --fail-score 80
|
|
57
|
+
|
|
58
|
+
# Display an existing CBOM
|
|
59
|
+
cobalt-sbom show cbom.cdx.json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## GitHub Actions
|
|
63
|
+
|
|
64
|
+
Add to `.github/workflows/cbom.yml`:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
- name: Install cobalt-sbom
|
|
68
|
+
run: pip install cobalt-sbom
|
|
69
|
+
|
|
70
|
+
- name: Scan cryptographic assets
|
|
71
|
+
run: cobalt-sbom scan . --output cbom.cdx.json --sign --fail-score 60
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Output format
|
|
75
|
+
|
|
76
|
+
CycloneDX 1.6 JSON — compatible with all CBOM/SBOM toolchains:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"bomFormat": "CycloneDX",
|
|
81
|
+
"specVersion": "1.6",
|
|
82
|
+
"components": [
|
|
83
|
+
{
|
|
84
|
+
"type": "cryptographic-asset",
|
|
85
|
+
"name": "ML-DSA-65",
|
|
86
|
+
"cryptoProperties": {
|
|
87
|
+
"assetType": "algorithm",
|
|
88
|
+
"algorithmProperties": {
|
|
89
|
+
"primitive": "sign",
|
|
90
|
+
"nistQuantumSecurityLevel": 3,
|
|
91
|
+
"cryptoFunctions": ["sign", "verify"]
|
|
92
|
+
},
|
|
93
|
+
"quantumSafety": "quantum-safe"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"summary": {
|
|
98
|
+
"quantum_readiness_score": 72,
|
|
99
|
+
"quantum_unsafe": 15,
|
|
100
|
+
"quantum_safe": 16,
|
|
101
|
+
"broken_algorithms": ["DES", "SHA-1"]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Why CBOM
|
|
107
|
+
|
|
108
|
+
Governments (USA EO 14028, EU CRA, NIST IR 8547) are mandating cryptographic inventories. The CBOM Working Group (CycloneDX) is standardizing the format. **First movers own the toolchain.**
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
Built on [OMEGA](https://omega-sovereign.fly.dev) — sovereign intelligence stack.
|
|
113
|
+
Signing powered by [AXIOM](https://axiom-trust.fly.dev) — ML-DSA-65 post-quantum certificates.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
cobalt_sbom/__init__.py
|
|
4
|
+
cobalt_sbom/cli.py
|
|
5
|
+
cobalt_sbom/cyclonedx.py
|
|
6
|
+
cobalt_sbom/detectors.py
|
|
7
|
+
cobalt_sbom/signer.py
|
|
8
|
+
cobalt_sbom.egg-info/PKG-INFO
|
|
9
|
+
cobalt_sbom.egg-info/SOURCES.txt
|
|
10
|
+
cobalt_sbom.egg-info/dependency_links.txt
|
|
11
|
+
cobalt_sbom.egg-info/entry_points.txt
|
|
12
|
+
cobalt_sbom.egg-info/requires.txt
|
|
13
|
+
cobalt_sbom.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cobalt_sbom
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cobalt-sbom"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Cryptographic Bill of Materials generator — CycloneDX 1.5, ML-DSA signed"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "QreativeLab / OMEGA", email = "dominik@qreativelab.io"}]
|
|
13
|
+
keywords = ["cbom", "sbom", "pqc", "cryptography", "cyclonedx", "quantum"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: Information Technology",
|
|
18
|
+
"Topic :: Security :: Cryptography",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"rich>=13.0",
|
|
25
|
+
"requests>=2.31",
|
|
26
|
+
"click>=8.1",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/dom-omg/cobalt-sbom"
|
|
31
|
+
Repository = "https://github.com/dom-omg/cobalt-sbom"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
cobalt-sbom = "cobalt_sbom.cli:main"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["."]
|
|
38
|
+
include = ["cobalt_sbom*"]
|