Some checks failed
validate / Rego (opa fmt + opa test) (push) Successful in 16s
validate / JSON Schema (ajv) (push) Successful in 20s
build-images / signing-service image (push) Successful in 24s
validate / DB (migrate up/down dry-run) (push) Failing after 26s
validate / Go (vet + test) (push) Successful in 1m16s
Adds services/verification-service exposing POST /api/v1/verify/chain for sink:dummy-sink. The handler runs the six hard-coded chain checks described in DEV-612 and reserves decision_id in a new `verification_decisions` table so a replayed sink delivery is refused without recomputing signatures. - DSSE signature verification against configurable approval + transform signer key_ids, loaded from VERIFICATION_SIGNER_KEYS (pem/hex/b64/b64u) - RFC 6962 inclusion-proof verifier (shared leaf/node hashing with services/transparency-log/pkg/backend) - Content binding: subject.digest.sha256 must match predicate.content_hash (approval) and request.payload_hash (transform) - Hard-coded coverage of allowed_transformations / allowed_sinks / expires_at predicates (OPA deferred to Phase 2 per the subtask) - Replay guard via new db/trust/migrations/0005_verification_decisions (in-memory store ships; Postgres skeleton mirrors sibling pattern) - Dockerfile mirrors signing-service pattern (distroless static + nonroot) - README documents endpoint, reason-code taxonomy, required predicate fields, and the Phase-2 non-scope Tests: happy path + 7 negatives (bad signature, missing inclusion proof, hash drift, sink not allowed, expired, replay, operation not allowed). Co-Authored-By: Paperclip <noreply@paperclip.ing>
102 lines
2.4 KiB
Go
102 lines
2.4 KiB
Go
package chain
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
|
|
"basicstack.de/proofedmesh/services/verification-service/internal/canonical"
|
|
"basicstack.de/proofedmesh/services/verification-service/internal/dsse"
|
|
"basicstack.de/proofedmesh/services/verification-service/internal/tlog"
|
|
)
|
|
|
|
// memTree is a test-only Merkle tree mirroring the transparency-log's
|
|
// in-memory backend. It lives in the chain tests (not under internal/tlog)
|
|
// so the proof verifier under test does not share code with the proof
|
|
// producer.
|
|
type memTree struct {
|
|
leaves [][]byte
|
|
}
|
|
|
|
func (t *memTree) add(env dsse.Envelope) tlog.Entry {
|
|
leafBytes, _ := canonical.Marshal(env)
|
|
t.leaves = append(t.leaves, leafHash(leafBytes))
|
|
idx := int64(len(t.leaves) - 1)
|
|
hash := "sha256:" + canonical.SHA256Hex(leafBytes)
|
|
return tlog.Entry{
|
|
ContentHash: hash,
|
|
Envelope: env,
|
|
LogIndex: idx,
|
|
InclusionProof: tlog.InclusionProof{},
|
|
}
|
|
}
|
|
|
|
func (t *memTree) refreshProofs(entries ...*tlog.Entry) {
|
|
root := merkleTreeHashT(t.leaves)
|
|
rootStr := "sha256:" + hex.EncodeToString(root)
|
|
for _, e := range entries {
|
|
path := auditPathT(t.leaves, int(e.LogIndex))
|
|
hashes := make([]string, 0, len(path))
|
|
for _, h := range path {
|
|
hashes = append(hashes, "sha256:"+hex.EncodeToString(h))
|
|
}
|
|
e.InclusionProof = tlog.InclusionProof{
|
|
TreeSize: int64(len(t.leaves)),
|
|
TreeRootHash: rootStr,
|
|
Hashes: hashes,
|
|
}
|
|
}
|
|
}
|
|
|
|
func leafHash(data []byte) []byte {
|
|
h := sha256.New()
|
|
h.Write([]byte{0x00})
|
|
h.Write(data)
|
|
return h.Sum(nil)
|
|
}
|
|
|
|
func nodeHashT(left, right []byte) []byte {
|
|
h := sha256.New()
|
|
h.Write([]byte{0x01})
|
|
h.Write(left)
|
|
h.Write(right)
|
|
return h.Sum(nil)
|
|
}
|
|
|
|
func merkleTreeHashT(leaves [][]byte) []byte {
|
|
switch len(leaves) {
|
|
case 0:
|
|
s := sha256.Sum256(nil)
|
|
return s[:]
|
|
case 1:
|
|
return append([]byte(nil), leaves[0]...)
|
|
}
|
|
k := largestPow2LessThanT(len(leaves))
|
|
return nodeHashT(merkleTreeHashT(leaves[:k]), merkleTreeHashT(leaves[k:]))
|
|
}
|
|
|
|
func auditPathT(leaves [][]byte, m int) [][]byte {
|
|
n := len(leaves)
|
|
if n <= 1 {
|
|
return nil
|
|
}
|
|
k := largestPow2LessThanT(n)
|
|
if m < k {
|
|
return append(auditPathT(leaves[:k], m), merkleTreeHashT(leaves[k:]))
|
|
}
|
|
return append(auditPathT(leaves[k:], m-k), merkleTreeHashT(leaves[:k]))
|
|
}
|
|
|
|
func largestPow2LessThanT(n int) int {
|
|
k := 1
|
|
for k < n {
|
|
k <<= 1
|
|
}
|
|
return k >> 1
|
|
}
|
|
|
|
func seedBytes(s string) []byte { return []byte(s) }
|
|
|
|
func sha(b []byte) []byte {
|
|
s := sha256.Sum256(b)
|
|
return s[:]
|
|
}
|