Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions cmd/mtc/log/internal/mtcproof/mtcproof.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"cmp"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"fmt"
"slices"
"strings"
Expand Down Expand Up @@ -58,15 +60,39 @@ type SubtreeSignature struct {
// case ml-dsa-44: opaque ml_dsa_44_signature[2420];
// } signature;
// } timestamped_signature;"
func NewSubtreeSignatureFromCosig(cosignerID []byte, cosig []byte) (SubtreeSignature, error) {
s := cryptobyte.String(cosig)
var timestamp uint64
if !s.ReadUint64(&timestamp) {
return SubtreeSignature{}, fmt.Errorf("cosignature too short (%d bytes, missing u64 timestamp)", len(cosig))
func NewSubtreeSignatureFromCosig(cosig []byte) (SubtreeSignature, error) {
l, ok := strings.CutPrefix(string(cosig), "— ")
if !ok {
return SubtreeSignature{}, fmt.Errorf("invalid cosignature format")
}
l, ok = strings.CutSuffix(l, "\n")
if !ok {
return SubtreeSignature{}, fmt.Errorf("invalid cosignature format")
}
name, sigB64, ok := strings.Cut(l, " ")
if !ok {
return SubtreeSignature{}, fmt.Errorf("invalid cosignature format")
}
cID, err := ParseCosignerID(name)
if err != nil {
return SubtreeSignature{}, fmt.Errorf("invalid cosigner ID: %v", err)
}
sigRaw, err := base64.StdEncoding.DecodeString(sigB64)
if err != nil {
return SubtreeSignature{}, fmt.Errorf("invalid cosignature base64: %v", err)
}
// Chomp 4 bytes of KeyHash
sigRaw = sigRaw[4:]
// Assert that timestamp is zero, otherwise client will never be able to verify the signature.
if t := binary.BigEndian.Uint64(sigRaw[:8]); t != 0 {
return SubtreeSignature{}, fmt.Errorf("invalid cosignature: timestamp (%d) is not zero", t)
}
// Remove timestamp
sigRaw = sigRaw[8:]

return SubtreeSignature{
CosignerID: cosignerID,
Signature: s,
CosignerID: cID,
Signature: sigRaw,
}, nil
}

Expand Down
27 changes: 21 additions & 6 deletions cmd/mtc/log/internal/mtcproof/mtcproof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ package mtcproof
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"reflect"
"strings"
"testing"
Expand Down Expand Up @@ -437,8 +439,21 @@ func TestParseCosignerID(t *testing.T) {
}

func TestNewSubtreeSignatureFromCosig(t *testing.T) {
cosignerID := []byte{0x2b, 0x06, 0x01, 0x04, 0x01}
partsToSig := func(t *testing.T, name string, hash uint32, timestamp uint64, sig []byte) []byte {
t.Helper()
s := make([]byte, 0, 4+8+len(sig))
s = binary.BigEndian.AppendUint32(s, hash)
s = binary.BigEndian.AppendUint64(s, timestamp)
s = append(s, sig...)
r := fmt.Appendf(nil, "— %s %s\n", name, base64.StdEncoding.EncodeToString(s))
t.Logf("sig: %q", string(r))
return r
}

cosignerID := []byte{0x01, 0x02, 0x03, 0x04}
cosignerName := "oid/1.3.6.1.4.1.1.2.3.4"
rawSig := []byte("raw-signature-bytes")
zeroTimestamp := uint64(0)

tests := []struct {
name string
Expand All @@ -447,14 +462,14 @@ func TestNewSubtreeSignatureFromCosig(t *testing.T) {
wantErr bool
}{
{
name: "valid cosignature with timestamp and signature",
input: append(binary.BigEndian.AppendUint64(nil, 1724867400), rawSig...),
name: "valid cosignature",
input: partsToSig(t, cosignerName, 0, zeroTimestamp, rawSig),
want: rawSig,
wantErr: false,
},
{
name: "valid cosignature with timestamp only",
input: binary.BigEndian.AppendUint64(nil, 1724867400),
input: partsToSig(t, cosignerName, 0, zeroTimestamp, []byte{}),
want: []byte{},
wantErr: false,
},
Expand All @@ -472,7 +487,7 @@ func TestNewSubtreeSignatureFromCosig(t *testing.T) {

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := NewSubtreeSignatureFromCosig(cosignerID, tc.input)
got, err := NewSubtreeSignatureFromCosig(tc.input)
if (err != nil) != tc.wantErr {
t.Fatalf("NewSubtreeSignatureFromCosig() error = %v, wantErr %v", err, tc.wantErr)
}
Expand All @@ -483,7 +498,7 @@ func TestNewSubtreeSignatureFromCosig(t *testing.T) {
t.Errorf("CosignerID = %x, want %x", got.CosignerID, cosignerID)
}
if !bytes.Equal(got.Signature, tc.want) {
t.Errorf("Signature = %x, want %x", got.Signature, tc.want)
t.Errorf("Signature = %s, want %s", got.Signature, tc.want)
}
})
}
Expand Down
19 changes: 8 additions & 11 deletions cmd/mtc/log/internal/subtreewitness/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@ type witnessKey struct {
}

type witness struct {
client SubtreeWitnessClient
verifier f_note.SubtreeVerifier
cosignerID []byte
client SubtreeWitnessClient
verifier f_note.SubtreeVerifier
}

// SubtreeWitnessClient defines the interface for calling a witness's sign-subtree endpoint.
Expand Down Expand Up @@ -85,19 +84,17 @@ func New(httpClient *http.Client, pol policy.TLogPolicy) (*Gateway, error) {
slog.WarnContext(context.Background(), "witness verifier does not implement SubtreeVerifier", slog.String("name", w.Name))
continue
}
cosignerID, err := mtcproof.ParseCosignerID(sv.Name())
if err != nil {
return nil, fmt.Errorf("invalid cosigner ID for witness %s: %w", sv.Name(), err)
if _, err := mtcproof.ParseCosignerID(sv.Name()); err != nil {
return nil, fmt.Errorf("invalid cosigner name in subtree verifier %q: %v", sv.Name(), err)
}
k := witnessKey{name: sv.Name(), keyHash: sv.KeyHash()}
if _, exists := witnesses[k]; exists {
return nil, fmt.Errorf("duplicate witness %q with key hash %x", k.name, k.keyHash)
}
client := wc.NewWitness(w.URL, httpClient)
witnesses[k] = witness{
client: client,
verifier: sv,
cosignerID: cosignerID,
client: client,
verifier: sv,
}
}

Expand Down Expand Up @@ -223,7 +220,7 @@ func (gw *Gateway) CosignSubtree(ctx context.Context, origin string, start, end
continue
}

if !w.verifier.VerifySubtree(0, origin, start, end, subRoot, sigBytes) {
if !w.verifier.VerifySubtree(origin, start, end, subRoot, sigBytes) {
slog.ErrorContext(ctx, "Subtree signature verification failed",
slog.String("witness", s.Name),
slog.Uint64("start", start),
Expand All @@ -232,7 +229,7 @@ func (gw *Gateway) CosignSubtree(ctx context.Context, origin string, start, end
continue
}

subSig, err := mtcproof.NewSubtreeSignatureFromCosig(w.cosignerID, sigBytes)
subSig, err := mtcproof.NewSubtreeSignatureFromCosig(sigBytes)
if err != nil {
slog.ErrorContext(ctx, "Failed to extract raw subtree signature",
slog.String("witness", s.Name),
Expand Down
23 changes: 9 additions & 14 deletions cmd/mtc/log/internal/subtreewitness/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ func (m *mockSubtreeClient) SignSubtree(ctx context.Context, start, end uint64,
// mustSignSubtree signs a subtree and formats the signature as a note-style signature line.
func mustSignSubtree(t *testing.T, s f_note.SubtreeSigner, origin string, start, end uint64, root []byte) (rawSig []byte, sigLine []byte) {
t.Helper()
noteSig, err := s.SignSubtree(0, origin, start, end, root)
noteSig, err := s.SignSubtree(origin, start, end, root)
if err != nil {
t.Fatalf("SignSubtree: %v", err)
}
buf := binary.BigEndian.AppendUint32(nil, s.KeyHash())
buf = append(buf, noteSig...)
sigLine = fmt.Appendf(nil, "— %s %s\n", s.Name(), base64.StdEncoding.EncodeToString(buf))
sigObj, err := mtcproof.NewSubtreeSignatureFromCosig(nil, noteSig)
sigObj, err := mtcproof.NewSubtreeSignatureFromCosig(noteSig)
if err != nil {
t.Fatalf("NewSubtreeSignatureFromCosig: %v", err)
}
Expand Down Expand Up @@ -202,8 +202,8 @@ func TestGateway_CosignSubtree(t *testing.T) {

rawSubSig, subSigLine := mustSignSubtree(t, signer1, origin, start, end, root)

corruptNoteSig, _ := signer1.SignSubtree(0, origin, start, end, root)
corruptNoteSig[len(corruptNoteSig)-1] ^= 0xff
corruptNoteSig, _ := signer1.SignSubtree(origin, start, end, root)
corruptNoteSig[len(corruptNoteSig)-4] ^= 0xff
corruptBuf := binary.BigEndian.AppendUint32(nil, signer1.KeyHash())
corruptBuf = append(corruptBuf, corruptNoteSig...)
corruptSubSigLine := fmt.Appendf(nil, "— %s %s\n", signer1.Name(), base64.StdEncoding.EncodeToString(corruptBuf))
Expand Down Expand Up @@ -239,8 +239,7 @@ func TestGateway_CosignSubtree(t *testing.T) {
return subSigLine, nil
},
},
verifier: ver1,
cosignerID: []byte{0x01},
verifier: ver1,
},
},
policy: policy1,
Expand All @@ -257,8 +256,7 @@ func TestGateway_CosignSubtree(t *testing.T) {
return append(bytes.Clone(subSigLine), subSigLine...), nil
},
},
verifier: ver1,
cosignerID: []byte{0x01},
verifier: ver1,
},
},
policy: policy1,
Expand All @@ -275,8 +273,7 @@ func TestGateway_CosignSubtree(t *testing.T) {
return subSigLine, nil
},
},
verifier: ver1,
cosignerID: []byte{0x01},
verifier: ver1,
},
},
policy: policy1,
Expand All @@ -293,8 +290,7 @@ func TestGateway_CosignSubtree(t *testing.T) {
return corruptSubSigLine, nil
},
},
verifier: ver1,
cosignerID: []byte{0x01},
verifier: ver1,
},
},
policy: policy1,
Expand All @@ -311,8 +307,7 @@ func TestGateway_CosignSubtree(t *testing.T) {
return nil, errors.New("witness down")
},
},
verifier: ver1,
cosignerID: []byte{0x01},
verifier: ver1,
},
},
policy: policy1,
Expand Down
4 changes: 2 additions & 2 deletions cmd/mtc/log/mtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,11 +498,11 @@ func (l *MTCLog) getSubtreeSigs(ctx context.Context, start, end uint64, rawCp []
return nil, fmt.Errorf("cannot compute subtree root for [%d, %d): %v", start, end, err)
}

selfSig, err := l.subtreeSigner.SignSubtree(0, l.origin, start, end, subRoot)
selfSig, err := l.subtreeSigner.SignSubtree(l.origin, start, end, subRoot)
if err != nil {
return nil, fmt.Errorf("cannot sign subtree [%d, %d): %v", start, end, err)
}
selfSubSig, err := mtcproof.NewSubtreeSignatureFromCosig(l.logCosignerID, selfSig)
selfSubSig, err := mtcproof.NewSubtreeSignatureFromCosig(selfSig)
if err != nil {
return nil, fmt.Errorf("cannot format self subtree signature: %w", err)
}
Expand Down
16 changes: 11 additions & 5 deletions cmd/mtc/log/mtc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,7 @@ func setupTestWitness(t *testing.T) (policy.TLogPolicy, note.SubtreeVerifier) {
http.Error(w, fmt.Sprintf("decode subRoot: %v", err), http.StatusBadRequest)
return
}
rawSig, err := signer.SignSubtree(0, testOrigin, start, end, subRoot)
rawSig, err := signer.SignSubtree(testOrigin, start, end, subRoot)
if err != nil {
http.Error(w, fmt.Sprintf("sign subtree: %v", err), http.StatusInternalServerError)
return
Expand Down Expand Up @@ -1092,16 +1092,22 @@ func TestMTCLog_AddTBS(t *testing.T) {
// SubtreeSignature.Signature contains the raw signature.
// SPEC: https://c2sp.org/tlog-cosignature
// note.SubtreeVerifier expects a C2SP timestamped_signature prefixed with the 8-byte u64 timestamp.
reconstructCosig := func(rawSig []byte) []byte {
return append(make([]byte, 8), rawSig...)
reconstructCosig := func(v note.SubtreeVerifier, rawSig []byte) []byte {
s := make([]byte, 4+8+len(rawSig))
binary.BigEndian.PutUint32(s[0:], v.KeyHash())
binary.BigEndian.PutUint64(s[4:], 0)
copy(s[12:], rawSig)
r := fmt.Appendf(nil, "— %s %s\n", v.Name(), base64.StdEncoding.EncodeToString(s))
t.Logf("reconstructed sig: %s", r)
return r
}
if !mtcLog.subtreeSigner.Verifier().VerifySubtree(0, mtcLog.origin, tc.wantStart, tc.wantEnd, subRoot, reconstructCosig(proofData.Signatures[0].Signature)) {
if !mtcLog.subtreeSigner.Verifier().VerifySubtree(mtcLog.origin, tc.wantStart, tc.wantEnd, subRoot, reconstructCosig(mtcLog.subtreeSigner.Verifier(), proofData.Signatures[0].Signature)) {
t.Errorf("VerifySubtree failed for log signature on entry%d", tc.entryIdx)
}
if !bytes.Equal(proofData.Signatures[0].CosignerID, mtcLog.logCosignerID) {
t.Errorf("CosignerID = %x, want %x", proofData.Signatures[0].CosignerID, mtcLog.logCosignerID)
}
if !witVerifier.VerifySubtree(0, mtcLog.origin, tc.wantStart, tc.wantEnd, subRoot, reconstructCosig(proofData.Signatures[1].Signature)) {
if !witVerifier.VerifySubtree(mtcLog.origin, tc.wantStart, tc.wantEnd, subRoot, reconstructCosig(witVerifier, proofData.Signatures[1].Signature)) {
t.Errorf("VerifySubtree failed for witness signature on entry%d", tc.entryIdx)
}
})
Expand Down
14 changes: 7 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ require (
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/muesli/termenv v0.16.0
github.com/rivo/tview v0.42.0
github.com/transparency-dev/formats v0.1.2-0.20260805102052-38e6e69c4152
github.com/transparency-dev/formats v0.1.2-0.20260916152522-091ce41666c0
github.com/transparency-dev/merkle v0.0.3-0.20260727102338-4491f478b7dc
github.com/transparency-dev/witness v0.0.0-20260814155820-ed55fd2d54a0
github.com/transparency-dev/witness v0.0.0-20260917140356-67c4b6b76572
go.opentelemetry.io/contrib/detectors/aws/ec2/v2 v2.5.3
go.opentelemetry.io/contrib/detectors/aws/ecs v1.46.0
go.opentelemetry.io/contrib/detectors/gcp v1.46.0
Expand Down Expand Up @@ -93,7 +93,7 @@ require (
modernc.org/libc v1.74.4 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.56.0 // indirect
modernc.org/sqlite v1.57.0 // indirect
mvdan.cc/sh/v3 v3.7.0 // indirect
)

Expand Down Expand Up @@ -127,13 +127,13 @@ require (
go.opentelemetry.io/otel v1.46.0
go.opentelemetry.io/otel/metric v1.46.0
go.opentelemetry.io/otel/trace v1.46.0
golang.org/x/crypto v0.56.0
golang.org/x/crypto v0.57.0
golang.org/x/net v0.58.0
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.23.0
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.41.0 // indirect
golang.org/x/sys v0.48.0 // indirect
golang.org/x/term v0.46.0 // indirect
golang.org/x/text v0.42.0 // indirect
golang.org/x/time v0.16.0
google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect
Expand Down
Loading
Loading