Skip to content

Commit

Permalink
feat: add SBOM support
Browse files Browse the repository at this point in the history
Add support for software bill of materials (SBOM) data object.
  • Loading branch information
tri-adam committed Sep 15, 2022
1 parent 4d7ab6f commit 0bd6c58
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 3 deletions.
21 changes: 21 additions & 0 deletions pkg/sif/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ type cryptoMessage struct {
Messagetype MessageType
}

// sbom represents the SIF SBOM data object descriptor.
type sbom struct {
Format SBOMFormat
}

var errNameTooLarge = errors.New("name value too large")

// setName encodes name into the name field of d.
Expand Down Expand Up @@ -230,6 +235,22 @@ func (d Descriptor) CryptoMessageMetadata() (FormatType, MessageType, error) {
return m.Formattype, m.Messagetype, nil
}

// SBOMMetadata gets metadata for a SBOM data object.
func (d Descriptor) SBOMMetadata() (SBOMFormat, error) {
if got, want := d.raw.DataType, DataSBOM; got != want {
return 0, &unexpectedDataTypeError{got, []DataType{want}}
}

var s sbom

b := bytes.NewReader(d.raw.Extra[:])
if err := binary.Read(b, binary.LittleEndian, &s); err != nil {
return 0, fmt.Errorf("%w", err)
}

return s.Format, nil
}

// GetData returns the data object associated with descriptor d.
func (d Descriptor) GetData() ([]byte, error) {
b := make([]byte, d.raw.Size)
Expand Down
20 changes: 19 additions & 1 deletion pkg/sif/descriptor_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,24 @@ func OptSignatureMetadata(ht crypto.Hash, fp []byte) DescriptorInputOpt {
}
}

// OptSBOMMetadata sets metadata for a SBOM data object. The SBOM format is set to f.
//
// If this option is applied to a data object with an incompatible type, an error is returned.
func OptSBOMMetadata(f SBOMFormat) DescriptorInputOpt {
return func(t DataType, opts *descriptorOpts) error {
if got, want := t, DataSBOM; got != want {
return &unexpectedDataTypeError{got, []DataType{want}}
}

s := sbom{
Format: f,
}

opts.extra = s
return nil
}
}

// DescriptorInput describes a new data object.
type DescriptorInput struct {
dt DataType
Expand All @@ -241,7 +259,7 @@ const DefaultObjectGroup = 1
//
// It is possible (and often necessary) to store additional metadata related to certain types of
// data objects. Consider supplying options such as OptCryptoMessageMetadata, OptPartitionMetadata,
// and OptSignatureMetadata for this purpose.
// OptSignatureMetadata, and OptSBOMMetadata for this purpose.
//
// By default, the data object will be placed in the default data object group (1). To override
// this behavior, use OptNoGroup or OptGroupID. To link this data object, use OptLinkedID or
Expand Down
17 changes: 16 additions & 1 deletion pkg/sif/descriptor_input_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021, Sylabs Inc. All rights reserved.
// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -163,6 +163,21 @@ func TestNewDescriptorInput(t *testing.T) {
OptSignatureMetadata(crypto.SHA256, fp),
},
},
{
name: "OptSBOMMetadataUnexpectedDataType",
t: DataGeneric,
opts: []DescriptorInputOpt{
OptSBOMMetadata(SBOMFormatCycloneDXJSON),
},
wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataSBOM}},
},
{
name: "OptSBOMMetadata",
t: DataSBOM,
opts: []DescriptorInputOpt{
OptSBOMMetadata(SBOMFormatCycloneDXJSON),
},
},
}
for _, tt := range tests {
tt := tt
Expand Down
52 changes: 51 additions & 1 deletion pkg/sif/descriptor_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -287,6 +287,56 @@ func TestDescriptor_CryptoMessageMetadata(t *testing.T) {
}
}

func TestDescriptor_SBOMMetadata(t *testing.T) {
m := sbom{
Format: SBOMFormatCycloneDXJSON,
}

rd := rawDescriptor{
DataType: DataSBOM,
}
if err := rd.setExtra(m); err != nil {
t.Fatal(err)
}

tests := []struct {
name string
rd rawDescriptor
wantFormat SBOMFormat
wantErr error
}{
{
name: "UnexpectedDataType",
rd: rawDescriptor{
DataType: DataGeneric,
},
wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataSBOM}},
},
{
name: "OK",
rd: rd,
wantFormat: SBOMFormatCycloneDXJSON,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := Descriptor{raw: tt.rd}

f, err := d.SBOMMetadata()

if got, want := err, tt.wantErr; !errors.Is(got, want) {
t.Fatalf("got error %v, want %v", got, want)
}

if err == nil {
if got, want := f, tt.wantFormat; got != want {
t.Fatalf("got format %v, want %v", got, want)
}
}
})
}
}

func TestDescriptor_GetIntegrityReader(t *testing.T) {
rd := rawDescriptor{
DataType: DataDeffile,
Expand Down
41 changes: 41 additions & 0 deletions pkg/sif/sif.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const (
DataGenericJSON // generic JSON meta-data
DataGeneric // generic / raw data
DataCryptoMessage // cryptographic message data object
DataSBOM // software bill of materials
)

// String returns a human-readable representation of t.
Expand All @@ -153,6 +154,8 @@ func (t DataType) String() string {
return "Generic/Raw"
case DataCryptoMessage:
return "Cryptographic Message"
case DataSBOM:
return "SBOM"
}
return "Unknown"
}
Expand Down Expand Up @@ -267,6 +270,44 @@ func (t MessageType) String() string {
return "Unknown"
}

// SBOMFormat represents the format used to store an SBOM object.
type SBOMFormat int32

// List of supported SBOM formats.
const (
SBOMFormatCycloneDXJSON SBOMFormat = iota + 1 // CycloneDX (JSON)
SBOMFormatCycloneDXXML // CycloneDX (XML)
SBOMFormatGitHubJSON // GitHub dependency snapshot (JSON)
SBOMFormatSPDXJSON // SPDX (JSON)
SBOMFormatSPDXRDF // SPDX (RDF/xml)
SBOMFormatSPDXTagValue // SPDX (tag/value)
SBOMFormatSPDXYAML // SPDX (YAML)
SBOMFormatSyftJSON // Syft (JSON)
)

// String returns a human-readable representation of f.
func (f SBOMFormat) String() string {
switch f {
case SBOMFormatCycloneDXJSON:
return "cyclonedx-json"
case SBOMFormatCycloneDXXML:
return "cyclonedx-xml"
case SBOMFormatGitHubJSON:
return "github-json"
case SBOMFormatSPDXJSON:
return "spdx-json"
case SBOMFormatSPDXRDF:
return "spdx-rdf"
case SBOMFormatSPDXTagValue:
return "spdx-tag-value"
case SBOMFormatSPDXYAML:
return "spdx-yaml"
case SBOMFormatSyftJSON:
return "syft-json"
}
return "unknown"
}

// header describes a loaded SIF file.
type header struct {
LaunchScript [hdrLaunchLen]byte
Expand Down
Binary file not shown.

0 comments on commit 0bd6c58

Please sign in to comment.