From 9c17b97cd0f83be3fff9fa4e87fd1d29052ea616 Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Fri, 4 Feb 2022 00:35:31 -0500 Subject: rename to github.com/pablotron/cvez, remove internal libs --- cpe/avstring.go | 54 ++ cpe/avstring_test.go | 95 +++ cpe/avstringtype_string.go | 25 + cpe/cpe.go | 5 + cpe/part.go | 41 ++ cpe/part_string.go | 41 ++ cpe/part_test.go | 108 ++++ cpe/token.go | 99 ++++ cpe/token_test.go | 149 +++++ cpe/tokentype_string.go | 25 + cpe/v23binding.go | 119 ++++ cpe/v23binding_test.go | 132 +++++ cpedict/cpedict.go | 46 ++ cpedict/cpedict_test.go | 136 +++++ cpedict/testdata/test-0.xml.gz | Bin 0 -> 1359 bytes cpematch/cpematch.go | 31 + cpematch/cpematch_test.go | 100 ++++ cpematch/testdata/test-0.json.gz | Bin 0 -> 291 bytes cvss/badkey.go | 20 + cvss/badmetric.go | 19 + cvss/badmetric_test.go | 25 + cvss/category.go | 13 + cvss/category_string.go | 26 + cvss/category_test.go | 41 ++ cvss/cvss.go | 23 + cvss/v2key.go | 92 +++ cvss/v2key_string.go | 37 ++ cvss/v2key_test.go | 116 ++++ cvss/v2metric.go | 244 ++++++++ cvss/v2metric_string.go | 78 +++ cvss/v2metric_test.go | 276 +++++++++ cvss/v2vector.go | 108 ++++ cvss/v2vector_test.go | 69 +++ cvss/v30vector.go | 119 ++++ cvss/v30vector_test.go | 69 +++ cvss/v31vector.go | 119 ++++ cvss/v31vector_test.go | 561 ++++++++++++++++++ cvss/v3key.go | 116 ++++ cvss/v3key_string.go | 45 ++ cvss/v3key_test.go | 142 +++++ cvss/v3metric.go | 331 +++++++++++ cvss/v3metric_string.go | 100 ++++ cvss/v3metric_test.go | 366 ++++++++++++ cvss/vector.go | 36 ++ cvss/vector_test.go | 659 +++++++++++++++++++++ cvss/version.go | 12 + cvss/version_string.go | 25 + cvss/version_test.go | 24 + feed/cveid.go | 112 ++++ feed/cveid_test.go | 295 +++++++++ feed/dataformat.go | 36 ++ feed/dataformat_string.go | 23 + feed/dataformat_test.go | 65 ++ feed/datatype.go | 36 ++ feed/datatype_string.go | 23 + feed/datatype_test.go | 65 ++ feed/dataversion.go | 36 ++ feed/dataversion_string.go | 23 + feed/dataversion_test.go | 65 ++ feed/feed.go | 255 ++++++++ feed/feed_test.go | 56 ++ feed/meta.go | 106 ++++ feed/meta_test.go | 149 +++++ feed/nodeop.go | 39 ++ feed/nodeop_string.go | 24 + feed/nodeop_test.go | 77 +++ feed/score.go | 34 ++ feed/score_test.go | 92 +++ feed/severity.go | 47 ++ feed/severity_string.go | 27 + feed/severity_test.go | 83 +++ feed/testdata/nvdcve-1.1-2002.json.gz | Bin 0 -> 1453835 bytes feed/testdata/nvdcve-1.1-2003.json.gz | Bin 0 -> 434020 bytes feed/testdata/nvdcve-1.1-2021.json.gz | Bin 0 -> 4852632 bytes feed/testdata/nvdcve-1.1-modified.json.gz | Bin 0 -> 288574 bytes feed/time.go | 44 ++ feed/time_test.go | 66 +++ feed/v2accesscomplexity.go | 42 ++ feed/v2accesscomplexity_string.go | 25 + feed/v2accesscomplexity_test.go | 79 +++ feed/v2accessvector.go | 42 ++ feed/v2accessvector_string.go | 25 + feed/v2accessvector_test.go | 79 +++ feed/v2authentication.go | 39 ++ feed/v2authentication_string.go | 24 + feed/v2authentication_test.go | 77 +++ feed/v2impact.go | 42 ++ feed/v2impact_string.go | 25 + feed/v2impact_test.go | 79 +++ feed/v2version.go | 36 ++ feed/v2version_string.go | 23 + feed/v2version_test.go | 75 +++ feed/v3attackcomplexity.go | 42 ++ feed/v3attackcomplexity_string.go | 25 + feed/v3attackcomplexity_test.go | 79 +++ feed/v3attackvector.go | 46 ++ feed/v3attackvector_string.go | 26 + feed/v3attackvector_test.go | 81 +++ feed/v3impact.go | 42 ++ feed/v3impact_string.go | 25 + feed/v3impact_test.go | 79 +++ feed/v3privilegesrequired.go | 45 ++ feed/v3privilegesrequired_string.go | 26 + feed/v3privilegesrequired_test.go | 81 +++ feed/v3scope.go | 39 ++ feed/v3scope_string.go | 24 + feed/v3scope_test.go | 77 +++ feed/v3userinteraction.go | 40 ++ feed/v3userinteraction_string.go | 24 + feed/v3userinteraction_test.go | 77 +++ feed/v3version.go | 36 ++ feed/v3version_string.go | 23 + feed/v3version_test.go | 75 +++ feed/vector.go | 39 ++ feed/vector_test.go | 99 ++++ go.mod | 2 +- internal/cpe/avstring.go | 54 -- internal/cpe/avstring_test.go | 95 --- internal/cpe/avstringtype_string.go | 25 - internal/cpe/cpe.go | 5 - internal/cpe/part.go | 41 -- internal/cpe/part_string.go | 41 -- internal/cpe/part_test.go | 108 ---- internal/cpe/token.go | 99 ---- internal/cpe/token_test.go | 149 ----- internal/cpe/tokentype_string.go | 25 - internal/cpe/v23binding.go | 119 ---- internal/cpe/v23binding_test.go | 132 ----- internal/cpedict/cpedict.go | 46 -- internal/cpedict/cpedict_test.go | 136 ----- internal/cpedict/testdata/test-0.xml.gz | Bin 1359 -> 0 bytes internal/cpematch/cpematch.go | 31 - internal/cpematch/cpematch_test.go | 100 ---- internal/cpematch/testdata/test-0.json.gz | Bin 291 -> 0 bytes internal/cvss/badkey.go | 20 - internal/cvss/badmetric.go | 19 - internal/cvss/badmetric_test.go | 25 - internal/cvss/category.go | 13 - internal/cvss/category_string.go | 26 - internal/cvss/category_test.go | 41 -- internal/cvss/cvss.go | 23 - internal/cvss/v2key.go | 92 --- internal/cvss/v2key_string.go | 37 -- internal/cvss/v2key_test.go | 116 ---- internal/cvss/v2metric.go | 244 -------- internal/cvss/v2metric_string.go | 78 --- internal/cvss/v2metric_test.go | 276 --------- internal/cvss/v2vector.go | 108 ---- internal/cvss/v2vector_test.go | 69 --- internal/cvss/v30vector.go | 119 ---- internal/cvss/v30vector_test.go | 69 --- internal/cvss/v31vector.go | 119 ---- internal/cvss/v31vector_test.go | 561 ------------------ internal/cvss/v3key.go | 116 ---- internal/cvss/v3key_string.go | 45 -- internal/cvss/v3key_test.go | 142 ----- internal/cvss/v3metric.go | 331 ----------- internal/cvss/v3metric_string.go | 100 ---- internal/cvss/v3metric_test.go | 366 ------------ internal/cvss/vector.go | 36 -- internal/cvss/vector_test.go | 659 --------------------- internal/cvss/version.go | 12 - internal/cvss/version_string.go | 25 - internal/cvss/version_test.go | 24 - internal/feed/cveid.go | 112 ---- internal/feed/cveid_test.go | 295 --------- internal/feed/dataformat.go | 36 -- internal/feed/dataformat_string.go | 23 - internal/feed/dataformat_test.go | 65 -- internal/feed/datatype.go | 36 -- internal/feed/datatype_string.go | 23 - internal/feed/datatype_test.go | 65 -- internal/feed/dataversion.go | 36 -- internal/feed/dataversion_string.go | 23 - internal/feed/dataversion_test.go | 65 -- internal/feed/feed.go | 255 -------- internal/feed/feed_test.go | 56 -- internal/feed/meta.go | 106 ---- internal/feed/meta_test.go | 149 ----- internal/feed/nodeop.go | 39 -- internal/feed/nodeop_string.go | 24 - internal/feed/nodeop_test.go | 77 --- internal/feed/score.go | 34 -- internal/feed/score_test.go | 92 --- internal/feed/severity.go | 47 -- internal/feed/severity_string.go | 27 - internal/feed/severity_test.go | 83 --- internal/feed/testdata/nvdcve-1.1-2002.json.gz | Bin 1453835 -> 0 bytes internal/feed/testdata/nvdcve-1.1-2003.json.gz | Bin 434020 -> 0 bytes internal/feed/testdata/nvdcve-1.1-2021.json.gz | Bin 4852632 -> 0 bytes internal/feed/testdata/nvdcve-1.1-modified.json.gz | Bin 288574 -> 0 bytes internal/feed/time.go | 44 -- internal/feed/time_test.go | 66 --- internal/feed/v2accesscomplexity.go | 42 -- internal/feed/v2accesscomplexity_string.go | 25 - internal/feed/v2accesscomplexity_test.go | 79 --- internal/feed/v2accessvector.go | 42 -- internal/feed/v2accessvector_string.go | 25 - internal/feed/v2accessvector_test.go | 79 --- internal/feed/v2authentication.go | 39 -- internal/feed/v2authentication_string.go | 24 - internal/feed/v2authentication_test.go | 77 --- internal/feed/v2impact.go | 42 -- internal/feed/v2impact_string.go | 25 - internal/feed/v2impact_test.go | 79 --- internal/feed/v2version.go | 36 -- internal/feed/v2version_string.go | 23 - internal/feed/v2version_test.go | 75 --- internal/feed/v3attackcomplexity.go | 42 -- internal/feed/v3attackcomplexity_string.go | 25 - internal/feed/v3attackcomplexity_test.go | 79 --- internal/feed/v3attackvector.go | 46 -- internal/feed/v3attackvector_string.go | 26 - internal/feed/v3attackvector_test.go | 81 --- internal/feed/v3impact.go | 42 -- internal/feed/v3impact_string.go | 25 - internal/feed/v3impact_test.go | 79 --- internal/feed/v3privilegesrequired.go | 45 -- internal/feed/v3privilegesrequired_string.go | 26 - internal/feed/v3privilegesrequired_test.go | 81 --- internal/feed/v3scope.go | 39 -- internal/feed/v3scope_string.go | 24 - internal/feed/v3scope_test.go | 77 --- internal/feed/v3userinteraction.go | 40 -- internal/feed/v3userinteraction_string.go | 24 - internal/feed/v3userinteraction_test.go | 77 --- internal/feed/v3version.go | 36 -- internal/feed/v3version_string.go | 23 - internal/feed/v3version_test.go | 75 --- internal/feed/vector.go | 39 -- internal/feed/vector_test.go | 99 ---- 231 files changed, 8858 insertions(+), 8858 deletions(-) create mode 100644 cpe/avstring.go create mode 100644 cpe/avstring_test.go create mode 100644 cpe/avstringtype_string.go create mode 100644 cpe/cpe.go create mode 100644 cpe/part.go create mode 100644 cpe/part_string.go create mode 100644 cpe/part_test.go create mode 100644 cpe/token.go create mode 100644 cpe/token_test.go create mode 100644 cpe/tokentype_string.go create mode 100644 cpe/v23binding.go create mode 100644 cpe/v23binding_test.go create mode 100644 cpedict/cpedict.go create mode 100644 cpedict/cpedict_test.go create mode 100644 cpedict/testdata/test-0.xml.gz create mode 100644 cpematch/cpematch.go create mode 100644 cpematch/cpematch_test.go create mode 100644 cpematch/testdata/test-0.json.gz create mode 100644 cvss/badkey.go create mode 100644 cvss/badmetric.go create mode 100644 cvss/badmetric_test.go create mode 100644 cvss/category.go create mode 100644 cvss/category_string.go create mode 100644 cvss/category_test.go create mode 100644 cvss/cvss.go create mode 100644 cvss/v2key.go create mode 100644 cvss/v2key_string.go create mode 100644 cvss/v2key_test.go create mode 100644 cvss/v2metric.go create mode 100644 cvss/v2metric_string.go create mode 100644 cvss/v2metric_test.go create mode 100644 cvss/v2vector.go create mode 100644 cvss/v2vector_test.go create mode 100644 cvss/v30vector.go create mode 100644 cvss/v30vector_test.go create mode 100644 cvss/v31vector.go create mode 100644 cvss/v31vector_test.go create mode 100644 cvss/v3key.go create mode 100644 cvss/v3key_string.go create mode 100644 cvss/v3key_test.go create mode 100644 cvss/v3metric.go create mode 100644 cvss/v3metric_string.go create mode 100644 cvss/v3metric_test.go create mode 100644 cvss/vector.go create mode 100644 cvss/vector_test.go create mode 100644 cvss/version.go create mode 100644 cvss/version_string.go create mode 100644 cvss/version_test.go create mode 100644 feed/cveid.go create mode 100644 feed/cveid_test.go create mode 100644 feed/dataformat.go create mode 100644 feed/dataformat_string.go create mode 100644 feed/dataformat_test.go create mode 100644 feed/datatype.go create mode 100644 feed/datatype_string.go create mode 100644 feed/datatype_test.go create mode 100644 feed/dataversion.go create mode 100644 feed/dataversion_string.go create mode 100644 feed/dataversion_test.go create mode 100644 feed/feed.go create mode 100644 feed/feed_test.go create mode 100644 feed/meta.go create mode 100644 feed/meta_test.go create mode 100644 feed/nodeop.go create mode 100644 feed/nodeop_string.go create mode 100644 feed/nodeop_test.go create mode 100644 feed/score.go create mode 100644 feed/score_test.go create mode 100644 feed/severity.go create mode 100644 feed/severity_string.go create mode 100644 feed/severity_test.go create mode 100644 feed/testdata/nvdcve-1.1-2002.json.gz create mode 100644 feed/testdata/nvdcve-1.1-2003.json.gz create mode 100644 feed/testdata/nvdcve-1.1-2021.json.gz create mode 100644 feed/testdata/nvdcve-1.1-modified.json.gz create mode 100644 feed/time.go create mode 100644 feed/time_test.go create mode 100644 feed/v2accesscomplexity.go create mode 100644 feed/v2accesscomplexity_string.go create mode 100644 feed/v2accesscomplexity_test.go create mode 100644 feed/v2accessvector.go create mode 100644 feed/v2accessvector_string.go create mode 100644 feed/v2accessvector_test.go create mode 100644 feed/v2authentication.go create mode 100644 feed/v2authentication_string.go create mode 100644 feed/v2authentication_test.go create mode 100644 feed/v2impact.go create mode 100644 feed/v2impact_string.go create mode 100644 feed/v2impact_test.go create mode 100644 feed/v2version.go create mode 100644 feed/v2version_string.go create mode 100644 feed/v2version_test.go create mode 100644 feed/v3attackcomplexity.go create mode 100644 feed/v3attackcomplexity_string.go create mode 100644 feed/v3attackcomplexity_test.go create mode 100644 feed/v3attackvector.go create mode 100644 feed/v3attackvector_string.go create mode 100644 feed/v3attackvector_test.go create mode 100644 feed/v3impact.go create mode 100644 feed/v3impact_string.go create mode 100644 feed/v3impact_test.go create mode 100644 feed/v3privilegesrequired.go create mode 100644 feed/v3privilegesrequired_string.go create mode 100644 feed/v3privilegesrequired_test.go create mode 100644 feed/v3scope.go create mode 100644 feed/v3scope_string.go create mode 100644 feed/v3scope_test.go create mode 100644 feed/v3userinteraction.go create mode 100644 feed/v3userinteraction_string.go create mode 100644 feed/v3userinteraction_test.go create mode 100644 feed/v3version.go create mode 100644 feed/v3version_string.go create mode 100644 feed/v3version_test.go create mode 100644 feed/vector.go create mode 100644 feed/vector_test.go delete mode 100644 internal/cpe/avstring.go delete mode 100644 internal/cpe/avstring_test.go delete mode 100644 internal/cpe/avstringtype_string.go delete mode 100644 internal/cpe/cpe.go delete mode 100644 internal/cpe/part.go delete mode 100644 internal/cpe/part_string.go delete mode 100644 internal/cpe/part_test.go delete mode 100644 internal/cpe/token.go delete mode 100644 internal/cpe/token_test.go delete mode 100644 internal/cpe/tokentype_string.go delete mode 100644 internal/cpe/v23binding.go delete mode 100644 internal/cpe/v23binding_test.go delete mode 100644 internal/cpedict/cpedict.go delete mode 100644 internal/cpedict/cpedict_test.go delete mode 100644 internal/cpedict/testdata/test-0.xml.gz delete mode 100644 internal/cpematch/cpematch.go delete mode 100644 internal/cpematch/cpematch_test.go delete mode 100644 internal/cpematch/testdata/test-0.json.gz delete mode 100644 internal/cvss/badkey.go delete mode 100644 internal/cvss/badmetric.go delete mode 100644 internal/cvss/badmetric_test.go delete mode 100644 internal/cvss/category.go delete mode 100644 internal/cvss/category_string.go delete mode 100644 internal/cvss/category_test.go delete mode 100644 internal/cvss/cvss.go delete mode 100644 internal/cvss/v2key.go delete mode 100644 internal/cvss/v2key_string.go delete mode 100644 internal/cvss/v2key_test.go delete mode 100644 internal/cvss/v2metric.go delete mode 100644 internal/cvss/v2metric_string.go delete mode 100644 internal/cvss/v2metric_test.go delete mode 100644 internal/cvss/v2vector.go delete mode 100644 internal/cvss/v2vector_test.go delete mode 100644 internal/cvss/v30vector.go delete mode 100644 internal/cvss/v30vector_test.go delete mode 100644 internal/cvss/v31vector.go delete mode 100644 internal/cvss/v31vector_test.go delete mode 100644 internal/cvss/v3key.go delete mode 100644 internal/cvss/v3key_string.go delete mode 100644 internal/cvss/v3key_test.go delete mode 100644 internal/cvss/v3metric.go delete mode 100644 internal/cvss/v3metric_string.go delete mode 100644 internal/cvss/v3metric_test.go delete mode 100644 internal/cvss/vector.go delete mode 100644 internal/cvss/vector_test.go delete mode 100644 internal/cvss/version.go delete mode 100644 internal/cvss/version_string.go delete mode 100644 internal/cvss/version_test.go delete mode 100644 internal/feed/cveid.go delete mode 100644 internal/feed/cveid_test.go delete mode 100644 internal/feed/dataformat.go delete mode 100644 internal/feed/dataformat_string.go delete mode 100644 internal/feed/dataformat_test.go delete mode 100644 internal/feed/datatype.go delete mode 100644 internal/feed/datatype_string.go delete mode 100644 internal/feed/datatype_test.go delete mode 100644 internal/feed/dataversion.go delete mode 100644 internal/feed/dataversion_string.go delete mode 100644 internal/feed/dataversion_test.go delete mode 100644 internal/feed/feed.go delete mode 100644 internal/feed/feed_test.go delete mode 100644 internal/feed/meta.go delete mode 100644 internal/feed/meta_test.go delete mode 100644 internal/feed/nodeop.go delete mode 100644 internal/feed/nodeop_string.go delete mode 100644 internal/feed/nodeop_test.go delete mode 100644 internal/feed/score.go delete mode 100644 internal/feed/score_test.go delete mode 100644 internal/feed/severity.go delete mode 100644 internal/feed/severity_string.go delete mode 100644 internal/feed/severity_test.go delete mode 100644 internal/feed/testdata/nvdcve-1.1-2002.json.gz delete mode 100644 internal/feed/testdata/nvdcve-1.1-2003.json.gz delete mode 100644 internal/feed/testdata/nvdcve-1.1-2021.json.gz delete mode 100644 internal/feed/testdata/nvdcve-1.1-modified.json.gz delete mode 100644 internal/feed/time.go delete mode 100644 internal/feed/time_test.go delete mode 100644 internal/feed/v2accesscomplexity.go delete mode 100644 internal/feed/v2accesscomplexity_string.go delete mode 100644 internal/feed/v2accesscomplexity_test.go delete mode 100644 internal/feed/v2accessvector.go delete mode 100644 internal/feed/v2accessvector_string.go delete mode 100644 internal/feed/v2accessvector_test.go delete mode 100644 internal/feed/v2authentication.go delete mode 100644 internal/feed/v2authentication_string.go delete mode 100644 internal/feed/v2authentication_test.go delete mode 100644 internal/feed/v2impact.go delete mode 100644 internal/feed/v2impact_string.go delete mode 100644 internal/feed/v2impact_test.go delete mode 100644 internal/feed/v2version.go delete mode 100644 internal/feed/v2version_string.go delete mode 100644 internal/feed/v2version_test.go delete mode 100644 internal/feed/v3attackcomplexity.go delete mode 100644 internal/feed/v3attackcomplexity_string.go delete mode 100644 internal/feed/v3attackcomplexity_test.go delete mode 100644 internal/feed/v3attackvector.go delete mode 100644 internal/feed/v3attackvector_string.go delete mode 100644 internal/feed/v3attackvector_test.go delete mode 100644 internal/feed/v3impact.go delete mode 100644 internal/feed/v3impact_string.go delete mode 100644 internal/feed/v3impact_test.go delete mode 100644 internal/feed/v3privilegesrequired.go delete mode 100644 internal/feed/v3privilegesrequired_string.go delete mode 100644 internal/feed/v3privilegesrequired_test.go delete mode 100644 internal/feed/v3scope.go delete mode 100644 internal/feed/v3scope_string.go delete mode 100644 internal/feed/v3scope_test.go delete mode 100644 internal/feed/v3userinteraction.go delete mode 100644 internal/feed/v3userinteraction_string.go delete mode 100644 internal/feed/v3userinteraction_test.go delete mode 100644 internal/feed/v3version.go delete mode 100644 internal/feed/v3version_string.go delete mode 100644 internal/feed/v3version_test.go delete mode 100644 internal/feed/vector.go delete mode 100644 internal/feed/vector_test.go diff --git a/cpe/avstring.go b/cpe/avstring.go new file mode 100644 index 0000000..46e16cf --- /dev/null +++ b/cpe/avstring.go @@ -0,0 +1,54 @@ +package cpe + +//go:generate stringer -linecomment -type=AvStringType + +import ( + "fmt" +) + +// type of avstring. +type AvStringType byte + +const ( + AnyString AvStringType = iota // any + NaString // na + ValString // val +) + +// String value (NISTIR 7695, CPE 2.3 spec, Figure 6-3) +type AvString struct { + Type AvStringType // value type + Val string // value +} + +// token type to avstring type map +var avTypes = map[tokenType]AvStringType { + anyToken: AnyString, + naToken: NaString, + valToken: ValString, +} + +// create new AvString from token. +func newAvString(t token) (AvString, error) { + if at, ok := avTypes[t.Type]; ok { + return AvString { at, t.Val }, nil + } else { + err := fmt.Errorf("invalid token type: 0x%02x", byte(t.Type)) + return AvString { 0, "" }, err + } +} + +// Serialize as string. +func (s AvString) String() string { + switch s.Type { + case AnyString: + return "*" + case NaString: + return "-" + case ValString: + return s.Val + default: + // not sure what to return here + return "" + } +} diff --git a/cpe/avstring_test.go b/cpe/avstring_test.go new file mode 100644 index 0000000..8cbb14b --- /dev/null +++ b/cpe/avstring_test.go @@ -0,0 +1,95 @@ +package cpe + +import ( + "testing" +) + +func TestNewAvString(t *testing.T) { + passTests := []struct { + name string + val token + exp AvString + } { + { "any", token { Type: anyToken }, AvString { Type: AnyString } }, + { "na", token { Type: naToken }, AvString { Type: NaString } }, + { "empty", token { Type: valToken }, AvString { ValString, "" } }, + { "foo", token { valToken, "foo" }, AvString { ValString, "foo" } }, + } + + for _, test := range(passTests) { + t.Run(test.name, func(t *testing.T) { + got, err := newAvString(test.val) + if err != nil { + t.Error(err) + } else if got.Type != test.exp.Type { + t.Errorf("token: got %s, exp %s", got.Type, test.exp.Type) + } else if got.Type == ValString && got.Val != test.exp.Val { + t.Errorf("value: got \"%s\", exp \"%s\"", got.Val, test.exp.Val) + } + }) + } + + failTests := []struct { + name string + val token + exp string + } {{ + name: "invalid token", + val: token { Type: tokenType(127), Val: "foo" }, + exp: "invalid token type: 0x7f", + }} + + for _, test := range(failTests) { + t.Run(test.name, func(t *testing.T) { + got, err := newAvString(test.val) + if err == nil { + t.Errorf("got %v, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +func TestAvStringString(t *testing.T) { + tests := []struct { + name string + val AvString + exp string + } { + { "any", AvString { AnyString, "" }, "*" }, + { "na", AvString { NaString, "" }, "-" }, + { "foo", AvString { ValString, "foo" }, "foo" }, + { "junk", AvString { AvStringType(255), "foo" }, "" }, + } + + for _, test := range(tests) { + t.Run(test.name, func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("value: got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestAvStringTypeString(t *testing.T) { + tests := []struct { + val AvStringType + exp string + } { + { AnyString, "any" }, + { NaString, "na" }, + { ValString, "val" }, + { AvStringType(255), "AvStringType(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/cpe/avstringtype_string.go b/cpe/avstringtype_string.go new file mode 100644 index 0000000..6fb88e1 --- /dev/null +++ b/cpe/avstringtype_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -linecomment -type=AvStringType"; DO NOT EDIT. + +package cpe + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[AnyString-0] + _ = x[NaString-1] + _ = x[ValString-2] +} + +const _AvStringType_name = "anynaval" + +var _AvStringType_index = [...]uint8{0, 3, 5, 8} + +func (i AvStringType) String() string { + if i >= AvStringType(len(_AvStringType_index)-1) { + return "AvStringType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _AvStringType_name[_AvStringType_index[i]:_AvStringType_index[i+1]] +} diff --git a/cpe/cpe.go b/cpe/cpe.go new file mode 100644 index 0000000..dc38419 --- /dev/null +++ b/cpe/cpe.go @@ -0,0 +1,5 @@ +// CPE 2.3 formatted string parser. +// +// Source: NISTIR 7695, figure 6-3: +// https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf +package cpe diff --git a/cpe/part.go b/cpe/part.go new file mode 100644 index 0000000..ef91f7c --- /dev/null +++ b/cpe/part.go @@ -0,0 +1,41 @@ +package cpe + +//go:generate stringer -linecomment -type=Part + +import ( + "fmt" +) + +// CPE part +type Part byte + +const ( + ApplicationPart Part = 'a' // a + OperatingSystemPart Part = 'o' // o + HardwarePart Part = 'h' // h + AnyPart Part = '*' // * + NAPart Part = '-' // - +) + +// create new part from token +func newPart(t token) (Part, error) { + switch t.Type { + case anyToken: + return AnyPart, nil + case naToken: + return NAPart, nil + case valToken: + switch t.Val { + case "a": + return ApplicationPart, nil + case "o": + return OperatingSystemPart, nil + case "h": + return HardwarePart, nil + default: + return 0, fmt.Errorf("unknown part: \"%s\"", t.Val) + } + default: + return 0, fmt.Errorf("unknown token type: 0x%02x", byte(t.Type)) + } +} diff --git a/cpe/part_string.go b/cpe/part_string.go new file mode 100644 index 0000000..98b9fd3 --- /dev/null +++ b/cpe/part_string.go @@ -0,0 +1,41 @@ +// Code generated by "stringer -linecomment -type=Part"; DO NOT EDIT. + +package cpe + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ApplicationPart-97] + _ = x[OperatingSystemPart-111] + _ = x[HardwarePart-104] + _ = x[AnyPart-42] + _ = x[NAPart-45] +} + +const ( + _Part_name_0 = "*" + _Part_name_1 = "-" + _Part_name_2 = "a" + _Part_name_3 = "h" + _Part_name_4 = "o" +) + +func (i Part) String() string { + switch { + case i == 42: + return _Part_name_0 + case i == 45: + return _Part_name_1 + case i == 97: + return _Part_name_2 + case i == 104: + return _Part_name_3 + case i == 111: + return _Part_name_4 + default: + return "Part(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/cpe/part_test.go b/cpe/part_test.go new file mode 100644 index 0000000..269d16e --- /dev/null +++ b/cpe/part_test.go @@ -0,0 +1,108 @@ +package cpe + +import ( + "testing" +) + +func TestNewPart(t *testing.T) { + passTests := []struct { + name string + val token + exp Part + } {{ + name: "any", + val: token { Type: anyToken }, + exp: AnyPart, + }, { + name: "na", + val: token { Type: naToken }, + exp: NAPart, + }, { + name: "a", + val: token { Type: valToken, Val: "a" }, + exp: ApplicationPart, + }, { + name: "h", + val: token { Type: valToken, Val: "h" }, + exp: HardwarePart, + }, { + name: "o", + val: token { Type: valToken, Val: "o" }, + exp: OperatingSystemPart, + }} + + for _, test := range(passTests) { + t.Run(test.name, func(t *testing.T) { + if got, err := newPart(test.val); err != nil { + t.Error(err) + } else if got != test.exp { + t.Errorf("got %s, exp %s", got, test.exp) + } + }) + } + + failTests := []struct { + name string + val token + exp string + } {{ + name: "invalid token", + val: token { Type: valToken, Val: "foo" }, + exp: "unknown part: \"foo\"", + }, { + name: "empty token", + val: token { Type: valToken }, + exp: "unknown part: \"\"", + }, { + name: "unknown token type", + val: token { Type: tokenType(255) }, + exp: "unknown token type: 0xff", + }} + + for _, test := range(failTests) { + t.Run(test.name, func(t *testing.T) { + // tokenize, check for error + got, err := newPart(test.val) + if err == nil { + t.Errorf("got %v, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +func TestPartString(t *testing.T) { + tests := []struct { + val Part + exp string + } {{ + val: AnyPart, + exp: "*", + }, { + val: NAPart, + exp: "-", + }, { + val: ApplicationPart, + exp: "a", + }, { + val: HardwarePart, + exp: "h", + }, { + val: OperatingSystemPart, + exp: "o", + }, { + val: Part(255), + exp: "Part(255)", + }} + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/cpe/token.go b/cpe/token.go new file mode 100644 index 0000000..d88e773 --- /dev/null +++ b/cpe/token.go @@ -0,0 +1,99 @@ +package cpe + +//go:generate stringer -linecomment -type=tokenType + +import ( + "errors" + "fmt" +) + +// token type +type tokenType byte + +const ( + anyToken tokenType = iota // any + naToken // na + valToken // val +) + +// token +type token struct { + Type tokenType // token type + Val string // token value +} + +// parse buffer into token. +func newToken(val []byte) token { + if len(val) > 0 { + switch val[0] { + case '*': + return token { Type: anyToken } + case '-': + return token { Type: naToken } + default: + return token { Type: valToken, Val: string(val) } + } + } else { + // empty value + return token { Type: valToken } + } +} + +// unterminated escape error +var unterminatedEsc = errors.New("unterminated escape at end of buffer") + +// Parse buffer into list of tokens. +func tokenize(buf []byte) ([]token, error) { + // build result + var r []token + + // current token and escape state + var curr []byte + esc := false + + // build result + for _, b := range(buf) { + if esc { + switch b { + // valid escaped characters + case '\\', '*', '-', '!', '"', '#', '$', '%', '&', '\'', '(', ')', + '+', ',', '/', ':', ';', '<', '=', '>', '@', '[', ']', '^', '`', + '{', '|', '}', '~': + curr = append(curr, b) + esc = false + default: + return r, fmt.Errorf("invalid escape byte: 0x%02x", b) + } + } else { + switch b { + case '\\': + esc = true + case ':': + // push token, clear buffer + r = append(r, newToken(curr)) + curr = nil + case 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '-', '.', '_', '*', '?': + curr = append(curr, b) + default: + return r, fmt.Errorf("invalid byte: 0x%02x", b) + } + } + } + + // check for unterminated escape + if esc { + return r, unterminatedEsc + } + + if len(curr) > 0 { + // push token, clear buffer + r = append(r, newToken(curr)) + curr = nil + } + + // return success + return r, nil +} diff --git a/cpe/token_test.go b/cpe/token_test.go new file mode 100644 index 0000000..595df2c --- /dev/null +++ b/cpe/token_test.go @@ -0,0 +1,149 @@ +package cpe + +import ( + "reflect" + "testing" +) + +func TestTokenTypeString(t *testing.T) { + tests := []struct { + val tokenType + exp string + } { + { anyToken, "any" }, + { naToken, "na" }, + { valToken, "val" }, + { tokenType(255), "tokenType(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestNewToken(t *testing.T) { + passTests := []struct { + name string + val string + exp token + } { + { "any", "*", token { Type: anyToken } }, + { "na", "-", token { Type: naToken } }, + { "empty", "", token { Type: valToken } }, + { "foo", "foo", token { Type: valToken, Val: "foo" } }, + } + + for _, test := range(passTests) { + t.Run(test.name, func(t *testing.T) { + got := newToken([]byte(test.val)) + if got.Type != test.exp.Type { + t.Errorf("token: got %s, exp %s", got.Type, test.exp.Type) + } else if got.Type == valToken && got.Val != test.exp.Val { + t.Errorf("value: got \"%s\", exp \"%s\"", got.Val, test.exp.Val) + } + }) + } +} + +func TestTokenize(t *testing.T) { + passTests := []struct { + val string + exp []token + } {{ + val: "foo", + exp: []token { token { Type: valToken, Val: "foo" } }, + }, { + val: "foo:bar", + exp: []token { + token { Type: valToken, Val: "foo" }, + token { Type: valToken, Val: "bar" }, + }, + }, { + val: "*", + exp: []token { token { Type: anyToken } }, + }, { + val: "-", + exp: []token { token { Type: naToken } }, + }, { + val: "*:bar", + exp: []token { + token { Type: anyToken }, + token { Type: valToken, Val: "bar" }, + }, + }, { + val: "foo:*", + exp: []token { + token { Type: valToken, Val: "foo" }, + token { Type: anyToken }, + }, + }, { + val: "-:bar", + exp: []token { + token { Type: naToken }, + token { Type: valToken, Val: "bar" }, + }, + }, { + val: "foo:-", + exp: []token { + token { Type: valToken, Val: "foo" }, + token { Type: naToken }, + }, + }, { + val: "foo\\*:-", + exp: []token { + token { Type: valToken, Val: "foo*" }, + token { Type: naToken }, + }, + }} + + for _, test := range(passTests) { + t.Run(test.val, func(t *testing.T) { + // tokenize, check for error + got, err := tokenize([]byte(test.val)) + if err != nil { + t.Error(err) + return + } + + if !reflect.DeepEqual(got, test.exp) { + t.Errorf("token: got %v, exp %v", got, test.exp) + return + } + }) + } + + failTests := []struct { + id string + val string + exp string + } {{ + id: "invalid escape", + val: "foo\\.", + exp: "invalid escape byte: 0x2e", + }, { + id: "invalid byte", + val: "\n", + exp: "invalid byte: 0x0a", + }, { + id: "unterminated escape", + val: "\\", + exp: "unterminated escape at end of buffer", + }} + + for _, test := range(failTests) { + t.Run(test.id, func(t *testing.T) { + // tokenize, check for error + got, err := tokenize([]byte(test.val)) + if err == nil { + t.Errorf("got %v, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} diff --git a/cpe/tokentype_string.go b/cpe/tokentype_string.go new file mode 100644 index 0000000..7b53758 --- /dev/null +++ b/cpe/tokentype_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -linecomment -type=tokenType"; DO NOT EDIT. + +package cpe + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[anyToken-0] + _ = x[naToken-1] + _ = x[valToken-2] +} + +const _tokenType_name = "anynaval" + +var _tokenType_index = [...]uint8{0, 3, 5, 8} + +func (i tokenType) String() string { + if i >= tokenType(len(_tokenType_index)-1) { + return "tokenType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _tokenType_name[_tokenType_index[i]:_tokenType_index[i+1]] +} diff --git a/cpe/v23binding.go b/cpe/v23binding.go new file mode 100644 index 0000000..24fb7b2 --- /dev/null +++ b/cpe/v23binding.go @@ -0,0 +1,119 @@ +package cpe + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +// CPE 2.3 binding. +type V23Binding struct { + Part Part // Part attribute (NISTIR7695 5.3.3.1). + Vendor AvString // Vendor attribute (NISTIR7695 5.3.3.2). + Product AvString // Product attribute (NISTIR7695 5.3.3.3). + Version AvString // Version attribute (NISTIR7695 5.3.3.4). + Update AvString // Update attribute (NISTIR7695 5.3.3.5). + Edition AvString // Edition attribute (NISTIR7695 5.3.3.6). + SwEdition AvString // Software edition attribute (NISTIR7695 5.3.3.7). + TargetSw AvString // Target software attribute (NISTIR7695 5.3.3.8). + TargetHw AvString // Target hardware attribute (NISTIR7695 5.3.3.9). + Lang AvString // Language attribute (NISTIR7695 5.3.3.10). + Other AvString // Other attribute (NISTIR7695 5.3.3.11). +} + +// formatted string prefix +var cpe23Prefix = "cpe:2.3:" + +// missing prefix error +var missingPrefix = errors.New("missing CPE 2.3 prefix") + +// Create binding from CPE 2.3 formatted string. +func NewV23Binding(s string) (V23Binding, error) { + // check prefix + if s[0:len(cpe23Prefix)] != cpe23Prefix { + return V23Binding{}, missingPrefix + } + + // tokenize string, check for error + toks, err := tokenize([]byte(s[len(cpe23Prefix):])) + if err != nil { + return V23Binding{}, err + } + + // check token count + if len(toks) != 11 { + err = fmt.Errorf("invalid attribute count: %d != 11", len(toks)) + return V23Binding{}, err + } + + // create part + part, err := newPart(toks[0]) + if err != nil { + return V23Binding{}, err + } + + // parse tokens into strings + strs := make([]AvString, len(toks) - 1) + for i, t := range(toks[1:]) { + if strs[i], err = newAvString(t); err != nil { + return V23Binding{}, err + } + } + + // build and return result + return V23Binding { + Part: part, + Vendor: strs[0], + Product: strs[1], + Version: strs[2], + Update: strs[3], + Edition: strs[4], + Lang: strs[5], + SwEdition: strs[6], + TargetSw: strs[7], + TargetHw: strs[8], + Other: strs[9], + }, nil +} + +// Serialize CPE 2.3 binding as formatted string. +func (v V23Binding) String() string { + return cpe23Prefix + strings.Join([]string { + v.Part.String(), + v.Vendor.String(), + v.Product.String(), + v.Version.String(), + v.Update.String(), + v.Edition.String(), + v.Lang.String(), + v.SwEdition.String(), + v.TargetSw.String(), + v.TargetHw.String(), + v.Other.String(), + }, ":") +} + +// Unmarshal CPE 2.3 binding from JSON string. +func (me *V23Binding) UnmarshalJSON(b []byte) error { + // decode json string + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // create binding + binding, err := NewV23Binding(s) + if err != nil { + return err + } + + // save result, return success + *me = binding + return nil +} + +// Marshal CPE binding as JSON string. +func (v V23Binding) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} diff --git a/cpe/v23binding_test.go b/cpe/v23binding_test.go new file mode 100644 index 0000000..4fa46bb --- /dev/null +++ b/cpe/v23binding_test.go @@ -0,0 +1,132 @@ +package cpe + +import ( + "encoding/json" + "testing" +) + +func TestNewV23Binding(t *testing.T) { + passTests := []string { + "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", + } + + for _, val := range(passTests) { + t.Run(val, func(t *testing.T) { + if _, err := NewV23Binding(val); err != nil { + t.Error(err) + } + }) + } + + failTests := []struct { + val string + exp string + } {{ + val: "o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", + exp: "missing CPE 2.3 prefix", + }, { + val: "cpe:2.3:\n", + exp: "invalid byte: 0x0a", + }, { + val: "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*", + exp: "invalid attribute count: 10 != 11", + }, { + val: "cpe:2.3:foo:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", + exp: "unknown part: \"foo\"", + }} + + for _, test := range(failTests) { + t.Run(test.val, func(t *testing.T) { + got, err := NewV23Binding(test.val) + if err == nil { + t.Errorf("got %v, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +func TestV23BindingString(t *testing.T) { + tests := []string { + "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", + } + + for _, val := range(tests) { + t.Run(val, func(t *testing.T) { + if got, err := NewV23Binding(val); err != nil { + t.Error(err) + } else if got.String() != val { + t.Errorf("got \"%s\", exp \"%s\"", got.String(), val) + } + }) + } +} + +func TestV23BindingUnmarshalJSON(t *testing.T) { + passTests := []string { + `"cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*"`, + } + + for _, val := range(passTests) { + t.Run(val, func(t *testing.T) { + var b V23Binding + if err := json.Unmarshal([]byte(val), &b); err != nil { + t.Error(err) + } + }) + } + + failTests := []struct { + val string + exp string + } {{ + val: `"o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*`, + exp: "unexpected end of JSON input", + }, { + val: `"o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*"`, + exp: "missing CPE 2.3 prefix", + }} + + for _, test := range(failTests) { + t.Run(test.val, func(t *testing.T) { + var got V23Binding + if err := json.Unmarshal([]byte(test.val), &got); err == nil { + t.Errorf("got %v, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +func TestV23BindingMarshalJSON(t *testing.T) { + tests := []string { + "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", + } + + for _, val := range(tests) { + t.Run(val, func(t *testing.T) { + // create binding, check for error + b, err := NewV23Binding(val) + if err != nil { + t.Error(err) + return + } + + // marshal json, check for error + got, err := json.Marshal(b) + if err != nil { + t.Error(err) + return + } + + // build/check expected value + exp := "\"" + val + "\"" + if string(got) != exp { + t.Errorf("got \"%s\", exp \"%s\"", string(got), exp) + return + } + }) + } +} diff --git a/cpedict/cpedict.go b/cpedict/cpedict.go new file mode 100644 index 0000000..0b5f77b --- /dev/null +++ b/cpedict/cpedict.go @@ -0,0 +1,46 @@ +// CPE 2.3 dictionary parser. +// +// The official NVD CPE dictionary is available here: +// https://nvd.nist.gov/products/cpe +package cpedict + +import "time" + +// Dictionary generator information. +type Generator struct { + ProductName string `xml:"product_name"` // Product name. + ProductVersion string `xml:"product_version"` // Product version. + SchemaVersion string `xml:"schema_version"` // Schema version. + Timestamp time.Time `xml:"timestamp"` // Generation timestamp. +} + +// Dictionary item title. +type Title struct { + Lang string `xml:"lang,attr"` // language code + Text string `xml:",chardata"` // value +} + +// Dictionary item reference. +type Reference struct { + Href string `xml:"href,attr"` // Link + Text string `xml:",chardata"` // Text +} + +// CPE 2.3 item attributes. +type Cpe23Item struct { + Name string `xml:"name,attr"` // CPE 2.3 formatting string. +} + +// Dictionary item. +type Item struct { + CpeUri string `xml:"name,attr"` // CPE URI. + Cpe23Item Cpe23Item `xml:"cpe23-item"` // CPE 2.3 formatting string. + Titles []Title `xml:"title"` // Item titles. + References []Reference `xml:"references>reference"` // References. +} + +// CPE dictionary. +type Dictionary struct { + Generator Generator `xml:"generator"` // Dictionary generator. + Items []Item `xml:"cpe-item"` // Dictionary items. +} diff --git a/cpedict/cpedict_test.go b/cpedict/cpedict_test.go new file mode 100644 index 0000000..eca0aeb --- /dev/null +++ b/cpedict/cpedict_test.go @@ -0,0 +1,136 @@ +package cpedict + +import ( + "compress/gzip" + "encoding/xml" + "os" + "testing" + "time" + "reflect" +) + +func TestDictionaryXMLUnmarshal(t *testing.T) { + // open test data + f, err := os.Open("testdata/test-0.xml.gz") + if err != nil { + t.Error(err) + return + } + defer f.Close() + + // create gzip reader + gz, err := gzip.NewReader(f) + if err != nil { + t.Error(err) + return + } + defer gz.Close() + + // create xml decoder, decode xml + d := xml.NewDecoder(gz) + var dict Dictionary + + if err := d.Decode(&dict); err != nil { + t.Error(err) + return + } + + // build expected time + var expTime time.Time + if err := expTime.UnmarshalText([]byte("2022-02-02T04:51:00.437Z")); err != nil { + t.Error(err) + return + } + + // expected generator + expGenerator := Generator { + ProductName: "National Vulnerability Database (NVD)", + ProductVersion: "4.9", + SchemaVersion: "2.3", + Timestamp: expTime, + } + + // compare generator + if !reflect.DeepEqual(dict.Generator, expGenerator) { + t.Errorf("got \"%v\", exp \"%v\"", dict.Generator, expGenerator) + return + } + + // check item count + gotNumItems := len(dict.Items) + expNumItems := 19 + if gotNumItems != expNumItems { + t.Errorf("item count: got %d, exp %d", gotNumItems, expNumItems) + return + } + + // build expected item + expItems := []Item { + // first item in the list + Item { + CpeUri: "cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~", + + Cpe23Item: Cpe23Item { + Name: "cpe:2.3:a:\\$0.99_kindle_books_project:\\$0.99_kindle_books:6:*:*:*:*:android:*:*", + }, + + Titles: []Title { + Title { + Lang: "en-US", + Text: "$0.99 Kindle Books project $0.99 Kindle Books (aka com.kindle.books.for99) for android 6.0", + }, + }, + + References: []Reference { + Reference { + Href: "https://play.google.com/store/apps/details?id=com.kindle.books.for99", + Text: "Product information", + }, + + Reference { + Href: "https://docs.google.com/spreadsheets/d/1t5GXwjw82SyunALVJb2w0zi3FoLRIkfGPc7AMjRF0r4/edit?pli=1#gid=1053404143", + Text: "Government Advisory", + }, + }, + }, + + // last item in the list + Item { + CpeUri: "cpe:/a:3com:3c16116-us:2.0", + + Cpe23Item: Cpe23Item { + Name: "cpe:2.3:a:3com:3c16116-us:2.0:*:*:*:*:*:*:*", + }, + + Titles: []Title { + Title { + Lang: "ja-JP", + Text: "スリーコム WebCache 3000 2.0", + }, + + Title { + Lang: "en-US", + Text: "3Com WebCache 3000 2.0", + }, + }, + }, + } + + // build item comparisons + compares := []struct { + name string + exp Item + got Item + } { + { "head", expItems[0], dict.Items[0] }, + { "tail", expItems[1], dict.Items[len(dict.Items) - 1] }, + } + + for _, row := range(compares) { + t.Run(row.name, func(t *testing.T) { + if !reflect.DeepEqual(row.got, row.exp) { + t.Errorf("got \"%v\", exp \"%v\"", row.got, row.exp) + } + }) + } +} diff --git a/cpedict/testdata/test-0.xml.gz b/cpedict/testdata/test-0.xml.gz new file mode 100644 index 0000000..110e965 Binary files /dev/null and b/cpedict/testdata/test-0.xml.gz differ diff --git a/cpematch/cpematch.go b/cpematch/cpematch.go new file mode 100644 index 0000000..0bae622 --- /dev/null +++ b/cpematch/cpematch.go @@ -0,0 +1,31 @@ +// CPE match feed 1.0 parser. +// +// Latest CPE match feed available from here: +// https://nvd.nist.gov/vuln/data-feeds#cpeMatch +// +// JSON schema available here: +// https://csrc.nist.gov/schema/cpematch/feed/1.0/nvd_cpematch_feed_json_1.0.schema +package cpematch + +// CPE name +type Name struct { + Cpe22Uri string `json:"cpe22Uri"` + Cpe23Uri string `json:"cpe23Uri"` +} + +// CPE match string or range +type Match struct { + Vulnerable *bool `json:"vulnerable"` + Cpe22Uri string `json:"cpe22Uri"` + Cpe23Uri string `json:"cpe23Uri"` + VersionStartExcluding string `json:"versionStartExcluding"` + VersionStartIncluding string `json:"versionStartIncluding"` + VersionEndExcluding string `json:"versionEndExcluding"` + VersionEndIncluding string `json:"versionEndIncluding"` + Names []Name `json:"cpe_name"` // CPE match strings +} + +// CPE matches +type Matches struct { + Matches []Match `json:"matches"` // Array of CPE matches +} diff --git a/cpematch/cpematch_test.go b/cpematch/cpematch_test.go new file mode 100644 index 0000000..7d69410 --- /dev/null +++ b/cpematch/cpematch_test.go @@ -0,0 +1,100 @@ +package cpematch + +import ( + "compress/gzip" + "encoding/json" + "os" + "reflect" + "testing" +) + +func TestMatchesUnmarshal(t *testing.T) { + // expected data + exp := Matches { + Matches: []Match { + Match { + Cpe23Uri: "cpe:2.3:a:101_project:101:*:*:*:*:*:node.js:*:*", + VersionStartIncluding: "1.0.0", + VersionEndIncluding: "1.6.3", + Names: []Name { + Name { + Cpe23Uri: "cpe:2.3:a:101_project:101:1.0.0:*:*:*:*:node.js:*:*", + }, + + Name { + Cpe23Uri: "cpe:2.3:a:101_project:101:1.1.0:*:*:*:*:node.js:*:*", + }, + + Name { + Cpe23Uri: "cpe:2.3:a:101_project:101:1.1.1:*:*:*:*:node.js:*:*", + }, + }, + }, + + Match { + Cpe23Uri: "cpe:2.3:a:1password:1password:*:*:*:*:*:macos:*:*", + VersionStartIncluding: "7.7.0", + VersionEndExcluding: "7.8.7", + Names: []Name { + Name { + Cpe23Uri: "cpe:2.3:a:1password:1password:7.7.0:*:*:*:*:macos:*:*", + }, + }, + }, + + Match { + Cpe23Uri: "cpe:2.3:a:zimbra:collaboration:*:*:*:*:*:*:*:*", + VersionStartExcluding: "8.8.0", + VersionEndExcluding: "8.8.15", + Names: []Name { + Name { + Cpe23Uri: "cpe:2.3:a:zimbra:collaboration:8.8.6:*:*:*:*:*:*:*", + }, + + Name { + Cpe23Uri: "cpe:2.3:a:zimbra:collaboration:8.8.7:*:*:*:*:*:*:*", + }, + + Name { + Cpe23Uri: "cpe:2.3:a:zimbra:collaboration:8.8.8:-:*:*:*:*:*:*", + }, + + Name { + Cpe23Uri: "cpe:2.3:a:zimbra:collaboration:8.8.8:p1:*:*:*:*:*:*", + }, + }, + }, + }, + } + + // open test data + f, err := os.Open("testdata/test-0.json.gz") + if err != nil { + t.Error(err) + return + } + defer f.Close() + + // create gzip reader + gz, err := gzip.NewReader(f) + if err != nil { + t.Error(err) + return + } + defer gz.Close() + + // create json decoder + d := json.NewDecoder(gz) + var got Matches + + // decode match data, check for error + if err := d.Decode(&got); err != nil { + t.Error(err) + return + } + + // check for match + if !reflect.DeepEqual(got, exp) { + t.Errorf("got \"%v\", exp \"%v\"", got, exp) + } +} diff --git a/cpematch/testdata/test-0.json.gz b/cpematch/testdata/test-0.json.gz new file mode 100644 index 0000000..611bd58 Binary files /dev/null and b/cpematch/testdata/test-0.json.gz differ diff --git a/cvss/badkey.go b/cvss/badkey.go new file mode 100644 index 0000000..d4bc845 --- /dev/null +++ b/cvss/badkey.go @@ -0,0 +1,20 @@ +package cvss + +// disabled for now (unused) +// import "fmt" +// +// // Bad metric key error. +// type badKey struct { +// version Version +// key string +// } +// +// // Create new bad key error. +// func newBadKey(version Version, key string) error { +// return &badKey { version, key } +// } +// +// // Return printable error string +// func (e badKey) Error() string { +// return fmt.Sprintf("invalid CVSS %s metric key: %s", e.version, e.key) +// } diff --git a/cvss/badmetric.go b/cvss/badmetric.go new file mode 100644 index 0000000..8f0bf5f --- /dev/null +++ b/cvss/badmetric.go @@ -0,0 +1,19 @@ +package cvss + +import "fmt" + +// Bad metric error. +type badMetric struct { + version Version // CVSS version + val string // metric value +} + +// Create new bad key error. +func newBadMetric(version Version, val string) error { + return &badMetric { version, val } +} + +// Return printable error string. +func (e badMetric) Error() string { + return fmt.Sprintf("invalid CVSS %s metric: %s", e.version, e.val) +} diff --git a/cvss/badmetric_test.go b/cvss/badmetric_test.go new file mode 100644 index 0000000..87316c0 --- /dev/null +++ b/cvss/badmetric_test.go @@ -0,0 +1,25 @@ +package cvss + +import "testing" + +func TestNewBadMetric(t *testing.T) { + tests := []struct { + name string + version Version + val string + exp string + } { + { "v20-foo", V20, "foo", "invalid CVSS 2.0 metric: foo" }, + { "v30-foo", V30, "foo", "invalid CVSS 3.0 metric: foo" }, + { "v31-foo", V31, "foo", "invalid CVSS 3.1 metric: foo" }, + } + + for _, test := range(tests) { + t.Run(test.name, func(t *testing.T) { + err := newBadMetric(test.version, test.val) + if err.Error() != test.exp { + t.Errorf("got: %s, exp: %s", err.Error(), test.exp) + } + }) + } +} diff --git a/cvss/category.go b/cvss/category.go new file mode 100644 index 0000000..54781dc --- /dev/null +++ b/cvss/category.go @@ -0,0 +1,13 @@ +package cvss + +//go:generate stringer -linecomment -type=Category + +// CVSS metric category. +type Category byte + +const ( + Base Category = iota // Base + Temporal // Temporal + Environmental // Environmental + InvalidCategory // invalid +) diff --git a/cvss/category_string.go b/cvss/category_string.go new file mode 100644 index 0000000..782f8fb --- /dev/null +++ b/cvss/category_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -linecomment -type=Category"; DO NOT EDIT. + +package cvss + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Base-0] + _ = x[Temporal-1] + _ = x[Environmental-2] + _ = x[InvalidCategory-3] +} + +const _Category_name = "BaseTemporalEnvironmentalinvalid" + +var _Category_index = [...]uint8{0, 4, 12, 25, 32} + +func (i Category) String() string { + if i >= Category(len(_Category_index)-1) { + return "Category(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Category_name[_Category_index[i]:_Category_index[i+1]] +} diff --git a/cvss/category_test.go b/cvss/category_test.go new file mode 100644 index 0000000..7f69689 --- /dev/null +++ b/cvss/category_test.go @@ -0,0 +1,41 @@ +package cvss + +import "testing" + +func TestCategoryString(t *testing.T) { + tests := []struct { + cat Category + exp string + } { + { Base, "Base" }, + { Temporal, "Temporal" }, + { Environmental, "Environmental" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.cat.String() + if got != test.exp { + t.Errorf("got: %s, exp: %s", got, test.exp) + } + }) + } +} + +func TestInvalidCategory(t *testing.T) { + tests := []struct { + cat Category + exp string + } { + { Category(byte(255)), "Category(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.cat.String() + if got != test.exp { + t.Errorf("got: %s, exp: %s", got, test.exp) + } + }) + } +} diff --git a/cvss/cvss.go b/cvss/cvss.go new file mode 100644 index 0000000..2bae0d7 --- /dev/null +++ b/cvss/cvss.go @@ -0,0 +1,23 @@ +// CVSS vector parser. +package cvss + +// Metric key. +type Key interface { + // Get full name. + Name() string + + // Get category. + Category() Category + + // Return string representation. + String() string +} + +// CVSS metric. +type Metric interface { + // Get metric key. + Key() Key + + // Return string representation of metric. + String() string +} diff --git a/cvss/v2key.go b/cvss/v2key.go new file mode 100644 index 0000000..740d26d --- /dev/null +++ b/cvss/v2key.go @@ -0,0 +1,92 @@ +package cvss + +//go:generate stringer -linecomment -type=v2Key + +// CVSS 2.0 metric key. +type v2Key byte + +const ( + v2AccessVector v2Key = iota // AV + v2AccessComplexity // AC + v2Authentication // Au + v2ConfidentialityImpact // C + v2IntegrityImpact // I + v2AvailabilityImpact // A + v2Exploitability // E + v2RemediationLevel // RL + v2ReportConfidence // RC + v2CollateralDamagePotential // CDP + v2TargetDistribution // TD + v2ConfidentialityRequirement // CR + v2IntegrityRequirement // IR + v2AvailabilityRequirement // AR + + v2InvalidKey // invalid +) + +// CVSS V2 metric key info lut +var v2Keys = map[v2Key]struct { + Name string + Category Category +} { + v2AccessVector: { "Access Vector", Base }, + v2AccessComplexity: { "Access Complexity", Base }, + v2Authentication: { "Authentication", Base }, + v2ConfidentialityImpact: { "Confidentiality Impact", Base }, + v2IntegrityImpact: { "Integrity Impact", Base }, + v2AvailabilityImpact: { "Availability Impact", Base }, + v2Exploitability: { "Exploitability", Temporal }, + v2RemediationLevel: { "Remediation Level", Temporal }, + v2ReportConfidence: { "Report Confidence", Temporal }, + v2CollateralDamagePotential: { "Collateral Damage Potential", Environmental }, + v2TargetDistribution: { "Target Distribution", Environmental }, + v2ConfidentialityRequirement: { "Confidentiality Requirement", Environmental }, + v2IntegrityRequirement: { "Integrity Requirement", Environmental }, + v2AvailabilityRequirement: { "Availability Requirement", Environmental }, +} + +// // v2 metric key IDs lut +// var v2KeyIds = map[string]v2Key { +// "AV": v2AccessVector, +// "AC": v2AccessComplexity, +// "Au": v2Authentication, +// "C": v2ConfidentialityImpact, +// "I": v2IntegrityImpact, +// "A": v2AvailabilityImpact, +// "E": v2Exploitability, +// "RL": v2RemediationLevel, +// "RC": v2ReportConfidence, +// "CDP": v2CollateralDamagePotential, +// "TD": v2TargetDistribution, +// "CR": v2ConfidentialityRequirement, +// "IR": v2IntegrityRequirement, +// "AR": v2AvailabilityRequirement, +// } +// +// // Get metric key from string. +// func getV2KeyFromString(s string) (v2Key, error) { +// k, ok := v2KeyIds[s] +// if ok { +// return k, nil +// } else { +// return v2InvalidKey, newBadKey(V20, s) +// } +// } + +// Get metric key name. +func (k v2Key) Name() string { + if data, ok := v2Keys[k]; ok { + return data.Name + } else { + return "invalid" + } +} + +// Get metric key category. +func (k v2Key) Category() Category { + if data, ok := v2Keys[k]; ok { + return data.Category + } else { + return InvalidCategory + } +} diff --git a/cvss/v2key_string.go b/cvss/v2key_string.go new file mode 100644 index 0000000..8e945fd --- /dev/null +++ b/cvss/v2key_string.go @@ -0,0 +1,37 @@ +// Code generated by "stringer -linecomment -type=v2Key"; DO NOT EDIT. + +package cvss + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[v2AccessVector-0] + _ = x[v2AccessComplexity-1] + _ = x[v2Authentication-2] + _ = x[v2ConfidentialityImpact-3] + _ = x[v2IntegrityImpact-4] + _ = x[v2AvailabilityImpact-5] + _ = x[v2Exploitability-6] + _ = x[v2RemediationLevel-7] + _ = x[v2ReportConfidence-8] + _ = x[v2CollateralDamagePotential-9] + _ = x[v2TargetDistribution-10] + _ = x[v2ConfidentialityRequirement-11] + _ = x[v2IntegrityRequirement-12] + _ = x[v2AvailabilityRequirement-13] + _ = x[v2InvalidKey-14] +} + +const _v2Key_name = "AVACAuCIAERLRCCDPTDCRIRARinvalid" + +var _v2Key_index = [...]uint8{0, 2, 4, 6, 7, 8, 9, 10, 12, 14, 17, 19, 21, 23, 25, 32} + +func (i v2Key) String() string { + if i >= v2Key(len(_v2Key_index)-1) { + return "v2Key(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _v2Key_name[_v2Key_index[i]:_v2Key_index[i+1]] +} diff --git a/cvss/v2key_test.go b/cvss/v2key_test.go new file mode 100644 index 0000000..a54f4e7 --- /dev/null +++ b/cvss/v2key_test.go @@ -0,0 +1,116 @@ +package cvss + +import "testing" + +func TestV2KeyString(t *testing.T) { + tests := []struct { + key v2Key + exp string + } { + { v2AccessVector, "AV" }, + { v2AccessComplexity, "AC" }, + { v2Authentication, "Au" }, + { v2ConfidentialityImpact, "C" }, + { v2IntegrityImpact, "I" }, + { v2AvailabilityImpact, "A" }, + { v2Exploitability, "E" }, + { v2RemediationLevel, "RL" }, + { v2ReportConfidence, "RC" }, + { v2CollateralDamagePotential, "CDP" }, + { v2TargetDistribution, "TD" }, + { v2ConfidentialityRequirement, "CR" }, + { v2IntegrityRequirement, "IR" }, + { v2AvailabilityRequirement, "AR" }, + + { v2Key(255), "v2Key(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.key.String() + if got != test.exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) + } + }) + } +} + +func TestV2KeyName(t *testing.T) { + tests := []struct { + key v2Key + exp string + } { + { v2AccessVector, "Access Vector" }, + { v2AccessComplexity, "Access Complexity" }, + { v2Authentication, "Authentication" }, + { v2ConfidentialityImpact, "Confidentiality Impact" }, + { v2IntegrityImpact, "Integrity Impact" }, + { v2AvailabilityImpact, "Availability Impact" }, + { v2Exploitability, "Exploitability" }, + { v2RemediationLevel, "Remediation Level" }, + { v2ReportConfidence, "Report Confidence" }, + { v2CollateralDamagePotential, "Collateral Damage Potential" }, + { v2TargetDistribution, "Target Distribution" }, + { v2ConfidentialityRequirement, "Confidentiality Requirement" }, + { v2IntegrityRequirement, "Integrity Requirement" }, + { v2AvailabilityRequirement, "Availability Requirement" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.key.Name() + if got != test.exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) + } + }) + } +} + +func TestV2KeyCategory(t *testing.T) { + tests := []struct { + key v2Key + exp Category + } { + { v2AccessVector, Base }, + { v2AccessComplexity, Base }, + { v2Authentication, Base }, + { v2ConfidentialityImpact, Base }, + { v2IntegrityImpact, Base }, + { v2AvailabilityImpact, Base }, + { v2Exploitability, Temporal }, + { v2RemediationLevel, Temporal }, + { v2ReportConfidence, Temporal }, + { v2CollateralDamagePotential, Environmental }, + { v2TargetDistribution, Environmental}, + { v2ConfidentialityRequirement, Environmental}, + { v2IntegrityRequirement, Environmental}, + { v2AvailabilityRequirement, Environmental}, + } + + for _, test := range(tests) { + t.Run(test.key.String(), func(t *testing.T) { + got := test.key.Category() + if got != test.exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) + } + }) + } +} + +func TestInvalidV2KeyName(t *testing.T) { + exp := "invalid" + got := v2Key(255).Name() + + if got != exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) + } +} + +func TestInvalidV2KeyCategory(t *testing.T) { + exp := InvalidCategory + got := v2Key(255).Category() + + if got != exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) + } +} diff --git a/cvss/v2metric.go b/cvss/v2metric.go new file mode 100644 index 0000000..2dee41a --- /dev/null +++ b/cvss/v2metric.go @@ -0,0 +1,244 @@ +package cvss + +//go:generate stringer -linecomment -type=v2Metric + +// CVSS v2 metric value +type v2Metric byte + +const ( + v2AVNetwork v2Metric = iota // AV:N + v2AVAdjacentNetwork // AV:A + v2AVLocal // AV:L + + v2ACLow // AC:L + v2ACMedium // AC:M + v2ACHigh // AC:H + + v2AuMultiple // Au:M + v2AuSingle // Au:S + v2AuNone // Au:N + + v2CNone // C:N + v2CPartial // C:P + v2CComplete // C:C + + v2INone // I:N + v2IPartial // I:P + v2IComplete // I:C + + v2ANone // A:N + v2APartial // A:P + v2AComplete // A:C + + v2ENotDefined // E:ND + v2EUnproven // E:U + v2EProofOfConcept // E:POC + v2EFunctional // E:F + v2EHigh // E:H + + v2RLOfficialFix // RL:OF + v2RLTemporaryFix // RL:TF + v2RLWorkaround // RL:W + v2RLUnavailable // RL:U + v2RLNotDefined // RL:ND + + v2RCUnconfirmed // RC:UC + v2RCUncorroborated // RC:UR + v2RCConfirmed // RC:C + v2RCNotDefined // RC:ND + + v2CDPNone // CDP:N + v2CDPLow // CDP:L + v2CDPLowMedium // CDP:LM + v2CDPMediumHigh // CDP:MH + v2CDPHigh // CDP:H + v2CDPNotDefined // CDP:ND + + v2TDNone // TD:N + v2TDLow // TD:L + v2TDMedium // TD:M + v2TDHigh // TD:H + v2TDNotDefined // TD:ND + + v2CRLow // CR:L + v2CRMedium // CR:M + v2CRHigh // CR:H + v2CRNotDefined // CR:ND + + v2IRLow // IR:L + v2IRMedium // IR:M + v2IRHigh // IR:H + v2IRNotDefined // IR:ND + + v2ARLow // AR:L + v2ARMedium // AR:M + v2ARHigh // AR:H + v2ARNotDefined // AR:ND + + v2InvalidMetric // invalid +) + +// map of metrics to metric keys +var v2KeyLut = map[v2Metric]v2Key { + v2AVNetwork: v2AccessVector, + v2AVAdjacentNetwork: v2AccessVector, + v2AVLocal: v2AccessVector, + + v2ACLow: v2AccessComplexity, + v2ACMedium: v2AccessComplexity, + v2ACHigh: v2AccessComplexity, + + v2AuMultiple: v2Authentication, + v2AuSingle: v2Authentication, + v2AuNone: v2Authentication, + + v2CNone: v2ConfidentialityImpact, + v2CPartial: v2ConfidentialityImpact, + v2CComplete: v2ConfidentialityImpact, + + v2INone: v2IntegrityImpact, + v2IPartial: v2IntegrityImpact, + v2IComplete: v2IntegrityImpact, + + v2ANone: v2AvailabilityImpact, + v2APartial: v2AvailabilityImpact, + v2AComplete: v2AvailabilityImpact, + + v2ENotDefined: v2Exploitability, + v2EUnproven: v2Exploitability, + v2EProofOfConcept: v2Exploitability, + v2EFunctional: v2Exploitability, + v2EHigh: v2Exploitability, + + v2RLOfficialFix: v2RemediationLevel, + v2RLTemporaryFix: v2RemediationLevel, + v2RLWorkaround: v2RemediationLevel, + v2RLUnavailable: v2RemediationLevel, + v2RLNotDefined: v2RemediationLevel, + + v2RCUnconfirmed: v2ReportConfidence, + v2RCUncorroborated: v2ReportConfidence, + v2RCConfirmed: v2ReportConfidence, + v2RCNotDefined: v2ReportConfidence, + + v2CDPNone: v2CollateralDamagePotential, + v2CDPLow: v2CollateralDamagePotential, + v2CDPLowMedium: v2CollateralDamagePotential, + v2CDPMediumHigh: v2CollateralDamagePotential, + v2CDPHigh: v2CollateralDamagePotential, + v2CDPNotDefined: v2CollateralDamagePotential, + + v2TDNone: v2TargetDistribution, + v2TDLow: v2TargetDistribution, + v2TDMedium: v2TargetDistribution, + v2TDHigh: v2TargetDistribution, + v2TDNotDefined: v2TargetDistribution, + + v2CRLow: v2ConfidentialityRequirement, + v2CRMedium: v2ConfidentialityRequirement, + v2CRHigh: v2ConfidentialityRequirement, + v2CRNotDefined: v2ConfidentialityRequirement, + + v2IRLow: v2IntegrityRequirement, + v2IRMedium: v2IntegrityRequirement, + v2IRHigh: v2IntegrityRequirement, + v2IRNotDefined: v2IntegrityRequirement, + + v2ARLow: v2AvailabilityRequirement, + v2ARMedium: v2AvailabilityRequirement, + v2ARHigh: v2AvailabilityRequirement, + v2ARNotDefined: v2AvailabilityRequirement, +} + +// map of metric strings to metrics +var v2MetricStrLut = map[string]v2Metric { + "AV:N": v2AVNetwork, + "AV:A": v2AVAdjacentNetwork, + "AV:L": v2AVLocal, + + "AC:L": v2ACLow, + "AC:M": v2ACMedium, + "AC:H": v2ACHigh, + + "Au:M": v2AuMultiple, + "Au:S": v2AuSingle, + "Au:N": v2AuNone, + + "C:N": v2CNone, + "C:P": v2CPartial, + "C:C": v2CComplete, + + "I:N": v2INone, + "I:P": v2IPartial, + "I:C": v2IComplete, + + "A:N": v2ANone, + "A:P": v2APartial, + "A:C": v2AComplete, + + "E:ND": v2ENotDefined, + "E:U": v2EUnproven, + "E:POC": v2EProofOfConcept, + "E:F": v2EFunctional, + "E:H": v2EHigh, + + "RL:OF": v2RLOfficialFix, + "RL:TF": v2RLTemporaryFix, + "RL:W": v2RLWorkaround, + "RL:U": v2RLUnavailable, + "RL:ND": v2RLNotDefined, + + "RC:UC": v2RCUnconfirmed, + "RC:UR": v2RCUncorroborated, + "RC:C": v2RCConfirmed, + "RC:ND": v2RCNotDefined, + + "CDP:N": v2CDPNone, + "CDP:L": v2CDPLow, + "CDP:LM": v2CDPLowMedium, + "CDP:MH": v2CDPMediumHigh, + "CDP:H": v2CDPHigh, + "CDP:ND": v2CDPNotDefined, + + "TD:N": v2TDNone, + "TD:L": v2TDLow, + "TD:M": v2TDMedium, + "TD:H": v2TDHigh, + "TD:ND": v2TDNotDefined, + + "CR:L": v2CRLow, + "CR:M": v2CRMedium, + "CR:H": v2CRHigh, + "CR:ND": v2CRNotDefined, + + "IR:L": v2IRLow, + "IR:M": v2IRMedium, + "IR:H": v2IRHigh, + "IR:ND": v2IRNotDefined, + + "AR:L": v2ARLow, + "AR:M": v2ARMedium, + "AR:H": v2ARHigh, + "AR:ND": v2ARNotDefined, +} + +// Convert string to CVSS 2.0 metric. +func getV2Metric(s string) (v2Metric, error) { + // get metric + m, ok := v2MetricStrLut[s] + if !ok { + return v2InvalidMetric, newBadMetric(V20, s) + } + + // return success + return m, nil +} + +// Get CVSS 2.0 metric key. +func (m v2Metric) Key() Key { + if k, ok := v2KeyLut[m]; ok { + return k + } else { + return v2InvalidKey + } +} diff --git a/cvss/v2metric_string.go b/cvss/v2metric_string.go new file mode 100644 index 0000000..6ad2f93 --- /dev/null +++ b/cvss/v2metric_string.go @@ -0,0 +1,78 @@ +// Code generated by "stringer -linecomment -type=v2Metric"; DO NOT EDIT. + +package cvss + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[v2AVNetwork-0] + _ = x[v2AVAdjacentNetwork-1] + _ = x[v2AVLocal-2] + _ = x[v2ACLow-3] + _ = x[v2ACMedium-4] + _ = x[v2ACHigh-5] + _ = x[v2AuMultiple-6] + _ = x[v2AuSingle-7] + _ = x[v2AuNone-8] + _ = x[v2CNone-9] + _ = x[v2CPartial-10] + _ = x[v2CComplete-11] + _ = x[v2INone-12] + _ = x[v2IPartial-13] + _ = x[v2IComplete-14] + _ = x[v2ANone-15] + _ = x[v2APartial-16] + _ = x[v2AComplete-17] + _ = x[v2ENotDefined-18] + _ = x[v2EUnproven-19] + _ = x[v2EProofOfConcept-20] + _ = x[v2EFunctional-21] + _ = x[v2EHigh-22] + _ = x[v2RLOfficialFix-23] + _ = x[v2RLTemporaryFix-24] + _ = x[v2RLWorkaround-25] + _ = x[v2RLUnavailable-26] + _ = x[v2RLNotDefined-27] + _ = x[v2RCUnconfirmed-28] + _ = x[v2RCUncorroborated-29] + _ = x[v2RCConfirmed-30] + _ = x[v2RCNotDefined-31] + _ = x[v2CDPNone-32] + _ = x[v2CDPLow-33] + _ = x[v2CDPLowMedium-34] + _ = x[v2CDPMediumHigh-35] + _ = x[v2CDPHigh-36] + _ = x[v2CDPNotDefined-37] + _ = x[v2TDNone-38] + _ = x[v2TDLow-39] + _ = x[v2TDMedium-40] + _ = x[v2TDHigh-41] + _ = x[v2TDNotDefined-42] + _ = x[v2CRLow-43] + _ = x[v2CRMedium-44] + _ = x[v2CRHigh-45] + _ = x[v2CRNotDefined-46] + _ = x[v2IRLow-47] + _ = x[v2IRMedium-48] + _ = x[v2IRHigh-49] + _ = x[v2IRNotDefined-50] + _ = x[v2ARLow-51] + _ = x[v2ARMedium-52] + _ = x[v2ARHigh-53] + _ = x[v2ARNotDefined-54] + _ = x[v2InvalidMetric-55] +} + +const _v2Metric_name = "AV:NAV:AAV:LAC:LAC:MAC:HAu:MAu:SAu:NC:NC:PC:CI:NI:PI:CA:NA:PA:CE:NDE:UE:POCE:FE:HRL:OFRL:TFRL:WRL:URL:NDRC:UCRC:URRC:CRC:NDCDP:NCDP:LCDP:LMCDP:MHCDP:HCDP:NDTD:NTD:LTD:MTD:HTD:NDCR:LCR:MCR:HCR:NDIR:LIR:MIR:HIR:NDAR:LAR:MAR:HAR:NDinvalid" + +var _v2Metric_index = [...]uint8{0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 67, 70, 75, 78, 81, 86, 91, 95, 99, 104, 109, 114, 118, 123, 128, 133, 139, 145, 150, 156, 160, 164, 168, 172, 177, 181, 185, 189, 194, 198, 202, 206, 211, 215, 219, 223, 228, 235} + +func (i v2Metric) String() string { + if i >= v2Metric(len(_v2Metric_index)-1) { + return "v2Metric(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _v2Metric_name[_v2Metric_index[i]:_v2Metric_index[i+1]] +} diff --git a/cvss/v2metric_test.go b/cvss/v2metric_test.go new file mode 100644 index 0000000..f606ed5 --- /dev/null +++ b/cvss/v2metric_test.go @@ -0,0 +1,276 @@ +package cvss + +import "testing" + +func TestGetV2Metric(t *testing.T) { + tests := []struct { + val string + exp v2Metric + ok bool + } { + { "AV:N", v2AVNetwork, true }, + { "AV:A", v2AVAdjacentNetwork, true }, + { "AV:L", v2AVLocal, true }, + + { "AC:L", v2ACLow, true }, + { "AC:M", v2ACMedium, true }, + { "AC:H", v2ACHigh, true }, + + { "Au:M", v2AuMultiple, true }, + { "Au:S", v2AuSingle, true }, + { "Au:N", v2AuNone, true }, + + { "C:N", v2CNone, true }, + { "C:P", v2CPartial, true }, + { "C:C", v2CComplete, true }, + + { "I:N", v2INone, true }, + { "I:P", v2IPartial, true }, + { "I:C", v2IComplete, true }, + + { "A:N", v2ANone, true }, + { "A:P", v2APartial, true }, + { "A:C", v2AComplete, true }, + + { "E:ND", v2ENotDefined, true }, + { "E:U", v2EUnproven, true }, + { "E:POC", v2EProofOfConcept, true }, + { "E:F", v2EFunctional, true }, + { "E:H", v2EHigh, true }, + + { "RL:OF", v2RLOfficialFix, true }, + { "RL:TF", v2RLTemporaryFix, true }, + { "RL:W", v2RLWorkaround, true }, + { "RL:U", v2RLUnavailable, true }, + { "RL:ND", v2RLNotDefined, true }, + + { "RC:UC", v2RCUnconfirmed, true }, + { "RC:UR", v2RCUncorroborated, true }, + { "RC:C", v2RCConfirmed, true }, + { "RC:ND", v2RCNotDefined, true }, + + { "CDP:N", v2CDPNone, true }, + { "CDP:L", v2CDPLow, true }, + { "CDP:LM", v2CDPLowMedium, true }, + { "CDP:MH", v2CDPMediumHigh, true }, + { "CDP:H", v2CDPHigh, true }, + { "CDP:ND", v2CDPNotDefined, true }, + + { "TD:N", v2TDNone, true }, + { "TD:L", v2TDLow, true }, + { "TD:M", v2TDMedium, true }, + { "TD:H", v2TDHigh, true }, + { "TD:ND", v2TDNotDefined, true }, + + { "CR:L", v2CRLow, true }, + { "CR:M", v2CRMedium, true }, + { "CR:H", v2CRHigh, true }, + { "CR:ND", v2CRNotDefined, true }, + + { "IR:L", v2IRLow, true }, + { "IR:M", v2IRMedium, true }, + { "IR:H", v2IRHigh, true }, + { "IR:ND", v2IRNotDefined, true }, + + { "AR:L", v2ARLow, true }, + { "AR:M", v2ARMedium, true }, + { "AR:H", v2ARHigh, true }, + { "AR:ND", v2ARNotDefined, true }, + + { "asdf", v2InvalidMetric, false }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + got, err := getV2Metric(test.val) + if test.ok && err == nil && got != test.exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) + } else if test.ok && err != nil { + t.Error(err) + } else if !test.ok && err == nil { + t.Errorf("got: \"%s\", exp: error", got) + } + }) + } +} + +func TestGetV2MetricKey(t *testing.T) { + tests := []struct { + val v2Metric + exp v2Key + } { + { v2AVNetwork, v2AccessVector }, + { v2AVAdjacentNetwork, v2AccessVector }, + { v2AVLocal, v2AccessVector }, + + { v2ACLow, v2AccessComplexity }, + { v2ACMedium, v2AccessComplexity }, + { v2ACHigh, v2AccessComplexity }, + + { v2AuMultiple, v2Authentication }, + { v2AuSingle, v2Authentication }, + { v2AuNone, v2Authentication }, + + { v2CNone, v2ConfidentialityImpact }, + { v2CPartial, v2ConfidentialityImpact }, + { v2CComplete, v2ConfidentialityImpact }, + + { v2INone, v2IntegrityImpact }, + { v2IPartial, v2IntegrityImpact }, + { v2IComplete, v2IntegrityImpact }, + + { v2ANone, v2AvailabilityImpact }, + { v2APartial, v2AvailabilityImpact }, + { v2AComplete, v2AvailabilityImpact }, + + { v2ENotDefined, v2Exploitability }, + { v2EUnproven, v2Exploitability }, + { v2EProofOfConcept, v2Exploitability }, + { v2EFunctional, v2Exploitability }, + { v2EHigh, v2Exploitability }, + + { v2RLOfficialFix, v2RemediationLevel }, + { v2RLTemporaryFix, v2RemediationLevel }, + { v2RLWorkaround, v2RemediationLevel }, + { v2RLUnavailable, v2RemediationLevel }, + { v2RLNotDefined, v2RemediationLevel }, + + { v2RCUnconfirmed, v2ReportConfidence }, + { v2RCUncorroborated, v2ReportConfidence }, + { v2RCConfirmed, v2ReportConfidence }, + { v2RCNotDefined, v2ReportConfidence }, + + { v2CDPNone, v2CollateralDamagePotential }, + { v2CDPLow, v2CollateralDamagePotential }, + { v2CDPLowMedium, v2CollateralDamagePotential }, + { v2CDPMediumHigh, v2CollateralDamagePotential }, + { v2CDPHigh, v2CollateralDamagePotential }, + { v2CDPNotDefined, v2CollateralDamagePotential }, + + { v2TDNone, v2TargetDistribution }, + { v2TDLow, v2TargetDistribution }, + { v2TDMedium, v2TargetDistribution }, + { v2TDHigh, v2TargetDistribution }, + { v2TDNotDefined, v2TargetDistribution }, + + { v2CRLow, v2ConfidentialityRequirement }, + { v2CRMedium, v2ConfidentialityRequirement }, + { v2CRHigh, v2ConfidentialityRequirement }, + { v2CRNotDefined, v2ConfidentialityRequirement }, + + { v2IRLow, v2IntegrityRequirement }, + { v2IRMedium, v2IntegrityRequirement }, + { v2IRHigh, v2IntegrityRequirement }, + { v2IRNotDefined, v2IntegrityRequirement }, + + { v2ARLow, v2AvailabilityRequirement }, + { v2ARMedium, v2AvailabilityRequirement }, + { v2ARHigh, v2AvailabilityRequirement }, + { v2ARNotDefined, v2AvailabilityRequirement }, + } + + for _, test := range(tests) { + t.Run(test.val.String(), func(t *testing.T) { + got := test.val.Key() + if got != test.exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) + } + }) + } +} + +func TestV2MetricString(t *testing.T) { + tests := []struct { + val v2Metric + exp string + } { + { v2AVNetwork, "AV:N" }, + { v2AVAdjacentNetwork, "AV:A" }, + { v2AVLocal, "AV:L" }, + + { v2ACLow, "AC:L" }, + { v2ACMedium, "AC:M" }, + { v2ACHigh, "AC:H" }, + + { v2AuMultiple, "Au:M" }, + { v2AuSingle, "Au:S" }, + { v2AuNone, "Au:N" }, + + { v2CNone, "C:N" }, + { v2CPartial, "C:P" }, + { v2CComplete, "C:C" }, + + { v2INone, "I:N" }, + { v2IPartial, "I:P" }, + { v2IComplete, "I:C" }, + + { v2ANone, "A:N" }, + { v2APartial, "A:P" }, + { v2AComplete, "A:C" }, + + { v2ENotDefined, "E:ND" }, + { v2EUnproven, "E:U" }, + { v2EProofOfConcept, "E:POC" }, + { v2EFunctional, "E:F" }, + { v2EHigh, "E:H" }, + + { v2RLOfficialFix, "RL:OF" }, + { v2RLTemporaryFix, "RL:TF" }, + { v2RLWorkaround, "RL:W" }, + { v2RLUnavailable, "RL:U" }, + { v2RLNotDefined, "RL:ND" }, + + { v2RCUnconfirmed, "RC:UC" }, + { v2RCUncorroborated, "RC:UR" }, + { v2RCConfirmed, "RC:C" }, + { v2RCNotDefined, "RC:ND" }, + + { v2CDPNone, "CDP:N" }, + { v2CDPLow, "CDP:L" }, + { v2CDPLowMedium, "CDP:LM" }, + { v2CDPMediumHigh, "CDP:MH" }, + { v2CDPHigh, "CDP:H" }, + { v2CDPNotDefined, "CDP:ND" }, + + { v2TDNone, "TD:N" }, + { v2TDLow, "TD:L" }, + { v2TDMedium, "TD:M" }, + { v2TDHigh, "TD:H" }, + { v2TDNotDefined, "TD:ND" }, + + { v2CRLow, "CR:L" }, + { v2CRMedium, "CR:M" }, + { v2CRHigh, "CR:H" }, + { v2CRNotDefined, "CR:ND" }, + + { v2IRLow, "IR:L" }, + { v2IRMedium, "IR:M" }, + { v2IRHigh, "IR:H" }, + { v2IRNotDefined, "IR:ND" }, + + { v2ARLow, "AR:L" }, + { v2ARMedium, "AR:M" }, + { v2ARHigh, "AR:H" }, + { v2ARNotDefined, "AR:ND" }, + + { v2Metric(255), "v2Metric(255)" }, + } + + for _, test := range(tests) { + t.Run(test.val.String(), func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) + } + }) + } +} + +func TestInvalidV2MetricKey(t *testing.T) { + got := v2Metric(255).Key() + exp := v2InvalidKey + + if got != exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) + } +} diff --git a/cvss/v2vector.go b/cvss/v2vector.go new file mode 100644 index 0000000..b1fca1f --- /dev/null +++ b/cvss/v2vector.go @@ -0,0 +1,108 @@ +package cvss + +import ( + // "encoding/json" + "regexp" + "strings" +) + +// CVSS 2.0 vector. +type v2Vector []v2Metric + +// Convert vector to string. +func (v v2Vector) String() string { + // convert to slice of metrics + metrics := []v2Metric(v) + + // build vector + r := make([]string, len(metrics)) + for i, m := range(metrics) { + r[i] = m.String() + } + + // build and return string + return strings.Join(r, "/") +} + +// Return CVSS version. +func (v2Vector) Version() Version { + return V20 +} + +// Return metrics in this vector. +func (v v2Vector) Metrics() []Metric { + // build result + r := make([]Metric, len(v)) + for i, m := range(v) { + r[i] = m + } + + // return result + return r +} + +// Create CVSS 2.0 vector from string. +func newV2Vector(s string) (v2Vector, error) { + strs := strings.Split(s, "/") + r := make([]v2Metric, len(strs)) + + // walk metric strings + for i, ms := range(strs) { + // convert string to vector + m, err := getV2Metric(ms) + if err != nil { + return nil, err + } + + // add to results + r[i] = m + } + + // build and return vector + return v2Vector(r), nil +} + +// // Unmarshal CVSS 2.0 vector from JSON string. +// func (me *v2Vector) UnmarshalJSON(b []byte) error { +// // decode string, check for error +// var s string +// if err := json.Unmarshal(b, &s); err != nil { +// return err +// } +// +// // parse vector, check for error +// r, err := newV2Vector(s) +// if err != nil { +// // return error +// return err +// } +// +// // save result, return success +// *me = r +// return nil +// } + +var v2MetricRe = "(?:" + strings.Join([]string { + "(?:AV:[NAL])", + "(?:AC:[LMH])", + "(?:Au:[MSN])", + "(?:C:[NPC])", + "(?:I:[NPC])", + "(?:A:[NPC])", + "(?:E:(?:ND|U|POC|F|H))", + "(?:RL:(?:OF|TF|W|U|ND))", + "(?:RC:(?:UC|UR|C|ND))", + "(?:CDP:(?:N|L|LM|MH|H|ND))", + "(?:TD:(?:N|L|M|H|ND))", + "(?:CR:(?:L|M|H|ND))", + "(?:IR:(?:L|M|H|ND))", +}, "|") + ")" + +var v2VecRe = regexp.MustCompile( + "\\A" + v2MetricRe + "(?:/" + v2MetricRe + ")*\\z", +) + +// Is the given string a CVSS v2 vector string? +func isV2VectorString(s string) bool { + return len(s) > 0 && v2VecRe.MatchString(s) +} diff --git a/cvss/v2vector_test.go b/cvss/v2vector_test.go new file mode 100644 index 0000000..f297ba6 --- /dev/null +++ b/cvss/v2vector_test.go @@ -0,0 +1,69 @@ +package cvss + +import ( + "testing" +) + +func TestNewV2Vector(t *testing.T) { + passTests := []struct { + val string + exp []string + } { + { + val: "AV:L/AC:L/Au:N/C:N/I:N/A:P", + exp: []string { "AV:L", "AC:L", "Au:N", "C:N", "I:N", "A:P" }, + }, + } + + for _, test := range(passTests) { + t.Run(test.val, func(t *testing.T) { + // parse vector + vec, err := newV2Vector(test.val) + if err != nil { + t.Error(err) + return + } + + // check version + if vec.Version() != V20 { + t.Errorf("got %s, exp %s", vec.Version(), V20) + return + } + + // check metric length + if len(vec.Metrics()) != len(test.exp) { + t.Errorf("got %d, exp %d", len(vec.Metrics()), len(test.exp)) + return + } + + // check metrics + for i, m := range(vec.Metrics()) { + got := m.String() + if got != test.exp[i] { + t.Errorf("got %s, exp %s", got, test.exp[i]) + } + } + }) + } + + failTests := []struct { + val string + exp string + } { + { + val: "AV:L/junk/Au:N/C:N/I:N/A:P", + exp: "invalid CVSS 2.0 metric: junk", + }, + } + + for _, test := range(failTests) { + t.Run(test.val, func(t *testing.T) { + got, err := newV2Vector(test.val) + if err != nil && err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } else if err == nil { + t.Errorf("got \"%s\", exp badMetric", got) + } + }) + } +} diff --git a/cvss/v30vector.go b/cvss/v30vector.go new file mode 100644 index 0000000..5ef1ae8 --- /dev/null +++ b/cvss/v30vector.go @@ -0,0 +1,119 @@ +package cvss + +import ( + // "encoding/json" + "regexp" + "strings" +) + +// CVSS v3.0 prefix +var v30Prefix = "CVSS:3.0/" + +// CVSS 3.0 vector. +type v30Vector []v3Metric + +// Convert vector to string +func (v v30Vector) String() string { + // convert to slice of metrics + metrics := []v3Metric(v) + + // build vector + r := make([]string, len(metrics)) + for i, m := range(metrics) { + r[i] = m.String() + } + + // build and return string + return v30Prefix + strings.Join(r, "/") +} + +// Return CVSS version. +func (v30Vector) Version() Version { + return V30 +} + +// Return metrics in this vector. +func (v v30Vector) Metrics() []Metric { + // build result + r := make([]Metric, len(v)) + for i, m := range(v) { + r[i] = m + } + + // return result + return r +} + +// Create CVSS 3.0 vector from string. +func newV30Vector(s string) (v30Vector, error) { + // strip version prefix, split into metric strings + strs := strings.Split(s[len(v30Prefix):], "/") + r := make([]v3Metric, len(strs)) + + // build results + for i, ms := range(strs) { + // get metric from string + m, err := getV3Metric(V30, ms) + if err != nil { + return nil, err + } + + r[i] = m + } + + // return result + return v30Vector(r), nil +} + +// // Unmarshal CVSS 3.0 vector from JSON string. +// func (me *v30Vector) UnmarshalJSON(b []byte) error { +// // decode string, check for error +// var s string +// if err := json.Unmarshal(b, &s); err != nil { +// return err +// } +// +// // parse vector, check for error +// r, err := newV30Vector(s) +// if err != nil { +// return err +// } +// +// // save result, return success +// *me = r +// return nil +// } + +var v30VecRe = regexp.MustCompile( + "\\ACVSS:3\\.0(?:/(?:" + strings.Join([]string { + "(?:AV:[NALP])", + "(?:AC:[LH])", + "(?:PR:[NLH])", + "(?:UI:[NR])", + "(?:S:[UC])", + "(?:C:[HLN])", + "(?:I:[HLN])", + "(?:A:[HLN])", + "(?:E:[XHFPU])", + "(?:RL:[XUWTO])", + "(?:RC:[XCRU])", + "(?:CR:[XHML])", + "(?:IR:[XHML])", + "(?:AR:[XHML])", + "(?:MAV:[XNALP])", + "(?:MAC:[XLH])", + "(?:MPR:[XNLH])", + "(?:MUI:[XNR])", + "(?:MS:[XUC])", + "(?:MC:[XNLH])", + "(?:MI:[XNLH])", + "(?:MA:[XNLH])", + }, "|") + "))+\\z", +) + +// Is the given string a CVSSv3.1 vector string? +func isV30VectorString(s string) bool { + return (len(s) > len(v30Prefix)) && + (s[:len(v30Prefix)] == v30Prefix) && + v30VecRe.MatchString(s) +} diff --git a/cvss/v30vector_test.go b/cvss/v30vector_test.go new file mode 100644 index 0000000..1d44d35 --- /dev/null +++ b/cvss/v30vector_test.go @@ -0,0 +1,69 @@ +package cvss + +import ( + "testing" +) + +func TestNewV30Vector(t *testing.T) { + passTests := []struct { + val string + exp []string + } { + { + val: "CVSS:3.0/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", + exp: []string { "AV:L", "AC:H", "PR:L", "UI:N", "S:U", "C:H", "I:N", "A:N" }, + }, + } + + for _, test := range(passTests) { + t.Run(test.val, func(t *testing.T) { + // parse vector + vec, err := newV30Vector(test.val) + if err != nil { + t.Error(err) + return + } + + // check version + if vec.Version() != V30 { + t.Errorf("got %s, exp %s", vec.Version(), V30) + return + } + + // check metric length + if len(vec.Metrics()) != len(test.exp) { + t.Errorf("got %d, exp %d", len(vec.Metrics()), len(test.exp)) + return + } + + // check metrics + for i, m := range(vec.Metrics()) { + got := m.String() + if got != test.exp[i] { + t.Errorf("got %s, exp %s", got, test.exp[i]) + } + } + }) + } + + failTests := []struct { + val string + exp string + } { + { + val: "CVSS:3.0/AV:L/junk/PR:L/UI:N/S:U/C:H/I:N/A:N", + exp: "invalid CVSS 3.0 metric: junk", + }, + } + + for _, test := range(failTests) { + t.Run(test.val, func(t *testing.T) { + got, err := newV30Vector(test.val) + if err != nil && err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } else if err == nil { + t.Errorf("got \"%s\", exp badMetric", got) + } + }) + } +} diff --git a/cvss/v31vector.go b/cvss/v31vector.go new file mode 100644 index 0000000..f286ea0 --- /dev/null +++ b/cvss/v31vector.go @@ -0,0 +1,119 @@ +package cvss + +import ( + "regexp" + "strings" +) + +// CVSS v3.1 prefix +var v31Prefix = "CVSS:3.1/" + +// CVSS 3.1 vector. +type v31Vector []v3Metric + +// Convert vector to string +func (v v31Vector) String() string { + // convert to slice of metrics + metrics := []v3Metric(v) + + // build vector + r := make([]string, len(metrics)) + for i, m := range(metrics) { + r[i] = m.String() + } + + // build and return string + return v31Prefix + strings.Join(r, "/") +} + +// Return CVSS version. +func (v31Vector) Version() Version { + return V31 +} + +// Return metrics in this vector. +func (v v31Vector) Metrics() []Metric { + // build slice of metrics + r := make([]Metric, len(v)) + for i, m := range(v) { + r[i] = m + } + + // return result + return r +} + +// create CVSS 3.1 vector from string. +func newV31Vector(s string) (v31Vector, error) { + // strip version prefix, split into metric strings + strs := strings.Split(s[len(v31Prefix):], "/") + r := make([]v3Metric, len(strs)) + + // build results + for i, ms := range(strs) { + // get metric from string + m, err := getV3Metric(V31, ms) + if err != nil { + return nil, err + } + + // add to results + r[i] = m + } + + // return result + return v31Vector(r), nil +} + +// // Unmarshal CVSS 3.1 vector from JSON string. +// func (me *v31Vector) UnmarshalJSON(b []byte) error { +// // decode string, check for error +// var s string +// if err := json.Unmarshal(b, &s); err != nil { +// return err +// } +// +// // parse vector, check for error +// r, err := newV31Vector(s) +// if err != nil { +// return err +// } +// +// // save result, return success +// *me = r +// return nil +// } + +var v31VecRe = regexp.MustCompile( + "\\ACVSS:3\\.1(?:/(?:" + strings.Join([]string { + "(?:AV:[NALP])", + "(?:AC:[LH])", + "(?:PR:[NLH])", + "(?:UI:[NR])", + "(?:S:[UC])", + "(?:C:[HLN])", + "(?:I:[HLN])", + "(?:A:[HLN])", + "(?:E:[XHFPU])", + "(?:RL:[XUWTO])", + "(?:RC:[XCRU])", + "(?:CR:[XHML])", + "(?:IR:[XHML])", + "(?:AR:[XHML])", + "(?:MAV:[XNALP])", + "(?:MAC:[XLH])", + "(?:MPR:[XNLH])", + "(?:MUI:[XNR])", + "(?:MS:[XUC])", + "(?:MC:[XNLH])", + "(?:MI:[XNLH])", + "(?:MA:[XNLH])", + }, "|") + "))+\\z", +) + +// Is the given string a CVSSv3.1 vector string? +func isV31VectorString(s string) bool { + return (len(s) > len(v31Prefix)) && + (s[:len(v31Prefix)] == v31Prefix) && + v31VecRe.MatchString(s) +} diff --git a/cvss/v31vector_test.go b/cvss/v31vector_test.go new file mode 100644 index 0000000..d05edca --- /dev/null +++ b/cvss/v31vector_test.go @@ -0,0 +1,561 @@ +package cvss + +import ( + "testing" +) + +func TestNewV31Vector(t *testing.T) { + passTests := []struct { + val string + exp []string + } { + { + val: "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", + exp: []string { "AV:L", "AC:H", "PR:L", "UI:N", "S:U", "C:H", "I:N", "A:N" }, + }, + } + + for _, test := range(passTests) { + t.Run(test.val, func(t *testing.T) { + // parse vector + vec, err := newV31Vector(test.val) + if err != nil { + t.Error(err) + return + } + + // check version + if vec.Version() != V31 { + t.Errorf("got %s, exp %s", vec.Version(), V31) + return + } + + // check metric length + if len(vec.Metrics()) != len(test.exp) { + t.Errorf("got %d, exp %d", len(vec.Metrics()), len(test.exp)) + return + } + + // check metrics + for i, m := range(vec.Metrics()) { + got := m.String() + if got != test.exp[i] { + t.Errorf("got %s, exp %s", got, test.exp[i]) + } + } + }) + } + + failTests := []struct { + val string + exp string + } { + { + val: "CVSS:3.1/AV:L/junk/PR:L/UI:N/S:U/C:H/I:N/A:N", + exp: "invalid CVSS 3.1 metric: junk", + }, + } + + for _, test := range(failTests) { + t.Run(test.val, func(t *testing.T) { + got, err := newV31Vector(test.val) + if err != nil && err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } else if err == nil { + t.Errorf("got \"%s\", exp badMetric", got) + } + }) + } +} + +func TestIsV31VectorString(t *testing.T) { + // test non v31 strings + passTests := []string { + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:C/C:L/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:H/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:P/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:P/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + } + + for _, test := range(passTests) { + t.Run(test, func(t *testing.T) { + if !isV31VectorString(test) { + t.Error("got false, exp true") + } + }) + } + + // test non v31 strings + failTests := []string { + "AV:N/AC:M/Au:S/C:N/I:P/A:C", + "AV:N/AC:M/Au:S/C:N/I:P/A:N", + "AV:N/AC:M/Au:S/C:N/I:P/A:P", + "AV:N/AC:M/Au:S/C:P/I:N/A:N", + "AV:N/AC:M/Au:S/C:P/I:N/A:P", + "AV:N/AC:M/Au:S/C:P/I:P/A:C", + "AV:N/AC:M/Au:S/C:P/I:P/A:N", + "AV:N/AC:M/Au:S/C:P/I:P/A:P", + + "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:L", + "CVSS:3.0/AV:A/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.0/AV:A/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.0/AV:A/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", + } + + for _, test := range(failTests) { + t.Run(test, func(t *testing.T) { + if isV31VectorString(test) { + t.Error("got true, exp false") + } + }) + } +} diff --git a/cvss/v3key.go b/cvss/v3key.go new file mode 100644 index 0000000..653c2da --- /dev/null +++ b/cvss/v3key.go @@ -0,0 +1,116 @@ +package cvss + +//go:generate stringer -linecomment -type=v3Key + +// CVSS v3 metric key +type v3Key byte + +const ( + v3AttackVector v3Key = iota // AV + v3AttackComplexity // AC + v3PrivilegesRequired // PR + v3UserInteraction // UI + v3Scope // S + v3Confidentiality // C + v3Integrity // I + v3Availability // A + v3ExploitCodeMaturity // E + v3RemediationLevel // RL + v3ReportConfidence // RC + v3ConfidentialityRequirement // CR + v3IntegrityRequirement // IR + v3AvailabilityRequirement // AR + v3ModifiedAttackVector // MAV + v3ModifiedAttackComplexity // MAC + v3ModifiedPrivilegesRequired // MPR + v3ModifiedUserInteraction // MUI + v3ModifiedScope // MS + v3ModifiedConfidentiality // MC + v3ModifiedIntegrity // MI + v3ModifiedAvailability // MA + + v3InvalidKey // invalid +) + +// CVSS v3 metric key info lut +var v3Keys = map[v3Key]struct { + Name string + Category Category +} { + v3AttackVector: { "Attack Vector", Base }, + v3AttackComplexity: { "Attack Complexity", Base }, + v3PrivilegesRequired: { "Privileges Required", Base }, + v3UserInteraction: { "User Interaction", Base }, + v3Scope: { "Scope", Base }, + v3Confidentiality: { "Confidentiality", Base }, + v3Integrity: { "Integrity", Base }, + v3Availability: { "Availability", Base }, + v3ExploitCodeMaturity: { "Exploit Code Maturity", Temporal }, + v3RemediationLevel: { "Remediation Level", Temporal }, + v3ReportConfidence: { "Report Confidence", Temporal }, + v3ConfidentialityRequirement: { "Confidentiality Requirement", Environmental }, + v3IntegrityRequirement: { "Integrity Requirement", Environmental }, + v3AvailabilityRequirement: { "Availability Requirement", Environmental }, + v3ModifiedAttackVector: { "Modified Attack Vector", Environmental }, + v3ModifiedAttackComplexity: { "Modified Attack Complexity", Environmental }, + v3ModifiedPrivilegesRequired: { "Modified Privileges Required", Environmental }, + v3ModifiedUserInteraction: { "Modified User Interaction", Environmental }, + v3ModifiedScope: { "Modified Scope", Environmental }, + v3ModifiedConfidentiality: { "Modified Confidentiality", Environmental }, + v3ModifiedIntegrity: { "Modified Integrity", Environmental }, + v3ModifiedAvailability: { "Modified Availability", Environmental }, +} + +// // metric key IDs lut +// var v3KeyIds = map[string]v3Key { +// "AV": v3AttackVector, +// "AC": v3AttackComplexity, +// "PR": v3PrivilegesRequired, +// "UI": v3UserInteraction, +// "S": v3Scope, +// "C": v3Confidentiality, +// "I": v3Integrity, +// "A": v3Availability, +// "E": v3ExploitCodeMaturity, +// "RL": v3RemediationLevel, +// "RC": v3ReportConfidence, +// "CR": v3ConfidentialityRequirement, +// "IR": v3IntegrityRequirement, +// "AR": v3AvailabilityRequirement, +// "MAV": v3ModifiedAttackVector, +// "MAC": v3ModifiedAttackComplexity, +// "MPR": v3ModifiedPrivilegesRequired, +// "MUI": v3ModifiedUserInteraction, +// "MS": v3ModifiedScope, +// "MC": v3ModifiedConfidentiality, +// "MI": v3ModifiedIntegrity, +// "MA": v3ModifiedAvailability, +// } +// +// // Get metric key from string. +// func getV3KeyFromString(s string) (v3Key, error) { +// k, ok := v3KeyIds[s] +// if ok { +// return k, nil +// } else { +// return v3InvalidKey, newBadKey(V30, s) +// } +// } + +// Get metric key name. +func (k v3Key) Name() string { + if data, ok := v3Keys[k]; ok { + return data.Name + } else { + return "invalid" + } +} + +// Get metric key category. +func (k v3Key) Category() Category { + if data, ok := v3Keys[k]; ok { + return data.Category + } else { + return InvalidCategory + } +} diff --git a/cvss/v3key_string.go b/cvss/v3key_string.go new file mode 100644 index 0000000..0644117 --- /dev/null +++ b/cvss/v3key_string.go @@ -0,0 +1,45 @@ +// Code generated by "stringer -linecomment -type=v3Key"; DO NOT EDIT. + +package cvss + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[v3AttackVector-0] + _ = x[v3AttackComplexity-1] + _ = x[v3PrivilegesRequired-2] + _ = x[v3UserInteraction-3] + _ = x[v3Scope-4] + _ = x[v3Confidentiality-5] + _ = x[v3Integrity-6] + _ = x[v3Availability-7] + _ = x[v3ExploitCodeMaturity-8] + _ = x[v3RemediationLevel-9] + _ = x[v3ReportConfidence-10] + _ = x[v3ConfidentialityRequirement-11] + _ = x[v3IntegrityRequirement-12] + _ = x[v3AvailabilityRequirement-13] + _ = x[v3ModifiedAttackVector-14] + _ = x[v3ModifiedAttackComplexity-15] + _ = x[v3ModifiedPrivilegesRequired-16] + _ = x[v3ModifiedUserInteraction-17] + _ = x[v3ModifiedScope-18] + _ = x[v3ModifiedConfidentiality-19] + _ = x[v3ModifiedIntegrity-20] + _ = x[v3ModifiedAvailability-21] + _ = x[v3InvalidKey-22] +} + +const _v3Key_name = "AVACPRUISCIAERLRCCRIRARMAVMACMPRMUIMSMCMIMAinvalid" + +var _v3Key_index = [...]uint8{0, 2, 4, 6, 8, 9, 10, 11, 12, 13, 15, 17, 19, 21, 23, 26, 29, 32, 35, 37, 39, 41, 43, 50} + +func (i v3Key) String() string { + if i >= v3Key(len(_v3Key_index)-1) { + return "v3Key(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _v3Key_name[_v3Key_index[i]:_v3Key_index[i+1]] +} diff --git a/cvss/v3key_test.go b/cvss/v3key_test.go new file mode 100644 index 0000000..517eebd --- /dev/null +++ b/cvss/v3key_test.go @@ -0,0 +1,142 @@ +package cvss + +import ( + "testing" +) + +func TestV3KeyString(t *testing.T) { + tests := []struct { + val v3Key + exp string + } { + { v3AttackVector, "AV" }, + { v3AttackComplexity, "AC" }, + { v3PrivilegesRequired, "PR" }, + { v3UserInteraction, "UI" }, + { v3Scope, "S" }, + { v3Confidentiality, "C" }, + { v3Integrity, "I" }, + { v3Availability, "A" }, + { v3ExploitCodeMaturity, "E" }, + { v3RemediationLevel, "RL" }, + { v3ReportConfidence, "RC" }, + { v3ConfidentialityRequirement, "CR" }, + { v3IntegrityRequirement, "IR" }, + { v3AvailabilityRequirement, "AR" }, + { v3ModifiedAttackVector, "MAV" }, + { v3ModifiedAttackComplexity, "MAC" }, + { v3ModifiedPrivilegesRequired, "MPR" }, + { v3ModifiedUserInteraction, "MUI" }, + { v3ModifiedScope, "MS" }, + { v3ModifiedConfidentiality, "MC" }, + { v3ModifiedIntegrity, "MI" }, + { v3ModifiedAvailability, "MA" }, + + { v3Key(255), "v3Key(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV3KeyName(t *testing.T) { + tests := []struct { + val v3Key + exp string + } { + { v3AttackVector, "Attack Vector" }, + { v3AttackComplexity, "Attack Complexity" }, + { v3PrivilegesRequired, "Privileges Required" }, + { v3UserInteraction, "User Interaction" }, + { v3Scope, "Scope" }, + { v3Confidentiality, "Confidentiality" }, + { v3Integrity, "Integrity" }, + { v3Availability, "Availability" }, + { v3ExploitCodeMaturity, "Exploit Code Maturity" }, + { v3RemediationLevel, "Remediation Level" }, + { v3ReportConfidence, "Report Confidence" }, + { v3ConfidentialityRequirement, "Confidentiality Requirement" }, + { v3IntegrityRequirement, "Integrity Requirement" }, + { v3AvailabilityRequirement, "Availability Requirement" }, + { v3ModifiedAttackVector, "Modified Attack Vector" }, + { v3ModifiedAttackComplexity, "Modified Attack Complexity" }, + { v3ModifiedPrivilegesRequired, "Modified Privileges Required" }, + { v3ModifiedUserInteraction, "Modified User Interaction" }, + { v3ModifiedScope, "Modified Scope" }, + { v3ModifiedConfidentiality, "Modified Confidentiality" }, + { v3ModifiedIntegrity, "Modified Integrity" }, + { v3ModifiedAvailability, "Modified Availability" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.Name() + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV3KeyCategory(t *testing.T) { + tests := []struct { + val v3Key + exp Category + } { + { v3AttackVector, Base }, + { v3AttackComplexity, Base }, + { v3PrivilegesRequired, Base }, + { v3UserInteraction, Base }, + { v3Scope, Base }, + { v3Confidentiality, Base }, + { v3Integrity, Base }, + { v3Availability, Base }, + { v3ExploitCodeMaturity, Temporal }, + { v3RemediationLevel, Temporal }, + { v3ReportConfidence, Temporal }, + { v3ConfidentialityRequirement, Environmental }, + { v3IntegrityRequirement, Environmental }, + { v3AvailabilityRequirement, Environmental }, + { v3ModifiedAttackVector, Environmental }, + { v3ModifiedAttackComplexity, Environmental }, + { v3ModifiedPrivilegesRequired, Environmental }, + { v3ModifiedUserInteraction, Environmental }, + { v3ModifiedScope, Environmental }, + { v3ModifiedConfidentiality, Environmental }, + { v3ModifiedIntegrity, Environmental }, + { v3ModifiedAvailability, Environmental }, + } + + for _, test := range(tests) { + t.Run(test.val.String(), func(t *testing.T) { + got := test.val.Category() + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestInvalidV3KeyName(t *testing.T) { + exp := "invalid" + got := v3Key(255).Name() + + if got != exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) + } +} + +func TestInvalidV3KeyCategory(t *testing.T) { + exp := InvalidCategory + got := v3Key(255).Category() + + if got != exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) + } +} diff --git a/cvss/v3metric.go b/cvss/v3metric.go new file mode 100644 index 0000000..fc4c860 --- /dev/null +++ b/cvss/v3metric.go @@ -0,0 +1,331 @@ +package cvss + +//go:generate stringer -linecomment -type=v3Metric + +// metric value +type v3Metric byte + +const ( + v3AVNetwork v3Metric = iota // AV:N + v3AVAdjacentNetwork // AV:A + v3AVLocal // AV:L + v3AVPhysical // AV:P + + v3ACLow // AC:L + v3ACHigh // AC:H + + v3PRNone // PR:N + v3PRLow // PR:L + v3PRHigh // PR:H + + v3UINone // UI:N + v3UIRequired // UI:R + + v3SUnchanged // S:U + v3SChanged // S:C + + v3CHigh // C:H + v3CLow // C:L + v3CNone // C:N + + v3IHigh // I:H + v3ILow // I:L + v3INone // I:N + + v3AHigh // A:H + v3ALow // A:L + v3ANone // A:N + + v3ENotDefined // E:X + v3EHigh // E:H + v3EFunctional // E:F + v3EProofOfConcept // E:P + v3EUnproven // E:U + + v3RLNotDefined // RL:X + v3RLUnavailable // RL:U + v3RLWorkaround // RL:W + v3RLTemporaryFix // RL:T + v3RLOfficialFix // RL:O + + v3RCNotDefined // RC:X + v3RCConfirmed // RC:C + v3RCReasonable // RC:R + v3RCUnknown // RC:U + + v3CRNotDefined // CR:X + v3CRHigh // CR:H + v3CRMedium // CR:M + v3CRLow // CR:L + + v3IRNotDefined // IR:X + v3IRHigh // IR:H + v3IRMedium // IR:M + v3IRLow // IR:L + + v3ARNotDefined // AR:X + v3ARHigh // AR:H + v3ARMedium // AR:M + v3ARLow // AR:L + + v3MAVNotDefined // MAV:X + v3MAVNetwork // MAV:N + v3MAVAdjacentNetwork // MAV:A + v3MAVLocal // MAV:L + v3MAVPhysical // MAV:P + + v3MACNotDefined // MAC:X + v3MACLow // MAC:L + v3MACHigh // MAC:H + + v3MMRNotDefined // MPR:X + v3MPRLow // MPR:L + v3MPRHigh // MPR:H + + v3MUINotDefined // MUI:X + v3MUINone // MUI:N + v3MUIRequired // MUI:R + + v3MSNotDefined // MMS:X + v3MSUnchanged // MMS:U + v3MSChanged // MMS:C + + v3MCNotDefined // MC:X + v3MCHigh // MC:H + v3MCLow // MC:L + v3MCNone // MC:N + + v3MINotDefined // MI:X + v3MIHigh // MI:H + v3MILow // MI:L + v3MINone // MI:N + + v3MANotDefined // MA:X + v3MAHigh // MA:H + v3MALow // MA:L + v3MANone // MA:N + + v3InvalidMetric // invalid +) + +// map of metrics to metric keys +var v3KeyLut = map[v3Metric]v3Key { + v3AVNetwork: v3AttackVector, // AV:N + v3AVAdjacentNetwork: v3AttackVector, // AV:A + v3AVLocal: v3AttackVector, // AV:L + v3AVPhysical: v3AttackVector, // AV:P + + v3ACLow: v3AttackComplexity, // AC:L + v3ACHigh: v3AttackComplexity, // AC:H + + v3PRNone: v3PrivilegesRequired, // PR:N + v3PRLow: v3PrivilegesRequired, // PR:L + v3PRHigh: v3PrivilegesRequired, // PR:H + + v3UINone: v3UserInteraction, // UI:N + v3UIRequired: v3UserInteraction, // UI:R + + v3SUnchanged: v3Scope, // S:U + v3SChanged: v3Scope, // S:C + + v3CHigh: v3Confidentiality, // C:H + v3CLow: v3Confidentiality, // C:L + v3CNone: v3Confidentiality, // C:N + + v3IHigh: v3Integrity, // I:H + v3ILow: v3Integrity, // I:L + v3INone: v3Integrity, // I:N + + v3AHigh: v3Availability, // A:H + v3ALow: v3Availability, // A:L + v3ANone: v3Availability, // A:N + + v3ENotDefined: v3ExploitCodeMaturity, // E:X + v3EHigh: v3ExploitCodeMaturity, // E:H + v3EFunctional: v3ExploitCodeMaturity, // E:F + v3EProofOfConcept: v3ExploitCodeMaturity, // E:P + v3EUnproven: v3ExploitCodeMaturity, // E:U + + v3RLNotDefined: v3RemediationLevel, // RL:X + v3RLUnavailable: v3RemediationLevel, // RL:U + v3RLWorkaround: v3RemediationLevel, // RL:W + v3RLTemporaryFix: v3RemediationLevel, // RL:T + v3RLOfficialFix: v3RemediationLevel, // RL:O + + v3RCNotDefined: v3ReportConfidence, // RC:X + v3RCConfirmed: v3ReportConfidence, // RC:C + v3RCReasonable: v3ReportConfidence, // RC:R + v3RCUnknown: v3ReportConfidence, // RC:U + + v3CRNotDefined: v3ConfidentialityRequirement, // CR:X + v3CRHigh: v3ConfidentialityRequirement, // CR:H + v3CRMedium: v3ConfidentialityRequirement, // CR:M + v3CRLow: v3ConfidentialityRequirement, // CR:L + + v3IRNotDefined: v3IntegrityRequirement, // IR:X + v3IRHigh: v3IntegrityRequirement, // IR:H + v3IRMedium: v3IntegrityRequirement, // IR:M + v3IRLow: v3IntegrityRequirement, // IR:L + + v3ARNotDefined: v3AvailabilityRequirement, // AR:X + v3ARHigh: v3AvailabilityRequirement, // AR:H + v3ARMedium: v3AvailabilityRequirement, // AR:M + v3ARLow: v3AvailabilityRequirement, // AR:L + + v3MAVNotDefined: v3ModifiedAttackVector, // MAV:X + v3MAVNetwork: v3ModifiedAttackVector, // MAV:N + v3MAVAdjacentNetwork: v3ModifiedAttackVector, // MAV:A + v3MAVLocal: v3ModifiedAttackVector, // MAV:L + v3MAVPhysical: v3ModifiedAttackVector, // MAV:P + + v3MACNotDefined: v3ModifiedAttackComplexity, // MAC:X + v3MACLow: v3ModifiedAttackComplexity, // MAC:L + v3MACHigh: v3ModifiedAttackComplexity, // MAC:H + + v3MMRNotDefined: v3ModifiedPrivilegesRequired, // MPR:X + v3MPRLow: v3ModifiedPrivilegesRequired, // MPR:L + v3MPRHigh: v3ModifiedPrivilegesRequired, // MPR:H + + v3MUINotDefined: v3ModifiedUserInteraction, // MUI:X + v3MUINone: v3ModifiedUserInteraction, // MUI:N + v3MUIRequired: v3ModifiedUserInteraction, // MUI:R + + v3MSNotDefined: v3ModifiedScope, // MMS:X + v3MSUnchanged: v3ModifiedConfidentiality, // MMS:U + v3MSChanged: v3ModifiedIntegrity, // MMS:C + + v3MCNotDefined: v3ModifiedConfidentiality, // MC:X + v3MCHigh: v3ModifiedConfidentiality, // MC:H + v3MCLow: v3ModifiedConfidentiality, // MC:L + v3MCNone: v3ModifiedConfidentiality, // MC:N + + v3MINotDefined: v3ModifiedIntegrity, // MI:X + v3MIHigh: v3ModifiedIntegrity, // MI:H + v3MILow: v3ModifiedIntegrity, // MI:L + v3MINone: v3ModifiedIntegrity, // MI:N + + v3MANotDefined: v3ModifiedAvailability, // MA:X + v3MAHigh: v3ModifiedAvailability, // MA:H + v3MALow: v3ModifiedAvailability, // MA:L + v3MANone: v3ModifiedAvailability, // MA:N +} + +// map of metric strings to metrics +var v3MetricStrLut = map[string]v3Metric { + "AV:N": v3AVNetwork, + "AV:A": v3AVAdjacentNetwork, + "AV:L": v3AVLocal, + "AV:P": v3AVPhysical, + + "AC:L": v3ACLow, + "AC:H": v3ACHigh, + + "PR:N": v3PRNone, + "PR:L": v3PRLow, + "PR:H": v3PRHigh, + + "UI:N": v3UINone, + "UI:R": v3UIRequired, + + "S:U": v3SUnchanged, + "S:C": v3SChanged, + + "C:H": v3CHigh, + "C:L": v3CLow, + "C:N": v3CNone, + + "I:H": v3IHigh, + "I:L": v3ILow, + "I:N": v3INone, + + "A:H": v3AHigh, + "A:L": v3ALow, + "A:N": v3ANone, + + "E:X": v3ENotDefined, + "E:H": v3EHigh, + "E:F": v3EFunctional, + "E:P": v3EProofOfConcept, + "E:U": v3EUnproven, + + "RL:X": v3RLNotDefined, + "RL:U": v3RLUnavailable, + "RL:W": v3RLWorkaround, + "RL:T": v3RLTemporaryFix, + "RL:O": v3RLOfficialFix, + + "RC:X": v3RCNotDefined, + "RC:C": v3RCConfirmed, + "RC:R": v3RCReasonable, + "RC:U": v3RCUnknown, + + "CR:X": v3CRNotDefined, + "CR:H": v3CRHigh, + "CR:M": v3CRMedium, + "CR:L": v3CRLow, + + "IR:X": v3IRNotDefined, + "IR:H": v3IRHigh, + "IR:M": v3IRMedium, + "IR:L": v3IRLow, + + "AR:X": v3ARNotDefined, + "AR:H": v3ARHigh, + "AR:M": v3ARMedium, + "AR:L": v3ARLow, + + "MAV:X": v3MAVNotDefined, + "MAV:N": v3MAVNetwork, + "MAV:A": v3MAVAdjacentNetwork, + "MAV:L": v3MAVLocal, + "MAV:P": v3MAVPhysical, + + "MAC:X": v3MACNotDefined, + "MAC:L": v3MACLow, + "MAC:H": v3MACHigh, + + "MPR:X": v3MMRNotDefined, + "MPR:L": v3MPRLow, + "MPR:H": v3MPRHigh, + + "MUI:X": v3MUINotDefined, + "MUI:N": v3MUINone, + "MUI:R": v3MUIRequired, + + "MMS:X": v3MSNotDefined, + "MMS:U": v3MSUnchanged, + "MMS:C": v3MSChanged, + + "MC:X": v3MCNotDefined, + "MC:H": v3MCHigh, + "MC:L": v3MCLow, + "MC:N": v3MCNone, + + "MI:X": v3MINotDefined, + "MI:H": v3MIHigh, + "MI:L": v3MILow, + "MI:N": v3MINone, + + "MA:X": v3MANotDefined, + "MA:H": v3MAHigh, + "MA:L": v3MALow, + "MA:N": v3MANone, +} + +// Get CVSS 3.x metric key. +func (m v3Metric) Key() Key { + if k, ok := v3KeyLut[m]; ok { + return k + } else { + return v3InvalidKey + } +} + +// Convert string to CVSS 3.1 metric. +func getV3Metric(version Version, s string) (v3Metric, error) { + if m, ok := v3MetricStrLut[s]; ok { + return m, nil + } else { + return v3InvalidMetric, newBadMetric(version, s) + } +} diff --git a/cvss/v3metric_string.go b/cvss/v3metric_string.go new file mode 100644 index 0000000..612ed83 --- /dev/null +++ b/cvss/v3metric_string.go @@ -0,0 +1,100 @@ +// Code generated by "stringer -linecomment -type=v3Metric"; DO NOT EDIT. + +package cvss + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[v3AVNetwork-0] + _ = x[v3AVAdjacentNetwork-1] + _ = x[v3AVLocal-2] + _ = x[v3AVPhysical-3] + _ = x[v3ACLow-4] + _ = x[v3ACHigh-5] + _ = x[v3PRNone-6] + _ = x[v3PRLow-7] + _ = x[v3PRHigh-8] + _ = x[v3UINone-9] + _ = x[v3UIRequired-10] + _ = x[v3SUnchanged-11] + _ = x[v3SChanged-12] + _ = x[v3CHigh-13] + _ = x[v3CLow-14] + _ = x[v3CNone-15] + _ = x[v3IHigh-16] + _ = x[v3ILow-17] + _ = x[v3INone-18] + _ = x[v3AHigh-19] + _ = x[v3ALow-20] + _ = x[v3ANone-21] + _ = x[v3ENotDefined-22] + _ = x[v3EHigh-23] + _ = x[v3EFunctional-24] + _ = x[v3EProofOfConcept-25] + _ = x[v3EUnproven-26] + _ = x[v3RLNotDefined-27] + _ = x[v3RLUnavailable-28] + _ = x[v3RLWorkaround-29] + _ = x[v3RLTemporaryFix-30] + _ = x[v3RLOfficialFix-31] + _ = x[v3RCNotDefined-32] + _ = x[v3RCConfirmed-33] + _ = x[v3RCReasonable-34] + _ = x[v3RCUnknown-35] + _ = x[v3CRNotDefined-36] + _ = x[v3CRHigh-37] + _ = x[v3CRMedium-38] + _ = x[v3CRLow-39] + _ = x[v3IRNotDefined-40] + _ = x[v3IRHigh-41] + _ = x[v3IRMedium-42] + _ = x[v3IRLow-43] + _ = x[v3ARNotDefined-44] + _ = x[v3ARHigh-45] + _ = x[v3ARMedium-46] + _ = x[v3ARLow-47] + _ = x[v3MAVNotDefined-48] + _ = x[v3MAVNetwork-49] + _ = x[v3MAVAdjacentNetwork-50] + _ = x[v3MAVLocal-51] + _ = x[v3MAVPhysical-52] + _ = x[v3MACNotDefined-53] + _ = x[v3MACLow-54] + _ = x[v3MACHigh-55] + _ = x[v3MMRNotDefined-56] + _ = x[v3MPRLow-57] + _ = x[v3MPRHigh-58] + _ = x[v3MUINotDefined-59] + _ = x[v3MUINone-60] + _ = x[v3MUIRequired-61] + _ = x[v3MSNotDefined-62] + _ = x[v3MSUnchanged-63] + _ = x[v3MSChanged-64] + _ = x[v3MCNotDefined-65] + _ = x[v3MCHigh-66] + _ = x[v3MCLow-67] + _ = x[v3MCNone-68] + _ = x[v3MINotDefined-69] + _ = x[v3MIHigh-70] + _ = x[v3MILow-71] + _ = x[v3MINone-72] + _ = x[v3MANotDefined-73] + _ = x[v3MAHigh-74] + _ = x[v3MALow-75] + _ = x[v3MANone-76] + _ = x[v3InvalidMetric-77] +} + +const _v3Metric_name = "AV:NAV:AAV:LAV:PAC:LAC:HPR:NPR:LPR:HUI:NUI:RS:US:CC:HC:LC:NI:HI:LI:NA:HA:LA:NE:XE:HE:FE:PE:URL:XRL:URL:WRL:TRL:ORC:XRC:CRC:RRC:UCR:XCR:HCR:MCR:LIR:XIR:HIR:MIR:LAR:XAR:HAR:MAR:LMAV:XMAV:NMAV:AMAV:LMAV:PMAC:XMAC:LMAC:HMPR:XMPR:LMPR:HMUI:XMUI:NMUI:RMMS:XMMS:UMMS:CMC:XMC:HMC:LMC:NMI:XMI:HMI:LMI:NMA:XMA:HMA:LMA:Ninvalid" + +var _v3Metric_index = [...]uint16{0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 47, 50, 53, 56, 59, 62, 65, 68, 71, 74, 77, 80, 83, 86, 89, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160, 164, 168, 172, 176, 181, 186, 191, 196, 201, 206, 211, 216, 221, 226, 231, 236, 241, 246, 251, 256, 261, 265, 269, 273, 277, 281, 285, 289, 293, 297, 301, 305, 309, 316} + +func (i v3Metric) String() string { + if i >= v3Metric(len(_v3Metric_index)-1) { + return "v3Metric(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _v3Metric_name[_v3Metric_index[i]:_v3Metric_index[i+1]] +} diff --git a/cvss/v3metric_test.go b/cvss/v3metric_test.go new file mode 100644 index 0000000..432d34a --- /dev/null +++ b/cvss/v3metric_test.go @@ -0,0 +1,366 @@ +package cvss + +import "testing" + +func TestGetV3Metric(t *testing.T) { + tests := []struct { + val string + exp v3Metric + ok bool + } { + { "AV:N", v3AVNetwork, true }, + { "AV:A", v3AVAdjacentNetwork, true }, + { "AV:L", v3AVLocal, true }, + { "AV:P", v3AVPhysical, true }, + + { "AC:L", v3ACLow, true }, + { "AC:H", v3ACHigh, true }, + + { "PR:N", v3PRNone, true }, + { "PR:L", v3PRLow, true }, + { "PR:H", v3PRHigh, true }, + + { "UI:N", v3UINone, true }, + { "UI:R", v3UIRequired, true }, + + { "S:U", v3SUnchanged, true }, + { "S:C", v3SChanged, true }, + + { "C:H", v3CHigh, true }, + { "C:L", v3CLow, true }, + { "C:N", v3CNone, true }, + + { "I:H", v3IHigh, true }, + { "I:L", v3ILow, true }, + { "I:N", v3INone, true }, + + { "A:H", v3AHigh, true }, + { "A:L", v3ALow, true }, + { "A:N", v3ANone, true }, + + { "E:X", v3ENotDefined, true }, + { "E:H", v3EHigh, true }, + { "E:F", v3EFunctional, true }, + { "E:P", v3EProofOfConcept, true }, + { "E:U", v3EUnproven, true }, + + { "RL:X", v3RLNotDefined, true }, + { "RL:U", v3RLUnavailable, true }, + { "RL:W", v3RLWorkaround, true }, + { "RL:T", v3RLTemporaryFix, true }, + { "RL:O", v3RLOfficialFix, true }, + + { "RC:X", v3RCNotDefined, true }, + { "RC:C", v3RCConfirmed, true }, + { "RC:R", v3RCReasonable, true }, + { "RC:U", v3RCUnknown, true }, + + { "CR:X", v3CRNotDefined, true }, + { "CR:H", v3CRHigh, true }, + { "CR:M", v3CRMedium, true }, + { "CR:L", v3CRLow, true }, + + { "IR:X", v3IRNotDefined, true }, + { "IR:H", v3IRHigh, true }, + { "IR:M", v3IRMedium, true }, + { "IR:L", v3IRLow, true }, + + { "AR:X", v3ARNotDefined, true }, + { "AR:H", v3ARHigh, true }, + { "AR:M", v3ARMedium, true }, + { "AR:L", v3ARLow, true }, + + { "MAV:X", v3MAVNotDefined, true }, + { "MAV:N", v3MAVNetwork, true }, + { "MAV:A", v3MAVAdjacentNetwork, true }, + { "MAV:L", v3MAVLocal, true }, + { "MAV:P", v3MAVPhysical, true }, + + { "MAC:X", v3MACNotDefined, true }, + { "MAC:L", v3MACLow, true }, + { "MAC:H", v3MACHigh, true }, + + { "MPR:X", v3MMRNotDefined, true }, + { "MPR:L", v3MPRLow, true }, + { "MPR:H", v3MPRHigh, true }, + + { "MUI:X", v3MUINotDefined, true }, + { "MUI:N", v3MUINone, true }, + { "MUI:R", v3MUIRequired, true }, + + { "MMS:X", v3MSNotDefined, true }, + { "MMS:U", v3MSUnchanged, true }, + { "MMS:C", v3MSChanged, true }, + + { "MC:X", v3MCNotDefined, true }, + { "MC:H", v3MCHigh, true }, + { "MC:L", v3MCLow, true }, + { "MC:N", v3MCNone, true }, + + { "MI:X", v3MINotDefined, true }, + { "MI:H", v3MIHigh, true }, + { "MI:L", v3MILow, true }, + { "MI:N", v3MINone, true }, + + { "MA:X", v3MANotDefined, true }, + { "MA:H", v3MAHigh, true }, + { "MA:L", v3MALow, true }, + { "MA:N", v3MANone, true }, + + { "invalid", v3InvalidMetric, false }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + got, err := getV3Metric(V31, test.val) + if test.ok && err == nil && got != test.exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) + } else if test.ok && err != nil { + t.Error(err) + } else if !test.ok && err == nil { + t.Errorf("got: \"%s\", exp: error", got) + } + }) + } +} + +func TestGetV3MetricKey(t *testing.T) { + tests := []struct { + val v3Metric + exp v3Key + } { + { v3AVNetwork, v3AttackVector }, // AV:N + { v3AVAdjacentNetwork, v3AttackVector }, // AV:A + { v3AVLocal, v3AttackVector }, // AV:L + { v3AVPhysical, v3AttackVector }, // AV:P + + { v3ACLow, v3AttackComplexity }, // AC:L + { v3ACHigh, v3AttackComplexity }, // AC:H + + { v3PRNone, v3PrivilegesRequired }, // PR:N + { v3PRLow, v3PrivilegesRequired }, // PR:L + { v3PRHigh, v3PrivilegesRequired }, // PR:H + + { v3UINone, v3UserInteraction }, // UI:N + { v3UIRequired, v3UserInteraction }, // UI:R + + { v3SUnchanged, v3Scope }, // S:U + { v3SChanged, v3Scope }, // S:C + + { v3CHigh, v3Confidentiality }, // C:H + { v3CLow, v3Confidentiality }, // C:L + { v3CNone, v3Confidentiality }, // C:N + + { v3IHigh, v3Integrity }, // I:H + { v3ILow, v3Integrity }, // I:L + { v3INone, v3Integrity }, // I:N + + { v3AHigh, v3Availability }, // A:H + { v3ALow, v3Availability }, // A:L + { v3ANone, v3Availability }, // A:N + + { v3ENotDefined, v3ExploitCodeMaturity }, // E:X + { v3EHigh, v3ExploitCodeMaturity }, // E:H + { v3EFunctional, v3ExploitCodeMaturity }, // E:F + { v3EProofOfConcept, v3ExploitCodeMaturity }, // E:P + { v3EUnproven, v3ExploitCodeMaturity }, // E:U + + { v3RLNotDefined, v3RemediationLevel }, // RL:X + { v3RLUnavailable, v3RemediationLevel }, // RL:U + { v3RLWorkaround, v3RemediationLevel }, // RL:W + { v3RLTemporaryFix, v3RemediationLevel }, // RL:T + { v3RLOfficialFix, v3RemediationLevel }, // RL:O + + { v3RCNotDefined, v3ReportConfidence }, // RC:X + { v3RCConfirmed, v3ReportConfidence }, // RC:C + { v3RCReasonable, v3ReportConfidence }, // RC:R + { v3RCUnknown, v3ReportConfidence }, // RC:U + + { v3CRNotDefined, v3ConfidentialityRequirement }, // CR:X + { v3CRHigh, v3ConfidentialityRequirement }, // CR:H + { v3CRMedium, v3ConfidentialityRequirement }, // CR:M + { v3CRLow, v3ConfidentialityRequirement }, // CR:L + + { v3IRNotDefined, v3IntegrityRequirement }, // IR:X + { v3IRHigh, v3IntegrityRequirement }, // IR:H + { v3IRMedium, v3IntegrityRequirement }, // IR:M + { v3IRLow, v3IntegrityRequirement }, // IR:L + + { v3ARNotDefined, v3AvailabilityRequirement }, // AR:X + { v3ARHigh, v3AvailabilityRequirement }, // AR:H + { v3ARMedium, v3AvailabilityRequirement }, // AR:M + { v3ARLow, v3AvailabilityRequirement }, // AR:L + + { v3MAVNotDefined, v3ModifiedAttackVector }, // MAV:X + { v3MAVNetwork, v3ModifiedAttackVector }, // MAV:N + { v3MAVAdjacentNetwork, v3ModifiedAttackVector }, // MAV:A + { v3MAVLocal, v3ModifiedAttackVector }, // MAV:L + { v3MAVPhysical, v3ModifiedAttackVector }, // MAV:P + + { v3MACNotDefined, v3ModifiedAttackComplexity }, // MAC:X + { v3MACLow, v3ModifiedAttackComplexity }, // MAC:L + { v3MACHigh, v3ModifiedAttackComplexity }, // MAC:H + + { v3MMRNotDefined, v3ModifiedPrivilegesRequired }, // MPR:X + { v3MPRLow, v3ModifiedPrivilegesRequired }, // MPR:L + { v3MPRHigh, v3ModifiedPrivilegesRequired }, // MPR:H + + { v3MUINotDefined, v3ModifiedUserInteraction }, // MUI:X + { v3MUINone, v3ModifiedUserInteraction }, // MUI:N + { v3MUIRequired, v3ModifiedUserInteraction }, // MUI:R + + { v3MSNotDefined, v3ModifiedScope }, // MMS:X + { v3MSUnchanged, v3ModifiedConfidentiality }, // MMS:U + { v3MSChanged, v3ModifiedIntegrity }, // MMS:C + + { v3MCNotDefined, v3ModifiedConfidentiality }, // MC:X + { v3MCHigh, v3ModifiedConfidentiality }, // MC:H + { v3MCLow, v3ModifiedConfidentiality }, // MC:L + { v3MCNone, v3ModifiedConfidentiality }, // MC:N + + { v3MINotDefined, v3ModifiedIntegrity }, // MI:X + { v3MIHigh, v3ModifiedIntegrity }, // MI:H + { v3MILow, v3ModifiedIntegrity }, // MI:L + { v3MINone, v3ModifiedIntegrity }, // MI:N + + { v3MANotDefined, v3ModifiedAvailability }, // MA:X + { v3MAHigh, v3ModifiedAvailability }, // MA:H + { v3MALow, v3ModifiedAvailability }, // MA:L + { v3MANone, v3ModifiedAvailability }, // MA:N + } + + for _, test := range(tests) { + t.Run(test.val.String(), func(t *testing.T) { + got := test.val.Key() + if got != test.exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) + } + }) + } +} + +func TestV3MetricString(t *testing.T) { + tests := []struct { + val v3Metric + exp string + } { + { v3AVNetwork, "AV:N" }, + { v3AVAdjacentNetwork, "AV:A" }, + { v3AVLocal, "AV:L" }, + { v3AVPhysical, "AV:P" }, + + { v3ACLow, "AC:L" }, + { v3ACHigh, "AC:H" }, + + { v3PRNone, "PR:N" }, + { v3PRLow, "PR:L" }, + { v3PRHigh, "PR:H" }, + + { v3UINone, "UI:N" }, + { v3UIRequired, "UI:R" }, + + { v3SUnchanged, "S:U" }, + { v3SChanged, "S:C" }, + + { v3CHigh, "C:H" }, + { v3CLow, "C:L" }, + { v3CNone, "C:N" }, + + { v3IHigh, "I:H" }, + { v3ILow, "I:L" }, + { v3INone, "I:N" }, + + { v3AHigh, "A:H" }, + { v3ALow, "A:L" }, + { v3ANone, "A:N" }, + + { v3ENotDefined, "E:X" }, + { v3EHigh, "E:H" }, + { v3EFunctional, "E:F" }, + { v3EProofOfConcept, "E:P" }, + { v3EUnproven, "E:U" }, + + { v3RLNotDefined, "RL:X" }, + { v3RLUnavailable, "RL:U" }, + { v3RLWorkaround, "RL:W" }, + { v3RLTemporaryFix, "RL:T" }, + { v3RLOfficialFix, "RL:O" }, + + { v3RCNotDefined, "RC:X" }, + { v3RCConfirmed, "RC:C" }, + { v3RCReasonable, "RC:R" }, + { v3RCUnknown, "RC:U" }, + + { v3CRNotDefined, "CR:X" }, + { v3CRHigh, "CR:H" }, + { v3CRMedium, "CR:M" }, + { v3CRLow, "CR:L" }, + + { v3IRNotDefined, "IR:X" }, + { v3IRHigh, "IR:H" }, + { v3IRMedium, "IR:M" }, + { v3IRLow, "IR:L" }, + + { v3ARNotDefined, "AR:X" }, + { v3ARHigh, "AR:H" }, + { v3ARMedium, "AR:M" }, + { v3ARLow, "AR:L" }, + + { v3MAVNotDefined, "MAV:X" }, + { v3MAVNetwork, "MAV:N" }, + { v3MAVAdjacentNetwork, "MAV:A" }, + { v3MAVLocal, "MAV:L" }, + { v3MAVPhysical, "MAV:P" }, + + { v3MACNotDefined, "MAC:X" }, + { v3MACLow, "MAC:L" }, + { v3MACHigh, "MAC:H" }, + + { v3MMRNotDefined, "MPR:X" }, + { v3MPRLow, "MPR:L" }, + { v3MPRHigh, "MPR:H" }, + + { v3MUINotDefined, "MUI:X" }, + { v3MUINone, "MUI:N" }, + { v3MUIRequired, "MUI:R" }, + + { v3MSNotDefined, "MMS:X" }, + { v3MSUnchanged, "MMS:U" }, + { v3MSChanged, "MMS:C" }, + + { v3MCNotDefined, "MC:X" }, + { v3MCHigh, "MC:H" }, + { v3MCLow, "MC:L" }, + { v3MCNone, "MC:N" }, + + { v3MINotDefined, "MI:X" }, + { v3MIHigh, "MI:H" }, + { v3MILow, "MI:L" }, + { v3MINone, "MI:N" }, + + { v3MANotDefined, "MA:X" }, + { v3MAHigh, "MA:H" }, + { v3MALow, "MA:L" }, + { v3MANone, "MA:N" }, + + { v3Metric(255), "v3Metric(255)" }, + } + + for _, test := range(tests) { + t.Run(test.val.String(), func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) + } + }) + } +} + +func TestInvalidV3MetricKey(t *testing.T) { + got := v3Metric(255).Key() + exp := v3InvalidKey + + if got != exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) + } +} diff --git a/cvss/vector.go b/cvss/vector.go new file mode 100644 index 0000000..3e465c5 --- /dev/null +++ b/cvss/vector.go @@ -0,0 +1,36 @@ +package cvss + +import ( + "fmt" +) + +// CVSS metric vector. +type Vector interface { + // Get CVSS version. + Version() Version + + // Get CVSS vector string. + String() string + + // Return metrics in this vector. + Metrics() []Metric + + // Unmarshal vector from JSON. + // UnmarshalJSON(b []byte) error +} + +// Create new CVSS vector from vector string. +func NewVector(s string) (Vector, error) { + if isV31VectorString(s) { + // create CVSS v3.1 vector. + return newV31Vector(s) + } else if isV30VectorString(s) { + // create CVSS v3.0 vector. + return newV30Vector(s) + } else if isV2VectorString(s) { + // create CVSS v2 vector. + return newV2Vector(s) + } else { + return nil, fmt.Errorf("invalid CVSS vector: %s", s) + } +} diff --git a/cvss/vector_test.go b/cvss/vector_test.go new file mode 100644 index 0000000..a9ea820 --- /dev/null +++ b/cvss/vector_test.go @@ -0,0 +1,659 @@ +package cvss + +import "testing" + +func TestNewVector(t *testing.T) { + passTests := []string { + "AV:A/AC:H/Au:S/C:C/I:C/A:C", + "AV:A/AC:H/Au:S/C:N/I:N/A:P", + "AV:A/AC:H/Au:S/C:P/I:P/A:P", + "AV:A/AC:L/Au:N/C:C/I:C/A:C", + "AV:A/AC:L/Au:N/C:N/I:N/A:C", + "AV:A/AC:L/Au:N/C:N/I:N/A:P", + "AV:A/AC:L/Au:N/C:N/I:P/A:N", + "AV:A/AC:L/Au:N/C:P/I:N/A:C", + "AV:A/AC:L/Au:N/C:P/I:N/A:N", + "AV:A/AC:L/Au:N/C:P/I:P/A:N", + "AV:A/AC:L/Au:N/C:P/I:P/A:P", + "AV:A/AC:L/Au:S/C:C/I:C/A:C", + "AV:A/AC:L/Au:S/C:C/I:N/A:C", + "AV:A/AC:L/Au:S/C:N/I:N/A:P", + "AV:A/AC:L/Au:S/C:N/I:P/A:N", + "AV:A/AC:L/Au:S/C:P/I:N/A:N", + "AV:A/AC:L/Au:S/C:P/I:N/A:P", + "AV:A/AC:L/Au:S/C:P/I:P/A:N", + "AV:A/AC:L/Au:S/C:P/I:P/A:P", + "AV:A/AC:M/Au:N/C:C/I:C/A:C", + "AV:A/AC:M/Au:N/C:N/I:N/A:C", + "AV:A/AC:M/Au:N/C:N/I:N/A:P", + "AV:A/AC:M/Au:N/C:N/I:P/A:N", + "AV:A/AC:M/Au:N/C:N/I:P/A:P", + "AV:A/AC:M/Au:N/C:P/I:N/A:N", + "AV:A/AC:M/Au:N/C:P/I:P/A:N", + "AV:A/AC:M/Au:N/C:P/I:P/A:P", + "AV:A/AC:M/Au:S/C:C/I:C/A:C", + "AV:A/AC:M/Au:S/C:N/I:N/A:P", + "AV:A/AC:M/Au:S/C:N/I:P/A:N", + "AV:A/AC:M/Au:S/C:P/I:N/A:N", + "AV:A/AC:M/Au:S/C:P/I:P/A:P", + "AV:L/AC:H/Au:N/C:C/I:C/A:C", + "AV:L/AC:H/Au:N/C:N/I:N/A:C", + "AV:L/AC:H/Au:N/C:N/I:N/A:P", + "AV:L/AC:H/Au:N/C:N/I:P/A:N", + "AV:L/AC:H/Au:N/C:P/I:N/A:N", + "AV:L/AC:H/Au:N/C:P/I:P/A:N", + "AV:L/AC:H/Au:S/C:P/I:P/A:P", + "AV:L/AC:L/Au:N/C:C/I:C/A:C", + "AV:L/AC:L/Au:N/C:C/I:C/A:N", + "AV:L/AC:L/Au:N/C:C/I:N/A:C", + "AV:L/AC:L/Au:N/C:C/I:N/A:N", + "AV:L/AC:L/Au:N/C:C/I:P/A:N", + "AV:L/AC:L/Au:N/C:N/I:C/A:C", + "AV:L/AC:L/Au:N/C:N/I:C/A:N", + "AV:L/AC:L/Au:N/C:N/I:N/A:C", + "AV:L/AC:L/Au:N/C:N/I:N/A:P", + "AV:L/AC:L/Au:N/C:N/I:P/A:C", + "AV:L/AC:L/Au:N/C:N/I:P/A:N", + "AV:L/AC:L/Au:N/C:N/I:P/A:P", + "AV:L/AC:L/Au:N/C:P/I:N/A:C", + "AV:L/AC:L/Au:N/C:P/I:N/A:N", + "AV:L/AC:L/Au:N/C:P/I:N/A:P", + "AV:L/AC:L/Au:N/C:P/I:P/A:C", + "AV:L/AC:L/Au:N/C:P/I:P/A:N", + "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "AV:L/AC:L/Au:S/C:P/I:N/A:N", + "AV:L/AC:M/Au:N/C:C/I:C/A:C", + "AV:L/AC:M/Au:N/C:C/I:N/A:N", + "AV:L/AC:M/Au:N/C:N/I:C/A:C", + "AV:L/AC:M/Au:N/C:N/I:C/A:N", + "AV:L/AC:M/Au:N/C:N/I:N/A:C", + "AV:L/AC:M/Au:N/C:N/I:N/A:P", + "AV:L/AC:M/Au:N/C:N/I:P/A:C", + "AV:L/AC:M/Au:N/C:N/I:P/A:N", + "AV:L/AC:M/Au:N/C:N/I:P/A:P", + "AV:L/AC:M/Au:N/C:P/I:N/A:N", + "AV:L/AC:M/Au:N/C:P/I:N/A:P", + "AV:L/AC:M/Au:N/C:P/I:P/A:C", + "AV:L/AC:M/Au:N/C:P/I:P/A:N", + "AV:L/AC:M/Au:N/C:P/I:P/A:P", + "AV:N/AC:H/Au:N/C:C/I:C/A:C", + "AV:N/AC:H/Au:N/C:N/I:N/A:C", + "AV:N/AC:H/Au:N/C:N/I:N/A:P", + "AV:N/AC:H/Au:N/C:N/I:P/A:N", + "AV:N/AC:H/Au:N/C:N/I:P/A:P", + "AV:N/AC:H/Au:N/C:P/I:N/A:N", + "AV:N/AC:H/Au:N/C:P/I:N/A:P", + "AV:N/AC:H/Au:N/C:P/I:P/A:N", + "AV:N/AC:H/Au:N/C:P/I:P/A:P", + "AV:N/AC:H/Au:S/C:N/I:P/A:N", + "AV:N/AC:H/Au:S/C:P/I:N/A:N", + "AV:N/AC:H/Au:S/C:P/I:P/A:N", + "AV:N/AC:H/Au:S/C:P/I:P/A:P", + "AV:N/AC:L/Au:N/C:C/I:C/A:C", + "AV:N/AC:L/Au:N/C:C/I:C/A:N", + "AV:N/AC:L/Au:N/C:C/I:N/A:C", + "AV:N/AC:L/Au:N/C:C/I:N/A:N", + "AV:N/AC:L/Au:N/C:N/I:C/A:C", + "AV:N/AC:L/Au:N/C:N/I:C/A:N", + "AV:N/AC:L/Au:N/C:N/I:N/A:C", + "AV:N/AC:L/Au:N/C:N/I:N/A:P", + "AV:N/AC:L/Au:N/C:N/I:P/A:N", + "AV:N/AC:L/Au:N/C:N/I:P/A:P", + "AV:N/AC:L/Au:N/C:P/I:N/A:C", + "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "AV:N/AC:L/Au:N/C:P/I:N/A:P", + "AV:N/AC:L/Au:N/C:P/I:P/A:C", + "AV:N/AC:L/Au:N/C:P/I:P/A:N", + "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "AV:N/AC:L/Au:S/C:C/I:C/A:C", + "AV:N/AC:L/Au:S/C:C/I:C/A:N", + "AV:N/AC:L/Au:S/C:C/I:C/A:P", + "AV:N/AC:L/Au:S/C:C/I:N/A:N", + "AV:N/AC:L/Au:S/C:C/I:P/A:C", + "AV:N/AC:L/Au:S/C:C/I:P/A:N", + "AV:N/AC:L/Au:S/C:N/I:C/A:C", + "AV:N/AC:L/Au:S/C:N/I:C/A:N", + "AV:N/AC:L/Au:S/C:N/I:N/A:C", + "AV:N/AC:L/Au:S/C:N/I:N/A:P", + "AV:N/AC:L/Au:S/C:N/I:P/A:C", + "AV:N/AC:L/Au:S/C:N/I:P/A:N", + "AV:N/AC:L/Au:S/C:N/I:P/A:P", + "AV:N/AC:L/Au:S/C:P/I:N/A:C", + "AV:N/AC:L/Au:S/C:P/I:N/A:N", + "AV:N/AC:L/Au:S/C:P/I:N/A:P", + "AV:N/AC:L/Au:S/C:P/I:P/A:C", + "AV:N/AC:L/Au:S/C:P/I:P/A:N", + "AV:N/AC:L/Au:S/C:P/I:P/A:P", + "AV:N/AC:M/Au:N/C:C/I:C/A:C", + "AV:N/AC:M/Au:N/C:C/I:C/A:N", + "AV:N/AC:M/Au:N/C:C/I:N/A:N", + "AV:N/AC:M/Au:N/C:C/I:P/A:P", + "AV:N/AC:M/Au:N/C:N/I:C/A:C", + "AV:N/AC:M/Au:N/C:N/I:C/A:N", + "AV:N/AC:M/Au:N/C:N/I:N/A:C", + "AV:N/AC:M/Au:N/C:N/I:N/A:P", + "AV:N/AC:M/Au:N/C:N/I:P/A:N", + "AV:N/AC:M/Au:N/C:N/I:P/A:P", + "AV:N/AC:M/Au:N/C:P/I:C/A:C", + "AV:N/AC:M/Au:N/C:P/I:N/A:N", + "AV:N/AC:M/Au:N/C:P/I:N/A:P", + "AV:N/AC:M/Au:N/C:P/I:P/A:C", + "AV:N/AC:M/Au:N/C:P/I:P/A:N", + "AV:N/AC:M/Au:N/C:P/I:P/A:P", + "AV:N/AC:M/Au:S/C:C/I:C/A:C", + "AV:N/AC:M/Au:S/C:C/I:N/A:C", + "AV:N/AC:M/Au:S/C:C/I:N/A:N", + "AV:N/AC:M/Au:S/C:N/I:C/A:C", + "AV:N/AC:M/Au:S/C:N/I:N/A:C", + "AV:N/AC:M/Au:S/C:N/I:N/A:P", + "AV:N/AC:M/Au:S/C:N/I:P/A:C", + "AV:N/AC:M/Au:S/C:N/I:P/A:N", + "AV:N/AC:M/Au:S/C:N/I:P/A:P", + "AV:N/AC:M/Au:S/C:P/I:N/A:N", + "AV:N/AC:M/Au:S/C:P/I:N/A:P", + "AV:N/AC:M/Au:S/C:P/I:P/A:C", + "AV:N/AC:M/Au:S/C:P/I:P/A:N", + "AV:N/AC:M/Au:S/C:P/I:P/A:P", + + "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:L", + "CVSS:3.0/AV:A/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.0/AV:A/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.0/AV:A/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", + + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:C/C:L/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:H/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:N/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:L", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L", + "CVSS:3.1/AV:P/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:P/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:P/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "CVSS:3.1/AV:P/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + } + + for _, test := range(passTests) { + t.Run(test, func(t *testing.T) { + vec, err := NewVector(test) + if err != nil { + t.Error(err) + return + } + + got := vec.String() + if got != test { + t.Errorf("got: \"%s\", exp: \"%s\"", got, test) + } + }) + } + + failTests := []struct { + val string + exp string + } { + { + val: "AV:A/junk/Au:S/C:C/I:C/A:C", + exp: "invalid CVSS vector: AV:A/junk/Au:S/C:C/I:C/A:C", + }, { + val: "CVSS:3.0/junk/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", + exp: "invalid CVSS vector: CVSS:3.0/junk/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", + }, { + val: "CVSS:3.1/AV:A/AC:H/junk/UI:N/S:U/C:H/I:H/A:H", + exp: "invalid CVSS vector: CVSS:3.1/AV:A/AC:H/junk/UI:N/S:U/C:H/I:H/A:H", + }, + } + + for _, test := range(failTests) { + t.Run(test.val, func(t *testing.T) { + got, err := NewVector(test.val) + if err != nil && err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } else if err == nil { + t.Errorf("got \"%s\", exp error", got) + } + }) + } +} diff --git a/cvss/version.go b/cvss/version.go new file mode 100644 index 0000000..c46c893 --- /dev/null +++ b/cvss/version.go @@ -0,0 +1,12 @@ +package cvss + +//go:generate stringer -linecomment -type=Version + +// CVSS version +type Version byte + +const ( + V20 Version = iota // 2.0 + V30 // 3.0 + V31 // 3.1 +) diff --git a/cvss/version_string.go b/cvss/version_string.go new file mode 100644 index 0000000..03e8904 --- /dev/null +++ b/cvss/version_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -linecomment -type=Version"; DO NOT EDIT. + +package cvss + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V20-0] + _ = x[V30-1] + _ = x[V31-2] +} + +const _Version_name = "2.03.03.1" + +var _Version_index = [...]uint8{0, 3, 6, 9} + +func (i Version) String() string { + if i >= Version(len(_Version_index)-1) { + return "Version(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Version_name[_Version_index[i]:_Version_index[i+1]] +} diff --git a/cvss/version_test.go b/cvss/version_test.go new file mode 100644 index 0000000..e010094 --- /dev/null +++ b/cvss/version_test.go @@ -0,0 +1,24 @@ +package cvss + +import "testing" + +func TestVersionString(t *testing.T) { + tests := []struct { + val Version + exp string + } { + { V20, "2.0" }, + { V30, "3.0" }, + { V31, "3.1" }, + { Version(255), "Version(255)" }, + } + + for _, test := range(tests) { + t.Run(test.val.String(), func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/cveid.go b/feed/cveid.go new file mode 100644 index 0000000..8796029 --- /dev/null +++ b/feed/cveid.go @@ -0,0 +1,112 @@ +package feed + +import ( + "encoding/json" + "fmt" + "regexp" + "strconv" +) + +// CVE ID +type CveId uint32 + +var cveIdRe = regexp.MustCompile("\\ACVE-(\\d{4})-(\\d{1,8})\\z") + +// parse year component of CVE ID +func parseCveIdYear(s string) (uint16, error) { + // parse year, check for error + year, err := strconv.ParseUint(s, 10, 16) + if err != nil { + return 0, err + } + + // check bounds + if year < 2000 || year > 2127 { + return 0, fmt.Errorf("year out of bounds: %s", s) + } + + // return value + return uint16(year), nil +} + +// parse number component of CVE ID +func parseCveIdNum(s string) (uint32, error) { + // parse number, check for error + num, err := strconv.ParseUint(s, 10, 32) + if err != nil { + return 0, err + } + + // check bounds + if num > 0x01ffffff { + return 0, fmt.Errorf("number out of bounds: %d", num) + } + + // return value + return uint32(num), nil +} + +// Encode CVE ID as uint32. +func encodeCveId(year uint16, num uint32) uint32 { + return uint32((uint32((year - 2000) & 0x7f) << 25) | (num & 0x01ffffff)) +} + +// Create CVE ID from string. +func NewCveId(s string) (CveId, error) { + // match components, check for error + md := cveIdRe.FindStringSubmatch(s) + if len(md) != 3 { + return CveId(0), fmt.Errorf("invalid CVE ID: %s", s) + } + + // parse year, check for error + year, err := parseCveIdYear(md[1]) + if err != nil { + return CveId(0), err + } + + // parse number, check for error + num, err := parseCveIdNum(md[2]) + if err != nil { + return CveId(0), err + } + + // encode and return result + return CveId(encodeCveId(year, num)), nil +} + +// Unmarshal CVE ID from JSON. +func (me *CveId) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // parse year, check for error + r, err := NewCveId(s) + if err != nil { + return err + } + + // serialize ID + *me = r + + // return success + return nil +} + +// Get year component. +func (me CveId) Year() uint16 { + return uint16((uint32(me) >> 25) & 0x7f) + 2000 +} + +// Get number component. +func (me CveId) Number() uint32 { + return (uint32(me) & 0x01ffffff) +} + +// Return string representation of CVE ID. +func (me CveId) String() string { + return fmt.Sprintf("CVE-%04d-%04d", me.Year(), me.Number()) +} diff --git a/feed/cveid_test.go b/feed/cveid_test.go new file mode 100644 index 0000000..8df3642 --- /dev/null +++ b/feed/cveid_test.go @@ -0,0 +1,295 @@ +package feed + +import ( + "encoding/json" + "fmt" + "strconv" + "testing" +) + +func TestParseCveIdYear(t *testing.T) { + if got, err := parseCveIdYear("asdf"); err == nil { + t.Errorf("got %d, exp error", got) + return + } + + goodTests := []struct { + val string + exp uint16 + } { + { "2000", 2000 }, + { "2001", 2001 }, + { "2100", 2100 }, + } + + for _, test := range(goodTests) { + t.Run(test.val, func(t *testing.T) { + got, err := parseCveIdYear(test.val) + if err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got %d, exp %d", got, test.exp) + return + } + }) + } + + badTests := []struct { + val string + exp string + } { + { "0000", "year out of bounds: 0000" }, + { "0001", "year out of bounds: 0001" }, + { "1999", "year out of bounds: 1999" }, + { "2128", "year out of bounds: 2128" }, + { "9999", "year out of bounds: 9999" }, + } + + for _, test := range(badTests) { + t.Run(test.val, func(t *testing.T) { + if got, err := parseCveIdYear(test.val); err == nil { + t.Errorf("got %d, exp error", got) + return + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +func TestParseCveIdNum(t *testing.T) { + if got, err := parseCveIdNum("asdf"); err == nil { + t.Errorf("got %d, exp error", got) + return + } + + goodTests := []struct { + val string + exp uint32 + } { + { "0", 0 }, + { "0001", 1 }, + { "2100", 2100 }, + { "999999", 999999 }, + { "33554431", 33554431 }, + } + + for _, test := range(goodTests) { + t.Run(test.val, func(t *testing.T) { + got, err := parseCveIdNum(test.val) + if err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got %d, exp %d", got, test.exp) + return + } + }) + } + + badTests := []struct { + val string + exp string + } { + { "33554432", "number out of bounds: 33554432" }, + { "99999999", "number out of bounds: 99999999" }, + } + + for _, test := range(badTests) { + t.Run(test.val, func(t *testing.T) { + if got, err := parseCveIdNum(test.val); err == nil { + t.Errorf("got %d, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +func TestNewCveId(t *testing.T) { + badMatchTests := []string { + "", + "\nCVE-2002-1234", + "CVE-2002-1234\n", + "CVE20021234\n", + "asdf", + } + + for _, test := range(badMatchTests) { + t.Run(test, func(t *testing.T) { + exp := fmt.Sprintf("invalid CVE ID: %s", test) + if got, err := NewCveId(test); err == nil { + t.Errorf("got %s, exp error", got) + } else if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } + }) + } + + badYearTests := []struct { + val string + exp string + } { + { "CVE-0000-1234", "year out of bounds: 0000" }, + { "CVE-1999-1234", "year out of bounds: 1999" }, + { "CVE-2128-1234", "year out of bounds: 2128" }, + { "CVE-9999-1234", "year out of bounds: 9999" }, + } + + for _, test := range(badYearTests) { + t.Run(test.val, func(t *testing.T) { + if got, err := NewCveId(test.val); err == nil { + t.Errorf("got %s, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } + + badNumTests := []struct { + val string + exp string + } { + { "CVE-2000-33554432", "number out of bounds: 33554432" }, + { "CVE-2000-99999999", "number out of bounds: 99999999" }, + } + + for _, test := range(badNumTests) { + t.Run(test.val, func(t *testing.T) { + if got, err := NewCveId(test.val); err == nil { + t.Errorf("got %s, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } + + goodTests := []string { + "CVE-2000-0", + "CVE-2127-0", + "CVE-2000-33554431", + "CVE-2127-33554431", + } + + for _, val := range(goodTests) { + t.Run(val, func(t *testing.T) { + if _, err := NewCveId(val); err != nil { + t.Error(err) + } + }) + } +} +func TestCveIdYear(t *testing.T) { + for year := 2000; year < 2127; year++ { + t.Run(strconv.FormatInt(int64(year), 10), func(t *testing.T) { + // expected value + exp := uint16(year) + + // build cve id, check for error + id, err := NewCveId(fmt.Sprintf("CVE-%04d-0000", year)) + if err != nil { + t.Error(err) + return + } + + // check year + got := id.Year() + if got != exp { + t.Errorf("got %d, exp %d", got, exp) + } + }) + } +} + +func TestCveIdNumber(t *testing.T) { + for num := 0; num < 99999; num++ { + t.Run(strconv.FormatInt(int64(num), 10), func(t *testing.T) { + // expected value + exp := uint32(num) + + // build cve id, check for error + id, err := NewCveId(fmt.Sprintf("CVE-2000-%04d", num)) + if err != nil { + t.Error(err) + return + } + + // check number + got := id.Number() + if got != exp { + t.Errorf("got %d, exp %d", got, exp) + } + }) + } +} + +func TestCveIdUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val CveId + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestCveIdUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "invalid CVE ID: foo" + var val CveId + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestCveIdUnmarshalValid(t *testing.T) { + tests := []struct { + val string + expYear uint16 + expNum uint32 + exp string + } { + { "\"CVE-2000-0\"", 2000, 0, "CVE-2000-0000" }, + { "\"CVE-2000-1234\"", 2000, 1234, "CVE-2000-1234" }, + { "\"CVE-2000-33554431\"", 2000, 33554431, "CVE-2000-33554431" }, + { "\"CVE-2127-0\"", 2127, 0, "CVE-2127-0000" }, + { "\"CVE-2127-1234\"", 2127, 1234, "CVE-2127-1234" }, + { "\"CVE-2127-33554431\"", 2127, 33554431, "CVE-2127-33554431" }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got CveId + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + // check year + if got.Year() != test.expYear { + t.Errorf("got \"%d\", exp \"%d\"", got.Year(), test.expYear) + } + + // check year + if got.Number() != test.expNum { + t.Errorf("got \"%d\", exp \"%d\"", got.Number(), test.expNum) + } + + // check string + if got.String() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got.String(), test.exp) + } + }) + } +} diff --git a/feed/dataformat.go b/feed/dataformat.go new file mode 100644 index 0000000..bb3f8f8 --- /dev/null +++ b/feed/dataformat.go @@ -0,0 +1,36 @@ +package feed + +//go:generate stringer -linecomment -type=DataFormat + +import ( + "encoding/json" + "fmt" +) + +// Data format for NVD feeds and feed items. +type DataFormat byte + +const ( + MitreFormat DataFormat = iota // MITRE +) + +// Unmarshal DataFormat from JSON. +func (me *DataFormat) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "MITRE": + *me = MitreFormat + default: + // return error + return fmt.Errorf("unknown data format: %s", s) + } + + // return success + return nil +} diff --git a/feed/dataformat_string.go b/feed/dataformat_string.go new file mode 100644 index 0000000..4b755f4 --- /dev/null +++ b/feed/dataformat_string.go @@ -0,0 +1,23 @@ +// Code generated by "stringer -linecomment -type=DataFormat"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[MitreFormat-0] +} + +const _DataFormat_name = "MITRE" + +var _DataFormat_index = [...]uint8{0, 5} + +func (i DataFormat) String() string { + if i >= DataFormat(len(_DataFormat_index)-1) { + return "DataFormat(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _DataFormat_name[_DataFormat_index[i]:_DataFormat_index[i+1]] +} diff --git a/feed/dataformat_test.go b/feed/dataformat_test.go new file mode 100644 index 0000000..efb4986 --- /dev/null +++ b/feed/dataformat_test.go @@ -0,0 +1,65 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestDataFormatUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val DataFormat + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestDataFormatUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown data format: foo" + var val DataFormat + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestDataFormatUnmarshalValid(t *testing.T) { + test := []byte(`"MITRE"`) + exp := MitreFormat + var got DataFormat + + if err := json.Unmarshal(test, &got); err != nil { + t.Error(err) + return + } + + if got != exp { + t.Errorf("got \"%s\", exp \"%s\"", got, exp) + } +} + +func TestDataFormatString(t *testing.T) { + tests := []struct { + val DataFormat + exp string + } { + { MitreFormat, "MITRE" }, + { DataFormat(255), "DataFormat(255)" }, + } + + for _, test := range(tests) { + t.Run(test.val.String(), func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/datatype.go b/feed/datatype.go new file mode 100644 index 0000000..6eaa145 --- /dev/null +++ b/feed/datatype.go @@ -0,0 +1,36 @@ +package feed + +//go:generate stringer -linecomment -type=DataType + +import ( + "encoding/json" + "fmt" +) + +// Data type for NVD feeds and feed items. +type DataType byte + +const ( + CveType DataType = iota // CVE +) + +// Unmarshal DataType from JSON. +func (me *DataType) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "CVE": + *me = CveType + default: + // return error + return fmt.Errorf("unknown data type: %s", s) + } + + // return success + return nil +} diff --git a/feed/datatype_string.go b/feed/datatype_string.go new file mode 100644 index 0000000..f126add --- /dev/null +++ b/feed/datatype_string.go @@ -0,0 +1,23 @@ +// Code generated by "stringer -linecomment -type=DataType"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[CveType-0] +} + +const _DataType_name = "CVE" + +var _DataType_index = [...]uint8{0, 3} + +func (i DataType) String() string { + if i >= DataType(len(_DataType_index)-1) { + return "DataType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _DataType_name[_DataType_index[i]:_DataType_index[i+1]] +} diff --git a/feed/datatype_test.go b/feed/datatype_test.go new file mode 100644 index 0000000..05f6a74 --- /dev/null +++ b/feed/datatype_test.go @@ -0,0 +1,65 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestDataTypeUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val DataType + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestDataTypeUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown data type: foo" + var val DataType + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestDataTypeUnmarshalValid(t *testing.T) { + test := []byte(`"CVE"`) + exp := CveType + var got DataType + + if err := json.Unmarshal(test, &got); err != nil { + t.Error(err) + return + } + + if got != exp { + t.Errorf("got \"%s\", exp \"%s\"", got, exp) + } +} + +func TestDataTypeString(t *testing.T) { + tests := []struct { + val DataType + exp string + } { + { CveType, "CVE" }, + { DataType(255), "DataType(255)" }, + } + + for _, test := range(tests) { + t.Run(test.val.String(), func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/dataversion.go b/feed/dataversion.go new file mode 100644 index 0000000..c6f1b8d --- /dev/null +++ b/feed/dataversion.go @@ -0,0 +1,36 @@ +package feed + +//go:generate stringer -linecomment -type=DataVersion + +import ( + "encoding/json" + "fmt" +) + +// Data version for NVD feeds and feed items. +type DataVersion byte + +const ( + V40 DataVersion = iota // 4.0 +) + +// Unmarshal data version from JSON. +func (me *DataVersion) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "4.0": + *me = V40 + default: + // return error + return fmt.Errorf("unknown data version: %s", s) + } + + // return success + return nil +} diff --git a/feed/dataversion_string.go b/feed/dataversion_string.go new file mode 100644 index 0000000..26a0fdb --- /dev/null +++ b/feed/dataversion_string.go @@ -0,0 +1,23 @@ +// Code generated by "stringer -linecomment -type=DataVersion"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V40-0] +} + +const _DataVersion_name = "4.0" + +var _DataVersion_index = [...]uint8{0, 3} + +func (i DataVersion) String() string { + if i >= DataVersion(len(_DataVersion_index)-1) { + return "DataVersion(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _DataVersion_name[_DataVersion_index[i]:_DataVersion_index[i+1]] +} diff --git a/feed/dataversion_test.go b/feed/dataversion_test.go new file mode 100644 index 0000000..6bf683c --- /dev/null +++ b/feed/dataversion_test.go @@ -0,0 +1,65 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestDataVersionUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val DataVersion + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestDataVersionUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown data version: foo" + var val DataVersion + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestDataVersionUnmarshalValid(t *testing.T) { + test := []byte(`"4.0"`) + exp := V40 + var got DataVersion + + if err := json.Unmarshal(test, &got); err != nil { + t.Error(err) + return + } + + if got != exp { + t.Errorf("got \"%s\", exp \"%s\"", got, exp) + } +} + +func TestDataVersionString(t *testing.T) { + tests := []struct { + val DataVersion + exp string + } { + { V40, "4.0" }, + { DataVersion(255), "DataVersion(255)" }, + } + + for _, test := range(tests) { + t.Run(test.val.String(), func(t *testing.T) { + got := test.val.String() + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/feed.go b/feed/feed.go new file mode 100644 index 0000000..bdf260c --- /dev/null +++ b/feed/feed.go @@ -0,0 +1,255 @@ +// NVD JSON feed parser. +package feed + +// import "nvd/internal/cvss" + +// TODO: parse cpe + +// CVE metadata +type CveMetadata struct { + // CVE ID + Id CveId `json:"ID"` + + // CVE assigner email address + Assigner string `json:"ASSIGNER"` +} + +// CVE description string. +type Description struct { + // Language code + Lang string `json:"lang"` + + // String value + Value string `json:"value"` +} + +// CVE problem type +type CveProblemType struct { + // problem type descriptions + Descriptions []Description `json:"description"` +} + +// Slice of CVE problem types. +type CveProblemTypes struct { + // problem types + ProblemTypes []CveProblemType `json:"problemtype_data"` +} + +// CVE reference +type CveReference struct { + // reference URL + Url string `json:"url"` + + // reference name + Name string `json:"name"` + + // reference source + RefSource string `json:"refsource"` + + // tags + Tags []string `json:"tags"` +} + +// Slice of CVE references +type CveReferences struct { + References []CveReference `json:"reference_data"` +} + +// CVE item descriptions +type CveDescription struct { + // slice of descriptions + Descriptions []Description `json:"description_data"` +} + +// CVE data +type Cve struct { + // feed data type + DataType DataType `json:"CVE_data_type"` + + // feed data format + DataFormat DataFormat `json:"CVE_data_format"` + + // feed data format version + DataVersion DataVersion `json:"CVE_data_version"` + + // CVE metadata + Metadata CveMetadata `json:"CVE_data_meta"` + + // CVE problem types + ProblemTypes CveProblemTypes `json:"problemtype"` + + // CVE references + References CveReferences `json:"references"` + + // CVE description + Description CveDescription `json:"description"` +} + +// CPE match +type CpeMatch struct { + // Vulnerable? + Vulnerable bool `json:"vulnerable"` + + VersionEndExcluding string `json:"versionEndExcluding"` + + // CPE URI (FIXME: decode this) + Cpe23Uri string `json:"cpe23Uri"` + + // CPE names (not sure if this is correct) + Names []string `json:"cpe_name"` +} + +// CVE item configuration node +type ConfigurationNode struct { + // node operator + Operator NodeOp `json:"operator"` + + // node children + Children []ConfigurationNode `json:"children"` + + CpeMatches []CpeMatch `json:"cpe_match"` +} + +// CVE item configurations +type ItemConfigurations struct { + // data version + DataVersion DataVersion `json:"CVE_data_version"` + + // slice of configuration nodes + Nodes []ConfigurationNode `json:"nodes"` +} + +// CVSS V3 +type CvssV3 struct { + // CVSS V3 version + Version V3Version `json:"version"` + + // CVSS V3 vector string + // VectorString string `json:"vectorString"` + + // CVSS vector + Vector Vector `json:"vectorString"` + + // attack vector + AttackVector V3AttackVector `json:"attackVector"` + + // attack complexity + AttackComplexity V3AttackComplexity `json:"attackComplexity"` + + // privileges required + PrivilegesRequired V3PrivilegesRequired `json:"privilegesRequired"` + + // user interaction + UserInteraction V3UserInteraction `json:"userInteraction"` + + // scope + Scope V3Scope `json:"scope"` + + // integrity impact + IntegrityImpact V3Impact `json:"integrityImpact"` + + // availability impact + AvailabilityImpact V3Impact `json:"availabilityImpact"` + + // base score + BaseScore Score `json:"baseScore"` + + // base severity + BaseSeverity Severity `json:"baseSeverity"` +} + +// CVSS V3 base metrics +type BaseMetricV3 struct { + CvssV3 CvssV3 `json:"cvssV3"` + ExploitabilityScore Score `json:"exploitabilityScore"` + ImpactScore Score `json:"impactScore"` +} + +// CVSS V2 +type CvssV2 struct { + // CVSS V2 version + Version V2Version `json:"version"` + + // CVSS vector string + // VectorString string `json:"vectorString"` + + // CVSS vector + Vector Vector `json:"vectorString"` + + // attack vector + AccessVector V2AccessVector `json:"accessVector"` + + // attack complexity + AccessComplexity V2AccessComplexity `json:"accessComplexity"` + + // authentication + Authentication V2Authentication `json:"authentication"` + + ConfidentialityImpact V2Impact `json:"confidentialityImpact"` + IntegrityImpact V2Impact `json:"integrityImpact"` + AvailabilityImpact V2Impact `json:"availabilityImpact"` + + // base score + BaseScore Score `json:"baseScore"` +} + +// CVSS V2 base metrics +type BaseMetricV2 struct { + CvssV2 CvssV2 `json:"cvssV2"` + Severity Severity `json:"severity"` + ExploitabilityScore Score `json:"exploitabilityScore"` + ImpactScore Score `json:"impactScore"` + InsufficientInfo bool `json:"acInsufInfo"` + ObtainAllPrivilege bool `json:"obtainAllPrivilege"` + ObtainUserPrivilege bool `json:"obtainUserPrivilege"` + ObtainOtherPrivilege bool `json:"obtainOtherPrivilege"` + UserInteractionRequired bool `json:"userInteractionRequired"` +} + +// Item impact +type Impact struct { + // CVSS V3 base metrics + BaseMetricV3 BaseMetricV3 `json:"baseMetricV3"` + + // CVSS V2 base metrics + BaseMetricV2 BaseMetricV2 `json:"baseMetricV2"` +} + +// CVE feed item +type Item struct { + // item CVE data + Cve Cve `json:"cve"` + + // item configuration + Configurations ItemConfigurations `json:"configurations"` + + // item impact + Impact Impact `json:"impact"` + + // item published date + PublishedDate Time `json:"publishedDate"` + + // last modification date + LastModifiedDate Time `json:"lastModifiedDate"` +} + +// NVD feed +type Feed struct { + // feed data type + DataType DataType `json:"CVE_data_type"` + + // feed data format + DataFormat DataFormat `json:"CVE_data_format"` + + // feed data format version + DataVersion DataVersion `json:"CVE_data_version"` + + // number of CVEs in feed + NumCVEs uint64 `json:"CVE_data_numberOfCVEs,string"` + + // data timestamp + Timestamp Time `json:"CVE_data_timestamp"` + + // CVE items + Items []Item `json:"CVE_Items"` +} diff --git a/feed/feed_test.go b/feed/feed_test.go new file mode 100644 index 0000000..f31a3ae --- /dev/null +++ b/feed/feed_test.go @@ -0,0 +1,56 @@ +package feed + +import ( + "compress/gzip" + "encoding/json" + "io" + // "fmt" + "os" + "testing" +) + +func openTest(path string) (io.Reader, error) { + // open file for reading + file, err := os.Open(path) + if err != nil { + return nil, err + } + + // wrap in reader, return success + return gzip.NewReader(file) +} + +// Test feed parser +func TestFeedParser(t *testing.T) { + t.Run("TestUnmarshalJSON", func(t *testing.T) { + var f Feed + + // read test data, check for error + src, err := openTest("testdata/nvdcve-1.1-2021.json.gz") + if err != nil { + t.Error(err) + } + + // decode cve feed, check for error + d := json.NewDecoder(src) + if err := d.Decode(&f); err != nil { + t.Error(err) + } + }) +// var f Feed +// +// // decode cve feed +// d := json.NewDecoder(os.Stdin) +// if err := d.Decode(&f); err != nil { +// t.Error(err) +// } +// +// var dst bytes.Buffer +// +// // create json encoder +// e := json.NewEncoder(&dst) +// if err := e.Encode(f); err != nil { +// t.Error(err) +// } +} + diff --git a/feed/meta.go b/feed/meta.go new file mode 100644 index 0000000..fd46025 --- /dev/null +++ b/feed/meta.go @@ -0,0 +1,106 @@ +package feed + +import ( + "bufio" + "encoding/hex" + "fmt" + "io" + "strconv" + "strings" + "time" +) + +// NVD metadata. +type Meta struct { + LastModifiedDate time.Time // last modified time + Size uint64 // uncompressed size, in bytes + ZipSize uint64 // zip file size, in bytes + GzSize uint64 // gz file size, in bytes + Sha256 [32]byte // sha256 hash of uncompressed data +} + +func parseMetaSize(name, val string) (uint64, error) { + // parse value, check for error + v, err := strconv.ParseUint(val, 10, 64) + if err == nil { + // return size + return v, nil + } else { + // return error + return 0, fmt.Errorf("invalid %s: \"%s\"", name, val) + } +} + +// Unmarshal new Metadata from reader. +func NewMeta(r io.Reader) (*Meta, error) { + // declare result + var m Meta + + // create scanner + scanner := bufio.NewScanner(r) + + // read lines + for scanner.Scan() { + // split into key/value pair, check for error + pair := strings.SplitN(scanner.Text(), ":", 2) + if len(pair) != 2 { + return nil, fmt.Errorf("bad meta line: \"%s\"", scanner.Text()) + } + + switch pair[0] { + case "lastModifiedDate": + // parse time, check for error + if err := m.LastModifiedDate.UnmarshalText([]byte(pair[1])); err != nil { + return nil, err + } + case "size": + if v, err := parseMetaSize("size", pair[1]); err == nil { + m.Size = v + } else { + return nil, err + } + case "zipSize": + if v, err := parseMetaSize("zipSize", pair[1]); err == nil { + m.ZipSize = v + } else { + return nil, err + } + case "gzSize": + if v, err := parseMetaSize("gzSize", pair[1]); err == nil { + m.GzSize = v + } else { + return nil, err + } + case "sha256": + // check hash length + if len(pair[1]) != 64 { + return nil, fmt.Errorf("invalid sha256 hash length: %d", len(pair[1])) + } + + // decode hex, check for error + buf, err := hex.DecodeString(pair[1]) + if err != nil { + return nil, fmt.Errorf("invalid sha256 hash: %v", err) + } + + // save to buffer, check for error + len := copy(m.Sha256[:], buf[0:32]) + if len != 32 { + // difficult to test, but this basically doesn't happen, see here: + // https://github.com/golang/go/blob/2ebe77a2fda1ee9ff6fd9a3e08933ad1ebaea039/src/runtime/slice.go#L247 + return nil, fmt.Errorf("invalid copy length: %d", len) + } + default: + // return error + return nil, fmt.Errorf("unknown key: \"%s\"", pair[0]) + } + } + + // check for scanner error + if err := scanner.Err(); err != nil { + return nil, err + } + + // return success + return &m, nil +} diff --git a/feed/meta_test.go b/feed/meta_test.go new file mode 100644 index 0000000..3ea5acb --- /dev/null +++ b/feed/meta_test.go @@ -0,0 +1,149 @@ +package feed + +import ( + "bytes" + "encoding/json" + "fmt" + "testing" +) + +func TestParseMetaSize(t *testing.T) { + passTests := []struct { + val string + exp uint64 + } { + { "0", 0 }, + { "1024", 1024 }, + { "18446744073709551615", 18446744073709551615 }, + } + + for _, test := range(passTests) { + t.Run(test.val, func(t *testing.T) { + got, err := parseMetaSize("foo", test.val) + if err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got %d, exp %d", got, test.exp) + return + } + }) + } + + failTests := []struct { + val string + exp string + } { + { "-1", "invalid foo: \"-1\"" }, + { "a", "invalid foo: \"a\"" }, + { "18446744073709551616", "invalid foo: \"18446744073709551616\"" }, + } + + for _, test := range(failTests) { + t.Run(test.val, func(t *testing.T) { + got, err := parseMetaSize("foo", test.val) + if err == nil { + t.Errorf("got %d, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +// test data +const testMeta = `lastModifiedDate:2022-01-29T03:01:16-05:00 +size:73202582 +zipSize:3753799 +gzSize:3753663 +sha256:B86258D5D9861507A1894A7B92011764803D7267787B1487539E240EA2405440 +` + +// Test meta parser +func TestNewMeta(t *testing.T) { + passTests := []string { + `lastModifiedDate:2022-01-29T03:01:16-05:00 +size:73202582 +zipSize:3753799 +gzSize:3753663 +sha256:B86258D5D9861507A1894A7B92011764803D7267787B1487539E240EA2405440 +`, + } + + for i, val := range(passTests) { + // build test name + name := fmt.Sprintf("passTests[%d]", i) + + t.Run(name, func(t *testing.T) { + // create buffer + buf := bytes.NewBufferString(val) + + // decode meta, check for error + _, err := NewMeta(buf) + if err != nil { + t.Error(err) + } + }) + } + + // build 65k token to make scanner fail + longVal := make([]byte, 65536) + for i := 0; i < cap(longVal); i++ { + longVal[i] = 'a' + } + + failTests := []struct { + val string + exp string + } { + { "asdf", "bad meta line: \"asdf\"" }, + { "lastModifiedDate:asdf", "parsing time \"asdf\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"asdf\" as \"2006\"" }, + { "size:a", "invalid size: \"a\"" }, + { "zipSize:a", "invalid zipSize: \"a\"" }, + { "gzSize:a", "invalid gzSize: \"a\"" }, + { "sha256:a", "invalid sha256 hash length: 1" }, + { + val: "sha256:0z00000000000000000000000000000000000000000000000000000000000000", + exp: "invalid sha256 hash: encoding/hex: invalid byte: U+007A 'z'", + }, + { string(longVal), "bufio.Scanner: token too long" }, + { "foo:bar", "unknown key: \"foo\"" }, + } + + for _, test := range(failTests) { + t.Run(test.val, func(t *testing.T) { + // create buffer + buf := bytes.NewBufferString(test.val) + + // decode meta, check for error + got, err := NewMeta(buf) + if err == nil { + t.Errorf("got %v, exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } + + t.Run("JsonEncode", func(t *testing.T) { + // create buffer + buf := bytes.NewBufferString(passTests[0]) + + // decode meta, check for error + meta, err := NewMeta(buf) + if err != nil { + t.Error(err) + } + + // create destination buffer + var dst bytes.Buffer + + // create json encoder + e := json.NewEncoder(&dst) + if err := e.Encode(meta); err != nil { + t.Error(err) + } + }) +} diff --git a/feed/nodeop.go b/feed/nodeop.go new file mode 100644 index 0000000..8bfa0a0 --- /dev/null +++ b/feed/nodeop.go @@ -0,0 +1,39 @@ +package feed + +//go:generate stringer -linecomment -type=NodeOp + +import ( + "encoding/json" + "fmt" +) + +// Node boolean operator. +type NodeOp byte + +const ( + OrOp NodeOp = iota // OR + AndOp // AND +) + +// Unmarshal DataVersion from JSON. +func (me *NodeOp) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "AND": + *me = AndOp + case "OR": + *me = OrOp + default: + // return error + return fmt.Errorf("unknown operator: %s", s) + } + + // return success + return nil +} diff --git a/feed/nodeop_string.go b/feed/nodeop_string.go new file mode 100644 index 0000000..2c120d4 --- /dev/null +++ b/feed/nodeop_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -linecomment -type=NodeOp"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[OrOp-0] + _ = x[AndOp-1] +} + +const _NodeOp_name = "ORAND" + +var _NodeOp_index = [...]uint8{0, 2, 5} + +func (i NodeOp) String() string { + if i >= NodeOp(len(_NodeOp_index)-1) { + return "NodeOp(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _NodeOp_name[_NodeOp_index[i]:_NodeOp_index[i+1]] +} diff --git a/feed/nodeop_test.go b/feed/nodeop_test.go new file mode 100644 index 0000000..dd538e5 --- /dev/null +++ b/feed/nodeop_test.go @@ -0,0 +1,77 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestNodeOpUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val NodeOp + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestNodeOpUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown operator: foo" + var val NodeOp + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestNodeOpUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp NodeOp + } { + { "\"AND\"", AndOp }, + { "\"OR\"", OrOp }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got NodeOp + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestNodeOpString(t *testing.T) { + tests := []struct { + val NodeOp + exp string + } { + { AndOp, "AND" }, + { OrOp, "OR" }, + + { NodeOp(255), "NodeOp(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/score.go b/feed/score.go new file mode 100644 index 0000000..051522f --- /dev/null +++ b/feed/score.go @@ -0,0 +1,34 @@ +package feed + +import ( + "encoding/json" + "fmt" + "math" + "strconv" +) + +// CVSS score +type Score uint8 + +// Unmarshal CVSS score from JSON. +func (me *Score) UnmarshalJSON(b []byte) error { + // decode float, check for error + var v float64 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + + // check score + if v < 0.0 || v > 10.0 { + return fmt.Errorf("CVSS score out of bounds: %2.1f", v) + } + + // save result, return success + *me = Score(uint8(math.Trunc(10.0 * v))) + return nil +} + +func (me Score) String() string { + val := float64(me) / 10.0 + return strconv.FormatFloat(val, 'f', 1, 64) +} diff --git a/feed/score_test.go b/feed/score_test.go new file mode 100644 index 0000000..2baa7ab --- /dev/null +++ b/feed/score_test.go @@ -0,0 +1,92 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestScoreUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val Score + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestScoreUnmarshalInvalidValues(t *testing.T) { + tests := []struct { + val string + exp string + } { + { `-100.0`, "CVSS score out of bounds: -100.0" }, + { `-90.0`, "CVSS score out of bounds: -90.0" }, + { `-9.3`, "CVSS score out of bounds: -9.3" }, + { `-1`, "CVSS score out of bounds: -1.0" }, + { `10.1`, "CVSS score out of bounds: 10.1" }, + { `100.0`, "CVSS score out of bounds: 100.0" }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got Score + + if err := json.Unmarshal([]byte(test.val), &got); err == nil { + t.Errorf("got \"%s\", exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } +} + +func TestScoreUnmarshalValidValues(t *testing.T) { + tests := []struct { + val string + exp uint8 + } { + { `0.0`, 0 }, + { `0.1`, 1 }, + { `1.2`, 12 }, + { `5.9`, 59 }, + { `9.9`, 99 }, + { `10.0`, 100 }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got Score + + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } else if uint8(got) != test.exp { + t.Errorf("got \"%d\", exp \"%d\"", uint8(got), test.exp) + } + }) + } +} + +func TestScoreString(t *testing.T) { + tests := []struct { + val uint8 + exp string + } { + { 0, "0.0" }, + { 1, "0.1" }, + { 9, "0.9" }, + { 12, "1.2" }, + { 59, "5.9" }, + { 99, "9.9" }, + { 100, "10.0" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := Score(test.val).String() + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/severity.go b/feed/severity.go new file mode 100644 index 0000000..50969ed --- /dev/null +++ b/feed/severity.go @@ -0,0 +1,47 @@ +package feed + +//go:generate stringer -linecomment -type=Severity + +import ( + "encoding/json" + "fmt" +) + +type Severity byte + +const ( + SeverityNone Severity = iota // NONE + SeverityLow // LOW + SeverityMedium // MEDIUM + SeverityHigh // HIGH + SeverityCritical // CRITICAL +) + +// Unmarshal CVSS severity from JSON. +func (me *Severity) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "NONE": + *me = SeverityNone + case "LOW": + *me = SeverityLow + case "MEDIUM": + *me = SeverityMedium + case "HIGH": + *me = SeverityHigh + case "CRITICAL": + *me = SeverityCritical + default: + // return error + return fmt.Errorf("unknown severity: %s", s) + } + + // return success + return nil +} diff --git a/feed/severity_string.go b/feed/severity_string.go new file mode 100644 index 0000000..28e1e91 --- /dev/null +++ b/feed/severity_string.go @@ -0,0 +1,27 @@ +// Code generated by "stringer -linecomment -type=Severity"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[SeverityNone-0] + _ = x[SeverityLow-1] + _ = x[SeverityMedium-2] + _ = x[SeverityHigh-3] + _ = x[SeverityCritical-4] +} + +const _Severity_name = "NONELOWMEDIUMHIGHCRITICAL" + +var _Severity_index = [...]uint8{0, 4, 7, 13, 17, 25} + +func (i Severity) String() string { + if i >= Severity(len(_Severity_index)-1) { + return "Severity(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Severity_name[_Severity_index[i]:_Severity_index[i+1]] +} diff --git a/feed/severity_test.go b/feed/severity_test.go new file mode 100644 index 0000000..75aec72 --- /dev/null +++ b/feed/severity_test.go @@ -0,0 +1,83 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestSeverityUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val Severity + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestSeverityUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown severity: foo" + var val Severity + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestSeverityUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp Severity + } { + { "\"NONE\"", SeverityNone }, + { "\"LOW\"", SeverityLow }, + { "\"MEDIUM\"", SeverityMedium }, + { "\"HIGH\"", SeverityHigh }, + { "\"CRITICAL\"", SeverityCritical }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got Severity + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestSeverityString(t *testing.T) { + tests := []struct { + val Severity + exp string + } { + { SeverityNone, "NONE" }, + { SeverityLow, "LOW" }, + { SeverityMedium, "MEDIUM" }, + { SeverityHigh, "HIGH" }, + { SeverityCritical, "CRITICAL" }, + + { Severity(255), "Severity(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/testdata/nvdcve-1.1-2002.json.gz b/feed/testdata/nvdcve-1.1-2002.json.gz new file mode 100644 index 0000000..45e714d Binary files /dev/null and b/feed/testdata/nvdcve-1.1-2002.json.gz differ diff --git a/feed/testdata/nvdcve-1.1-2003.json.gz b/feed/testdata/nvdcve-1.1-2003.json.gz new file mode 100644 index 0000000..c7796a6 Binary files /dev/null and b/feed/testdata/nvdcve-1.1-2003.json.gz differ diff --git a/feed/testdata/nvdcve-1.1-2021.json.gz b/feed/testdata/nvdcve-1.1-2021.json.gz new file mode 100644 index 0000000..83ca5e6 Binary files /dev/null and b/feed/testdata/nvdcve-1.1-2021.json.gz differ diff --git a/feed/testdata/nvdcve-1.1-modified.json.gz b/feed/testdata/nvdcve-1.1-modified.json.gz new file mode 100644 index 0000000..c675fb6 Binary files /dev/null and b/feed/testdata/nvdcve-1.1-modified.json.gz differ diff --git a/feed/time.go b/feed/time.go new file mode 100644 index 0000000..6eb5d37 --- /dev/null +++ b/feed/time.go @@ -0,0 +1,44 @@ +package feed + +import ( + "encoding/json" + "fmt" + // "strconv" + "regexp" + "time" +) + +// partial timestamp +type Time time.Time + +var timeRe = regexp.MustCompile("\\A\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}Z\\z") + +// Unmarshal timestamp from JSON. +func (me *Time) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // match partial string regex + if !timeRe.MatchString(s) { + return fmt.Errorf("invalid time: \"%s\"", s) + } + + // correct string suffix + s = s[0:16] + ":00Z" + + // unmarshal time + var t time.Time + if err := t.UnmarshalText([]byte(s)); err != nil { + return err + } + + // save time + *me = Time(t) + + // return success + return nil +} + diff --git a/feed/time_test.go b/feed/time_test.go new file mode 100644 index 0000000..cc490c5 --- /dev/null +++ b/feed/time_test.go @@ -0,0 +1,66 @@ +package feed + +import ( + "encoding/json" + "testing" + "time" +) + +func TestTimeUnmarshallInvalidData(t *testing.T) { + test := []byte(`{}`) + var val Time + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%v\", exp error", val) + } +} + +func TestTimeUnmarshallInvalidString(t *testing.T) { + test := []byte(`"2020-"`) + exp := "invalid time: \"2020-\"" + var val Time + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%v\", exp error", val) + } else if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestTimeUnmarshallInvalidTime(t *testing.T) { + test := []byte(`"2020-99-99T99:99Z"`) + var val Time + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%v\", exp error", val) + } +} + +func TestTimeString(t *testing.T) { + tests := []struct { + val string + exp string + } { + { "\"2021-06-09T20:15Z\"", "2021-06-09T20:15:00Z" }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var gotTime Time + if err := json.Unmarshal([]byte(test.val), &gotTime); err != nil { + t.Error(err) + return + } + + got, err := time.Time(gotTime).MarshalText() + if err != nil { + t.Error(err) + return + } + + if string(got) != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", string(got), test.exp) + } + }) + } +} diff --git a/feed/v2accesscomplexity.go b/feed/v2accesscomplexity.go new file mode 100644 index 0000000..5885e0d --- /dev/null +++ b/feed/v2accesscomplexity.go @@ -0,0 +1,42 @@ +package feed + +//go:generate stringer -linecomment -type=V2AccessComplexity + +import ( + "encoding/json" + "fmt" +) + +// CVSS v2 access complexity +type V2AccessComplexity byte + +const ( + V2ACLow V2AccessComplexity = iota // LOW + V2ACMedium // MEDIUM + V2ACHigh // HIGH +) + +// Unmarshal CVSS V2 access complexity from JSON. +func (me *V2AccessComplexity) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "LOW": + *me = V2ACLow + case "MEDIUM": + *me = V2ACMedium + case "HIGH": + *me = V2ACHigh + default: + // return error + return fmt.Errorf("unknown CVSS v2 access complexity: %s", s) + } + + // return success + return nil +} diff --git a/feed/v2accesscomplexity_string.go b/feed/v2accesscomplexity_string.go new file mode 100644 index 0000000..8638b3d --- /dev/null +++ b/feed/v2accesscomplexity_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -linecomment -type=V2AccessComplexity"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V2ACLow-0] + _ = x[V2ACMedium-1] + _ = x[V2ACHigh-2] +} + +const _V2AccessComplexity_name = "LOWMEDIUMHIGH" + +var _V2AccessComplexity_index = [...]uint8{0, 3, 9, 13} + +func (i V2AccessComplexity) String() string { + if i >= V2AccessComplexity(len(_V2AccessComplexity_index)-1) { + return "V2AccessComplexity(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V2AccessComplexity_name[_V2AccessComplexity_index[i]:_V2AccessComplexity_index[i+1]] +} diff --git a/feed/v2accesscomplexity_test.go b/feed/v2accesscomplexity_test.go new file mode 100644 index 0000000..2dd173d --- /dev/null +++ b/feed/v2accesscomplexity_test.go @@ -0,0 +1,79 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV2AccessComplexityUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V2AccessComplexity + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV2AccessComplexityUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS v2 access complexity: foo" + var val V2AccessComplexity + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV2AccessComplexityUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V2AccessComplexity + } { + { "\"LOW\"", V2ACLow }, + { "\"MEDIUM\"", V2ACMedium }, + { "\"HIGH\"", V2ACHigh }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V2AccessComplexity + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV2AccessComplexityString(t *testing.T) { + tests := []struct { + val V2AccessComplexity + exp string + } { + { V2ACLow, "LOW" }, + { V2ACMedium, "MEDIUM" }, + { V2ACHigh, "HIGH" }, + + { V2AccessComplexity(255), "V2AccessComplexity(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/v2accessvector.go b/feed/v2accessvector.go new file mode 100644 index 0000000..80490c2 --- /dev/null +++ b/feed/v2accessvector.go @@ -0,0 +1,42 @@ +package feed + +//go:generate stringer -linecomment -type=V2AccessVector + +import ( + "encoding/json" + "fmt" +) + +type V2AccessVector byte + +const ( + V2AVAdjacentNetwork V2AccessVector = iota // ADJACENT_NETWORK + V2AVLocal // LOCAL + V2AVNetwork // NETWORK +) + +// Unmarshal CVSS V2 access vector from JSON. +func (me *V2AccessVector) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "ADJACENT_NETWORK": + *me = V2AVAdjacentNetwork + case "LOCAL": + *me = V2AVLocal + case "NETWORK": + *me = V2AVNetwork + default: + // return error + return fmt.Errorf("unknown CVSS v2 access vector: %s", s) + } + + // return success + return nil +} + diff --git a/feed/v2accessvector_string.go b/feed/v2accessvector_string.go new file mode 100644 index 0000000..bf354fc --- /dev/null +++ b/feed/v2accessvector_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -linecomment -type=V2AccessVector"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V2AVAdjacentNetwork-0] + _ = x[V2AVLocal-1] + _ = x[V2AVNetwork-2] +} + +const _V2AccessVector_name = "ADJACENT_NETWORKLOCALNETWORK" + +var _V2AccessVector_index = [...]uint8{0, 16, 21, 28} + +func (i V2AccessVector) String() string { + if i >= V2AccessVector(len(_V2AccessVector_index)-1) { + return "V2AccessVector(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V2AccessVector_name[_V2AccessVector_index[i]:_V2AccessVector_index[i+1]] +} diff --git a/feed/v2accessvector_test.go b/feed/v2accessvector_test.go new file mode 100644 index 0000000..6e0df24 --- /dev/null +++ b/feed/v2accessvector_test.go @@ -0,0 +1,79 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV2AccessVectorUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V2AccessVector + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV2AccessVectorUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS v2 access vector: foo" + var val V2AccessVector + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV2AccessVectorUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V2AccessVector + } { + { "\"ADJACENT_NETWORK\"", V2AVAdjacentNetwork }, + { "\"LOCAL\"", V2AVLocal }, + { "\"NETWORK\"", V2AVNetwork }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V2AccessVector + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV2AccessVectorString(t *testing.T) { + tests := []struct { + val V2AccessVector + exp string + } { + { V2AVAdjacentNetwork, "ADJACENT_NETWORK" }, + { V2AVLocal, "LOCAL" }, + { V2AVNetwork, "NETWORK" }, + + { V2AccessVector(255), "V2AccessVector(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/v2authentication.go b/feed/v2authentication.go new file mode 100644 index 0000000..853954f --- /dev/null +++ b/feed/v2authentication.go @@ -0,0 +1,39 @@ +package feed + +//go:generate stringer -linecomment -type=V2Authentication + +import ( + "encoding/json" + "fmt" +) + +// CVSS v2 authentication +type V2Authentication byte + +const ( + V2AuthNone V2Authentication = iota // NONE + V2AuthSingle // SINGLE +) + +// Unmarshal CVSS V2 authentication from JSON. +func (me *V2Authentication) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "NONE": + *me = V2AuthNone + case "SINGLE": + *me = V2AuthSingle + default: + // return error + return fmt.Errorf("unknown CVSS v2 authentication: %s", s) + } + + // return success + return nil +} diff --git a/feed/v2authentication_string.go b/feed/v2authentication_string.go new file mode 100644 index 0000000..856c808 --- /dev/null +++ b/feed/v2authentication_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -linecomment -type=V2Authentication"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V2AuthNone-0] + _ = x[V2AuthSingle-1] +} + +const _V2Authentication_name = "NONESINGLE" + +var _V2Authentication_index = [...]uint8{0, 4, 10} + +func (i V2Authentication) String() string { + if i >= V2Authentication(len(_V2Authentication_index)-1) { + return "V2Authentication(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V2Authentication_name[_V2Authentication_index[i]:_V2Authentication_index[i+1]] +} diff --git a/feed/v2authentication_test.go b/feed/v2authentication_test.go new file mode 100644 index 0000000..4f23764 --- /dev/null +++ b/feed/v2authentication_test.go @@ -0,0 +1,77 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV2AuthenticationUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V2Authentication + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV2AuthenticationUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS v2 authentication: foo" + var val V2Authentication + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV2AuthenticationUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V2Authentication + } { + { "\"NONE\"", V2AuthNone }, + { "\"SINGLE\"", V2AuthSingle }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V2Authentication + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV2AuthenticationString(t *testing.T) { + tests := []struct { + val V2Authentication + exp string + } { + { V2AuthNone, "NONE" }, + { V2AuthSingle, "SINGLE" }, + + { V2Authentication(255), "V2Authentication(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/v2impact.go b/feed/v2impact.go new file mode 100644 index 0000000..1585e18 --- /dev/null +++ b/feed/v2impact.go @@ -0,0 +1,42 @@ +package feed + +//go:generate stringer -linecomment -type=V2Impact + +import ( + "encoding/json" + "fmt" +) + +// CVSS v2 impact level. +type V2Impact byte + +const ( + V2ImpactNone V2Impact = iota // NONE + V2ImpactPartial // PARTIAL + V2ImpactComplete // COMPLETE +) + +// Unmarshal CVSS v2 impact level from JSON. +func (me *V2Impact) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "NONE": + *me = V2ImpactNone + case "PARTIAL": + *me = V2ImpactPartial + case "COMPLETE": + *me = V2ImpactComplete + default: + // return error + return fmt.Errorf("unknown CVSS v2 impact: %s", s) + } + + // return success + return nil +} diff --git a/feed/v2impact_string.go b/feed/v2impact_string.go new file mode 100644 index 0000000..1dcf21b --- /dev/null +++ b/feed/v2impact_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -linecomment -type=V2Impact"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V2ImpactNone-0] + _ = x[V2ImpactPartial-1] + _ = x[V2ImpactComplete-2] +} + +const _V2Impact_name = "NONEPARTIALCOMPLETE" + +var _V2Impact_index = [...]uint8{0, 4, 11, 19} + +func (i V2Impact) String() string { + if i >= V2Impact(len(_V2Impact_index)-1) { + return "V2Impact(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V2Impact_name[_V2Impact_index[i]:_V2Impact_index[i+1]] +} diff --git a/feed/v2impact_test.go b/feed/v2impact_test.go new file mode 100644 index 0000000..54dc566 --- /dev/null +++ b/feed/v2impact_test.go @@ -0,0 +1,79 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV2ImpactUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V2Impact + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV2ImpactUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS v2 impact: foo" + var val V2Impact + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV2ImpactUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V2Impact + } { + { "\"NONE\"", V2ImpactNone }, + { "\"PARTIAL\"", V2ImpactPartial }, + { "\"COMPLETE\"", V2ImpactComplete }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V2Impact + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV2ImpactString(t *testing.T) { + tests := []struct { + val V2Impact + exp string + } { + { V2ImpactNone, "NONE" }, + { V2ImpactPartial, "PARTIAL" }, + { V2ImpactComplete, "COMPLETE" }, + + { V2Impact(255), "V2Impact(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/v2version.go b/feed/v2version.go new file mode 100644 index 0000000..76e6134 --- /dev/null +++ b/feed/v2version.go @@ -0,0 +1,36 @@ +package feed + +//go:generate stringer -linecomment -type=V2Version + +import ( + "encoding/json" + "fmt" +) + +// CVSS v2 version +type V2Version byte + +const ( + V20 V2Version = iota // 2.0 +) + +// Unmarshal CVSS V2 version from JSON. +func (me *V2Version) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "2.0": + *me = V20 + default: + // return error + return fmt.Errorf("unknown CVSS version: %s", s) + } + + // return success + return nil +} diff --git a/feed/v2version_string.go b/feed/v2version_string.go new file mode 100644 index 0000000..6b13870 --- /dev/null +++ b/feed/v2version_string.go @@ -0,0 +1,23 @@ +// Code generated by "stringer -linecomment -type=V2Version"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V20-0] +} + +const _V2Version_name = "2.0" + +var _V2Version_index = [...]uint8{0, 3} + +func (i V2Version) String() string { + if i >= V2Version(len(_V2Version_index)-1) { + return "V2Version(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V2Version_name[_V2Version_index[i]:_V2Version_index[i+1]] +} diff --git a/feed/v2version_test.go b/feed/v2version_test.go new file mode 100644 index 0000000..3b9b029 --- /dev/null +++ b/feed/v2version_test.go @@ -0,0 +1,75 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV2VersionUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V2Version + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV2VersionUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS version: foo" + var val V2Version + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV2VersionUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V2Version + } { + { "\"2.0\"", V20 }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V2Version + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV2VersionString(t *testing.T) { + tests := []struct { + val V2Version + exp string + } { + { V20, "2.0" }, + + { V2Version(255), "V2Version(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/v3attackcomplexity.go b/feed/v3attackcomplexity.go new file mode 100644 index 0000000..6e7481c --- /dev/null +++ b/feed/v3attackcomplexity.go @@ -0,0 +1,42 @@ +package feed + +//go:generate stringer -linecomment -type=V3AttackComplexity + +import ( + "encoding/json" + "fmt" +) + +// CVSS v3 attack complexity +type V3AttackComplexity byte + +const ( + V3ACLow V3AttackComplexity = iota // LOW + V3ACMedium // MEDIUM + V3ACHigh // HIGH +) + +// Unmarshal CVSS v3 attack complexity from JSON. +func (me *V3AttackComplexity) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "LOW": + *me = V3ACLow + case "MEDIUM": + *me = V3ACMedium + case "HIGH": + *me = V3ACHigh + default: + // return error + return fmt.Errorf("unknown CVSS v3 attack complexity: %s", s) + } + + // return success + return nil +} diff --git a/feed/v3attackcomplexity_string.go b/feed/v3attackcomplexity_string.go new file mode 100644 index 0000000..12110c8 --- /dev/null +++ b/feed/v3attackcomplexity_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -linecomment -type=V3AttackComplexity"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V3ACLow-0] + _ = x[V3ACMedium-1] + _ = x[V3ACHigh-2] +} + +const _V3AttackComplexity_name = "LOWMEDIUMHIGH" + +var _V3AttackComplexity_index = [...]uint8{0, 3, 9, 13} + +func (i V3AttackComplexity) String() string { + if i >= V3AttackComplexity(len(_V3AttackComplexity_index)-1) { + return "V3AttackComplexity(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V3AttackComplexity_name[_V3AttackComplexity_index[i]:_V3AttackComplexity_index[i+1]] +} diff --git a/feed/v3attackcomplexity_test.go b/feed/v3attackcomplexity_test.go new file mode 100644 index 0000000..a76efe3 --- /dev/null +++ b/feed/v3attackcomplexity_test.go @@ -0,0 +1,79 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV3AttackComplexityUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V3AttackComplexity + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV3AttackComplexityUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS v3 attack complexity: foo" + var val V3AttackComplexity + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV3AttackComplexityUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V3AttackComplexity + } { + { "\"LOW\"", V3ACLow }, + { "\"MEDIUM\"", V3ACMedium }, + { "\"HIGH\"", V3ACHigh }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V3AttackComplexity + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV3AttackComplexityString(t *testing.T) { + tests := []struct { + val V3AttackComplexity + exp string + } { + { V3ACLow, "LOW" }, + { V3ACMedium, "MEDIUM" }, + { V3ACHigh, "HIGH" }, + + { V3AttackComplexity(255), "V3AttackComplexity(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/v3attackvector.go b/feed/v3attackvector.go new file mode 100644 index 0000000..ecc309a --- /dev/null +++ b/feed/v3attackvector.go @@ -0,0 +1,46 @@ +package feed + +//go:generate stringer -linecomment -type=V3AttackVector + +import ( + "encoding/json" + "fmt" +) + +// CVSS v3 attack vector. +type V3AttackVector byte + +const ( + V3AVAdjacentNetwork V3AttackVector = iota // ADJACENT_NETWORK + V3AVNetwork // NETWORK + V3AVLocal // LOCAL + V3AVPhysical // PHYSICAL +) + +// Unmarshal CVSS v3 attack vector from JSON. +func (me *V3AttackVector) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "ADJACENT_NETWORK": + *me = V3AVAdjacentNetwork + case "LOCAL": + *me = V3AVLocal + case "NETWORK": + *me = V3AVNetwork + case "PHYSICAL": + *me = V3AVPhysical + default: + // return error + return fmt.Errorf("unknown CVSS v3 attack vector: %s", s) + } + + // return success + return nil +} + diff --git a/feed/v3attackvector_string.go b/feed/v3attackvector_string.go new file mode 100644 index 0000000..277520f --- /dev/null +++ b/feed/v3attackvector_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -linecomment -type=V3AttackVector"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V3AVAdjacentNetwork-0] + _ = x[V3AVNetwork-1] + _ = x[V3AVLocal-2] + _ = x[V3AVPhysical-3] +} + +const _V3AttackVector_name = "ADJACENT_NETWORKNETWORKLOCALPHYSICAL" + +var _V3AttackVector_index = [...]uint8{0, 16, 23, 28, 36} + +func (i V3AttackVector) String() string { + if i >= V3AttackVector(len(_V3AttackVector_index)-1) { + return "V3AttackVector(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V3AttackVector_name[_V3AttackVector_index[i]:_V3AttackVector_index[i+1]] +} diff --git a/feed/v3attackvector_test.go b/feed/v3attackvector_test.go new file mode 100644 index 0000000..251cfd4 --- /dev/null +++ b/feed/v3attackvector_test.go @@ -0,0 +1,81 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV3AttackVectorUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V3AttackVector + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV3AttackVectorUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS v3 attack vector: foo" + var val V3AttackVector + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV3AttackVectorUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V3AttackVector + } { + { "\"ADJACENT_NETWORK\"", V3AVAdjacentNetwork }, + { "\"LOCAL\"", V3AVLocal }, + { "\"NETWORK\"", V3AVNetwork }, + { "\"PHYSICAL\"", V3AVPhysical }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V3AttackVector + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV3AttackVectorString(t *testing.T) { + tests := []struct { + val V3AttackVector + exp string + } { + { V3AVAdjacentNetwork, "ADJACENT_NETWORK" }, + { V3AVLocal, "LOCAL" }, + { V3AVNetwork, "NETWORK" }, + { V3AVPhysical, "PHYSICAL" }, + + { V3AttackVector(255), "V3AttackVector(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/v3impact.go b/feed/v3impact.go new file mode 100644 index 0000000..d6c450e --- /dev/null +++ b/feed/v3impact.go @@ -0,0 +1,42 @@ +package feed + +//go:generate stringer -linecomment -type=V3Impact + +import ( + "encoding/json" + "fmt" +) + +// CVSS v3 impact level. +type V3Impact byte + +const ( + V3ImpactNone V3Impact = iota // NONE + V3ImpactLow // LOW + V3ImpactHigh // HIGH +) + +// Unmarshal CVSS v3 impact level from JSON. +func (me *V3Impact) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "NONE": + *me = V3ImpactNone + case "LOW": + *me = V3ImpactLow + case "HIGH": + *me = V3ImpactHigh + default: + // return error + return fmt.Errorf("unknown CVSS v3 impact: %s", s) + } + + // return success + return nil +} diff --git a/feed/v3impact_string.go b/feed/v3impact_string.go new file mode 100644 index 0000000..13c7ee3 --- /dev/null +++ b/feed/v3impact_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -linecomment -type=V3Impact"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V3ImpactNone-0] + _ = x[V3ImpactLow-1] + _ = x[V3ImpactHigh-2] +} + +const _V3Impact_name = "NONELOWHIGH" + +var _V3Impact_index = [...]uint8{0, 4, 7, 11} + +func (i V3Impact) String() string { + if i >= V3Impact(len(_V3Impact_index)-1) { + return "V3Impact(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V3Impact_name[_V3Impact_index[i]:_V3Impact_index[i+1]] +} diff --git a/feed/v3impact_test.go b/feed/v3impact_test.go new file mode 100644 index 0000000..a369f44 --- /dev/null +++ b/feed/v3impact_test.go @@ -0,0 +1,79 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV3ImpactUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V3Impact + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV3ImpactUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS v3 impact: foo" + var val V3Impact + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV3ImpactUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V3Impact + } { + { "\"NONE\"", V3ImpactNone }, + { "\"LOW\"", V3ImpactLow }, + { "\"HIGH\"", V3ImpactHigh }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V3Impact + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV3ImpactString(t *testing.T) { + tests := []struct { + val V3Impact + exp string + } { + { V3ImpactNone, "NONE" }, + { V3ImpactLow, "LOW" }, + { V3ImpactHigh, "HIGH" }, + + { V3Impact(255), "V3Impact(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/v3privilegesrequired.go b/feed/v3privilegesrequired.go new file mode 100644 index 0000000..3e69334 --- /dev/null +++ b/feed/v3privilegesrequired.go @@ -0,0 +1,45 @@ +package feed + +//go:generate stringer -linecomment -type=V3PrivilegesRequired + +import ( + "encoding/json" + "fmt" +) + +// CVSS v3 privileges required. +type V3PrivilegesRequired byte + +const ( + V3PRNone V3PrivilegesRequired = iota // NONE + V3PRLow // LOW + V3PRMedium // MEDIUM + V3PRHigh // HIGH +) + +// Unmarshal CVSS privileges required from JSON. +func (me *V3PrivilegesRequired) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "NONE": + *me = V3PRNone + case "LOW": + *me = V3PRLow + case "MEDIUM": + *me = V3PRMedium + case "HIGH": + *me = V3PRHigh + default: + // return error + return fmt.Errorf("unknown CVSS v3 privileges required: %s", s) + } + + // return success + return nil +} diff --git a/feed/v3privilegesrequired_string.go b/feed/v3privilegesrequired_string.go new file mode 100644 index 0000000..2951a64 --- /dev/null +++ b/feed/v3privilegesrequired_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -linecomment -type=V3PrivilegesRequired"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V3PRNone-0] + _ = x[V3PRLow-1] + _ = x[V3PRMedium-2] + _ = x[V3PRHigh-3] +} + +const _V3PrivilegesRequired_name = "NONELOWMEDIUMHIGH" + +var _V3PrivilegesRequired_index = [...]uint8{0, 4, 7, 13, 17} + +func (i V3PrivilegesRequired) String() string { + if i >= V3PrivilegesRequired(len(_V3PrivilegesRequired_index)-1) { + return "V3PrivilegesRequired(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V3PrivilegesRequired_name[_V3PrivilegesRequired_index[i]:_V3PrivilegesRequired_index[i+1]] +} diff --git a/feed/v3privilegesrequired_test.go b/feed/v3privilegesrequired_test.go new file mode 100644 index 0000000..f200ed1 --- /dev/null +++ b/feed/v3privilegesrequired_test.go @@ -0,0 +1,81 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV3PrivilegesRequiredUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V3PrivilegesRequired + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV3PrivilegesRequiredUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS v3 privileges required: foo" + var val V3PrivilegesRequired + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV3PrivilegesRequiredUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V3PrivilegesRequired + } { + { "\"NONE\"", V3PRNone }, + { "\"LOW\"", V3PRLow }, + { "\"MEDIUM\"", V3PRMedium }, + { "\"HIGH\"", V3PRHigh }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V3PrivilegesRequired + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV3PrivilegesRequiredString(t *testing.T) { + tests := []struct { + val V3PrivilegesRequired + exp string + } { + { V3PRNone, "NONE" }, + { V3PRLow, "LOW" }, + { V3PRMedium, "MEDIUM" }, + { V3PRHigh, "HIGH" }, + + { V3PrivilegesRequired(255), "V3PrivilegesRequired(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/v3scope.go b/feed/v3scope.go new file mode 100644 index 0000000..20fe0a5 --- /dev/null +++ b/feed/v3scope.go @@ -0,0 +1,39 @@ +package feed + +//go:generate stringer -linecomment -type=V3Scope + +import ( + "encoding/json" + "fmt" +) + +// CVSS v3 scope. +type V3Scope byte + +const ( + V3ScopeChanged V3Scope = iota // CHANGED + V3ScopeUnchanged // UNCHANGED +) + +// Unmarshal CVSS scope from JSON. +func (me *V3Scope) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "CHANGED": + *me = V3ScopeChanged + case "UNCHANGED": + *me = V3ScopeUnchanged + default: + // return error + return fmt.Errorf("unknown CVSS v3 scope: %s", s) + } + + // return success + return nil +} diff --git a/feed/v3scope_string.go b/feed/v3scope_string.go new file mode 100644 index 0000000..982cead --- /dev/null +++ b/feed/v3scope_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -linecomment -type=V3Scope"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V3ScopeChanged-0] + _ = x[V3ScopeUnchanged-1] +} + +const _V3Scope_name = "CHANGEDUNCHANGED" + +var _V3Scope_index = [...]uint8{0, 7, 16} + +func (i V3Scope) String() string { + if i >= V3Scope(len(_V3Scope_index)-1) { + return "V3Scope(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V3Scope_name[_V3Scope_index[i]:_V3Scope_index[i+1]] +} diff --git a/feed/v3scope_test.go b/feed/v3scope_test.go new file mode 100644 index 0000000..54170b0 --- /dev/null +++ b/feed/v3scope_test.go @@ -0,0 +1,77 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV3ScopeUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V3Scope + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV3ScopeUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS v3 scope: foo" + var val V3Scope + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV3ScopeUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V3Scope + } { + { "\"CHANGED\"", V3ScopeChanged }, + { "\"UNCHANGED\"", V3ScopeUnchanged }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V3Scope + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV3ScopeString(t *testing.T) { + tests := []struct { + val V3Scope + exp string + } { + { V3ScopeChanged, "CHANGED" }, + { V3ScopeUnchanged, "UNCHANGED" }, + + { V3Scope(255), "V3Scope(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/v3userinteraction.go b/feed/v3userinteraction.go new file mode 100644 index 0000000..a6a53ca --- /dev/null +++ b/feed/v3userinteraction.go @@ -0,0 +1,40 @@ +package feed + +//go:generate stringer -linecomment -type=V3UserInteraction + +import ( + "encoding/json" + "fmt" +) + +// CVSS v3 user interaction +type V3UserInteraction byte + +const ( + V3UINone V3UserInteraction = iota // NONE + V3UIRequired // REQUIRED +) + +// Unmarshal CVSS user interaction from JSON. +func (me *V3UserInteraction) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "NONE": + *me = V3UINone + case "REQUIRED": + *me = V3UIRequired + default: + // return error + return fmt.Errorf("unknown CVSS v3 user interaction: %s", s) + } + + // return success + return nil +} + diff --git a/feed/v3userinteraction_string.go b/feed/v3userinteraction_string.go new file mode 100644 index 0000000..be78920 --- /dev/null +++ b/feed/v3userinteraction_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -linecomment -type=V3UserInteraction"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V3UINone-0] + _ = x[V3UIRequired-1] +} + +const _V3UserInteraction_name = "NONEREQUIRED" + +var _V3UserInteraction_index = [...]uint8{0, 4, 12} + +func (i V3UserInteraction) String() string { + if i >= V3UserInteraction(len(_V3UserInteraction_index)-1) { + return "V3UserInteraction(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V3UserInteraction_name[_V3UserInteraction_index[i]:_V3UserInteraction_index[i+1]] +} diff --git a/feed/v3userinteraction_test.go b/feed/v3userinteraction_test.go new file mode 100644 index 0000000..c5949c2 --- /dev/null +++ b/feed/v3userinteraction_test.go @@ -0,0 +1,77 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV3UserInteractionUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V3UserInteraction + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV3UserInteractionUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS v3 user interaction: foo" + var val V3UserInteraction + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV3UserInteractionUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V3UserInteraction + } { + { "\"NONE\"", V3UINone }, + { "\"REQUIRED\"", V3UIRequired }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V3UserInteraction + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV3UserInteractionString(t *testing.T) { + tests := []struct { + val V3UserInteraction + exp string + } { + { V3UINone, "NONE" }, + { V3UIRequired, "REQUIRED" }, + + { V3UserInteraction(255), "V3UserInteraction(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/v3version.go b/feed/v3version.go new file mode 100644 index 0000000..537fecc --- /dev/null +++ b/feed/v3version.go @@ -0,0 +1,36 @@ +package feed + +//go:generate stringer -linecomment -type=V3Version + +import ( + "encoding/json" + "fmt" +) + +// CVSS v3 version +type V3Version byte + +const ( + V31 V3Version = iota // 3.1 +) + +// Unmarshal CVSS V3 version from JSON. +func (me *V3Version) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // check value + switch s { + case "3.1": + *me = V31 + default: + // return error + return fmt.Errorf("unknown CVSS version: %s", s) + } + + // return success + return nil +} diff --git a/feed/v3version_string.go b/feed/v3version_string.go new file mode 100644 index 0000000..9de58a7 --- /dev/null +++ b/feed/v3version_string.go @@ -0,0 +1,23 @@ +// Code generated by "stringer -linecomment -type=V3Version"; DO NOT EDIT. + +package feed + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[V31-0] +} + +const _V3Version_name = "3.1" + +var _V3Version_index = [...]uint8{0, 3} + +func (i V3Version) String() string { + if i >= V3Version(len(_V3Version_index)-1) { + return "V3Version(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _V3Version_name[_V3Version_index[i]:_V3Version_index[i+1]] +} diff --git a/feed/v3version_test.go b/feed/v3version_test.go new file mode 100644 index 0000000..89cc6ed --- /dev/null +++ b/feed/v3version_test.go @@ -0,0 +1,75 @@ +package feed + +import ( + "encoding/json" + "testing" +) + +func TestV3VersionUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val V3Version + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestV3VersionUnmarshalUnknown(t *testing.T) { + test := []byte(`"foo"`) + exp := "unknown CVSS version: foo" + var val V3Version + + err := json.Unmarshal(test, &val) + if err == nil { + t.Errorf("got \"%s\", exp error", val) + return + } + + if err.Error() != exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) + } +} + +func TestV3VersionUnmarshalValid(t *testing.T) { + tests := []struct { + val string + exp V3Version + } { + { "\"3.1\"", V31 }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + var got V3Version + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + return + } + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} + +func TestV3VersionString(t *testing.T) { + tests := []struct { + val V3Version + exp string + } { + { V31, "3.1" }, + + { V3Version(255), "V3Version(255)" }, + } + + for _, test := range(tests) { + t.Run(test.exp, func(t *testing.T) { + got := test.val.String() + + if got != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} diff --git a/feed/vector.go b/feed/vector.go new file mode 100644 index 0000000..9f20dc6 --- /dev/null +++ b/feed/vector.go @@ -0,0 +1,39 @@ +// NVD JSON feed parser. +package feed + +import ( + "encoding/json" + "github.com/pablotron/cvez/cvss" +) + +// CVSS vector +type Vector struct { + // CVSS vector + Vector cvss.Vector +} + +// Unmarshal CVSS vector from JSON. +func (me *Vector) UnmarshalJSON(b []byte) error { + // decode string, check for error + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // parse vector + vec, err := cvss.NewVector(s) + if err != nil { + return err + } + + // save result + me.Vector = vec + + // return success + return nil +} + +// Marshal CVSS vector to JSON. +func (me Vector) MarshalJSON() ([]byte, error) { + return json.Marshal(me.Vector.String()) +} diff --git a/feed/vector_test.go b/feed/vector_test.go new file mode 100644 index 0000000..16b1d64 --- /dev/null +++ b/feed/vector_test.go @@ -0,0 +1,99 @@ +package feed + +import ( + "encoding/json" + "github.com/pablotron/cvez/cvss" + "testing" +) + +func TestVectorUnmarshalInvalidData(t *testing.T) { + test := []byte(`{}`) + var val Vector + + if err := json.Unmarshal(test, &val); err == nil { + t.Errorf("got \"%s\", exp error", val) + } +} + +func TestVectorUnmarshalJSON(t *testing.T) { + failTests := []struct { + val string + exp string + } { + { + val: "\"AV:N/junk/Au:S/C:P/I:P/A:P\"", + exp: "invalid CVSS vector: AV:N/junk/Au:S/C:P/I:P/A:P", + }, { + val: "\"CVSS:3.0/junk/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H\"", + exp: "invalid CVSS vector: CVSS:3.0/junk/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", + }, { + val: "\"CVSS:3.1/AV:A/junk/PR:N/UI:N/S:U/C:H/I:H/A:H\"", + exp: "invalid CVSS vector: CVSS:3.1/AV:A/junk/PR:N/UI:N/S:U/C:H/I:H/A:H", + }, + } + + for _, test := range(failTests) { + t.Run(test.val, func(t *testing.T) { + var got Vector + + if err := json.Unmarshal([]byte(test.val), &got); err == nil { + t.Errorf("got \"%v\", exp error", got) + } else if err.Error() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) + } + }) + } + + passTests := []string { + "AV:N/AC:M/Au:S/C:P/I:P/A:P", + "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + } + + for _, val := range(passTests) { + t.Run(val, func(t *testing.T) { + var got Vector + + if err := json.Unmarshal([]byte("\"" + val + "\""), &got); err != nil { + t.Error(err) + } else if got.Vector.String() != val { + t.Errorf("got \"%s\", exp \"%s\"", got.Vector.String(), val) + } + }) + } +} + +func TestVectorMarshalJSON(t *testing.T) { + tests := []string { + "AV:N/AC:M/Au:S/C:P/I:P/A:P", + "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", + "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + } + + for _, val := range(tests) { + t.Run(val, func(t *testing.T) { + // get expected string + exp := "\"" + val + "\"" + + // create inner vector + vec, err := cvss.NewVector(val) + if err != nil { + t.Error(err) + return + } + + // serialize as json + buf, err := json.Marshal(Vector { vec }) + if err != nil { + t.Error(err) + return + } + + // check result + got := string(buf) + if got != exp { + t.Errorf("got \"%s\", exp \"%s\"", got, exp) + } + }) + } +} diff --git a/go.mod b/go.mod index 8b801e0..8f6363c 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module cvez +module github.com/pablotron/cvez go 1.18 diff --git a/internal/cpe/avstring.go b/internal/cpe/avstring.go deleted file mode 100644 index 46e16cf..0000000 --- a/internal/cpe/avstring.go +++ /dev/null @@ -1,54 +0,0 @@ -package cpe - -//go:generate stringer -linecomment -type=AvStringType - -import ( - "fmt" -) - -// type of avstring. -type AvStringType byte - -const ( - AnyString AvStringType = iota // any - NaString // na - ValString // val -) - -// String value (NISTIR 7695, CPE 2.3 spec, Figure 6-3) -type AvString struct { - Type AvStringType // value type - Val string // value -} - -// token type to avstring type map -var avTypes = map[tokenType]AvStringType { - anyToken: AnyString, - naToken: NaString, - valToken: ValString, -} - -// create new AvString from token. -func newAvString(t token) (AvString, error) { - if at, ok := avTypes[t.Type]; ok { - return AvString { at, t.Val }, nil - } else { - err := fmt.Errorf("invalid token type: 0x%02x", byte(t.Type)) - return AvString { 0, "" }, err - } -} - -// Serialize as string. -func (s AvString) String() string { - switch s.Type { - case AnyString: - return "*" - case NaString: - return "-" - case ValString: - return s.Val - default: - // not sure what to return here - return "" - } -} diff --git a/internal/cpe/avstring_test.go b/internal/cpe/avstring_test.go deleted file mode 100644 index 8cbb14b..0000000 --- a/internal/cpe/avstring_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package cpe - -import ( - "testing" -) - -func TestNewAvString(t *testing.T) { - passTests := []struct { - name string - val token - exp AvString - } { - { "any", token { Type: anyToken }, AvString { Type: AnyString } }, - { "na", token { Type: naToken }, AvString { Type: NaString } }, - { "empty", token { Type: valToken }, AvString { ValString, "" } }, - { "foo", token { valToken, "foo" }, AvString { ValString, "foo" } }, - } - - for _, test := range(passTests) { - t.Run(test.name, func(t *testing.T) { - got, err := newAvString(test.val) - if err != nil { - t.Error(err) - } else if got.Type != test.exp.Type { - t.Errorf("token: got %s, exp %s", got.Type, test.exp.Type) - } else if got.Type == ValString && got.Val != test.exp.Val { - t.Errorf("value: got \"%s\", exp \"%s\"", got.Val, test.exp.Val) - } - }) - } - - failTests := []struct { - name string - val token - exp string - } {{ - name: "invalid token", - val: token { Type: tokenType(127), Val: "foo" }, - exp: "invalid token type: 0x7f", - }} - - for _, test := range(failTests) { - t.Run(test.name, func(t *testing.T) { - got, err := newAvString(test.val) - if err == nil { - t.Errorf("got %v, exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } -} - -func TestAvStringString(t *testing.T) { - tests := []struct { - name string - val AvString - exp string - } { - { "any", AvString { AnyString, "" }, "*" }, - { "na", AvString { NaString, "" }, "-" }, - { "foo", AvString { ValString, "foo" }, "foo" }, - { "junk", AvString { AvStringType(255), "foo" }, "" }, - } - - for _, test := range(tests) { - t.Run(test.name, func(t *testing.T) { - got := test.val.String() - if got != test.exp { - t.Errorf("value: got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestAvStringTypeString(t *testing.T) { - tests := []struct { - val AvStringType - exp string - } { - { AnyString, "any" }, - { NaString, "na" }, - { ValString, "val" }, - { AvStringType(255), "AvStringType(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/cpe/avstringtype_string.go b/internal/cpe/avstringtype_string.go deleted file mode 100644 index 6fb88e1..0000000 --- a/internal/cpe/avstringtype_string.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by "stringer -linecomment -type=AvStringType"; DO NOT EDIT. - -package cpe - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[AnyString-0] - _ = x[NaString-1] - _ = x[ValString-2] -} - -const _AvStringType_name = "anynaval" - -var _AvStringType_index = [...]uint8{0, 3, 5, 8} - -func (i AvStringType) String() string { - if i >= AvStringType(len(_AvStringType_index)-1) { - return "AvStringType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _AvStringType_name[_AvStringType_index[i]:_AvStringType_index[i+1]] -} diff --git a/internal/cpe/cpe.go b/internal/cpe/cpe.go deleted file mode 100644 index dc38419..0000000 --- a/internal/cpe/cpe.go +++ /dev/null @@ -1,5 +0,0 @@ -// CPE 2.3 formatted string parser. -// -// Source: NISTIR 7695, figure 6-3: -// https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf -package cpe diff --git a/internal/cpe/part.go b/internal/cpe/part.go deleted file mode 100644 index ef91f7c..0000000 --- a/internal/cpe/part.go +++ /dev/null @@ -1,41 +0,0 @@ -package cpe - -//go:generate stringer -linecomment -type=Part - -import ( - "fmt" -) - -// CPE part -type Part byte - -const ( - ApplicationPart Part = 'a' // a - OperatingSystemPart Part = 'o' // o - HardwarePart Part = 'h' // h - AnyPart Part = '*' // * - NAPart Part = '-' // - -) - -// create new part from token -func newPart(t token) (Part, error) { - switch t.Type { - case anyToken: - return AnyPart, nil - case naToken: - return NAPart, nil - case valToken: - switch t.Val { - case "a": - return ApplicationPart, nil - case "o": - return OperatingSystemPart, nil - case "h": - return HardwarePart, nil - default: - return 0, fmt.Errorf("unknown part: \"%s\"", t.Val) - } - default: - return 0, fmt.Errorf("unknown token type: 0x%02x", byte(t.Type)) - } -} diff --git a/internal/cpe/part_string.go b/internal/cpe/part_string.go deleted file mode 100644 index 98b9fd3..0000000 --- a/internal/cpe/part_string.go +++ /dev/null @@ -1,41 +0,0 @@ -// Code generated by "stringer -linecomment -type=Part"; DO NOT EDIT. - -package cpe - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[ApplicationPart-97] - _ = x[OperatingSystemPart-111] - _ = x[HardwarePart-104] - _ = x[AnyPart-42] - _ = x[NAPart-45] -} - -const ( - _Part_name_0 = "*" - _Part_name_1 = "-" - _Part_name_2 = "a" - _Part_name_3 = "h" - _Part_name_4 = "o" -) - -func (i Part) String() string { - switch { - case i == 42: - return _Part_name_0 - case i == 45: - return _Part_name_1 - case i == 97: - return _Part_name_2 - case i == 104: - return _Part_name_3 - case i == 111: - return _Part_name_4 - default: - return "Part(" + strconv.FormatInt(int64(i), 10) + ")" - } -} diff --git a/internal/cpe/part_test.go b/internal/cpe/part_test.go deleted file mode 100644 index 269d16e..0000000 --- a/internal/cpe/part_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package cpe - -import ( - "testing" -) - -func TestNewPart(t *testing.T) { - passTests := []struct { - name string - val token - exp Part - } {{ - name: "any", - val: token { Type: anyToken }, - exp: AnyPart, - }, { - name: "na", - val: token { Type: naToken }, - exp: NAPart, - }, { - name: "a", - val: token { Type: valToken, Val: "a" }, - exp: ApplicationPart, - }, { - name: "h", - val: token { Type: valToken, Val: "h" }, - exp: HardwarePart, - }, { - name: "o", - val: token { Type: valToken, Val: "o" }, - exp: OperatingSystemPart, - }} - - for _, test := range(passTests) { - t.Run(test.name, func(t *testing.T) { - if got, err := newPart(test.val); err != nil { - t.Error(err) - } else if got != test.exp { - t.Errorf("got %s, exp %s", got, test.exp) - } - }) - } - - failTests := []struct { - name string - val token - exp string - } {{ - name: "invalid token", - val: token { Type: valToken, Val: "foo" }, - exp: "unknown part: \"foo\"", - }, { - name: "empty token", - val: token { Type: valToken }, - exp: "unknown part: \"\"", - }, { - name: "unknown token type", - val: token { Type: tokenType(255) }, - exp: "unknown token type: 0xff", - }} - - for _, test := range(failTests) { - t.Run(test.name, func(t *testing.T) { - // tokenize, check for error - got, err := newPart(test.val) - if err == nil { - t.Errorf("got %v, exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } -} - -func TestPartString(t *testing.T) { - tests := []struct { - val Part - exp string - } {{ - val: AnyPart, - exp: "*", - }, { - val: NAPart, - exp: "-", - }, { - val: ApplicationPart, - exp: "a", - }, { - val: HardwarePart, - exp: "h", - }, { - val: OperatingSystemPart, - exp: "o", - }, { - val: Part(255), - exp: "Part(255)", - }} - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/cpe/token.go b/internal/cpe/token.go deleted file mode 100644 index d88e773..0000000 --- a/internal/cpe/token.go +++ /dev/null @@ -1,99 +0,0 @@ -package cpe - -//go:generate stringer -linecomment -type=tokenType - -import ( - "errors" - "fmt" -) - -// token type -type tokenType byte - -const ( - anyToken tokenType = iota // any - naToken // na - valToken // val -) - -// token -type token struct { - Type tokenType // token type - Val string // token value -} - -// parse buffer into token. -func newToken(val []byte) token { - if len(val) > 0 { - switch val[0] { - case '*': - return token { Type: anyToken } - case '-': - return token { Type: naToken } - default: - return token { Type: valToken, Val: string(val) } - } - } else { - // empty value - return token { Type: valToken } - } -} - -// unterminated escape error -var unterminatedEsc = errors.New("unterminated escape at end of buffer") - -// Parse buffer into list of tokens. -func tokenize(buf []byte) ([]token, error) { - // build result - var r []token - - // current token and escape state - var curr []byte - esc := false - - // build result - for _, b := range(buf) { - if esc { - switch b { - // valid escaped characters - case '\\', '*', '-', '!', '"', '#', '$', '%', '&', '\'', '(', ')', - '+', ',', '/', ':', ';', '<', '=', '>', '@', '[', ']', '^', '`', - '{', '|', '}', '~': - curr = append(curr, b) - esc = false - default: - return r, fmt.Errorf("invalid escape byte: 0x%02x", b) - } - } else { - switch b { - case '\\': - esc = true - case ':': - // push token, clear buffer - r = append(r, newToken(curr)) - curr = nil - case 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', - 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', - 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '-', '.', '_', '*', '?': - curr = append(curr, b) - default: - return r, fmt.Errorf("invalid byte: 0x%02x", b) - } - } - } - - // check for unterminated escape - if esc { - return r, unterminatedEsc - } - - if len(curr) > 0 { - // push token, clear buffer - r = append(r, newToken(curr)) - curr = nil - } - - // return success - return r, nil -} diff --git a/internal/cpe/token_test.go b/internal/cpe/token_test.go deleted file mode 100644 index 595df2c..0000000 --- a/internal/cpe/token_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package cpe - -import ( - "reflect" - "testing" -) - -func TestTokenTypeString(t *testing.T) { - tests := []struct { - val tokenType - exp string - } { - { anyToken, "any" }, - { naToken, "na" }, - { valToken, "val" }, - { tokenType(255), "tokenType(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestNewToken(t *testing.T) { - passTests := []struct { - name string - val string - exp token - } { - { "any", "*", token { Type: anyToken } }, - { "na", "-", token { Type: naToken } }, - { "empty", "", token { Type: valToken } }, - { "foo", "foo", token { Type: valToken, Val: "foo" } }, - } - - for _, test := range(passTests) { - t.Run(test.name, func(t *testing.T) { - got := newToken([]byte(test.val)) - if got.Type != test.exp.Type { - t.Errorf("token: got %s, exp %s", got.Type, test.exp.Type) - } else if got.Type == valToken && got.Val != test.exp.Val { - t.Errorf("value: got \"%s\", exp \"%s\"", got.Val, test.exp.Val) - } - }) - } -} - -func TestTokenize(t *testing.T) { - passTests := []struct { - val string - exp []token - } {{ - val: "foo", - exp: []token { token { Type: valToken, Val: "foo" } }, - }, { - val: "foo:bar", - exp: []token { - token { Type: valToken, Val: "foo" }, - token { Type: valToken, Val: "bar" }, - }, - }, { - val: "*", - exp: []token { token { Type: anyToken } }, - }, { - val: "-", - exp: []token { token { Type: naToken } }, - }, { - val: "*:bar", - exp: []token { - token { Type: anyToken }, - token { Type: valToken, Val: "bar" }, - }, - }, { - val: "foo:*", - exp: []token { - token { Type: valToken, Val: "foo" }, - token { Type: anyToken }, - }, - }, { - val: "-:bar", - exp: []token { - token { Type: naToken }, - token { Type: valToken, Val: "bar" }, - }, - }, { - val: "foo:-", - exp: []token { - token { Type: valToken, Val: "foo" }, - token { Type: naToken }, - }, - }, { - val: "foo\\*:-", - exp: []token { - token { Type: valToken, Val: "foo*" }, - token { Type: naToken }, - }, - }} - - for _, test := range(passTests) { - t.Run(test.val, func(t *testing.T) { - // tokenize, check for error - got, err := tokenize([]byte(test.val)) - if err != nil { - t.Error(err) - return - } - - if !reflect.DeepEqual(got, test.exp) { - t.Errorf("token: got %v, exp %v", got, test.exp) - return - } - }) - } - - failTests := []struct { - id string - val string - exp string - } {{ - id: "invalid escape", - val: "foo\\.", - exp: "invalid escape byte: 0x2e", - }, { - id: "invalid byte", - val: "\n", - exp: "invalid byte: 0x0a", - }, { - id: "unterminated escape", - val: "\\", - exp: "unterminated escape at end of buffer", - }} - - for _, test := range(failTests) { - t.Run(test.id, func(t *testing.T) { - // tokenize, check for error - got, err := tokenize([]byte(test.val)) - if err == nil { - t.Errorf("got %v, exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } -} diff --git a/internal/cpe/tokentype_string.go b/internal/cpe/tokentype_string.go deleted file mode 100644 index 7b53758..0000000 --- a/internal/cpe/tokentype_string.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by "stringer -linecomment -type=tokenType"; DO NOT EDIT. - -package cpe - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[anyToken-0] - _ = x[naToken-1] - _ = x[valToken-2] -} - -const _tokenType_name = "anynaval" - -var _tokenType_index = [...]uint8{0, 3, 5, 8} - -func (i tokenType) String() string { - if i >= tokenType(len(_tokenType_index)-1) { - return "tokenType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _tokenType_name[_tokenType_index[i]:_tokenType_index[i+1]] -} diff --git a/internal/cpe/v23binding.go b/internal/cpe/v23binding.go deleted file mode 100644 index 24fb7b2..0000000 --- a/internal/cpe/v23binding.go +++ /dev/null @@ -1,119 +0,0 @@ -package cpe - -import ( - "encoding/json" - "errors" - "fmt" - "strings" -) - -// CPE 2.3 binding. -type V23Binding struct { - Part Part // Part attribute (NISTIR7695 5.3.3.1). - Vendor AvString // Vendor attribute (NISTIR7695 5.3.3.2). - Product AvString // Product attribute (NISTIR7695 5.3.3.3). - Version AvString // Version attribute (NISTIR7695 5.3.3.4). - Update AvString // Update attribute (NISTIR7695 5.3.3.5). - Edition AvString // Edition attribute (NISTIR7695 5.3.3.6). - SwEdition AvString // Software edition attribute (NISTIR7695 5.3.3.7). - TargetSw AvString // Target software attribute (NISTIR7695 5.3.3.8). - TargetHw AvString // Target hardware attribute (NISTIR7695 5.3.3.9). - Lang AvString // Language attribute (NISTIR7695 5.3.3.10). - Other AvString // Other attribute (NISTIR7695 5.3.3.11). -} - -// formatted string prefix -var cpe23Prefix = "cpe:2.3:" - -// missing prefix error -var missingPrefix = errors.New("missing CPE 2.3 prefix") - -// Create binding from CPE 2.3 formatted string. -func NewV23Binding(s string) (V23Binding, error) { - // check prefix - if s[0:len(cpe23Prefix)] != cpe23Prefix { - return V23Binding{}, missingPrefix - } - - // tokenize string, check for error - toks, err := tokenize([]byte(s[len(cpe23Prefix):])) - if err != nil { - return V23Binding{}, err - } - - // check token count - if len(toks) != 11 { - err = fmt.Errorf("invalid attribute count: %d != 11", len(toks)) - return V23Binding{}, err - } - - // create part - part, err := newPart(toks[0]) - if err != nil { - return V23Binding{}, err - } - - // parse tokens into strings - strs := make([]AvString, len(toks) - 1) - for i, t := range(toks[1:]) { - if strs[i], err = newAvString(t); err != nil { - return V23Binding{}, err - } - } - - // build and return result - return V23Binding { - Part: part, - Vendor: strs[0], - Product: strs[1], - Version: strs[2], - Update: strs[3], - Edition: strs[4], - Lang: strs[5], - SwEdition: strs[6], - TargetSw: strs[7], - TargetHw: strs[8], - Other: strs[9], - }, nil -} - -// Serialize CPE 2.3 binding as formatted string. -func (v V23Binding) String() string { - return cpe23Prefix + strings.Join([]string { - v.Part.String(), - v.Vendor.String(), - v.Product.String(), - v.Version.String(), - v.Update.String(), - v.Edition.String(), - v.Lang.String(), - v.SwEdition.String(), - v.TargetSw.String(), - v.TargetHw.String(), - v.Other.String(), - }, ":") -} - -// Unmarshal CPE 2.3 binding from JSON string. -func (me *V23Binding) UnmarshalJSON(b []byte) error { - // decode json string - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // create binding - binding, err := NewV23Binding(s) - if err != nil { - return err - } - - // save result, return success - *me = binding - return nil -} - -// Marshal CPE binding as JSON string. -func (v V23Binding) MarshalJSON() ([]byte, error) { - return json.Marshal(v.String()) -} diff --git a/internal/cpe/v23binding_test.go b/internal/cpe/v23binding_test.go deleted file mode 100644 index 4fa46bb..0000000 --- a/internal/cpe/v23binding_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package cpe - -import ( - "encoding/json" - "testing" -) - -func TestNewV23Binding(t *testing.T) { - passTests := []string { - "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", - } - - for _, val := range(passTests) { - t.Run(val, func(t *testing.T) { - if _, err := NewV23Binding(val); err != nil { - t.Error(err) - } - }) - } - - failTests := []struct { - val string - exp string - } {{ - val: "o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", - exp: "missing CPE 2.3 prefix", - }, { - val: "cpe:2.3:\n", - exp: "invalid byte: 0x0a", - }, { - val: "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*", - exp: "invalid attribute count: 10 != 11", - }, { - val: "cpe:2.3:foo:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", - exp: "unknown part: \"foo\"", - }} - - for _, test := range(failTests) { - t.Run(test.val, func(t *testing.T) { - got, err := NewV23Binding(test.val) - if err == nil { - t.Errorf("got %v, exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } -} - -func TestV23BindingString(t *testing.T) { - tests := []string { - "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", - } - - for _, val := range(tests) { - t.Run(val, func(t *testing.T) { - if got, err := NewV23Binding(val); err != nil { - t.Error(err) - } else if got.String() != val { - t.Errorf("got \"%s\", exp \"%s\"", got.String(), val) - } - }) - } -} - -func TestV23BindingUnmarshalJSON(t *testing.T) { - passTests := []string { - `"cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*"`, - } - - for _, val := range(passTests) { - t.Run(val, func(t *testing.T) { - var b V23Binding - if err := json.Unmarshal([]byte(val), &b); err != nil { - t.Error(err) - } - }) - } - - failTests := []struct { - val string - exp string - } {{ - val: `"o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*`, - exp: "unexpected end of JSON input", - }, { - val: `"o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*"`, - exp: "missing CPE 2.3 prefix", - }} - - for _, test := range(failTests) { - t.Run(test.val, func(t *testing.T) { - var got V23Binding - if err := json.Unmarshal([]byte(test.val), &got); err == nil { - t.Errorf("got %v, exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } -} - -func TestV23BindingMarshalJSON(t *testing.T) { - tests := []string { - "cpe:2.3:o:intel:ethernet_controller_e810_firmware:*:*:*:*:*:*:*:*", - } - - for _, val := range(tests) { - t.Run(val, func(t *testing.T) { - // create binding, check for error - b, err := NewV23Binding(val) - if err != nil { - t.Error(err) - return - } - - // marshal json, check for error - got, err := json.Marshal(b) - if err != nil { - t.Error(err) - return - } - - // build/check expected value - exp := "\"" + val + "\"" - if string(got) != exp { - t.Errorf("got \"%s\", exp \"%s\"", string(got), exp) - return - } - }) - } -} diff --git a/internal/cpedict/cpedict.go b/internal/cpedict/cpedict.go deleted file mode 100644 index 0b5f77b..0000000 --- a/internal/cpedict/cpedict.go +++ /dev/null @@ -1,46 +0,0 @@ -// CPE 2.3 dictionary parser. -// -// The official NVD CPE dictionary is available here: -// https://nvd.nist.gov/products/cpe -package cpedict - -import "time" - -// Dictionary generator information. -type Generator struct { - ProductName string `xml:"product_name"` // Product name. - ProductVersion string `xml:"product_version"` // Product version. - SchemaVersion string `xml:"schema_version"` // Schema version. - Timestamp time.Time `xml:"timestamp"` // Generation timestamp. -} - -// Dictionary item title. -type Title struct { - Lang string `xml:"lang,attr"` // language code - Text string `xml:",chardata"` // value -} - -// Dictionary item reference. -type Reference struct { - Href string `xml:"href,attr"` // Link - Text string `xml:",chardata"` // Text -} - -// CPE 2.3 item attributes. -type Cpe23Item struct { - Name string `xml:"name,attr"` // CPE 2.3 formatting string. -} - -// Dictionary item. -type Item struct { - CpeUri string `xml:"name,attr"` // CPE URI. - Cpe23Item Cpe23Item `xml:"cpe23-item"` // CPE 2.3 formatting string. - Titles []Title `xml:"title"` // Item titles. - References []Reference `xml:"references>reference"` // References. -} - -// CPE dictionary. -type Dictionary struct { - Generator Generator `xml:"generator"` // Dictionary generator. - Items []Item `xml:"cpe-item"` // Dictionary items. -} diff --git a/internal/cpedict/cpedict_test.go b/internal/cpedict/cpedict_test.go deleted file mode 100644 index eca0aeb..0000000 --- a/internal/cpedict/cpedict_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package cpedict - -import ( - "compress/gzip" - "encoding/xml" - "os" - "testing" - "time" - "reflect" -) - -func TestDictionaryXMLUnmarshal(t *testing.T) { - // open test data - f, err := os.Open("testdata/test-0.xml.gz") - if err != nil { - t.Error(err) - return - } - defer f.Close() - - // create gzip reader - gz, err := gzip.NewReader(f) - if err != nil { - t.Error(err) - return - } - defer gz.Close() - - // create xml decoder, decode xml - d := xml.NewDecoder(gz) - var dict Dictionary - - if err := d.Decode(&dict); err != nil { - t.Error(err) - return - } - - // build expected time - var expTime time.Time - if err := expTime.UnmarshalText([]byte("2022-02-02T04:51:00.437Z")); err != nil { - t.Error(err) - return - } - - // expected generator - expGenerator := Generator { - ProductName: "National Vulnerability Database (NVD)", - ProductVersion: "4.9", - SchemaVersion: "2.3", - Timestamp: expTime, - } - - // compare generator - if !reflect.DeepEqual(dict.Generator, expGenerator) { - t.Errorf("got \"%v\", exp \"%v\"", dict.Generator, expGenerator) - return - } - - // check item count - gotNumItems := len(dict.Items) - expNumItems := 19 - if gotNumItems != expNumItems { - t.Errorf("item count: got %d, exp %d", gotNumItems, expNumItems) - return - } - - // build expected item - expItems := []Item { - // first item in the list - Item { - CpeUri: "cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~", - - Cpe23Item: Cpe23Item { - Name: "cpe:2.3:a:\\$0.99_kindle_books_project:\\$0.99_kindle_books:6:*:*:*:*:android:*:*", - }, - - Titles: []Title { - Title { - Lang: "en-US", - Text: "$0.99 Kindle Books project $0.99 Kindle Books (aka com.kindle.books.for99) for android 6.0", - }, - }, - - References: []Reference { - Reference { - Href: "https://play.google.com/store/apps/details?id=com.kindle.books.for99", - Text: "Product information", - }, - - Reference { - Href: "https://docs.google.com/spreadsheets/d/1t5GXwjw82SyunALVJb2w0zi3FoLRIkfGPc7AMjRF0r4/edit?pli=1#gid=1053404143", - Text: "Government Advisory", - }, - }, - }, - - // last item in the list - Item { - CpeUri: "cpe:/a:3com:3c16116-us:2.0", - - Cpe23Item: Cpe23Item { - Name: "cpe:2.3:a:3com:3c16116-us:2.0:*:*:*:*:*:*:*", - }, - - Titles: []Title { - Title { - Lang: "ja-JP", - Text: "スリーコム WebCache 3000 2.0", - }, - - Title { - Lang: "en-US", - Text: "3Com WebCache 3000 2.0", - }, - }, - }, - } - - // build item comparisons - compares := []struct { - name string - exp Item - got Item - } { - { "head", expItems[0], dict.Items[0] }, - { "tail", expItems[1], dict.Items[len(dict.Items) - 1] }, - } - - for _, row := range(compares) { - t.Run(row.name, func(t *testing.T) { - if !reflect.DeepEqual(row.got, row.exp) { - t.Errorf("got \"%v\", exp \"%v\"", row.got, row.exp) - } - }) - } -} diff --git a/internal/cpedict/testdata/test-0.xml.gz b/internal/cpedict/testdata/test-0.xml.gz deleted file mode 100644 index 110e965..0000000 Binary files a/internal/cpedict/testdata/test-0.xml.gz and /dev/null differ diff --git a/internal/cpematch/cpematch.go b/internal/cpematch/cpematch.go deleted file mode 100644 index 0bae622..0000000 --- a/internal/cpematch/cpematch.go +++ /dev/null @@ -1,31 +0,0 @@ -// CPE match feed 1.0 parser. -// -// Latest CPE match feed available from here: -// https://nvd.nist.gov/vuln/data-feeds#cpeMatch -// -// JSON schema available here: -// https://csrc.nist.gov/schema/cpematch/feed/1.0/nvd_cpematch_feed_json_1.0.schema -package cpematch - -// CPE name -type Name struct { - Cpe22Uri string `json:"cpe22Uri"` - Cpe23Uri string `json:"cpe23Uri"` -} - -// CPE match string or range -type Match struct { - Vulnerable *bool `json:"vulnerable"` - Cpe22Uri string `json:"cpe22Uri"` - Cpe23Uri string `json:"cpe23Uri"` - VersionStartExcluding string `json:"versionStartExcluding"` - VersionStartIncluding string `json:"versionStartIncluding"` - VersionEndExcluding string `json:"versionEndExcluding"` - VersionEndIncluding string `json:"versionEndIncluding"` - Names []Name `json:"cpe_name"` // CPE match strings -} - -// CPE matches -type Matches struct { - Matches []Match `json:"matches"` // Array of CPE matches -} diff --git a/internal/cpematch/cpematch_test.go b/internal/cpematch/cpematch_test.go deleted file mode 100644 index 7d69410..0000000 --- a/internal/cpematch/cpematch_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package cpematch - -import ( - "compress/gzip" - "encoding/json" - "os" - "reflect" - "testing" -) - -func TestMatchesUnmarshal(t *testing.T) { - // expected data - exp := Matches { - Matches: []Match { - Match { - Cpe23Uri: "cpe:2.3:a:101_project:101:*:*:*:*:*:node.js:*:*", - VersionStartIncluding: "1.0.0", - VersionEndIncluding: "1.6.3", - Names: []Name { - Name { - Cpe23Uri: "cpe:2.3:a:101_project:101:1.0.0:*:*:*:*:node.js:*:*", - }, - - Name { - Cpe23Uri: "cpe:2.3:a:101_project:101:1.1.0:*:*:*:*:node.js:*:*", - }, - - Name { - Cpe23Uri: "cpe:2.3:a:101_project:101:1.1.1:*:*:*:*:node.js:*:*", - }, - }, - }, - - Match { - Cpe23Uri: "cpe:2.3:a:1password:1password:*:*:*:*:*:macos:*:*", - VersionStartIncluding: "7.7.0", - VersionEndExcluding: "7.8.7", - Names: []Name { - Name { - Cpe23Uri: "cpe:2.3:a:1password:1password:7.7.0:*:*:*:*:macos:*:*", - }, - }, - }, - - Match { - Cpe23Uri: "cpe:2.3:a:zimbra:collaboration:*:*:*:*:*:*:*:*", - VersionStartExcluding: "8.8.0", - VersionEndExcluding: "8.8.15", - Names: []Name { - Name { - Cpe23Uri: "cpe:2.3:a:zimbra:collaboration:8.8.6:*:*:*:*:*:*:*", - }, - - Name { - Cpe23Uri: "cpe:2.3:a:zimbra:collaboration:8.8.7:*:*:*:*:*:*:*", - }, - - Name { - Cpe23Uri: "cpe:2.3:a:zimbra:collaboration:8.8.8:-:*:*:*:*:*:*", - }, - - Name { - Cpe23Uri: "cpe:2.3:a:zimbra:collaboration:8.8.8:p1:*:*:*:*:*:*", - }, - }, - }, - }, - } - - // open test data - f, err := os.Open("testdata/test-0.json.gz") - if err != nil { - t.Error(err) - return - } - defer f.Close() - - // create gzip reader - gz, err := gzip.NewReader(f) - if err != nil { - t.Error(err) - return - } - defer gz.Close() - - // create json decoder - d := json.NewDecoder(gz) - var got Matches - - // decode match data, check for error - if err := d.Decode(&got); err != nil { - t.Error(err) - return - } - - // check for match - if !reflect.DeepEqual(got, exp) { - t.Errorf("got \"%v\", exp \"%v\"", got, exp) - } -} diff --git a/internal/cpematch/testdata/test-0.json.gz b/internal/cpematch/testdata/test-0.json.gz deleted file mode 100644 index 611bd58..0000000 Binary files a/internal/cpematch/testdata/test-0.json.gz and /dev/null differ diff --git a/internal/cvss/badkey.go b/internal/cvss/badkey.go deleted file mode 100644 index d4bc845..0000000 --- a/internal/cvss/badkey.go +++ /dev/null @@ -1,20 +0,0 @@ -package cvss - -// disabled for now (unused) -// import "fmt" -// -// // Bad metric key error. -// type badKey struct { -// version Version -// key string -// } -// -// // Create new bad key error. -// func newBadKey(version Version, key string) error { -// return &badKey { version, key } -// } -// -// // Return printable error string -// func (e badKey) Error() string { -// return fmt.Sprintf("invalid CVSS %s metric key: %s", e.version, e.key) -// } diff --git a/internal/cvss/badmetric.go b/internal/cvss/badmetric.go deleted file mode 100644 index 8f0bf5f..0000000 --- a/internal/cvss/badmetric.go +++ /dev/null @@ -1,19 +0,0 @@ -package cvss - -import "fmt" - -// Bad metric error. -type badMetric struct { - version Version // CVSS version - val string // metric value -} - -// Create new bad key error. -func newBadMetric(version Version, val string) error { - return &badMetric { version, val } -} - -// Return printable error string. -func (e badMetric) Error() string { - return fmt.Sprintf("invalid CVSS %s metric: %s", e.version, e.val) -} diff --git a/internal/cvss/badmetric_test.go b/internal/cvss/badmetric_test.go deleted file mode 100644 index 87316c0..0000000 --- a/internal/cvss/badmetric_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package cvss - -import "testing" - -func TestNewBadMetric(t *testing.T) { - tests := []struct { - name string - version Version - val string - exp string - } { - { "v20-foo", V20, "foo", "invalid CVSS 2.0 metric: foo" }, - { "v30-foo", V30, "foo", "invalid CVSS 3.0 metric: foo" }, - { "v31-foo", V31, "foo", "invalid CVSS 3.1 metric: foo" }, - } - - for _, test := range(tests) { - t.Run(test.name, func(t *testing.T) { - err := newBadMetric(test.version, test.val) - if err.Error() != test.exp { - t.Errorf("got: %s, exp: %s", err.Error(), test.exp) - } - }) - } -} diff --git a/internal/cvss/category.go b/internal/cvss/category.go deleted file mode 100644 index 54781dc..0000000 --- a/internal/cvss/category.go +++ /dev/null @@ -1,13 +0,0 @@ -package cvss - -//go:generate stringer -linecomment -type=Category - -// CVSS metric category. -type Category byte - -const ( - Base Category = iota // Base - Temporal // Temporal - Environmental // Environmental - InvalidCategory // invalid -) diff --git a/internal/cvss/category_string.go b/internal/cvss/category_string.go deleted file mode 100644 index 782f8fb..0000000 --- a/internal/cvss/category_string.go +++ /dev/null @@ -1,26 +0,0 @@ -// Code generated by "stringer -linecomment -type=Category"; DO NOT EDIT. - -package cvss - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[Base-0] - _ = x[Temporal-1] - _ = x[Environmental-2] - _ = x[InvalidCategory-3] -} - -const _Category_name = "BaseTemporalEnvironmentalinvalid" - -var _Category_index = [...]uint8{0, 4, 12, 25, 32} - -func (i Category) String() string { - if i >= Category(len(_Category_index)-1) { - return "Category(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _Category_name[_Category_index[i]:_Category_index[i+1]] -} diff --git a/internal/cvss/category_test.go b/internal/cvss/category_test.go deleted file mode 100644 index 7f69689..0000000 --- a/internal/cvss/category_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package cvss - -import "testing" - -func TestCategoryString(t *testing.T) { - tests := []struct { - cat Category - exp string - } { - { Base, "Base" }, - { Temporal, "Temporal" }, - { Environmental, "Environmental" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.cat.String() - if got != test.exp { - t.Errorf("got: %s, exp: %s", got, test.exp) - } - }) - } -} - -func TestInvalidCategory(t *testing.T) { - tests := []struct { - cat Category - exp string - } { - { Category(byte(255)), "Category(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.cat.String() - if got != test.exp { - t.Errorf("got: %s, exp: %s", got, test.exp) - } - }) - } -} diff --git a/internal/cvss/cvss.go b/internal/cvss/cvss.go deleted file mode 100644 index 2bae0d7..0000000 --- a/internal/cvss/cvss.go +++ /dev/null @@ -1,23 +0,0 @@ -// CVSS vector parser. -package cvss - -// Metric key. -type Key interface { - // Get full name. - Name() string - - // Get category. - Category() Category - - // Return string representation. - String() string -} - -// CVSS metric. -type Metric interface { - // Get metric key. - Key() Key - - // Return string representation of metric. - String() string -} diff --git a/internal/cvss/v2key.go b/internal/cvss/v2key.go deleted file mode 100644 index 740d26d..0000000 --- a/internal/cvss/v2key.go +++ /dev/null @@ -1,92 +0,0 @@ -package cvss - -//go:generate stringer -linecomment -type=v2Key - -// CVSS 2.0 metric key. -type v2Key byte - -const ( - v2AccessVector v2Key = iota // AV - v2AccessComplexity // AC - v2Authentication // Au - v2ConfidentialityImpact // C - v2IntegrityImpact // I - v2AvailabilityImpact // A - v2Exploitability // E - v2RemediationLevel // RL - v2ReportConfidence // RC - v2CollateralDamagePotential // CDP - v2TargetDistribution // TD - v2ConfidentialityRequirement // CR - v2IntegrityRequirement // IR - v2AvailabilityRequirement // AR - - v2InvalidKey // invalid -) - -// CVSS V2 metric key info lut -var v2Keys = map[v2Key]struct { - Name string - Category Category -} { - v2AccessVector: { "Access Vector", Base }, - v2AccessComplexity: { "Access Complexity", Base }, - v2Authentication: { "Authentication", Base }, - v2ConfidentialityImpact: { "Confidentiality Impact", Base }, - v2IntegrityImpact: { "Integrity Impact", Base }, - v2AvailabilityImpact: { "Availability Impact", Base }, - v2Exploitability: { "Exploitability", Temporal }, - v2RemediationLevel: { "Remediation Level", Temporal }, - v2ReportConfidence: { "Report Confidence", Temporal }, - v2CollateralDamagePotential: { "Collateral Damage Potential", Environmental }, - v2TargetDistribution: { "Target Distribution", Environmental }, - v2ConfidentialityRequirement: { "Confidentiality Requirement", Environmental }, - v2IntegrityRequirement: { "Integrity Requirement", Environmental }, - v2AvailabilityRequirement: { "Availability Requirement", Environmental }, -} - -// // v2 metric key IDs lut -// var v2KeyIds = map[string]v2Key { -// "AV": v2AccessVector, -// "AC": v2AccessComplexity, -// "Au": v2Authentication, -// "C": v2ConfidentialityImpact, -// "I": v2IntegrityImpact, -// "A": v2AvailabilityImpact, -// "E": v2Exploitability, -// "RL": v2RemediationLevel, -// "RC": v2ReportConfidence, -// "CDP": v2CollateralDamagePotential, -// "TD": v2TargetDistribution, -// "CR": v2ConfidentialityRequirement, -// "IR": v2IntegrityRequirement, -// "AR": v2AvailabilityRequirement, -// } -// -// // Get metric key from string. -// func getV2KeyFromString(s string) (v2Key, error) { -// k, ok := v2KeyIds[s] -// if ok { -// return k, nil -// } else { -// return v2InvalidKey, newBadKey(V20, s) -// } -// } - -// Get metric key name. -func (k v2Key) Name() string { - if data, ok := v2Keys[k]; ok { - return data.Name - } else { - return "invalid" - } -} - -// Get metric key category. -func (k v2Key) Category() Category { - if data, ok := v2Keys[k]; ok { - return data.Category - } else { - return InvalidCategory - } -} diff --git a/internal/cvss/v2key_string.go b/internal/cvss/v2key_string.go deleted file mode 100644 index 8e945fd..0000000 --- a/internal/cvss/v2key_string.go +++ /dev/null @@ -1,37 +0,0 @@ -// Code generated by "stringer -linecomment -type=v2Key"; DO NOT EDIT. - -package cvss - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[v2AccessVector-0] - _ = x[v2AccessComplexity-1] - _ = x[v2Authentication-2] - _ = x[v2ConfidentialityImpact-3] - _ = x[v2IntegrityImpact-4] - _ = x[v2AvailabilityImpact-5] - _ = x[v2Exploitability-6] - _ = x[v2RemediationLevel-7] - _ = x[v2ReportConfidence-8] - _ = x[v2CollateralDamagePotential-9] - _ = x[v2TargetDistribution-10] - _ = x[v2ConfidentialityRequirement-11] - _ = x[v2IntegrityRequirement-12] - _ = x[v2AvailabilityRequirement-13] - _ = x[v2InvalidKey-14] -} - -const _v2Key_name = "AVACAuCIAERLRCCDPTDCRIRARinvalid" - -var _v2Key_index = [...]uint8{0, 2, 4, 6, 7, 8, 9, 10, 12, 14, 17, 19, 21, 23, 25, 32} - -func (i v2Key) String() string { - if i >= v2Key(len(_v2Key_index)-1) { - return "v2Key(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _v2Key_name[_v2Key_index[i]:_v2Key_index[i+1]] -} diff --git a/internal/cvss/v2key_test.go b/internal/cvss/v2key_test.go deleted file mode 100644 index a54f4e7..0000000 --- a/internal/cvss/v2key_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package cvss - -import "testing" - -func TestV2KeyString(t *testing.T) { - tests := []struct { - key v2Key - exp string - } { - { v2AccessVector, "AV" }, - { v2AccessComplexity, "AC" }, - { v2Authentication, "Au" }, - { v2ConfidentialityImpact, "C" }, - { v2IntegrityImpact, "I" }, - { v2AvailabilityImpact, "A" }, - { v2Exploitability, "E" }, - { v2RemediationLevel, "RL" }, - { v2ReportConfidence, "RC" }, - { v2CollateralDamagePotential, "CDP" }, - { v2TargetDistribution, "TD" }, - { v2ConfidentialityRequirement, "CR" }, - { v2IntegrityRequirement, "IR" }, - { v2AvailabilityRequirement, "AR" }, - - { v2Key(255), "v2Key(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.key.String() - if got != test.exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) - } - }) - } -} - -func TestV2KeyName(t *testing.T) { - tests := []struct { - key v2Key - exp string - } { - { v2AccessVector, "Access Vector" }, - { v2AccessComplexity, "Access Complexity" }, - { v2Authentication, "Authentication" }, - { v2ConfidentialityImpact, "Confidentiality Impact" }, - { v2IntegrityImpact, "Integrity Impact" }, - { v2AvailabilityImpact, "Availability Impact" }, - { v2Exploitability, "Exploitability" }, - { v2RemediationLevel, "Remediation Level" }, - { v2ReportConfidence, "Report Confidence" }, - { v2CollateralDamagePotential, "Collateral Damage Potential" }, - { v2TargetDistribution, "Target Distribution" }, - { v2ConfidentialityRequirement, "Confidentiality Requirement" }, - { v2IntegrityRequirement, "Integrity Requirement" }, - { v2AvailabilityRequirement, "Availability Requirement" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.key.Name() - if got != test.exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) - } - }) - } -} - -func TestV2KeyCategory(t *testing.T) { - tests := []struct { - key v2Key - exp Category - } { - { v2AccessVector, Base }, - { v2AccessComplexity, Base }, - { v2Authentication, Base }, - { v2ConfidentialityImpact, Base }, - { v2IntegrityImpact, Base }, - { v2AvailabilityImpact, Base }, - { v2Exploitability, Temporal }, - { v2RemediationLevel, Temporal }, - { v2ReportConfidence, Temporal }, - { v2CollateralDamagePotential, Environmental }, - { v2TargetDistribution, Environmental}, - { v2ConfidentialityRequirement, Environmental}, - { v2IntegrityRequirement, Environmental}, - { v2AvailabilityRequirement, Environmental}, - } - - for _, test := range(tests) { - t.Run(test.key.String(), func(t *testing.T) { - got := test.key.Category() - if got != test.exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) - } - }) - } -} - -func TestInvalidV2KeyName(t *testing.T) { - exp := "invalid" - got := v2Key(255).Name() - - if got != exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) - } -} - -func TestInvalidV2KeyCategory(t *testing.T) { - exp := InvalidCategory - got := v2Key(255).Category() - - if got != exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) - } -} diff --git a/internal/cvss/v2metric.go b/internal/cvss/v2metric.go deleted file mode 100644 index 2dee41a..0000000 --- a/internal/cvss/v2metric.go +++ /dev/null @@ -1,244 +0,0 @@ -package cvss - -//go:generate stringer -linecomment -type=v2Metric - -// CVSS v2 metric value -type v2Metric byte - -const ( - v2AVNetwork v2Metric = iota // AV:N - v2AVAdjacentNetwork // AV:A - v2AVLocal // AV:L - - v2ACLow // AC:L - v2ACMedium // AC:M - v2ACHigh // AC:H - - v2AuMultiple // Au:M - v2AuSingle // Au:S - v2AuNone // Au:N - - v2CNone // C:N - v2CPartial // C:P - v2CComplete // C:C - - v2INone // I:N - v2IPartial // I:P - v2IComplete // I:C - - v2ANone // A:N - v2APartial // A:P - v2AComplete // A:C - - v2ENotDefined // E:ND - v2EUnproven // E:U - v2EProofOfConcept // E:POC - v2EFunctional // E:F - v2EHigh // E:H - - v2RLOfficialFix // RL:OF - v2RLTemporaryFix // RL:TF - v2RLWorkaround // RL:W - v2RLUnavailable // RL:U - v2RLNotDefined // RL:ND - - v2RCUnconfirmed // RC:UC - v2RCUncorroborated // RC:UR - v2RCConfirmed // RC:C - v2RCNotDefined // RC:ND - - v2CDPNone // CDP:N - v2CDPLow // CDP:L - v2CDPLowMedium // CDP:LM - v2CDPMediumHigh // CDP:MH - v2CDPHigh // CDP:H - v2CDPNotDefined // CDP:ND - - v2TDNone // TD:N - v2TDLow // TD:L - v2TDMedium // TD:M - v2TDHigh // TD:H - v2TDNotDefined // TD:ND - - v2CRLow // CR:L - v2CRMedium // CR:M - v2CRHigh // CR:H - v2CRNotDefined // CR:ND - - v2IRLow // IR:L - v2IRMedium // IR:M - v2IRHigh // IR:H - v2IRNotDefined // IR:ND - - v2ARLow // AR:L - v2ARMedium // AR:M - v2ARHigh // AR:H - v2ARNotDefined // AR:ND - - v2InvalidMetric // invalid -) - -// map of metrics to metric keys -var v2KeyLut = map[v2Metric]v2Key { - v2AVNetwork: v2AccessVector, - v2AVAdjacentNetwork: v2AccessVector, - v2AVLocal: v2AccessVector, - - v2ACLow: v2AccessComplexity, - v2ACMedium: v2AccessComplexity, - v2ACHigh: v2AccessComplexity, - - v2AuMultiple: v2Authentication, - v2AuSingle: v2Authentication, - v2AuNone: v2Authentication, - - v2CNone: v2ConfidentialityImpact, - v2CPartial: v2ConfidentialityImpact, - v2CComplete: v2ConfidentialityImpact, - - v2INone: v2IntegrityImpact, - v2IPartial: v2IntegrityImpact, - v2IComplete: v2IntegrityImpact, - - v2ANone: v2AvailabilityImpact, - v2APartial: v2AvailabilityImpact, - v2AComplete: v2AvailabilityImpact, - - v2ENotDefined: v2Exploitability, - v2EUnproven: v2Exploitability, - v2EProofOfConcept: v2Exploitability, - v2EFunctional: v2Exploitability, - v2EHigh: v2Exploitability, - - v2RLOfficialFix: v2RemediationLevel, - v2RLTemporaryFix: v2RemediationLevel, - v2RLWorkaround: v2RemediationLevel, - v2RLUnavailable: v2RemediationLevel, - v2RLNotDefined: v2RemediationLevel, - - v2RCUnconfirmed: v2ReportConfidence, - v2RCUncorroborated: v2ReportConfidence, - v2RCConfirmed: v2ReportConfidence, - v2RCNotDefined: v2ReportConfidence, - - v2CDPNone: v2CollateralDamagePotential, - v2CDPLow: v2CollateralDamagePotential, - v2CDPLowMedium: v2CollateralDamagePotential, - v2CDPMediumHigh: v2CollateralDamagePotential, - v2CDPHigh: v2CollateralDamagePotential, - v2CDPNotDefined: v2CollateralDamagePotential, - - v2TDNone: v2TargetDistribution, - v2TDLow: v2TargetDistribution, - v2TDMedium: v2TargetDistribution, - v2TDHigh: v2TargetDistribution, - v2TDNotDefined: v2TargetDistribution, - - v2CRLow: v2ConfidentialityRequirement, - v2CRMedium: v2ConfidentialityRequirement, - v2CRHigh: v2ConfidentialityRequirement, - v2CRNotDefined: v2ConfidentialityRequirement, - - v2IRLow: v2IntegrityRequirement, - v2IRMedium: v2IntegrityRequirement, - v2IRHigh: v2IntegrityRequirement, - v2IRNotDefined: v2IntegrityRequirement, - - v2ARLow: v2AvailabilityRequirement, - v2ARMedium: v2AvailabilityRequirement, - v2ARHigh: v2AvailabilityRequirement, - v2ARNotDefined: v2AvailabilityRequirement, -} - -// map of metric strings to metrics -var v2MetricStrLut = map[string]v2Metric { - "AV:N": v2AVNetwork, - "AV:A": v2AVAdjacentNetwork, - "AV:L": v2AVLocal, - - "AC:L": v2ACLow, - "AC:M": v2ACMedium, - "AC:H": v2ACHigh, - - "Au:M": v2AuMultiple, - "Au:S": v2AuSingle, - "Au:N": v2AuNone, - - "C:N": v2CNone, - "C:P": v2CPartial, - "C:C": v2CComplete, - - "I:N": v2INone, - "I:P": v2IPartial, - "I:C": v2IComplete, - - "A:N": v2ANone, - "A:P": v2APartial, - "A:C": v2AComplete, - - "E:ND": v2ENotDefined, - "E:U": v2EUnproven, - "E:POC": v2EProofOfConcept, - "E:F": v2EFunctional, - "E:H": v2EHigh, - - "RL:OF": v2RLOfficialFix, - "RL:TF": v2RLTemporaryFix, - "RL:W": v2RLWorkaround, - "RL:U": v2RLUnavailable, - "RL:ND": v2RLNotDefined, - - "RC:UC": v2RCUnconfirmed, - "RC:UR": v2RCUncorroborated, - "RC:C": v2RCConfirmed, - "RC:ND": v2RCNotDefined, - - "CDP:N": v2CDPNone, - "CDP:L": v2CDPLow, - "CDP:LM": v2CDPLowMedium, - "CDP:MH": v2CDPMediumHigh, - "CDP:H": v2CDPHigh, - "CDP:ND": v2CDPNotDefined, - - "TD:N": v2TDNone, - "TD:L": v2TDLow, - "TD:M": v2TDMedium, - "TD:H": v2TDHigh, - "TD:ND": v2TDNotDefined, - - "CR:L": v2CRLow, - "CR:M": v2CRMedium, - "CR:H": v2CRHigh, - "CR:ND": v2CRNotDefined, - - "IR:L": v2IRLow, - "IR:M": v2IRMedium, - "IR:H": v2IRHigh, - "IR:ND": v2IRNotDefined, - - "AR:L": v2ARLow, - "AR:M": v2ARMedium, - "AR:H": v2ARHigh, - "AR:ND": v2ARNotDefined, -} - -// Convert string to CVSS 2.0 metric. -func getV2Metric(s string) (v2Metric, error) { - // get metric - m, ok := v2MetricStrLut[s] - if !ok { - return v2InvalidMetric, newBadMetric(V20, s) - } - - // return success - return m, nil -} - -// Get CVSS 2.0 metric key. -func (m v2Metric) Key() Key { - if k, ok := v2KeyLut[m]; ok { - return k - } else { - return v2InvalidKey - } -} diff --git a/internal/cvss/v2metric_string.go b/internal/cvss/v2metric_string.go deleted file mode 100644 index 6ad2f93..0000000 --- a/internal/cvss/v2metric_string.go +++ /dev/null @@ -1,78 +0,0 @@ -// Code generated by "stringer -linecomment -type=v2Metric"; DO NOT EDIT. - -package cvss - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[v2AVNetwork-0] - _ = x[v2AVAdjacentNetwork-1] - _ = x[v2AVLocal-2] - _ = x[v2ACLow-3] - _ = x[v2ACMedium-4] - _ = x[v2ACHigh-5] - _ = x[v2AuMultiple-6] - _ = x[v2AuSingle-7] - _ = x[v2AuNone-8] - _ = x[v2CNone-9] - _ = x[v2CPartial-10] - _ = x[v2CComplete-11] - _ = x[v2INone-12] - _ = x[v2IPartial-13] - _ = x[v2IComplete-14] - _ = x[v2ANone-15] - _ = x[v2APartial-16] - _ = x[v2AComplete-17] - _ = x[v2ENotDefined-18] - _ = x[v2EUnproven-19] - _ = x[v2EProofOfConcept-20] - _ = x[v2EFunctional-21] - _ = x[v2EHigh-22] - _ = x[v2RLOfficialFix-23] - _ = x[v2RLTemporaryFix-24] - _ = x[v2RLWorkaround-25] - _ = x[v2RLUnavailable-26] - _ = x[v2RLNotDefined-27] - _ = x[v2RCUnconfirmed-28] - _ = x[v2RCUncorroborated-29] - _ = x[v2RCConfirmed-30] - _ = x[v2RCNotDefined-31] - _ = x[v2CDPNone-32] - _ = x[v2CDPLow-33] - _ = x[v2CDPLowMedium-34] - _ = x[v2CDPMediumHigh-35] - _ = x[v2CDPHigh-36] - _ = x[v2CDPNotDefined-37] - _ = x[v2TDNone-38] - _ = x[v2TDLow-39] - _ = x[v2TDMedium-40] - _ = x[v2TDHigh-41] - _ = x[v2TDNotDefined-42] - _ = x[v2CRLow-43] - _ = x[v2CRMedium-44] - _ = x[v2CRHigh-45] - _ = x[v2CRNotDefined-46] - _ = x[v2IRLow-47] - _ = x[v2IRMedium-48] - _ = x[v2IRHigh-49] - _ = x[v2IRNotDefined-50] - _ = x[v2ARLow-51] - _ = x[v2ARMedium-52] - _ = x[v2ARHigh-53] - _ = x[v2ARNotDefined-54] - _ = x[v2InvalidMetric-55] -} - -const _v2Metric_name = "AV:NAV:AAV:LAC:LAC:MAC:HAu:MAu:SAu:NC:NC:PC:CI:NI:PI:CA:NA:PA:CE:NDE:UE:POCE:FE:HRL:OFRL:TFRL:WRL:URL:NDRC:UCRC:URRC:CRC:NDCDP:NCDP:LCDP:LMCDP:MHCDP:HCDP:NDTD:NTD:LTD:MTD:HTD:NDCR:LCR:MCR:HCR:NDIR:LIR:MIR:HIR:NDAR:LAR:MAR:HAR:NDinvalid" - -var _v2Metric_index = [...]uint8{0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 67, 70, 75, 78, 81, 86, 91, 95, 99, 104, 109, 114, 118, 123, 128, 133, 139, 145, 150, 156, 160, 164, 168, 172, 177, 181, 185, 189, 194, 198, 202, 206, 211, 215, 219, 223, 228, 235} - -func (i v2Metric) String() string { - if i >= v2Metric(len(_v2Metric_index)-1) { - return "v2Metric(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _v2Metric_name[_v2Metric_index[i]:_v2Metric_index[i+1]] -} diff --git a/internal/cvss/v2metric_test.go b/internal/cvss/v2metric_test.go deleted file mode 100644 index f606ed5..0000000 --- a/internal/cvss/v2metric_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package cvss - -import "testing" - -func TestGetV2Metric(t *testing.T) { - tests := []struct { - val string - exp v2Metric - ok bool - } { - { "AV:N", v2AVNetwork, true }, - { "AV:A", v2AVAdjacentNetwork, true }, - { "AV:L", v2AVLocal, true }, - - { "AC:L", v2ACLow, true }, - { "AC:M", v2ACMedium, true }, - { "AC:H", v2ACHigh, true }, - - { "Au:M", v2AuMultiple, true }, - { "Au:S", v2AuSingle, true }, - { "Au:N", v2AuNone, true }, - - { "C:N", v2CNone, true }, - { "C:P", v2CPartial, true }, - { "C:C", v2CComplete, true }, - - { "I:N", v2INone, true }, - { "I:P", v2IPartial, true }, - { "I:C", v2IComplete, true }, - - { "A:N", v2ANone, true }, - { "A:P", v2APartial, true }, - { "A:C", v2AComplete, true }, - - { "E:ND", v2ENotDefined, true }, - { "E:U", v2EUnproven, true }, - { "E:POC", v2EProofOfConcept, true }, - { "E:F", v2EFunctional, true }, - { "E:H", v2EHigh, true }, - - { "RL:OF", v2RLOfficialFix, true }, - { "RL:TF", v2RLTemporaryFix, true }, - { "RL:W", v2RLWorkaround, true }, - { "RL:U", v2RLUnavailable, true }, - { "RL:ND", v2RLNotDefined, true }, - - { "RC:UC", v2RCUnconfirmed, true }, - { "RC:UR", v2RCUncorroborated, true }, - { "RC:C", v2RCConfirmed, true }, - { "RC:ND", v2RCNotDefined, true }, - - { "CDP:N", v2CDPNone, true }, - { "CDP:L", v2CDPLow, true }, - { "CDP:LM", v2CDPLowMedium, true }, - { "CDP:MH", v2CDPMediumHigh, true }, - { "CDP:H", v2CDPHigh, true }, - { "CDP:ND", v2CDPNotDefined, true }, - - { "TD:N", v2TDNone, true }, - { "TD:L", v2TDLow, true }, - { "TD:M", v2TDMedium, true }, - { "TD:H", v2TDHigh, true }, - { "TD:ND", v2TDNotDefined, true }, - - { "CR:L", v2CRLow, true }, - { "CR:M", v2CRMedium, true }, - { "CR:H", v2CRHigh, true }, - { "CR:ND", v2CRNotDefined, true }, - - { "IR:L", v2IRLow, true }, - { "IR:M", v2IRMedium, true }, - { "IR:H", v2IRHigh, true }, - { "IR:ND", v2IRNotDefined, true }, - - { "AR:L", v2ARLow, true }, - { "AR:M", v2ARMedium, true }, - { "AR:H", v2ARHigh, true }, - { "AR:ND", v2ARNotDefined, true }, - - { "asdf", v2InvalidMetric, false }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - got, err := getV2Metric(test.val) - if test.ok && err == nil && got != test.exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) - } else if test.ok && err != nil { - t.Error(err) - } else if !test.ok && err == nil { - t.Errorf("got: \"%s\", exp: error", got) - } - }) - } -} - -func TestGetV2MetricKey(t *testing.T) { - tests := []struct { - val v2Metric - exp v2Key - } { - { v2AVNetwork, v2AccessVector }, - { v2AVAdjacentNetwork, v2AccessVector }, - { v2AVLocal, v2AccessVector }, - - { v2ACLow, v2AccessComplexity }, - { v2ACMedium, v2AccessComplexity }, - { v2ACHigh, v2AccessComplexity }, - - { v2AuMultiple, v2Authentication }, - { v2AuSingle, v2Authentication }, - { v2AuNone, v2Authentication }, - - { v2CNone, v2ConfidentialityImpact }, - { v2CPartial, v2ConfidentialityImpact }, - { v2CComplete, v2ConfidentialityImpact }, - - { v2INone, v2IntegrityImpact }, - { v2IPartial, v2IntegrityImpact }, - { v2IComplete, v2IntegrityImpact }, - - { v2ANone, v2AvailabilityImpact }, - { v2APartial, v2AvailabilityImpact }, - { v2AComplete, v2AvailabilityImpact }, - - { v2ENotDefined, v2Exploitability }, - { v2EUnproven, v2Exploitability }, - { v2EProofOfConcept, v2Exploitability }, - { v2EFunctional, v2Exploitability }, - { v2EHigh, v2Exploitability }, - - { v2RLOfficialFix, v2RemediationLevel }, - { v2RLTemporaryFix, v2RemediationLevel }, - { v2RLWorkaround, v2RemediationLevel }, - { v2RLUnavailable, v2RemediationLevel }, - { v2RLNotDefined, v2RemediationLevel }, - - { v2RCUnconfirmed, v2ReportConfidence }, - { v2RCUncorroborated, v2ReportConfidence }, - { v2RCConfirmed, v2ReportConfidence }, - { v2RCNotDefined, v2ReportConfidence }, - - { v2CDPNone, v2CollateralDamagePotential }, - { v2CDPLow, v2CollateralDamagePotential }, - { v2CDPLowMedium, v2CollateralDamagePotential }, - { v2CDPMediumHigh, v2CollateralDamagePotential }, - { v2CDPHigh, v2CollateralDamagePotential }, - { v2CDPNotDefined, v2CollateralDamagePotential }, - - { v2TDNone, v2TargetDistribution }, - { v2TDLow, v2TargetDistribution }, - { v2TDMedium, v2TargetDistribution }, - { v2TDHigh, v2TargetDistribution }, - { v2TDNotDefined, v2TargetDistribution }, - - { v2CRLow, v2ConfidentialityRequirement }, - { v2CRMedium, v2ConfidentialityRequirement }, - { v2CRHigh, v2ConfidentialityRequirement }, - { v2CRNotDefined, v2ConfidentialityRequirement }, - - { v2IRLow, v2IntegrityRequirement }, - { v2IRMedium, v2IntegrityRequirement }, - { v2IRHigh, v2IntegrityRequirement }, - { v2IRNotDefined, v2IntegrityRequirement }, - - { v2ARLow, v2AvailabilityRequirement }, - { v2ARMedium, v2AvailabilityRequirement }, - { v2ARHigh, v2AvailabilityRequirement }, - { v2ARNotDefined, v2AvailabilityRequirement }, - } - - for _, test := range(tests) { - t.Run(test.val.String(), func(t *testing.T) { - got := test.val.Key() - if got != test.exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) - } - }) - } -} - -func TestV2MetricString(t *testing.T) { - tests := []struct { - val v2Metric - exp string - } { - { v2AVNetwork, "AV:N" }, - { v2AVAdjacentNetwork, "AV:A" }, - { v2AVLocal, "AV:L" }, - - { v2ACLow, "AC:L" }, - { v2ACMedium, "AC:M" }, - { v2ACHigh, "AC:H" }, - - { v2AuMultiple, "Au:M" }, - { v2AuSingle, "Au:S" }, - { v2AuNone, "Au:N" }, - - { v2CNone, "C:N" }, - { v2CPartial, "C:P" }, - { v2CComplete, "C:C" }, - - { v2INone, "I:N" }, - { v2IPartial, "I:P" }, - { v2IComplete, "I:C" }, - - { v2ANone, "A:N" }, - { v2APartial, "A:P" }, - { v2AComplete, "A:C" }, - - { v2ENotDefined, "E:ND" }, - { v2EUnproven, "E:U" }, - { v2EProofOfConcept, "E:POC" }, - { v2EFunctional, "E:F" }, - { v2EHigh, "E:H" }, - - { v2RLOfficialFix, "RL:OF" }, - { v2RLTemporaryFix, "RL:TF" }, - { v2RLWorkaround, "RL:W" }, - { v2RLUnavailable, "RL:U" }, - { v2RLNotDefined, "RL:ND" }, - - { v2RCUnconfirmed, "RC:UC" }, - { v2RCUncorroborated, "RC:UR" }, - { v2RCConfirmed, "RC:C" }, - { v2RCNotDefined, "RC:ND" }, - - { v2CDPNone, "CDP:N" }, - { v2CDPLow, "CDP:L" }, - { v2CDPLowMedium, "CDP:LM" }, - { v2CDPMediumHigh, "CDP:MH" }, - { v2CDPHigh, "CDP:H" }, - { v2CDPNotDefined, "CDP:ND" }, - - { v2TDNone, "TD:N" }, - { v2TDLow, "TD:L" }, - { v2TDMedium, "TD:M" }, - { v2TDHigh, "TD:H" }, - { v2TDNotDefined, "TD:ND" }, - - { v2CRLow, "CR:L" }, - { v2CRMedium, "CR:M" }, - { v2CRHigh, "CR:H" }, - { v2CRNotDefined, "CR:ND" }, - - { v2IRLow, "IR:L" }, - { v2IRMedium, "IR:M" }, - { v2IRHigh, "IR:H" }, - { v2IRNotDefined, "IR:ND" }, - - { v2ARLow, "AR:L" }, - { v2ARMedium, "AR:M" }, - { v2ARHigh, "AR:H" }, - { v2ARNotDefined, "AR:ND" }, - - { v2Metric(255), "v2Metric(255)" }, - } - - for _, test := range(tests) { - t.Run(test.val.String(), func(t *testing.T) { - got := test.val.String() - if got != test.exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) - } - }) - } -} - -func TestInvalidV2MetricKey(t *testing.T) { - got := v2Metric(255).Key() - exp := v2InvalidKey - - if got != exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) - } -} diff --git a/internal/cvss/v2vector.go b/internal/cvss/v2vector.go deleted file mode 100644 index b1fca1f..0000000 --- a/internal/cvss/v2vector.go +++ /dev/null @@ -1,108 +0,0 @@ -package cvss - -import ( - // "encoding/json" - "regexp" - "strings" -) - -// CVSS 2.0 vector. -type v2Vector []v2Metric - -// Convert vector to string. -func (v v2Vector) String() string { - // convert to slice of metrics - metrics := []v2Metric(v) - - // build vector - r := make([]string, len(metrics)) - for i, m := range(metrics) { - r[i] = m.String() - } - - // build and return string - return strings.Join(r, "/") -} - -// Return CVSS version. -func (v2Vector) Version() Version { - return V20 -} - -// Return metrics in this vector. -func (v v2Vector) Metrics() []Metric { - // build result - r := make([]Metric, len(v)) - for i, m := range(v) { - r[i] = m - } - - // return result - return r -} - -// Create CVSS 2.0 vector from string. -func newV2Vector(s string) (v2Vector, error) { - strs := strings.Split(s, "/") - r := make([]v2Metric, len(strs)) - - // walk metric strings - for i, ms := range(strs) { - // convert string to vector - m, err := getV2Metric(ms) - if err != nil { - return nil, err - } - - // add to results - r[i] = m - } - - // build and return vector - return v2Vector(r), nil -} - -// // Unmarshal CVSS 2.0 vector from JSON string. -// func (me *v2Vector) UnmarshalJSON(b []byte) error { -// // decode string, check for error -// var s string -// if err := json.Unmarshal(b, &s); err != nil { -// return err -// } -// -// // parse vector, check for error -// r, err := newV2Vector(s) -// if err != nil { -// // return error -// return err -// } -// -// // save result, return success -// *me = r -// return nil -// } - -var v2MetricRe = "(?:" + strings.Join([]string { - "(?:AV:[NAL])", - "(?:AC:[LMH])", - "(?:Au:[MSN])", - "(?:C:[NPC])", - "(?:I:[NPC])", - "(?:A:[NPC])", - "(?:E:(?:ND|U|POC|F|H))", - "(?:RL:(?:OF|TF|W|U|ND))", - "(?:RC:(?:UC|UR|C|ND))", - "(?:CDP:(?:N|L|LM|MH|H|ND))", - "(?:TD:(?:N|L|M|H|ND))", - "(?:CR:(?:L|M|H|ND))", - "(?:IR:(?:L|M|H|ND))", -}, "|") + ")" - -var v2VecRe = regexp.MustCompile( - "\\A" + v2MetricRe + "(?:/" + v2MetricRe + ")*\\z", -) - -// Is the given string a CVSS v2 vector string? -func isV2VectorString(s string) bool { - return len(s) > 0 && v2VecRe.MatchString(s) -} diff --git a/internal/cvss/v2vector_test.go b/internal/cvss/v2vector_test.go deleted file mode 100644 index f297ba6..0000000 --- a/internal/cvss/v2vector_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package cvss - -import ( - "testing" -) - -func TestNewV2Vector(t *testing.T) { - passTests := []struct { - val string - exp []string - } { - { - val: "AV:L/AC:L/Au:N/C:N/I:N/A:P", - exp: []string { "AV:L", "AC:L", "Au:N", "C:N", "I:N", "A:P" }, - }, - } - - for _, test := range(passTests) { - t.Run(test.val, func(t *testing.T) { - // parse vector - vec, err := newV2Vector(test.val) - if err != nil { - t.Error(err) - return - } - - // check version - if vec.Version() != V20 { - t.Errorf("got %s, exp %s", vec.Version(), V20) - return - } - - // check metric length - if len(vec.Metrics()) != len(test.exp) { - t.Errorf("got %d, exp %d", len(vec.Metrics()), len(test.exp)) - return - } - - // check metrics - for i, m := range(vec.Metrics()) { - got := m.String() - if got != test.exp[i] { - t.Errorf("got %s, exp %s", got, test.exp[i]) - } - } - }) - } - - failTests := []struct { - val string - exp string - } { - { - val: "AV:L/junk/Au:N/C:N/I:N/A:P", - exp: "invalid CVSS 2.0 metric: junk", - }, - } - - for _, test := range(failTests) { - t.Run(test.val, func(t *testing.T) { - got, err := newV2Vector(test.val) - if err != nil && err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } else if err == nil { - t.Errorf("got \"%s\", exp badMetric", got) - } - }) - } -} diff --git a/internal/cvss/v30vector.go b/internal/cvss/v30vector.go deleted file mode 100644 index 5ef1ae8..0000000 --- a/internal/cvss/v30vector.go +++ /dev/null @@ -1,119 +0,0 @@ -package cvss - -import ( - // "encoding/json" - "regexp" - "strings" -) - -// CVSS v3.0 prefix -var v30Prefix = "CVSS:3.0/" - -// CVSS 3.0 vector. -type v30Vector []v3Metric - -// Convert vector to string -func (v v30Vector) String() string { - // convert to slice of metrics - metrics := []v3Metric(v) - - // build vector - r := make([]string, len(metrics)) - for i, m := range(metrics) { - r[i] = m.String() - } - - // build and return string - return v30Prefix + strings.Join(r, "/") -} - -// Return CVSS version. -func (v30Vector) Version() Version { - return V30 -} - -// Return metrics in this vector. -func (v v30Vector) Metrics() []Metric { - // build result - r := make([]Metric, len(v)) - for i, m := range(v) { - r[i] = m - } - - // return result - return r -} - -// Create CVSS 3.0 vector from string. -func newV30Vector(s string) (v30Vector, error) { - // strip version prefix, split into metric strings - strs := strings.Split(s[len(v30Prefix):], "/") - r := make([]v3Metric, len(strs)) - - // build results - for i, ms := range(strs) { - // get metric from string - m, err := getV3Metric(V30, ms) - if err != nil { - return nil, err - } - - r[i] = m - } - - // return result - return v30Vector(r), nil -} - -// // Unmarshal CVSS 3.0 vector from JSON string. -// func (me *v30Vector) UnmarshalJSON(b []byte) error { -// // decode string, check for error -// var s string -// if err := json.Unmarshal(b, &s); err != nil { -// return err -// } -// -// // parse vector, check for error -// r, err := newV30Vector(s) -// if err != nil { -// return err -// } -// -// // save result, return success -// *me = r -// return nil -// } - -var v30VecRe = regexp.MustCompile( - "\\ACVSS:3\\.0(?:/(?:" + strings.Join([]string { - "(?:AV:[NALP])", - "(?:AC:[LH])", - "(?:PR:[NLH])", - "(?:UI:[NR])", - "(?:S:[UC])", - "(?:C:[HLN])", - "(?:I:[HLN])", - "(?:A:[HLN])", - "(?:E:[XHFPU])", - "(?:RL:[XUWTO])", - "(?:RC:[XCRU])", - "(?:CR:[XHML])", - "(?:IR:[XHML])", - "(?:AR:[XHML])", - "(?:MAV:[XNALP])", - "(?:MAC:[XLH])", - "(?:MPR:[XNLH])", - "(?:MUI:[XNR])", - "(?:MS:[XUC])", - "(?:MC:[XNLH])", - "(?:MI:[XNLH])", - "(?:MA:[XNLH])", - }, "|") + "))+\\z", -) - -// Is the given string a CVSSv3.1 vector string? -func isV30VectorString(s string) bool { - return (len(s) > len(v30Prefix)) && - (s[:len(v30Prefix)] == v30Prefix) && - v30VecRe.MatchString(s) -} diff --git a/internal/cvss/v30vector_test.go b/internal/cvss/v30vector_test.go deleted file mode 100644 index 1d44d35..0000000 --- a/internal/cvss/v30vector_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package cvss - -import ( - "testing" -) - -func TestNewV30Vector(t *testing.T) { - passTests := []struct { - val string - exp []string - } { - { - val: "CVSS:3.0/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", - exp: []string { "AV:L", "AC:H", "PR:L", "UI:N", "S:U", "C:H", "I:N", "A:N" }, - }, - } - - for _, test := range(passTests) { - t.Run(test.val, func(t *testing.T) { - // parse vector - vec, err := newV30Vector(test.val) - if err != nil { - t.Error(err) - return - } - - // check version - if vec.Version() != V30 { - t.Errorf("got %s, exp %s", vec.Version(), V30) - return - } - - // check metric length - if len(vec.Metrics()) != len(test.exp) { - t.Errorf("got %d, exp %d", len(vec.Metrics()), len(test.exp)) - return - } - - // check metrics - for i, m := range(vec.Metrics()) { - got := m.String() - if got != test.exp[i] { - t.Errorf("got %s, exp %s", got, test.exp[i]) - } - } - }) - } - - failTests := []struct { - val string - exp string - } { - { - val: "CVSS:3.0/AV:L/junk/PR:L/UI:N/S:U/C:H/I:N/A:N", - exp: "invalid CVSS 3.0 metric: junk", - }, - } - - for _, test := range(failTests) { - t.Run(test.val, func(t *testing.T) { - got, err := newV30Vector(test.val) - if err != nil && err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } else if err == nil { - t.Errorf("got \"%s\", exp badMetric", got) - } - }) - } -} diff --git a/internal/cvss/v31vector.go b/internal/cvss/v31vector.go deleted file mode 100644 index f286ea0..0000000 --- a/internal/cvss/v31vector.go +++ /dev/null @@ -1,119 +0,0 @@ -package cvss - -import ( - "regexp" - "strings" -) - -// CVSS v3.1 prefix -var v31Prefix = "CVSS:3.1/" - -// CVSS 3.1 vector. -type v31Vector []v3Metric - -// Convert vector to string -func (v v31Vector) String() string { - // convert to slice of metrics - metrics := []v3Metric(v) - - // build vector - r := make([]string, len(metrics)) - for i, m := range(metrics) { - r[i] = m.String() - } - - // build and return string - return v31Prefix + strings.Join(r, "/") -} - -// Return CVSS version. -func (v31Vector) Version() Version { - return V31 -} - -// Return metrics in this vector. -func (v v31Vector) Metrics() []Metric { - // build slice of metrics - r := make([]Metric, len(v)) - for i, m := range(v) { - r[i] = m - } - - // return result - return r -} - -// create CVSS 3.1 vector from string. -func newV31Vector(s string) (v31Vector, error) { - // strip version prefix, split into metric strings - strs := strings.Split(s[len(v31Prefix):], "/") - r := make([]v3Metric, len(strs)) - - // build results - for i, ms := range(strs) { - // get metric from string - m, err := getV3Metric(V31, ms) - if err != nil { - return nil, err - } - - // add to results - r[i] = m - } - - // return result - return v31Vector(r), nil -} - -// // Unmarshal CVSS 3.1 vector from JSON string. -// func (me *v31Vector) UnmarshalJSON(b []byte) error { -// // decode string, check for error -// var s string -// if err := json.Unmarshal(b, &s); err != nil { -// return err -// } -// -// // parse vector, check for error -// r, err := newV31Vector(s) -// if err != nil { -// return err -// } -// -// // save result, return success -// *me = r -// return nil -// } - -var v31VecRe = regexp.MustCompile( - "\\ACVSS:3\\.1(?:/(?:" + strings.Join([]string { - "(?:AV:[NALP])", - "(?:AC:[LH])", - "(?:PR:[NLH])", - "(?:UI:[NR])", - "(?:S:[UC])", - "(?:C:[HLN])", - "(?:I:[HLN])", - "(?:A:[HLN])", - "(?:E:[XHFPU])", - "(?:RL:[XUWTO])", - "(?:RC:[XCRU])", - "(?:CR:[XHML])", - "(?:IR:[XHML])", - "(?:AR:[XHML])", - "(?:MAV:[XNALP])", - "(?:MAC:[XLH])", - "(?:MPR:[XNLH])", - "(?:MUI:[XNR])", - "(?:MS:[XUC])", - "(?:MC:[XNLH])", - "(?:MI:[XNLH])", - "(?:MA:[XNLH])", - }, "|") + "))+\\z", -) - -// Is the given string a CVSSv3.1 vector string? -func isV31VectorString(s string) bool { - return (len(s) > len(v31Prefix)) && - (s[:len(v31Prefix)] == v31Prefix) && - v31VecRe.MatchString(s) -} diff --git a/internal/cvss/v31vector_test.go b/internal/cvss/v31vector_test.go deleted file mode 100644 index d05edca..0000000 --- a/internal/cvss/v31vector_test.go +++ /dev/null @@ -1,561 +0,0 @@ -package cvss - -import ( - "testing" -) - -func TestNewV31Vector(t *testing.T) { - passTests := []struct { - val string - exp []string - } { - { - val: "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", - exp: []string { "AV:L", "AC:H", "PR:L", "UI:N", "S:U", "C:H", "I:N", "A:N" }, - }, - } - - for _, test := range(passTests) { - t.Run(test.val, func(t *testing.T) { - // parse vector - vec, err := newV31Vector(test.val) - if err != nil { - t.Error(err) - return - } - - // check version - if vec.Version() != V31 { - t.Errorf("got %s, exp %s", vec.Version(), V31) - return - } - - // check metric length - if len(vec.Metrics()) != len(test.exp) { - t.Errorf("got %d, exp %d", len(vec.Metrics()), len(test.exp)) - return - } - - // check metrics - for i, m := range(vec.Metrics()) { - got := m.String() - if got != test.exp[i] { - t.Errorf("got %s, exp %s", got, test.exp[i]) - } - } - }) - } - - failTests := []struct { - val string - exp string - } { - { - val: "CVSS:3.1/AV:L/junk/PR:L/UI:N/S:U/C:H/I:N/A:N", - exp: "invalid CVSS 3.1 metric: junk", - }, - } - - for _, test := range(failTests) { - t.Run(test.val, func(t *testing.T) { - got, err := newV31Vector(test.val) - if err != nil && err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } else if err == nil { - t.Errorf("got \"%s\", exp badMetric", got) - } - }) - } -} - -func TestIsV31VectorString(t *testing.T) { - // test non v31 strings - passTests := []string { - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:C/C:L/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:H/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:P/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:P/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - } - - for _, test := range(passTests) { - t.Run(test, func(t *testing.T) { - if !isV31VectorString(test) { - t.Error("got false, exp true") - } - }) - } - - // test non v31 strings - failTests := []string { - "AV:N/AC:M/Au:S/C:N/I:P/A:C", - "AV:N/AC:M/Au:S/C:N/I:P/A:N", - "AV:N/AC:M/Au:S/C:N/I:P/A:P", - "AV:N/AC:M/Au:S/C:P/I:N/A:N", - "AV:N/AC:M/Au:S/C:P/I:N/A:P", - "AV:N/AC:M/Au:S/C:P/I:P/A:C", - "AV:N/AC:M/Au:S/C:P/I:P/A:N", - "AV:N/AC:M/Au:S/C:P/I:P/A:P", - - "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:L", - "CVSS:3.0/AV:A/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.0/AV:A/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.0/AV:A/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", - } - - for _, test := range(failTests) { - t.Run(test, func(t *testing.T) { - if isV31VectorString(test) { - t.Error("got true, exp false") - } - }) - } -} diff --git a/internal/cvss/v3key.go b/internal/cvss/v3key.go deleted file mode 100644 index 653c2da..0000000 --- a/internal/cvss/v3key.go +++ /dev/null @@ -1,116 +0,0 @@ -package cvss - -//go:generate stringer -linecomment -type=v3Key - -// CVSS v3 metric key -type v3Key byte - -const ( - v3AttackVector v3Key = iota // AV - v3AttackComplexity // AC - v3PrivilegesRequired // PR - v3UserInteraction // UI - v3Scope // S - v3Confidentiality // C - v3Integrity // I - v3Availability // A - v3ExploitCodeMaturity // E - v3RemediationLevel // RL - v3ReportConfidence // RC - v3ConfidentialityRequirement // CR - v3IntegrityRequirement // IR - v3AvailabilityRequirement // AR - v3ModifiedAttackVector // MAV - v3ModifiedAttackComplexity // MAC - v3ModifiedPrivilegesRequired // MPR - v3ModifiedUserInteraction // MUI - v3ModifiedScope // MS - v3ModifiedConfidentiality // MC - v3ModifiedIntegrity // MI - v3ModifiedAvailability // MA - - v3InvalidKey // invalid -) - -// CVSS v3 metric key info lut -var v3Keys = map[v3Key]struct { - Name string - Category Category -} { - v3AttackVector: { "Attack Vector", Base }, - v3AttackComplexity: { "Attack Complexity", Base }, - v3PrivilegesRequired: { "Privileges Required", Base }, - v3UserInteraction: { "User Interaction", Base }, - v3Scope: { "Scope", Base }, - v3Confidentiality: { "Confidentiality", Base }, - v3Integrity: { "Integrity", Base }, - v3Availability: { "Availability", Base }, - v3ExploitCodeMaturity: { "Exploit Code Maturity", Temporal }, - v3RemediationLevel: { "Remediation Level", Temporal }, - v3ReportConfidence: { "Report Confidence", Temporal }, - v3ConfidentialityRequirement: { "Confidentiality Requirement", Environmental }, - v3IntegrityRequirement: { "Integrity Requirement", Environmental }, - v3AvailabilityRequirement: { "Availability Requirement", Environmental }, - v3ModifiedAttackVector: { "Modified Attack Vector", Environmental }, - v3ModifiedAttackComplexity: { "Modified Attack Complexity", Environmental }, - v3ModifiedPrivilegesRequired: { "Modified Privileges Required", Environmental }, - v3ModifiedUserInteraction: { "Modified User Interaction", Environmental }, - v3ModifiedScope: { "Modified Scope", Environmental }, - v3ModifiedConfidentiality: { "Modified Confidentiality", Environmental }, - v3ModifiedIntegrity: { "Modified Integrity", Environmental }, - v3ModifiedAvailability: { "Modified Availability", Environmental }, -} - -// // metric key IDs lut -// var v3KeyIds = map[string]v3Key { -// "AV": v3AttackVector, -// "AC": v3AttackComplexity, -// "PR": v3PrivilegesRequired, -// "UI": v3UserInteraction, -// "S": v3Scope, -// "C": v3Confidentiality, -// "I": v3Integrity, -// "A": v3Availability, -// "E": v3ExploitCodeMaturity, -// "RL": v3RemediationLevel, -// "RC": v3ReportConfidence, -// "CR": v3ConfidentialityRequirement, -// "IR": v3IntegrityRequirement, -// "AR": v3AvailabilityRequirement, -// "MAV": v3ModifiedAttackVector, -// "MAC": v3ModifiedAttackComplexity, -// "MPR": v3ModifiedPrivilegesRequired, -// "MUI": v3ModifiedUserInteraction, -// "MS": v3ModifiedScope, -// "MC": v3ModifiedConfidentiality, -// "MI": v3ModifiedIntegrity, -// "MA": v3ModifiedAvailability, -// } -// -// // Get metric key from string. -// func getV3KeyFromString(s string) (v3Key, error) { -// k, ok := v3KeyIds[s] -// if ok { -// return k, nil -// } else { -// return v3InvalidKey, newBadKey(V30, s) -// } -// } - -// Get metric key name. -func (k v3Key) Name() string { - if data, ok := v3Keys[k]; ok { - return data.Name - } else { - return "invalid" - } -} - -// Get metric key category. -func (k v3Key) Category() Category { - if data, ok := v3Keys[k]; ok { - return data.Category - } else { - return InvalidCategory - } -} diff --git a/internal/cvss/v3key_string.go b/internal/cvss/v3key_string.go deleted file mode 100644 index 0644117..0000000 --- a/internal/cvss/v3key_string.go +++ /dev/null @@ -1,45 +0,0 @@ -// Code generated by "stringer -linecomment -type=v3Key"; DO NOT EDIT. - -package cvss - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[v3AttackVector-0] - _ = x[v3AttackComplexity-1] - _ = x[v3PrivilegesRequired-2] - _ = x[v3UserInteraction-3] - _ = x[v3Scope-4] - _ = x[v3Confidentiality-5] - _ = x[v3Integrity-6] - _ = x[v3Availability-7] - _ = x[v3ExploitCodeMaturity-8] - _ = x[v3RemediationLevel-9] - _ = x[v3ReportConfidence-10] - _ = x[v3ConfidentialityRequirement-11] - _ = x[v3IntegrityRequirement-12] - _ = x[v3AvailabilityRequirement-13] - _ = x[v3ModifiedAttackVector-14] - _ = x[v3ModifiedAttackComplexity-15] - _ = x[v3ModifiedPrivilegesRequired-16] - _ = x[v3ModifiedUserInteraction-17] - _ = x[v3ModifiedScope-18] - _ = x[v3ModifiedConfidentiality-19] - _ = x[v3ModifiedIntegrity-20] - _ = x[v3ModifiedAvailability-21] - _ = x[v3InvalidKey-22] -} - -const _v3Key_name = "AVACPRUISCIAERLRCCRIRARMAVMACMPRMUIMSMCMIMAinvalid" - -var _v3Key_index = [...]uint8{0, 2, 4, 6, 8, 9, 10, 11, 12, 13, 15, 17, 19, 21, 23, 26, 29, 32, 35, 37, 39, 41, 43, 50} - -func (i v3Key) String() string { - if i >= v3Key(len(_v3Key_index)-1) { - return "v3Key(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _v3Key_name[_v3Key_index[i]:_v3Key_index[i+1]] -} diff --git a/internal/cvss/v3key_test.go b/internal/cvss/v3key_test.go deleted file mode 100644 index 517eebd..0000000 --- a/internal/cvss/v3key_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package cvss - -import ( - "testing" -) - -func TestV3KeyString(t *testing.T) { - tests := []struct { - val v3Key - exp string - } { - { v3AttackVector, "AV" }, - { v3AttackComplexity, "AC" }, - { v3PrivilegesRequired, "PR" }, - { v3UserInteraction, "UI" }, - { v3Scope, "S" }, - { v3Confidentiality, "C" }, - { v3Integrity, "I" }, - { v3Availability, "A" }, - { v3ExploitCodeMaturity, "E" }, - { v3RemediationLevel, "RL" }, - { v3ReportConfidence, "RC" }, - { v3ConfidentialityRequirement, "CR" }, - { v3IntegrityRequirement, "IR" }, - { v3AvailabilityRequirement, "AR" }, - { v3ModifiedAttackVector, "MAV" }, - { v3ModifiedAttackComplexity, "MAC" }, - { v3ModifiedPrivilegesRequired, "MPR" }, - { v3ModifiedUserInteraction, "MUI" }, - { v3ModifiedScope, "MS" }, - { v3ModifiedConfidentiality, "MC" }, - { v3ModifiedIntegrity, "MI" }, - { v3ModifiedAvailability, "MA" }, - - { v3Key(255), "v3Key(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV3KeyName(t *testing.T) { - tests := []struct { - val v3Key - exp string - } { - { v3AttackVector, "Attack Vector" }, - { v3AttackComplexity, "Attack Complexity" }, - { v3PrivilegesRequired, "Privileges Required" }, - { v3UserInteraction, "User Interaction" }, - { v3Scope, "Scope" }, - { v3Confidentiality, "Confidentiality" }, - { v3Integrity, "Integrity" }, - { v3Availability, "Availability" }, - { v3ExploitCodeMaturity, "Exploit Code Maturity" }, - { v3RemediationLevel, "Remediation Level" }, - { v3ReportConfidence, "Report Confidence" }, - { v3ConfidentialityRequirement, "Confidentiality Requirement" }, - { v3IntegrityRequirement, "Integrity Requirement" }, - { v3AvailabilityRequirement, "Availability Requirement" }, - { v3ModifiedAttackVector, "Modified Attack Vector" }, - { v3ModifiedAttackComplexity, "Modified Attack Complexity" }, - { v3ModifiedPrivilegesRequired, "Modified Privileges Required" }, - { v3ModifiedUserInteraction, "Modified User Interaction" }, - { v3ModifiedScope, "Modified Scope" }, - { v3ModifiedConfidentiality, "Modified Confidentiality" }, - { v3ModifiedIntegrity, "Modified Integrity" }, - { v3ModifiedAvailability, "Modified Availability" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.Name() - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV3KeyCategory(t *testing.T) { - tests := []struct { - val v3Key - exp Category - } { - { v3AttackVector, Base }, - { v3AttackComplexity, Base }, - { v3PrivilegesRequired, Base }, - { v3UserInteraction, Base }, - { v3Scope, Base }, - { v3Confidentiality, Base }, - { v3Integrity, Base }, - { v3Availability, Base }, - { v3ExploitCodeMaturity, Temporal }, - { v3RemediationLevel, Temporal }, - { v3ReportConfidence, Temporal }, - { v3ConfidentialityRequirement, Environmental }, - { v3IntegrityRequirement, Environmental }, - { v3AvailabilityRequirement, Environmental }, - { v3ModifiedAttackVector, Environmental }, - { v3ModifiedAttackComplexity, Environmental }, - { v3ModifiedPrivilegesRequired, Environmental }, - { v3ModifiedUserInteraction, Environmental }, - { v3ModifiedScope, Environmental }, - { v3ModifiedConfidentiality, Environmental }, - { v3ModifiedIntegrity, Environmental }, - { v3ModifiedAvailability, Environmental }, - } - - for _, test := range(tests) { - t.Run(test.val.String(), func(t *testing.T) { - got := test.val.Category() - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestInvalidV3KeyName(t *testing.T) { - exp := "invalid" - got := v3Key(255).Name() - - if got != exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) - } -} - -func TestInvalidV3KeyCategory(t *testing.T) { - exp := InvalidCategory - got := v3Key(255).Category() - - if got != exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) - } -} diff --git a/internal/cvss/v3metric.go b/internal/cvss/v3metric.go deleted file mode 100644 index fc4c860..0000000 --- a/internal/cvss/v3metric.go +++ /dev/null @@ -1,331 +0,0 @@ -package cvss - -//go:generate stringer -linecomment -type=v3Metric - -// metric value -type v3Metric byte - -const ( - v3AVNetwork v3Metric = iota // AV:N - v3AVAdjacentNetwork // AV:A - v3AVLocal // AV:L - v3AVPhysical // AV:P - - v3ACLow // AC:L - v3ACHigh // AC:H - - v3PRNone // PR:N - v3PRLow // PR:L - v3PRHigh // PR:H - - v3UINone // UI:N - v3UIRequired // UI:R - - v3SUnchanged // S:U - v3SChanged // S:C - - v3CHigh // C:H - v3CLow // C:L - v3CNone // C:N - - v3IHigh // I:H - v3ILow // I:L - v3INone // I:N - - v3AHigh // A:H - v3ALow // A:L - v3ANone // A:N - - v3ENotDefined // E:X - v3EHigh // E:H - v3EFunctional // E:F - v3EProofOfConcept // E:P - v3EUnproven // E:U - - v3RLNotDefined // RL:X - v3RLUnavailable // RL:U - v3RLWorkaround // RL:W - v3RLTemporaryFix // RL:T - v3RLOfficialFix // RL:O - - v3RCNotDefined // RC:X - v3RCConfirmed // RC:C - v3RCReasonable // RC:R - v3RCUnknown // RC:U - - v3CRNotDefined // CR:X - v3CRHigh // CR:H - v3CRMedium // CR:M - v3CRLow // CR:L - - v3IRNotDefined // IR:X - v3IRHigh // IR:H - v3IRMedium // IR:M - v3IRLow // IR:L - - v3ARNotDefined // AR:X - v3ARHigh // AR:H - v3ARMedium // AR:M - v3ARLow // AR:L - - v3MAVNotDefined // MAV:X - v3MAVNetwork // MAV:N - v3MAVAdjacentNetwork // MAV:A - v3MAVLocal // MAV:L - v3MAVPhysical // MAV:P - - v3MACNotDefined // MAC:X - v3MACLow // MAC:L - v3MACHigh // MAC:H - - v3MMRNotDefined // MPR:X - v3MPRLow // MPR:L - v3MPRHigh // MPR:H - - v3MUINotDefined // MUI:X - v3MUINone // MUI:N - v3MUIRequired // MUI:R - - v3MSNotDefined // MMS:X - v3MSUnchanged // MMS:U - v3MSChanged // MMS:C - - v3MCNotDefined // MC:X - v3MCHigh // MC:H - v3MCLow // MC:L - v3MCNone // MC:N - - v3MINotDefined // MI:X - v3MIHigh // MI:H - v3MILow // MI:L - v3MINone // MI:N - - v3MANotDefined // MA:X - v3MAHigh // MA:H - v3MALow // MA:L - v3MANone // MA:N - - v3InvalidMetric // invalid -) - -// map of metrics to metric keys -var v3KeyLut = map[v3Metric]v3Key { - v3AVNetwork: v3AttackVector, // AV:N - v3AVAdjacentNetwork: v3AttackVector, // AV:A - v3AVLocal: v3AttackVector, // AV:L - v3AVPhysical: v3AttackVector, // AV:P - - v3ACLow: v3AttackComplexity, // AC:L - v3ACHigh: v3AttackComplexity, // AC:H - - v3PRNone: v3PrivilegesRequired, // PR:N - v3PRLow: v3PrivilegesRequired, // PR:L - v3PRHigh: v3PrivilegesRequired, // PR:H - - v3UINone: v3UserInteraction, // UI:N - v3UIRequired: v3UserInteraction, // UI:R - - v3SUnchanged: v3Scope, // S:U - v3SChanged: v3Scope, // S:C - - v3CHigh: v3Confidentiality, // C:H - v3CLow: v3Confidentiality, // C:L - v3CNone: v3Confidentiality, // C:N - - v3IHigh: v3Integrity, // I:H - v3ILow: v3Integrity, // I:L - v3INone: v3Integrity, // I:N - - v3AHigh: v3Availability, // A:H - v3ALow: v3Availability, // A:L - v3ANone: v3Availability, // A:N - - v3ENotDefined: v3ExploitCodeMaturity, // E:X - v3EHigh: v3ExploitCodeMaturity, // E:H - v3EFunctional: v3ExploitCodeMaturity, // E:F - v3EProofOfConcept: v3ExploitCodeMaturity, // E:P - v3EUnproven: v3ExploitCodeMaturity, // E:U - - v3RLNotDefined: v3RemediationLevel, // RL:X - v3RLUnavailable: v3RemediationLevel, // RL:U - v3RLWorkaround: v3RemediationLevel, // RL:W - v3RLTemporaryFix: v3RemediationLevel, // RL:T - v3RLOfficialFix: v3RemediationLevel, // RL:O - - v3RCNotDefined: v3ReportConfidence, // RC:X - v3RCConfirmed: v3ReportConfidence, // RC:C - v3RCReasonable: v3ReportConfidence, // RC:R - v3RCUnknown: v3ReportConfidence, // RC:U - - v3CRNotDefined: v3ConfidentialityRequirement, // CR:X - v3CRHigh: v3ConfidentialityRequirement, // CR:H - v3CRMedium: v3ConfidentialityRequirement, // CR:M - v3CRLow: v3ConfidentialityRequirement, // CR:L - - v3IRNotDefined: v3IntegrityRequirement, // IR:X - v3IRHigh: v3IntegrityRequirement, // IR:H - v3IRMedium: v3IntegrityRequirement, // IR:M - v3IRLow: v3IntegrityRequirement, // IR:L - - v3ARNotDefined: v3AvailabilityRequirement, // AR:X - v3ARHigh: v3AvailabilityRequirement, // AR:H - v3ARMedium: v3AvailabilityRequirement, // AR:M - v3ARLow: v3AvailabilityRequirement, // AR:L - - v3MAVNotDefined: v3ModifiedAttackVector, // MAV:X - v3MAVNetwork: v3ModifiedAttackVector, // MAV:N - v3MAVAdjacentNetwork: v3ModifiedAttackVector, // MAV:A - v3MAVLocal: v3ModifiedAttackVector, // MAV:L - v3MAVPhysical: v3ModifiedAttackVector, // MAV:P - - v3MACNotDefined: v3ModifiedAttackComplexity, // MAC:X - v3MACLow: v3ModifiedAttackComplexity, // MAC:L - v3MACHigh: v3ModifiedAttackComplexity, // MAC:H - - v3MMRNotDefined: v3ModifiedPrivilegesRequired, // MPR:X - v3MPRLow: v3ModifiedPrivilegesRequired, // MPR:L - v3MPRHigh: v3ModifiedPrivilegesRequired, // MPR:H - - v3MUINotDefined: v3ModifiedUserInteraction, // MUI:X - v3MUINone: v3ModifiedUserInteraction, // MUI:N - v3MUIRequired: v3ModifiedUserInteraction, // MUI:R - - v3MSNotDefined: v3ModifiedScope, // MMS:X - v3MSUnchanged: v3ModifiedConfidentiality, // MMS:U - v3MSChanged: v3ModifiedIntegrity, // MMS:C - - v3MCNotDefined: v3ModifiedConfidentiality, // MC:X - v3MCHigh: v3ModifiedConfidentiality, // MC:H - v3MCLow: v3ModifiedConfidentiality, // MC:L - v3MCNone: v3ModifiedConfidentiality, // MC:N - - v3MINotDefined: v3ModifiedIntegrity, // MI:X - v3MIHigh: v3ModifiedIntegrity, // MI:H - v3MILow: v3ModifiedIntegrity, // MI:L - v3MINone: v3ModifiedIntegrity, // MI:N - - v3MANotDefined: v3ModifiedAvailability, // MA:X - v3MAHigh: v3ModifiedAvailability, // MA:H - v3MALow: v3ModifiedAvailability, // MA:L - v3MANone: v3ModifiedAvailability, // MA:N -} - -// map of metric strings to metrics -var v3MetricStrLut = map[string]v3Metric { - "AV:N": v3AVNetwork, - "AV:A": v3AVAdjacentNetwork, - "AV:L": v3AVLocal, - "AV:P": v3AVPhysical, - - "AC:L": v3ACLow, - "AC:H": v3ACHigh, - - "PR:N": v3PRNone, - "PR:L": v3PRLow, - "PR:H": v3PRHigh, - - "UI:N": v3UINone, - "UI:R": v3UIRequired, - - "S:U": v3SUnchanged, - "S:C": v3SChanged, - - "C:H": v3CHigh, - "C:L": v3CLow, - "C:N": v3CNone, - - "I:H": v3IHigh, - "I:L": v3ILow, - "I:N": v3INone, - - "A:H": v3AHigh, - "A:L": v3ALow, - "A:N": v3ANone, - - "E:X": v3ENotDefined, - "E:H": v3EHigh, - "E:F": v3EFunctional, - "E:P": v3EProofOfConcept, - "E:U": v3EUnproven, - - "RL:X": v3RLNotDefined, - "RL:U": v3RLUnavailable, - "RL:W": v3RLWorkaround, - "RL:T": v3RLTemporaryFix, - "RL:O": v3RLOfficialFix, - - "RC:X": v3RCNotDefined, - "RC:C": v3RCConfirmed, - "RC:R": v3RCReasonable, - "RC:U": v3RCUnknown, - - "CR:X": v3CRNotDefined, - "CR:H": v3CRHigh, - "CR:M": v3CRMedium, - "CR:L": v3CRLow, - - "IR:X": v3IRNotDefined, - "IR:H": v3IRHigh, - "IR:M": v3IRMedium, - "IR:L": v3IRLow, - - "AR:X": v3ARNotDefined, - "AR:H": v3ARHigh, - "AR:M": v3ARMedium, - "AR:L": v3ARLow, - - "MAV:X": v3MAVNotDefined, - "MAV:N": v3MAVNetwork, - "MAV:A": v3MAVAdjacentNetwork, - "MAV:L": v3MAVLocal, - "MAV:P": v3MAVPhysical, - - "MAC:X": v3MACNotDefined, - "MAC:L": v3MACLow, - "MAC:H": v3MACHigh, - - "MPR:X": v3MMRNotDefined, - "MPR:L": v3MPRLow, - "MPR:H": v3MPRHigh, - - "MUI:X": v3MUINotDefined, - "MUI:N": v3MUINone, - "MUI:R": v3MUIRequired, - - "MMS:X": v3MSNotDefined, - "MMS:U": v3MSUnchanged, - "MMS:C": v3MSChanged, - - "MC:X": v3MCNotDefined, - "MC:H": v3MCHigh, - "MC:L": v3MCLow, - "MC:N": v3MCNone, - - "MI:X": v3MINotDefined, - "MI:H": v3MIHigh, - "MI:L": v3MILow, - "MI:N": v3MINone, - - "MA:X": v3MANotDefined, - "MA:H": v3MAHigh, - "MA:L": v3MALow, - "MA:N": v3MANone, -} - -// Get CVSS 3.x metric key. -func (m v3Metric) Key() Key { - if k, ok := v3KeyLut[m]; ok { - return k - } else { - return v3InvalidKey - } -} - -// Convert string to CVSS 3.1 metric. -func getV3Metric(version Version, s string) (v3Metric, error) { - if m, ok := v3MetricStrLut[s]; ok { - return m, nil - } else { - return v3InvalidMetric, newBadMetric(version, s) - } -} diff --git a/internal/cvss/v3metric_string.go b/internal/cvss/v3metric_string.go deleted file mode 100644 index 612ed83..0000000 --- a/internal/cvss/v3metric_string.go +++ /dev/null @@ -1,100 +0,0 @@ -// Code generated by "stringer -linecomment -type=v3Metric"; DO NOT EDIT. - -package cvss - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[v3AVNetwork-0] - _ = x[v3AVAdjacentNetwork-1] - _ = x[v3AVLocal-2] - _ = x[v3AVPhysical-3] - _ = x[v3ACLow-4] - _ = x[v3ACHigh-5] - _ = x[v3PRNone-6] - _ = x[v3PRLow-7] - _ = x[v3PRHigh-8] - _ = x[v3UINone-9] - _ = x[v3UIRequired-10] - _ = x[v3SUnchanged-11] - _ = x[v3SChanged-12] - _ = x[v3CHigh-13] - _ = x[v3CLow-14] - _ = x[v3CNone-15] - _ = x[v3IHigh-16] - _ = x[v3ILow-17] - _ = x[v3INone-18] - _ = x[v3AHigh-19] - _ = x[v3ALow-20] - _ = x[v3ANone-21] - _ = x[v3ENotDefined-22] - _ = x[v3EHigh-23] - _ = x[v3EFunctional-24] - _ = x[v3EProofOfConcept-25] - _ = x[v3EUnproven-26] - _ = x[v3RLNotDefined-27] - _ = x[v3RLUnavailable-28] - _ = x[v3RLWorkaround-29] - _ = x[v3RLTemporaryFix-30] - _ = x[v3RLOfficialFix-31] - _ = x[v3RCNotDefined-32] - _ = x[v3RCConfirmed-33] - _ = x[v3RCReasonable-34] - _ = x[v3RCUnknown-35] - _ = x[v3CRNotDefined-36] - _ = x[v3CRHigh-37] - _ = x[v3CRMedium-38] - _ = x[v3CRLow-39] - _ = x[v3IRNotDefined-40] - _ = x[v3IRHigh-41] - _ = x[v3IRMedium-42] - _ = x[v3IRLow-43] - _ = x[v3ARNotDefined-44] - _ = x[v3ARHigh-45] - _ = x[v3ARMedium-46] - _ = x[v3ARLow-47] - _ = x[v3MAVNotDefined-48] - _ = x[v3MAVNetwork-49] - _ = x[v3MAVAdjacentNetwork-50] - _ = x[v3MAVLocal-51] - _ = x[v3MAVPhysical-52] - _ = x[v3MACNotDefined-53] - _ = x[v3MACLow-54] - _ = x[v3MACHigh-55] - _ = x[v3MMRNotDefined-56] - _ = x[v3MPRLow-57] - _ = x[v3MPRHigh-58] - _ = x[v3MUINotDefined-59] - _ = x[v3MUINone-60] - _ = x[v3MUIRequired-61] - _ = x[v3MSNotDefined-62] - _ = x[v3MSUnchanged-63] - _ = x[v3MSChanged-64] - _ = x[v3MCNotDefined-65] - _ = x[v3MCHigh-66] - _ = x[v3MCLow-67] - _ = x[v3MCNone-68] - _ = x[v3MINotDefined-69] - _ = x[v3MIHigh-70] - _ = x[v3MILow-71] - _ = x[v3MINone-72] - _ = x[v3MANotDefined-73] - _ = x[v3MAHigh-74] - _ = x[v3MALow-75] - _ = x[v3MANone-76] - _ = x[v3InvalidMetric-77] -} - -const _v3Metric_name = "AV:NAV:AAV:LAV:PAC:LAC:HPR:NPR:LPR:HUI:NUI:RS:US:CC:HC:LC:NI:HI:LI:NA:HA:LA:NE:XE:HE:FE:PE:URL:XRL:URL:WRL:TRL:ORC:XRC:CRC:RRC:UCR:XCR:HCR:MCR:LIR:XIR:HIR:MIR:LAR:XAR:HAR:MAR:LMAV:XMAV:NMAV:AMAV:LMAV:PMAC:XMAC:LMAC:HMPR:XMPR:LMPR:HMUI:XMUI:NMUI:RMMS:XMMS:UMMS:CMC:XMC:HMC:LMC:NMI:XMI:HMI:LMI:NMA:XMA:HMA:LMA:Ninvalid" - -var _v3Metric_index = [...]uint16{0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 47, 50, 53, 56, 59, 62, 65, 68, 71, 74, 77, 80, 83, 86, 89, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160, 164, 168, 172, 176, 181, 186, 191, 196, 201, 206, 211, 216, 221, 226, 231, 236, 241, 246, 251, 256, 261, 265, 269, 273, 277, 281, 285, 289, 293, 297, 301, 305, 309, 316} - -func (i v3Metric) String() string { - if i >= v3Metric(len(_v3Metric_index)-1) { - return "v3Metric(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _v3Metric_name[_v3Metric_index[i]:_v3Metric_index[i+1]] -} diff --git a/internal/cvss/v3metric_test.go b/internal/cvss/v3metric_test.go deleted file mode 100644 index 432d34a..0000000 --- a/internal/cvss/v3metric_test.go +++ /dev/null @@ -1,366 +0,0 @@ -package cvss - -import "testing" - -func TestGetV3Metric(t *testing.T) { - tests := []struct { - val string - exp v3Metric - ok bool - } { - { "AV:N", v3AVNetwork, true }, - { "AV:A", v3AVAdjacentNetwork, true }, - { "AV:L", v3AVLocal, true }, - { "AV:P", v3AVPhysical, true }, - - { "AC:L", v3ACLow, true }, - { "AC:H", v3ACHigh, true }, - - { "PR:N", v3PRNone, true }, - { "PR:L", v3PRLow, true }, - { "PR:H", v3PRHigh, true }, - - { "UI:N", v3UINone, true }, - { "UI:R", v3UIRequired, true }, - - { "S:U", v3SUnchanged, true }, - { "S:C", v3SChanged, true }, - - { "C:H", v3CHigh, true }, - { "C:L", v3CLow, true }, - { "C:N", v3CNone, true }, - - { "I:H", v3IHigh, true }, - { "I:L", v3ILow, true }, - { "I:N", v3INone, true }, - - { "A:H", v3AHigh, true }, - { "A:L", v3ALow, true }, - { "A:N", v3ANone, true }, - - { "E:X", v3ENotDefined, true }, - { "E:H", v3EHigh, true }, - { "E:F", v3EFunctional, true }, - { "E:P", v3EProofOfConcept, true }, - { "E:U", v3EUnproven, true }, - - { "RL:X", v3RLNotDefined, true }, - { "RL:U", v3RLUnavailable, true }, - { "RL:W", v3RLWorkaround, true }, - { "RL:T", v3RLTemporaryFix, true }, - { "RL:O", v3RLOfficialFix, true }, - - { "RC:X", v3RCNotDefined, true }, - { "RC:C", v3RCConfirmed, true }, - { "RC:R", v3RCReasonable, true }, - { "RC:U", v3RCUnknown, true }, - - { "CR:X", v3CRNotDefined, true }, - { "CR:H", v3CRHigh, true }, - { "CR:M", v3CRMedium, true }, - { "CR:L", v3CRLow, true }, - - { "IR:X", v3IRNotDefined, true }, - { "IR:H", v3IRHigh, true }, - { "IR:M", v3IRMedium, true }, - { "IR:L", v3IRLow, true }, - - { "AR:X", v3ARNotDefined, true }, - { "AR:H", v3ARHigh, true }, - { "AR:M", v3ARMedium, true }, - { "AR:L", v3ARLow, true }, - - { "MAV:X", v3MAVNotDefined, true }, - { "MAV:N", v3MAVNetwork, true }, - { "MAV:A", v3MAVAdjacentNetwork, true }, - { "MAV:L", v3MAVLocal, true }, - { "MAV:P", v3MAVPhysical, true }, - - { "MAC:X", v3MACNotDefined, true }, - { "MAC:L", v3MACLow, true }, - { "MAC:H", v3MACHigh, true }, - - { "MPR:X", v3MMRNotDefined, true }, - { "MPR:L", v3MPRLow, true }, - { "MPR:H", v3MPRHigh, true }, - - { "MUI:X", v3MUINotDefined, true }, - { "MUI:N", v3MUINone, true }, - { "MUI:R", v3MUIRequired, true }, - - { "MMS:X", v3MSNotDefined, true }, - { "MMS:U", v3MSUnchanged, true }, - { "MMS:C", v3MSChanged, true }, - - { "MC:X", v3MCNotDefined, true }, - { "MC:H", v3MCHigh, true }, - { "MC:L", v3MCLow, true }, - { "MC:N", v3MCNone, true }, - - { "MI:X", v3MINotDefined, true }, - { "MI:H", v3MIHigh, true }, - { "MI:L", v3MILow, true }, - { "MI:N", v3MINone, true }, - - { "MA:X", v3MANotDefined, true }, - { "MA:H", v3MAHigh, true }, - { "MA:L", v3MALow, true }, - { "MA:N", v3MANone, true }, - - { "invalid", v3InvalidMetric, false }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - got, err := getV3Metric(V31, test.val) - if test.ok && err == nil && got != test.exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) - } else if test.ok && err != nil { - t.Error(err) - } else if !test.ok && err == nil { - t.Errorf("got: \"%s\", exp: error", got) - } - }) - } -} - -func TestGetV3MetricKey(t *testing.T) { - tests := []struct { - val v3Metric - exp v3Key - } { - { v3AVNetwork, v3AttackVector }, // AV:N - { v3AVAdjacentNetwork, v3AttackVector }, // AV:A - { v3AVLocal, v3AttackVector }, // AV:L - { v3AVPhysical, v3AttackVector }, // AV:P - - { v3ACLow, v3AttackComplexity }, // AC:L - { v3ACHigh, v3AttackComplexity }, // AC:H - - { v3PRNone, v3PrivilegesRequired }, // PR:N - { v3PRLow, v3PrivilegesRequired }, // PR:L - { v3PRHigh, v3PrivilegesRequired }, // PR:H - - { v3UINone, v3UserInteraction }, // UI:N - { v3UIRequired, v3UserInteraction }, // UI:R - - { v3SUnchanged, v3Scope }, // S:U - { v3SChanged, v3Scope }, // S:C - - { v3CHigh, v3Confidentiality }, // C:H - { v3CLow, v3Confidentiality }, // C:L - { v3CNone, v3Confidentiality }, // C:N - - { v3IHigh, v3Integrity }, // I:H - { v3ILow, v3Integrity }, // I:L - { v3INone, v3Integrity }, // I:N - - { v3AHigh, v3Availability }, // A:H - { v3ALow, v3Availability }, // A:L - { v3ANone, v3Availability }, // A:N - - { v3ENotDefined, v3ExploitCodeMaturity }, // E:X - { v3EHigh, v3ExploitCodeMaturity }, // E:H - { v3EFunctional, v3ExploitCodeMaturity }, // E:F - { v3EProofOfConcept, v3ExploitCodeMaturity }, // E:P - { v3EUnproven, v3ExploitCodeMaturity }, // E:U - - { v3RLNotDefined, v3RemediationLevel }, // RL:X - { v3RLUnavailable, v3RemediationLevel }, // RL:U - { v3RLWorkaround, v3RemediationLevel }, // RL:W - { v3RLTemporaryFix, v3RemediationLevel }, // RL:T - { v3RLOfficialFix, v3RemediationLevel }, // RL:O - - { v3RCNotDefined, v3ReportConfidence }, // RC:X - { v3RCConfirmed, v3ReportConfidence }, // RC:C - { v3RCReasonable, v3ReportConfidence }, // RC:R - { v3RCUnknown, v3ReportConfidence }, // RC:U - - { v3CRNotDefined, v3ConfidentialityRequirement }, // CR:X - { v3CRHigh, v3ConfidentialityRequirement }, // CR:H - { v3CRMedium, v3ConfidentialityRequirement }, // CR:M - { v3CRLow, v3ConfidentialityRequirement }, // CR:L - - { v3IRNotDefined, v3IntegrityRequirement }, // IR:X - { v3IRHigh, v3IntegrityRequirement }, // IR:H - { v3IRMedium, v3IntegrityRequirement }, // IR:M - { v3IRLow, v3IntegrityRequirement }, // IR:L - - { v3ARNotDefined, v3AvailabilityRequirement }, // AR:X - { v3ARHigh, v3AvailabilityRequirement }, // AR:H - { v3ARMedium, v3AvailabilityRequirement }, // AR:M - { v3ARLow, v3AvailabilityRequirement }, // AR:L - - { v3MAVNotDefined, v3ModifiedAttackVector }, // MAV:X - { v3MAVNetwork, v3ModifiedAttackVector }, // MAV:N - { v3MAVAdjacentNetwork, v3ModifiedAttackVector }, // MAV:A - { v3MAVLocal, v3ModifiedAttackVector }, // MAV:L - { v3MAVPhysical, v3ModifiedAttackVector }, // MAV:P - - { v3MACNotDefined, v3ModifiedAttackComplexity }, // MAC:X - { v3MACLow, v3ModifiedAttackComplexity }, // MAC:L - { v3MACHigh, v3ModifiedAttackComplexity }, // MAC:H - - { v3MMRNotDefined, v3ModifiedPrivilegesRequired }, // MPR:X - { v3MPRLow, v3ModifiedPrivilegesRequired }, // MPR:L - { v3MPRHigh, v3ModifiedPrivilegesRequired }, // MPR:H - - { v3MUINotDefined, v3ModifiedUserInteraction }, // MUI:X - { v3MUINone, v3ModifiedUserInteraction }, // MUI:N - { v3MUIRequired, v3ModifiedUserInteraction }, // MUI:R - - { v3MSNotDefined, v3ModifiedScope }, // MMS:X - { v3MSUnchanged, v3ModifiedConfidentiality }, // MMS:U - { v3MSChanged, v3ModifiedIntegrity }, // MMS:C - - { v3MCNotDefined, v3ModifiedConfidentiality }, // MC:X - { v3MCHigh, v3ModifiedConfidentiality }, // MC:H - { v3MCLow, v3ModifiedConfidentiality }, // MC:L - { v3MCNone, v3ModifiedConfidentiality }, // MC:N - - { v3MINotDefined, v3ModifiedIntegrity }, // MI:X - { v3MIHigh, v3ModifiedIntegrity }, // MI:H - { v3MILow, v3ModifiedIntegrity }, // MI:L - { v3MINone, v3ModifiedIntegrity }, // MI:N - - { v3MANotDefined, v3ModifiedAvailability }, // MA:X - { v3MAHigh, v3ModifiedAvailability }, // MA:H - { v3MALow, v3ModifiedAvailability }, // MA:L - { v3MANone, v3ModifiedAvailability }, // MA:N - } - - for _, test := range(tests) { - t.Run(test.val.String(), func(t *testing.T) { - got := test.val.Key() - if got != test.exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) - } - }) - } -} - -func TestV3MetricString(t *testing.T) { - tests := []struct { - val v3Metric - exp string - } { - { v3AVNetwork, "AV:N" }, - { v3AVAdjacentNetwork, "AV:A" }, - { v3AVLocal, "AV:L" }, - { v3AVPhysical, "AV:P" }, - - { v3ACLow, "AC:L" }, - { v3ACHigh, "AC:H" }, - - { v3PRNone, "PR:N" }, - { v3PRLow, "PR:L" }, - { v3PRHigh, "PR:H" }, - - { v3UINone, "UI:N" }, - { v3UIRequired, "UI:R" }, - - { v3SUnchanged, "S:U" }, - { v3SChanged, "S:C" }, - - { v3CHigh, "C:H" }, - { v3CLow, "C:L" }, - { v3CNone, "C:N" }, - - { v3IHigh, "I:H" }, - { v3ILow, "I:L" }, - { v3INone, "I:N" }, - - { v3AHigh, "A:H" }, - { v3ALow, "A:L" }, - { v3ANone, "A:N" }, - - { v3ENotDefined, "E:X" }, - { v3EHigh, "E:H" }, - { v3EFunctional, "E:F" }, - { v3EProofOfConcept, "E:P" }, - { v3EUnproven, "E:U" }, - - { v3RLNotDefined, "RL:X" }, - { v3RLUnavailable, "RL:U" }, - { v3RLWorkaround, "RL:W" }, - { v3RLTemporaryFix, "RL:T" }, - { v3RLOfficialFix, "RL:O" }, - - { v3RCNotDefined, "RC:X" }, - { v3RCConfirmed, "RC:C" }, - { v3RCReasonable, "RC:R" }, - { v3RCUnknown, "RC:U" }, - - { v3CRNotDefined, "CR:X" }, - { v3CRHigh, "CR:H" }, - { v3CRMedium, "CR:M" }, - { v3CRLow, "CR:L" }, - - { v3IRNotDefined, "IR:X" }, - { v3IRHigh, "IR:H" }, - { v3IRMedium, "IR:M" }, - { v3IRLow, "IR:L" }, - - { v3ARNotDefined, "AR:X" }, - { v3ARHigh, "AR:H" }, - { v3ARMedium, "AR:M" }, - { v3ARLow, "AR:L" }, - - { v3MAVNotDefined, "MAV:X" }, - { v3MAVNetwork, "MAV:N" }, - { v3MAVAdjacentNetwork, "MAV:A" }, - { v3MAVLocal, "MAV:L" }, - { v3MAVPhysical, "MAV:P" }, - - { v3MACNotDefined, "MAC:X" }, - { v3MACLow, "MAC:L" }, - { v3MACHigh, "MAC:H" }, - - { v3MMRNotDefined, "MPR:X" }, - { v3MPRLow, "MPR:L" }, - { v3MPRHigh, "MPR:H" }, - - { v3MUINotDefined, "MUI:X" }, - { v3MUINone, "MUI:N" }, - { v3MUIRequired, "MUI:R" }, - - { v3MSNotDefined, "MMS:X" }, - { v3MSUnchanged, "MMS:U" }, - { v3MSChanged, "MMS:C" }, - - { v3MCNotDefined, "MC:X" }, - { v3MCHigh, "MC:H" }, - { v3MCLow, "MC:L" }, - { v3MCNone, "MC:N" }, - - { v3MINotDefined, "MI:X" }, - { v3MIHigh, "MI:H" }, - { v3MILow, "MI:L" }, - { v3MINone, "MI:N" }, - - { v3MANotDefined, "MA:X" }, - { v3MAHigh, "MA:H" }, - { v3MALow, "MA:L" }, - { v3MANone, "MA:N" }, - - { v3Metric(255), "v3Metric(255)" }, - } - - for _, test := range(tests) { - t.Run(test.val.String(), func(t *testing.T) { - got := test.val.String() - if got != test.exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) - } - }) - } -} - -func TestInvalidV3MetricKey(t *testing.T) { - got := v3Metric(255).Key() - exp := v3InvalidKey - - if got != exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, exp) - } -} diff --git a/internal/cvss/vector.go b/internal/cvss/vector.go deleted file mode 100644 index 3e465c5..0000000 --- a/internal/cvss/vector.go +++ /dev/null @@ -1,36 +0,0 @@ -package cvss - -import ( - "fmt" -) - -// CVSS metric vector. -type Vector interface { - // Get CVSS version. - Version() Version - - // Get CVSS vector string. - String() string - - // Return metrics in this vector. - Metrics() []Metric - - // Unmarshal vector from JSON. - // UnmarshalJSON(b []byte) error -} - -// Create new CVSS vector from vector string. -func NewVector(s string) (Vector, error) { - if isV31VectorString(s) { - // create CVSS v3.1 vector. - return newV31Vector(s) - } else if isV30VectorString(s) { - // create CVSS v3.0 vector. - return newV30Vector(s) - } else if isV2VectorString(s) { - // create CVSS v2 vector. - return newV2Vector(s) - } else { - return nil, fmt.Errorf("invalid CVSS vector: %s", s) - } -} diff --git a/internal/cvss/vector_test.go b/internal/cvss/vector_test.go deleted file mode 100644 index a9ea820..0000000 --- a/internal/cvss/vector_test.go +++ /dev/null @@ -1,659 +0,0 @@ -package cvss - -import "testing" - -func TestNewVector(t *testing.T) { - passTests := []string { - "AV:A/AC:H/Au:S/C:C/I:C/A:C", - "AV:A/AC:H/Au:S/C:N/I:N/A:P", - "AV:A/AC:H/Au:S/C:P/I:P/A:P", - "AV:A/AC:L/Au:N/C:C/I:C/A:C", - "AV:A/AC:L/Au:N/C:N/I:N/A:C", - "AV:A/AC:L/Au:N/C:N/I:N/A:P", - "AV:A/AC:L/Au:N/C:N/I:P/A:N", - "AV:A/AC:L/Au:N/C:P/I:N/A:C", - "AV:A/AC:L/Au:N/C:P/I:N/A:N", - "AV:A/AC:L/Au:N/C:P/I:P/A:N", - "AV:A/AC:L/Au:N/C:P/I:P/A:P", - "AV:A/AC:L/Au:S/C:C/I:C/A:C", - "AV:A/AC:L/Au:S/C:C/I:N/A:C", - "AV:A/AC:L/Au:S/C:N/I:N/A:P", - "AV:A/AC:L/Au:S/C:N/I:P/A:N", - "AV:A/AC:L/Au:S/C:P/I:N/A:N", - "AV:A/AC:L/Au:S/C:P/I:N/A:P", - "AV:A/AC:L/Au:S/C:P/I:P/A:N", - "AV:A/AC:L/Au:S/C:P/I:P/A:P", - "AV:A/AC:M/Au:N/C:C/I:C/A:C", - "AV:A/AC:M/Au:N/C:N/I:N/A:C", - "AV:A/AC:M/Au:N/C:N/I:N/A:P", - "AV:A/AC:M/Au:N/C:N/I:P/A:N", - "AV:A/AC:M/Au:N/C:N/I:P/A:P", - "AV:A/AC:M/Au:N/C:P/I:N/A:N", - "AV:A/AC:M/Au:N/C:P/I:P/A:N", - "AV:A/AC:M/Au:N/C:P/I:P/A:P", - "AV:A/AC:M/Au:S/C:C/I:C/A:C", - "AV:A/AC:M/Au:S/C:N/I:N/A:P", - "AV:A/AC:M/Au:S/C:N/I:P/A:N", - "AV:A/AC:M/Au:S/C:P/I:N/A:N", - "AV:A/AC:M/Au:S/C:P/I:P/A:P", - "AV:L/AC:H/Au:N/C:C/I:C/A:C", - "AV:L/AC:H/Au:N/C:N/I:N/A:C", - "AV:L/AC:H/Au:N/C:N/I:N/A:P", - "AV:L/AC:H/Au:N/C:N/I:P/A:N", - "AV:L/AC:H/Au:N/C:P/I:N/A:N", - "AV:L/AC:H/Au:N/C:P/I:P/A:N", - "AV:L/AC:H/Au:S/C:P/I:P/A:P", - "AV:L/AC:L/Au:N/C:C/I:C/A:C", - "AV:L/AC:L/Au:N/C:C/I:C/A:N", - "AV:L/AC:L/Au:N/C:C/I:N/A:C", - "AV:L/AC:L/Au:N/C:C/I:N/A:N", - "AV:L/AC:L/Au:N/C:C/I:P/A:N", - "AV:L/AC:L/Au:N/C:N/I:C/A:C", - "AV:L/AC:L/Au:N/C:N/I:C/A:N", - "AV:L/AC:L/Au:N/C:N/I:N/A:C", - "AV:L/AC:L/Au:N/C:N/I:N/A:P", - "AV:L/AC:L/Au:N/C:N/I:P/A:C", - "AV:L/AC:L/Au:N/C:N/I:P/A:N", - "AV:L/AC:L/Au:N/C:N/I:P/A:P", - "AV:L/AC:L/Au:N/C:P/I:N/A:C", - "AV:L/AC:L/Au:N/C:P/I:N/A:N", - "AV:L/AC:L/Au:N/C:P/I:N/A:P", - "AV:L/AC:L/Au:N/C:P/I:P/A:C", - "AV:L/AC:L/Au:N/C:P/I:P/A:N", - "AV:L/AC:L/Au:N/C:P/I:P/A:P", - "AV:L/AC:L/Au:S/C:P/I:N/A:N", - "AV:L/AC:M/Au:N/C:C/I:C/A:C", - "AV:L/AC:M/Au:N/C:C/I:N/A:N", - "AV:L/AC:M/Au:N/C:N/I:C/A:C", - "AV:L/AC:M/Au:N/C:N/I:C/A:N", - "AV:L/AC:M/Au:N/C:N/I:N/A:C", - "AV:L/AC:M/Au:N/C:N/I:N/A:P", - "AV:L/AC:M/Au:N/C:N/I:P/A:C", - "AV:L/AC:M/Au:N/C:N/I:P/A:N", - "AV:L/AC:M/Au:N/C:N/I:P/A:P", - "AV:L/AC:M/Au:N/C:P/I:N/A:N", - "AV:L/AC:M/Au:N/C:P/I:N/A:P", - "AV:L/AC:M/Au:N/C:P/I:P/A:C", - "AV:L/AC:M/Au:N/C:P/I:P/A:N", - "AV:L/AC:M/Au:N/C:P/I:P/A:P", - "AV:N/AC:H/Au:N/C:C/I:C/A:C", - "AV:N/AC:H/Au:N/C:N/I:N/A:C", - "AV:N/AC:H/Au:N/C:N/I:N/A:P", - "AV:N/AC:H/Au:N/C:N/I:P/A:N", - "AV:N/AC:H/Au:N/C:N/I:P/A:P", - "AV:N/AC:H/Au:N/C:P/I:N/A:N", - "AV:N/AC:H/Au:N/C:P/I:N/A:P", - "AV:N/AC:H/Au:N/C:P/I:P/A:N", - "AV:N/AC:H/Au:N/C:P/I:P/A:P", - "AV:N/AC:H/Au:S/C:N/I:P/A:N", - "AV:N/AC:H/Au:S/C:P/I:N/A:N", - "AV:N/AC:H/Au:S/C:P/I:P/A:N", - "AV:N/AC:H/Au:S/C:P/I:P/A:P", - "AV:N/AC:L/Au:N/C:C/I:C/A:C", - "AV:N/AC:L/Au:N/C:C/I:C/A:N", - "AV:N/AC:L/Au:N/C:C/I:N/A:C", - "AV:N/AC:L/Au:N/C:C/I:N/A:N", - "AV:N/AC:L/Au:N/C:N/I:C/A:C", - "AV:N/AC:L/Au:N/C:N/I:C/A:N", - "AV:N/AC:L/Au:N/C:N/I:N/A:C", - "AV:N/AC:L/Au:N/C:N/I:N/A:P", - "AV:N/AC:L/Au:N/C:N/I:P/A:N", - "AV:N/AC:L/Au:N/C:N/I:P/A:P", - "AV:N/AC:L/Au:N/C:P/I:N/A:C", - "AV:N/AC:L/Au:N/C:P/I:N/A:N", - "AV:N/AC:L/Au:N/C:P/I:N/A:P", - "AV:N/AC:L/Au:N/C:P/I:P/A:C", - "AV:N/AC:L/Au:N/C:P/I:P/A:N", - "AV:N/AC:L/Au:N/C:P/I:P/A:P", - "AV:N/AC:L/Au:S/C:C/I:C/A:C", - "AV:N/AC:L/Au:S/C:C/I:C/A:N", - "AV:N/AC:L/Au:S/C:C/I:C/A:P", - "AV:N/AC:L/Au:S/C:C/I:N/A:N", - "AV:N/AC:L/Au:S/C:C/I:P/A:C", - "AV:N/AC:L/Au:S/C:C/I:P/A:N", - "AV:N/AC:L/Au:S/C:N/I:C/A:C", - "AV:N/AC:L/Au:S/C:N/I:C/A:N", - "AV:N/AC:L/Au:S/C:N/I:N/A:C", - "AV:N/AC:L/Au:S/C:N/I:N/A:P", - "AV:N/AC:L/Au:S/C:N/I:P/A:C", - "AV:N/AC:L/Au:S/C:N/I:P/A:N", - "AV:N/AC:L/Au:S/C:N/I:P/A:P", - "AV:N/AC:L/Au:S/C:P/I:N/A:C", - "AV:N/AC:L/Au:S/C:P/I:N/A:N", - "AV:N/AC:L/Au:S/C:P/I:N/A:P", - "AV:N/AC:L/Au:S/C:P/I:P/A:C", - "AV:N/AC:L/Au:S/C:P/I:P/A:N", - "AV:N/AC:L/Au:S/C:P/I:P/A:P", - "AV:N/AC:M/Au:N/C:C/I:C/A:C", - "AV:N/AC:M/Au:N/C:C/I:C/A:N", - "AV:N/AC:M/Au:N/C:C/I:N/A:N", - "AV:N/AC:M/Au:N/C:C/I:P/A:P", - "AV:N/AC:M/Au:N/C:N/I:C/A:C", - "AV:N/AC:M/Au:N/C:N/I:C/A:N", - "AV:N/AC:M/Au:N/C:N/I:N/A:C", - "AV:N/AC:M/Au:N/C:N/I:N/A:P", - "AV:N/AC:M/Au:N/C:N/I:P/A:N", - "AV:N/AC:M/Au:N/C:N/I:P/A:P", - "AV:N/AC:M/Au:N/C:P/I:C/A:C", - "AV:N/AC:M/Au:N/C:P/I:N/A:N", - "AV:N/AC:M/Au:N/C:P/I:N/A:P", - "AV:N/AC:M/Au:N/C:P/I:P/A:C", - "AV:N/AC:M/Au:N/C:P/I:P/A:N", - "AV:N/AC:M/Au:N/C:P/I:P/A:P", - "AV:N/AC:M/Au:S/C:C/I:C/A:C", - "AV:N/AC:M/Au:S/C:C/I:N/A:C", - "AV:N/AC:M/Au:S/C:C/I:N/A:N", - "AV:N/AC:M/Au:S/C:N/I:C/A:C", - "AV:N/AC:M/Au:S/C:N/I:N/A:C", - "AV:N/AC:M/Au:S/C:N/I:N/A:P", - "AV:N/AC:M/Au:S/C:N/I:P/A:C", - "AV:N/AC:M/Au:S/C:N/I:P/A:N", - "AV:N/AC:M/Au:S/C:N/I:P/A:P", - "AV:N/AC:M/Au:S/C:P/I:N/A:N", - "AV:N/AC:M/Au:S/C:P/I:N/A:P", - "AV:N/AC:M/Au:S/C:P/I:P/A:C", - "AV:N/AC:M/Au:S/C:P/I:P/A:N", - "AV:N/AC:M/Au:S/C:P/I:P/A:P", - - "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:L", - "CVSS:3.0/AV:A/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.0/AV:A/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.0/AV:A/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", - - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:C/C:L/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:H/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:N/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:L", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L", - "CVSS:3.1/AV:P/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:P/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:P/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "CVSS:3.1/AV:P/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - } - - for _, test := range(passTests) { - t.Run(test, func(t *testing.T) { - vec, err := NewVector(test) - if err != nil { - t.Error(err) - return - } - - got := vec.String() - if got != test { - t.Errorf("got: \"%s\", exp: \"%s\"", got, test) - } - }) - } - - failTests := []struct { - val string - exp string - } { - { - val: "AV:A/junk/Au:S/C:C/I:C/A:C", - exp: "invalid CVSS vector: AV:A/junk/Au:S/C:C/I:C/A:C", - }, { - val: "CVSS:3.0/junk/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", - exp: "invalid CVSS vector: CVSS:3.0/junk/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", - }, { - val: "CVSS:3.1/AV:A/AC:H/junk/UI:N/S:U/C:H/I:H/A:H", - exp: "invalid CVSS vector: CVSS:3.1/AV:A/AC:H/junk/UI:N/S:U/C:H/I:H/A:H", - }, - } - - for _, test := range(failTests) { - t.Run(test.val, func(t *testing.T) { - got, err := NewVector(test.val) - if err != nil && err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } else if err == nil { - t.Errorf("got \"%s\", exp error", got) - } - }) - } -} diff --git a/internal/cvss/version.go b/internal/cvss/version.go deleted file mode 100644 index c46c893..0000000 --- a/internal/cvss/version.go +++ /dev/null @@ -1,12 +0,0 @@ -package cvss - -//go:generate stringer -linecomment -type=Version - -// CVSS version -type Version byte - -const ( - V20 Version = iota // 2.0 - V30 // 3.0 - V31 // 3.1 -) diff --git a/internal/cvss/version_string.go b/internal/cvss/version_string.go deleted file mode 100644 index 03e8904..0000000 --- a/internal/cvss/version_string.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by "stringer -linecomment -type=Version"; DO NOT EDIT. - -package cvss - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V20-0] - _ = x[V30-1] - _ = x[V31-2] -} - -const _Version_name = "2.03.03.1" - -var _Version_index = [...]uint8{0, 3, 6, 9} - -func (i Version) String() string { - if i >= Version(len(_Version_index)-1) { - return "Version(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _Version_name[_Version_index[i]:_Version_index[i+1]] -} diff --git a/internal/cvss/version_test.go b/internal/cvss/version_test.go deleted file mode 100644 index e010094..0000000 --- a/internal/cvss/version_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package cvss - -import "testing" - -func TestVersionString(t *testing.T) { - tests := []struct { - val Version - exp string - } { - { V20, "2.0" }, - { V30, "3.0" }, - { V31, "3.1" }, - { Version(255), "Version(255)" }, - } - - for _, test := range(tests) { - t.Run(test.val.String(), func(t *testing.T) { - got := test.val.String() - if got != test.exp { - t.Errorf("got: \"%s\", exp: \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/cveid.go b/internal/feed/cveid.go deleted file mode 100644 index 8796029..0000000 --- a/internal/feed/cveid.go +++ /dev/null @@ -1,112 +0,0 @@ -package feed - -import ( - "encoding/json" - "fmt" - "regexp" - "strconv" -) - -// CVE ID -type CveId uint32 - -var cveIdRe = regexp.MustCompile("\\ACVE-(\\d{4})-(\\d{1,8})\\z") - -// parse year component of CVE ID -func parseCveIdYear(s string) (uint16, error) { - // parse year, check for error - year, err := strconv.ParseUint(s, 10, 16) - if err != nil { - return 0, err - } - - // check bounds - if year < 2000 || year > 2127 { - return 0, fmt.Errorf("year out of bounds: %s", s) - } - - // return value - return uint16(year), nil -} - -// parse number component of CVE ID -func parseCveIdNum(s string) (uint32, error) { - // parse number, check for error - num, err := strconv.ParseUint(s, 10, 32) - if err != nil { - return 0, err - } - - // check bounds - if num > 0x01ffffff { - return 0, fmt.Errorf("number out of bounds: %d", num) - } - - // return value - return uint32(num), nil -} - -// Encode CVE ID as uint32. -func encodeCveId(year uint16, num uint32) uint32 { - return uint32((uint32((year - 2000) & 0x7f) << 25) | (num & 0x01ffffff)) -} - -// Create CVE ID from string. -func NewCveId(s string) (CveId, error) { - // match components, check for error - md := cveIdRe.FindStringSubmatch(s) - if len(md) != 3 { - return CveId(0), fmt.Errorf("invalid CVE ID: %s", s) - } - - // parse year, check for error - year, err := parseCveIdYear(md[1]) - if err != nil { - return CveId(0), err - } - - // parse number, check for error - num, err := parseCveIdNum(md[2]) - if err != nil { - return CveId(0), err - } - - // encode and return result - return CveId(encodeCveId(year, num)), nil -} - -// Unmarshal CVE ID from JSON. -func (me *CveId) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // parse year, check for error - r, err := NewCveId(s) - if err != nil { - return err - } - - // serialize ID - *me = r - - // return success - return nil -} - -// Get year component. -func (me CveId) Year() uint16 { - return uint16((uint32(me) >> 25) & 0x7f) + 2000 -} - -// Get number component. -func (me CveId) Number() uint32 { - return (uint32(me) & 0x01ffffff) -} - -// Return string representation of CVE ID. -func (me CveId) String() string { - return fmt.Sprintf("CVE-%04d-%04d", me.Year(), me.Number()) -} diff --git a/internal/feed/cveid_test.go b/internal/feed/cveid_test.go deleted file mode 100644 index 8df3642..0000000 --- a/internal/feed/cveid_test.go +++ /dev/null @@ -1,295 +0,0 @@ -package feed - -import ( - "encoding/json" - "fmt" - "strconv" - "testing" -) - -func TestParseCveIdYear(t *testing.T) { - if got, err := parseCveIdYear("asdf"); err == nil { - t.Errorf("got %d, exp error", got) - return - } - - goodTests := []struct { - val string - exp uint16 - } { - { "2000", 2000 }, - { "2001", 2001 }, - { "2100", 2100 }, - } - - for _, test := range(goodTests) { - t.Run(test.val, func(t *testing.T) { - got, err := parseCveIdYear(test.val) - if err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got %d, exp %d", got, test.exp) - return - } - }) - } - - badTests := []struct { - val string - exp string - } { - { "0000", "year out of bounds: 0000" }, - { "0001", "year out of bounds: 0001" }, - { "1999", "year out of bounds: 1999" }, - { "2128", "year out of bounds: 2128" }, - { "9999", "year out of bounds: 9999" }, - } - - for _, test := range(badTests) { - t.Run(test.val, func(t *testing.T) { - if got, err := parseCveIdYear(test.val); err == nil { - t.Errorf("got %d, exp error", got) - return - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } -} - -func TestParseCveIdNum(t *testing.T) { - if got, err := parseCveIdNum("asdf"); err == nil { - t.Errorf("got %d, exp error", got) - return - } - - goodTests := []struct { - val string - exp uint32 - } { - { "0", 0 }, - { "0001", 1 }, - { "2100", 2100 }, - { "999999", 999999 }, - { "33554431", 33554431 }, - } - - for _, test := range(goodTests) { - t.Run(test.val, func(t *testing.T) { - got, err := parseCveIdNum(test.val) - if err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got %d, exp %d", got, test.exp) - return - } - }) - } - - badTests := []struct { - val string - exp string - } { - { "33554432", "number out of bounds: 33554432" }, - { "99999999", "number out of bounds: 99999999" }, - } - - for _, test := range(badTests) { - t.Run(test.val, func(t *testing.T) { - if got, err := parseCveIdNum(test.val); err == nil { - t.Errorf("got %d, exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } -} - -func TestNewCveId(t *testing.T) { - badMatchTests := []string { - "", - "\nCVE-2002-1234", - "CVE-2002-1234\n", - "CVE20021234\n", - "asdf", - } - - for _, test := range(badMatchTests) { - t.Run(test, func(t *testing.T) { - exp := fmt.Sprintf("invalid CVE ID: %s", test) - if got, err := NewCveId(test); err == nil { - t.Errorf("got %s, exp error", got) - } else if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } - }) - } - - badYearTests := []struct { - val string - exp string - } { - { "CVE-0000-1234", "year out of bounds: 0000" }, - { "CVE-1999-1234", "year out of bounds: 1999" }, - { "CVE-2128-1234", "year out of bounds: 2128" }, - { "CVE-9999-1234", "year out of bounds: 9999" }, - } - - for _, test := range(badYearTests) { - t.Run(test.val, func(t *testing.T) { - if got, err := NewCveId(test.val); err == nil { - t.Errorf("got %s, exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } - - badNumTests := []struct { - val string - exp string - } { - { "CVE-2000-33554432", "number out of bounds: 33554432" }, - { "CVE-2000-99999999", "number out of bounds: 99999999" }, - } - - for _, test := range(badNumTests) { - t.Run(test.val, func(t *testing.T) { - if got, err := NewCveId(test.val); err == nil { - t.Errorf("got %s, exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } - - goodTests := []string { - "CVE-2000-0", - "CVE-2127-0", - "CVE-2000-33554431", - "CVE-2127-33554431", - } - - for _, val := range(goodTests) { - t.Run(val, func(t *testing.T) { - if _, err := NewCveId(val); err != nil { - t.Error(err) - } - }) - } -} -func TestCveIdYear(t *testing.T) { - for year := 2000; year < 2127; year++ { - t.Run(strconv.FormatInt(int64(year), 10), func(t *testing.T) { - // expected value - exp := uint16(year) - - // build cve id, check for error - id, err := NewCveId(fmt.Sprintf("CVE-%04d-0000", year)) - if err != nil { - t.Error(err) - return - } - - // check year - got := id.Year() - if got != exp { - t.Errorf("got %d, exp %d", got, exp) - } - }) - } -} - -func TestCveIdNumber(t *testing.T) { - for num := 0; num < 99999; num++ { - t.Run(strconv.FormatInt(int64(num), 10), func(t *testing.T) { - // expected value - exp := uint32(num) - - // build cve id, check for error - id, err := NewCveId(fmt.Sprintf("CVE-2000-%04d", num)) - if err != nil { - t.Error(err) - return - } - - // check number - got := id.Number() - if got != exp { - t.Errorf("got %d, exp %d", got, exp) - } - }) - } -} - -func TestCveIdUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val CveId - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestCveIdUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "invalid CVE ID: foo" - var val CveId - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestCveIdUnmarshalValid(t *testing.T) { - tests := []struct { - val string - expYear uint16 - expNum uint32 - exp string - } { - { "\"CVE-2000-0\"", 2000, 0, "CVE-2000-0000" }, - { "\"CVE-2000-1234\"", 2000, 1234, "CVE-2000-1234" }, - { "\"CVE-2000-33554431\"", 2000, 33554431, "CVE-2000-33554431" }, - { "\"CVE-2127-0\"", 2127, 0, "CVE-2127-0000" }, - { "\"CVE-2127-1234\"", 2127, 1234, "CVE-2127-1234" }, - { "\"CVE-2127-33554431\"", 2127, 33554431, "CVE-2127-33554431" }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got CveId - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - // check year - if got.Year() != test.expYear { - t.Errorf("got \"%d\", exp \"%d\"", got.Year(), test.expYear) - } - - // check year - if got.Number() != test.expNum { - t.Errorf("got \"%d\", exp \"%d\"", got.Number(), test.expNum) - } - - // check string - if got.String() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got.String(), test.exp) - } - }) - } -} diff --git a/internal/feed/dataformat.go b/internal/feed/dataformat.go deleted file mode 100644 index bb3f8f8..0000000 --- a/internal/feed/dataformat.go +++ /dev/null @@ -1,36 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=DataFormat - -import ( - "encoding/json" - "fmt" -) - -// Data format for NVD feeds and feed items. -type DataFormat byte - -const ( - MitreFormat DataFormat = iota // MITRE -) - -// Unmarshal DataFormat from JSON. -func (me *DataFormat) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "MITRE": - *me = MitreFormat - default: - // return error - return fmt.Errorf("unknown data format: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/dataformat_string.go b/internal/feed/dataformat_string.go deleted file mode 100644 index 4b755f4..0000000 --- a/internal/feed/dataformat_string.go +++ /dev/null @@ -1,23 +0,0 @@ -// Code generated by "stringer -linecomment -type=DataFormat"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[MitreFormat-0] -} - -const _DataFormat_name = "MITRE" - -var _DataFormat_index = [...]uint8{0, 5} - -func (i DataFormat) String() string { - if i >= DataFormat(len(_DataFormat_index)-1) { - return "DataFormat(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _DataFormat_name[_DataFormat_index[i]:_DataFormat_index[i+1]] -} diff --git a/internal/feed/dataformat_test.go b/internal/feed/dataformat_test.go deleted file mode 100644 index efb4986..0000000 --- a/internal/feed/dataformat_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestDataFormatUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val DataFormat - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestDataFormatUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown data format: foo" - var val DataFormat - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestDataFormatUnmarshalValid(t *testing.T) { - test := []byte(`"MITRE"`) - exp := MitreFormat - var got DataFormat - - if err := json.Unmarshal(test, &got); err != nil { - t.Error(err) - return - } - - if got != exp { - t.Errorf("got \"%s\", exp \"%s\"", got, exp) - } -} - -func TestDataFormatString(t *testing.T) { - tests := []struct { - val DataFormat - exp string - } { - { MitreFormat, "MITRE" }, - { DataFormat(255), "DataFormat(255)" }, - } - - for _, test := range(tests) { - t.Run(test.val.String(), func(t *testing.T) { - got := test.val.String() - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/datatype.go b/internal/feed/datatype.go deleted file mode 100644 index 6eaa145..0000000 --- a/internal/feed/datatype.go +++ /dev/null @@ -1,36 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=DataType - -import ( - "encoding/json" - "fmt" -) - -// Data type for NVD feeds and feed items. -type DataType byte - -const ( - CveType DataType = iota // CVE -) - -// Unmarshal DataType from JSON. -func (me *DataType) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "CVE": - *me = CveType - default: - // return error - return fmt.Errorf("unknown data type: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/datatype_string.go b/internal/feed/datatype_string.go deleted file mode 100644 index f126add..0000000 --- a/internal/feed/datatype_string.go +++ /dev/null @@ -1,23 +0,0 @@ -// Code generated by "stringer -linecomment -type=DataType"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[CveType-0] -} - -const _DataType_name = "CVE" - -var _DataType_index = [...]uint8{0, 3} - -func (i DataType) String() string { - if i >= DataType(len(_DataType_index)-1) { - return "DataType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _DataType_name[_DataType_index[i]:_DataType_index[i+1]] -} diff --git a/internal/feed/datatype_test.go b/internal/feed/datatype_test.go deleted file mode 100644 index 05f6a74..0000000 --- a/internal/feed/datatype_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestDataTypeUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val DataType - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestDataTypeUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown data type: foo" - var val DataType - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestDataTypeUnmarshalValid(t *testing.T) { - test := []byte(`"CVE"`) - exp := CveType - var got DataType - - if err := json.Unmarshal(test, &got); err != nil { - t.Error(err) - return - } - - if got != exp { - t.Errorf("got \"%s\", exp \"%s\"", got, exp) - } -} - -func TestDataTypeString(t *testing.T) { - tests := []struct { - val DataType - exp string - } { - { CveType, "CVE" }, - { DataType(255), "DataType(255)" }, - } - - for _, test := range(tests) { - t.Run(test.val.String(), func(t *testing.T) { - got := test.val.String() - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/dataversion.go b/internal/feed/dataversion.go deleted file mode 100644 index c6f1b8d..0000000 --- a/internal/feed/dataversion.go +++ /dev/null @@ -1,36 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=DataVersion - -import ( - "encoding/json" - "fmt" -) - -// Data version for NVD feeds and feed items. -type DataVersion byte - -const ( - V40 DataVersion = iota // 4.0 -) - -// Unmarshal data version from JSON. -func (me *DataVersion) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "4.0": - *me = V40 - default: - // return error - return fmt.Errorf("unknown data version: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/dataversion_string.go b/internal/feed/dataversion_string.go deleted file mode 100644 index 26a0fdb..0000000 --- a/internal/feed/dataversion_string.go +++ /dev/null @@ -1,23 +0,0 @@ -// Code generated by "stringer -linecomment -type=DataVersion"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V40-0] -} - -const _DataVersion_name = "4.0" - -var _DataVersion_index = [...]uint8{0, 3} - -func (i DataVersion) String() string { - if i >= DataVersion(len(_DataVersion_index)-1) { - return "DataVersion(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _DataVersion_name[_DataVersion_index[i]:_DataVersion_index[i+1]] -} diff --git a/internal/feed/dataversion_test.go b/internal/feed/dataversion_test.go deleted file mode 100644 index 6bf683c..0000000 --- a/internal/feed/dataversion_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestDataVersionUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val DataVersion - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestDataVersionUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown data version: foo" - var val DataVersion - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestDataVersionUnmarshalValid(t *testing.T) { - test := []byte(`"4.0"`) - exp := V40 - var got DataVersion - - if err := json.Unmarshal(test, &got); err != nil { - t.Error(err) - return - } - - if got != exp { - t.Errorf("got \"%s\", exp \"%s\"", got, exp) - } -} - -func TestDataVersionString(t *testing.T) { - tests := []struct { - val DataVersion - exp string - } { - { V40, "4.0" }, - { DataVersion(255), "DataVersion(255)" }, - } - - for _, test := range(tests) { - t.Run(test.val.String(), func(t *testing.T) { - got := test.val.String() - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/feed.go b/internal/feed/feed.go deleted file mode 100644 index bdf260c..0000000 --- a/internal/feed/feed.go +++ /dev/null @@ -1,255 +0,0 @@ -// NVD JSON feed parser. -package feed - -// import "nvd/internal/cvss" - -// TODO: parse cpe - -// CVE metadata -type CveMetadata struct { - // CVE ID - Id CveId `json:"ID"` - - // CVE assigner email address - Assigner string `json:"ASSIGNER"` -} - -// CVE description string. -type Description struct { - // Language code - Lang string `json:"lang"` - - // String value - Value string `json:"value"` -} - -// CVE problem type -type CveProblemType struct { - // problem type descriptions - Descriptions []Description `json:"description"` -} - -// Slice of CVE problem types. -type CveProblemTypes struct { - // problem types - ProblemTypes []CveProblemType `json:"problemtype_data"` -} - -// CVE reference -type CveReference struct { - // reference URL - Url string `json:"url"` - - // reference name - Name string `json:"name"` - - // reference source - RefSource string `json:"refsource"` - - // tags - Tags []string `json:"tags"` -} - -// Slice of CVE references -type CveReferences struct { - References []CveReference `json:"reference_data"` -} - -// CVE item descriptions -type CveDescription struct { - // slice of descriptions - Descriptions []Description `json:"description_data"` -} - -// CVE data -type Cve struct { - // feed data type - DataType DataType `json:"CVE_data_type"` - - // feed data format - DataFormat DataFormat `json:"CVE_data_format"` - - // feed data format version - DataVersion DataVersion `json:"CVE_data_version"` - - // CVE metadata - Metadata CveMetadata `json:"CVE_data_meta"` - - // CVE problem types - ProblemTypes CveProblemTypes `json:"problemtype"` - - // CVE references - References CveReferences `json:"references"` - - // CVE description - Description CveDescription `json:"description"` -} - -// CPE match -type CpeMatch struct { - // Vulnerable? - Vulnerable bool `json:"vulnerable"` - - VersionEndExcluding string `json:"versionEndExcluding"` - - // CPE URI (FIXME: decode this) - Cpe23Uri string `json:"cpe23Uri"` - - // CPE names (not sure if this is correct) - Names []string `json:"cpe_name"` -} - -// CVE item configuration node -type ConfigurationNode struct { - // node operator - Operator NodeOp `json:"operator"` - - // node children - Children []ConfigurationNode `json:"children"` - - CpeMatches []CpeMatch `json:"cpe_match"` -} - -// CVE item configurations -type ItemConfigurations struct { - // data version - DataVersion DataVersion `json:"CVE_data_version"` - - // slice of configuration nodes - Nodes []ConfigurationNode `json:"nodes"` -} - -// CVSS V3 -type CvssV3 struct { - // CVSS V3 version - Version V3Version `json:"version"` - - // CVSS V3 vector string - // VectorString string `json:"vectorString"` - - // CVSS vector - Vector Vector `json:"vectorString"` - - // attack vector - AttackVector V3AttackVector `json:"attackVector"` - - // attack complexity - AttackComplexity V3AttackComplexity `json:"attackComplexity"` - - // privileges required - PrivilegesRequired V3PrivilegesRequired `json:"privilegesRequired"` - - // user interaction - UserInteraction V3UserInteraction `json:"userInteraction"` - - // scope - Scope V3Scope `json:"scope"` - - // integrity impact - IntegrityImpact V3Impact `json:"integrityImpact"` - - // availability impact - AvailabilityImpact V3Impact `json:"availabilityImpact"` - - // base score - BaseScore Score `json:"baseScore"` - - // base severity - BaseSeverity Severity `json:"baseSeverity"` -} - -// CVSS V3 base metrics -type BaseMetricV3 struct { - CvssV3 CvssV3 `json:"cvssV3"` - ExploitabilityScore Score `json:"exploitabilityScore"` - ImpactScore Score `json:"impactScore"` -} - -// CVSS V2 -type CvssV2 struct { - // CVSS V2 version - Version V2Version `json:"version"` - - // CVSS vector string - // VectorString string `json:"vectorString"` - - // CVSS vector - Vector Vector `json:"vectorString"` - - // attack vector - AccessVector V2AccessVector `json:"accessVector"` - - // attack complexity - AccessComplexity V2AccessComplexity `json:"accessComplexity"` - - // authentication - Authentication V2Authentication `json:"authentication"` - - ConfidentialityImpact V2Impact `json:"confidentialityImpact"` - IntegrityImpact V2Impact `json:"integrityImpact"` - AvailabilityImpact V2Impact `json:"availabilityImpact"` - - // base score - BaseScore Score `json:"baseScore"` -} - -// CVSS V2 base metrics -type BaseMetricV2 struct { - CvssV2 CvssV2 `json:"cvssV2"` - Severity Severity `json:"severity"` - ExploitabilityScore Score `json:"exploitabilityScore"` - ImpactScore Score `json:"impactScore"` - InsufficientInfo bool `json:"acInsufInfo"` - ObtainAllPrivilege bool `json:"obtainAllPrivilege"` - ObtainUserPrivilege bool `json:"obtainUserPrivilege"` - ObtainOtherPrivilege bool `json:"obtainOtherPrivilege"` - UserInteractionRequired bool `json:"userInteractionRequired"` -} - -// Item impact -type Impact struct { - // CVSS V3 base metrics - BaseMetricV3 BaseMetricV3 `json:"baseMetricV3"` - - // CVSS V2 base metrics - BaseMetricV2 BaseMetricV2 `json:"baseMetricV2"` -} - -// CVE feed item -type Item struct { - // item CVE data - Cve Cve `json:"cve"` - - // item configuration - Configurations ItemConfigurations `json:"configurations"` - - // item impact - Impact Impact `json:"impact"` - - // item published date - PublishedDate Time `json:"publishedDate"` - - // last modification date - LastModifiedDate Time `json:"lastModifiedDate"` -} - -// NVD feed -type Feed struct { - // feed data type - DataType DataType `json:"CVE_data_type"` - - // feed data format - DataFormat DataFormat `json:"CVE_data_format"` - - // feed data format version - DataVersion DataVersion `json:"CVE_data_version"` - - // number of CVEs in feed - NumCVEs uint64 `json:"CVE_data_numberOfCVEs,string"` - - // data timestamp - Timestamp Time `json:"CVE_data_timestamp"` - - // CVE items - Items []Item `json:"CVE_Items"` -} diff --git a/internal/feed/feed_test.go b/internal/feed/feed_test.go deleted file mode 100644 index f31a3ae..0000000 --- a/internal/feed/feed_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package feed - -import ( - "compress/gzip" - "encoding/json" - "io" - // "fmt" - "os" - "testing" -) - -func openTest(path string) (io.Reader, error) { - // open file for reading - file, err := os.Open(path) - if err != nil { - return nil, err - } - - // wrap in reader, return success - return gzip.NewReader(file) -} - -// Test feed parser -func TestFeedParser(t *testing.T) { - t.Run("TestUnmarshalJSON", func(t *testing.T) { - var f Feed - - // read test data, check for error - src, err := openTest("testdata/nvdcve-1.1-2021.json.gz") - if err != nil { - t.Error(err) - } - - // decode cve feed, check for error - d := json.NewDecoder(src) - if err := d.Decode(&f); err != nil { - t.Error(err) - } - }) -// var f Feed -// -// // decode cve feed -// d := json.NewDecoder(os.Stdin) -// if err := d.Decode(&f); err != nil { -// t.Error(err) -// } -// -// var dst bytes.Buffer -// -// // create json encoder -// e := json.NewEncoder(&dst) -// if err := e.Encode(f); err != nil { -// t.Error(err) -// } -} - diff --git a/internal/feed/meta.go b/internal/feed/meta.go deleted file mode 100644 index fd46025..0000000 --- a/internal/feed/meta.go +++ /dev/null @@ -1,106 +0,0 @@ -package feed - -import ( - "bufio" - "encoding/hex" - "fmt" - "io" - "strconv" - "strings" - "time" -) - -// NVD metadata. -type Meta struct { - LastModifiedDate time.Time // last modified time - Size uint64 // uncompressed size, in bytes - ZipSize uint64 // zip file size, in bytes - GzSize uint64 // gz file size, in bytes - Sha256 [32]byte // sha256 hash of uncompressed data -} - -func parseMetaSize(name, val string) (uint64, error) { - // parse value, check for error - v, err := strconv.ParseUint(val, 10, 64) - if err == nil { - // return size - return v, nil - } else { - // return error - return 0, fmt.Errorf("invalid %s: \"%s\"", name, val) - } -} - -// Unmarshal new Metadata from reader. -func NewMeta(r io.Reader) (*Meta, error) { - // declare result - var m Meta - - // create scanner - scanner := bufio.NewScanner(r) - - // read lines - for scanner.Scan() { - // split into key/value pair, check for error - pair := strings.SplitN(scanner.Text(), ":", 2) - if len(pair) != 2 { - return nil, fmt.Errorf("bad meta line: \"%s\"", scanner.Text()) - } - - switch pair[0] { - case "lastModifiedDate": - // parse time, check for error - if err := m.LastModifiedDate.UnmarshalText([]byte(pair[1])); err != nil { - return nil, err - } - case "size": - if v, err := parseMetaSize("size", pair[1]); err == nil { - m.Size = v - } else { - return nil, err - } - case "zipSize": - if v, err := parseMetaSize("zipSize", pair[1]); err == nil { - m.ZipSize = v - } else { - return nil, err - } - case "gzSize": - if v, err := parseMetaSize("gzSize", pair[1]); err == nil { - m.GzSize = v - } else { - return nil, err - } - case "sha256": - // check hash length - if len(pair[1]) != 64 { - return nil, fmt.Errorf("invalid sha256 hash length: %d", len(pair[1])) - } - - // decode hex, check for error - buf, err := hex.DecodeString(pair[1]) - if err != nil { - return nil, fmt.Errorf("invalid sha256 hash: %v", err) - } - - // save to buffer, check for error - len := copy(m.Sha256[:], buf[0:32]) - if len != 32 { - // difficult to test, but this basically doesn't happen, see here: - // https://github.com/golang/go/blob/2ebe77a2fda1ee9ff6fd9a3e08933ad1ebaea039/src/runtime/slice.go#L247 - return nil, fmt.Errorf("invalid copy length: %d", len) - } - default: - // return error - return nil, fmt.Errorf("unknown key: \"%s\"", pair[0]) - } - } - - // check for scanner error - if err := scanner.Err(); err != nil { - return nil, err - } - - // return success - return &m, nil -} diff --git a/internal/feed/meta_test.go b/internal/feed/meta_test.go deleted file mode 100644 index 3ea5acb..0000000 --- a/internal/feed/meta_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package feed - -import ( - "bytes" - "encoding/json" - "fmt" - "testing" -) - -func TestParseMetaSize(t *testing.T) { - passTests := []struct { - val string - exp uint64 - } { - { "0", 0 }, - { "1024", 1024 }, - { "18446744073709551615", 18446744073709551615 }, - } - - for _, test := range(passTests) { - t.Run(test.val, func(t *testing.T) { - got, err := parseMetaSize("foo", test.val) - if err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got %d, exp %d", got, test.exp) - return - } - }) - } - - failTests := []struct { - val string - exp string - } { - { "-1", "invalid foo: \"-1\"" }, - { "a", "invalid foo: \"a\"" }, - { "18446744073709551616", "invalid foo: \"18446744073709551616\"" }, - } - - for _, test := range(failTests) { - t.Run(test.val, func(t *testing.T) { - got, err := parseMetaSize("foo", test.val) - if err == nil { - t.Errorf("got %d, exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } -} - -// test data -const testMeta = `lastModifiedDate:2022-01-29T03:01:16-05:00 -size:73202582 -zipSize:3753799 -gzSize:3753663 -sha256:B86258D5D9861507A1894A7B92011764803D7267787B1487539E240EA2405440 -` - -// Test meta parser -func TestNewMeta(t *testing.T) { - passTests := []string { - `lastModifiedDate:2022-01-29T03:01:16-05:00 -size:73202582 -zipSize:3753799 -gzSize:3753663 -sha256:B86258D5D9861507A1894A7B92011764803D7267787B1487539E240EA2405440 -`, - } - - for i, val := range(passTests) { - // build test name - name := fmt.Sprintf("passTests[%d]", i) - - t.Run(name, func(t *testing.T) { - // create buffer - buf := bytes.NewBufferString(val) - - // decode meta, check for error - _, err := NewMeta(buf) - if err != nil { - t.Error(err) - } - }) - } - - // build 65k token to make scanner fail - longVal := make([]byte, 65536) - for i := 0; i < cap(longVal); i++ { - longVal[i] = 'a' - } - - failTests := []struct { - val string - exp string - } { - { "asdf", "bad meta line: \"asdf\"" }, - { "lastModifiedDate:asdf", "parsing time \"asdf\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"asdf\" as \"2006\"" }, - { "size:a", "invalid size: \"a\"" }, - { "zipSize:a", "invalid zipSize: \"a\"" }, - { "gzSize:a", "invalid gzSize: \"a\"" }, - { "sha256:a", "invalid sha256 hash length: 1" }, - { - val: "sha256:0z00000000000000000000000000000000000000000000000000000000000000", - exp: "invalid sha256 hash: encoding/hex: invalid byte: U+007A 'z'", - }, - { string(longVal), "bufio.Scanner: token too long" }, - { "foo:bar", "unknown key: \"foo\"" }, - } - - for _, test := range(failTests) { - t.Run(test.val, func(t *testing.T) { - // create buffer - buf := bytes.NewBufferString(test.val) - - // decode meta, check for error - got, err := NewMeta(buf) - if err == nil { - t.Errorf("got %v, exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } - - t.Run("JsonEncode", func(t *testing.T) { - // create buffer - buf := bytes.NewBufferString(passTests[0]) - - // decode meta, check for error - meta, err := NewMeta(buf) - if err != nil { - t.Error(err) - } - - // create destination buffer - var dst bytes.Buffer - - // create json encoder - e := json.NewEncoder(&dst) - if err := e.Encode(meta); err != nil { - t.Error(err) - } - }) -} diff --git a/internal/feed/nodeop.go b/internal/feed/nodeop.go deleted file mode 100644 index 8bfa0a0..0000000 --- a/internal/feed/nodeop.go +++ /dev/null @@ -1,39 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=NodeOp - -import ( - "encoding/json" - "fmt" -) - -// Node boolean operator. -type NodeOp byte - -const ( - OrOp NodeOp = iota // OR - AndOp // AND -) - -// Unmarshal DataVersion from JSON. -func (me *NodeOp) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "AND": - *me = AndOp - case "OR": - *me = OrOp - default: - // return error - return fmt.Errorf("unknown operator: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/nodeop_string.go b/internal/feed/nodeop_string.go deleted file mode 100644 index 2c120d4..0000000 --- a/internal/feed/nodeop_string.go +++ /dev/null @@ -1,24 +0,0 @@ -// Code generated by "stringer -linecomment -type=NodeOp"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[OrOp-0] - _ = x[AndOp-1] -} - -const _NodeOp_name = "ORAND" - -var _NodeOp_index = [...]uint8{0, 2, 5} - -func (i NodeOp) String() string { - if i >= NodeOp(len(_NodeOp_index)-1) { - return "NodeOp(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _NodeOp_name[_NodeOp_index[i]:_NodeOp_index[i+1]] -} diff --git a/internal/feed/nodeop_test.go b/internal/feed/nodeop_test.go deleted file mode 100644 index dd538e5..0000000 --- a/internal/feed/nodeop_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestNodeOpUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val NodeOp - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestNodeOpUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown operator: foo" - var val NodeOp - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestNodeOpUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp NodeOp - } { - { "\"AND\"", AndOp }, - { "\"OR\"", OrOp }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got NodeOp - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestNodeOpString(t *testing.T) { - tests := []struct { - val NodeOp - exp string - } { - { AndOp, "AND" }, - { OrOp, "OR" }, - - { NodeOp(255), "NodeOp(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/score.go b/internal/feed/score.go deleted file mode 100644 index 051522f..0000000 --- a/internal/feed/score.go +++ /dev/null @@ -1,34 +0,0 @@ -package feed - -import ( - "encoding/json" - "fmt" - "math" - "strconv" -) - -// CVSS score -type Score uint8 - -// Unmarshal CVSS score from JSON. -func (me *Score) UnmarshalJSON(b []byte) error { - // decode float, check for error - var v float64 - if err := json.Unmarshal(b, &v); err != nil { - return err - } - - // check score - if v < 0.0 || v > 10.0 { - return fmt.Errorf("CVSS score out of bounds: %2.1f", v) - } - - // save result, return success - *me = Score(uint8(math.Trunc(10.0 * v))) - return nil -} - -func (me Score) String() string { - val := float64(me) / 10.0 - return strconv.FormatFloat(val, 'f', 1, 64) -} diff --git a/internal/feed/score_test.go b/internal/feed/score_test.go deleted file mode 100644 index 2baa7ab..0000000 --- a/internal/feed/score_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestScoreUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val Score - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestScoreUnmarshalInvalidValues(t *testing.T) { - tests := []struct { - val string - exp string - } { - { `-100.0`, "CVSS score out of bounds: -100.0" }, - { `-90.0`, "CVSS score out of bounds: -90.0" }, - { `-9.3`, "CVSS score out of bounds: -9.3" }, - { `-1`, "CVSS score out of bounds: -1.0" }, - { `10.1`, "CVSS score out of bounds: 10.1" }, - { `100.0`, "CVSS score out of bounds: 100.0" }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got Score - - if err := json.Unmarshal([]byte(test.val), &got); err == nil { - t.Errorf("got \"%s\", exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } -} - -func TestScoreUnmarshalValidValues(t *testing.T) { - tests := []struct { - val string - exp uint8 - } { - { `0.0`, 0 }, - { `0.1`, 1 }, - { `1.2`, 12 }, - { `5.9`, 59 }, - { `9.9`, 99 }, - { `10.0`, 100 }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got Score - - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } else if uint8(got) != test.exp { - t.Errorf("got \"%d\", exp \"%d\"", uint8(got), test.exp) - } - }) - } -} - -func TestScoreString(t *testing.T) { - tests := []struct { - val uint8 - exp string - } { - { 0, "0.0" }, - { 1, "0.1" }, - { 9, "0.9" }, - { 12, "1.2" }, - { 59, "5.9" }, - { 99, "9.9" }, - { 100, "10.0" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := Score(test.val).String() - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/severity.go b/internal/feed/severity.go deleted file mode 100644 index 50969ed..0000000 --- a/internal/feed/severity.go +++ /dev/null @@ -1,47 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=Severity - -import ( - "encoding/json" - "fmt" -) - -type Severity byte - -const ( - SeverityNone Severity = iota // NONE - SeverityLow // LOW - SeverityMedium // MEDIUM - SeverityHigh // HIGH - SeverityCritical // CRITICAL -) - -// Unmarshal CVSS severity from JSON. -func (me *Severity) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "NONE": - *me = SeverityNone - case "LOW": - *me = SeverityLow - case "MEDIUM": - *me = SeverityMedium - case "HIGH": - *me = SeverityHigh - case "CRITICAL": - *me = SeverityCritical - default: - // return error - return fmt.Errorf("unknown severity: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/severity_string.go b/internal/feed/severity_string.go deleted file mode 100644 index 28e1e91..0000000 --- a/internal/feed/severity_string.go +++ /dev/null @@ -1,27 +0,0 @@ -// Code generated by "stringer -linecomment -type=Severity"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[SeverityNone-0] - _ = x[SeverityLow-1] - _ = x[SeverityMedium-2] - _ = x[SeverityHigh-3] - _ = x[SeverityCritical-4] -} - -const _Severity_name = "NONELOWMEDIUMHIGHCRITICAL" - -var _Severity_index = [...]uint8{0, 4, 7, 13, 17, 25} - -func (i Severity) String() string { - if i >= Severity(len(_Severity_index)-1) { - return "Severity(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _Severity_name[_Severity_index[i]:_Severity_index[i+1]] -} diff --git a/internal/feed/severity_test.go b/internal/feed/severity_test.go deleted file mode 100644 index 75aec72..0000000 --- a/internal/feed/severity_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestSeverityUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val Severity - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestSeverityUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown severity: foo" - var val Severity - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestSeverityUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp Severity - } { - { "\"NONE\"", SeverityNone }, - { "\"LOW\"", SeverityLow }, - { "\"MEDIUM\"", SeverityMedium }, - { "\"HIGH\"", SeverityHigh }, - { "\"CRITICAL\"", SeverityCritical }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got Severity - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestSeverityString(t *testing.T) { - tests := []struct { - val Severity - exp string - } { - { SeverityNone, "NONE" }, - { SeverityLow, "LOW" }, - { SeverityMedium, "MEDIUM" }, - { SeverityHigh, "HIGH" }, - { SeverityCritical, "CRITICAL" }, - - { Severity(255), "Severity(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/testdata/nvdcve-1.1-2002.json.gz b/internal/feed/testdata/nvdcve-1.1-2002.json.gz deleted file mode 100644 index 45e714d..0000000 Binary files a/internal/feed/testdata/nvdcve-1.1-2002.json.gz and /dev/null differ diff --git a/internal/feed/testdata/nvdcve-1.1-2003.json.gz b/internal/feed/testdata/nvdcve-1.1-2003.json.gz deleted file mode 100644 index c7796a6..0000000 Binary files a/internal/feed/testdata/nvdcve-1.1-2003.json.gz and /dev/null differ diff --git a/internal/feed/testdata/nvdcve-1.1-2021.json.gz b/internal/feed/testdata/nvdcve-1.1-2021.json.gz deleted file mode 100644 index 83ca5e6..0000000 Binary files a/internal/feed/testdata/nvdcve-1.1-2021.json.gz and /dev/null differ diff --git a/internal/feed/testdata/nvdcve-1.1-modified.json.gz b/internal/feed/testdata/nvdcve-1.1-modified.json.gz deleted file mode 100644 index c675fb6..0000000 Binary files a/internal/feed/testdata/nvdcve-1.1-modified.json.gz and /dev/null differ diff --git a/internal/feed/time.go b/internal/feed/time.go deleted file mode 100644 index 6eb5d37..0000000 --- a/internal/feed/time.go +++ /dev/null @@ -1,44 +0,0 @@ -package feed - -import ( - "encoding/json" - "fmt" - // "strconv" - "regexp" - "time" -) - -// partial timestamp -type Time time.Time - -var timeRe = regexp.MustCompile("\\A\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}Z\\z") - -// Unmarshal timestamp from JSON. -func (me *Time) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // match partial string regex - if !timeRe.MatchString(s) { - return fmt.Errorf("invalid time: \"%s\"", s) - } - - // correct string suffix - s = s[0:16] + ":00Z" - - // unmarshal time - var t time.Time - if err := t.UnmarshalText([]byte(s)); err != nil { - return err - } - - // save time - *me = Time(t) - - // return success - return nil -} - diff --git a/internal/feed/time_test.go b/internal/feed/time_test.go deleted file mode 100644 index cc490c5..0000000 --- a/internal/feed/time_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" - "time" -) - -func TestTimeUnmarshallInvalidData(t *testing.T) { - test := []byte(`{}`) - var val Time - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%v\", exp error", val) - } -} - -func TestTimeUnmarshallInvalidString(t *testing.T) { - test := []byte(`"2020-"`) - exp := "invalid time: \"2020-\"" - var val Time - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%v\", exp error", val) - } else if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestTimeUnmarshallInvalidTime(t *testing.T) { - test := []byte(`"2020-99-99T99:99Z"`) - var val Time - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%v\", exp error", val) - } -} - -func TestTimeString(t *testing.T) { - tests := []struct { - val string - exp string - } { - { "\"2021-06-09T20:15Z\"", "2021-06-09T20:15:00Z" }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var gotTime Time - if err := json.Unmarshal([]byte(test.val), &gotTime); err != nil { - t.Error(err) - return - } - - got, err := time.Time(gotTime).MarshalText() - if err != nil { - t.Error(err) - return - } - - if string(got) != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", string(got), test.exp) - } - }) - } -} diff --git a/internal/feed/v2accesscomplexity.go b/internal/feed/v2accesscomplexity.go deleted file mode 100644 index 5885e0d..0000000 --- a/internal/feed/v2accesscomplexity.go +++ /dev/null @@ -1,42 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V2AccessComplexity - -import ( - "encoding/json" - "fmt" -) - -// CVSS v2 access complexity -type V2AccessComplexity byte - -const ( - V2ACLow V2AccessComplexity = iota // LOW - V2ACMedium // MEDIUM - V2ACHigh // HIGH -) - -// Unmarshal CVSS V2 access complexity from JSON. -func (me *V2AccessComplexity) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "LOW": - *me = V2ACLow - case "MEDIUM": - *me = V2ACMedium - case "HIGH": - *me = V2ACHigh - default: - // return error - return fmt.Errorf("unknown CVSS v2 access complexity: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/v2accesscomplexity_string.go b/internal/feed/v2accesscomplexity_string.go deleted file mode 100644 index 8638b3d..0000000 --- a/internal/feed/v2accesscomplexity_string.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by "stringer -linecomment -type=V2AccessComplexity"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V2ACLow-0] - _ = x[V2ACMedium-1] - _ = x[V2ACHigh-2] -} - -const _V2AccessComplexity_name = "LOWMEDIUMHIGH" - -var _V2AccessComplexity_index = [...]uint8{0, 3, 9, 13} - -func (i V2AccessComplexity) String() string { - if i >= V2AccessComplexity(len(_V2AccessComplexity_index)-1) { - return "V2AccessComplexity(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V2AccessComplexity_name[_V2AccessComplexity_index[i]:_V2AccessComplexity_index[i+1]] -} diff --git a/internal/feed/v2accesscomplexity_test.go b/internal/feed/v2accesscomplexity_test.go deleted file mode 100644 index 2dd173d..0000000 --- a/internal/feed/v2accesscomplexity_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV2AccessComplexityUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V2AccessComplexity - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV2AccessComplexityUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS v2 access complexity: foo" - var val V2AccessComplexity - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV2AccessComplexityUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V2AccessComplexity - } { - { "\"LOW\"", V2ACLow }, - { "\"MEDIUM\"", V2ACMedium }, - { "\"HIGH\"", V2ACHigh }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V2AccessComplexity - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV2AccessComplexityString(t *testing.T) { - tests := []struct { - val V2AccessComplexity - exp string - } { - { V2ACLow, "LOW" }, - { V2ACMedium, "MEDIUM" }, - { V2ACHigh, "HIGH" }, - - { V2AccessComplexity(255), "V2AccessComplexity(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/v2accessvector.go b/internal/feed/v2accessvector.go deleted file mode 100644 index 80490c2..0000000 --- a/internal/feed/v2accessvector.go +++ /dev/null @@ -1,42 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V2AccessVector - -import ( - "encoding/json" - "fmt" -) - -type V2AccessVector byte - -const ( - V2AVAdjacentNetwork V2AccessVector = iota // ADJACENT_NETWORK - V2AVLocal // LOCAL - V2AVNetwork // NETWORK -) - -// Unmarshal CVSS V2 access vector from JSON. -func (me *V2AccessVector) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "ADJACENT_NETWORK": - *me = V2AVAdjacentNetwork - case "LOCAL": - *me = V2AVLocal - case "NETWORK": - *me = V2AVNetwork - default: - // return error - return fmt.Errorf("unknown CVSS v2 access vector: %s", s) - } - - // return success - return nil -} - diff --git a/internal/feed/v2accessvector_string.go b/internal/feed/v2accessvector_string.go deleted file mode 100644 index bf354fc..0000000 --- a/internal/feed/v2accessvector_string.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by "stringer -linecomment -type=V2AccessVector"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V2AVAdjacentNetwork-0] - _ = x[V2AVLocal-1] - _ = x[V2AVNetwork-2] -} - -const _V2AccessVector_name = "ADJACENT_NETWORKLOCALNETWORK" - -var _V2AccessVector_index = [...]uint8{0, 16, 21, 28} - -func (i V2AccessVector) String() string { - if i >= V2AccessVector(len(_V2AccessVector_index)-1) { - return "V2AccessVector(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V2AccessVector_name[_V2AccessVector_index[i]:_V2AccessVector_index[i+1]] -} diff --git a/internal/feed/v2accessvector_test.go b/internal/feed/v2accessvector_test.go deleted file mode 100644 index 6e0df24..0000000 --- a/internal/feed/v2accessvector_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV2AccessVectorUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V2AccessVector - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV2AccessVectorUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS v2 access vector: foo" - var val V2AccessVector - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV2AccessVectorUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V2AccessVector - } { - { "\"ADJACENT_NETWORK\"", V2AVAdjacentNetwork }, - { "\"LOCAL\"", V2AVLocal }, - { "\"NETWORK\"", V2AVNetwork }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V2AccessVector - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV2AccessVectorString(t *testing.T) { - tests := []struct { - val V2AccessVector - exp string - } { - { V2AVAdjacentNetwork, "ADJACENT_NETWORK" }, - { V2AVLocal, "LOCAL" }, - { V2AVNetwork, "NETWORK" }, - - { V2AccessVector(255), "V2AccessVector(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/v2authentication.go b/internal/feed/v2authentication.go deleted file mode 100644 index 853954f..0000000 --- a/internal/feed/v2authentication.go +++ /dev/null @@ -1,39 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V2Authentication - -import ( - "encoding/json" - "fmt" -) - -// CVSS v2 authentication -type V2Authentication byte - -const ( - V2AuthNone V2Authentication = iota // NONE - V2AuthSingle // SINGLE -) - -// Unmarshal CVSS V2 authentication from JSON. -func (me *V2Authentication) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "NONE": - *me = V2AuthNone - case "SINGLE": - *me = V2AuthSingle - default: - // return error - return fmt.Errorf("unknown CVSS v2 authentication: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/v2authentication_string.go b/internal/feed/v2authentication_string.go deleted file mode 100644 index 856c808..0000000 --- a/internal/feed/v2authentication_string.go +++ /dev/null @@ -1,24 +0,0 @@ -// Code generated by "stringer -linecomment -type=V2Authentication"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V2AuthNone-0] - _ = x[V2AuthSingle-1] -} - -const _V2Authentication_name = "NONESINGLE" - -var _V2Authentication_index = [...]uint8{0, 4, 10} - -func (i V2Authentication) String() string { - if i >= V2Authentication(len(_V2Authentication_index)-1) { - return "V2Authentication(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V2Authentication_name[_V2Authentication_index[i]:_V2Authentication_index[i+1]] -} diff --git a/internal/feed/v2authentication_test.go b/internal/feed/v2authentication_test.go deleted file mode 100644 index 4f23764..0000000 --- a/internal/feed/v2authentication_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV2AuthenticationUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V2Authentication - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV2AuthenticationUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS v2 authentication: foo" - var val V2Authentication - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV2AuthenticationUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V2Authentication - } { - { "\"NONE\"", V2AuthNone }, - { "\"SINGLE\"", V2AuthSingle }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V2Authentication - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV2AuthenticationString(t *testing.T) { - tests := []struct { - val V2Authentication - exp string - } { - { V2AuthNone, "NONE" }, - { V2AuthSingle, "SINGLE" }, - - { V2Authentication(255), "V2Authentication(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/v2impact.go b/internal/feed/v2impact.go deleted file mode 100644 index 1585e18..0000000 --- a/internal/feed/v2impact.go +++ /dev/null @@ -1,42 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V2Impact - -import ( - "encoding/json" - "fmt" -) - -// CVSS v2 impact level. -type V2Impact byte - -const ( - V2ImpactNone V2Impact = iota // NONE - V2ImpactPartial // PARTIAL - V2ImpactComplete // COMPLETE -) - -// Unmarshal CVSS v2 impact level from JSON. -func (me *V2Impact) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "NONE": - *me = V2ImpactNone - case "PARTIAL": - *me = V2ImpactPartial - case "COMPLETE": - *me = V2ImpactComplete - default: - // return error - return fmt.Errorf("unknown CVSS v2 impact: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/v2impact_string.go b/internal/feed/v2impact_string.go deleted file mode 100644 index 1dcf21b..0000000 --- a/internal/feed/v2impact_string.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by "stringer -linecomment -type=V2Impact"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V2ImpactNone-0] - _ = x[V2ImpactPartial-1] - _ = x[V2ImpactComplete-2] -} - -const _V2Impact_name = "NONEPARTIALCOMPLETE" - -var _V2Impact_index = [...]uint8{0, 4, 11, 19} - -func (i V2Impact) String() string { - if i >= V2Impact(len(_V2Impact_index)-1) { - return "V2Impact(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V2Impact_name[_V2Impact_index[i]:_V2Impact_index[i+1]] -} diff --git a/internal/feed/v2impact_test.go b/internal/feed/v2impact_test.go deleted file mode 100644 index 54dc566..0000000 --- a/internal/feed/v2impact_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV2ImpactUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V2Impact - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV2ImpactUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS v2 impact: foo" - var val V2Impact - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV2ImpactUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V2Impact - } { - { "\"NONE\"", V2ImpactNone }, - { "\"PARTIAL\"", V2ImpactPartial }, - { "\"COMPLETE\"", V2ImpactComplete }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V2Impact - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV2ImpactString(t *testing.T) { - tests := []struct { - val V2Impact - exp string - } { - { V2ImpactNone, "NONE" }, - { V2ImpactPartial, "PARTIAL" }, - { V2ImpactComplete, "COMPLETE" }, - - { V2Impact(255), "V2Impact(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/v2version.go b/internal/feed/v2version.go deleted file mode 100644 index 76e6134..0000000 --- a/internal/feed/v2version.go +++ /dev/null @@ -1,36 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V2Version - -import ( - "encoding/json" - "fmt" -) - -// CVSS v2 version -type V2Version byte - -const ( - V20 V2Version = iota // 2.0 -) - -// Unmarshal CVSS V2 version from JSON. -func (me *V2Version) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "2.0": - *me = V20 - default: - // return error - return fmt.Errorf("unknown CVSS version: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/v2version_string.go b/internal/feed/v2version_string.go deleted file mode 100644 index 6b13870..0000000 --- a/internal/feed/v2version_string.go +++ /dev/null @@ -1,23 +0,0 @@ -// Code generated by "stringer -linecomment -type=V2Version"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V20-0] -} - -const _V2Version_name = "2.0" - -var _V2Version_index = [...]uint8{0, 3} - -func (i V2Version) String() string { - if i >= V2Version(len(_V2Version_index)-1) { - return "V2Version(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V2Version_name[_V2Version_index[i]:_V2Version_index[i+1]] -} diff --git a/internal/feed/v2version_test.go b/internal/feed/v2version_test.go deleted file mode 100644 index 3b9b029..0000000 --- a/internal/feed/v2version_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV2VersionUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V2Version - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV2VersionUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS version: foo" - var val V2Version - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV2VersionUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V2Version - } { - { "\"2.0\"", V20 }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V2Version - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV2VersionString(t *testing.T) { - tests := []struct { - val V2Version - exp string - } { - { V20, "2.0" }, - - { V2Version(255), "V2Version(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/v3attackcomplexity.go b/internal/feed/v3attackcomplexity.go deleted file mode 100644 index 6e7481c..0000000 --- a/internal/feed/v3attackcomplexity.go +++ /dev/null @@ -1,42 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V3AttackComplexity - -import ( - "encoding/json" - "fmt" -) - -// CVSS v3 attack complexity -type V3AttackComplexity byte - -const ( - V3ACLow V3AttackComplexity = iota // LOW - V3ACMedium // MEDIUM - V3ACHigh // HIGH -) - -// Unmarshal CVSS v3 attack complexity from JSON. -func (me *V3AttackComplexity) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "LOW": - *me = V3ACLow - case "MEDIUM": - *me = V3ACMedium - case "HIGH": - *me = V3ACHigh - default: - // return error - return fmt.Errorf("unknown CVSS v3 attack complexity: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/v3attackcomplexity_string.go b/internal/feed/v3attackcomplexity_string.go deleted file mode 100644 index 12110c8..0000000 --- a/internal/feed/v3attackcomplexity_string.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by "stringer -linecomment -type=V3AttackComplexity"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V3ACLow-0] - _ = x[V3ACMedium-1] - _ = x[V3ACHigh-2] -} - -const _V3AttackComplexity_name = "LOWMEDIUMHIGH" - -var _V3AttackComplexity_index = [...]uint8{0, 3, 9, 13} - -func (i V3AttackComplexity) String() string { - if i >= V3AttackComplexity(len(_V3AttackComplexity_index)-1) { - return "V3AttackComplexity(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V3AttackComplexity_name[_V3AttackComplexity_index[i]:_V3AttackComplexity_index[i+1]] -} diff --git a/internal/feed/v3attackcomplexity_test.go b/internal/feed/v3attackcomplexity_test.go deleted file mode 100644 index a76efe3..0000000 --- a/internal/feed/v3attackcomplexity_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV3AttackComplexityUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V3AttackComplexity - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV3AttackComplexityUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS v3 attack complexity: foo" - var val V3AttackComplexity - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV3AttackComplexityUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V3AttackComplexity - } { - { "\"LOW\"", V3ACLow }, - { "\"MEDIUM\"", V3ACMedium }, - { "\"HIGH\"", V3ACHigh }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V3AttackComplexity - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV3AttackComplexityString(t *testing.T) { - tests := []struct { - val V3AttackComplexity - exp string - } { - { V3ACLow, "LOW" }, - { V3ACMedium, "MEDIUM" }, - { V3ACHigh, "HIGH" }, - - { V3AttackComplexity(255), "V3AttackComplexity(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/v3attackvector.go b/internal/feed/v3attackvector.go deleted file mode 100644 index ecc309a..0000000 --- a/internal/feed/v3attackvector.go +++ /dev/null @@ -1,46 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V3AttackVector - -import ( - "encoding/json" - "fmt" -) - -// CVSS v3 attack vector. -type V3AttackVector byte - -const ( - V3AVAdjacentNetwork V3AttackVector = iota // ADJACENT_NETWORK - V3AVNetwork // NETWORK - V3AVLocal // LOCAL - V3AVPhysical // PHYSICAL -) - -// Unmarshal CVSS v3 attack vector from JSON. -func (me *V3AttackVector) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "ADJACENT_NETWORK": - *me = V3AVAdjacentNetwork - case "LOCAL": - *me = V3AVLocal - case "NETWORK": - *me = V3AVNetwork - case "PHYSICAL": - *me = V3AVPhysical - default: - // return error - return fmt.Errorf("unknown CVSS v3 attack vector: %s", s) - } - - // return success - return nil -} - diff --git a/internal/feed/v3attackvector_string.go b/internal/feed/v3attackvector_string.go deleted file mode 100644 index 277520f..0000000 --- a/internal/feed/v3attackvector_string.go +++ /dev/null @@ -1,26 +0,0 @@ -// Code generated by "stringer -linecomment -type=V3AttackVector"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V3AVAdjacentNetwork-0] - _ = x[V3AVNetwork-1] - _ = x[V3AVLocal-2] - _ = x[V3AVPhysical-3] -} - -const _V3AttackVector_name = "ADJACENT_NETWORKNETWORKLOCALPHYSICAL" - -var _V3AttackVector_index = [...]uint8{0, 16, 23, 28, 36} - -func (i V3AttackVector) String() string { - if i >= V3AttackVector(len(_V3AttackVector_index)-1) { - return "V3AttackVector(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V3AttackVector_name[_V3AttackVector_index[i]:_V3AttackVector_index[i+1]] -} diff --git a/internal/feed/v3attackvector_test.go b/internal/feed/v3attackvector_test.go deleted file mode 100644 index 251cfd4..0000000 --- a/internal/feed/v3attackvector_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV3AttackVectorUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V3AttackVector - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV3AttackVectorUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS v3 attack vector: foo" - var val V3AttackVector - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV3AttackVectorUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V3AttackVector - } { - { "\"ADJACENT_NETWORK\"", V3AVAdjacentNetwork }, - { "\"LOCAL\"", V3AVLocal }, - { "\"NETWORK\"", V3AVNetwork }, - { "\"PHYSICAL\"", V3AVPhysical }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V3AttackVector - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV3AttackVectorString(t *testing.T) { - tests := []struct { - val V3AttackVector - exp string - } { - { V3AVAdjacentNetwork, "ADJACENT_NETWORK" }, - { V3AVLocal, "LOCAL" }, - { V3AVNetwork, "NETWORK" }, - { V3AVPhysical, "PHYSICAL" }, - - { V3AttackVector(255), "V3AttackVector(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/v3impact.go b/internal/feed/v3impact.go deleted file mode 100644 index d6c450e..0000000 --- a/internal/feed/v3impact.go +++ /dev/null @@ -1,42 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V3Impact - -import ( - "encoding/json" - "fmt" -) - -// CVSS v3 impact level. -type V3Impact byte - -const ( - V3ImpactNone V3Impact = iota // NONE - V3ImpactLow // LOW - V3ImpactHigh // HIGH -) - -// Unmarshal CVSS v3 impact level from JSON. -func (me *V3Impact) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "NONE": - *me = V3ImpactNone - case "LOW": - *me = V3ImpactLow - case "HIGH": - *me = V3ImpactHigh - default: - // return error - return fmt.Errorf("unknown CVSS v3 impact: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/v3impact_string.go b/internal/feed/v3impact_string.go deleted file mode 100644 index 13c7ee3..0000000 --- a/internal/feed/v3impact_string.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by "stringer -linecomment -type=V3Impact"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V3ImpactNone-0] - _ = x[V3ImpactLow-1] - _ = x[V3ImpactHigh-2] -} - -const _V3Impact_name = "NONELOWHIGH" - -var _V3Impact_index = [...]uint8{0, 4, 7, 11} - -func (i V3Impact) String() string { - if i >= V3Impact(len(_V3Impact_index)-1) { - return "V3Impact(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V3Impact_name[_V3Impact_index[i]:_V3Impact_index[i+1]] -} diff --git a/internal/feed/v3impact_test.go b/internal/feed/v3impact_test.go deleted file mode 100644 index a369f44..0000000 --- a/internal/feed/v3impact_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV3ImpactUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V3Impact - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV3ImpactUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS v3 impact: foo" - var val V3Impact - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV3ImpactUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V3Impact - } { - { "\"NONE\"", V3ImpactNone }, - { "\"LOW\"", V3ImpactLow }, - { "\"HIGH\"", V3ImpactHigh }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V3Impact - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV3ImpactString(t *testing.T) { - tests := []struct { - val V3Impact - exp string - } { - { V3ImpactNone, "NONE" }, - { V3ImpactLow, "LOW" }, - { V3ImpactHigh, "HIGH" }, - - { V3Impact(255), "V3Impact(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/v3privilegesrequired.go b/internal/feed/v3privilegesrequired.go deleted file mode 100644 index 3e69334..0000000 --- a/internal/feed/v3privilegesrequired.go +++ /dev/null @@ -1,45 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V3PrivilegesRequired - -import ( - "encoding/json" - "fmt" -) - -// CVSS v3 privileges required. -type V3PrivilegesRequired byte - -const ( - V3PRNone V3PrivilegesRequired = iota // NONE - V3PRLow // LOW - V3PRMedium // MEDIUM - V3PRHigh // HIGH -) - -// Unmarshal CVSS privileges required from JSON. -func (me *V3PrivilegesRequired) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "NONE": - *me = V3PRNone - case "LOW": - *me = V3PRLow - case "MEDIUM": - *me = V3PRMedium - case "HIGH": - *me = V3PRHigh - default: - // return error - return fmt.Errorf("unknown CVSS v3 privileges required: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/v3privilegesrequired_string.go b/internal/feed/v3privilegesrequired_string.go deleted file mode 100644 index 2951a64..0000000 --- a/internal/feed/v3privilegesrequired_string.go +++ /dev/null @@ -1,26 +0,0 @@ -// Code generated by "stringer -linecomment -type=V3PrivilegesRequired"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V3PRNone-0] - _ = x[V3PRLow-1] - _ = x[V3PRMedium-2] - _ = x[V3PRHigh-3] -} - -const _V3PrivilegesRequired_name = "NONELOWMEDIUMHIGH" - -var _V3PrivilegesRequired_index = [...]uint8{0, 4, 7, 13, 17} - -func (i V3PrivilegesRequired) String() string { - if i >= V3PrivilegesRequired(len(_V3PrivilegesRequired_index)-1) { - return "V3PrivilegesRequired(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V3PrivilegesRequired_name[_V3PrivilegesRequired_index[i]:_V3PrivilegesRequired_index[i+1]] -} diff --git a/internal/feed/v3privilegesrequired_test.go b/internal/feed/v3privilegesrequired_test.go deleted file mode 100644 index f200ed1..0000000 --- a/internal/feed/v3privilegesrequired_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV3PrivilegesRequiredUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V3PrivilegesRequired - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV3PrivilegesRequiredUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS v3 privileges required: foo" - var val V3PrivilegesRequired - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV3PrivilegesRequiredUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V3PrivilegesRequired - } { - { "\"NONE\"", V3PRNone }, - { "\"LOW\"", V3PRLow }, - { "\"MEDIUM\"", V3PRMedium }, - { "\"HIGH\"", V3PRHigh }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V3PrivilegesRequired - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV3PrivilegesRequiredString(t *testing.T) { - tests := []struct { - val V3PrivilegesRequired - exp string - } { - { V3PRNone, "NONE" }, - { V3PRLow, "LOW" }, - { V3PRMedium, "MEDIUM" }, - { V3PRHigh, "HIGH" }, - - { V3PrivilegesRequired(255), "V3PrivilegesRequired(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/v3scope.go b/internal/feed/v3scope.go deleted file mode 100644 index 20fe0a5..0000000 --- a/internal/feed/v3scope.go +++ /dev/null @@ -1,39 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V3Scope - -import ( - "encoding/json" - "fmt" -) - -// CVSS v3 scope. -type V3Scope byte - -const ( - V3ScopeChanged V3Scope = iota // CHANGED - V3ScopeUnchanged // UNCHANGED -) - -// Unmarshal CVSS scope from JSON. -func (me *V3Scope) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "CHANGED": - *me = V3ScopeChanged - case "UNCHANGED": - *me = V3ScopeUnchanged - default: - // return error - return fmt.Errorf("unknown CVSS v3 scope: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/v3scope_string.go b/internal/feed/v3scope_string.go deleted file mode 100644 index 982cead..0000000 --- a/internal/feed/v3scope_string.go +++ /dev/null @@ -1,24 +0,0 @@ -// Code generated by "stringer -linecomment -type=V3Scope"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V3ScopeChanged-0] - _ = x[V3ScopeUnchanged-1] -} - -const _V3Scope_name = "CHANGEDUNCHANGED" - -var _V3Scope_index = [...]uint8{0, 7, 16} - -func (i V3Scope) String() string { - if i >= V3Scope(len(_V3Scope_index)-1) { - return "V3Scope(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V3Scope_name[_V3Scope_index[i]:_V3Scope_index[i+1]] -} diff --git a/internal/feed/v3scope_test.go b/internal/feed/v3scope_test.go deleted file mode 100644 index 54170b0..0000000 --- a/internal/feed/v3scope_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV3ScopeUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V3Scope - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV3ScopeUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS v3 scope: foo" - var val V3Scope - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV3ScopeUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V3Scope - } { - { "\"CHANGED\"", V3ScopeChanged }, - { "\"UNCHANGED\"", V3ScopeUnchanged }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V3Scope - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV3ScopeString(t *testing.T) { - tests := []struct { - val V3Scope - exp string - } { - { V3ScopeChanged, "CHANGED" }, - { V3ScopeUnchanged, "UNCHANGED" }, - - { V3Scope(255), "V3Scope(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/v3userinteraction.go b/internal/feed/v3userinteraction.go deleted file mode 100644 index a6a53ca..0000000 --- a/internal/feed/v3userinteraction.go +++ /dev/null @@ -1,40 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V3UserInteraction - -import ( - "encoding/json" - "fmt" -) - -// CVSS v3 user interaction -type V3UserInteraction byte - -const ( - V3UINone V3UserInteraction = iota // NONE - V3UIRequired // REQUIRED -) - -// Unmarshal CVSS user interaction from JSON. -func (me *V3UserInteraction) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "NONE": - *me = V3UINone - case "REQUIRED": - *me = V3UIRequired - default: - // return error - return fmt.Errorf("unknown CVSS v3 user interaction: %s", s) - } - - // return success - return nil -} - diff --git a/internal/feed/v3userinteraction_string.go b/internal/feed/v3userinteraction_string.go deleted file mode 100644 index be78920..0000000 --- a/internal/feed/v3userinteraction_string.go +++ /dev/null @@ -1,24 +0,0 @@ -// Code generated by "stringer -linecomment -type=V3UserInteraction"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V3UINone-0] - _ = x[V3UIRequired-1] -} - -const _V3UserInteraction_name = "NONEREQUIRED" - -var _V3UserInteraction_index = [...]uint8{0, 4, 12} - -func (i V3UserInteraction) String() string { - if i >= V3UserInteraction(len(_V3UserInteraction_index)-1) { - return "V3UserInteraction(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V3UserInteraction_name[_V3UserInteraction_index[i]:_V3UserInteraction_index[i+1]] -} diff --git a/internal/feed/v3userinteraction_test.go b/internal/feed/v3userinteraction_test.go deleted file mode 100644 index c5949c2..0000000 --- a/internal/feed/v3userinteraction_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV3UserInteractionUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V3UserInteraction - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV3UserInteractionUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS v3 user interaction: foo" - var val V3UserInteraction - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV3UserInteractionUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V3UserInteraction - } { - { "\"NONE\"", V3UINone }, - { "\"REQUIRED\"", V3UIRequired }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V3UserInteraction - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV3UserInteractionString(t *testing.T) { - tests := []struct { - val V3UserInteraction - exp string - } { - { V3UINone, "NONE" }, - { V3UIRequired, "REQUIRED" }, - - { V3UserInteraction(255), "V3UserInteraction(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/v3version.go b/internal/feed/v3version.go deleted file mode 100644 index 537fecc..0000000 --- a/internal/feed/v3version.go +++ /dev/null @@ -1,36 +0,0 @@ -package feed - -//go:generate stringer -linecomment -type=V3Version - -import ( - "encoding/json" - "fmt" -) - -// CVSS v3 version -type V3Version byte - -const ( - V31 V3Version = iota // 3.1 -) - -// Unmarshal CVSS V3 version from JSON. -func (me *V3Version) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // check value - switch s { - case "3.1": - *me = V31 - default: - // return error - return fmt.Errorf("unknown CVSS version: %s", s) - } - - // return success - return nil -} diff --git a/internal/feed/v3version_string.go b/internal/feed/v3version_string.go deleted file mode 100644 index 9de58a7..0000000 --- a/internal/feed/v3version_string.go +++ /dev/null @@ -1,23 +0,0 @@ -// Code generated by "stringer -linecomment -type=V3Version"; DO NOT EDIT. - -package feed - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[V31-0] -} - -const _V3Version_name = "3.1" - -var _V3Version_index = [...]uint8{0, 3} - -func (i V3Version) String() string { - if i >= V3Version(len(_V3Version_index)-1) { - return "V3Version(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _V3Version_name[_V3Version_index[i]:_V3Version_index[i+1]] -} diff --git a/internal/feed/v3version_test.go b/internal/feed/v3version_test.go deleted file mode 100644 index 89cc6ed..0000000 --- a/internal/feed/v3version_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package feed - -import ( - "encoding/json" - "testing" -) - -func TestV3VersionUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val V3Version - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestV3VersionUnmarshalUnknown(t *testing.T) { - test := []byte(`"foo"`) - exp := "unknown CVSS version: foo" - var val V3Version - - err := json.Unmarshal(test, &val) - if err == nil { - t.Errorf("got \"%s\", exp error", val) - return - } - - if err.Error() != exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), exp) - } -} - -func TestV3VersionUnmarshalValid(t *testing.T) { - tests := []struct { - val string - exp V3Version - } { - { "\"3.1\"", V31 }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - var got V3Version - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - return - } - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} - -func TestV3VersionString(t *testing.T) { - tests := []struct { - val V3Version - exp string - } { - { V31, "3.1" }, - - { V3Version(255), "V3Version(255)" }, - } - - for _, test := range(tests) { - t.Run(test.exp, func(t *testing.T) { - got := test.val.String() - - if got != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/internal/feed/vector.go b/internal/feed/vector.go deleted file mode 100644 index 9e75de7..0000000 --- a/internal/feed/vector.go +++ /dev/null @@ -1,39 +0,0 @@ -// NVD JSON feed parser. -package feed - -import ( - "encoding/json" - "nvd/internal/cvss" -) - -// CVSS vector -type Vector struct { - // CVSS vector - Vector cvss.Vector -} - -// Unmarshal CVSS vector from JSON. -func (me *Vector) UnmarshalJSON(b []byte) error { - // decode string, check for error - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // parse vector - vec, err := cvss.NewVector(s) - if err != nil { - return err - } - - // save result - me.Vector = vec - - // return success - return nil -} - -// Marshal CVSS vector to JSON. -func (me Vector) MarshalJSON() ([]byte, error) { - return json.Marshal(me.Vector.String()) -} diff --git a/internal/feed/vector_test.go b/internal/feed/vector_test.go deleted file mode 100644 index 5dee8ba..0000000 --- a/internal/feed/vector_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package feed - -import ( - "encoding/json" - "nvd/internal/cvss" - "testing" -) - -func TestVectorUnmarshalInvalidData(t *testing.T) { - test := []byte(`{}`) - var val Vector - - if err := json.Unmarshal(test, &val); err == nil { - t.Errorf("got \"%s\", exp error", val) - } -} - -func TestVectorUnmarshalJSON(t *testing.T) { - failTests := []struct { - val string - exp string - } { - { - val: "\"AV:N/junk/Au:S/C:P/I:P/A:P\"", - exp: "invalid CVSS vector: AV:N/junk/Au:S/C:P/I:P/A:P", - }, { - val: "\"CVSS:3.0/junk/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H\"", - exp: "invalid CVSS vector: CVSS:3.0/junk/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", - }, { - val: "\"CVSS:3.1/AV:A/junk/PR:N/UI:N/S:U/C:H/I:H/A:H\"", - exp: "invalid CVSS vector: CVSS:3.1/AV:A/junk/PR:N/UI:N/S:U/C:H/I:H/A:H", - }, - } - - for _, test := range(failTests) { - t.Run(test.val, func(t *testing.T) { - var got Vector - - if err := json.Unmarshal([]byte(test.val), &got); err == nil { - t.Errorf("got \"%v\", exp error", got) - } else if err.Error() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp) - } - }) - } - - passTests := []string { - "AV:N/AC:M/Au:S/C:P/I:P/A:P", - "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", - } - - for _, val := range(passTests) { - t.Run(val, func(t *testing.T) { - var got Vector - - if err := json.Unmarshal([]byte("\"" + val + "\""), &got); err != nil { - t.Error(err) - } else if got.Vector.String() != val { - t.Errorf("got \"%s\", exp \"%s\"", got.Vector.String(), val) - } - }) - } -} - -func TestVectorMarshalJSON(t *testing.T) { - tests := []string { - "AV:N/AC:M/Au:S/C:P/I:P/A:P", - "CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H", - "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", - } - - for _, val := range(tests) { - t.Run(val, func(t *testing.T) { - // get expected string - exp := "\"" + val + "\"" - - // create inner vector - vec, err := cvss.NewVector(val) - if err != nil { - t.Error(err) - return - } - - // serialize as json - buf, err := json.Marshal(Vector { vec }) - if err != nil { - t.Error(err) - return - } - - // check result - got := string(buf) - if got != exp { - t.Errorf("got \"%s\", exp \"%s\"", got, exp) - } - }) - } -} -- cgit v1.2.3