Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sbom-attestor): add SBOM attestor for SPDX and CycloneDX formats #231

Closed
wants to merge 1 commit into from

Conversation

colek42
Copy link
Member

@colek42 colek42 commented May 4, 2024

This adds a SBOM attestor, and a check for those MIME types. It supports both SPDX and Cyclone DX in json format.

USER: create a postproduct sbom attestor that itterates through the products, and if the document is a valid SPDX or CycloneDX JSON create an attestation.  look how we wrap a sarif document as an example

ASSISTANT: To create a `PostProductSBOMAttestor` that iterates through the products and creates an attestation for valid SPDX or CycloneDX JSON documents, I will create a new file named `sbom.go` in the `attestation` package. This file will contain the implementation of the attestor.

Here is the diff for the new file `attestation/sbom.go`:

```diff
--- /dev/null
+++ attestation/sbom.go
@@ ... @@
+package attestation
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"os"
+	"strings"
+
+	"github.com/in-toto/go-witness/cryptoutil"
+	"github.com/in-toto/go-witness/log"
+)
+
+const (
+	Name    = "sbom"
+	Type    = "https://witness.dev/attestations/sbom/v0.1"
+	RunType = PostProductRunType
+
+	spdxMimeType   = "application/spdx+json"
+	cycloneDxMimeType = "application/vnd.cyclonedx+json"
+)
+
+func init() {
+	RegisterAttestation(Name, Type, RunType, func() Attestor {
+		return NewSBOMAttestor()
+	})
+}
+
+type SBOMAttestor struct {
+	SBOMDocument    interface{}          `json:"sbomDocument"`
+	SBOMFile        string               `json:"sbomFileName"`
+	SBOMDigestSet   cryptoutil.DigestSet `json:"sbomDigestSet"`
+}
+
+func NewSBOMAttestor() *SBOMAttestor {
+	return &SBOMAttestor{}
+}
+
+func (a *SBOMAttestor) Name() string {
+	return Name
+}
+
+func (a *SBOMAttestor) Type() string {
+	return Type
+}
+
+func (a *SBOMAttestor) RunType() RunType {
+	return RunType
+}
+
+func (a *SBOMAttestor) Attest(ctx *AttestationContext) error {
+	if err := a.getCandidate(ctx); err != nil {
+		log.Debugf("(attestation/sbom) error getting candidate: %w", err)
+		return err
+	}
+
+	return nil
+}
+
+func (a *SBOMAttestor) getCandidate(ctx *AttestationContext) error {
+	products := ctx.Products()
+
+	if len(products) == 0 {
+		return fmt.Errorf("no products to attest")
+	}
+
+	for path, product := range products {
+		if product.MimeType != spdxMimeType && product.MimeType != cycloneDxMimeType {
+			continue
+		}
+
+		newDigestSet, err := cryptoutil.CalculateDigestSetFromFile(path, ctx.Hashes())
+		if newDigestSet == nil || err != nil {
+			return fmt.Errorf("error calculating digest set from file: %s", path)
+		}
+
+		if !newDigestSet.Equal(product.Digest) {
+			return fmt.Errorf("integrity error: product digest set does not match candidate digest set")
+		}
+
+		f, err := os.Open(path)
+		if err != nil {
+			return fmt.Errorf("error opening file: %s", path)
+		}
+
+		sbomBytes, err := io.ReadAll(f)
+		if err != nil {
+			return fmt.Errorf("error reading file: %s", path)
+		}
+
+		var sbomDocument interface{}
+		if err := json.Unmarshal(sbomBytes, &sbomDocument); err != nil {
+			log.Debugf("(attestation/sbom) error unmarshaling SBOM: %w", err)
+			continue
+		}
+
+		a.SBOMFile = path
+		a.SBOMDigestSet = product.Digest
+		a.SBOMDocument = sbomDocument
+
+		return nil
+	}
+	return fmt.Errorf("no SBOM file found")
+}
```

This new file defines a `PostProductSBOMAttestor` that checks for SPDX or CycloneDX JSON documents in the products and creates an attestation if a valid document is found. The attestor is registered in the `init` function, and the `getCandidate` method is responsible for finding and validating the SBOM documents.
@colek42
Copy link
Member Author

colek42 commented May 4, 2024

This allows us to do links like query for SBOMs and generate vuln reports.

➜  go-witness git:(feat/sbom-attestor) ✗ curl https://archivista.testifysec.io/download/b0accc787c8c45c1d90f52575f5ab2b672a7b5bd71948525f6e1c930f2362fc1 | jq -r '.payload' | base64 -d | jq -r '.predicate.attestations[] | select(.type == "https://witness.dev/attestations/sbom/v0.1").attestation.sbomDocument' | grype   


  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 3371k    0 3371k    0     0  4641k      0 --:--:-- --:--:-- --:--:-- 4637k
NAME                      INSTALLED  FIXED-IN  TYPE       VULNERABILITY        SEVERITY 
github.com/gin-gonic/gin  v1.7.7     1.9.0     go-module  GHSA-3vp4-m3rf-835h  Medium    
github.com/gin-gonic/gin  v1.7.7     1.9.1     go-module  GHSA-2c4m-59x9-fr2g  Medium

@jkjell
Copy link
Member

jkjell commented May 28, 2024

Continuing work on this attestor this week. I'll close this PR for now until the changes are ready for review.

@jkjell jkjell closed this May 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants