Skip to content

Commit

Permalink
Improve Verify Error Responses (#210)
Browse files Browse the repository at this point in the history
Improved messaging when performing a policy verification.

---------

Signed-off-by: chaosinthecrd <[email protected]>
Signed-off-by: Tom Meadows <[email protected]>
  • Loading branch information
ChaosInTheCRD authored May 8, 2024
1 parent 064637b commit 90e0da2
Show file tree
Hide file tree
Showing 13 changed files with 463 additions and 137 deletions.
21 changes: 19 additions & 2 deletions dsse/dsse.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package dsse

import (
"fmt"

"github.com/in-toto/go-witness/log"
)

type ErrNoSignatures struct{}
Expand All @@ -24,10 +26,25 @@ func (e ErrNoSignatures) Error() string {
return "no signatures in dsse envelope"
}

type ErrNoMatchingSigs struct{}
type ErrNoMatchingSigs struct {
Verifiers []CheckedVerifier
}

func (e ErrNoMatchingSigs) Error() string {
return "no valid signatures for the provided verifiers found"
mess := "no valid signatures for the provided verifiers found for keyids:\n"
for _, v := range e.Verifiers {
if v.Error != nil {
kid, err := v.Verifier.KeyID()
if err != nil {
log.Warn("failed to get key id from verifier: %v", err)
}

s := fmt.Sprintf(" %s: %v\n", kid, v.Error)
mess += s
}
}

return mess
}

type ErrThresholdNotMet struct {
Expand Down
51 changes: 39 additions & 12 deletions dsse/dsse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func TestVerify(t *testing.T) {
env, err := Sign("dummydata", bytes.NewReader([]byte("this is some dummy data")), SignWithSigners(signer))
require.NoError(t, err)
approvedVerifiers, err := env.Verify(VerifyWithVerifiers(verifier))
assert.ElementsMatch(t, approvedVerifiers, []PassedVerifier{{Verifier: verifier}})
assert.ElementsMatch(t, approvedVerifiers, []CheckedVerifier{{Verifier: verifier}})
require.NoError(t, err)
}

Expand All @@ -165,38 +165,44 @@ func TestFailVerify(t *testing.T) {
require.NoError(t, err)
approvedVerifiers, err := env.Verify(VerifyWithVerifiers(verifier))
assert.Empty(t, approvedVerifiers)
require.ErrorIs(t, err, ErrNoMatchingSigs{})
require.ErrorAs(t, err, &ErrNoMatchingSigs{Verifiers: []CheckedVerifier{{Verifier: verifier, Error: rsa.ErrVerification}}})
}

func TestMultiSigners(t *testing.T) {
signers := []cryptoutil.Signer{}
verifiers := []cryptoutil.Verifier{}
expectedVerifiers := []PassedVerifier{}
expectedVerifiers := []CheckedVerifier{}
for i := 0; i < 5; i++ {
s, v, err := createTestKey()
require.NoError(t, err)
signers = append(signers, s)
verifiers = append(verifiers, v)
expectedVerifiers = append(expectedVerifiers, PassedVerifier{Verifier: v})
expectedVerifiers = append(expectedVerifiers, CheckedVerifier{Verifier: v})
}

env, err := Sign("dummydata", bytes.NewReader([]byte("this is some dummy data")), SignWithSigners(signers...))
require.NoError(t, err)

approvedVerifiers, err := env.Verify(VerifyWithVerifiers(verifiers...))
checkedVerifiers, err := env.Verify(VerifyWithVerifiers(verifiers...))
approvedVerifiers := []CheckedVerifier{}
for _, v := range checkedVerifiers {
if v.Error == nil {
approvedVerifiers = append(approvedVerifiers, v)
}
}
require.NoError(t, err)
assert.ElementsMatch(t, approvedVerifiers, expectedVerifiers)
}

func TestThreshold(t *testing.T) {
signers := []cryptoutil.Signer{}
expectedVerifiers := []PassedVerifier{}
expectedVerifiers := []CheckedVerifier{}
verifiers := []cryptoutil.Verifier{}
for i := 0; i < 5; i++ {
s, v, err := createTestKey()
require.NoError(t, err)
signers = append(signers, s)
expectedVerifiers = append(expectedVerifiers, PassedVerifier{Verifier: v})
expectedVerifiers = append(expectedVerifiers, CheckedVerifier{Verifier: v})
verifiers = append(verifiers, v)
}

Expand All @@ -210,12 +216,26 @@ func TestThreshold(t *testing.T) {
env, err := Sign("dummydata", bytes.NewReader([]byte("this is some dummy data")), SignWithSigners(signers...))
require.NoError(t, err)

approvedVerifiers, err := env.Verify(VerifyWithVerifiers(verifiers...), VerifyWithThreshold(5))
checkedVerifiers, err := env.Verify(VerifyWithVerifiers(verifiers...), VerifyWithThreshold(5))
require.NoError(t, err)

approvedVerifiers := []CheckedVerifier{}
for _, v := range checkedVerifiers {
if v.Error == nil {
approvedVerifiers = append(approvedVerifiers, v)
}
}
assert.ElementsMatch(t, approvedVerifiers, expectedVerifiers)

approvedVerifiers, err = env.Verify(VerifyWithVerifiers(verifiers...), VerifyWithThreshold(10))
checkedVerifiers, err = env.Verify(VerifyWithVerifiers(verifiers...), VerifyWithThreshold(10))
require.ErrorIs(t, err, ErrThresholdNotMet{Actual: 5, Theshold: 10})

approvedVerifiers = []CheckedVerifier{}
for _, v := range checkedVerifiers {
if v.Error == nil {
approvedVerifiers = append(approvedVerifiers, v)
}
}
assert.ElementsMatch(t, approvedVerifiers, expectedVerifiers)

_, err = env.Verify(VerifyWithVerifiers(verifiers...), VerifyWithThreshold(-10))
Expand Down Expand Up @@ -257,9 +277,16 @@ func TestTimestamp(t *testing.T) {
env, err := Sign("dummydata", bytes.NewReader([]byte("this is some dummy data")), SignWithSigners(s), SignWithTimestampers(allTimestampers...))
require.NoError(t, err)

approvedVerifiers, err := env.Verify(VerifyWithVerifiers(v), VerifyWithRoots(root), VerifyWithIntermediates(intermediate), VerifyWithTimestampVerifiers(allTimestampVerifiers...))
checkedVerifiers, err := env.Verify(VerifyWithVerifiers(v), VerifyWithRoots(root), VerifyWithIntermediates(intermediate), VerifyWithTimestampVerifiers(allTimestampVerifiers...))
require.NoError(t, err)

approvedVerifiers := []CheckedVerifier{}
for _, v := range checkedVerifiers {
if v.Error == nil {
approvedVerifiers = append(approvedVerifiers, v)
}
}
assert.Len(t, approvedVerifiers, 1)
assert.Len(t, approvedVerifiers[0].PassedTimestampVerifiers, len(expectedTimestampers))
assert.ElementsMatch(t, approvedVerifiers[0].PassedTimestampVerifiers, expectedTimestampers)
assert.Len(t, approvedVerifiers[0].TimestampVerifiers, len(expectedTimestampers))
assert.ElementsMatch(t, approvedVerifiers[0].TimestampVerifiers, expectedTimestampers)
}
66 changes: 45 additions & 21 deletions dsse/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"bytes"
"context"
"crypto/x509"
"fmt"
"time"

"github.com/in-toto/go-witness/cryptoutil"
Expand Down Expand Up @@ -65,12 +66,15 @@ func VerifyWithTimestampVerifiers(verifiers ...timestamp.TimestampVerifier) Veri
}
}

type PassedVerifier struct {
Verifier cryptoutil.Verifier
PassedTimestampVerifiers []timestamp.TimestampVerifier
type CheckedVerifier struct {
Verifier cryptoutil.Verifier
TimestampVerifiers []timestamp.TimestampVerifier
Error error
}

func (e Envelope) Verify(opts ...VerificationOption) ([]PassedVerifier, error) {
type FailedVerifier struct{}

func (e Envelope) Verify(opts ...VerificationOption) ([]CheckedVerifier, error) {
options := &verificationOptions{
threshold: 1,
}
Expand All @@ -88,8 +92,8 @@ func (e Envelope) Verify(opts ...VerificationOption) ([]PassedVerifier, error) {
return nil, ErrNoSignatures{}
}

matchingSigFound := false
passedVerifiers := make([]PassedVerifier, 0)
checkedVerifiers := make([]CheckedVerifier, 0)
verified := 0
for _, sig := range e.Signatures {
if sig.Certificate != nil && len(sig.Certificate) > 0 {
cert, err := cryptoutil.TryParseCertificate(sig.Certificate)
Expand All @@ -110,14 +114,17 @@ func (e Envelope) Verify(opts ...VerificationOption) ([]PassedVerifier, error) {
sigIntermediates = append(sigIntermediates, options.intermediates...)
if len(options.timestampVerifiers) == 0 {
if verifier, err := verifyX509Time(cert, sigIntermediates, options.roots, pae, sig.Signature, time.Now()); err == nil {
matchingSigFound = true
passedVerifiers = append(passedVerifiers, PassedVerifier{Verifier: verifier})
checkedVerifiers = append(checkedVerifiers, CheckedVerifier{Verifier: verifier})
verified += 1
} else {
checkedVerifiers = append(checkedVerifiers, CheckedVerifier{Verifier: verifier, Error: err})
log.Debugf("failed to verify with timestamp verifier: %w", err)
}
} else {
var passedVerifier cryptoutil.Verifier
failed := []cryptoutil.Verifier{}
passedTimestampVerifiers := []timestamp.TimestampVerifier{}
failedTimestampVerifiers := []timestamp.TimestampVerifier{}

for _, timestampVerifier := range options.timestampVerifiers {
for _, sigTimestamp := range sig.Timestamps {
Expand All @@ -127,44 +134,61 @@ func (e Envelope) Verify(opts ...VerificationOption) ([]PassedVerifier, error) {
}

if verifier, err := verifyX509Time(cert, sigIntermediates, options.roots, pae, sig.Signature, timestamp); err == nil {
// NOTE: do we not want to save all the passed verifiers?
passedVerifier = verifier
passedTimestampVerifiers = append(passedTimestampVerifiers, timestampVerifier)
} else {
failed = append(failed, verifier)
failedTimestampVerifiers = append(failedTimestampVerifiers, timestampVerifier)
log.Debugf("failed to verify with timestamp verifier: %w", err)
}

}
}

if len(passedTimestampVerifiers) > 0 {
matchingSigFound = true
passedVerifiers = append(passedVerifiers, PassedVerifier{
Verifier: passedVerifier,
PassedTimestampVerifiers: passedTimestampVerifiers,
verified += 1
checkedVerifiers = append(checkedVerifiers, CheckedVerifier{
Verifier: passedVerifier,
TimestampVerifiers: passedTimestampVerifiers,
})
} else {
for _, v := range failed {
checkedVerifiers = append(checkedVerifiers, CheckedVerifier{
Verifier: v,
TimestampVerifiers: failedTimestampVerifiers,
Error: fmt.Errorf("no valid timestamps found"),
})
}
}
}
}

for _, verifier := range options.verifiers {
if verifier != nil {
kid, err := verifier.KeyID()
if err != nil {
log.Warn("failed to get key id from verifier: %v", err)
}
log.Debug("verifying with verifier with KeyID ", kid)

if err := verifier.Verify(bytes.NewReader(pae), sig.Signature); err == nil {
passedVerifiers = append(passedVerifiers, PassedVerifier{Verifier: verifier})
matchingSigFound = true
verified += 1
checkedVerifiers = append(checkedVerifiers, CheckedVerifier{Verifier: verifier})
} else {
checkedVerifiers = append(checkedVerifiers, CheckedVerifier{Verifier: verifier, Error: err})
}
}
}
}

if !matchingSigFound {
return nil, ErrNoMatchingSigs{}
}

if len(passedVerifiers) < options.threshold {
return passedVerifiers, ErrThresholdNotMet{Theshold: options.threshold, Actual: len(passedVerifiers)}
if verified == 0 {
return nil, ErrNoMatchingSigs{Verifiers: checkedVerifiers}
} else if verified < options.threshold {
return checkedVerifiers, ErrThresholdNotMet{Theshold: options.threshold, Actual: verified}
}

return passedVerifiers, nil
return checkedVerifiers, nil
}

func verifyX509Time(cert *x509.Certificate, sigIntermediates, roots []*x509.Certificate, pae, sig []byte, trustedTime time.Time) (cryptoutil.Verifier, error) {
Expand Down
25 changes: 21 additions & 4 deletions policy/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,27 @@ import (
"github.com/in-toto/go-witness/cryptoutil"
)

type ErrNoAttestations string
type ErrVerifyArtifactsFailed struct {
Reasons []string
}

func (e ErrVerifyArtifactsFailed) Error() string {
mess := "failed to verify artifacts: \n"
for i, r := range e.Reasons {
if i == len(e.Reasons)-1 {
mess += r + "\n"
}
mess += r + ", \n"
}
return fmt.Sprintf("failed to verify artifacts: %v", e.Reasons)
}

type ErrNoCollections struct {
Step string
}

func (e ErrNoAttestations) Error() string {
return fmt.Sprintf("no attestations found for step %v", string(e))
func (e ErrNoCollections) Error() string {
return fmt.Sprintf("no collections found for step %v", e.Step)
}

type ErrMissingAttestation struct {
Expand Down Expand Up @@ -89,7 +106,7 @@ type ErrPolicyDenied struct {
}

func (e ErrPolicyDenied) Error() string {
return fmt.Sprintf("policy was denied due to:\n%v", strings.Join(e.Reasons, "\n -"))
return fmt.Sprintf("policy was denied due to: %v", strings.Join(e.Reasons, ", "))
}

type ErrConstraintCheckFailed struct {
Expand Down
Loading

0 comments on commit 90e0da2

Please sign in to comment.