From 9373b9782c8d74c3823334165a42ed108caab371 Mon Sep 17 00:00:00 2001 From: Mikhail Swift Date: Fri, 3 Nov 2023 15:36:37 -0400 Subject: [PATCH] wip: demo hacking --- attestation/policyverify/policyverify.go | 92 +++++++++++++++++++++++- cryptoutil/digestset.go | 14 ++-- cryptoutil/gitoid.go | 2 +- 3 files changed, 99 insertions(+), 9 deletions(-) diff --git a/attestation/policyverify/policyverify.go b/attestation/policyverify/policyverify.go index dedd8e20..77eefcfb 100644 --- a/attestation/policyverify/policyverify.go +++ b/attestation/policyverify/policyverify.go @@ -19,11 +19,13 @@ import ( "crypto/x509" "encoding/json" "fmt" + "strings" "time" "github.com/testifysec/go-witness/attestation" "github.com/testifysec/go-witness/cryptoutil" "github.com/testifysec/go-witness/dsse" + "github.com/testifysec/go-witness/intoto" "github.com/testifysec/go-witness/log" "github.com/testifysec/go-witness/policy" "github.com/testifysec/go-witness/slsa" @@ -48,6 +50,7 @@ type Attestor struct { policyVerifiers []cryptoutil.Verifier collectionSource source.Sourcer subjectDigests []string + addtlSubjects map[string]cryptoutil.DigestSet } type Option func(*Attestor) @@ -81,7 +84,7 @@ func VerifyWithCollectionSource(source source.Sourcer) Option { } func New(opts ...Option) *Attestor { - a := &Attestor{} + a := &Attestor{addtlSubjects: make(map[string]cryptoutil.DigestSet)} for _, opt := range opts { opt(a) } @@ -110,6 +113,10 @@ func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet { } subjects[fmt.Sprintf("policy:%v", a.VerificationSummary.Policy.URI)] = a.VerificationSummary.Policy.Digest + for name, ds := range a.addtlSubjects { + subjects[name] = ds + } + return subjects } @@ -178,9 +185,92 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error { return fmt.Errorf("failed to generate verification summary: %w", err) } + a.findRelevantSubjects(policyResult.EvidenceByStep) return nil } +// findRelevantSubjects will find any image tags in attestations that passed our policy check. +// we do this by searching the subject digests the user tested the policy against, and if we find +// a collection with OCI subjects for an imageid that matches one of the provided subject digests, +// we grab the imagetag subjects off of that attestation. +// todo: we need a better solution for this +func (a *Attestor) findRelevantSubjects(evidenceByStep map[string][]source.VerifiedCollection) { + const imageTagSubjectPrefix = "https://witness.dev/attestations/oci/v0.1/imagetag:" + const imageIdSubjectPrefix = "https://witness.dev/attestations/oci/v0.1/imageid:" + const githubProjectPrefix = "https://witness.dev/attestations/github/v0.1/projecturl:" + const gitlabProjectPrefix = "https://witness.dev/attestations/gitlab/v0.1/projecturl:" + + for _, collections := range evidenceByStep { + for _, collection := range collections { + candidates := make([]intoto.Subject, 0) + matchedSubject := false + + // search through every subject on the in-toto statment. if we find imagetags, we set them aside as possible candidates. + // if we find an imageid subject that matches, we consider all the candidates to be matching subjects and return them + for _, subject := range collection.Statement.Subject { + // if we find an image tag subject, add it to the list of candidates + if strings.HasPrefix(subject.Name, imageTagSubjectPrefix) { + candidates = append(candidates, intoto.Subject{ + Name: fmt.Sprintf("imagetag:%v", strings.TrimPrefix(subject.Name, imageTagSubjectPrefix)), + Digest: subject.Digest, + }) + } + + if strings.HasPrefix(subject.Name, githubProjectPrefix) { + candidates = append(candidates, intoto.Subject{ + Name: fmt.Sprintf("projecturl:%v", strings.TrimPrefix(subject.Name, githubProjectPrefix)), + Digest: subject.Digest, + }) + } + + if strings.HasPrefix(subject.Name, gitlabProjectPrefix) { + candidates = append(candidates, intoto.Subject{ + Name: fmt.Sprintf("projecturl:%v", strings.TrimPrefix(subject.Name, gitlabProjectPrefix)), + Digest: subject.Digest, + }) + } + + // if we find an imageid subject, check to see if any the digests we verified match the imageid + if strings.HasPrefix(subject.Name, imageIdSubjectPrefix) { + for _, imageIdDigest := range subject.Digest { + for _, testImageIdDigest := range a.subjectDigests { + if imageIdDigest == testImageIdDigest { + matchedSubject = true + candidates = append(candidates, intoto.Subject{ + Name: fmt.Sprintf("imageid:%v", testImageIdDigest), + Digest: subject.Digest, + }) + } + } + + // if we found a matching imageid subject with one of our test subject digests, stop looking + if matchedSubject { + break + } + } + } + } + + // after we've checked all the subjects, if we found a match, add our candidates to our additional subjects + if matchedSubject { + for _, candidate := range candidates { + ds := cryptoutil.DigestSet{} + for hash, value := range candidate.Digest { + digestValue, err := cryptoutil.DigestValueFromString(hash) + if err != nil { + continue + } + + ds[digestValue] = value + } + + a.addtlSubjects[candidate.Name] = ds + } + } + } + } +} + func verificationSummaryFromResults(ctx *attestation.AttestationContext, policyEnvelope dsse.Envelope, policyResult policy.PolicyResult, accepted bool) (slsa.VerificationSummary, error) { inputAttestations := make([]slsa.ResourceDescriptor, 0, len(policyResult.EvidenceByStep)) for _, input := range policyResult.EvidenceByStep { diff --git a/cryptoutil/digestset.go b/cryptoutil/digestset.go index 6de2ea12..1006fd68 100644 --- a/cryptoutil/digestset.go +++ b/cryptoutil/digestset.go @@ -85,20 +85,20 @@ func (dv DigestValue) New() hash.Hash { type DigestSet map[DigestValue]string -func HashToString(h crypto.Hash) (string, error) { - if name, ok := hashNames[DigestValue{Hash: h}]; ok { +func DigestValueToString(dv DigestValue) (string, error) { + if name, ok := hashNames[dv]; ok { return name, nil } - return "", ErrUnsupportedHash(h.String()) + return "", ErrUnsupportedHash(dv.Hash.String()) } -func HashFromString(name string) (crypto.Hash, error) { - if hash, ok := hashesByName[name]; ok { - return hash.Hash, nil +func DigestValueFromString(name string) (DigestValue, error) { + if dv, ok := hashesByName[name]; ok { + return dv, nil } - return crypto.Hash(0), ErrUnsupportedHash(name) + return DigestValue{}, ErrUnsupportedHash(name) } // Equal returns true if every digest for hash functions both artifacts have in common are equal. diff --git a/cryptoutil/gitoid.go b/cryptoutil/gitoid.go index f3fe365c..a6a26816 100644 --- a/cryptoutil/gitoid.go +++ b/cryptoutil/gitoid.go @@ -61,7 +61,7 @@ func (gh *gitoidHasher) Reset() { // Size returns the number of bytes Sum will return. func (gh *gitoidHasher) Size() int { - hashName, err := HashToString(gh.hash) + hashName, err := DigestValueToString(DigestValue{Hash: gh.hash}) if err != nil { return 0 }