cpe23 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: aab30b1d7b5b8b3a9be29bc890e242c9ab72d2d47f13ebbe8268d680e9995dd5
4
+ data.tar.gz: 9a3bddfa0059410d25ce32c8aa8dfce9fefd0b0a23bf6ce224cb0957c9e1c12d
5
+ SHA512:
6
+ metadata.gz: c0183e27dec8bde99cbf321e832ac5d21ea05ac2df87d0b128e56d0c67427776669a11144e2b4665b3ce81c3f8365954df55c239779a4099cd1a0e34365906fa
7
+ data.tar.gz: aea8a89821b56ffa184bc200b3d2aa62b5326aaf5780b4e8b6d62d3d75b0f53aac63f616f838a8fc9043e7c2d8c340650b8183384c08e9d5d8c91728d05a0a87
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.0
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cpe23.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "minitest", "~> 5.0"
8
+
9
+ gem "pry", "~> 0.12.2", :groups => [:development, :test]
10
+
11
+ gem "pry-byebug", "~> 3.8", :groups => [:development, :test]
12
+
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Jeremy Symon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,52 @@
1
+ # Cpe23
2
+
3
+ Parse and serialise CPEs in CPE23, URI, and WFN formats.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'cpe23'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install cpe23
20
+
21
+ ## Usage
22
+
23
+ Parse a CPE in CPE23, URI, or WFN format:
24
+ ``` ruby
25
+ Cpe23.parse(string)
26
+ ```
27
+
28
+ Serialise a CPE:
29
+ ``` ruby
30
+ cpe.to_str
31
+ cpe.to_uri
32
+ cpe.to_wfn
33
+ ```
34
+
35
+ Compare two CPEs:
36
+ - all non-wildcard components must match
37
+ - version compares the least specific of the two CPEs
38
+ - CPEs that differ only in version are ordered by their version
39
+
40
+ ## Development
41
+
42
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake test` to run the tests.
43
+
44
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
45
+
46
+ ## Contributing
47
+
48
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jtsymon/cpe23.
49
+
50
+ ## License
51
+
52
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,26 @@
1
+ require_relative 'lib/cpe23/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "cpe23"
5
+ spec.version = Cpe23::VERSION
6
+ spec.authors = ["Jeremy Symon"]
7
+ spec.email = ["jtsymon@gmail.com"]
8
+
9
+ spec.summary = %q{CPE parser/generator/matcher}
10
+ spec.description = %q{Library for parsing, generating, and matching CPEs}
11
+ spec.homepage = "https://github.com/jtsymon/cpe23"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+ spec.add_runtime_dependency('citrus', '~> 3.0')
26
+ end
@@ -0,0 +1,293 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cpe23/version'
4
+ require 'cpe23/version_wildcard'
5
+ require 'citrus'
6
+
7
+ Citrus.load(File.expand_path('cpe23/wfn', File.dirname(__FILE__)))
8
+ Citrus.load(File.expand_path('cpe23/cpe23', File.dirname(__FILE__)))
9
+
10
+ # Implementation of CPE 2.3: https://cpe.mitre.org/specification
11
+ class Cpe23
12
+ include Comparable
13
+ # The part attribute SHALL have one of these three string values:
14
+ # The value "a", when the WFN is for a class of applications.
15
+ # The value "o", when the WFN is for a class of operating systems.
16
+ # The value "h", when the WFN is for a class of hardware devices.
17
+ attr_accessor :part
18
+
19
+ # Values for this attribute SHOULD describe or identify the person or
20
+ # organization that manufactured or created the product. Values for this
21
+ # attribute SHOULD be selected from an attribute-specific valid-values list,
22
+ # which MAY be defined by other specifications that utilize this
23
+ # specification. Any character string meeting the requirements for WFNs (cf.
24
+ # 5.3.2) MAY be specified as the value of the attribute.
25
+ attr_accessor :vendor
26
+
27
+ # Values for this attribute SHOULD describe or identify the most common and
28
+ # recognizable title or name of the product. Values for this attribute SHOULD
29
+ # be selected from an attribute-specific valid-values list, which MAYbe
30
+ # defined by other specifications that utilize this specification. Any
31
+ # character string meeting the requirements for WFNs(cf. 5.3.2) MAY be
32
+ # specified as the value of the attribute.
33
+ attr_accessor :product
34
+
35
+ # Values for this attribute SHOULD be vendor-specific alphanumeric strings
36
+ # characterizing the particular release version of the product. Version
37
+ # information SHOULD be copied directly (with escaping of printable
38
+ # non-alphanumeric characters as required) from discoverable data and SHOULD
39
+ # NOT be truncated or otherwise modified. Any character string meeting the
40
+ # requirements for WFNs (cf. 5.3.2) MAY be specified as the value of the
41
+ # attribute.
42
+ def version_raw
43
+ @version
44
+ end
45
+
46
+ def version
47
+ Cpe23::Version.new(@version)
48
+ end
49
+
50
+ attr_writer :version
51
+
52
+ # Values for this attribute SHOULD be vendor-specific alphanumeric strings
53
+ # characterizing the particular update, service pack, or point release of the
54
+ # product.Values for this attribute SHOULD be selected from an
55
+ # attribute-specific valid-values list, which MAYbe defined by other
56
+ # specifications that utilize this specification. Any character string meeting
57
+ # the requirements for WFNs (cf. 5.3.2) MAYbe specified as the value of the
58
+ # attribute.
59
+ attr_accessor :update
60
+
61
+ # The edition attribute isconsidered deprecatedin this specification, and it
62
+ # SHOULD be assigned the logical value ANY except where required for backward
63
+ # compatibility with version 2.2 of the CPE specification.This attribute is
64
+ # referred to as the "legacyedition" attribute. If this attribute is used,
65
+ # values for this attribute SHOULD capture edition-related terms applied by
66
+ # the vendor to the product. Values for this attribute SHOULD be selected from
67
+ # an attribute-specific valid-values list, which MAYbe defined by other
68
+ # specifications that utilize this specification. Any character string meeting
69
+ # the requirements for WFNs (cf. 5.3.2) MAY be specified as the value of the
70
+ # attribute.
71
+ attr_accessor :edition
72
+
73
+ # Values for thisattribute SHALL be valid language tagsas defined by
74
+ # [RFC5646], and SHOULD be used to define the language supported in the user
75
+ # interface of the product being described.Although any valid language tag MAY
76
+ # be used, only tags containing language and region codes SHOULD be used.
77
+ attr_accessor :language
78
+
79
+ # Values for this attribute SHOULD characterize how the product is tailored to
80
+ # a particular market or class of end users. Values for this attribute SHOULD
81
+ # be selected from an attribute-specific valid-values list, which MAYbe
82
+ # defined by other specifications that utilize this specification. Any
83
+ # character string meeting the requirements for WFNs(cf. 5.3.2) MAYbe
84
+ # specified as the value of the attribute.
85
+ attr_accessor :sw_edition
86
+
87
+ # Values for this attribute SHOULDcharacterize the software computing
88
+ # environment within which the product operates.Values for this attribute
89
+ # SHOULD be selected from an attribute-specific valid-values list, which MAYbe
90
+ # defined by other specifications that utilize this specification. Any
91
+ # character string meeting the requirements for WFNs(cf. 5.3.2) MAYbe
92
+ # specified as the value of the attribute.
93
+ attr_accessor :target_sw
94
+
95
+ # Valuesfor this attribute SHOULD characterize the instruction set
96
+ # architecture (e.g., x86) on which the product being described or identified
97
+ # by the WFN operates. Bytecode-intermediate languages, such as Java bytecode
98
+ # for the Java Virtual Machine or Microsoft Common Intermediate Language for
99
+ # the Common Language Runtime virtual machine, SHALL be considered instruction
100
+ # set architectures. Values for this attribute SHOULD be selected from an
101
+ # attribute-specific valid-values list, which MAY be defined by other
102
+ # specifications that utilize this specification. Any character string meeting
103
+ # the requirements for WFNs(cf. 5.3.2) MAY be specified as the value of the
104
+ # attribute.
105
+ attr_accessor :target_hw
106
+
107
+ # Values for this attribute SHOULD capture any other general descriptive or
108
+ # identifying information which is vendor-or product-specific and which does
109
+ # not logically fit in any other attribute value. Values SHOULD NOT be used
110
+ # for storing instance-specific data (e.g., globally-unique identifiers or
111
+ # Internet Protocol addresses).Values for this attribute SHOULD be selected
112
+ # from a valid-values list that is refined over time; this list MAYbe defined
113
+ # by other specifications that utilize this specification. Any character
114
+ # string meeting the requirements for WFNs (cf. 5.3.2) MAYbe specified as the
115
+ # value of the attribute.
116
+ attr_accessor :other
117
+
118
+ def initialize(part: nil, vendor: nil, product: nil, version: nil,
119
+ update: nil, edition: nil, language: nil, sw_edition: nil,
120
+ target_sw: nil, target_hw: nil, other: nil)
121
+ @part = part
122
+ @vendor = vendor
123
+ @product = product
124
+ @version = version
125
+ @update = update
126
+ @edition = edition
127
+ @language = language
128
+ @sw_edition = sw_edition
129
+ @target_sw = target_sw
130
+ @target_hw = target_hw
131
+ @other = other
132
+ end
133
+
134
+ def <=>(other)
135
+ unless other.is_a? Cpe23
136
+ begin
137
+ other = Cpe23.parse(other)
138
+ rescue StandardError
139
+ return nil
140
+ end
141
+ end
142
+ return nil unless
143
+ Cpe23.attr_match?(part, other.part) &&
144
+ Cpe23.attr_match?(vendor, other.vendor) &&
145
+ Cpe23.attr_match?(product, other.product) &&
146
+ Cpe23.attr_match?(update, other.update) &&
147
+ Cpe23.attr_match?(edition, other.edition) &&
148
+ Cpe23.attr_match?(language, other.language) &&
149
+ Cpe23.attr_match?(target_sw, other.target_sw) &&
150
+ Cpe23.attr_match?(target_hw, other.target_hw) &&
151
+ Cpe23.attr_match?(self.other, other.other)
152
+
153
+ version <=> other.version
154
+ end
155
+
156
+ def to_wfn
157
+ attrs = %i[part vendor product version update edition language sw_edition
158
+ target_sw target_hw other].map do |key|
159
+ value = instance_variable_get("@#{key}")
160
+ str = case value
161
+ when nil then 'NA'
162
+ when '*' then 'ANY'
163
+ else "\"#{value.downcase}\""
164
+ end
165
+ "#{key}=#{str}"
166
+ end
167
+ "wfn:[#{attrs.join(',')}]"
168
+ end
169
+
170
+ def to_uri
171
+ fields = [@part, @vendor, @product, @version, @update, @edition, @language]
172
+ # Strip trailing empty fields
173
+ fields = fields[0...-1] while fields.any? && fields[-1].nil?
174
+ fields.map! do |f|
175
+ f.sub('?', '%01')
176
+ .sub('*', '%02')
177
+ end
178
+ 'cpe:/' + fields.join(':').downcase
179
+ end
180
+
181
+ def to_str
182
+ ['cpe', '2.3', @part, @vendor, @product, @version, @update, @edition,
183
+ @language, @sw_edition, @target_sw, @target_hw, @other].join(':').downcase
184
+ end
185
+
186
+ alias to_s to_str
187
+
188
+ class << self
189
+ def parse(str)
190
+ str = str.strip
191
+ if str.start_with? 'wfn:'
192
+ parse_wfn(str)
193
+ elsif str.start_with? 'cpe:/'
194
+ parse_uri(str)
195
+ elsif str.start_with? 'cpe:2.3:'
196
+ parse_str(str)
197
+ else
198
+ raise ArgumentError, 'CPE malformed'
199
+ end
200
+ end
201
+
202
+ def attr_match?(first, second)
203
+ first == '*' || second == '*' || first == second
204
+ end
205
+
206
+ private
207
+
208
+ def parse_wfn(str)
209
+ data = {}
210
+ WFN.parse(str)[:attr].each do |attr|
211
+ key = attr.capture(:symbol).value.to_sym
212
+ value = attr.capture(:value).then do |val|
213
+ if (str = val.capture(:string))
214
+ str.capture(:content).value
215
+ else
216
+ # Translate WFN special values (only applies to non-string)
217
+ case (str = val.value)
218
+ when 'ANY' then '*'
219
+ when 'NA' then nil
220
+ else str
221
+ end
222
+ end
223
+ end
224
+ if data.include? key
225
+ raise ArgumentError, 'Attribute defined multiple times'
226
+ end
227
+
228
+ data[key] = value
229
+ end
230
+
231
+ new(**data)
232
+ end
233
+
234
+ def parse_uri(str)
235
+ tag, body = str.split(':/', 2)
236
+ raise ArgumentError, 'Not a CPE URI' if tag != 'cpe' || body.nil?
237
+
238
+ body.sub!('%01', '?')
239
+ body.sub!('%02', '*')
240
+
241
+ data = {}
242
+ data[:part], data[:vendor], data[:product], data[:version], data[:update],
243
+ data[:edition], data[:language], remainder = body.split(':')
244
+
245
+ raise ArgumentError, 'CPE URI malformed' unless remainder.nil?
246
+
247
+ # All attributes are optional.
248
+ new(**data)
249
+ end
250
+
251
+ # Faster implementation of CPE parser... Citrus is not fast :(
252
+ def parse_cpe23(str)
253
+ raise ArgumentError, 'Not a CPE str' unless str.start_with?('cpe:2.3')
254
+
255
+ index = 7
256
+ size = str.size
257
+ char = str[index]
258
+ attr = 11.times.map do
259
+ raise ArgumentError, 'CPE formatted string malformed' if char != ':'
260
+
261
+ index += 1
262
+ attr_index = index
263
+ until index >= size || (char = str[index]) == ':'
264
+ index += 1
265
+ index += 1 if char == '\\' # Skip escaped characters
266
+ end
267
+ str[attr_index...index]
268
+ end
269
+ raise ArgumentError, 'CPE formatted string malformed' if index != size
270
+
271
+ attr
272
+ end
273
+
274
+ def parse_str(str)
275
+ # attr = Cpe23.parse(str)[:attr].map(&:value)
276
+ attr = parse_cpe23(str)
277
+ data = {}
278
+ data[:part], data[:vendor], data[:product], data[:version],
279
+ data[:update], data[:edition], data[:language], data[:sw_edition],
280
+ data[:target_sw], data[:target_hw], data[:other], remainder = attr
281
+
282
+ # All attributes MUST appear
283
+ if data.any? { |_k, v| v.nil? } || !remainder.nil?
284
+ raise ArgumentError, 'CPE formatted string malformed'
285
+ end
286
+
287
+ # Remove empty attributes to avoid confusing the constructor
288
+ data.reject! { |_k, v| v.empty? }
289
+
290
+ new(**data)
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,13 @@
1
+ grammar CPE23
2
+ rule cpe23
3
+ 'cpe:2.3' (':' attr) 11*11
4
+ end
5
+
6
+ rule escaped
7
+ '\\' .
8
+ end
9
+
10
+ rule attr
11
+ (escaped | /[^:]/)*
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Cpe23
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Cpe23
4
+ class Version
5
+ include Comparable
6
+ attr_reader :parts
7
+
8
+ def initialize(str)
9
+ @parts = str.split('.')
10
+ wildcard_index = @parts.index '*'
11
+ if wildcard_index.nil?
12
+ @parts << '*'
13
+ elsif wildcard_index < @parts.size - 1
14
+ raise 'Wildcard must be at the end of a version'
15
+ end
16
+ end
17
+
18
+ def <=>(other)
19
+ unless other.is_a? Version
20
+ begin
21
+ other = Version.new(other)
22
+ rescue StandardError
23
+ return nil
24
+ end
25
+ end
26
+ @parts.zip(other.parts).each do |a, b|
27
+ break if a == '*' || b == '*'
28
+
29
+ # Compare parts numerically if they are numeric
30
+ if int?(a) && int?(b)
31
+ a = a.to_i
32
+ b = b.to_i
33
+ end
34
+ return -1 if a.to_i < b.to_i
35
+ return 1 if a.to_i > b.to_i
36
+ end
37
+ 0
38
+ end
39
+
40
+ private
41
+
42
+ def int?(str)
43
+ true if Integer(str)
44
+ rescue StandardError
45
+ false
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,33 @@
1
+ grammar WFN
2
+ rule wfn
3
+ 'wfn:[' space (attr (space ',' space attr)*)? space ']'
4
+ end
5
+
6
+ rule attr
7
+ symbol space '=' space value
8
+ end
9
+
10
+ rule symbol
11
+ [a-zA-Z0-9_]*
12
+ end
13
+
14
+ rule escaped
15
+ '\\' .
16
+ end
17
+
18
+ rule content
19
+ (escaped | /[^"]/)*
20
+ end
21
+
22
+ rule string
23
+ '"' content '"'
24
+ end
25
+
26
+ rule value
27
+ string | symbol
28
+ end
29
+
30
+ rule space
31
+ [ \t]*
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cpe23
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Symon
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-02-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: citrus
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ description: Library for parsing, generating, and matching CPEs
28
+ email:
29
+ - jtsymon@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - ".travis.yml"
36
+ - Gemfile
37
+ - LICENSE.txt
38
+ - README.md
39
+ - Rakefile
40
+ - cpe23.gemspec
41
+ - lib/cpe23.rb
42
+ - lib/cpe23/cpe23.citrus
43
+ - lib/cpe23/version.rb
44
+ - lib/cpe23/version_wildcard.rb
45
+ - lib/cpe23/wfn.citrus
46
+ homepage: https://github.com/jtsymon/cpe23
47
+ licenses:
48
+ - MIT
49
+ metadata:
50
+ homepage_uri: https://github.com/jtsymon/cpe23
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.3.0
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.1.2
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: CPE parser/generator/matcher
70
+ test_files: []