providers/vfeed/schema/convert.go (172 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package schema
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
nvd "github.com/facebookincubator/nvdtools/cvefeed/nvd/schema"
)
type basicCVEData struct {
id string
summary string
modified *time.Time
published *time.Time
}
// Convert reads a vendor item and outputs it in the NVD format.
func (item *Item) Convert() (*nvd.NVDCVEFeedJSON10DefCVEItem, error) {
basicData, err := item.basicCVEData()
if err != nil {
return nil, errors.Wrap(err, "failed to extract cve data")
}
references := ProvidersNewReferences()
if item.Information != nil && item.Information.References != nil {
for _, ref := range item.Information.References {
references.Add(ref.Vendor, ref.URL)
}
}
config, err := item.configurationData()
if err != nil {
return nil, errors.Wrap(err, "failed to extract configuration data")
}
cvss2, cvss3, err := item.riskData()
if err != nil {
return nil, errors.Wrap(err, "failed to extract risk data")
}
return ProvidersNewItem(
&ProvidersItem{
Vendor: vendor,
ID: basicData.id,
Description: basicData.summary,
References: references,
Configuration: config,
CWEs: item.weaknessData(),
CVSS2: cvss2,
CVSS3: cvss3,
LastModifiedDate: basicData.modified,
PublishedDate: basicData.published,
},
)
}
func (item *Item) riskData() (cvss2 *ProvidersCVSS, cvss3 *ProvidersCVSS, err error) {
if item.Risk == nil || item.Risk.CVSS == nil {
return
}
cvss := item.Risk.CVSS
if cvss.CVSS2 != nil {
cvss2, err = cvssData(cvss.CVSS2.Vector, cvss.CVSS2.BaseScore)
if err != nil {
err = errors.Wrap(err, "failed to extract cvss2 data")
return
}
}
if cvss.CVSS3 != nil {
cvss3, err = cvssData(cvss.CVSS3.Vector, cvss.CVSS3.BaseScore)
if err != nil {
err = errors.Wrap(err, "failed to extract cvss3 data")
return
}
}
return
}
func cvssData(vector, strBaseScore string) (*ProvidersCVSS, error) {
if vector == cvssUndefined {
return nil, nil
}
baseScore, err := strconv.ParseFloat(strBaseScore, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse base score: %v", err)
}
return &ProvidersCVSS{
Vector: vector,
BaseScore: baseScore,
}, nil
}
// extractVersion receives a version with a qualifier and returns the version
// number and whether it is exclusive. Example input version:
// "2.1 (excluding)"
func extractVersion(version string) (string, bool, error) {
if version == "" {
return "", false, nil
}
fields := strings.Fields(version)
if n := len(fields); n != 2 {
return "", false, fmt.Errorf("expected two fields in version %q, found %d", version, n)
}
return fields[0], fields[1] == exclusionString, nil
}
func (item *Item) weaknessData() []string {
var cwes []string
if item.Classification != nil && item.Classification.Weaknesses != nil {
for _, w := range item.Classification.Weaknesses {
cwes = append(cwes, w.ID)
}
}
return cwes
}
func (item *Item) basicCVEData() (*basicCVEData, error) {
if item.Information == nil {
return nil, nil
}
// The schema has support for multiple CVEs, but in practice only one is used
// per item, which is all we support.
if n := len(item.Information.Descriptions); n != 1 {
return nil, fmt.Errorf("we only support 1 CVE per item, found %d", n)
}
description := item.Information.Descriptions[0]
descParams := description.Parameters
if descParams == nil {
return nil, nil
}
modified, err := ProvidersConvertStrTime(timeLayout, descParams.Modified)
if err != nil {
return nil, errors.Wrap(err, "failed to convert last modified date")
}
published, err := ProvidersConvertStrTime(timeLayout, descParams.Published)
if err != nil {
return nil, errors.Wrap(err, "failed to convert last modified date")
}
return &basicCVEData{
id: description.ID,
summary: descParams.Summary,
modified: modified,
published: published,
}, nil
}
func (item *Item) configurationData() (*ProvidersConfiguration, error) {
if item.Classification == nil {
return nil, nil
}
config := ProvidersNewConfiguration()
if item.Classification.Targets != nil {
for _, target := range item.Classification.Targets {
node := config.NewNode()
// Parameters will follow one of the cases:
// 1 - A list of vulnerable CPEs with versions
// 2 - One vulnerable CPE with versions followed by "running_on",
// conditional non-vulnerable CPEs without versions.
for _, params := range target.Parameters {
if params.RunningOn != nil {
for _, running := range params.RunningOn {
node.AddConditionalMatch(
&ProvidersMatch{
CPE22URI: running.CPE22,
CPE23URI: running.CPE23,
Vulnerable: false,
},
)
}
continue
}
match := &ProvidersMatch{
CPE22URI: params.CPE22,
CPE23URI: params.CPE23,
Vulnerable: true,
}
version := params.VersionAffected
from, excluding, err := extractVersion(version.From)
if err != nil {
return nil, err
}
match.AddVersionStart(from, excluding)
to, excluding, err := extractVersion(version.To)
if err != nil {
return nil, err
}
match.AddVersionEnd(to, excluding)
node.AddMatch(match)
}
}
}
return config, nil
}