From b9fe1a954b4cc91138d707e8704901ab54aa0f17 Mon Sep 17 00:00:00 2001 From: Hayden B <8418760+haydentherapper@users.noreply.github.com> Date: Tue, 18 Mar 2025 20:25:55 -0700 Subject: [PATCH 01/10] Create NoteVerifier, verification library functions Signed-off-by: Hayden B <8418760+haydentherapper@users.noreply.github.com> --- pkg/note/note.go | 68 ++++++++++++++++++++++++++++++++++++++++---- pkg/verify/verify.go | 60 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 pkg/verify/verify.go diff --git a/pkg/note/note.go b/pkg/note/note.go index e79de1d..40742c2 100644 --- a/pkg/note/note.go +++ b/pkg/note/note.go @@ -67,6 +67,24 @@ func (n *noteSigner) Sign(msg []byte) ([]byte, error) { return n.sign(msg) } +type noteVerifier struct { + name string + hash uint32 + verify func(msg, sig []byte) bool +} + +func (n *noteVerifier) Name() string { + return n.name +} + +func (n *noteVerifier) KeyHash() uint32 { + return n.hash +} + +func (n *noteVerifier) Verify(msg, sig []byte) bool { + return n.verify(msg, sig) +} + // isValidName reports whether the name conforms to the spec for the origin string of the note text // as defined in https://github.com/C2SP/C2SP/blob/main/tlog-checkpoint.md#note-text. func isValidName(name string) bool { @@ -115,29 +133,29 @@ func rsaKeyHash(name string, key *rsa.PublicKey) (uint32, error) { // NewNoteSigner converts a sigstore/sigstore/pkg/signature.Signer into a note.Signer. func NewNoteSigner(ctx context.Context, origin string, signer signature.Signer) (note.Signer, error) { if !isValidName(origin) { - return ¬eSigner{}, fmt.Errorf("invalid name %s", origin) + return nil, fmt.Errorf("invalid name %s", origin) } pubKey, err := signer.PublicKey() if err != nil { - return ¬eSigner{}, fmt.Errorf("getting public key: %w", err) + return nil, fmt.Errorf("getting public key: %w", err) } var keyID uint32 switch pk := pubKey.(type) { case *ecdsa.PublicKey: keyID, err = ecdsaKeyHash(pk) if err != nil { - return ¬eSigner{}, fmt.Errorf("getting ECDSA key hash: %w", err) + return nil, fmt.Errorf("getting ECDSA key hash: %w", err) } case ed25519.PublicKey: keyID = ed25519KeyHash(origin, pk) case *rsa.PublicKey: keyID, err = rsaKeyHash(origin, pk) if err != nil { - return ¬eSigner{}, fmt.Errorf("getting RSA key hash: %w", err) + return nil, fmt.Errorf("getting RSA key hash: %w", err) } default: - return ¬eSigner{}, fmt.Errorf("unsupported key type: %T", pubKey) + return nil, fmt.Errorf("unsupported key type: %T", pubKey) } sign := func(msg []byte) ([]byte, error) { @@ -150,3 +168,43 @@ func NewNoteSigner(ctx context.Context, origin string, signer signature.Signer) sign: sign, }, nil } + +func NewNoteVerifier(ctx context.Context, origin string, verifier signature.Verifier) (note.Verifier, error) { + if !isValidName(origin) { + return nil, fmt.Errorf("invalid name %s", origin) + } + + pubKey, err := verifier.PublicKey() + if err != nil { + return nil, fmt.Errorf("getting public key: %w", err) + } + + var keyID uint32 + switch pk := pubKey.(type) { + case *ecdsa.PublicKey: + keyID, err = ecdsaKeyHash(pk) + if err != nil { + return nil, fmt.Errorf("getting ECDSA key hash: %w", err) + } + case ed25519.PublicKey: + keyID = ed25519KeyHash(origin, pk) + case *rsa.PublicKey: + keyID, err = rsaKeyHash(origin, pk) + if err != nil { + return nil, fmt.Errorf("getting RSA key hash: %w", err) + } + default: + return nil, fmt.Errorf("unsupported key type: %T", pubKey) + } + + return ¬eVerifier{ + name: origin, + hash: keyID, + verify: func(msg, sig []byte) bool { + if err := verifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(msg)); err != nil { + return false + } + return true + }, + }, nil +} diff --git a/pkg/verify/verify.go b/pkg/verify/verify.go new file mode 100644 index 0000000..7fe60a8 --- /dev/null +++ b/pkg/verify/verify.go @@ -0,0 +1,60 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package verify + +import ( + "context" + "crypto/sha256" + "fmt" + + pbs "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" + "github.com/sigstore/rekor-tiles/pkg/note" + "github.com/sigstore/sigstore/pkg/signature" + f_log "github.com/transparency-dev/formats/log" + "github.com/transparency-dev/merkle/proof" + "github.com/transparency-dev/merkle/rfc6962" +) + +func VerifyInclusionProof(entry *pbs.TransparencyLogEntry, cp *f_log.Checkpoint) error { + leafHash := sha256.Sum256(entry.CanonicalizedBody) + if err := proof.VerifyInclusion(rfc6962.DefaultHasher, uint64(entry.LogIndex), cp.Size, leafHash[:], entry.InclusionProof.Hashes, cp.Hash); err != nil { + return err + } + return nil +} + +func VerifyCheckpoint(ctx context.Context, entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) (*f_log.Checkpoint, error) { + v, err := note.NewNoteVerifier(ctx, origin, verifier) + if err != nil { + return nil, fmt.Errorf("error creating note verifier: %v", err) + } + cp, _, _, err := f_log.ParseCheckpoint([]byte(entry.InclusionProof.GetCheckpoint().GetEnvelope()), v.Name(), v) + if err != nil { + return nil, fmt.Errorf("unverified checkpoint signature: %v", err) + } + return cp, nil +} + +func VerifyLogEntry(ctx context.Context, entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) error { + cp, err := VerifyCheckpoint(ctx, entry, origin, verifier) + if err != nil { + return err + } + if err := VerifyInclusionProof(entry, cp); err != nil { + return nil + } + return nil +} From a28764ed1d3e9d16489680d945239c6a8a2b09cf Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Fri, 21 Mar 2025 10:15:17 +0200 Subject: [PATCH 02/10] note: Refactor Refactor keyid calculation out of both constructors Also remove the unused context argument Signed-off-by: Jussi Kukkonen --- pkg/note/note.go | 63 ++++++++++++++++++++++---------------------- pkg/verify/verify.go | 9 +++---- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/pkg/note/note.go b/pkg/note/note.go index 40742c2..d04c8eb 100644 --- a/pkg/note/note.go +++ b/pkg/note/note.go @@ -21,6 +21,7 @@ package note import ( "bytes" "context" + "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" @@ -130,32 +131,45 @@ func rsaKeyHash(name string, key *rsa.PublicKey) (uint32, error) { return genConformantKeyHash(name, rsaAlg, marshaled), nil } -// NewNoteSigner converts a sigstore/sigstore/pkg/signature.Signer into a note.Signer. -func NewNoteSigner(ctx context.Context, origin string, signer signature.Signer) (note.Signer, error) { - if !isValidName(origin) { - return nil, fmt.Errorf("invalid name %s", origin) - } - - pubKey, err := signer.PublicKey() - if err != nil { - return nil, fmt.Errorf("getting public key: %w", err) - } +// keyHash generates a 4-byte identifier for a public key/origin +func keyHash(origin string, key crypto.PublicKey) (uint32, error) { var keyID uint32 - switch pk := pubKey.(type) { + var err error + + switch pk := key.(type) { case *ecdsa.PublicKey: keyID, err = ecdsaKeyHash(pk) if err != nil { - return nil, fmt.Errorf("getting ECDSA key hash: %w", err) + return 0, fmt.Errorf("getting ECDSA key hash: %w", err) } case ed25519.PublicKey: keyID = ed25519KeyHash(origin, pk) case *rsa.PublicKey: keyID, err = rsaKeyHash(origin, pk) if err != nil { - return nil, fmt.Errorf("getting RSA key hash: %w", err) + return 0, fmt.Errorf("getting RSA key hash: %w", err) } default: - return nil, fmt.Errorf("unsupported key type: %T", pubKey) + return 0, fmt.Errorf("unsupported key type: %T", key) + } + + return keyID, nil +} + +// NewNoteSigner converts a sigstore/sigstore/pkg/signature.Signer into a note.Signer. +func NewNoteSigner(ctx context.Context, origin string, signer signature.Signer) (note.Signer, error) { + if !isValidName(origin) { + return nil, fmt.Errorf("invalid name %s", origin) + } + + pubKey, err := signer.PublicKey() + if err != nil { + return nil, fmt.Errorf("getting public key: %w", err) + } + + keyID, err := keyHash(origin, pubKey) + if err != nil { + return nil, err } sign := func(msg []byte) ([]byte, error) { @@ -169,7 +183,7 @@ func NewNoteSigner(ctx context.Context, origin string, signer signature.Signer) }, nil } -func NewNoteVerifier(ctx context.Context, origin string, verifier signature.Verifier) (note.Verifier, error) { +func NewNoteVerifier(origin string, verifier signature.Verifier) (note.Verifier, error) { if !isValidName(origin) { return nil, fmt.Errorf("invalid name %s", origin) } @@ -179,22 +193,9 @@ func NewNoteVerifier(ctx context.Context, origin string, verifier signature.Veri return nil, fmt.Errorf("getting public key: %w", err) } - var keyID uint32 - switch pk := pubKey.(type) { - case *ecdsa.PublicKey: - keyID, err = ecdsaKeyHash(pk) - if err != nil { - return nil, fmt.Errorf("getting ECDSA key hash: %w", err) - } - case ed25519.PublicKey: - keyID = ed25519KeyHash(origin, pk) - case *rsa.PublicKey: - keyID, err = rsaKeyHash(origin, pk) - if err != nil { - return nil, fmt.Errorf("getting RSA key hash: %w", err) - } - default: - return nil, fmt.Errorf("unsupported key type: %T", pubKey) + keyID, err := keyHash(origin, pubKey) + if err != nil { + return nil, err } return ¬eVerifier{ diff --git a/pkg/verify/verify.go b/pkg/verify/verify.go index 7fe60a8..88ee315 100644 --- a/pkg/verify/verify.go +++ b/pkg/verify/verify.go @@ -16,7 +16,6 @@ package verify import ( - "context" "crypto/sha256" "fmt" @@ -36,8 +35,8 @@ func VerifyInclusionProof(entry *pbs.TransparencyLogEntry, cp *f_log.Checkpoint) return nil } -func VerifyCheckpoint(ctx context.Context, entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) (*f_log.Checkpoint, error) { - v, err := note.NewNoteVerifier(ctx, origin, verifier) +func VerifyCheckpoint(entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) (*f_log.Checkpoint, error) { + v, err := note.NewNoteVerifier(origin, verifier) if err != nil { return nil, fmt.Errorf("error creating note verifier: %v", err) } @@ -48,8 +47,8 @@ func VerifyCheckpoint(ctx context.Context, entry *pbs.TransparencyLogEntry, orig return cp, nil } -func VerifyLogEntry(ctx context.Context, entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) error { - cp, err := VerifyCheckpoint(ctx, entry, origin, verifier) +func VerifyLogEntry(entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) error { + cp, err := VerifyCheckpoint(entry, origin, verifier) if err != nil { return err } From ff8ea6dc8238d8140edc92c76b872d7ee08efa19 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Fri, 21 Mar 2025 10:16:00 +0200 Subject: [PATCH 03/10] verify: Various small changes in verify functions * Use SafeInt64 * fix VerifyLogEntry error return value Signed-off-by: Jussi Kukkonen --- pkg/verify/verify.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/pkg/verify/verify.go b/pkg/verify/verify.go index 88ee315..9589f79 100644 --- a/pkg/verify/verify.go +++ b/pkg/verify/verify.go @@ -1,5 +1,5 @@ // -// Copyright 2022 The Sigstore Authors. +// Copyright 2025 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,21 +21,28 @@ import ( pbs "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" "github.com/sigstore/rekor-tiles/pkg/note" + "github.com/sigstore/rekor-tiles/pkg/tessera" "github.com/sigstore/sigstore/pkg/signature" f_log "github.com/transparency-dev/formats/log" "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" ) -func VerifyInclusionProof(entry *pbs.TransparencyLogEntry, cp *f_log.Checkpoint) error { +// VerifyInclusionProof verifies an entry's inclusion proof +func VerifyInclusionProof(entry *pbs.TransparencyLogEntry, cp *f_log.Checkpoint) error { //nolint: revive leafHash := sha256.Sum256(entry.CanonicalizedBody) - if err := proof.VerifyInclusion(rfc6962.DefaultHasher, uint64(entry.LogIndex), cp.Size, leafHash[:], entry.InclusionProof.Hashes, cp.Hash); err != nil { - return err + index, err := tessera.NewSafeInt64(entry.LogIndex) + if err != nil { + return fmt.Errorf("invalid index: %w", err) + } + if err := proof.VerifyInclusion(rfc6962.DefaultHasher, index.U(), cp.Size, leafHash[:], entry.InclusionProof.Hashes, cp.Hash); err != nil { + return fmt.Errorf("verifying inclusion: %w", err) } return nil } -func VerifyCheckpoint(entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) (*f_log.Checkpoint, error) { +// VerifyCheckpoint verifies the signature on the entry's inclusion proof checkpoint +func VerifyCheckpoint(entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) (*f_log.Checkpoint, error) { //nolint: revive v, err := note.NewNoteVerifier(origin, verifier) if err != nil { return nil, fmt.Errorf("error creating note verifier: %v", err) @@ -47,13 +54,12 @@ func VerifyCheckpoint(entry *pbs.TransparencyLogEntry, origin string, verifier s return cp, nil } -func VerifyLogEntry(entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) error { +// VerifyLogEntry verifies the log entry: This includes verifying the signature on the entry's +// inclusion proof checkpoint and verifying the entry inclusion proof +func VerifyLogEntry(entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) error { //nolint: revive cp, err := VerifyCheckpoint(entry, origin, verifier) if err != nil { return err } - if err := VerifyInclusionProof(entry, cp); err != nil { - return nil - } - return nil + return VerifyInclusionProof(entry, cp) } From 219827a5c52933470c1245ab5069c9869ead45eb Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Fri, 21 Mar 2025 11:29:49 +0200 Subject: [PATCH 04/10] verify: Use sumdb Verifier as the argument tessera functions accept the sumdb Verifier so this likely makes client code more consistent: it does mean caller needs to handle v, e := note.NewNoteVerifier(origin, verifier) if they have a signature.Verifier. Signed-off-by: Jussi Kukkonen --- pkg/verify/verify.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/pkg/verify/verify.go b/pkg/verify/verify.go index 9589f79..74447f8 100644 --- a/pkg/verify/verify.go +++ b/pkg/verify/verify.go @@ -20,12 +20,11 @@ import ( "fmt" pbs "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" - "github.com/sigstore/rekor-tiles/pkg/note" "github.com/sigstore/rekor-tiles/pkg/tessera" - "github.com/sigstore/sigstore/pkg/signature" f_log "github.com/transparency-dev/formats/log" "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" + sumdb_note "golang.org/x/mod/sumdb/note" ) // VerifyInclusionProof verifies an entry's inclusion proof @@ -42,22 +41,18 @@ func VerifyInclusionProof(entry *pbs.TransparencyLogEntry, cp *f_log.Checkpoint) } // VerifyCheckpoint verifies the signature on the entry's inclusion proof checkpoint -func VerifyCheckpoint(entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) (*f_log.Checkpoint, error) { //nolint: revive - v, err := note.NewNoteVerifier(origin, verifier) - if err != nil { - return nil, fmt.Errorf("error creating note verifier: %v", err) - } - cp, _, _, err := f_log.ParseCheckpoint([]byte(entry.InclusionProof.GetCheckpoint().GetEnvelope()), v.Name(), v) +func VerifyCheckpoint(entry *pbs.TransparencyLogEntry, verifier sumdb_note.Verifier) (*f_log.Checkpoint, error) { //nolint: revive + cp, _, _, err := f_log.ParseCheckpoint([]byte(entry.InclusionProof.GetCheckpoint().GetEnvelope()), verifier.Name(), verifier) if err != nil { return nil, fmt.Errorf("unverified checkpoint signature: %v", err) } return cp, nil } -// VerifyLogEntry verifies the log entry: This includes verifying the signature on the entry's +// VerifyLogEntry verifies the log entry. This includes verifying the signature on the entry's // inclusion proof checkpoint and verifying the entry inclusion proof -func VerifyLogEntry(entry *pbs.TransparencyLogEntry, origin string, verifier signature.Verifier) error { //nolint: revive - cp, err := VerifyCheckpoint(entry, origin, verifier) +func VerifyLogEntry(entry *pbs.TransparencyLogEntry, verifier sumdb_note.Verifier) error { //nolint: revive + cp, err := VerifyCheckpoint(entry, verifier) if err != nil { return err } From 15975d6861a54ec8128631485eee9d213cce5d88 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 24 Mar 2025 11:39:46 +0200 Subject: [PATCH 05/10] verify: Use correct hash in inclusion proof Signed-off-by: Jussi Kukkonen --- pkg/verify/verify.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/verify/verify.go b/pkg/verify/verify.go index 74447f8..cb4ca21 100644 --- a/pkg/verify/verify.go +++ b/pkg/verify/verify.go @@ -16,7 +16,6 @@ package verify import ( - "crypto/sha256" "fmt" pbs "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" @@ -29,12 +28,12 @@ import ( // VerifyInclusionProof verifies an entry's inclusion proof func VerifyInclusionProof(entry *pbs.TransparencyLogEntry, cp *f_log.Checkpoint) error { //nolint: revive - leafHash := sha256.Sum256(entry.CanonicalizedBody) + leafHash := rfc6962.DefaultHasher.HashLeaf(entry.CanonicalizedBody) index, err := tessera.NewSafeInt64(entry.LogIndex) if err != nil { return fmt.Errorf("invalid index: %w", err) } - if err := proof.VerifyInclusion(rfc6962.DefaultHasher, index.U(), cp.Size, leafHash[:], entry.InclusionProof.Hashes, cp.Hash); err != nil { + if err := proof.VerifyInclusion(rfc6962.DefaultHasher, index.U(), cp.Size, leafHash, entry.InclusionProof.Hashes, cp.Hash); err != nil { return fmt.Errorf("verifying inclusion: %w", err) } return nil From a32adbb56c2a047b6ccab4e04f5cb0b0aad5a735 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Mon, 24 Mar 2025 13:38:36 +0200 Subject: [PATCH 06/10] verify: Add initial verification test from Colleen * Tests are roughly like TestInclusion TestCheckpoint in https://github.com/sigstore/rekor/blob/main/pkg/verify/verify_test.go (the entry content is from there as well) * The signed checkpoint envelope creation in TestVerifyCheckpoint is from https://github.com/transparency-dev/trillian-tessera/blob/ae724376e1ace4046767511c72c6006bde3ec87e/append_lifecycle.go#L298-L316 Signed-off-by: Jussi Kukkonen --- pkg/verify/verify_test.go | 85 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 pkg/verify/verify_test.go diff --git a/pkg/verify/verify_test.go b/pkg/verify/verify_test.go new file mode 100644 index 0000000..d5481c7 --- /dev/null +++ b/pkg/verify/verify_test.go @@ -0,0 +1,85 @@ +package verify + +import ( + "context" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "testing" + + pbs "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" + rekornote "github.com/sigstore/rekor-tiles/pkg/note" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/stretchr/testify/assert" + f_log "github.com/transparency-dev/formats/log" + note "golang.org/x/mod/sumdb/note" +) + +func TestVerifyInclusionProof(t *testing.T) { + hash, err := hex.DecodeString("59a575f157274702c38de3ab1e1784226f391fb79500ebf9f02b4439fb77574c") + if err != nil { + t.Fatal(err) + } + rootHash, err := hex.DecodeString("5be1758dd2228acfaf2546b4b6ce8aa40c82a3748f3dcb550e0d67ba34f02a45") + if err != nil { + t.Fatal(err) + } + body, err := base64.StdEncoding.DecodeString("eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoicmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJlY2RjNTUzNmY3M2JkYWU4ODE2ZjBlYTQwNzI2ZWY1ZTliODEwZDkxNDQ5MzA3NTkwM2JiOTA2MjNkOTdiMWQ4In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUQvUGRQUW1LV0MxKzBCTkVkNWdLdlFHcjF4eGwzaWVVZmZ2M2prMXp6Skt3SWhBTEJqM3hmQXlXeGx6NGpwb0lFSVYxVWZLOXZua1VVT1NvZVp4QlpQSEtQQyIsImZvcm1hdCI6Ing1MDkiLCJwdWJsaWNLZXkiOnsiY29udGVudCI6IkxTMHRMUzFDUlVkSlRpQlFWVUpNU1VNZ1MwVlpMUzB0TFMwS1RVWnJkMFYzV1VoTGIxcEplbW93UTBGUldVbExiMXBKZW1vd1JFRlJZMFJSWjBGRlRVOWpWR1pTUWxNNWFtbFlUVGd4UmxvNFoyMHZNU3R2YldWTmR3cHRiaTh6TkRjdk5UVTJaeTlzY21sVE56SjFUV2haT1V4alZDczFWVW8yWmtkQ1oyeHlOVm80VERCS1RsTjFZWE41WldRNVQzUmhVblozUFQwS0xTMHRMUzFGVGtRZ1VGVkNURWxESUV0RldTMHRMUzB0Q2c9PSJ9fX19") + if err != nil { + t.Fatal(err) + } + entry := &pbs.TransparencyLogEntry{ + LogIndex: 1, + InclusionProof: &pbs.InclusionProof{ + LogIndex: 1, + TreeSize: 2, + Hashes: [][]byte{ + []byte(hash), + }, + }, + CanonicalizedBody: body, + } + checkpoint := &f_log.Checkpoint{ + Size: 2, + Hash: rootHash, + } + gotErr := VerifyInclusionProof(entry, checkpoint) + assert.NoError(t, gotErr) +} + +func TestVerifyCheckpoint(t *testing.T) { + hostname := "rekor.localhost" + treeSize := uint64(2) + sv, _, err := signature.NewDefaultECDSASignerVerifier() + if err != nil { + t.Fatal(err) + } + noteSigner, err := rekornote.NewNoteSigner(context.Background(), hostname, sv) + if err != nil { + t.Fatal(err) + } + noteVerifier, err := rekornote.NewNoteVerifier(hostname, sv) + if err != nil { + t.Fatal(err) + } + rootHash := sha256.Sum256([]byte{1, 2, 3}) + cpRaw := f_log.Checkpoint{ + Origin: hostname, + Size: treeSize, + Hash: rootHash[:], + }.Marshal() + + n, err := note.Sign(¬e.Note{Text: string(cpRaw)}, noteSigner) + if err != nil { + t.Fatal(err) + } + entry := &pbs.TransparencyLogEntry{ + InclusionProof: &pbs.InclusionProof{ + Checkpoint: &pbs.Checkpoint{ + Envelope: string(n), + }, + }, + } + _, gotErr := VerifyCheckpoint(entry, noteVerifier) + assert.NoError(t, gotErr) +} From 9e4e04afc2e28b5a0a0ac5956e8fb4f9ec0dd63a Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 24 Mar 2025 14:23:05 +0200 Subject: [PATCH 07/10] verify: Add a few error cases to TestVerifyCheckpoint Signed-off-by: Jussi Kukkonen --- pkg/verify/verify_test.go | 68 ++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/pkg/verify/verify_test.go b/pkg/verify/verify_test.go index d5481c7..b820bc7 100644 --- a/pkg/verify/verify_test.go +++ b/pkg/verify/verify_test.go @@ -47,25 +47,15 @@ func TestVerifyInclusionProof(t *testing.T) { assert.NoError(t, gotErr) } -func TestVerifyCheckpoint(t *testing.T) { - hostname := "rekor.localhost" - treeSize := uint64(2) - sv, _, err := signature.NewDefaultECDSASignerVerifier() - if err != nil { - t.Fatal(err) - } - noteSigner, err := rekornote.NewNoteSigner(context.Background(), hostname, sv) - if err != nil { - t.Fatal(err) - } - noteVerifier, err := rekornote.NewNoteVerifier(hostname, sv) +func getTestEntry(t *testing.T, signer signature.Signer, hostname string) *pbs.TransparencyLogEntry { + noteSigner, err := rekornote.NewNoteSigner(context.Background(), hostname, signer) if err != nil { t.Fatal(err) } rootHash := sha256.Sum256([]byte{1, 2, 3}) cpRaw := f_log.Checkpoint{ Origin: hostname, - Size: treeSize, + Size: uint64(2), Hash: rootHash[:], }.Marshal() @@ -73,13 +63,59 @@ func TestVerifyCheckpoint(t *testing.T) { if err != nil { t.Fatal(err) } - entry := &pbs.TransparencyLogEntry{ + + return &pbs.TransparencyLogEntry{ InclusionProof: &pbs.InclusionProof{ Checkpoint: &pbs.Checkpoint{ Envelope: string(n), }, }, } - _, gotErr := VerifyCheckpoint(entry, noteVerifier) - assert.NoError(t, gotErr) +} + +func TestVerifyCheckpoint(t *testing.T) { + hostname := "rekor.localhost" + sv, _, err := signature.NewDefaultECDSASignerVerifier() + if err != nil { + t.Fatal(err) + } + + otherSigner, _, err := signature.NewDefaultECDSASignerVerifier() + if err != nil { + t.Fatal(err) + } + + noteVerifier, err := rekornote.NewNoteVerifier(hostname, sv) + if err != nil { + t.Fatal(err) + } + + for _, test := range []struct { + name string + entry *pbs.TransparencyLogEntry + wantErr bool + }{ + { + name: "valid checkpoint", + entry: getTestEntry(t, sv, hostname), + wantErr: false, + }, + { + name: "hostname mismatch", + entry: getTestEntry(t, sv, "other.host"), + wantErr: true, + }, + { + name: "signature mismatch", + entry: getTestEntry(t, otherSigner, hostname), + wantErr: true, + }, + } { + t.Run(string(test.name), func(t *testing.T) { + _, gotErr := VerifyCheckpoint(test.entry, noteVerifier) + if (gotErr != nil) != test.wantErr { + t.Fatalf("VerifyCheckpoint = %t, wantErr %t", gotErr, test.wantErr) + } + }) + } } From e4704771ac85ace706819923b00ca4c79d1aafb2 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 24 Mar 2025 15:10:59 +0200 Subject: [PATCH 08/10] verify: Add some error cases to TestVerifyInclusionProof Also refactor a bit, to avoid unnecessary error handling Signed-off-by: Jussi Kukkonen --- pkg/verify/verify_test.go | 96 +++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/pkg/verify/verify_test.go b/pkg/verify/verify_test.go index b820bc7..a4a7b98 100644 --- a/pkg/verify/verify_test.go +++ b/pkg/verify/verify_test.go @@ -4,47 +4,95 @@ import ( "context" "crypto/sha256" "encoding/base64" - "encoding/hex" "testing" pbs "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" rekornote "github.com/sigstore/rekor-tiles/pkg/note" "github.com/sigstore/sigstore/pkg/signature" - "github.com/stretchr/testify/assert" f_log "github.com/transparency-dev/formats/log" note "golang.org/x/mod/sumdb/note" ) func TestVerifyInclusionProof(t *testing.T) { - hash, err := hex.DecodeString("59a575f157274702c38de3ab1e1784226f391fb79500ebf9f02b4439fb77574c") - if err != nil { - t.Fatal(err) - } - rootHash, err := hex.DecodeString("5be1758dd2228acfaf2546b4b6ce8aa40c82a3748f3dcb550e0d67ba34f02a45") - if err != nil { - t.Fatal(err) - } + hash := []byte{89, 165, 117, 241, 87, 39, 71, 2, 195, 141, 227, 171, 30, 23, 132, 34, 111, 57, 31, 183, 149, 0, 235, 249, 240, 43, 68, 57, 251, 119, 87, 76} + rootHash := []byte{91, 225, 117, 141, 210, 34, 138, 207, 175, 37, 70, 180, 182, 206, 138, 164, 12, 130, 163, 116, 143, 61, 203, 85, 14, 13, 103, 186, 52, 240, 42, 69} body, err := base64.StdEncoding.DecodeString("eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoicmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJlY2RjNTUzNmY3M2JkYWU4ODE2ZjBlYTQwNzI2ZWY1ZTliODEwZDkxNDQ5MzA3NTkwM2JiOTA2MjNkOTdiMWQ4In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUQvUGRQUW1LV0MxKzBCTkVkNWdLdlFHcjF4eGwzaWVVZmZ2M2prMXp6Skt3SWhBTEJqM3hmQXlXeGx6NGpwb0lFSVYxVWZLOXZua1VVT1NvZVp4QlpQSEtQQyIsImZvcm1hdCI6Ing1MDkiLCJwdWJsaWNLZXkiOnsiY29udGVudCI6IkxTMHRMUzFDUlVkSlRpQlFWVUpNU1VNZ1MwVlpMUzB0TFMwS1RVWnJkMFYzV1VoTGIxcEplbW93UTBGUldVbExiMXBKZW1vd1JFRlJZMFJSWjBGRlRVOWpWR1pTUWxNNWFtbFlUVGd4UmxvNFoyMHZNU3R2YldWTmR3cHRiaTh6TkRjdk5UVTJaeTlzY21sVE56SjFUV2haT1V4alZDczFWVW8yWmtkQ1oyeHlOVm80VERCS1RsTjFZWE41WldRNVQzUmhVblozUFQwS0xTMHRMUzFGVGtRZ1VGVkNURWxESUV0RldTMHRMUzB0Q2c9PSJ9fX19") if err != nil { t.Fatal(err) } - entry := &pbs.TransparencyLogEntry{ - LogIndex: 1, - InclusionProof: &pbs.InclusionProof{ - LogIndex: 1, - TreeSize: 2, - Hashes: [][]byte{ - []byte(hash), + + for _, test := range []struct { + name string + proof *pbs.InclusionProof + logSize uint64 + wantErr bool + }{ + { + name: "valid inclusionproof", + proof: &pbs.InclusionProof{ + LogIndex: 1, + TreeSize: 2, + Hashes: [][]byte{ + []byte(hash), + }, }, + logSize: 2, + wantErr: false, }, - CanonicalizedBody: body, - } - checkpoint := &f_log.Checkpoint{ - Size: 2, - Hash: rootHash, + { + name: "invalid hash", + proof: &pbs.InclusionProof{ + LogIndex: 1, + TreeSize: 2, + Hashes: [][]byte{ + []byte([]byte{0, 165, 117, 241, 87, 39, 71, 2, 195, 141, 227, 171, 30, 23, 132, 34, 111, 57, 31, 183, 149, 0, 235, 249, 240, 43, 68, 57, 251, 119, 87, 76}), + }, + }, + logSize: 2, + wantErr: true, + }, + { + name: "inclusion index beyond log size", + proof: &pbs.InclusionProof{ + LogIndex: 1, + TreeSize: 2, + Hashes: [][]byte{ + []byte(hash), + }, + }, + logSize: 1, + wantErr: true, + }, + { + name: "wrong proof size", + proof: &pbs.InclusionProof{ + LogIndex: 1, + TreeSize: 2, + Hashes: [][]byte{ + []byte(hash), + }, + }, + logSize: 3, + wantErr: true, + }, + } { + t.Run(string(test.name), func(t *testing.T) { + checkpoint := &f_log.Checkpoint{ + Size: test.logSize, + Hash: rootHash, + } + + entry := &pbs.TransparencyLogEntry{ + LogIndex: 1, + InclusionProof: test.proof, + CanonicalizedBody: body, + } + gotErr := VerifyInclusionProof(entry, checkpoint) + if (gotErr != nil) != test.wantErr { + t.Fatalf("VerifyCheckpoint = %t, wantErr %t", gotErr, test.wantErr) + } + }) } - gotErr := VerifyInclusionProof(entry, checkpoint) - assert.NoError(t, gotErr) } func getTestEntry(t *testing.T, signer signature.Signer, hostname string) *pbs.TransparencyLogEntry { From c1e1bca9776f2dd7534a1124b28e0a6f3bcf1614 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 24 Mar 2025 15:17:05 +0200 Subject: [PATCH 09/10] verify: Add license blurb to verify_tests.go Signed-off-by: Jussi Kukkonen --- pkg/verify/verify_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkg/verify/verify_test.go b/pkg/verify/verify_test.go index a4a7b98..801c2e8 100644 --- a/pkg/verify/verify_test.go +++ b/pkg/verify/verify_test.go @@ -1,3 +1,18 @@ +// +// Copyright 2025 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package verify import ( From c6190681e8677c3b8237fb447cbe57e6aa597cfc Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 24 Mar 2025 18:09:28 +0200 Subject: [PATCH 10/10] verify: Add very basic test for VerifyLogEntry Signed-off-by: Jussi Kukkonen --- pkg/verify/verify_test.go | 59 +++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/pkg/verify/verify_test.go b/pkg/verify/verify_test.go index 801c2e8..267d01d 100644 --- a/pkg/verify/verify_test.go +++ b/pkg/verify/verify_test.go @@ -18,12 +18,12 @@ package verify import ( "context" "crypto/sha256" - "encoding/base64" "testing" pbs "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" rekornote "github.com/sigstore/rekor-tiles/pkg/note" "github.com/sigstore/sigstore/pkg/signature" + "github.com/stretchr/testify/assert" f_log "github.com/transparency-dev/formats/log" note "golang.org/x/mod/sumdb/note" ) @@ -31,10 +31,7 @@ import ( func TestVerifyInclusionProof(t *testing.T) { hash := []byte{89, 165, 117, 241, 87, 39, 71, 2, 195, 141, 227, 171, 30, 23, 132, 34, 111, 57, 31, 183, 149, 0, 235, 249, 240, 43, 68, 57, 251, 119, 87, 76} rootHash := []byte{91, 225, 117, 141, 210, 34, 138, 207, 175, 37, 70, 180, 182, 206, 138, 164, 12, 130, 163, 116, 143, 61, 203, 85, 14, 13, 103, 186, 52, 240, 42, 69} - body, err := base64.StdEncoding.DecodeString("eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoicmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJlY2RjNTUzNmY3M2JkYWU4ODE2ZjBlYTQwNzI2ZWY1ZTliODEwZDkxNDQ5MzA3NTkwM2JiOTA2MjNkOTdiMWQ4In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUQvUGRQUW1LV0MxKzBCTkVkNWdLdlFHcjF4eGwzaWVVZmZ2M2prMXp6Skt3SWhBTEJqM3hmQXlXeGx6NGpwb0lFSVYxVWZLOXZua1VVT1NvZVp4QlpQSEtQQyIsImZvcm1hdCI6Ing1MDkiLCJwdWJsaWNLZXkiOnsiY29udGVudCI6IkxTMHRMUzFDUlVkSlRpQlFWVUpNU1VNZ1MwVlpMUzB0TFMwS1RVWnJkMFYzV1VoTGIxcEplbW93UTBGUldVbExiMXBKZW1vd1JFRlJZMFJSWjBGRlRVOWpWR1pTUWxNNWFtbFlUVGd4UmxvNFoyMHZNU3R2YldWTmR3cHRiaTh6TkRjdk5UVTJaeTlzY21sVE56SjFUV2haT1V4alZDczFWVW8yWmtkQ1oyeHlOVm80VERCS1RsTjFZWE41WldRNVQzUmhVblozUFQwS0xTMHRMUzFGVGtRZ1VGVkNURWxESUV0RldTMHRMUzB0Q2c9PSJ9fX19") - if err != nil { - t.Fatal(err) - } + body := []byte("{\"apiVersion\":\"0.0.1\",\"kind\":\"rekord\",\"spec\":{\"data\":{\"hash\":{\"algorithm\":\"sha256\",\"value\":\"ecdc5536f73bdae8816f0ea40726ef5e9b810d914493075903bb90623d97b1d8\"}},\"signature\":{\"content\":\"MEYCIQD/PdPQmKWC1+0BNEd5gKvQGr1xxl3ieUffv3jk1zzJKwIhALBj3xfAyWxlz4jpoIEIV1UfK9vnkUUOSoeZxBZPHKPC\",\"format\":\"x509\",\"publicKey\":{\"content\":\"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFTU9jVGZSQlM5amlYTTgxRlo4Z20vMStvbWVNdwptbi8zNDcvNTU2Zy9scmlTNzJ1TWhZOUxjVCs1VUo2ZkdCZ2xyNVo4TDBKTlN1YXN5ZWQ5T3RhUnZ3PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==\"}}}}") for _, test := range []struct { name string @@ -182,3 +179,55 @@ func TestVerifyCheckpoint(t *testing.T) { }) } } + +func TestVerifyLogEntry(t *testing.T) { + hostname := "rekor.localhost" + hash := []byte{89, 165, 117, 241, 87, 39, 71, 2, 195, 141, 227, 171, 30, 23, 132, 34, 111, 57, 31, 183, 149, 0, 235, 249, 240, 43, 68, 57, 251, 119, 87, 76} + rootHash := []byte{91, 225, 117, 141, 210, 34, 138, 207, 175, 37, 70, 180, 182, 206, 138, 164, 12, 130, 163, 116, 143, 61, 203, 85, 14, 13, 103, 186, 52, 240, 42, 69} + body := []byte("{\"apiVersion\":\"0.0.1\",\"kind\":\"rekord\",\"spec\":{\"data\":{\"hash\":{\"algorithm\":\"sha256\",\"value\":\"ecdc5536f73bdae8816f0ea40726ef5e9b810d914493075903bb90623d97b1d8\"}},\"signature\":{\"content\":\"MEYCIQD/PdPQmKWC1+0BNEd5gKvQGr1xxl3ieUffv3jk1zzJKwIhALBj3xfAyWxlz4jpoIEIV1UfK9vnkUUOSoeZxBZPHKPC\",\"format\":\"x509\",\"publicKey\":{\"content\":\"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFTU9jVGZSQlM5amlYTTgxRlo4Z20vMStvbWVNdwptbi8zNDcvNTU2Zy9scmlTNzJ1TWhZOUxjVCs1VUo2ZkdCZ2xyNVo4TDBKTlN1YXN5ZWQ5T3RhUnZ3PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==\"}}}}") + + sv, _, err := signature.NewDefaultECDSASignerVerifier() + if err != nil { + t.Fatal(err) + } + + noteVerifier, err := rekornote.NewNoteVerifier(hostname, sv) + if err != nil { + t.Fatal(err) + } + + noteSigner, err := rekornote.NewNoteSigner(context.Background(), hostname, sv) + if err != nil { + t.Fatal(err) + } + cpRaw := f_log.Checkpoint{ + Origin: hostname, + Size: uint64(2), + Hash: rootHash, + }.Marshal() + + n, err := note.Sign(¬e.Note{Text: string(cpRaw)}, noteSigner) + if err != nil { + t.Fatal(err) + } + + proof := &pbs.InclusionProof{ + LogIndex: 1, + TreeSize: 2, + Hashes: [][]byte{ + []byte(hash), + }, + Checkpoint: &pbs.Checkpoint{ + Envelope: string(n), + }, + } + + entry := &pbs.TransparencyLogEntry{ + CanonicalizedBody: body, + InclusionProof: proof, + LogIndex: 1, + } + + gotErr := VerifyLogEntry(entry, noteVerifier) + assert.NoError(t, gotErr) +}