code wiki / (root) / nx_provstmt.nx

nx_provstmt.nx source

↩ module page · 141 lines · 6273 B

1// nx_provstmt.nx -- CR3 of /compare/capregistry: ATTEST THE PROMOTED ARTIFACT, NOT THE EVIDENCE FRAME. 2// 3// nx_provstmt <target> <artifact_sha256> <src_sha256> <closure_sha256> [out.json] 4// 5// WHERE THE ARGUMENTS COME FROM, because they are the whole point: /api/build's response carries src_sha256 6// and closure_sha256, and /api/promote's response carries the live artifact sha256 after the rename. The 7// estate computes real SLSA build provenance on every build and every caller discards it at the end of the 8// HTTP response. This organ is the missing last step: bind those already-computed digests to the artifact 9// they describe and write the in-toto ITE-6 Statement down. 10// 11// THE POLICY THAT MAKES IT SAFE TO RUN LATE: the organ RE-HASHES THE LIVE ARTIFACT ITSELF and refuses when 12// the computed digest differs from the receipt's. Without that check, a stale receipt -- from before a 13// sibling's re-promote, or from a build that never went live -- would mint a Statement binding provenance to 14// bytes that are NOT being served: a false attestation with a valid shape, which is worse than none. 15// The in-toto spec matches subjects PURELY BY DIGEST, so the digest being right is the entire contract. 16// 17// EXITS: 0 statement written, receipt printed | 1 ATTEST-REFUSED, live artifact hash differs from the 18// receipt (both digests printed; NOTHING written) | 2 usage / malformed digest (names WHICH argument) | 19// 3 UNPROVEN, live artifact unreadable -- an absent artifact must abstain, never attest. 20 21import "nx_syscalls.nx" 22import "nx_sha256.nx" 23import "_hdl_build/nx_provstmt_lib.nx" 24 25const PVO_NAME_CAP: i64 = 128 26const PVO_PATH_CAP: i64 = 512 27 28func pvo_puts(s: *u8) -> i64 { 29 var n: i64 = 0 30 while s[n] != (0 as u8) { n = n + 1 } 31 sys_write(1, s, n) 32 return 0 33} 34 35func pvo_len(s: *u8) -> i64 { 36 var n: i64 = 0 37 while s[n] != (0 as u8) { n = n + 1 } 38 return n 39} 40 41func pvo_mk(dst: *u8, a: *u8, b: *u8) -> i64 { 42 var p: i64 = 0 43 var i: i64 = 0 44 while a[i] != (0 as u8) { dst[p] = a[i]; p = p + 1; i = i + 1 } 45 i = 0 46 while b[i] != (0 as u8) { dst[p] = b[i]; p = p + 1; i = i + 1 } 47 dst[p] = 0 as u8 48 return p 49} 50 51func main(argc: i64, argv: *i64) -> i64 { 52 if argc < 5 { 53 pvo_puts("usage: nx_provstmt <target> <artifact_sha256> <src_sha256> <closure_sha256> [out.json]\n digests come from the /api/build and /api/promote receipts of the SAME ship -- this organ re-hashes\n the live artifact and refuses when the receipt does not match what is actually being served\n" as *u8) 54 return 2 55 } 56 let target: *u8 = argv[1] as *u8 57 let art_sha: *u8 = argv[2] as *u8 58 let src_sha: *u8 = argv[3] as *u8 59 let clo_sha: *u8 = argv[4] as *u8 60 if pvo_len(target) >= PVO_NAME_CAP { 61 pvo_puts("PROVSTMT usage: target name exceeds the name cap\n" as *u8) 62 return 2 63 } 64 // NAME WHICH ARGUMENT: three digests, three separate refusals, or a caller who swapped two of them 65 // spends the investigation on the wrong one. 66 if pv_hex_ok(art_sha) == 0 { pvo_puts("PROVSTMT usage: artifact_sha256 is not 64 lowercase hex chars\n" as *u8); return 2 } 67 if pv_hex_ok(src_sha) == 0 { pvo_puts("PROVSTMT usage: src_sha256 is not 64 lowercase hex chars\n" as *u8); return 2 } 68 if pv_hex_ok(clo_sha) == 0 { pvo_puts("PROVSTMT usage: closure_sha256 is not 64 lowercase hex chars\n" as *u8); return 2 } 69 70 // ---- re-hash the LIVE artifact: the subject is what is being served, never what a receipt remembers ---- 71 let path: *u8 = sys_mmap(PVO_PATH_CAP) 72 pvo_mk(path, target, ".elf" as *u8) 73 let lp: *i64 = sys_mmap(16) as *i64 74 lp[0] = 0 75 let body: *u8 = sys_read_file(path, lp) 76 let bn: i64 = lp[0] 77 if bn <= 0 { 78 pvo_puts("PROVSTMT UNPROVEN -- live artifact unreadable: " as *u8) 79 pvo_puts(path) 80 pvo_puts("\n An artifact that cannot be read cannot be attested; refusing is not the same as failing.\n" as *u8) 81 return 3 82 } 83 let dig: *u8 = sys_mmap(40) 84 sha256_digest(body, bn, dig) 85 sys_free_file(body, bn) 86 let live_hex: *u8 = sys_mmap(72) 87 pv_hex_of(dig, live_hex) 88 89 if pv_hex_eq(live_hex, art_sha) == 0 { 90 pvo_puts("PROVSTMT ATTEST-REFUSED -- the live artifact is not the one the receipt describes\n receipt " as *u8) 91 pvo_puts(art_sha) 92 pvo_puts("\n live " as *u8) 93 pvo_puts(live_hex) 94 pvo_puts("\n Binding provenance to bytes that are not being served would be a false attestation with a\n valid shape, which is worse than none. Re-ship, or pass the receipt from the ship that is live.\n NOTHING was written.\n" as *u8) 95 return 1 96 } 97 98 // ---- serialise ---- 99 let J: *u8 = sys_mmap(PV_STMT_CAP) 100 let jn: i64 = pv_build(J, target, art_sha, src_sha, clo_sha) 101 if jn <= 0 { 102 pvo_puts("PROVSTMT usage: statement build refused its inputs\n" as *u8) 103 return 2 104 } 105 var opath: *u8 = sys_mmap(PVO_PATH_CAP) 106 if argc > 5 { 107 opath = argv[5] as *u8 108 } else { 109 let pfx: *u8 = sys_mmap(PVO_PATH_CAP) 110 pvo_mk(pfx, "knowledge/status/provstmt_" as *u8, target) 111 pvo_mk(opath, pfx, ".intoto.json" as *u8) 112 } 113 let fd: i64 = sys_openat_wr(opath, MODE_0644) 114 var wrote: i64 = 0 115 if fd >= 0 { 116 wrote = sys_write(fd, J, jn) 117 sys_close(fd) 118 } 119 if wrote != jn { 120 pvo_puts("PROVSTMT UNPROVEN -- statement could not be written whole (wrote/expected differ); a partial\n statement on disk would be worse than none, but truncate-open means the partial may exist: re-run.\n" as *u8) 121 return 3 122 } 123 124 pvo_puts("PROVSTMT OK subject=" as *u8) 125 pvo_puts(target) 126 pvo_puts(".elf digest-verified-against-live=1 bytes=" as *u8) 127 let nb: *u8 = sys_mmap(32) 128 var x: i64 = jn 129 var nn: i64 = 0 130 if x == 0 { nb[0] = 48 as u8; nn = 1 } 131 while x > 0 { nb[nn] = ((x % 10) + 48) as u8; x = x / 10; nn = nn + 1 } 132 var y: i64 = nn 133 let ob: *u8 = sys_mmap(32) 134 var oo: i64 = 0 135 while y > 0 { y = y - 1; ob[oo] = nb[y]; oo = oo + 1 } 136 sys_write(1, ob, oo) 137 pvo_puts(" of=" as *u8) 138 pvo_puts(opath) 139 pvo_puts("\n UNSIGNED STATEMENT: the DSSE envelope is owed and its absence is declared inside the JSON too.\n" as *u8) 140 return 0 141}