Skip to content

Add tests for verifying a C2SP-compliant checkpoint#241

Merged
Hayden-IO merged 2 commits into
sigstore:mainfrom
Hayden-IO:checkpoint-tests
Aug 21, 2025
Merged

Add tests for verifying a C2SP-compliant checkpoint#241
Hayden-IO merged 2 commits into
sigstore:mainfrom
Hayden-IO:checkpoint-tests

Conversation

@Hayden-IO

@Hayden-IO Hayden-IO commented Aug 19, 2025

Copy link
Copy Markdown
Collaborator

The test for a valid checkpoint with a single signature is already tested in the happy path. This adds positive tests for:

  • A checkpoint with multiple signatures from the log, which could happen when a log has multiple signing algorithms, e.g. for PQC
  • A checkpoint with a cosignature from a witness
  • A checkpoint with two cosignatures from different witnesses
  • A checkpoint with both multiple log signatures and a cosignature
  • A checkpoint where the log signature is second and a witness signature is first, to check that there are no assumptions on ordering

This also adds negative tests for:

  • Missing origin
  • Missing size
  • Missing root hash
  • Missing log signature

I did not add positive tests for a log signed with ECDSA or RSA since we'd need to generate a custom trusted root.

Fixes sigstore/rekor-tiles#182

Summary

Release Note

Documentation

@Hayden-IO

Copy link
Copy Markdown
Collaborator Author

@jku @loosebazooka Can you test this against your clients? Looks like this is failing for sigstore-python due to this check that each signature line is from the same key.

The test for a valid checkpoint with a single signature is already
tested in the happy path. This adds positive tests for:

* A checkpoint with multiple signatures from the log, which could happen
  when a log has multiple signing algorithms, e.g. for PQC
* A checkpoint with a cosignature from a witness
* A checkpoint with two cosignatures from different witnesses
* A checkpoint with both multiple log signatures and a cosignature
* A checkpoint where the log signature is second and a witness signature
  is first, to check that there are no assumptions on ordering

This also adds negative tests for:

* Missing origin
* Missing size
* Missing root hash
* Missing log signature

I did not add positive tests for a log signed with ECDSA or RSA since
we'd need to generate a custom trusted root.

Signed-off-by: Hayden B <8418760+haydentherapper@users.noreply.github.com>
@Hayden-IO

Copy link
Copy Markdown
Collaborator Author

Also if someone already has a python environment set up and could test against sigstore-go, that'd be helpful, otherwise I can get this set up

@Hayden-IO

Copy link
Copy Markdown
Collaborator Author

Also for posterity, here's the script I used to generate these:

package main

import (
	"context"
	"crypto"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"encoding/base64"
	"flag"
	"fmt"
	"os"

	rekor_note "github.com/sigstore/rekor-tiles/pkg/note"
	"github.com/sigstore/sigstore/pkg/signature"
	f_note "github.com/transparency-dev/formats/note"
	"golang.org/x/mod/sumdb/note"
)

var (
	checkpointPath = flag.String("checkpoint", "", "path to transparency log checkpoint to sign with additional signers")
	origin         = flag.String("origin", "", "origin of the checkpoint")
	pubKeyPath     = flag.String("pubkey", "", "path to public key to verify checkpoint")
)

func main() {
	flag.Parse()

	if *checkpointPath == "" || *origin == "" || *pubKeyPath == "" {
		flag.Usage()
		return
	}
	checkpoint, err := os.ReadFile(*checkpointPath)
	if err != nil {
		panic(err)
	}
	pubKey, err := os.ReadFile(*pubKeyPath)
	if err != nil {
		panic(err)
	}

	// Parse checkpoint
	pkixPubKey, err := base64.StdEncoding.DecodeString(string(pubKey))
	if err != nil {
		panic(err)
	}
	pub, err := x509.ParsePKIXPublicKey(pkixPubKey)
	if err != nil {
		panic(err)
	}
	sigVer, err := signature.LoadVerifier(pub, crypto.SHA256)
	if err != nil {
		panic(err)
	}
	nv, err := rekor_note.NewNoteVerifier(*origin, sigVer)
	if err != nil {
		panic(err)
	}
	n, err := note.Open(checkpoint, note.VerifierList(nv))
	if err != nil {
		panic(err)
	}

	// Generate two additional signatures, one for the same origin to simulate PQC signing...
	sv, _, err := signature.NewECDSASignerVerifier(elliptic.P256(), rand.Reader, crypto.SHA256)
	if err != nil {
		panic(err)
	}
	addNoteSigner, err := rekor_note.NewNoteSigner(context.Background(), *origin, sv)
	if err != nil {
		panic(err)
	}
	aaNoteVerifier, err := rekor_note.NewNoteVerifier(*origin, sv)
	if err != nil {
		panic(err)
	}
	// ...and one for a witness cosignature
	skey, vkey, err := note.GenerateKey(rand.Reader, "witness.example/w1")
	if err != nil {
		panic(err)
	}
	coSigner, err := f_note.NewSignerForCosignatureV1(skey)
	if err != nil {
		panic(err)
	}
	coVerifier, err := f_note.NewVerifierForCosignatureV1(vkey)
	if err != nil {
		panic(err)
	}
	skey2, vkey2, err := note.GenerateKey(rand.Reader, "witness.example/w2")
	if err != nil {
		panic(err)
	}
	coSigner2, err := f_note.NewSignerForCosignatureV1(skey2)
	if err != nil {
		panic(err)
	}
	coVerifier2, err := f_note.NewVerifierForCosignatureV1(vkey2)
	if err != nil {
		panic(err)
	}

	// Sign checkpoint with additional signers
	signedNote, err := note.Sign(n, addNoteSigner, coSigner, coSigner2)
	if err != nil {
		panic(err)
	}

	// Verify note with all verifiers
	verifiedNote, err := note.Open(signedNote, note.VerifierList(nv, aaNoteVerifier, coVerifier, coVerifier2))
	if err != nil {
		panic(err)
	}
	if len(verifiedNote.Sigs) != 4 {
		panic("expected 4 signatures")
	}

	fmt.Println(string(signedNote))
}

@jku

jku commented Aug 20, 2025

Copy link
Copy Markdown
Member

sigstore-go (current main branch): 100% pass with this conformance

I'll work on the python fix, you have two options for this PR:

  • either wait for the python fix to be merged, then update the hash in selftest-requirements.txt
  • or add a xfails in .github/workflows/conformance.yml for now (this ones a bit annoying since you will need multiple xfails for this)

@loosebazooka

Copy link
Copy Markdown
Member

java passes.

All the missing fails are with Checkpoint header must contain at least 3 lines which is fine, should we follow up with the data being bad (vs missing)?

@Hayden-IO

Copy link
Copy Markdown
Collaborator Author

sigstore-go (current main branch): 100% pass with this conformance

Thank you for testing this!

either wait for the python fix to be merged, then update the hash in selftest-requirements.txt

Happy to wait.

All the missing fails are with Checkpoint header must contain at least 3 lines which is fine, should we follow up with the data being bad (vs missing)?

A more precise error would be nice but definitely not a high priority.

This brings in the fix for checkpoint signature lookup

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>

@jku jku left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. I updated the selftest commit hash so selftest should pass now.

@Hayden-IO Hayden-IO merged commit 2436121 into sigstore:main Aug 21, 2025
5 checks passed
@Hayden-IO Hayden-IO deleted the checkpoint-tests branch August 21, 2025 16:09
@Hayden-IO

Copy link
Copy Markdown
Collaborator Author

Thanks all!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tests for verifying a C2SP-compliant checkpoint

3 participants