cli/pkg/cmd/attestation/verification/sigstore.go
2024-04-26 17:20:26 +02:00

208 lines
6.7 KiB
Go

package verification
import (
"fmt"
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
"github.com/cli/cli/v2/pkg/cmd/attestation/io"
"github.com/sigstore/sigstore-go/pkg/bundle"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/tuf"
"github.com/sigstore/sigstore-go/pkg/verify"
)
const (
PublicGoodIssuerOrg = "sigstore.dev"
GitHubIssuerOrg = "GitHub, Inc."
)
// AttestationProcessingResult captures processing a given attestation's signature verification and policy evaluation
type AttestationProcessingResult struct {
Attestation *api.Attestation `json:"attestation"`
VerificationResult *verify.VerificationResult `json:"verificationResult"`
}
type SigstoreResults struct {
VerifyResults []*AttestationProcessingResult
Error error
}
type SigstoreConfig struct {
CustomTrustedRoot string
Logger *io.Handler
NoPublicGood bool
}
type SigstoreVerifier interface {
Verify(attestations []*api.Attestation, policy verify.PolicyBuilder) *SigstoreResults
}
type LiveSigstoreVerifier struct {
config SigstoreConfig
}
// NewLiveSigstoreVerifier creates a new LiveSigstoreVerifier struct
// that is used to verify artifacts and attestations against the
// Public Good, GitHub, or a custom trusted root.
func NewLiveSigstoreVerifier(config SigstoreConfig) *LiveSigstoreVerifier {
return &LiveSigstoreVerifier{
config: config,
}
}
func (v *LiveSigstoreVerifier) chooseVerifier(b *bundle.ProtobufBundle) (*verify.SignedEntityVerifier, string, error) {
verifyContent, err := b.VerificationContent()
if err != nil {
return nil, "", fmt.Errorf("failed to get bundle verification content: %v", err)
}
leafCert, ok := verifyContent.HasCertificate()
if !ok {
return nil, "", fmt.Errorf("leaf cert not found")
}
if len(leafCert.Issuer.Organization) != 1 {
return nil, "", fmt.Errorf("expected the leaf certificate issuer to only have one organization")
}
issuer := leafCert.Issuer.Organization[0]
// if user provided a custom trusted root file path, use the custom verifier
if v.config.CustomTrustedRoot != "" {
customVerifier, err := newCustomVerifier(v.config.CustomTrustedRoot)
if err != nil {
return nil, "", fmt.Errorf("failed to create custom verifier: %v", err)
}
return customVerifier, issuer, nil
}
if leafCert.Issuer.Organization[0] == PublicGoodIssuerOrg && !v.config.NoPublicGood {
publicGoodVerifier, err := newPublicGoodVerifier()
if err != nil {
return nil, "", fmt.Errorf("failed to create Public Good Sigstore verifier: %v", err)
}
return publicGoodVerifier, issuer, nil
} else if leafCert.Issuer.Organization[0] == GitHubIssuerOrg || v.config.NoPublicGood {
ghVerifier, err := newGitHubVerifier()
if err != nil {
return nil, "", fmt.Errorf("failed to create GitHub Sigstore verifier: %v", err)
}
return ghVerifier, issuer, nil
}
return nil, "", fmt.Errorf("leaf certificate issuer is not recognized")
}
func (v *LiveSigstoreVerifier) Verify(attestations []*api.Attestation, policy verify.PolicyBuilder) *SigstoreResults {
// initialize the processing results before attempting to verify
// with multiple verifiers
results := make([]*AttestationProcessingResult, len(attestations))
for i, att := range attestations {
apr := &AttestationProcessingResult{
Attestation: att,
}
results[i] = apr
}
totalAttestations := len(attestations)
for i, apr := range results {
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 &SigstoreResults{
Error: 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
if err != nil {
v.config.Logger.VerbosePrint(v.config.Logger.ColorScheme.Redf(
"Failed to verify against issuer \"%s\" \n\n", issuer,
))
return &SigstoreResults{
Error: fmt.Errorf("verifying with issuer \"%s\": %v", issuer, err),
}
}
// 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
}
return &SigstoreResults{
VerifyResults: results,
}
}
func newCustomVerifier(trustedRootFilePath string) (*verify.SignedEntityVerifier, error) {
trustedRoot, err := root.NewTrustedRootFromPath(trustedRootFilePath)
if err != nil {
return nil, fmt.Errorf("failed to create trusted root from file %s: %v", trustedRootFilePath, err)
}
verifierConfig := []verify.VerifierOption{}
verifierConfig = append(verifierConfig, verify.WithSignedCertificateTimestamps(1))
verifierConfig = append(verifierConfig, verify.WithObserverTimestamps(1))
// Infer verification options from contents of trusted root
if len(trustedRoot.TimestampingAuthorities()) > 0 {
verifierConfig = append(verifierConfig, verify.WithSignedTimestamps(1))
}
if len(trustedRoot.RekorLogs()) > 0 {
verifierConfig = append(verifierConfig, verify.WithTransparencyLog(1))
}
gv, err := verify.NewSignedEntityVerifier(trustedRoot, verifierConfig...)
if err != nil {
return nil, fmt.Errorf("failed to create custom verifier: %v", err)
}
return gv, nil
}
func newGitHubVerifier() (*verify.SignedEntityVerifier, error) {
opts := GitHubTUFOptions()
client, err := tuf.New(opts)
if err != nil {
return nil, fmt.Errorf("failed to create TUF client: %v", err)
}
trustedRoot, err := root.GetTrustedRoot(client)
if err != nil {
return nil, err
}
gv, err := verify.NewSignedEntityVerifier(trustedRoot, verify.WithSignedTimestamps(1))
if err != nil {
return nil, fmt.Errorf("failed to create GitHub verifier: %v", err)
}
return gv, nil
}
func newPublicGoodVerifier() (*verify.SignedEntityVerifier, error) {
opts := DefaultOptionsWithCacheSetting()
client, err := tuf.New(opts)
if err != nil {
return nil, fmt.Errorf("failed to create TUF client: %v", err)
}
trustedRoot, err := root.GetTrustedRoot(client)
if err != nil {
return nil, fmt.Errorf("failed to get trusted root: %v", err)
}
sv, err := verify.NewSignedEntityVerifier(trustedRoot, verify.WithSignedCertificateTimestamps(1), verify.WithTransparencyLog(1), verify.WithObserverTimestamps(1))
if err != nil {
return nil, fmt.Errorf("failed to create Public Good verifier: %v", err)
}
return sv, nil
}