package sbom import ( "bytes" "context" "encoding/json" "errors" "github.com/in-toto/in-toto-golang/in_toto" "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/attestation" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/rekor" ) var ErrNoSBOMAttestation = xerrors.New("no SBOM attestation found") type Rekor struct { client *rekor.Client } func NewRekor(url string) (Rekor, error) { c, err := rekor.NewClient(url) if err != nil { return Rekor{}, xerrors.Errorf("rekor client error: %w", err) } return Rekor{ client: c, }, nil } func (r *Rekor) RetrieveSBOM(ctx context.Context, digest string) ([]byte, error) { entryIDs, err := r.client.Search(ctx, digest) if err != nil { return nil, xerrors.Errorf("failed to search rekor records: %w", err) } else if len(entryIDs) == 0 { return nil, ErrNoSBOMAttestation } log.Debug("Found matching Rekor entries", log.Any("entry_ids", entryIDs)) for _, ids := range lo.Chunk[rekor.EntryID](entryIDs, rekor.MaxGetEntriesLimit) { entries, err := r.client.GetEntries(ctx, ids) if err != nil { return nil, xerrors.Errorf("failed to get entries: %w", err) } for _, entry := range entries { ref, err := r.inspectRecord(entry) if errors.Is(err, ErrNoSBOMAttestation) { continue } else if err != nil { return nil, xerrors.Errorf("rekor record inspection error: %w", err) } return ref, nil } } return nil, ErrNoSBOMAttestation } func (r *Rekor) inspectRecord(entry rekor.Entry) ([]byte, error) { // TODO: Trivy SBOM should take precedence raw, err := r.parseStatement(entry) if err != nil { return nil, err } return raw, nil } func (r *Rekor) parseStatement(entry rekor.Entry) (json.RawMessage, error) { // Skip base64-encoded attestation if bytes.HasPrefix(entry.Statement, []byte(`eyJ`)) { return nil, ErrNoSBOMAttestation } // Parse statement of in-toto attestation var raw json.RawMessage statement := &in_toto.Statement{ Predicate: &attestation.CosignPredicate{ Data: &raw, // Extract CycloneDX or SPDX }, } if err := json.Unmarshal(entry.Statement, &statement); err != nil { return nil, xerrors.Errorf("attestation parse error: %w", err) } // TODO: add support for SPDX if statement.PredicateType != in_toto.PredicateCycloneDX { return nil, xerrors.Errorf("unsupported predicate type %s: %w", statement.PredicateType, ErrNoSBOMAttestation) } return raw, nil }