Update monotonic verification logic and testing by malancas · Pull Request #9856 · cli/cli · GitHub
Skip to content
19 changes: 10 additions & 9 deletions pkg/cmd/attestation/verification/extensions.go
45 changes: 33 additions & 12 deletions pkg/cmd/attestation/verification/extensions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@ import (
"github.com/stretchr/testify/require"
)

func TestVerifyCertExtensions(t *testing.T) {
results := []*AttestationProcessingResult{
{
VerificationResult: &verify.VerificationResult{
Signature: &verify.SignatureVerificationResult{
Certificate: &certificate.Summary{
Extensions: certificate.Extensions{
SourceRepositoryOwnerURI: "https://github.com/owner",
SourceRepositoryURI: "https://github.com/owner/repo",
Issuer: "https://token.actions.githubusercontent.com",
},
func createSampleResult() *AttestationProcessingResult {
return &AttestationProcessingResult{
VerificationResult: &verify.VerificationResult{
Signature: &verify.SignatureVerificationResult{
Certificate: &certificate.Summary{
Extensions: certificate.Extensions{
SourceRepositoryOwnerURI: "https://github.com/owner",
SourceRepositoryURI: "https://github.com/owner/repo",
Issuer: "https://token.actions.githubusercontent.com",
},
},
},
},
}
}

func TestVerifyCertExtensions(t *testing.T) {
results := []*AttestationProcessingResult{createSampleResult()}

certSummary := certificate.Summary{}
certSummary.SourceRepositoryOwnerURI = "https://github.com/owner"
Expand All @@ -34,11 +36,30 @@ func TestVerifyCertExtensions(t *testing.T) {
Certificate: certSummary,
}

t.Run("success", func(t *testing.T) {
t.Run("passes with one result", func(t *testing.T) {
err := VerifyCertExtensions(results, c)
require.NoError(t, err)
})

t.Run("passes with 1/2 valid results", func(t *testing.T) {
twoResults := []*AttestationProcessingResult{createSampleResult(), createSampleResult()}
require.Len(t, twoResults, 2)
twoResults[1].VerificationResult.Signature.Certificate.Extensions.SourceRepositoryOwnerURI = "https://github.com/wrong"

err := VerifyCertExtensions(twoResults, c)
require.NoError(t, err)
})

t.Run("fails when all results fail verification", func(t *testing.T) {
twoResults := []*AttestationProcessingResult{createSampleResult(), createSampleResult()}
require.Len(t, twoResults, 2)
twoResults[0].VerificationResult.Signature.Certificate.Extensions.SourceRepositoryOwnerURI = "https://github.com/wrong"
twoResults[1].VerificationResult.Signature.Certificate.Extensions.SourceRepositoryOwnerURI = "https://github.com/wrong"

err := VerifyCertExtensions(twoResults, c)
require.Error(t, err)
})

t.Run("with wrong SourceRepositoryOwnerURI", func(t *testing.T) {
expectedCriteria := c
expectedCriteria.Certificate.SourceRepositoryOwnerURI = "https://github.com/wrong"
Expand Down
87 changes: 50 additions & 37 deletions pkg/cmd/attestation/verification/sigstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,55 +167,68 @@ func getLowestCertInChain(ca *root.CertificateAuthority) (*x509.Certificate, err
return nil, fmt.Errorf("certificate authority had no certificates")
}

func (v *LiveSigstoreVerifier) Verify(attestations []*api.Attestation, policy verify.PolicyBuilder) ([]*AttestationProcessingResult, error) {
// initialize the processing apResults before attempting to verify
// with multiple verifiers
apResults := make([]*AttestationProcessingResult, len(attestations))
for i, att := range attestations {
apr := &AttestationProcessingResult{
Attestation: att,
}
apResults[i] = apr
func (v *LiveSigstoreVerifier) verify(attestation *api.Attestation, policy verify.PolicyBuilder) (*AttestationProcessingResult, error) {
// determine which verifier should attempt verification against the bundle
verifier, issuer, err := v.chooseVerifier(attestation.Bundle)
if err != nil {
return nil, fmt.Errorf("failed to find recognized issuer from bundle content: %v", err)
}

v.config.Logger.VerbosePrintf("Attempting verification against issuer \"%s\"\n", issuer)
// attempt to verify the attestation
result, err := verifier.Verify(attestation.Bundle, policy)
// if verification fails, create the error and exit verification early
if err != nil {
v.config.Logger.VerbosePrint(v.config.Logger.ColorScheme.Redf(
"Failed to verify against issuer \"%s\" \n\n", issuer,
))

return nil, fmt.Errorf("verifying with issuer \"%s\"", issuer)
}

var atLeastOneVerified bool
// if verification is successful, add the result
// to the AttestationProcessingResult entry
v.config.Logger.VerbosePrint(v.config.Logger.ColorScheme.Greenf(
"SUCCESS - attestation signature verified with \"%s\"\n", issuer,
))

return &AttestationProcessingResult{
Attestation: attestation,
VerificationResult: result,
}, nil
}

func (v *LiveSigstoreVerifier) Verify(attestations []*api.Attestation, policy verify.PolicyBuilder) ([]*AttestationProcessingResult, error) {
if len(attestations) == 0 {
return nil, ErrNoAttestationsVerified
}

results := make([]*AttestationProcessingResult, len(attestations))
var verifyCount int
var lastError error
totalAttestations := len(attestations)
for i, apr := range apResults {
for i, a := range attestations {
v.config.Logger.VerbosePrintf("Verifying attestation %d/%d against the configured Sigstore trust roots\n", i+1, totalAttestations)

// determine which verifier should attempt verification against the bundle
verifier, issuer, err := v.chooseVerifier(apr.Attestation.Bundle)
if err != nil {
return nil, fmt.Errorf("failed to find recognized issuer from bundle content: %v", err)
}

v.config.Logger.VerbosePrintf("Attempting verification against issuer \"%s\"\n", issuer)
// attempt to verify the attestation
result, err := verifier.Verify(apr.Attestation.Bundle, policy)
// if verification fails, create the error and exit verification early
apr, err := v.verify(a, policy)
if err != nil {
v.config.Logger.VerbosePrint(v.config.Logger.ColorScheme.Redf(
"Failed to verify against issuer \"%s\" \n\n", issuer,
))

return nil, fmt.Errorf("verifying with issuer \"%s\"", issuer)
lastError = err
// move onto the next attestation in the for loop if verification fails
continue
}

// if verification is successful, add the result
// to the AttestationProcessingResult entry
v.config.Logger.VerbosePrint(v.config.Logger.ColorScheme.Greenf(
"SUCCESS - attestation signature verified with \"%s\"\n", issuer,
))
apr.VerificationResult = result
atLeastOneVerified = true
// otherwise, add the result to the results slice and increment verifyCount
results[verifyCount] = apr
verifyCount++
}

if atLeastOneVerified {
return apResults, nil
if verifyCount == 0 {
return nil, lastError
}

return nil, ErrNoAttestationsVerified
// truncate the results slice to only include verified attestations
results = results[:verifyCount]

return results, nil
}

func newCustomVerifier(trustedRoot *root.TrustedRoot) (*verify.SignedEntityVerifier, error) {
Expand Down
31 changes: 31 additions & 0 deletions pkg/cmd/attestation/verification/sigstore_integration_test.go