diff --git a/README.md b/README.md index 38bc8d7..9e29fd8 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ node pos.mjs keygen --out keys.json node test/pos.test.mjs # and the other tests below ``` -Node.js 22 or later, and git. Results measured on 2026-09-29 (Node.js 24.14.1, Windows; git 2.x, npm 11): +Node.js 22 or later, and git. Results measured on 2026-09-30 (Node.js 24.14.1, Windows; git 2.x, npm 11): | test | result | negative control | |---|---|---| @@ -29,8 +29,9 @@ Node.js 22 or later, and git. Results measured on 2026-09-29 (Node.js 24.14.1, W | ML-BOM of a model directory | 8/8 (`test/model.test.mjs`) | `test/model-control-negativ.mjs` edits `pos.mjs` in place and restores it; not run for this table | | developer credential | 12/12 (`test/credential.test.mjs`) | 7/7 (`test/credential-control-negativ.mjs`) | | who signed (`--signer`) | 7/7 (`test/semnatar.test.mjs`) | 4/4 (`test/semnatar-control-negativ.mjs`) | -| npm SBOM from the committed lockfile | 8/8 (`test/sbom.test.mjs`) | 3/3 (`test/sbom-control-negativ.mjs`) | -| Go SBOM from the committed go.sum | 13/13 (`test/sbom-go.test.mjs`) | 7/7 (`test/sbom-go-control-negativ.mjs`) | +| npm SBOM from the committed lockfile | 11/11 (`test/sbom.test.mjs`) | 5/5 (`test/sbom-control-negativ.mjs`) | +| Go SBOM from the committed go.sum | 14/14 (`test/sbom-go.test.mjs`) | 8/8 (`test/sbom-go-control-negativ.mjs`) | +| .NET and Gradle SBOMs from the committed lock files | 27/27 (`test/sbom-nuget-gradle.test.mjs`) | 20/20 (`test/sbom-nuget-gradle-control-negativ.mjs`) | | hybrid signature library | 34/34 (`sdk-pq-sign/test/pq-sign.test.mjs`); its check of the ML-DSA half on chain is skipped without a Cloud key | inside the test | | GitHub Action | `test/action.test.mjs` (long; installs pinned dependencies from the npm registry) | not measured for this publication | @@ -43,4 +44,4 @@ the documentation are in English. No third party has reviewed this code. ## Licence -MIT, see [LICENSE](LICENSE). Files: 35 (tools/proof-of-software 24, sdk-pq-sign 6, sdk 3). +MIT, see [LICENSE](LICENSE). Files: 48 (tools/proof-of-software 37, sdk-pq-sign 6, sdk 3). diff --git a/tools/proof-of-software/README.md b/tools/proof-of-software/README.md index e10cc58..f55033b 100644 --- a/tools/proof-of-software/README.md +++ b/tools/proof-of-software/README.md @@ -164,9 +164,10 @@ with its hashes and scope, and the dependency graph. Same content with another s committed tree without `package-lock.json` cannot re-derive anything, and verification says so instead of passing it. ``` -node test/sbom.test.mjs # 8/8: two runs differ in bytes, not in content; a component dropped, a version changed, an - # integrity hash changed -> caught; SBOM not handed / no committed lockfile -> reported, not passed -node test/sbom-control-negativ.mjs # on a copy, 3 guards removed -> their own tests turn red +node test/sbom.test.mjs # 11/11: two runs differ in bytes, not in content; a component dropped, a version changed, an + # integrity hash changed -> caught; SBOM not handed / no committed lockfile -> reported, not passed; + # an edited SBOM that names another lock file (1.5.0) -> caught +node test/sbom-control-negativ.mjs # on a copy, 5 guards removed -> their own tests turn red ``` ## A Go module's SBOM, from the committed go.sum (1.4.0) @@ -191,12 +192,65 @@ pack`; for a Go binary the rebuild of the artifacts is reported as not attempted SBOM are still judged. ``` -node test/sbom-go.test.mjs # 13/13 on real go.mod/go.sum files (ours, and golang/mock v1.6.0 with 23 go.mod-only entries): +node test/sbom-go.test.mjs # 14/14 on real go.mod/go.sum files (ours, and golang/mock v1.6.0 with 23 go.mod-only entries): # deterministic, h1 equal to what Go recorded when it downloaded the module (when the module is # in the local Go module cache; skipped otherwise), a module dropped, a version, an h1, the root - # changed -> caught; no committed go.sum -> reported, not passed -node test/sbom-go-control-negativ.mjs # on a copy, 7 guards removed -> their own tests turn red + # changed -> caught; no committed go.sum -> reported, not passed; an edited SBOM with the + # property removed -> not judged, and the reason names go.sum (1.5.0) +node test/sbom-go-control-negativ.mjs # on a copy, 8 guards removed -> their own tests turn red ``` -Java (Maven, Gradle) and .NET lockfiles: not done; the machine these were built and measured on has neither toolchain to produce -real lockfiles to test against, and a parser tested only on files written by hand would test its own assumptions. +## .NET and Gradle SBOMs, from the committed lock files (1.5.0) + +``` +node pos.mjs sbom-nuget --source-path ./src/MyApp --version 1.4.2 --out sbom.cdx.json # packages.lock.json + the ONE project file next to it +node pos.mjs sbom-gradle --source-path ./myapp --version 1.4.2 --out sbom.cdx.json # gradle/verification-metadata.xml (+ gradle.lockfile) +``` + +Both read only the committed tree (`:`), with no toolchain and no network, and the same files always give the same +bytes. `verify --rebuild-from` re-derives the SBOM the same way and compares what the two say. + +**.NET.** From `packages.lock.json` (written by NuGet with `RestorePackagesWithLockFile`): every package of every target framework +(and framework/RID section) with its RESOLVED version and a `pkg:nuget` purl, project references as components `project:`, and +the dependency graph as the lock file records it. NuGet's `contentHash` is kept as the property `aere:nuget-content-hash`, not as a +SHA-512 of the package: NuGet computes it over the package without its repository signature, so it is not the hash of the `.nupkg` +file you download (measured on signed packages). The root is named by the project file, so the directory must hold exactly one. + +**Gradle.** From `gradle/verification-metadata.xml` (Gradle's dependency verification, written with `--write-verification-metadata`) +and, when committed, `gradle.lockfile` (`--write-locks`). Every component with a `pkg:maven` purl; the main artifact's checksums +(`-.jar`/`.aar`) are the file's own, so they are CycloneDX hashes; every artifact (POMs, Gradle module files) is kept +in `aere:gradle-artifact` with its checksums, `also-trust` values and PGP keys; the configurations each coordinate is locked in are in +`aere:gradle-configurations`; the number of `trusted-artifacts` rules (which switch verification off for what they match) and the +`verify-metadata`/`verify-signatures` settings are in the metadata. What it does NOT say: neither file records the dependency graph, +so `dependencies` is empty and the metadata says so; the verification metadata lists everything the build resolved (parent POMs, +plugins too). A coordinate locked in `gradle.lockfile` with no checksum in the verification metadata is listed and counted +(`aere:gradle-no-checksum`), not hidden. The reader accepts only the XML Gradle writes: a DOCTYPE, CDATA, an unclosed tag or comment, +crossed tags or an unquoted attribute are refused with the reason, not guessed. + +In both cases the SBOM says what the committed lock files pin or resolve, not what was compiled: the build uses them only in locked +mode (`dotnet restore --locked-mode`, Gradle's strict dependency verification and locking). + +**The committed tree decides what an SBOM is judged against (1.5.0).** Until 1.4.0 the attested SBOM chose its own judge through its +`aere:derived-from` property: an npm SBOM edited by hand that said `derived-from: go.sum` came out not judged ("the tree has no +go.mod"), and a Go SBOM edited by hand with the property removed was judged as npm and came out not judged too, both with the verdict +VALID. Now the property is a claim about the tree: a named file the committed tree does not have fails the check; an SBOM that names +no file (npm sbom and other tools do not), or one the verifier does not know, is judged against `package-lock.json`; and when the tree +has no `package-lock.json` but has `go.sum`, `packages.lock.json` or Gradle's verification metadata, it stays not judged and the reason +names the file and the command whose SBOM would be judged. What remains: whoever attests can always hand an SBOM made by another tool, +which this verifier cannot judge; the verdict line counts it among the not judged. + +The files these read belong to the tree being attested, that is to whoever attests, and the verifier reads them: every reader here +costs linear time (measured on forms made for the purpose: a 80 KB tag, 80,000 blank lines, 8,000 unclosed tags, under a millisecond +to a few tens of milliseconds each). + +``` +node test/sbom-nuget-gradle.test.mjs # 27/27 on real lock files written by the tools themselves (test/fixturi-nuget: + # .NET SDK 10.0.302, restored offline from the local package cache; test/fixturi-gradle: + # Gradle 8.14.3 --offline), contentHash equal to what NuGet recorded at restore (90/90), + # SHA-256 equal to the jars in Gradle's cache (when present; skipped otherwise); a package + # dropped, a version, a hash, an edge, a configuration changed -> caught; broken inputs refused +node test/sbom-nuget-gradle-control-negativ.mjs # on a copy, 20 guards removed -> their own tests turn red +``` + +Maven (`pom.xml` without a lock file) is not done: Maven has no lock file of its own, and an SBOM derived from `pom.xml` ranges would +say what was asked for, not what was resolved. diff --git a/tools/proof-of-software/pos.mjs b/tools/proof-of-software/pos.mjs index 5da870e..488febb 100644 --- a/tools/proof-of-software/pos.mjs +++ b/tools/proof-of-software/pos.mjs @@ -8,6 +8,10 @@ // node pos.mjs pack-npm --source-path DIR [--commit C] [--out-dir D] (npm pack of the COMMITTED tree, prints the file) // node pos.mjs sbom-go --source-path DIR --version V [--commit C] [--out sbom.cdx.json] (1.4.0: SBOM of a Go module from the // COMMITTED go.mod and go.sum, deterministic; verify --rebuild-from re-derives and compares it) +// node pos.mjs sbom-nuget --source-path DIR --version V [--commit C] [--out sbom.cdx.json] (1.5.0: .NET, from the COMMITTED +// packages.lock.json and the project file next to it; deterministic, re-derived by verify --rebuild-from) +// node pos.mjs sbom-gradle --source-path DIR --version V [--commit C] [--out sbom.cdx.json] (1.5.0: Gradle, from the COMMITTED +// gradle/verification-metadata.xml and gradle.lockfile; deterministic, re-derived by verify --rebuild-from) // node pos.mjs model-bom --model-dir DIR --out mlbom.json [--name N --version V] [--task T] [--dataset NAME=FILE ...] // node pos.mjs pubkey --keys keys.json [--out pub.json] (the public part, to hand out) // node pos.mjs credential issue --issuer-keys org.json --issuer-name O --subject-pub dev.pub.json --subject-name D [--valid-days 365] @@ -41,7 +45,7 @@ import { fileURLToPath } from 'node:url'; import * as pq from '../../sdk-pq-sign/index.mjs'; import { AereCloud } from '../../sdk/index.mjs'; -export const TOOL = 'aere-proof-of-software/1.4.0'; +export const TOOL = 'aere-proof-of-software/1.5.0'; const AICI = path.dirname(fileURLToPath(import.meta.url)); // peste prag, in flux: o greutate de model de cativa GB nu are voie sa fie citita intreaga in memorie (2026-09-25) @@ -238,7 +242,8 @@ export function sbomSemantica(bom) { // 1.4.0: si proprietatile 'aere:' (un SBOM Go tine acolo hash-ul h1 din go.sum); un SBOM npm nu are asemenea proprietati, deci // judecata lui ramane aceeasi const aere = (ps) => (ps || []).filter((x) => String(x.name).startsWith('aere:')).map((x) => `${x.name}=${x.value}`).sort(); - const comp = (c) => ({ purl: c.purl || `${c.name}@${c.version}`, scope: c.scope || null, + // 1.5.0: o componenta fara purl (o referinta de proiect .NET, radacina Gradle) se numeste prin bom-ref + const comp = (c) => ({ purl: c.purl || c['bom-ref'] || `${c.name}@${c.version}`, scope: c.scope || null, hashes: (c.hashes || []).map((h) => `${h.alg}:${String(h.content).toLowerCase()}`).sort(), props: aere(c.properties) }); const cheie = (x) => JSON.stringify(x); const componente = (bom.components || []).map(comp).sort((a, b) => (cheie(a) < cheie(b) ? -1 : 1)); @@ -300,16 +305,279 @@ export function sbomGoFromTree(repoDir, commit, rel, version) { const goSum = blob('go.sum'); if (goSum == null) return { lipsa: 'the committed tree has no go.sum, so the SBOM cannot be re-derived from it' }; try { return { bom: sbomGo({ goSum, goMod, version }) }; } catch (e) { return { lipsa: 'the committed go.sum or go.mod cannot be read: ' + String(e.message || e).slice(0, 100) }; } } -const esteSbomGo = (bom) => !!(bom && bom.metadata && (bom.metadata.properties || []).some((x) => x.name === 'aere:derived-from' && x.value === 'go.sum')); +const derivatDin = (bom) => ((bom && bom.metadata && (bom.metadata.properties || []).find((x) => x.name === 'aere:derived-from')) || {}).value || null; +// numarul de serie al unui SBOM derivat: UUID (forma 5) din continut, deci aceleasi fisiere dau aceiasi octeti +function serieDerivata(o) { + const b = crypto.createHash('sha256').update(JSON.stringify(o)).digest(); + b[6] = (b[6] & 0x0f) | 0x50; b[8] = (b[8] & 0x3f) | 0x80; + const h = b.subarray(0, 16).toString('hex'); + return `urn:uuid:${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`; +} +const propr = (o) => Object.entries(o).filter(([, v]) => v != null).map(([name, value]) => ({ name, value: String(value) })).sort((a, b) => (a.name < b.name ? -1 : 1)); +const graf = (muchii) => { const m = new Map(); for (const [de, la] of muchii) { if (!m.has(de)) m.set(de, new Set()); m.get(de).add(la); } + return [...m.entries()].map(([ref, s]) => ({ ref, dependsOn: [...s].sort() })).sort((a, b) => (a.ref < b.ref ? -1 : 1)); }; +const blobDin = (repoDir, commit, cale) => { try { return execFileSync('git', ['cat-file', '-p', `${commit}:${cale}`], { cwd: repoDir, stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 1 << 28 }).toString(); } catch { return null; } }; +const numeDin = (repoDir, commit, rel) => { try { return execFileSync('git', ['ls-tree', '--name-only', `${commit}:${rel}`], { cwd: repoDir, stdio: ['ignore', 'pipe', 'ignore'] }).toString().split('\n').filter(Boolean); } catch { return null; } }; + +// 1.5.0 (2026-09-29): SBOM-ul unui proiect .NET, derivat DETERMINIST din packages.lock.json COMIS (scris de NuGet cu +// RestorePackagesWithLockFile). Componentele sunt pachetele din toate sectiunile (cadru, si cadru/RID), cu versiunea REZOLVATA; +// contentHash-ul NuGet e o proprietate (`aere:nuget-content-hash`), nu un `hashes` SHA-512: NuGet il calculeaza peste continutul +// pachetului FARA semnatura de depozit, deci nu e hash-ul fisierului .nupkg descarcat (masurat pe pachete reale semnate). Referintele +// de proiect (tip Project) sunt componente cu bom-ref `project:`, fara purl. Graful vine din lockfile: fiecare dependinta +// numita se rezolva la versiunea rezolvata a aceluiasi pachet din aceeasi sectiune (sau din cadrul de baza, pentru o sectiune RID). +const NUGET_TIPURI = new Set(['Direct', 'Transitive', 'CentralTransitive', 'Project']); +const NUGET_HASH = /^[A-Za-z0-9+/]{86}==$/; +const purlNuget = (n, v) => `pkg:nuget/${encodeURIComponent(n)}@${encodeURIComponent(v)}`; +export function sbomNuget({ lock, projectName, version }) { + let j; try { j = typeof lock === 'string' ? JSON.parse(lock) : lock; } catch { throw new Error('packages.lock.json is not JSON'); } + if (!j || ![1, 2].includes(j.version) || !j.dependencies || typeof j.dependencies !== 'object') throw new Error('packages.lock.json has no version 1 or 2, or no dependencies'); + if (!projectName) throw new Error('an SBOM for a .NET project needs the project name (one project file next to packages.lock.json)'); + if (!version) throw new Error('an SBOM for a .NET project needs --version (the project version is not in packages.lock.json)'); + const root = purlNuget(projectName, version); + const sectiuni = Object.keys(j.dependencies).sort(); + const index = new Map(sectiuni.map((s) => [s, new Map(Object.entries(j.dependencies[s] || {}).map(([n, e]) => [n.toLowerCase(), [n, e]]))])); + const pachete = new Map(), proiecte = new Map(), muchii = []; + const refDe = ([n, e]) => (e.type === 'Project' ? `project:${n}` : purlNuget(n, e.resolved)); + for (const s of sectiuni) { + const ix = index.get(s), baza = s.includes('/') ? index.get(s.split('/')[0]) : null; + const rezolva = (dn) => { const x = ix.get(dn.toLowerCase()) || (baza && baza.get(dn.toLowerCase())); if (!x) throw new Error(`packages.lock.json: ${dn} is a dependency in ${s} and is not resolved in it`); return x; }; + for (const [lower, [n, e]] of ix) { + if (!e || !NUGET_TIPURI.has(e.type)) throw new Error(`packages.lock.json: ${n} in ${s} has an unknown type ${e && e.type}`); + if (e.type === 'Project') { proiecte.set(lower, n); } + else { + if (typeof e.resolved !== 'string' || !e.resolved) throw new Error(`packages.lock.json: ${n} in ${s} has no resolved version`); + if (!NUGET_HASH.test(String(e.contentHash))) throw new Error(`packages.lock.json: ${n} ${e.resolved} in ${s} has no SHA-512 content hash`); + const k = `${lower}@${e.resolved.toLowerCase()}`; + const p = pachete.get(k) || { name: n, version: e.resolved, hash: e.contentHash, sectiuni: new Set(), tipuri: new Set() }; + if (p.hash !== e.contentHash) throw new Error(`packages.lock.json: ${n} ${e.resolved} has two content hashes`); + p.sectiuni.add(s); p.tipuri.add(e.type); pachete.set(k, p); + } + if (e.type === 'Direct' || e.type === 'Project') muchii.push([root, refDe([n, e])]); + for (const dn of Object.keys(e.dependencies || {})) muchii.push([refDe([n, e]), refDe(rezolva(dn))]); + } + } + const components = [ + ...[...pachete.values()].map((p) => { const purl = purlNuget(p.name, p.version); + return { type: 'library', 'bom-ref': purl, name: p.name, version: p.version, purl, + properties: propr({ 'aere:nuget-content-hash': 'sha512-' + p.hash, 'aere:nuget-frameworks': [...p.sectiuni].sort().join(','), 'aere:nuget-type': [...p.tipuri].sort().join(',') }) }; }), + ...[...proiecte.values()].map((n) => ({ type: 'library', 'bom-ref': `project:${n}`, name: n, properties: propr({ 'aere:nuget-type': 'Project' }) })), + ].sort((a, b) => (a['bom-ref'] < b['bom-ref'] ? -1 : a['bom-ref'] > b['bom-ref'] ? 1 : 0)); + const metadata = { + component: { type: 'application', 'bom-ref': root, name: projectName, version, purl: root }, + tools: { components: [{ type: 'application', name: 'aere-proof-of-software', version: TOOL.split('/')[1] }] }, + properties: propr({ 'aere:derived-from': 'packages.lock.json', 'aere:nuget-lock-version': j.version, 'aere:nuget-frameworks': sectiuni.join(',') }), + }; + const dependencies = graf(muchii); + return { bomFormat: 'CycloneDX', specVersion: '1.6', serialNumber: serieDerivata({ metadata, components, dependencies }), version: 1, metadata, components, dependencies }; +} +/** SBOM-ul .NET al arborelui COMIS :: packages.lock.json si numele UNUI fisier de proiect din acelasi dosar. */ +export function sbomNugetFromTree(repoDir, commit, rel, version) { + const lock = blobDin(repoDir, commit, `${rel}/packages.lock.json`); + if (lock == null) return { lipsa: 'the committed tree has no packages.lock.json, so the SBOM cannot be re-derived from it' }; + const proj = (numeDin(repoDir, commit, rel) || []).filter((n) => /\.(cs|fs|vb)proj$/i.test(n)); + if (proj.length !== 1) return { lipsa: `the committed directory has ${proj.length} project files; exactly one names the SBOM root` }; + try { return { bom: sbomNuget({ lock, projectName: proj[0].replace(/\.(cs|fs|vb)proj$/i, ''), version }) }; } catch (e) { return { lipsa: 'the committed packages.lock.json cannot be read: ' + String(e.message || e).slice(0, 120) }; } +} + +// 1.5.0 (2026-09-29): SBOM-ul unui proiect Gradle (un singur proiect), derivat DETERMINIST din gradle/verification-metadata.xml +// COMIS (verificarea dependintelor lui Gradle: fiecare artefact rezolvat cu hash-urile lui) si, daca e comis, din gradle.lockfile +// (coordonatele fiecarei configuratii). Hash-urile artefactului principal (-.jar/.aar) sunt chiar hash-urile fisierului, +// deci intra in `hashes`; toate artefactele (si pom-urile, modulele) stau ca `aere:gradle-artifact`. Ce NU spune: graful de dependinte +// (niciunul din cele doua fisiere nu il tine, deci `dependencies` e gol si metadata o spune); verification-metadata cuprinde tot ce +// rezolva constructia (si pom-uri parinte, si pluginuri), iar configuratiile din gradle.lockfile spun ce ajunge in care classpath. +const XML_ENT = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" }; +const xmlDec = (s) => String(s).replace(/&(amp|lt|gt|quot|apos);/g, (_, e) => XML_ENT[e]); +const CDX_ALG = { sha1: 'SHA-1', sha256: 'SHA-256', sha512: 'SHA-512', md5: 'MD5' }; +const purlMaven = (g, n, v) => `pkg:maven/${encodeURIComponent(g)}/${encodeURIComponent(n)}@${encodeURIComponent(v)}`; +// B-22 (2026-09-30): fisierul e al arborelui ATESTAT, deci al celui care atesta, si il citeste verificatorul. Prima forma a lui 1.5.0 il +// citea cu expresii regulate care costau PATRATIC pe forme facute anume (masurat: un tag de 80 KB fara `=` -> 6,9 s, un settings.gradle +// cu 80.000 de randuri goale -> 5 s; la 1 MB, zeci de minute). Cititorul de mai jos trece o SINGURA data prin text (indexOf, fara +// intoarceri): taguri, atribute cu ghilimele duble, text, comentarii si sarite; orice altceva (DOCTYPE, CDATA, `<` intr-un tag, +// un tag sau un comentariu neinchis, un atribut fara ghilimele, taguri incrucisate) e REFUZAT cu motivul, nu ghicit. +const NUME_XML = /^[A-Za-z_][\w.:-]*$/; +function atributeXml(s, unde) { + const a = {}; let i = 0; const n = s.length; + const alb = (c) => c === ' ' || c === '\t' || c === '\n' || c === '\r'; + while (i < n) { + while (i < n && alb(s[i])) i++; + if (i >= n) break; + let j = i; while (j < n && !alb(s[j]) && s[j] !== '=') j++; + const nume = s.slice(i, j); if (!NUME_XML.test(nume)) throw new Error(`${unde}: an attribute name that is not a name`); + while (j < n && alb(s[j])) j++; + if (s[j] !== '=') throw new Error(`${unde}: attribute ${nume} has no value`); + j++; while (j < n && alb(s[j])) j++; + if (s[j] !== '"') throw new Error(`${unde}: attribute ${nume} is not in double quotes`); + const f = s.indexOf('"', j + 1); if (f === -1) throw new Error(`${unde}: attribute ${nume} is not closed`); + a[nume] = xmlDec(s.slice(j + 1, f)); i = f + 1; + } + return a; +} +function xmlTokeni(x, unde) { + const out = []; let i = 0; const n = x.length; + while (i < n) { + const lt = x.indexOf('<', i); + if (lt === -1) { out.push({ text: x.slice(i) }); break; } + if (lt > i) out.push({ text: x.slice(i, lt) }); + if (x.startsWith('', lt + 4); if (e === -1) throw new Error(`${unde}: a comment is not closed`); i = e + 3; continue; } + if (x.startsWith('', lt + 2); if (e === -1) throw new Error(`${unde}: a declaration is not closed`); i = e + 2; continue; } + if (x.startsWith('` intr-o valoare nu inchide tagul); fiecare caracter vazut o data + let j = lt + 1; + for (;;) { + if (j >= n) throw new Error(`${unde}: a tag is not closed`); + const c = x[j]; + if (c === '>') break; + if (c === '<') throw new Error(`${unde}: "<" inside a tag`); + if (c === '"') { const f = x.indexOf('"', j + 1); if (f === -1) throw new Error(`${unde}: a quoted value is not closed`); j = f + 1; continue; } + j++; + } + let corp = x.slice(lt + 1, j); + if (corp[0] === '/') { + const nume = corp.slice(1).trim(); if (!NUME_XML.test(nume)) throw new Error(`${unde}: a closing tag that is not a name`); + out.push({ close: nume }); + } else { + const self = corp.endsWith('/'); if (self) corp = corp.slice(0, -1); + let k = 0; while (k < corp.length && !' \t\n\r'.includes(corp[k])) k++; + const nume = corp.slice(0, k); if (!NUME_XML.test(nume)) throw new Error(`${unde}: a tag whose name is not a name`); + out.push({ open: nume, attrs: atributeXml(corp.slice(k), unde), self }); + } + i = j + 1; + } + return out; +} +/** Citirea lui gradle/verification-metadata.xml: configuratia (verify-metadata, verify-signatures, regulile de incredere) si componentele. */ +export function citesteVerificationMetadata(text) { + const U = 'gradle/verification-metadata.xml'; + const tok = xmlTokeni(String(text || ''), U); + const stiva = []; let radacina = false; let verMeta = null, verSemn = null, reguliIncredere = 0; + const coord = new Map(); let comp = null, art = null; + for (const t of tok) { + const sus = stiva[stiva.length - 1]; + if (t.text != null) { + const v = t.text.trim(); if (!v) continue; + if (sus === 'verify-metadata' && stiva[stiva.length - 2] === 'configuration') verMeta = v; + else if (sus === 'verify-signatures' && stiva[stiva.length - 2] === 'configuration') verSemn = v; + continue; + } + if (t.open) { + if (!stiva.length) { + if (radacina) throw new Error(`${U}: a second root element <${t.open}>`); + if (t.open !== 'verification-metadata') throw new Error(`${U} has no element`); + radacina = true; + } + if (t.open === 'trust' && stiva.includes('configuration')) reguliIncredere++; + if (t.open === 'component') { + if (sus !== 'components') throw new Error(`${U}: a outside `); + const a = t.attrs; if (!a.group || !a.name || !a.version) throw new Error(`${U}: a component without group, name or version`); + const k = `${a.group}:${a.name}:${a.version}`; if (coord.has(k)) throw new Error(`${U}: ${k} twice`); + comp = { group: a.group, name: a.name, version: a.version, arte: [], configuratii: null }; coord.set(k, comp); + } else if (t.open === 'artifact') { + if (sus !== 'component') throw new Error(`${U}: an outside a `); + if (!t.attrs.name) throw new Error(`${U}: an artifact of ${comp.group}:${comp.name} has no name`); + art = { nume: t.attrs.name, valori: [], principale: {} }; comp.arte.push(art); + } else if (['sha1', 'sha256', 'sha512', 'md5', 'pgp'].includes(t.open) && sus === 'artifact') { + const v = String(t.attrs.value || '').toLowerCase(); + if (v) { art.valori.push(`${t.open}:${v}`); if (t.open !== 'pgp') art.principale[t.open] = v; } + } else if (t.open === 'also-trust' && ['sha1', 'sha256', 'sha512', 'md5'].includes(sus) && stiva[stiva.length - 2] === 'artifact') { + const v = String(t.attrs.value || '').toLowerCase(); if (v) art.valori.push(`also-trust:${v}`); + } + if (!t.self) stiva.push(t.open); + continue; + } + const scos = stiva.pop(); + if (scos !== t.close) throw new Error(`${U}: closes <${scos || 'nothing'}>`); + if (scos === 'artifact') art = null; else if (scos === 'component') comp = null; + } + if (!radacina) throw new Error(`${U} has no element`); + if (stiva.length) throw new Error(`${U}: <${stiva[stiva.length - 1]}> is not closed`); + for (const c of coord.values()) for (const a of c.arte) a.linie = `${a.nume} ${a.valori.sort().join(' ')}`; + return { verMeta, verSemn, reguliIncredere, coord }; +} +export function sbomGradle({ verificationMetadata, lockfile = null, projectName, version }) { + if (!projectName) throw new Error('an SBOM for a Gradle project needs the project name (rootProject.name in settings.gradle)'); + if (!version) throw new Error('an SBOM for a Gradle project needs --version'); + const { verMeta, verSemn, reguliIncredere, coord } = citesteVerificationMetadata(verificationMetadata); + let blocate = 0; + if (lockfile != null) { + String(lockfile).replace(/\r/g, '').split('\n').forEach((l, i) => { + const t = l.trim(); if (!t || t.startsWith('#')) return; + const m = /^([^:=\s]+):([^:=\s]+):([^:=\s]+)=([\w,.-]*)$/.exec(t); + if (!m) { if (/^empty=[\w,.-]*$/.test(t)) return; throw new Error(`gradle.lockfile line ${i + 1} is not "::="`); } + const k = `${m[1]}:${m[2]}:${m[3]}`; + const c = coord.get(k) || { group: m[1], name: m[2], version: m[3], arte: [], configuratii: null }; + c.configuratii = m[4].split(',').filter(Boolean).sort(); coord.set(k, c); blocate++; + }); + } + const components = [...coord.values()].map((c) => { + const purl = purlMaven(c.group, c.name, c.version); + const princ = c.arte.find((ar) => new RegExp(`^${c.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-${c.version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\.(jar|aar|klib|war)$`).test(ar.nume)); + const hashes = princ ? Object.entries(princ.principale).map(([alg, content]) => ({ alg: CDX_ALG[alg], content })).sort((p, q) => (p.alg < q.alg ? -1 : 1)) : []; + const properties = [ + ...c.arte.map((ar) => ({ name: 'aere:gradle-artifact', value: ar.linie })), + ...(lockfile != null ? [{ name: 'aere:gradle-configurations', value: (c.configuratii || []).join(',') }] : []), + ...(!c.arte.length ? [{ name: 'aere:gradle-no-checksum', value: 'true' }] : []), + ].sort((p, q) => (p.name + p.value < q.name + q.value ? -1 : 1)); + return { type: 'library', 'bom-ref': purl, group: c.group, name: c.name, version: c.version, purl, ...(hashes.length ? { hashes } : {}), properties }; + }).sort((a, b) => (a.purl < b.purl ? -1 : a.purl > b.purl ? 1 : 0)); + const root = `gradle-project:${projectName}@${version}`; + const metadata = { + component: { type: 'application', 'bom-ref': root, name: projectName, version }, + tools: { components: [{ type: 'application', name: 'aere-proof-of-software', version: TOOL.split('/')[1] }] }, + properties: propr({ 'aere:derived-from': 'gradle/verification-metadata.xml', 'aere:gradle-lockfile': lockfile != null ? 'gradle.lockfile' : 'absent', + 'aere:gradle-verify-metadata': verMeta, 'aere:gradle-verify-signatures': verSemn, 'aere:gradle-trust-rules': reguliIncredere, + 'aere:gradle-locked-without-checksum': lockfile != null ? components.filter((c) => c.properties.some((p) => p.name === 'aere:gradle-no-checksum')).length : null, + 'aere:dependency-graph': 'absent: neither gradle/verification-metadata.xml nor gradle.lockfile records it' }), + }; + return { bomFormat: 'CycloneDX', specVersion: '1.6', serialNumber: serieDerivata({ metadata, components, blocate }), version: 1, metadata, components, dependencies: [] }; +} +export const RADACINA_GRADLE = /^[ \t]*rootProject\.name[ \t]*=[ \t]*['"]([^'"\r\n]+)['"][ \t]*\r?$/m; +/** SBOM-ul Gradle al arborelui COMIS :: gradle/verification-metadata.xml, gradle.lockfile (daca e), rootProject.name. */ +export function sbomGradleFromTree(repoDir, commit, rel, version) { + const vm = blobDin(repoDir, commit, `${rel}/gradle/verification-metadata.xml`); + if (vm == null) return { lipsa: 'the committed tree has no gradle/verification-metadata.xml, so the SBOM cannot be re-derived from it' }; + const setari = blobDin(repoDir, commit, `${rel}/settings.gradle`) ?? blobDin(repoDir, commit, `${rel}/settings.gradle.kts`); + // B-22: `^\s*` cu /m trece peste randuri goale si se reia de la fiecare rand (patratic: 80.000 de randuri goale -> 5 s); spatiile + // se cauta numai in rand + const nm = setari && RADACINA_GRADLE.exec(setari); + if (!nm) return { lipsa: 'the committed settings.gradle(.kts) sets no rootProject.name, which names the SBOM root' }; + try { return { bom: sbomGradle({ verificationMetadata: vm, lockfile: blobDin(repoDir, commit, `${rel}/gradle.lockfile`), projectName: nm[1], version }) }; } + catch (e) { return { lipsa: 'the committed Gradle verification metadata cannot be read: ' + String(e.message || e).slice(0, 120) }; } +} function sbomRebuildCheck(att, repoDir, sbomPath, ok, checks) { const s = att.statement.source; if (!att.statement.sbom) return; if (!sbomPath) { checks.push({ name: 'rebuild: SBOM', pass: null, detail: 'the attested SBOM was not handed to verify, so it was not compared with the committed lockfile' }); return; } let atestat; try { atestat = JSON.parse(fs.readFileSync(sbomPath, 'utf8')); } catch { return ok('rebuild: the attested SBOM parses', false, 'not JSON'); } - // 1.4.0: un SBOM scris de `sbom-go` se re-deriva din go.sum/go.mod comise; oricare altul, din package-lock.json (npm sbom) - const go = esteSbomGo(atestat); - const r = go ? sbomGoFromTree(repoDir, s.commit, s.path, att.statement.subject && att.statement.subject.version) : sbomNpmFromTree(repoDir, `${s.commit}:${s.path}`); + // 1.4.0: un SBOM scris de `sbom-go` se re-deriva din go.sum/go.mod comise; 1.5.0: unul scris de `sbom-nuget` din packages.lock.json, + // unul scris de `sbom-gradle` din gradle/verification-metadata.xml (+ gradle.lockfile); oricare altul, din package-lock.json (npm sbom) + // 1.5.0 (B-21, 2026-09-30): pana la 1.4.0 felul il alegea SBOM-ul atestat, deci un SBOM npm editat de mana care spunea + // `aere:derived-from: go.sum` scapa de judecata (NEJUDECAT, verdict VALID, cu motivul "arborele nu are go.mod"), la fel un SBOM Go + // editat caruia i se scotea proprietatea (judecat ca npm, "arborele nu are package-lock.json"). Acum proprietatea e o afirmatie + // despre ARBORE: un fisier numit care nu e in arborele comis e o afirmatie FALSA; iar un SBOM care nu isi numeste fisierul, intr-un + // arbore fara package-lock.json, ramane nejudecat, dar motivul numeste fisierele din care arborele se POATE judeca. + const din = derivatDin(atestat), ver = att.statement.subject && att.statement.subject.version; + const inArbore = (f) => { try { execFileSync('git', ['cat-file', '-e', `${s.commit}:${s.path}/${f}`], { cwd: repoDir, stdio: 'ignore' }); return true; } catch { return false; } }; + const UNEALTA = { 'go.sum': 'sbom-go', 'packages.lock.json': 'sbom-nuget', 'gradle/verification-metadata.xml': 'sbom-gradle', 'package-lock.json': 'npm sbom' }; + const prezente = Object.keys(UNEALTA).filter(inArbore); + if (din && !inArbore(din)) { + return ok('rebuild: the file the attested SBOM says it was derived from is in the committed tree', false, + `the SBOM says it was derived from ${din}, which ${s.commit.slice(0, 12)}:${s.path} does not have${prezente.length ? `; the tree has ${prezente.join(', ')}` : ''}`); + } + // un SBOM fara fisier numit (npm sbom, alte unelte) sau cu unul pe care verificatorul nu il stie se judeca fata de package-lock.json, + // ca pana acum; fara package-lock.json, dar cu alt fisier cunoscut in arbore, ramane nejudecat si motivul spune din ce se putea judeca + // (un nume necunoscut nu poate scoate un arbore npm de sub judecata: altfel oricare fisier al arborelui, numit, ar fi o scapare) + const FEL = { 'go.sum': 'go', 'packages.lock.json': 'nuget', 'gradle/verification-metadata.xml': 'gradle' }; + const fel = typeof din === 'string' && Object.hasOwn(FEL, din) ? FEL[din] : 'npm'; + if (fel === 'npm' && !prezente.includes('package-lock.json') && prezente.length) { + const cum = din ? `says it was derived from ${din}, which this verifier does not re-derive,` : 'names no lockfile (npm sbom and other tools do not)'; + checks.push({ name: 'rebuild: SBOM', pass: null, detail: `NOT JUDGED although the tree can be: the attested SBOM ${cum} and the committed tree has no package-lock.json; it has ${prezente.join(', ')}, and an SBOM made from it with pos.mjs ${prezente.map((f) => UNEALTA[f]).join(' / ')} is judged here` }); + return; + } + const r = fel === 'go' ? sbomGoFromTree(repoDir, s.commit, s.path, ver) + : fel === 'nuget' ? sbomNugetFromTree(repoDir, s.commit, s.path, ver) + : fel === 'gradle' ? sbomGradleFromTree(repoDir, s.commit, s.path, ver) + : sbomNpmFromTree(repoDir, `${s.commit}:${s.path}`); if (r.lipsa) { checks.push({ name: 'rebuild: SBOM', pass: null, detail: r.lipsa }); return; } const a = sbomSemantica(atestat), b = sbomSemantica(r.bom); const aceleasi = JSON.stringify(a) === JSON.stringify(b); @@ -319,7 +587,8 @@ function sbomRebuildCheck(att, repoDir, sbomPath, ok, checks) { const doarA = [...pa].filter((x) => !pb.has(x)).length, doarB = [...pb].filter((x) => !pa.has(x)).length; detaliu = `${doarA} component(s) only in the attested SBOM, ${doarB} only in the lockfile; graph ${JSON.stringify(a.dependente) === JSON.stringify(b.dependente) ? 'same' : 'differs'}; root ${a.radacina === b.radacina ? 'same' : 'differs'}`; } - ok(`rebuild: the attested SBOM says what the committed ${go ? 'go.sum pins' : 'lockfile says'} (${b.componente.length} components)`, aceleasi, detaliu); + const spune = { go: 'go.sum pins', nuget: 'packages.lock.json resolves', gradle: 'Gradle verification metadata pins' }[fel] || 'lockfile says'; + ok(`rebuild: the attested SBOM says what the committed ${spune} (${b.componente.length} components)`, aceleasi, detaliu); } // Rebuild check: from a clone the VERIFIER chose, take the attested commit, require that : is the attested @@ -539,6 +808,21 @@ async function main() { console.log(`SBOM written to ${out}: ${r.bom.components.length} module(s) pinned in go.sum, ${r.bom.metadata.properties[1].value} pinned by go.mod only (not listed)`); return; } + if (cmd === 'sbom-nuget' || cmd === 'sbom-gradle') { + // 1.5.0: din arborele COMIS, ca sbom-go: exact ce re-deriva verify --rebuild-from + const src = arg(args, '--source-path'); const out = arg(args, '--out', 'sbom.cdx.json'); const version = arg(args, '--version'); + const commit = arg(args, '--commit', 'HEAD'); + if (!src) throw new Error(`${cmd} needs --source-path `); + const top = git(process.cwd(), ['rev-parse', '--show-toplevel']); if (!top) throw new Error(`${cmd} needs a git checkout`); + const rel = path.relative(path.resolve(top), path.resolve(src)).split(path.sep).join('/'); + if (!rel || rel.startsWith('..')) throw new Error(`--source-path ${src}: must be a directory below the repository root`); + const r = (cmd === 'sbom-nuget' ? sbomNugetFromTree : sbomGradleFromTree)(top, git(top, ['rev-parse', commit]), rel, version); + if (r.lipsa) throw new Error(r.lipsa); + fs.writeFileSync(out, JSON.stringify(r.bom, null, 1) + '\n'); + const cu = r.bom.components.filter((c) => c.purl).length; + console.log(`SBOM written to ${out}: ${cu} package(s)${cmd === 'sbom-nuget' ? `, ${r.bom.components.length - cu} project reference(s), ${r.bom.dependencies.length} dependency record(s)` : ', no dependency graph (the Gradle lock files do not record it)'}`); + return; + } if (cmd === 'model-bom') { const dir = arg(args, '--model-dir'); const out = arg(args, '--out', 'mlbom.json'); const name = arg(args, '--name'); const version = arg(args, '--version'); const task = arg(args, '--task'); @@ -550,7 +834,7 @@ async function main() { console.log('attest it with: node pos.mjs attest --base ' + dir + ' --sbom ' + out + ' --out attestation.json '); return; } - console.log('usage: pos.mjs keygen|pack-npm|sbom-go|attest|verify|model-bom ... (see README.md)'); + console.log('usage: pos.mjs keygen|pack-npm|sbom-go|sbom-nuget|sbom-gradle|attest|verify|model-bom ... (see README.md)'); process.exitCode = 2; } if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main().catch((e) => { console.error('error:', e.message || e); process.exitCode = 1; }); diff --git a/tools/proof-of-software/test/_control.mjs b/tools/proof-of-software/test/_control.mjs index 4b112ae..aad45b1 100644 --- a/tools/proof-of-software/test/_control.mjs +++ b/tools/proof-of-software/test/_control.mjs @@ -2,16 +2,19 @@ // temporar; depozitul nu se atinge), un paznic scos in pos.mjs-ul copiei, suita rulata pe copie. Martorul (copia neatinsa) trebuie // verde; fiecare plantare trebuie sa inroseasca EXACT proba numita, cu suita chiar rulata (rezumatul ei exista). O ancora care nu // apare exact o data e un esec al controlului (STRICAT), nu o linie informativa. +// 1.5.0 (2026-09-29): plantarile ruleaza cate PARALEL deodata (fiecare in copia ei), ca toate controalele sa incapa in bugetul portii +// D-424; rezultatele se tiparesc in ordinea plantarilor. Deci controleaza() intoarce o promisiune: `process.exitCode = await controleaza(...)`. import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; -import { spawnSync } from 'node:child_process'; +import { spawn } from 'node:child_process'; import { fileURLToPath } from 'node:url'; const AICI = path.dirname(fileURLToPath(import.meta.url)); const RAD = path.resolve(AICI, '..', '..', '..'); -export function controleaza(suita, plantari) { +const PARALEL = 4; +export async function controleaza(suita, plantari) { function copie() { const t = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-cn-')); for (const d of ['sdk-pq-sign', 'sdk']) fs.cpSync(path.join(RAD, d), path.join(t, d), { recursive: true }); @@ -25,22 +28,32 @@ export function controleaza(suita, plantari) { return t; } function ruleaza(t) { - const r = spawnSync(process.execPath, [path.join(t, 'tools', 'proof-of-software', 'test', suita)], { encoding: 'utf8', timeout: 400000 }); - const out = (r.stdout || '') + (r.stderr || ''); - return { cod: r.status, rulat: /\d+ treceri, \d+ esecuri/.test(out), picate: out.split('\n').filter((l) => l.startsWith(' ESEC')) }; + return new Promise((resolve) => { + const c = spawn(process.execPath, [path.join(t, 'tools', 'proof-of-software', 'test', suita)]); let out = ''; + const ceas = setTimeout(() => c.kill(), 400000); + c.stdout.on('data', (x) => { out += x; }); c.stderr.on('data', (x) => { out += x; }); + c.on('close', (cod) => { clearTimeout(ceas); resolve({ cod, rulat: /\d+ treceri, \d+ esecuri/.test(out), picate: out.split('\n').filter((l) => l.startsWith(' ESEC')) }); }); + }); + } + async function planteaza([nume, din, inl, tinta]) { + const t = copie(); + try { + const f = path.join(t, 'tools', 'proof-of-software', 'pos.mjs'); const src = fs.readFileSync(f, 'utf8'); + if (src.split(din).length !== 2) return [false, ` STRICAT ${nume}: ancora apare de ${src.split(din).length - 1} ori`]; + fs.writeFileSync(f, src.replace(din, inl)); + const r = await ruleaza(t); + if (!r.rulat) return [false, ` STRICAT ${nume}: suita nu a ajuns la rezumat (cod ${r.cod})`]; + if (r.picate.some((l) => l.includes(tinta))) return [true, ` ROSU cum trebuia ${nume} (proba '${tinta}' pica)`]; + return [false, ` CONTROL CAZUT ${nume}: proba '${tinta}' a ramas verde (${r.picate.length} esecuri altundeva)`]; + } catch (e) { return [false, ` STRICAT ${nume}: controlul a cazut pe ea (${String(e.message || e).slice(0, 80)})`]; } + finally { fs.rmSync(t, { recursive: true, force: true }); } } let rele = 0; - const t0 = copie(); const m = ruleaza(t0); fs.rmSync(t0, { recursive: true, force: true }); + const t0 = copie(); const m = await ruleaza(t0); fs.rmSync(t0, { recursive: true, force: true }); if (m.cod === 0 && m.rulat && !m.picate.length) console.log(' OK martorul: copia neatinsa verde'); else { rele++; console.log(` STRICAT martorul nu e verde (cod ${m.cod}, ${m.picate.length} esecuri)`); } - for (const [nume, din, inl, tinta] of plantari) { - const t = copie(); const f = path.join(t, 'tools', 'proof-of-software', 'pos.mjs'); const src = fs.readFileSync(f, 'utf8'); - if (src.split(din).length !== 2) { rele++; console.log(` STRICAT ${nume}: ancora apare de ${src.split(din).length - 1} ori`); fs.rmSync(t, { recursive: true, force: true }); continue; } - fs.writeFileSync(f, src.replace(din, inl)); - const r = ruleaza(t); fs.rmSync(t, { recursive: true, force: true }); - if (!r.rulat) { rele++; console.log(` STRICAT ${nume}: suita nu a ajuns la rezumat (cod ${r.cod})`); continue; } - if (r.picate.some((l) => l.includes(tinta))) console.log(` ROSU cum trebuia ${nume} (proba '${tinta}' pica)`); - else { rele++; console.log(` CONTROL CAZUT ${nume}: proba '${tinta}' a ramas verde (${r.picate.length} esecuri altundeva)`); } - } + const rez = new Array(plantari.length); let i = 0; + await Promise.all(Array.from({ length: PARALEL }, async () => { while (i < plantari.length) { const k = i++; rez[k] = await planteaza(plantari[k]); } })); + for (const [bun, linie] of rez) { console.log(linie); if (!bun) rele++; } console.log(rele ? `CONTROL NEGATIV: ${rele} probleme` : `DOVEDIT: martorul verde, ${plantari.length} din ${plantari.length} paznici scosi -> proba lor rosie`); return rele ? 1 : 0; } diff --git a/tools/proof-of-software/test/credential-control-negativ.mjs b/tools/proof-of-software/test/credential-control-negativ.mjs index aad31a4..b1448f6 100644 --- a/tools/proof-of-software/test/credential-control-negativ.mjs +++ b/tools/proof-of-software/test/credential-control-negativ.mjs @@ -3,7 +3,7 @@ // node aerenew/tools/proof-of-software/test/credential-control-negativ.mjs import { controleaza } from './_control.mjs'; -process.exitCode = controleaza('credential.test.mjs', [ +process.exitCode = await controleaza('credential.test.mjs', [ ['radacina de incredere nu se mai compara', "keyId(publicKeysOf(trustIssuer)) === keyId(b.issuer && b.issuer.keys)", 'true', '2. CONTROL'], ['semnatarul acreditarii nu mai trebuie sa fie emitentul numit', 'sv.valid && semnatarEmitent,', 'sv.valid,', '6. CONTROL'], ['cheile subiectului nu se mai compara cu semnatarul declaratiei', "!!att.signature && keyId(publicKeysOf(att.signature)) === keyId(b.subject && b.subject.keys)", '!!att.signature', '7. CONTROL'], diff --git a/tools/proof-of-software/test/fixturi-gradle/build.gradle b/tools/proof-of-software/test/fixturi-gradle/build.gradle new file mode 100644 index 0000000..23f94cb --- /dev/null +++ b/tools/proof-of-software/test/fixturi-gradle/build.gradle @@ -0,0 +1,9 @@ +plugins { id 'java-library' } +group = 'network.aere.fixture' +version = '1.0.0' +repositories { mavenCentral() } +dependencies { + implementation 'org.bouncycastle:bcpkix-jdk18on:1.79' + implementation 'com.google.code.gson:gson:2.10.1' +} +dependencyLocking { lockAllConfigurations() } diff --git a/tools/proof-of-software/test/fixturi-gradle/genereaza.sh b/tools/proof-of-software/test/fixturi-gradle/genereaza.sh new file mode 100644 index 0000000..a55f14b --- /dev/null +++ b/tools/proof-of-software/test/fixturi-gradle/genereaza.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# genereaza.sh: cum s-au facut fixturile Gradle (2026-09-29): un proiect Java mic, rezolvat de Gradle INSUSI, `--offline` (numai din +# memoria locala a lui Gradle, nicio descarcare), cu `--write-verification-metadata sha256,sha512` (gradle/verification-metadata.xml, +# hash-urile fiecarui artefact verificat) si `--write-locks` (gradle.lockfile: coordonatele fiecarei configuratii). Deci ambele fisiere +# sunt scrise de Gradle, nu de noi. Dependinte: bcpkix-jdk18on 1.79 (aduce bcutil si bcprov) si gson 2.10.1. +# bash genereaza.sh [gradle] rescrie build.gradle, settings.gradle, gradle.lockfile, gradle/verification-metadata.xml +set -eu +AICI="$(cd "$(dirname "$0")" && pwd)" +GR="${1:-$(ls -d "$HOME"/.gradle/wrapper/dists/gradle-8.14.3-bin/*/gradle-8.14.3 2>/dev/null | head -1)/bin/gradle}" +[ -f "$GR" ] || { echo "NEMASURAT: nu gasesc distributia Gradle 8.14.3 in ~/.gradle/wrapper/dists (sau dati calea)"; exit 2; } +T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT +cat > "$T/settings.gradle" <<'EOF' +rootProject.name = 'pos-fixture' +EOF +cat > "$T/build.gradle" <<'EOF' +plugins { id 'java-library' } +group = 'network.aere.fixture' +version = '1.0.0' +repositories { mavenCentral() } +dependencies { + implementation 'org.bouncycastle:bcpkix-jdk18on:1.79' + implementation 'com.google.code.gson:gson:2.10.1' +} +dependencyLocking { lockAllConfigurations() } +EOF +( cd "$T" && bash "$GR" --offline --no-daemon -q --write-verification-metadata sha256,sha512 dependencies --write-locks < /dev/null > "$T/out.txt" 2>&1 ) || { tail -20 "$T/out.txt"; exit 1; } +mkdir -p "$AICI/gradle" +cp "$T/settings.gradle" "$T/build.gradle" "$T/gradle.lockfile" "$AICI/" +cp "$T/gradle/verification-metadata.xml" "$AICI/gradle/" +echo "scris: gradle.lockfile ($(grep -c '=' "$AICI/gradle.lockfile") randuri), gradle/verification-metadata.xml ($(grep -c ' + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/proof-of-software/test/fixturi-gradle/settings.gradle b/tools/proof-of-software/test/fixturi-gradle/settings.gradle new file mode 100644 index 0000000..fa6d24d --- /dev/null +++ b/tools/proof-of-software/test/fixturi-gradle/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'pos-fixture' diff --git a/tools/proof-of-software/test/fixturi-nuget/genereaza.sh b/tools/proof-of-software/test/fixturi-nuget/genereaza.sh new file mode 100644 index 0000000..c38987f --- /dev/null +++ b/tools/proof-of-software/test/fixturi-nuget/genereaza.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# genereaza.sh: cum s-au facut fixturile NuGet (2026-09-29): un proiect mic, restaurat de .NET SDK INSUSI cu lockfile-ul pornit, FARA +# retea: nuget.config sterge toate sursele si pune ca sursa unica memoria locala de pachete (~/.nuget/packages, asezarea ei e a unui +# dosar-sursa ierarhic), iar pachetele se extrag intr-un dosar temporar (memoria locala nu e modificata). Deci packages.lock.json e +# scris de NuGet, nu de noi. Doua proiecte: `lib` (net8.0 si net10.0, Newtonsoft.Json si BouncyCastle.Cryptography) si `lib.tests` +# (net8.0, xunit, cu referinta de proiect catre lib: intrari de tip Project si multe tranzitive). +# bash genereaza.sh [dotnet] rescrie lib/packages.lock.json si lib.tests/packages.lock.json +set -eu +AICI="$(cd "$(dirname "$0")" && pwd)" +DOTNET="${1:-$HOME/.dotnet/dotnet.exe}" +SURSA="$HOME/.nuget/packages" +[ -d "$SURSA" ] || { echo "NEMASURAT: nu exista memoria locala de pachete $SURSA"; exit 2; } +T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT +cat > "$T/nuget.config" < + + /dev/null || echo "$SURSA")" /> + /dev/null || echo "$T/pk")" /> + +EOF +mkdir -p "$T/lib" "$T/lib.tests" +cat > "$T/lib/lib.csproj" <<'EOF' + + + net8.0;net10.0 + true + false + + + + + + +EOF +cat > "$T/lib.tests/lib.tests.csproj" <<'EOF' + + + net8.0 + false + true + false + + + + + + + + + +EOF +export DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1 DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 DOTNET_CLI_WORKLOAD_UPDATE_NOTIFY_DISABLE=1 +( cd "$T/lib.tests" && "$DOTNET" restore --configfile "$(cygpath -w "$T/nuget.config" 2>/dev/null || echo "$T/nuget.config")" < /dev/null ) +mkdir -p "$AICI/lib" "$AICI/lib.tests" +# fisierele de proiect se pastreaza langa lockfile (numele proiectului e radacina SBOM-ului), fara nicio schimbare +cp "$T/lib/lib.csproj" "$T/lib/packages.lock.json" "$AICI/lib/" +cp "$T/lib.tests/lib.tests.csproj" "$T/lib.tests/packages.lock.json" "$AICI/lib.tests/" +# NuGet scrie contentHash-ul sau (SHA-512 peste continutul pachetului, fara semnatura de depozit) si in .nupkg.metadata: il copiem +# pentru controlul "hash-ul din lockfile e al lui NuGet pentru acel pachet" (nu e SHA-512 al fisierului .nupkg semnat) +( cd "$T/pk" && for m in */*/.nupkg.metadata; do printf '%s %s\n' "$(dirname "$m")" "$(grep -o '"contentHash": *"[^"]*"' "$m" | cut -d'"' -f4)"; done ) | sort > "$AICI/nupkg-metadata-contenthash.txt" +echo "scris: lib/packages.lock.json, lib.tests/packages.lock.json, nupkg-metadata-contenthash.txt ($(wc -l < "$AICI/nupkg-metadata-contenthash.txt") pachete)" diff --git a/tools/proof-of-software/test/fixturi-nuget/lib.tests/lib.tests.csproj b/tools/proof-of-software/test/fixturi-nuget/lib.tests/lib.tests.csproj new file mode 100644 index 0000000..ef9ee59 --- /dev/null +++ b/tools/proof-of-software/test/fixturi-nuget/lib.tests/lib.tests.csproj @@ -0,0 +1,15 @@ + + + net8.0 + false + true + false + + + + + + + + + diff --git a/tools/proof-of-software/test/fixturi-nuget/lib.tests/packages.lock.json b/tools/proof-of-software/test/fixturi-nuget/lib.tests/packages.lock.json new file mode 100644 index 0000000..c5d4a4c --- /dev/null +++ b/tools/proof-of-software/test/fixturi-nuget/lib.tests/packages.lock.json @@ -0,0 +1,1033 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "tW3lsNS+dAEII6YGUX/VMoJjBS1QvsxqJeqLaJXub08y1FSjasFPtQ4UBUsudE9PNrzLjooClMsPtY2cZLdXpQ==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.8.0, )", + "resolved": "17.8.0", + "contentHash": "BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw==", + "dependencies": { + "Microsoft.CodeCoverage": "17.8.0", + "Microsoft.TestPlatform.TestHost": "17.8.0" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.5.3, )", + "resolved": "2.5.3", + "contentHash": "VxYDiWSwrLxOJ3UEN+ZPrBybB0SFShQ1E6PjT65VdoKCJhorgerFznThjSwawRH/WAip73YnucDVsE8WRj/8KQ==", + "dependencies": { + "xunit.analyzers": "1.4.0", + "xunit.assert": "2.5.3", + "xunit.core": "[2.5.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[2.5.3, )", + "resolved": "2.5.3", + "contentHash": "HFFL6O+QLEOfs555SqHii48ovVa4CqGYanY+B32BjLpPptdE+wEJmCFNXlLHdEOD5LYeayb9EroaUpydGpcybg==" + }, + "BouncyCastle.Cryptography": { + "type": "Transitive", + "resolved": "2.6.2", + "contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.8.0", + "contentHash": "KC8SXWbGIdoFVdlxKk9WHccm0llm9HypcHMLUUFabRiTS3SO2fQXNZfdiF3qkEdTJhbRrxhdRxjL4jbtwPq4Ew==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.8.0", + "contentHash": "AYy6vlpGMfz5kOFq99L93RGbqftW/8eQTqjT9iGXW6s9MRP3UdtY8idJ8rJcjeSja8A18IhIro5YnH3uv1nz4g==", + "dependencies": { + "NuGet.Frameworks": "6.5.0", + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.8.0", + "contentHash": "9ivcl/7SGRmOT0YYrHQGohWiT5YCpkmy/UEzldfVisLm6QxbLaK3FAJqZXI34rnRLmqqDCeMQxKINwmKwAPiDw==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.8.0", + "Newtonsoft.Json": "13.0.1" + } + }, + "Microsoft.Win32.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "NETStandard.Library": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.Win32.Primitives": "4.3.0", + "System.AppContext": "4.3.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Console": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tools": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.Compression": "4.3.0", + "System.IO.Compression.ZipFile": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Linq": "4.3.0", + "System.Linq.Expressions": "4.3.0", + "System.Net.Http": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Net.Sockets": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Text.RegularExpressions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "System.Threading.Timer": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0", + "System.Xml.XDocument": "4.3.0" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "NuGet.Frameworks": { + "type": "Transitive", + "resolved": "6.5.0", + "contentHash": "QWINE2x3MbTODsWT1Gh71GaGb5icBz4chS8VYvTgsBnsi8esgN6wtHhydd7fvToWECYGq7T4cgBBDiKD/363fg==" + }, + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" + }, + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" + }, + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" + }, + "runtime.native.System": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", + "dependencies": { + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" + } + }, + "runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", + "dependencies": { + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" + }, + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" + }, + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" + }, + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" + }, + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" + }, + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" + }, + "System.AppContext": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", + "dependencies": { + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Collections.Concurrent": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Console": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Diagnostics.Tools": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Calendars": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Buffers": "4.3.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.IO.Compression": "4.3.0" + } + }, + "System.IO.Compression.ZipFile": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", + "dependencies": { + "System.Buffers": "4.3.0", + "System.IO": "4.3.0", + "System.IO.Compression": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.IO.FileSystem": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.FileSystem.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Linq": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Linq.Expressions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Linq": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Emit.Lightweight": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.DiagnosticSource": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Net.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Net.Sockets": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.ObjectModel": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", + "dependencies": { + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit.ILGeneration": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit.Lightweight": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.TypeExtensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0" + } + }, + "System.Runtime.Numerics": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", + "dependencies": { + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.Apple": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Cng": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Security.Cryptography.Csp": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Linq": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", + "dependencies": { + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", + "dependencies": { + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Cng": "4.3.0", + "System.Security.Cryptography.Csp": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Text.RegularExpressions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", + "dependencies": { + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Threading.Timer": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Xml.ReaderWriter": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Text.RegularExpressions": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "System.Threading.Tasks.Extensions": "4.3.0" + } + }, + "System.Xml.XDocument": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tools": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "7ljnTJfFjz5zK+Jf0h2dd2QOSO6UmFizXsojv/x4QX7TU5vEgtKZPk9RvpkiuUqg2bddtNZufBoKQalsi7djfA==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.5.3", + "contentHash": "MK3HiBckO3vdxEdUxXZyyRPsBNPsC/nz6y1gj/UZIZkjMnsVQyZPU8yxS/3cjTchYcqskt/nqUOS5wmD8JezdQ==", + "dependencies": { + "NETStandard.Library": "1.6.1" + } + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.5.3", + "contentHash": "FE8yEEUkoMLd6kOHDXm/QYfX/dYzwc0c+Q4MQon6VGRwFuy6UVGwK/CFA5LEea+ZBEmcco7AEl2q78VjsA0j/w==", + "dependencies": { + "xunit.extensibility.core": "[2.5.3]", + "xunit.extensibility.execution": "[2.5.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.5.3", + "contentHash": "IjAQlPeZWXP89pl1EuOG9991GH1qgAL0rQfkmX2UV+PDenbYb7oBnQopL9ujE6YaXxgaQazp7lFjsDyyxD6Mtw==", + "dependencies": { + "NETStandard.Library": "1.6.1", + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.5.3", + "contentHash": "w9eGCHl+gJj1GzZSf0VTzYPp/gv4fiUDkr+yR7/Wv9/ucO2CHltGg2TnyySLFjzekkjuxVJZUE+tZyDNzryJFw==", + "dependencies": { + "NETStandard.Library": "1.6.1", + "xunit.extensibility.core": "[2.5.3]" + } + }, + "lib": { + "type": "Project", + "dependencies": { + "BouncyCastle.Cryptography": "[2.6.2, )", + "Newtonsoft.Json": "[13.0.1, )" + } + } + } + } +} \ No newline at end of file diff --git a/tools/proof-of-software/test/fixturi-nuget/lib/lib.csproj b/tools/proof-of-software/test/fixturi-nuget/lib/lib.csproj new file mode 100644 index 0000000..5ae3907 --- /dev/null +++ b/tools/proof-of-software/test/fixturi-nuget/lib/lib.csproj @@ -0,0 +1,11 @@ + + + net8.0;net10.0 + true + false + + + + + + diff --git a/tools/proof-of-software/test/fixturi-nuget/lib/packages.lock.json b/tools/proof-of-software/test/fixturi-nuget/lib/packages.lock.json new file mode 100644 index 0000000..f02432a --- /dev/null +++ b/tools/proof-of-software/test/fixturi-nuget/lib/packages.lock.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "BouncyCastle.Cryptography": { + "type": "Direct", + "requested": "[2.6.2, )", + "resolved": "2.6.2", + "contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" + }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.1, )", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + } + }, + "net8.0": { + "BouncyCastle.Cryptography": { + "type": "Direct", + "requested": "[2.6.2, )", + "resolved": "2.6.2", + "contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" + }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.1, )", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + } + } + } +} \ No newline at end of file diff --git a/tools/proof-of-software/test/fixturi-nuget/nupkg-metadata-contenthash.txt b/tools/proof-of-software/test/fixturi-nuget/nupkg-metadata-contenthash.txt new file mode 100644 index 0000000..e0f1b40 --- /dev/null +++ b/tools/proof-of-software/test/fixturi-nuget/nupkg-metadata-contenthash.txt @@ -0,0 +1,90 @@ +bouncycastle.cryptography/2.6.2 7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w== +coverlet.collector/6.0.0 tW3lsNS+dAEII6YGUX/VMoJjBS1QvsxqJeqLaJXub08y1FSjasFPtQ4UBUsudE9PNrzLjooClMsPtY2cZLdXpQ== +microsoft.codecoverage/17.8.0 KC8SXWbGIdoFVdlxKk9WHccm0llm9HypcHMLUUFabRiTS3SO2fQXNZfdiF3qkEdTJhbRrxhdRxjL4jbtwPq4Ew== +microsoft.net.test.sdk/17.8.0 BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw== +microsoft.netcore.platforms/1.1.0 kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A== +microsoft.netcore.targets/1.1.0 aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg== +microsoft.testplatform.objectmodel/17.8.0 AYy6vlpGMfz5kOFq99L93RGbqftW/8eQTqjT9iGXW6s9MRP3UdtY8idJ8rJcjeSja8A18IhIro5YnH3uv1nz4g== +microsoft.testplatform.testhost/17.8.0 9ivcl/7SGRmOT0YYrHQGohWiT5YCpkmy/UEzldfVisLm6QxbLaK3FAJqZXI34rnRLmqqDCeMQxKINwmKwAPiDw== +microsoft.win32.primitives/4.3.0 9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA== +netstandard.library/1.6.1 WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A== +newtonsoft.json/13.0.1 ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A== +nuget.frameworks/6.5.0 QWINE2x3MbTODsWT1Gh71GaGb5icBz4chS8VYvTgsBnsi8esgN6wtHhydd7fvToWECYGq7T4cgBBDiKD/363fg== +runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl/4.3.0 HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q== +runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl/4.3.0 +yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA== +runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl/4.3.0 c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw== +runtime.native.system.io.compression/4.3.0 INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ== +runtime.native.system.net.http/4.3.0 ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg== +runtime.native.system.security.cryptography.apple/4.3.0 DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q== +runtime.native.system.security.cryptography.openssl/4.3.0 NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw== +runtime.native.system/4.3.0 c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw== +runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl/4.3.0 b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A== +runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl/4.3.0 KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ== +runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple/4.3.0 kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ== +runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0 X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g== +runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl/4.3.0 nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg== +runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0 ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ== +runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0 I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A== +runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0 VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg== +system.appcontext/4.3.0 fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA== +system.buffers/4.3.0 ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ== +system.collections.concurrent/4.3.0 ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ== +system.collections/4.3.0 3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw== +system.console/4.3.0 DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg== +system.diagnostics.debug/4.3.0 ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g== +system.diagnostics.diagnosticsource/4.3.0 tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA== +system.diagnostics.tools/4.3.0 UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA== +system.diagnostics.tracing/4.3.0 rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw== +system.globalization.calendars/4.3.0 GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA== +system.globalization.extensions/4.3.0 FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ== +system.globalization/4.3.0 kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg== +system.io.compression.zipfile/4.3.0 G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg== +system.io.compression/4.3.0 YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg== +system.io.filesystem.primitives/4.3.0 6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw== +system.io.filesystem/4.3.0 3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA== +system.io/4.3.0 3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg== +system.linq.expressions/4.3.0 PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg== +system.linq/4.3.0 5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw== +system.net.http/4.3.0 sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA== +system.net.primitives/4.3.0 qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA== +system.net.sockets/4.3.0 m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw== +system.objectmodel/4.3.0 bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ== +system.reflection.emit.ilgeneration/4.3.0 59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng== +system.reflection.emit.lightweight/4.3.0 oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA== +system.reflection.emit/4.3.0 228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg== +system.reflection.extensions/4.3.0 rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ== +system.reflection.metadata/1.6.0 COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ== +system.reflection.primitives/4.3.0 5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA== +system.reflection.typeextensions/4.3.0 7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA== +system.reflection/4.3.0 KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ== +system.resources.resourcemanager/4.3.0 /zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A== +system.runtime.extensions/4.3.0 guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g== +system.runtime.handles/4.3.0 OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg== +system.runtime.interopservices.runtimeinformation/4.3.0 cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw== +system.runtime.interopservices/4.3.0 uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ== +system.runtime.numerics/4.3.0 yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ== +system.runtime/4.3.0 JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw== +system.security.cryptography.algorithms/4.3.0 W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg== +system.security.cryptography.cng/4.3.0 03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g== +system.security.cryptography.csp/4.3.0 X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA== +system.security.cryptography.encoding/4.3.0 1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw== +system.security.cryptography.openssl/4.3.0 h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w== +system.security.cryptography.primitives/4.3.0 7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg== +system.security.cryptography.x509certificates/4.3.0 t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw== +system.text.encoding.extensions/4.3.0 YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw== +system.text.encoding/4.3.0 BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw== +system.text.regularexpressions/4.3.0 RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA== +system.threading.tasks.extensions/4.3.0 npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug== +system.threading.tasks/4.3.0 LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA== +system.threading.timer/4.3.0 Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ== +system.threading/4.3.0 VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw== +system.xml.readerwriter/4.3.0 GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA== +system.xml.xdocument/4.3.0 5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ== +xunit.abstractions/2.0.3 pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg== +xunit.analyzers/1.4.0 7ljnTJfFjz5zK+Jf0h2dd2QOSO6UmFizXsojv/x4QX7TU5vEgtKZPk9RvpkiuUqg2bddtNZufBoKQalsi7djfA== +xunit.assert/2.5.3 MK3HiBckO3vdxEdUxXZyyRPsBNPsC/nz6y1gj/UZIZkjMnsVQyZPU8yxS/3cjTchYcqskt/nqUOS5wmD8JezdQ== +xunit.core/2.5.3 FE8yEEUkoMLd6kOHDXm/QYfX/dYzwc0c+Q4MQon6VGRwFuy6UVGwK/CFA5LEea+ZBEmcco7AEl2q78VjsA0j/w== +xunit.extensibility.core/2.5.3 IjAQlPeZWXP89pl1EuOG9991GH1qgAL0rQfkmX2UV+PDenbYb7oBnQopL9ujE6YaXxgaQazp7lFjsDyyxD6Mtw== +xunit.extensibility.execution/2.5.3 w9eGCHl+gJj1GzZSf0VTzYPp/gv4fiUDkr+yR7/Wv9/ucO2CHltGg2TnyySLFjzekkjuxVJZUE+tZyDNzryJFw== +xunit.runner.visualstudio/2.5.3 HFFL6O+QLEOfs555SqHii48ovVa4CqGYanY+B32BjLpPptdE+wEJmCFNXlLHdEOD5LYeayb9EroaUpydGpcybg== +xunit/2.5.3 VxYDiWSwrLxOJ3UEN+ZPrBybB0SFShQ1E6PjT65VdoKCJhorgerFznThjSwawRH/WAip73YnucDVsE8WRj/8KQ== diff --git a/tools/proof-of-software/test/sbom-control-negativ.mjs b/tools/proof-of-software/test/sbom-control-negativ.mjs index 4e2d259..1cb19f2 100644 --- a/tools/proof-of-software/test/sbom-control-negativ.mjs +++ b/tools/proof-of-software/test/sbom-control-negativ.mjs @@ -2,9 +2,15 @@ // node aerenew/tools/proof-of-software/test/sbom-control-negativ.mjs import { controleaza } from './_control.mjs'; -process.exitCode = controleaza('sbom.test.mjs', [ +process.exitCode = await controleaza('sbom.test.mjs', [ ['comparatia SBOM-ului cu lockfile-ul scoasa', 'const aceleasi = JSON.stringify(a) === JSON.stringify(b);', 'const aceleasi = true;', 'CONTROL: o componenta scoasa'], // ancora pe partea STABILA a randului (1.4.0 i-a adaugat `props` la coada si ancora veche a murit: STRICAT, prins de chiar acest control) ['hash-urile de integritate scoase din forma semantica', "hashes: (c.hashes || []).map((h) => `${h.alg}:${String(h.content).toLowerCase()}`).sort(),", 'hashes: [],', 'CONTROL: un hash de integritate'], + // B-21 (2026-09-30): felul SBOM-ului il alegea SBOM-ul insusi; fara paznic, un fisier numit care nu e in arbore scapa nejudecat + ['un SBOM care numeste un fisier absent din arbore primit (B-21)', 'if (din && !inArbore(din)) {', 'if (false) {', 'CONTROL B-21: un SBOM npm editat care spune ca e derivat din go.sum'], + // prima forma a reparatiei B-21 (revizuita in aceeasi ora): un nume necunoscut, dar prezent in arbore, iesea nejudecat + ['un nume de fisier necunoscut scoate arborele npm de sub judecata (B-21)', " if (fel === 'npm' && !prezente.includes('package-lock.json') && prezente.length) {", + " if (din && !['go.sum', 'packages.lock.json', 'gradle/verification-metadata.xml'].includes(din)) { checks.push({ name: 'rebuild: SBOM', pass: null, detail: `derived from ${din}, which this verifier does not re-derive` }); return; }\n if (fel === 'npm' && !prezente.includes('package-lock.json') && prezente.length) {", + 'CONTROL B-21: un SBOM npm editat care numeste un fisier AL arborelui'], ['un arbore fara lockfile trecut drept re-derivat', "if (!fs.existsSync(path.join(src, 'package-lock.json'))) return { lipsa:", "if (false) return { lipsa:", 'fara package-lock'], ]); diff --git a/tools/proof-of-software/test/sbom-go-control-negativ.mjs b/tools/proof-of-software/test/sbom-go-control-negativ.mjs index 88bbc75..2c17bbc 100644 --- a/tools/proof-of-software/test/sbom-go-control-negativ.mjs +++ b/tools/proof-of-software/test/sbom-go-control-negativ.mjs @@ -3,12 +3,15 @@ // node aerenew/tools/proof-of-software/test/sbom-go-control-negativ.mjs import { controleaza } from './_control.mjs'; -process.exitCode = controleaza('sbom-go.test.mjs', [ +process.exitCode = await controleaza('sbom-go.test.mjs', [ ['hash-ul h1 scos din forma semantica', 'props: aere(c.properties) });', 'props: [] });', 'CONTROL: un hash h1 schimbat'], - ['SBOM-ul Go judecat ca unul npm', 'const go = esteSbomGo(atestat);', 'const go = false;', 'verify --rebuild-from: arborele e cel atestat'], + // 1.5.0 (B-21): felul se ia din tabla FEL (numele fisierului numit, verificat in arbore); fara intrarea go.sum, SBOM-ul Go cade pe npm + ['SBOM-ul Go judecat ca unul npm', "const FEL = { 'go.sum': 'go', ", 'const FEL = { ', 'verify --rebuild-from: arborele e cel atestat'], ['o atestare fara npm pack declarata falsa la reconstructie (forma 1.3.0)', "checks.push({ name: 'rebuild: artifacts', pass: null, detail:", "checks.push({ name: 'rebuild: artifacts', pass: false, detail:", 'verify --rebuild-from: arborele e cel atestat'], ['modulele fixate numai prin go.mod listate drept componente', '.filter((e) => e.h1).map((e) => {', '.filter((e) => true).map((e) => {', 'gomock: numai modulele cu continut'], ['un arbore fara go.sum trecut drept re-derivat', "const goSum = blob('go.sum'); if (goSum == null) return { lipsa:", "const goSum = blob('go.sum') ?? ''; if (false) return { lipsa:", 'un arbore fara go.sum'], + // B-21 (2026-09-30): un SBOM care nu isi numeste fisierul, intr-un arbore fara package-lock.json, trebuie sa spuna din ce se putea judeca + ['motivul nejudecarii nu mai numeste fisierele arborelui (B-21)', "if (fel === 'npm' && !prezente.includes('package-lock.json') && prezente.length) {", 'if (false) {', 'B-21: un SBOM Go editat'], ['numarul de serie aleator (SBOM-ul nu mai e determinist)', 'const b = crypto.createHash(\'sha256\').update(JSON.stringify({ metadata, components })).digest();', 'const b = crypto.randomBytes(32);', 'determinist'], ['radacina scoasa din comparatie', 'return { radacina: radacina && radacina.purl, meta:', 'return { radacina: null, meta:', 'CONTROL: SBOM-ul spune alt modul radacina'], ]); diff --git a/tools/proof-of-software/test/sbom-go.test.mjs b/tools/proof-of-software/test/sbom-go.test.mjs index 5e58b19..5637d43 100644 --- a/tools/proof-of-software/test/sbom-go.test.mjs +++ b/tools/proof-of-software/test/sbom-go.test.mjs @@ -97,6 +97,20 @@ await test('CONTROL: un modul scos din SBOM de mana, re-atestat -> prins la reco await test('CONTROL: o versiune schimbata in SBOM (si purl-ul ei) -> prins', () => editat('versiune', (b) => { const c = b.components.find((x) => /x\/sys/.test(x.name)); c.version = 'v0.48.0'; c.purl = c.purl.replace('v0.47.0', 'v0.48.0'); c['bom-ref'] = c.purl; })); await test('CONTROL: un hash h1 schimbat in SBOM -> prins', () => editat('h1', (b) => { const c = b.components[0]; c.properties = [{ name: 'aere:go-sum-h1', value: 'h1:' + 'A'.repeat(43) + '=' }]; })); await test('CONTROL: SBOM-ul spune alt modul radacina decat go.mod-ul comis -> prins', () => editat('radacina', (b) => { b.metadata.component.name = 'example.com/altul'; b.metadata.component.purl = 'pkg:golang/example.com/altul@v1.0.0'; })); +// B-21 (2026-09-30): un SBOM Go editat caruia i se scoate `aere:derived-from` era judecat ca npm si iesea NEJUDECAT cu motivul +// "arborele nu are package-lock.json", adica fara sa spuna ca arborele SE PUTEA judeca. Nu poate iesi fals (un SBOM facut cu alta +// unealta nu isi numeste fisierul, si nu e fals din asta), dar motivul trebuie sa numeasca go.sum si unealta care il judeca. +await test('B-21: un SBOM Go editat, fara aere:derived-from -> nejudecat, cu motivul care numeste go.sum si sbom-go (nu "nu se poate re-deriva")', async () => { + const bom = JSON.parse(fs.readFileSync(S, 'utf8')); + bom.components[0].properties = [{ name: 'aere:go-sum-h1', value: 'h1:' + 'A'.repeat(43) + '=' }]; + bom.metadata.properties = bom.metadata.properties.filter((p) => p.name !== 'aere:derived-from'); + const d = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-go-e-')); const f = path.join(d, 'app.sbom.cdx.json'); fs.writeFileSync(f, JSON.stringify(bom, null, 1)); + const a = await attest({ artifacts: [BIN], sbom: f, name: 'app', version: 'v1.0.0', sourcePath: 'mod', cwd: G }); + const r = await verify(a, [BIN, f], { rebuildFrom: C }); fs.rmSync(d, { recursive: true, force: true }); + const sb = check(r, /^rebuild: SBOM$/); + cere(sb && sb.pass === null && /names no lockfile/.test(sb.detail) && /it has go\.sum/.test(sb.detail) && /sbom-go/.test(sb.detail), JSON.stringify(sb)); + cere(r.checks.filter((c) => c.pass === null).some((c) => c === sb), 'randul nejudecat se numara in verdict'); +}); await test('un arbore fara go.sum comis: SBOM-ul nu se poate re-deriva, si se spune (nu e trecut drept VALID)', () => { const r = sbomGoFromTree(G, g(['rev-parse', 'HEAD'], G), 'fara', 'v1'); cere(!r.bom && /no go.sum/.test(r.lipsa || ''), JSON.stringify(r).slice(0, 120)); diff --git a/tools/proof-of-software/test/sbom-nuget-gradle-control-negativ.mjs b/tools/proof-of-software/test/sbom-nuget-gradle-control-negativ.mjs new file mode 100644 index 0000000..807dfc0 --- /dev/null +++ b/tools/proof-of-software/test/sbom-nuget-gradle-control-negativ.mjs @@ -0,0 +1,29 @@ +// Controlul negativ al SBOM-urilor .NET si Gradle (sbom-nuget-gradle.test.mjs, Proof of Software 1.5.0), prin mecanismul comun din +// _control.mjs: fiecare paznic scos intr-o copie a lui pos.mjs trebuie sa inroseasca EXACT proba lui, cu suita chiar rulata. +// node aerenew/tools/proof-of-software/test/sbom-nuget-gradle-control-negativ.mjs +import { controleaza } from './_control.mjs'; + +process.exitCode = await controleaza('sbom-nuget-gradle.test.mjs', [ + ['contentHash-ul NuGet scos din componenta', "'aere:nuget-content-hash': 'sha512-' + p.hash,", "'aere:nuget-content-hash': null,", 'CONTROL POZITIV al metodei: fiecare hash din SBOM'], + ['un tip necunoscut de intrare NuGet primit', 'if (!e || !NUGET_TIPURI.has(e.type)) throw', 'if (!e) throw', 'CONTROL: un lockfile .NET stricat'], + ['doua hash-uri pentru acelasi pachet primite', 'if (p.hash !== e.contentHash) throw', 'if (false) throw', 'CONTROL: un lockfile .NET stricat'], + ['muchiile pachet -> dependinta nu mai sunt scrise', 'for (const dn of Object.keys(e.dependencies || {})) muchii.push([refDe([n, e]), refDe(rezolva(dn))]);', '', 'nuget: graful din lockfile'], + ['radacina nu mai depinde de directe si de proiect', "if (e.type === 'Direct' || e.type === 'Project') muchii.push([root, refDe([n, e])]);", '', 'nuget: graful din lockfile'], + ['SBOM-urile .NET si Gradle judecate ca npm la reconstructie', 'const din = derivatDin(atestat), ver =', 'const din = null, ver =', "verify --rebuild-from: SBOM-ul .NET"], + ['SBOM-ul Gradle judecat ca npm la reconstructie', "fel === 'gradle' ? sbomGradleFromTree", "fel === 'gradle-niciodata' ? sbomGradleFromTree", 'verify --rebuild-from: SBOM-ul Gradle'], + ['hash-urile jar-ului principal nu mai sunt scrise', '...(hashes.length ? { hashes } : {}), properties };', '...({}), properties };', 'gradle: cinci componente'], + ['o coordonata blocata fara hash nu mai e semnalata', "...(!c.arte.length ? [{ name: 'aere:gradle-no-checksum', value: 'true' }] : []),", '...([]),', 'CONTROL: o coordonata blocata fara hash'], + ['regulile trusted-artifacts nu mai sunt numarate', "if (t.open === 'trust' && stiva.includes('configuration')) reguliIncredere++;", 'if (false) reguliIncredere++;', 'CONTROL: regulile care slabesc'], + // B-22 (2026-09-30): cititorul liniar; fiecare garda de structura scoasa trebuie sa inroseasca proba ei + ['o componenta fara coordonate primita', 'if (!a.group || !a.name || !a.version) throw', 'if (false) throw', 'CONTROL: verification-metadata cu o componenta necitita'], + ['o componenta in afara primita', "if (sus !== 'components') throw", 'if (false) throw', 'B-22 CONTROL: structura pe care cititorul nu o stie'], + ['taguri incrucisate primite', 'if (scos !== t.close) throw', 'if (false) throw', 'B-22 CONTROL: structura pe care cititorul nu o stie'], + ['un comentariu neinchis inghite restul in tacere', "if (e === -1) throw new Error(`${unde}: a comment is not closed`);", 'if (e === -1) break;', 'B-22 CONTROL: structura pe care cititorul nu o stie'], + // forma veche, patratica, a atributelor si a radacinii (masurat pe ea: 6,9 s si 5 s) + ['atributele citite iar cu expresia patratica', 'out.push({ open: nume, attrs: atributeXml(corp.slice(k), unde), self });', 'out.push({ open: nume, attrs: Object.fromEntries([...corp.slice(k).matchAll(/([A-Za-z_][\\w.-]*)\\s*=\\s*"([^"]*)"/g)].map((m) => [m[1], m[2]])), self });', 'B-22: citirea costa LINIAR'], + ['radacina din settings.gradle citita iar cu \\s* sub /m', "export const RADACINA_GRADLE = /^[ \\t]*rootProject", "export const RADACINA_GRADLE = /^\\s*rootProject", 'B-22: citirea costa LINIAR'], + ['configuratiile din gradle.lockfile pierdute', "c.configuratii = m[4].split(',').filter(Boolean).sort(); coord.set(k, c);", 'c.configuratii = []; coord.set(k, c);', 'gradle: configuratiile din gradle.lockfile'], + ['numarul de serie aleator (SBOM-ul nu mai e determinist)', 'serialNumber: serieDerivata({ metadata, components, dependencies }),', "serialNumber: 'urn:uuid:' + crypto.randomUUID(),", 'determinist'], + ['componentele fara purl numite name@version in forma semantica', "const comp = (c) => ({ purl: c.purl || c['bom-ref'] || `${c.name}@${c.version}`,", 'const comp = (c) => ({ purl: c.purl || `${c.name}@${c.version}`,', 'forma semantica: componentele fara purl'], + ['doua fisiere de proiect: primul ales in tacere', 'if (proj.length !== 1) return { lipsa:', 'if (proj.length < 1) return { lipsa:', 'un arbore fara lockfile / cu doua proiecte'], +]); diff --git a/tools/proof-of-software/test/sbom-nuget-gradle.test.mjs b/tools/proof-of-software/test/sbom-nuget-gradle.test.mjs new file mode 100644 index 0000000..adf4f4e --- /dev/null +++ b/tools/proof-of-software/test/sbom-nuget-gradle.test.mjs @@ -0,0 +1,239 @@ +// Proof of Software 1.5.0: SBOM-ul unui proiect .NET din packages.lock.json COMIS si al unui proiect Gradle din +// gradle/verification-metadata.xml + gradle.lockfile COMISE, derivate determinist si judecate la verify --rebuild-from. +// Fixturile sunt fisiere REALE, scrise de unelte (test/fixturi-nuget/genereaza.sh: .NET SDK 10.0.302, restaurare fara retea din memoria +// locala de pachete; test/fixturi-gradle/genereaza.sh: Gradle 8.14.3 --offline). Fiecare afirmatie cu perechea ei negativa. Offline. +// node test/sbom-nuget-gradle.test.mjs iesire 0 = toate cum trebuia +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import crypto from 'node:crypto'; +import { execFileSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { attest, verify, sbomNuget, sbomNugetFromTree, sbomGradle, sbomGradleFromTree, sbomSemantica, RADACINA_GRADLE } from '../pos.mjs'; + +const AICI = path.dirname(fileURLToPath(import.meta.url)); +const POS = path.resolve(AICI, '..', 'pos.mjs'); +const FN = path.join(AICI, 'fixturi-nuget'), FG = path.join(AICI, 'fixturi-gradle'); +let treceri = 0; const esecuri = []; +async function test(nume, fn) { try { await fn(); treceri++; console.log(' OK ' + nume); } catch (e) { esecuri.push(nume); console.log(' ESEC ' + nume + ' — ' + (e.message || e)); } } +const cere = (c, m) => { if (!c) throw new Error(m); }; +const g = (args, cwd) => execFileSync('git', args, { cwd, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim(); +const check = (r, re) => r.checks.find((c) => re.test(c.name)); +const prop = (c, n) => ((c.properties || []).find((x) => x.name === n) || {}).value; +const arunca = (f) => { try { f(); return null; } catch (e) { return e.message; } }; + +const LIB = fs.readFileSync(path.join(FN, 'lib', 'packages.lock.json'), 'utf8'); +const TESTS = fs.readFileSync(path.join(FN, 'lib.tests', 'packages.lock.json'), 'utf8'); +const VM = fs.readFileSync(path.join(FG, 'gradle', 'verification-metadata.xml'), 'utf8'); +const GL = fs.readFileSync(path.join(FG, 'gradle.lockfile'), 'utf8'); +const bLib = sbomNuget({ lock: LIB, projectName: 'lib', version: '1.0.0' }); +const bTests = sbomNuget({ lock: TESTS, projectName: 'lib.tests', version: '1.0.0' }); +const bGr = sbomGradle({ verificationMetadata: VM, lockfile: GL, projectName: 'pos-fixture', version: '1.0.0' }); + +// ---------------------------------------------------------------- .NET +await test('nuget lib: doua pachete, fiecare in ambele cadre (net10.0, net8.0), purl pkg:nuget; radacina din numele proiectului', () => { + cere(bLib.components.map((c) => c.purl).join(' ') === 'pkg:nuget/BouncyCastle.Cryptography@2.6.2 pkg:nuget/Newtonsoft.Json@13.0.1', bLib.components.map((c) => c.purl).join(' ')); + cere(bLib.components.every((c) => prop(c, 'aere:nuget-frameworks') === 'net10.0,net8.0' && prop(c, 'aere:nuget-type') === 'Direct'), 'cadre sau tip'); + cere(bLib.metadata.component.purl === 'pkg:nuget/lib@1.0.0' && prop(bLib.metadata, 'aere:derived-from') === 'packages.lock.json', bLib.metadata.component.purl); +}); +await test('nuget lib.tests: 90 de pachete (numaratoarea lui NuGet insusi, din .nupkg.metadata-urile restaurarii) si o referinta de proiect', () => { + const nuget = fs.readFileSync(path.join(FN, 'nupkg-metadata-contenthash.txt'), 'utf8').split('\n').filter(Boolean); + const pachete = bTests.components.filter((c) => c.purl), proiecte = bTests.components.filter((c) => !c.purl); + cere(pachete.length === nuget.length && nuget.length === 90, `pachete ${pachete.length}, NuGet ${nuget.length}`); + cere(proiecte.length === 1 && proiecte[0]['bom-ref'] === 'project:lib' && prop(proiecte[0], 'aere:nuget-type') === 'Project', JSON.stringify(proiecte)); +}); +await test('CONTROL POZITIV al metodei: fiecare hash din SBOM e contentHash-ul pe care NuGet l-a scris in .nupkg.metadata la restaurare (90/90)', () => { + const nuget = new Map(fs.readFileSync(path.join(FN, 'nupkg-metadata-contenthash.txt'), 'utf8').split('\n').filter(Boolean).map((l) => l.split(' '))); + let n = 0; + for (const c of bTests.components.filter((x) => x.purl)) { + const k = `${c.name.toLowerCase()}/${c.version.toLowerCase()}`; + cere(nuget.has(k) && 'sha512-' + nuget.get(k) === prop(c, 'aere:nuget-content-hash'), `${k}: ${nuget.get(k)} fata de ${prop(c, 'aere:nuget-content-hash')}`); + cere(!(c.hashes || []).length, `${k}: contentHash-ul NuGet nu e scris drept hash al fisierului`); + n++; + } + cere(n === 90, 'comparate ' + n); +}); +await test('de ce contentHash e proprietate si nu SHA-512: pe pachetele semnate din memoria locala, SHA-512 al fisierului .nupkg e ALTUL', () => { + const cache = path.join(os.homedir(), '.nuget', 'packages'); + const perechi = bLib.components.map((c) => ({ c, f: path.join(cache, c.name.toLowerCase(), c.version.toLowerCase(), `${c.name.toLowerCase()}.${c.version.toLowerCase()}.nupkg`) })).filter((x) => fs.existsSync(x.f)); + if (!perechi.length) { console.log(' SARIT (pachetele nu sunt in memoria locala NuGet a acestei masini)'); return; } + for (const { c, f } of perechi) { + const fisier = 'sha512-' + crypto.createHash('sha512').update(fs.readFileSync(f)).digest('base64'); + cere(fisier !== prop(c, 'aere:nuget-content-hash'), `${c.name}: hash-ul fisierului e chiar contentHash-ul (atunci ar trebui scris ca SHA-512)`); + cere(fs.readFileSync(f + '.sha512', 'utf8').trim() === fisier.slice(7), `${c.name}: .nupkg.sha512 al NuGet nu e SHA-512 al fisierului`); + } + console.log(` (${perechi.length} pachete: SHA-512 al .nupkg = fisierul .sha512 al NuGet, diferit de contentHash)`); +}); +await test('nuget: graful din lockfile, cu versiunile REZOLVATE; radacina -> directe si proiectul; proiectul -> pachetele lui', () => { + const dep = (ref) => (bTests.dependencies.find((d) => d.ref === ref) || { dependsOn: [] }).dependsOn; + cere(dep('pkg:nuget/lib.tests@1.0.0').join(' ') === 'pkg:nuget/Microsoft.NET.Test.Sdk@17.8.0 pkg:nuget/coverlet.collector@6.0.0 pkg:nuget/xunit.runner.visualstudio@2.5.3 pkg:nuget/xunit@2.5.3 project:lib', dep('pkg:nuget/lib.tests@1.0.0').join(' ')); + cere(dep('project:lib').join(' ') === 'pkg:nuget/BouncyCastle.Cryptography@2.6.2 pkg:nuget/Newtonsoft.Json@13.0.1', dep('project:lib').join(' ')); + cere(dep('pkg:nuget/xunit@2.5.3').join(' ') === 'pkg:nuget/xunit.analyzers@1.4.0 pkg:nuget/xunit.assert@2.5.3 pkg:nuget/xunit.core@2.5.3', dep('pkg:nuget/xunit@2.5.3').join(' ')); + const refs = new Set([bTests.metadata.component['bom-ref'], ...bTests.components.map((c) => c['bom-ref'])]); + const orfane = bTests.dependencies.flatMap((d) => [d.ref, ...d.dependsOn]).filter((r) => !refs.has(r)); + cere(!orfane.length, 'referinte fara componenta: ' + orfane.slice(0, 3).join(', ')); +}); +await test('determinist: aceleasi fisiere dau aceiasi octeti; alta versiune, alt numar de serie', () => { + cere(JSON.stringify(sbomNuget({ lock: TESTS, projectName: 'lib.tests', version: '1.0.0' })) === JSON.stringify(bTests), 'doua derivari .NET difera'); + cere(JSON.stringify(sbomGradle({ verificationMetadata: VM, lockfile: GL, projectName: 'pos-fixture', version: '1.0.0' })) === JSON.stringify(bGr), 'doua derivari Gradle difera'); + cere(sbomNuget({ lock: TESTS, projectName: 'lib.tests', version: '1.0.1' }).serialNumber !== bTests.serialNumber, 'CONTROL: alta versiune, alt numar de serie'); + cere(/^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(bGr.serialNumber), bGr.serialNumber); +}); +await test('CONTROL: un lockfile .NET stricat e refuzat cu motivul, nu ghicit (tip necunoscut, fara hash, dependinta nerezolvata, doua hash-uri)', () => { + const j = () => JSON.parse(LIB); + const a = j(); a.dependencies['net8.0']['Newtonsoft.Json'].type = 'Floating'; + const b = j(); delete b.dependencies['net8.0']['Newtonsoft.Json'].contentHash; + const c = j(); c.dependencies['net8.0']['Newtonsoft.Json'].dependencies = { 'System.Nope': '1.0.0' }; + const d = j(); d.dependencies['net10.0']['Newtonsoft.Json'].contentHash = 'A'.repeat(86) + '=='; + const m = [a, b, c, d].map((x) => arunca(() => sbomNuget({ lock: x, projectName: 'lib', version: '1' }))); + cere(/unknown type/.test(m[0] || '') && /no SHA-512 content hash/.test(m[1] || '') && /not resolved/.test(m[2] || '') && /two content hashes/.test(m[3] || ''), m.join(' | ')); +}); + +// ---------------------------------------------------------------- Gradle +await test('gradle: cinci componente pkg:maven; jar-ul principal cu SHA-256 si SHA-512 din verification-metadata; pom-ul parinte fara hashes si fara configuratie', () => { + cere(bGr.components.map((c) => c.purl).join(' ') === 'pkg:maven/com.google.code.gson/gson-parent@2.10.1 pkg:maven/com.google.code.gson/gson@2.10.1 pkg:maven/org.bouncycastle/bcpkix-jdk18on@1.79 pkg:maven/org.bouncycastle/bcprov-jdk18on@1.79 pkg:maven/org.bouncycastle/bcutil-jdk18on@1.79', bGr.components.map((c) => c.purl).join(' ')); + const bcprov = bGr.components.find((c) => c.name === 'bcprov-jdk18on'), parinte = bGr.components.find((c) => c.name === 'gson-parent'); + cere((bcprov.hashes || []).map((h) => `${h.alg}:${h.content}`).join(' ').startsWith('SHA-256:0d81ecc3124536b539bce9aa3fe9621b7f84c9cee371b635a5b31c78b79ab1da SHA-512:27bc5415'), JSON.stringify(bcprov.hashes)); + cere(!parinte.hashes && prop(parinte, 'aere:gradle-configurations') === '' && /gson-parent-2\.10\.1\.pom sha256:/.test(prop(parinte, 'aere:gradle-artifact')), JSON.stringify(parinte)); + cere(prop(bGr.metadata, 'aere:gradle-verify-metadata') === 'true' && prop(bGr.metadata, 'aere:gradle-trust-rules') === '0' && /absent/.test(prop(bGr.metadata, 'aere:dependency-graph')) && !bGr.dependencies.length, JSON.stringify(bGr.metadata.properties)); +}); +await test('gradle: configuratiile din gradle.lockfile, pe fiecare componenta blocata (numaratoare independenta din lockfile)', () => { + const linii = GL.split('\n').filter((l) => /^[^#][^=]*:[^=]*:[^=]*=/.test(l)); + cere(linii.length === 4, 'linii ' + linii.length); + for (const l of linii) { + const [coord, conf] = l.trim().split('='); const [gr, n, v] = coord.split(':'); + const c = bGr.components.find((x) => x.group === gr && x.name === n && x.version === v); + cere(c && prop(c, 'aere:gradle-configurations') === conf.split(',').sort().join(','), coord); + } +}); +await test('CONTROL POZITIV al metodei: SHA-256 din SBOM e chiar hash-ul jar-ului din memoria locala a lui Gradle', () => { + const G = path.join(os.homedir(), '.gradle', 'caches', 'modules-2', 'files-2.1'); + let n = 0; + for (const c of bGr.components.filter((x) => x.hashes)) { + const d = path.join(G, c.group, c.name, c.version); if (!fs.existsSync(d)) continue; + const f = fs.readdirSync(d).map((h) => path.join(d, h, `${c.name}-${c.version}.jar`)).find((x) => fs.existsSync(x)); if (!f) continue; + const h = crypto.createHash('sha256').update(fs.readFileSync(f)).digest('hex'); + cere(h === c.hashes.find((x) => x.alg === 'SHA-256').content, `${c.name}: ${h}`); n++; + } + if (!n) { console.log(' SARIT (jar-urile nu sunt in memoria locala Gradle a acestei masini)'); return; } + console.log(` (${n} din ${bGr.components.filter((x) => x.hashes).length} jar-uri comparate cu memoria Gradle)`); +}); +await test('CONTROL: o coordonata blocata fara hash in verification-metadata e LISTATA si NUMARATA, nu ascunsa', () => { + const b = sbomGradle({ verificationMetadata: VM, lockfile: GL + 'org.example:fara-hash:1.0=runtimeClasspath\n', projectName: 'p', version: '1' }); + const c = b.components.find((x) => x.name === 'fara-hash'); + cere(c && prop(c, 'aere:gradle-no-checksum') === 'true' && !c.hashes && prop(b.metadata, 'aere:gradle-locked-without-checksum') === '1', JSON.stringify(c)); +}); +await test('CONTROL: regulile care slabesc verificarea (trusted-artifacts) se numara, iar also-trust si pgp apar in linia artefactului', () => { + const vm = VM.replace('false', 'false\n ') + .replace(/(/, '$1>\n '); + const b = sbomGradle({ verificationMetadata: vm, lockfile: GL, projectName: 'p', version: '1' }); + const linie = prop(b.components.find((x) => x.name === 'bcprov-jdk18on'), 'aere:gradle-artifact'); + const jar = b.components.find((x) => x.name === 'bcprov-jdk18on').properties.filter((p) => p.name === 'aere:gradle-artifact').map((p) => p.value).find((v) => v.startsWith('bcprov-jdk18on-1.79.jar')); + cere(prop(b.metadata, 'aere:gradle-trust-rules') === '2', 'reguli ' + prop(b.metadata, 'aere:gradle-trust-rules')); + cere(/also-trust:ab/.test(jar) && /pgp:abcd1234/.test(jar), jar + ' | ' + linie); +}); +await test('CONTROL: verification-metadata cu o componenta necitita, sau un gradle.lockfile stricat -> refuzat cu motivul', () => { + const m1 = arunca(() => sbomGradle({ verificationMetadata: VM.replace('', ''), lockfile: GL, projectName: 'p', version: '1' })); + const m2 = arunca(() => sbomGradle({ verificationMetadata: VM, lockfile: GL + 'nu e o linie de lockfile\n', projectName: 'p', version: '1' })); + const m3 = arunca(() => sbomGradle({ verificationMetadata: '', lockfile: GL, projectName: 'p', version: '1' })); + // un element pe care cititorul nu il poate citi deloc (fara atribute) -> refuz, nu o componenta sarita + const m4 = arunca(() => sbomGradle({ verificationMetadata: VM.replace('', '\n \n '), lockfile: GL, projectName: 'p', version: '1' })); + cere(/without group, name or version/.test(m1 || '') && /gradle.lockfile line \d+/.test(m2 || '') && /no /.test(m3 || '') && /without group, name or version/.test(m4 || ''), [m1, m2, m3, m4].join(' | ')); +}); +await test('B-22 CONTROL: structura pe care cititorul nu o stie e REFUZATA cu motivul (componenta in afara , taguri incrucisate, tag sau comentariu neinchis, DOCTYPE, atribut fara ghilimele)', () => { + const cu = (f) => arunca(() => sbomGradle({ verificationMetadata: f(VM), lockfile: GL, projectName: 'p', version: '1' })) || 'ACCEPTAT'; + const m = [ + cu((v) => v.replace('', '\n ')), + cu((v) => v.replace('', '')), + cu((v) => v.replace('', ' v.replace('', '