providers/vfeed/schema/providers.go (246 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 andT47981963
// limitations under the License.
package schema
import (
"errors"
"fmt"
"time"
nvd "github.com/facebookincubator/nvdtools/cvefeed/nvd/schema"
)
// TODO: move this file to its own package in the future, to implement a common
// API for all providers (hence the "Providers" namespace). We will need to add
// missing features to support all providers.
const (
providersDataFormat = "MITRE"
providersDataType = "CVE"
providersDataLang = "en"
providersDataVersion = "4.0"
providersTimeLayout = "2006-01-02T15:04Z"
)
// ProvidersItem captures the top-level CVE information for a vendor.
type ProvidersItem struct {
Vendor string
ID string
Description string
CWEs []string
References *ProvidersReferences
Configuration *ProvidersConfiguration
CVSS2 *ProvidersCVSS
CVSS3 *ProvidersCVSS
LastModifiedDate *time.Time
PublishedDate *time.Time
}
// ProvidersCVSS is used to store CVSS2 and CVSS3 data.
type ProvidersCVSS struct {
BaseScore float64
TemporalScore float64
Vector string
}
// ProvidersReferences hold data related to the thread.
type ProvidersReferences struct {
referenceData []*nvd.CVEJSON40Reference
}
// ProvidersConfiguration captures what specific software versions are vulnerable.
type ProvidersConfiguration struct {
Nodes []*ProvidersNode
}
// ProvidersNode holds a set of matches, any positive match being able to
// configure a CVE. If conditional matches are present as well, then at least
// one conditional match should also be positive to configure a CVE.
type ProvidersNode struct {
matches []*nvd.NVDCVEFeedJSON10DefCPEMatch
conditionalMatches []*nvd.NVDCVEFeedJSON10DefCPEMatch
}
// ProvidersMatch represents software versions that match a CPE.
type ProvidersMatch struct {
CPE22URI string
CPE23URI string
VersionStartExcluding string
VersionStartIncluding string
VersionEndExcluding string
VersionEndIncluding string
Vulnerable bool
}
// ProvidersConvertStrTime takes a time layout and a string representing time in that
// layout and converts it to a Time type. If the string is empty nil is
// returned.
func ProvidersConvertStrTime(layout, strTime string) (*time.Time, error) {
if strTime == "" {
return nil, nil
}
t, err := time.Parse(layout, strTime)
if err != nil {
return nil, err
}
return &t, nil
}
func providersConvertTime(t *time.Time) string {
if t == nil {
return ""
}
return t.Format(providersTimeLayout)
}
// ProvidersNewItem creates a vendor item.
func ProvidersNewItem(item *ProvidersItem) (*nvd.NVDCVEFeedJSON10DefCVEItem, error) {
if err := item.validate(); err != nil {
return nil, fmt.Errorf("validation error: %v", err)
}
return &nvd.NVDCVEFeedJSON10DefCVEItem{
CVE: &nvd.CVEJSON40{
CVEDataMeta: &nvd.CVEJSON40CVEDataMeta{
ID: item.ID,
ASSIGNER: item.Vendor,
},
DataFormat: providersDataFormat,
DataType: providersDataType,
DataVersion: providersDataVersion,
Description: &nvd.CVEJSON40Description{
DescriptionData: []*nvd.CVEJSON40LangString{
{
Lang: providersDataLang,
Value: item.Description,
},
},
},
Problemtype: item.problemType(),
References: item.references(),
},
Configurations: item.Configuration.convertToNVD(),
Impact: &nvd.NVDCVEFeedJSON10DefImpact{
BaseMetricV2: &nvd.NVDCVEFeedJSON10DefImpactBaseMetricV2{
CVSSV2: item.cvssV20(),
},
BaseMetricV3: &nvd.NVDCVEFeedJSON10DefImpactBaseMetricV3{
CVSSV3: item.cvssV30(),
},
},
LastModifiedDate: providersConvertTime(item.LastModifiedDate),
PublishedDate: providersConvertTime(item.PublishedDate),
}, nil
}
// validate will return an error if any mandatory fields are missing.
func (item *ProvidersItem) validate() error {
if item.ID == "" || item.Vendor == "" || item.Description == "" {
return errors.New("id, vendor and description can't be empty")
}
if item.Configuration == nil {
return errors.New("Configuration can't be nil")
}
return nil
}
func (item *ProvidersItem) cvssV20() *nvd.CVSSV20 {
if item.CVSS2 == nil {
return nil
}
return &nvd.CVSSV20{
BaseScore: item.CVSS2.BaseScore,
TemporalScore: item.CVSS2.TemporalScore,
VectorString: item.CVSS2.Vector,
}
}
func (item *ProvidersItem) cvssV30() *nvd.CVSSV30 {
if item.CVSS3 == nil {
return nil
}
return &nvd.CVSSV30{
BaseScore: item.CVSS3.BaseScore,
TemporalScore: item.CVSS3.TemporalScore,
VectorString: item.CVSS3.Vector,
}
}
func (item *ProvidersItem) references() *nvd.CVEJSON40References {
if item.References == nil || item.References.referenceData == nil {
return nil
}
return &nvd.CVEJSON40References{
ReferenceData: item.References.referenceData,
}
}
func (item *ProvidersItem) problemType() *nvd.CVEJSON40Problemtype {
if item.CWEs == nil {
return nil
}
var weaknesses []*nvd.CVEJSON40LangString
for _, cwe := range item.CWEs {
weaknesses = append(weaknesses, &nvd.CVEJSON40LangString{
Lang: providersDataLang,
Value: cwe,
})
}
return &nvd.CVEJSON40Problemtype{
ProblemtypeData: []*nvd.CVEJSON40ProblemtypeProblemtypeData{
{
Description: weaknesses,
},
},
}
}
// ProvidersNewReferences creates a ProvidersReferences.
func ProvidersNewReferences() *ProvidersReferences {
return &ProvidersReferences{}
}
// Add adds a new reference to the references.
func (r *ProvidersReferences) Add(name, url string) {
r.referenceData = append(r.referenceData, &nvd.CVEJSON40Reference{
Name: name,
URL: url,
})
}
// ProvidersNewConfiguration creates a ProvidersConfiguration.
func ProvidersNewConfiguration() *ProvidersConfiguration {
return &ProvidersConfiguration{
Nodes: []*ProvidersNode{},
}
}
func (c *ProvidersConfiguration) convertToNVD() *nvd.NVDCVEFeedJSON10DefConfigurations {
var nvdNodes []*nvd.NVDCVEFeedJSON10DefNode
for _, node := range c.Nodes {
nvdNode := &nvd.NVDCVEFeedJSON10DefNode{}
if len(node.conditionalMatches) > 0 {
nvdNode.Operator = "AND"
nvdNode.Children = []*nvd.NVDCVEFeedJSON10DefNode{
&nvd.NVDCVEFeedJSON10DefNode{
Operator: "OR",
CPEMatch: node.matches,
},
&nvd.NVDCVEFeedJSON10DefNode{
Operator: "OR",
CPEMatch: node.conditionalMatches,
},
}
} else {
nvdNode.Operator = "OR"
nvdNode.CPEMatch = node.matches
}
nvdNodes = append(nvdNodes, nvdNode)
}
return &nvd.NVDCVEFeedJSON10DefConfigurations{
CVEDataVersion: providersDataVersion,
Nodes: nvdNodes,
}
}
// NewNode creates a Node in the ProvidersConfiguration
func (c *ProvidersConfiguration) NewNode() *ProvidersNode {
node := &ProvidersNode{
matches: []*nvd.NVDCVEFeedJSON10DefCPEMatch{},
conditionalMatches: []*nvd.NVDCVEFeedJSON10DefCPEMatch{},
}
c.Nodes = append(c.Nodes, node)
return node
}
// AddMatch adds a ProvidersMatch to a ProvidersNode.
func (node *ProvidersNode) AddMatch(m *ProvidersMatch) {
node.matches = append(node.matches, m.convertToNVD())
}
// AddConditionalMatch adds a ProvidersMatch to a ProvidersNode.
func (node *ProvidersNode) AddConditionalMatch(m *ProvidersMatch) {
node.conditionalMatches = append(node.conditionalMatches, m.convertToNVD())
}
// ProvidersNewMatch creates a ProvidersMatch.
func ProvidersNewMatch(cpe22uri, cpe23uri string, vulnerable bool) *ProvidersMatch {
return &ProvidersMatch{
CPE22URI: cpe22uri,
CPE23URI: cpe23uri,
Vulnerable: vulnerable,
}
}
func (m *ProvidersMatch) convertToNVD() *nvd.NVDCVEFeedJSON10DefCPEMatch {
return &nvd.NVDCVEFeedJSON10DefCPEMatch{
Cpe22Uri: m.CPE22URI,
Cpe23Uri: m.CPE23URI,
VersionStartExcluding: m.VersionStartExcluding,
VersionStartIncluding: m.VersionStartIncluding,
VersionEndExcluding: m.VersionEndExcluding,
VersionEndIncluding: m.VersionEndIncluding,
Vulnerable: m.Vulnerable,
}
}
// AddVersionStart adds the starting version to a Match, along with
// whether that version is included or excluded from the Match.
func (m *ProvidersMatch) AddVersionStart(version string, excluding bool) {
if excluding {
m.VersionStartExcluding = version
} else {
m.VersionStartIncluding = version
}
}
// AddVersionEnd adds the ending version to a Match, along with
// whether that version is included or excluded from the Match.
func (m *ProvidersMatch) AddVersionEnd(version string, excluding bool) {
if excluding {
m.VersionEndExcluding = version
} else {
m.VersionEndIncluding = version
}
}