github.com/google/osv-scalibr@v0.4.1/converter/converter.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package converter provides utility functions for converting SCALIBR's scan results to
    16  // standardized inventory formats.
    17  package converter
    18  
    19  import (
    20  	"time"
    21  
    22  	"github.com/CycloneDX/cyclonedx-go"
    23  	"github.com/google/osv-scalibr/converter/spdx"
    24  	"github.com/google/osv-scalibr/extractor"
    25  	cdxmeta "github.com/google/osv-scalibr/extractor/filesystem/sbom/cdx/metadata"
    26  	spdxmeta "github.com/google/osv-scalibr/extractor/filesystem/sbom/spdx/metadata"
    27  	"github.com/google/osv-scalibr/purl"
    28  	"github.com/google/osv-scalibr/result"
    29  	"github.com/google/uuid"
    30  	"github.com/spdx/tools-golang/spdx/v2/v2_3"
    31  )
    32  
    33  // ToPURL converts a SCALIBR package structure into a package URL.
    34  func ToPURL(p *extractor.Package) *purl.PackageURL {
    35  	return p.PURL()
    36  }
    37  
    38  // ToSPDX23 converts the SCALIBR scan results into an SPDX v2.3 document.
    39  func ToSPDX23(r *result.ScanResult, c spdx.Config) *v2_3.Document {
    40  	return spdx.ToSPDX23(r, c)
    41  }
    42  
    43  // CDXConfig describes custom settings that should be applied to the generated CDX file.
    44  type CDXConfig struct {
    45  	ComponentName    string
    46  	ComponentVersion string
    47  	ComponentType    string
    48  	Authors          []string
    49  }
    50  
    51  // ToCDX converts the SCALIBR scan results into a CycloneDX document.
    52  func ToCDX(r *result.ScanResult, c CDXConfig) *cyclonedx.BOM {
    53  	bom := cyclonedx.NewBOM()
    54  	bom.Metadata = &cyclonedx.Metadata{
    55  		Timestamp: time.Now().UTC().Format("2006-01-02T15:04:05Z"),
    56  		Component: &cyclonedx.Component{
    57  			Name:    c.ComponentName,
    58  			Version: c.ComponentVersion,
    59  			Type:    cyclonedx.ComponentType(c.ComponentType),
    60  			BOMRef:  uuid.New().String(),
    61  		},
    62  		Tools: &cyclonedx.ToolsChoice{
    63  			Components: &[]cyclonedx.Component{
    64  				{
    65  					Type: cyclonedx.ComponentTypeApplication,
    66  					Name: "SCALIBR",
    67  					ExternalReferences: &[]cyclonedx.ExternalReference{
    68  						{
    69  							URL:  "https://github.com/google/osv-scalibr",
    70  							Type: cyclonedx.ERTypeWebsite,
    71  						},
    72  					},
    73  				},
    74  			},
    75  		},
    76  	}
    77  	if len(c.Authors) > 0 {
    78  		authors := make([]cyclonedx.OrganizationalContact, 0, len(c.Authors))
    79  		for _, author := range c.Authors {
    80  			authors = append(authors, cyclonedx.OrganizationalContact{
    81  				Name: author,
    82  			})
    83  		}
    84  		bom.Metadata.Authors = &authors
    85  	}
    86  
    87  	comps := make([]cyclonedx.Component, 0, len(r.Inventory.Packages))
    88  	for _, pkg := range r.Inventory.Packages {
    89  		comp := cyclonedx.Component{
    90  			BOMRef:  uuid.New().String(),
    91  			Type:    cyclonedx.ComponentTypeLibrary,
    92  			Name:    pkg.Name,
    93  			Version: pkg.Version,
    94  		}
    95  		if p := ToPURL(pkg); p != nil {
    96  			comp.PackageURL = p.String()
    97  		}
    98  		if cpes := extractCPEs(pkg); len(cpes) > 0 {
    99  			comp.CPE = cpes[0]
   100  		}
   101  		if len(pkg.Locations) > 0 {
   102  			occ := make([]cyclonedx.EvidenceOccurrence, 0, len((pkg.Locations)))
   103  			for _, loc := range pkg.Locations {
   104  				occ = append(occ, cyclonedx.EvidenceOccurrence{
   105  					Location: loc,
   106  				})
   107  			}
   108  			comp.Evidence = &cyclonedx.Evidence{
   109  				Occurrences: &occ,
   110  			}
   111  		}
   112  		comps = append(comps, comp)
   113  	}
   114  	bom.Components = &comps
   115  
   116  	return bom
   117  }
   118  
   119  func extractCPEs(p *extractor.Package) []string {
   120  	// Only the two SBOM package types support storing CPEs.
   121  	if m, ok := p.Metadata.(*spdxmeta.Metadata); ok {
   122  		return m.CPEs
   123  	}
   124  	if m, ok := p.Metadata.(*cdxmeta.Metadata); ok {
   125  		return m.CPEs
   126  	}
   127  	return nil
   128  }