132 lines
4.2 KiB
Go
132 lines
4.2 KiB
Go
package shared
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
|
|
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact"
|
|
att_io "github.com/cli/cli/v2/pkg/cmd/attestation/io"
|
|
"github.com/cli/cli/v2/pkg/cmd/attestation/test/data"
|
|
"github.com/cli/cli/v2/pkg/cmd/attestation/verification"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/sigstore/sigstore-go/pkg/fulcio/certificate"
|
|
"github.com/sigstore/sigstore-go/pkg/verify"
|
|
|
|
v1 "github.com/in-toto/attestation/go/v1"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
)
|
|
|
|
type Verifier interface {
|
|
// VerifyAttestation verifies the attestation for a given artifact
|
|
VerifyAttestation(art *artifact.DigestedArtifact, att *api.Attestation) (*verification.AttestationProcessingResult, error)
|
|
}
|
|
|
|
type AttestationVerifier struct {
|
|
AttClient api.Client
|
|
HttpClient *http.Client
|
|
IO *iostreams.IOStreams
|
|
TrustedRoot string
|
|
}
|
|
|
|
func (v *AttestationVerifier) VerifyAttestation(art *artifact.DigestedArtifact, att *api.Attestation) (*verification.AttestationProcessingResult, error) {
|
|
td, err := v.AttClient.GetTrustDomain()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
verifier, err := verification.NewLiveSigstoreVerifier(verification.SigstoreConfig{
|
|
HttpClient: v.HttpClient,
|
|
Logger: att_io.NewHandler(v.IO),
|
|
NoPublicGood: true,
|
|
TrustDomain: td,
|
|
TrustedRoot: v.TrustedRoot,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
policy := buildVerificationPolicy(*art, td)
|
|
sigstoreVerified, err := verifier.Verify([]*api.Attestation{att}, policy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return sigstoreVerified[0], nil
|
|
}
|
|
|
|
func FilterAttestationsByTag(attestations []*api.Attestation, tagName string) ([]*api.Attestation, error) {
|
|
var filtered []*api.Attestation
|
|
for _, att := range attestations {
|
|
statement := att.Bundle.Bundle.GetDsseEnvelope().Payload
|
|
var statementData v1.Statement
|
|
err := protojson.Unmarshal([]byte(statement), &statementData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal statement: %w", err)
|
|
}
|
|
tagValue := statementData.Predicate.GetFields()["tag"].GetStringValue()
|
|
|
|
if tagValue == tagName {
|
|
filtered = append(filtered, att)
|
|
}
|
|
}
|
|
return filtered, nil
|
|
}
|
|
|
|
func FilterAttestationsByFileDigest(attestations []*api.Attestation, fileDigest string) ([]*api.Attestation, error) {
|
|
var filtered []*api.Attestation
|
|
for _, att := range attestations {
|
|
statement := att.Bundle.Bundle.GetDsseEnvelope().Payload
|
|
var statementData v1.Statement
|
|
err := protojson.Unmarshal([]byte(statement), &statementData)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal statement: %w", err)
|
|
}
|
|
subjects := statementData.Subject
|
|
for _, subject := range subjects {
|
|
digestMap := subject.GetDigest()
|
|
alg := "sha256"
|
|
|
|
digest := digestMap[alg]
|
|
if digest == fileDigest {
|
|
filtered = append(filtered, att)
|
|
}
|
|
}
|
|
|
|
}
|
|
return filtered, nil
|
|
}
|
|
|
|
// buildVerificationPolicy constructs a verification policy for GitHub releases
|
|
func buildVerificationPolicy(a artifact.DigestedArtifact, trustDomain string) verify.PolicyBuilder {
|
|
// If no trust domain is specified, default to "dotcom"
|
|
if trustDomain == "" {
|
|
trustDomain = "dotcom"
|
|
}
|
|
// SAN must match the GitHub releases domain. No issuer extension (match anything)
|
|
sanMatcher, _ := verify.NewSANMatcher("", fmt.Sprintf("^https://%s\\.releases\\.github\\.com$", trustDomain))
|
|
issuerMatcher, _ := verify.NewIssuerMatcher("", ".*")
|
|
certId, _ := verify.NewCertificateIdentity(sanMatcher, issuerMatcher, certificate.Extensions{})
|
|
|
|
artifactDigestPolicyOption, _ := verification.BuildDigestPolicyOption(a)
|
|
return verify.NewPolicy(artifactDigestPolicyOption, verify.WithCertificateIdentity(certId))
|
|
}
|
|
|
|
type MockVerifier struct {
|
|
mockResult *verification.AttestationProcessingResult
|
|
}
|
|
|
|
func NewMockVerifier(mockResult *verification.AttestationProcessingResult) *MockVerifier {
|
|
return &MockVerifier{mockResult: mockResult}
|
|
}
|
|
|
|
func (v *MockVerifier) VerifyAttestation(art *artifact.DigestedArtifact, att *api.Attestation) (*verification.AttestationProcessingResult, error) {
|
|
return &verification.AttestationProcessingResult{
|
|
Attestation: &api.Attestation{
|
|
Bundle: data.GitHubReleaseBundle(nil),
|
|
BundleURL: "https://example.com",
|
|
},
|
|
VerificationResult: nil,
|
|
}, nil
|
|
}
|