Lesson 15: Crypto reach¶
A CycloneDX Cryptography Bill of Materials (CBOM) inventories what crypto a system uses: algorithms, key material, certificates. It cannot answer the question that follows naturally: is this weak algorithm on a path an entry point reaches? The crypto-reach command joins a report's crypto inventory to entry-point reachability, per engine, at the granularity each engine's evidence supports.
The granularity ladder¶
The join can only be as precise as the evidence. The four engines that carry crypto sections support different claims, and every rendering states which one it is making:
dosai record-level the engine's own ReachableFromEntryPoint verdicts,
quoted verbatim
rusi function-level for materials, anchored in the call graph;
package-level for libraries
kosi mixed by kind operations join at function grain, materials and
findings at file grain, assets, protocols and
libraries carry no location and render as
inventory only
golem package-level crypto items carry no enclosing function, so the
claim is "this site sits in a package that carries
tainted flows", never "this call is on a tainted
path"
There is deliberately no cross-engine total. Four granularities do not sum, and counts are per engine. The header of every output repeats this before a single number appears.
dosai: record level, verdicts quoted¶
The dosai crypto fixture from eShopOnWeb shows the strongest join in the ecosystem:
$ atom-tools crypto-reach -i test/data/ecosystem/dotnet-eshoponweb-dosai-crypto.json
Crypto reach: dosai — join granularity per engine: dosai record-level (engine)
reachability means a different thing at each granularity; counts are per engine
and are never summed across them.
* dosai: 13 item(s) (3 assets, 5 findings, 2 materials, 3 operations, 0 protocols)
triage population (weak/legacy strength or a weak-crypto rule): 8 item(s)
1. finding Weak or legacy symmetric cipher was detected. (DOSAI-CRYPTO-WEAK-CIPHER),
severity High, CWE-327 tests/FunctionalTests/PublicApi/ApiTokenHelper.cs:46
reach: engine-reported · not reachable from any entry point (engine verdict)
witness slices: dfs1
...
5. finding Weak or legacy symmetric cipher was detected. (DOSAI-CRYPTO-WEAK-CIPHER),
severity High, CWE-327 src/Infrastructure/Identity/IdentityTokenClaimService.cs:43
reach: engine-reported · reachable from an entry point (engine verdict)
entry point: POST /api/authenticate
entry point: ANY /AuthenticateEndpoint/Handle
witness slices: dfs3
...
Item 1 and item 5 are the same finding in different files, and the join separates
them cleanly. The test copy is not reachable from any entry point: dead test
code, low priority. The production copy in IdentityTokenClaimService.cs is
reachable from POST /api/authenticate: a weak symmetric cipher minting tokens
on a live route. Same finding, same severity, opposite triage outcome, and the
difference is entirely the reach line. The verdicts are dosai's own, quoted
verbatim, with witness slice ids for follow-up in the dataflows report.
kosi: kind decides the grain¶
The kosi fixture mixes granularities in one output, and prints the exact claim each line supports:
$ atom-tools crypto-reach -i test/data/ecosystem/kotlin-crypto-material-flow-kosi.json
Crypto reach: kosi — join granularity per engine: kosi file/function-level (derived)
...
* kosi: 8 item(s) (3 assets, 1 findings, 0 libraries, 2 materials, 2 operations, 0 protocols)
triage population (weak/legacy strength or a weak-crypto rule): 1 item(s)
1. finding low-iteration-pbkdf2, severity medium src/main/kotlin/fixtures/cryptoflow/TokenService.kt:30
reach: file-level · 2 tainted flow(s) through this file
claim: the crypto site sits in a file that carries tainted flows — not that
the crypto call itself is on a tainted path
...
7. operation Mac HmacSHA256 src/main/kotlin/fixtures/cryptoflow/TokenService.kt:25
reach: function-level · in the call graph, in no anchored endpoint's closure ·
static callers: none
8. operation SecretKeyFactory PBKDF2WithHmacSHA256 .../TokenService.kt:31
reach: function-level · in the call graph, in no anchored endpoint's closure ·
static callers: none
coverage: endpoint closures from the attack-surface join: 0 of 0 endpoint(s)
anchored in this input's call graph
The finding and the materials join at file grain, so their lines carry the
"claim" sentence spelling out what a file-level reach does and does not mean. The
operations carry an enclosing function, so they join at function grain: both sit
in the call graph with no static callers at all, which is itself a finding worth
a look. The assets (EC, HmacSHA256, PBKDF2WithHmacSHA256) carry no location
fields whatsoever, so they render as inventory with an explicit "nothing to join
on", not as unreachable.
golem: package level, and a second signal¶
The golem fixture (ipsw again) demonstrates the coarsest join plus a bonus signal:
$ atom-tools crypto-reach -i test/data/ecosystem/go-ipsw-golem.json --weak-only
Crypto reach: golem — join granularity per engine: golem package-level (derived)
...
* golem: 15 item(s) (9 assets, 146 findings, 106 libraries, 320 materials, 323
operations, 1 protocols)
triage population (weak/legacy strength or a weak-crypto rule): 15 item(s)
1. finding GOLEM-CRYPTO-TLS-INSECURE-SKIP-VERIFY, severity critical
.../internal/download/transport_apple.go:31
reach: package-level · 59 tainted flow(s) through package
claim: the crypto site sits in a package that carries tainted flows — not
that the crypto call itself is on a tainted path
engine roots: 0 of 30 call-graph node(s) in this package are reachable from
the engine's root set
2. finding GOLEM-CRYPTO-TLS-INSECURE-SKIP-VERIFY, severity critical
.../pkg/tss/tss.go:192
reach: package-level · no analysed flow runs through this item's package
...
Note the parenthetical counts in the inventory line: 15 items listed but 146
findings, 320 materials and 323 operations exist in the report. The triage
population (weak or legacy strength items plus findings) is what --weak-only
keeps, and the text rendering caps at --max-entries items with a counted
remainder; the json format carries everything.
The engine roots line on item 1 is the call graph root signal from lesson 13
surfacing here: none of the thirty call-graph nodes in that package are
reachable from golem's root set for this run. Read together with lesson 13's
root-set caveats, it reframes the critical finding: the insecure TLS transport
sits in a package that carries tainted flows, but the engine's own root
reachability says the code may not be live from where it roots reachability.
Out of scope, stated plainly¶
Atom slices and unified documents carry no crypto section. The command refuses to produce an empty report that would read as "no crypto found":
$ atom-tools crypto-reach -i test/data/java-petclinic-reachables.json
no input carries a crypto section (golem analyze --dataflow crypto|all, rusi
analyze, dosai crypto). atom emits no crypto section of any kind; there is
nothing to join for this input
An empty inventory and an unsupported input are different facts, and only one of them is a finding.
Exercises¶
Run the json format on the dosai fixture and inspect what each item carries that the text rendering trims. Then feed a mixed input, the dosai crypto fixture plus the golem fixture, and read how the output keeps the two engines' counts apart:
$ atom-tools crypto-reach -i test/data/ecosystem/dotnet-eshoponweb-dosai-crypto.json -f json -o crypto.json
$ atom-tools crypto-reach -i "test/data/ecosystem/dotnet-eshoponweb-dosai-crypto.json,test/data/ecosystem/go-ipsw-golem.json"