blob: b9c5ec2ce52789b9eaaf6f8a51dba12374457160 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"golang.org/x/vulndb/report"
"gopkg.in/yaml.v2"
)
// Affects
type Affects struct {
Vendor Vendor `json:"vendor"`
}
// CVEDataMeta
type CVEDataMeta struct {
ASSIGNER string `json:"ASSIGNER"`
ID string `json:"ID"`
STATE string `json:"STATE"`
}
// Description
type Description struct {
DescriptionData []LangString `json:"description_data"`
}
// LangString
type LangString struct {
Lang string `json:"lang"`
Value string `json:"value"`
}
// Problemtype
type Problemtype struct {
ProblemtypeData []ProblemtypeDataItems `json:"problemtype_data"`
}
// ProblemtypeDataItems
type ProblemtypeDataItems struct {
Description []LangString `json:"description"`
}
type VersionData struct {
VersionData []VersionDataItems `json:"version_data"`
}
type ProductDataItem struct {
ProductName string `json:"product_name"`
Version VersionData `json:"version"`
}
// Product
type Product struct {
ProductData []ProductDataItem `json:"product_data"`
}
// Reference
type Reference struct {
URL string `json:"url"`
}
// References
type References struct {
ReferenceData []Reference `json:"reference_data"`
}
// Vendor
type Vendor struct {
VendorData []VendorDataItems `json:"vendor_data"`
}
// VendorDataItems
type VendorDataItems struct {
Product Product `json:"product"`
VendorName string `json:"vendor_name"`
}
// VersionDataItems
type VersionDataItems struct {
VersionValue string `json:"version_value"`
VersionAffected string `json:"version_affected"`
}
// CVE
type CVE struct {
DataType string `json:"data_type"`
DataFormat string `json:"data_format"`
DataVersion string `json:"data_version"`
CVEDataMeta CVEDataMeta `json:"CVE_data_meta"`
Affects Affects `json:"affects"`
Description Description `json:"description"`
Problemtype Problemtype `json:"problemtype"`
References References `json:"references"`
}
func FromReport(report *report.Report) (*CVE, error) {
if report.CVE != "" {
return nil, errors.New("report has CVE ID is wrong section (should be in cve_metadata for self-issued CVEs)")
}
if report.CVEMetadata == nil {
return nil, errors.New("report missing cve_metadata section")
}
if report.CVEMetadata.ID == "" {
return nil, errors.New("report missing CVE ID")
}
cve := &CVE{
DataType: "CVE",
DataFormat: "MITRE",
DataVersion: "4.0",
CVEDataMeta: CVEDataMeta{
ID: report.CVEMetadata.ID,
ASSIGNER: "security@golang.org",
STATE: "PUBLIC",
},
Description: Description{
DescriptionData: []LangString{
{
Lang: "eng",
Value: strings.TrimSuffix(report.CVEMetadata.Description, "\n"),
},
},
},
Problemtype: Problemtype{
ProblemtypeData: []ProblemtypeDataItems{
{
Description: []LangString{
{
Lang: "eng",
Value: report.CVEMetadata.CWE,
},
},
},
},
},
Affects: Affects{
Vendor: Vendor{
VendorData: []VendorDataItems{
{
VendorName: "n/a", // ???
Product: Product{
ProductData: []ProductDataItem{
{
ProductName: report.Package,
Version: versionToVersion(report.Versions),
},
},
},
},
},
},
},
}
for _, additional := range report.AdditionalPackages {
cve.Affects.Vendor.VendorData = append(cve.Affects.Vendor.VendorData, VendorDataItems{
VendorName: "n/a",
Product: Product{
ProductData: []ProductDataItem{
{
ProductName: additional.Package,
Version: versionToVersion(additional.Versions),
},
},
},
})
}
if report.Links.PR != "" {
cve.References.ReferenceData = append(cve.References.ReferenceData, Reference{URL: report.Links.PR})
}
if report.Links.Commit != "" {
cve.References.ReferenceData = append(cve.References.ReferenceData, Reference{URL: report.Links.Commit})
}
for _, url := range report.Links.Context {
cve.References.ReferenceData = append(cve.References.ReferenceData, Reference{URL: url})
}
return cve, nil
}
func versionToVersion(versions []report.VersionRange) VersionData {
vd := VersionData{}
for _, vr := range versions {
if vr.Introduced != "" {
vd.VersionData = append(vd.VersionData, VersionDataItems{
VersionValue: vr.Introduced,
VersionAffected: ">=",
})
}
if vr.Fixed != "" {
vd.VersionData = append(vd.VersionData, VersionDataItems{
VersionValue: vr.Fixed,
VersionAffected: "<",
})
}
}
return vd
}
func main() {
if len(os.Args) != 2 {
fmt.Fprint(os.Stderr, "usage: report2cve report.toml")
os.Exit(1)
}
reportPath := os.Args[1]
b, err := ioutil.ReadFile(reportPath)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to read %q: %s\n", reportPath, err)
os.Exit(1)
}
var r report.Report
if err = yaml.UnmarshalStrict(b, &r); err != nil {
fmt.Fprintf(os.Stderr, "failed to parse %q: %s\n", reportPath, err)
os.Exit(1)
}
cve, err := FromReport(&r)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to generate CVE: %s\n", err)
os.Exit(1)
}
// We need to use an encoder so that it doesn't escape angle
// brackets.
e := json.NewEncoder(os.Stdout)
e.SetEscapeHTML(false)
e.SetIndent("", "\t")
if err = e.Encode(cve); err != nil {
fmt.Fprintf(os.Stderr, "failed to marshal CVE: %s\n", err)
os.Exit(1)
}
}