- services/transparency-log/pkg/backend: Backend interface with in-memory append-log (RFC 6962 Merkle tree) + Trillian wrapper (prod stub). - Idempotenz-Pflicht: Wiederaufruf mit identischem content_hash liefert 200 + gleichen log_index + aktuellen inclusion_proof. - content_hash-Verifikation serverseitig; Mismatch → 409. - pkg/httpapi: ADR-0005-konformer Handler (201/200/400/404/409/503). - cmd/transparency-log: minimaler HTTP-Server, memory default, --backend trillian als Stub (503). - Interop-Test in services/admin-api/internal/tlog/interop_test.go fährt die echte transparency-log pkg gegen die admin-api HTTPSubmitter — Happy-Path, Idempotenz und content_hash-Mismatch abgedeckt. - go test: 10 Tests transparency-log + 2 Interop-Tests admin-api, go vet clean in beiden Modulen. Folge-Subtask: Trillian-Deployment + Keys (siehe ADR-0005 Open Points). Co-Authored-By: Paperclip <noreply@paperclip.ing>
73 lines
1.9 KiB
Go
73 lines
1.9 KiB
Go
package backend
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
)
|
|
|
|
// RFC 6962 §2.1 Merkle tree hashing. We use the standard CT scheme:
|
|
// leaf hash = SHA-256(0x00 || data)
|
|
// node hash = SHA-256(0x01 || left || right)
|
|
// so that an inclusion proof from this server can later be audited by
|
|
// anything that speaks the certificate-transparency merkle format.
|
|
|
|
func leafHash(data []byte) []byte {
|
|
h := sha256.New()
|
|
h.Write([]byte{0x00})
|
|
h.Write(data)
|
|
return h.Sum(nil)
|
|
}
|
|
|
|
func nodeHash(left, right []byte) []byte {
|
|
h := sha256.New()
|
|
h.Write([]byte{0x01})
|
|
h.Write(left)
|
|
h.Write(right)
|
|
return h.Sum(nil)
|
|
}
|
|
|
|
// merkleTreeHash computes the Merkle Tree Hash (MTH) of the given list of
|
|
// leaf hashes. MTH([]) is SHA-256 of the empty string per RFC 6962, but we
|
|
// never call it on an empty slice in practice.
|
|
func merkleTreeHash(leaves [][]byte) []byte {
|
|
n := len(leaves)
|
|
switch n {
|
|
case 0:
|
|
h := sha256.Sum256(nil)
|
|
return h[:]
|
|
case 1:
|
|
return append([]byte(nil), leaves[0]...)
|
|
}
|
|
k := largestPow2LessThan(n)
|
|
return nodeHash(merkleTreeHash(leaves[:k]), merkleTreeHash(leaves[k:]))
|
|
}
|
|
|
|
// auditPath returns the RFC 6962 audit path for leaf index m within the
|
|
// given leaf-hash slice (tree size n = len(leaves)). The returned slice is
|
|
// the ordered list of sibling hashes from the leaf up to (but not including)
|
|
// the root. Returns an empty slice for a single-leaf tree.
|
|
func auditPath(leaves [][]byte, m int) [][]byte {
|
|
n := len(leaves)
|
|
if n <= 1 {
|
|
return nil
|
|
}
|
|
k := largestPow2LessThan(n)
|
|
if m < k {
|
|
return append(auditPath(leaves[:k], m), merkleTreeHash(leaves[k:]))
|
|
}
|
|
return append(auditPath(leaves[k:], m-k), merkleTreeHash(leaves[:k]))
|
|
}
|
|
|
|
// largestPow2LessThan returns the largest power of two strictly smaller than
|
|
// n (n must be >= 2).
|
|
func largestPow2LessThan(n int) int {
|
|
k := 1
|
|
for k < n {
|
|
k <<= 1
|
|
}
|
|
return k >> 1
|
|
}
|
|
|
|
func sha256Hex(b []byte) string {
|
|
return "sha256:" + hex.EncodeToString(b)
|
|
}
|