Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create NoteVerifier, verification library functions (v2) #119

Merged
merged 10 commits into from
Mar 25, 2025
87 changes: 73 additions & 14 deletions pkg/note/note.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
Expand Down Expand Up @@ -67,6 +68,24 @@
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

Check warning on line 78 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L77-L78

Added lines #L77 - L78 were not covered by tests
}

func (n *noteVerifier) KeyHash() uint32 {
return n.hash

Check warning on line 82 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L81-L82

Added lines #L81 - L82 were not covered by tests
}

func (n *noteVerifier) Verify(msg, sig []byte) bool {
return n.verify(msg, sig)

Check warning on line 86 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L85-L86

Added lines #L85 - L86 were not covered by tests
}

// 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 {
Expand Down Expand Up @@ -112,32 +131,45 @@
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 &noteSigner{}, fmt.Errorf("invalid name %s", origin)
}

pubKey, err := signer.PublicKey()
if err != nil {
return &noteSigner{}, 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)
Copy link
Member Author

Choose a reason for hiding this comment

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

unlike other keytypes, ecdsaKeyHash does not hash "origin". I don't have the context to understand if this is an issue or no

Copy link
Contributor

Choose a reason for hiding this comment

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

if err != nil {
return &noteSigner{}, fmt.Errorf("getting ECDSA key hash: %w", err)
return 0, fmt.Errorf("getting ECDSA key hash: %w", err)

Check warning on line 143 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L143

Added line #L143 was not covered by tests
}
case ed25519.PublicKey:
keyID = ed25519KeyHash(origin, pk)
case *rsa.PublicKey:
keyID, err = rsaKeyHash(origin, pk)
if err != nil {
return &noteSigner{}, fmt.Errorf("getting RSA key hash: %w", err)
return 0, fmt.Errorf("getting RSA key hash: %w", err)

Check warning on line 150 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L150

Added line #L150 was not covered by tests
}
default:
return &noteSigner{}, fmt.Errorf("unsupported key type: %T", pubKey)
return 0, fmt.Errorf("unsupported key type: %T", key)

Check warning on line 153 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L153

Added line #L153 was not covered by tests
}

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)
}

Check warning on line 163 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L162-L163

Added lines #L162 - L163 were not covered by tests

pubKey, err := signer.PublicKey()
if err != nil {
return nil, fmt.Errorf("getting public key: %w", err)
}

Check warning on line 168 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L167-L168

Added lines #L167 - L168 were not covered by tests

keyID, err := keyHash(origin, pubKey)
if err != nil {
return nil, err

Check warning on line 172 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L172

Added line #L172 was not covered by tests
}

sign := func(msg []byte) ([]byte, error) {
Expand All @@ -150,3 +182,30 @@
sign: sign,
}, nil
}

func NewNoteVerifier(origin string, verifier signature.Verifier) (note.Verifier, error) {
if !isValidName(origin) {
return nil, fmt.Errorf("invalid name %s", origin)
}

Check warning on line 189 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L186-L189

Added lines #L186 - L189 were not covered by tests

pubKey, err := verifier.PublicKey()
if err != nil {
return nil, fmt.Errorf("getting public key: %w", err)
}

Check warning on line 194 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L191-L194

Added lines #L191 - L194 were not covered by tests

keyID, err := keyHash(origin, pubKey)
if err != nil {
return nil, err
}

Check warning on line 199 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L196-L199

Added lines #L196 - L199 were not covered by tests

return &noteVerifier{
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

Check warning on line 208 in pkg/note/note.go

View check run for this annotation

Codecov / codecov/patch

pkg/note/note.go#L201-L208

Added lines #L201 - L208 were not covered by tests
},
}, nil
}
59 changes: 59 additions & 0 deletions pkg/verify/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// 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 (
"fmt"

pbs "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1"
"github.com/sigstore/rekor-tiles/pkg/tessera"
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
func VerifyInclusionProof(entry *pbs.TransparencyLogEntry, cp *f_log.Checkpoint) error { //nolint: revive
leafHash := rfc6962.DefaultHasher.HashLeaf(entry.CanonicalizedBody)
index, err := tessera.NewSafeInt64(entry.LogIndex)
if err != nil {
return fmt.Errorf("invalid index: %w", err)
}

Check warning on line 35 in pkg/verify/verify.go

View check run for this annotation

Codecov / codecov/patch

pkg/verify/verify.go#L34-L35

Added lines #L34 - L35 were not covered by tests
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
}

// VerifyCheckpoint verifies the signature on the entry's inclusion proof checkpoint
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
// inclusion proof checkpoint and verifying the entry inclusion proof
func VerifyLogEntry(entry *pbs.TransparencyLogEntry, verifier sumdb_note.Verifier) error { //nolint: revive
cp, err := VerifyCheckpoint(entry, verifier)
if err != nil {
return err
}

Check warning on line 57 in pkg/verify/verify.go

View check run for this annotation

Codecov / codecov/patch

pkg/verify/verify.go#L56-L57

Added lines #L56 - L57 were not covered by tests
return VerifyInclusionProof(entry, cp)
}
Loading