Mercurial > farol > farolluz
comparison farolluz/cvrf.py @ 0:e18b61a73a68
Initial Release
author | Benoît Allard <benoit.allard@greenbone.net> |
---|---|
date | Tue, 23 Sep 2014 15:19:14 +0200 |
parents | |
children | d47e1164740f |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:e18b61a73a68 |
---|---|
1 # -*- coding: utf-8 -*- | |
2 # | |
3 # Authors: | |
4 # BenoƮt Allard <benoit.allard@greenbone.net> | |
5 # | |
6 # Copyright: | |
7 # Copyright (C) 2014 Greenbone Networks GmbH | |
8 # | |
9 # This program is free software; you can redistribute it and/or | |
10 # modify it under the terms of the GNU General Public License | |
11 # as published by the Free Software Foundation; either version 2 | |
12 # of the License, or (at your option) any later version. | |
13 # | |
14 # This program is distributed in the hope that it will be useful, | |
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 # GNU General Public License for more details. | |
18 # | |
19 # You should have received a copy of the GNU General Public License | |
20 # along with this program; if not, write to the Free Software | |
21 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
22 | |
23 """\ | |
24 Objects related to CVRF Documents | |
25 """ | |
26 | |
27 class ValidationError(Exception): pass | |
28 | |
29 class CVRFPublisher(object): | |
30 TYPES = ('Vendor', 'Discoverer', 'Coordinator', 'User', 'Other') | |
31 def __init__(self, _type, vendorid=None): | |
32 self._type = _type | |
33 self._vendorid = vendorid | |
34 self._contact = None | |
35 self._authority = None | |
36 | |
37 def setContact(self, contact): | |
38 self._contact = contact | |
39 | |
40 def setAuthority(self, authority): | |
41 self._authority = authority | |
42 | |
43 def validate(self): | |
44 if not self._type: | |
45 raise ValidationError('Document Publisher needs to have a type') | |
46 if self._type not in self.TYPES: | |
47 raise ValidationError('Document Publisher Type needs to be one of %s' % ', '.join(self.TYPES)) | |
48 | |
49 def __str__(self): | |
50 s = 'CVRFPublisher: %s' % self._type | |
51 if self._vendorid is not None: | |
52 s += ' ID: %s' % self._vendorid | |
53 if self._contact is not None: | |
54 s += ' Contact: "%s"' % self._contact | |
55 if self._authority is not None: | |
56 s += ' Authority: "%s"' % self._authority | |
57 return s | |
58 | |
59 | |
60 class CVRFTrackingID(object): | |
61 def __init__(self, _id): | |
62 self._id = _id | |
63 self._aliases = [] | |
64 | |
65 def addAlias(self, alias): | |
66 self._aliases.append(alias) | |
67 | |
68 def validate(self): | |
69 if not self._id: | |
70 raise ValidationError('Document ID cannot be left empty') | |
71 | |
72 def __str__(self): | |
73 if self._aliases: | |
74 return "%s (%s)" % (self._id, ', '.join(self._aliases)) | |
75 return self._id | |
76 | |
77 | |
78 class CVRFTracking(object): | |
79 STATUSES = ('Draft', 'Interim', 'Final') | |
80 def __init__(self, _id, status, version, initial, current): | |
81 self._identification = _id | |
82 self._status = status | |
83 self._version = version | |
84 self._history = [] | |
85 self._initialDate = initial | |
86 self._currentDate = current | |
87 self._generator = None | |
88 | |
89 def addRevision(self, revision): | |
90 self._history.append(revision) | |
91 | |
92 def setGenerator(self, generator): | |
93 self._generator = generator | |
94 | |
95 def validate(self): | |
96 if self._identification is None: | |
97 raise ValidationError('Document Tracking needs to have an Identification') | |
98 self._identification.validate() | |
99 if not self._status: | |
100 raise ValidationError('Document status must be set') | |
101 if self._status not in self.STATUSES: | |
102 raise ValidationError('Document Status must be one of %s' % ', '.join(self.STATUSES)) | |
103 if not self._version: | |
104 raise ValidationError('Document Version must be set') | |
105 if len(self._version) > 4: | |
106 raise ValidationError('Document Version must be comprised between `nn` and `nn.nn.nn.nn`') | |
107 if not self._history: | |
108 raise ValidationError('Document must have at least a revision') | |
109 if not self._initialDate: | |
110 raise ValidationError('Document must have an initial Release date set') | |
111 prev_date = self._initialDate | |
112 if self._history[0]._date < self._initialDate: | |
113 # Documents could have revisions before being released | |
114 prev_date = self._history[0]._date | |
115 prev = () | |
116 for revision in self._history: | |
117 revision.validate() | |
118 if revision._number <= prev: | |
119 raise ValidationError('Revision numbers must always be increasing') | |
120 if revision._date < prev_date: | |
121 raise ValidationError('Revision dates must always be increasing') | |
122 prev = revision._number | |
123 prev_date = revision._date | |
124 if not self._currentDate: | |
125 raise ValidationError('Document must have a Current Release Date set') | |
126 if self._currentDate != self._history[-1]._date: | |
127 raise ValidationError('Current Release Date must be the same as the Date from the last Revision') | |
128 if self._initialDate > self._currentDate: | |
129 raise ValidationError('Initial date must not be after current Date') | |
130 if self._version != self._history[-1]._number: | |
131 raise ValidationError('Document version must be the same as the number of the last Revision') | |
132 | |
133 def __str__(self): | |
134 s = "ID: %s" % self._identification | |
135 s += " Status: %s" % self._status | |
136 s += " v%s" % '.'.join('%d' % i for i in self._version) | |
137 s += " %d revisions" % len(self._history) | |
138 s += " Initial release: %s" % self._initialDate.isoformat() | |
139 return s | |
140 | |
141 | |
142 class CVRFRevision(object): | |
143 def __init__(self, number, date, description): | |
144 self._number = number | |
145 self._date = date | |
146 self._description = description | |
147 | |
148 def validate(self): | |
149 if not self._number: | |
150 raise ValidationError('A Revision must have a Number') | |
151 if not self._date: | |
152 raise ValidationError('A Revision must have a Date') | |
153 if not self._description: | |
154 raise ValidationError('A Revision must have a Description') | |
155 | |
156 class CVRFGenerator(object): | |
157 def __init__(self): | |
158 self._engine = None | |
159 self._date = None | |
160 | |
161 def setEngine(self, engine): | |
162 self._engine = engine | |
163 | |
164 def setDate(self, date): | |
165 self._date = date | |
166 | |
167 def validate(self): | |
168 if (not self._engine) and (not self._date): | |
169 raise ValidationError('The Generator must have at least an Engine or a Date') | |
170 | |
171 | |
172 class CVRFNote(object): | |
173 TYPES = ('General', 'Details', 'Description', 'Summary', 'FAQ', | |
174 'Legal Disclaimer', 'Other') | |
175 def __init__(self, _type, ordinal, note, title=None, audience=None): | |
176 self._type = _type | |
177 self._ordinal = ordinal | |
178 self._note = note | |
179 self._title = title | |
180 self._audience = audience | |
181 | |
182 def getTitle(self): | |
183 """ returns something that can be used as a title """ | |
184 if self._title is None: | |
185 return "%s (#%d)" % (self._type, self._ordinal) | |
186 return "%s (%s)" % (self._title, self._type) | |
187 | |
188 def validate(self): | |
189 if not self._type: | |
190 raise ValidationError('A Note needs to have a Type set') | |
191 if self._type not in self.TYPES: | |
192 raise ValidationError('A Note Type needs to be one of %s' % ', '.join(self.TYPES)) | |
193 if self._ordinal < 0: | |
194 raise ValidationError('A Note ordinal must be a positive integer') | |
195 if not self._note: | |
196 raise ValidationError('A Note must contain some text') | |
197 | |
198 | |
199 def __str__(self): | |
200 return self._note | |
201 | |
202 | |
203 class CVRFReference(object): | |
204 TYPES = ('Self', 'External') | |
205 def __init__(self, url, description, _type=None): | |
206 self._url = url | |
207 self._description = description | |
208 self._type = _type | |
209 | |
210 def validate(self): | |
211 if (self._type is not None) and (self._type not in self.TYPES): | |
212 raise ValidationError('If a Reference type is set, it mist be one of %s' % ', '.join(self.TYPES)) | |
213 if not self._url: | |
214 raise ValidationError('A Reference must contain an URL') | |
215 if not self._description: | |
216 raise ValidationError('A Reference must contain a description') | |
217 | |
218 | |
219 class CVRFAcknowledgment(object): | |
220 def __init__(self, name=None, organization=None, description=None, | |
221 url=None): | |
222 self._name = name | |
223 self._organization = organization | |
224 self._description = description | |
225 self._url = url | |
226 | |
227 def getTitle(self): | |
228 return "%s - %s" % (self._name, self._organization) | |
229 | |
230 def validate(self): | |
231 if (not self._name) and (not self._organization) and (not self._description): | |
232 raise ValidationError('An Acknowledgment must have at least a Name, an Organization or a Description') | |
233 | |
234 | |
235 class CVRFProductTree(object): | |
236 def __init__(self): | |
237 # All the branches, they can be order with their `parent` attribute | |
238 self._branches = [] | |
239 self._groups = [] | |
240 self._relationships = [] | |
241 self._products = [] | |
242 self._groups = [] | |
243 | |
244 def addBranch(self, branch): | |
245 parent = self.getBranch(branch.getParent().getPath()) | |
246 if parent is self: | |
247 self._branches.append(branch) | |
248 else: | |
249 parent._childs.append(branch) | |
250 | |
251 def addProduct(self, product): | |
252 if product not in self._products: | |
253 self._products.append(product) | |
254 if product._parent is not self: | |
255 product._parent._product = product | |
256 | |
257 def addRelationship(self, rel): | |
258 self._relationships.append(rel) | |
259 | |
260 def addGroup(self, group): | |
261 self._groups.append(group) | |
262 | |
263 def getProductForID(self, productid): | |
264 for product in self._products: | |
265 if product._productid == productid: | |
266 return product | |
267 raise KeyError(productid) | |
268 | |
269 def getGroupForID(self, groupid): | |
270 for group in self._groups: | |
271 if group._groupid == groupid: | |
272 return group | |
273 raise KeyError(groupid) | |
274 | |
275 def decomposeProduct(self, productid): | |
276 """ In case of product defined as a relationship (product X installed | |
277 on OS Y), this gives us the following tuple: (OS, product). """ | |
278 product = self.getProductForID(productid) | |
279 parent = product._parent | |
280 if parent is None: | |
281 return (None, None) | |
282 if not isinstance(parent, CVRFRelationship): | |
283 return (None, None) | |
284 relationtype = parent._relationtype.replace(' ', '').lower() | |
285 if relationtype not in ('defaultcomponentof', 'installedon'): | |
286 return (None, None) | |
287 return ( | |
288 self.getProductForID(parent._relatestoproductreference), | |
289 self.getProductForID(parent._productreference) | |
290 ) | |
291 | |
292 def getBranch(self, path): | |
293 if len(path) == 0: | |
294 return self | |
295 branches = self._branches | |
296 node = None | |
297 for idx in path: | |
298 node = branches[idx] | |
299 branches = node._childs | |
300 return node | |
301 | |
302 def getBranches(self): | |
303 for branch in self._branches: | |
304 yield branch | |
305 for sub_branch in branch.getBranches(): | |
306 yield sub_branch | |
307 | |
308 def getPath(self): | |
309 return () | |
310 | |
311 def getNameOfRelationship(self, relationship): | |
312 if relationship is None: | |
313 return '' | |
314 return ' '.join((self.getProductForID(relationship._productreference)._name, 'as', | |
315 relationship._relationtype.lower(), | |
316 self.getProductForID(relationship._relatestoproductreference)._name)) | |
317 | |
318 def getOrphanedBranches(self, product=None): | |
319 """ The branches that could accept `product` as Product Definition """ | |
320 white_list = [] | |
321 if product is not None: | |
322 white_list = [product._parent] | |
323 for branch in self.getBranches(): | |
324 if (branch in white_list) or branch.isOrphaned(): | |
325 yield branch | |
326 | |
327 def getNotTerminalBranches(self, b2=None): | |
328 """\ | |
329 The branches that could accept `b2` as new sub-branches | |
330 Note that b2 and all its sub-branches cannot be listed | |
331 """ | |
332 black_list = [] | |
333 if b2 is not None: | |
334 black_list = [b2] + list(b2.getBranches()) | |
335 for branch in self.getBranches(): | |
336 if branch in black_list: | |
337 continue | |
338 if branch._product is None: | |
339 yield branch | |
340 | |
341 def getOrphanedRelationships(self, product=None): | |
342 """ The relationships that need a product defninition """ | |
343 white_list = [] | |
344 if product is not None: | |
345 white_list = [product.getCurrentRelationship()] | |
346 for i, relationship in enumerate(self._relationships): | |
347 if (relationship in white_list) or relationship.isOrphaned(): | |
348 yield (i, relationship) | |
349 | |
350 def nbProducts(self): | |
351 """ Amount of 'raw' Products """ | |
352 return len([p for p in self._products if p._parent is self]) | |
353 | |
354 def validate(self): | |
355 for branch in self._branches: | |
356 branch.validate() | |
357 productids = set() | |
358 for product in self._products: | |
359 product.validate() | |
360 if product._productid in productids: | |
361 raise ValidationError('Each ProductID must be unique (%s)' % product._productid) | |
362 productids.add(product._productid) | |
363 for relationship in self._relationships: | |
364 relationship.validate() | |
365 for productid in (relationship._productreference, | |
366 relationship._relatestoproductreference): | |
367 if productid not in productids: | |
368 raise ValidationError('ProductID %s is unknown' % productid) | |
369 groupids = set() | |
370 for group in self._groups: | |
371 group.validate() | |
372 if group._groupid in groupids: | |
373 raise ValidationError('Duplicated GroupID: %s' % group._groupid) | |
374 groupids.add(group._groupid) | |
375 for productid in group._productids: | |
376 if productid not in productids: | |
377 raise ValidationError('ProductID %s is unknown' % productid) | |
378 return productids, groupids | |
379 | |
380 def __str__(self): | |
381 return 'Products: %s' % '\n'.join(str(p) for p in self._products) | |
382 | |
383 | |
384 class CVRFProductBranch(object): | |
385 TYPES = ('Vendor', 'Product Family', 'Product Name', 'Product Version', | |
386 'Patch Level', 'Service Pack', 'Architecture', 'Language', | |
387 'Legacy', 'Specification') | |
388 def __init__(self, _type, name, parentbranch): | |
389 self._type = _type | |
390 self._name = name | |
391 self._parentbranch = parentbranch | |
392 self._childs = [] | |
393 self._product = None | |
394 | |
395 def getParent(self): | |
396 return self._parentbranch | |
397 | |
398 def getPath(self, string=False): | |
399 """ return the path to that branch element as a tuple """ | |
400 if self.isRoot(): | |
401 for i, b in enumerate(self._parentbranch._branches): | |
402 if b is self: | |
403 if string: | |
404 return '%d' % i | |
405 return (i, ) | |
406 else: | |
407 for i, b in enumerate(self._parentbranch._childs): | |
408 if b is self: | |
409 if string: | |
410 return '/'.join([self._parentbranch.getPath(string), '%d' % i]) | |
411 return self._parentbranch.getPath(string) + (i,) | |
412 if string: | |
413 return '' | |
414 return () | |
415 | |
416 def getTree(self): | |
417 """ this returns a list of tuples (type, name) leading to here""" | |
418 if self.isRoot(): | |
419 return [(self._type, self._name)] | |
420 return self._parentbranch.getTree() + [(self._type, self._name)] | |
421 | |
422 def getName(self): | |
423 return ' / '.join("%s: %s" % (type_, name) for type_, name in self.getTree()) | |
424 | |
425 def getParentPath(self): | |
426 """ return as string the path to the parent """ | |
427 return '/'.join('%s' % p for p in self.getPath()[:-1]) | |
428 | |
429 def isRoot(self): | |
430 return isinstance(self._parentbranch, CVRFProductTree) | |
431 | |
432 def isOrphaned(self): | |
433 """ Has no childs and no product """ | |
434 return len(self._childs) == 0 and (self._product is None) | |
435 | |
436 def getBranches(self): | |
437 for branch in self._childs: | |
438 yield branch | |
439 for sub_branch in branch.getBranches(): | |
440 yield sub_branch | |
441 | |
442 def unlink(self): | |
443 """ Unset our _parent, and remove us from the _parent._childs """ | |
444 if self.isRoot(): | |
445 self.getParent()._branches.remove(self) | |
446 else: | |
447 self.getParent()._childs.remove(self) | |
448 self._parentbranch = None | |
449 | |
450 def validate(self): | |
451 if not self._type: | |
452 raise ValidationError('A Branch must have a Type') | |
453 if self._type not in self.TYPES: | |
454 raise ValidationError('A Branch Type must be one of %s' % ', '.join(self.TYPES)) | |
455 if not self._name: | |
456 raise ValidationError('A Branch must have a Name') | |
457 for branch in self._childs: | |
458 branch.validate() | |
459 if self.isOrphaned(): | |
460 raise ValidationError('A Branch must have at least a sub-product or sub-branches') | |
461 | |
462 def __str__(self): | |
463 return "%s: %s" % (self._type, self._name) | |
464 | |
465 | |
466 class CVRFFullProductName(object): | |
467 def __init__(self, productid, name, parent, cpe=None): | |
468 self._productid = productid | |
469 self._name = name | |
470 # Can be None (directly under the tree), a ProductBranch, or a | |
471 # Relationship | |
472 self._parent = parent | |
473 self._cpe = cpe | |
474 | |
475 def isRoot(self): | |
476 return isinstance(self._parent, CVRFProductTree) | |
477 | |
478 def isRelationship(self): | |
479 return isinstance(self._parent, CVRFRelationship) | |
480 | |
481 def getTree(self): | |
482 if not isinstance(self._parent, CVRFProductBranch): | |
483 return [] | |
484 return self._parent.getTree() | |
485 | |
486 def getParentPath(self): | |
487 if self.isRoot() or self.isRelationship(): | |
488 return '' | |
489 return self._parent.getPath(True) | |
490 | |
491 def getCurrentRelationship(self): | |
492 if self.isRelationship(): | |
493 return self._parent | |
494 return None | |
495 | |
496 def unlink(self): | |
497 """ Unset our _parent, and remove us from the _parent._childs """ | |
498 if self.isRoot(): | |
499 self._parent._products.remove(self) | |
500 else: | |
501 self._parent._product = None | |
502 self._parent = None | |
503 | |
504 def validate(self): | |
505 if not self._productid: | |
506 raise ValidationError('A Product must have a ProductID') | |
507 if not self._name: | |
508 raise ValidationError('A Product must have a Name') | |
509 | |
510 def __str__(self): | |
511 return "%s (%s)" % (self._productid, self._name) | |
512 | |
513 | |
514 class CVRFRelationship(object): | |
515 TYPES = ('Default Component Of', 'Optional Component Of', | |
516 'External Component Of', 'Installed On', 'Installed With') | |
517 def __init__(self, productref, reltype, relatestoproductref): | |
518 self._productreference = productref | |
519 self._relationtype = reltype | |
520 self._relatestoproductreference = relatestoproductref | |
521 self._product = None | |
522 | |
523 def getParent(self): | |
524 """ All parent element of a FullProductName should implement that | |
525 method """ | |
526 return None | |
527 | |
528 def isOrphaned(self): | |
529 return self._product is None | |
530 | |
531 def validate(self): | |
532 if not self._productreference: | |
533 raise ValidationError('A Relationship must have a Product Reference') | |
534 if not self._relationtype: | |
535 raise ValidationError('A Relationship must have a Relation Type') | |
536 if self._relationtype not in self.TYPES: | |
537 raise ValidationError('Relation Type must be one of %s' % ', '.join(self.TYPES)) | |
538 if not self._relatestoproductreference: | |
539 raise ValidationError('A Relationship must have a "Relates To product Reference"') | |
540 if self._productreference == self._relatestoproductreference: | |
541 raise ValidationError('A Relationship cannot reference twice the same Product') | |
542 | |
543 | |
544 class CVRFGroup(object): | |
545 def __init__(self, groupid): | |
546 self._groupid = groupid | |
547 self._description = None | |
548 self._productids = [] | |
549 | |
550 def setDescription(self, description): | |
551 self._description = description | |
552 | |
553 def addProductID(self, productid): | |
554 self._productids.append(productid) | |
555 | |
556 def getTitle(self): | |
557 if self._description: | |
558 return "%s (%d products)" % (self._description, len(self._productids)) | |
559 return "#%s (%d products)" % (self._groupid, len(self._productids)) | |
560 | |
561 def validate(self): | |
562 if not self._groupid: | |
563 raise ValidationError('A Group must have a GroupID') | |
564 if not self._productids or len(self._productids) < 2: | |
565 raise ValidationError('A Group must contain at least two products') | |
566 | |
567 | |
568 class CVRFVulnerabilityID(object): | |
569 def __init__(self, systemname, value): | |
570 self._systemname = systemname | |
571 self._value = value | |
572 | |
573 def validate(self): | |
574 if not self._systemname: | |
575 raise ValidationError('A Vulnerability ID must have a System Name') | |
576 if not self._value: | |
577 raise ValidationError('A Vulnerability ID must have a value') | |
578 | |
579 | |
580 class CVRFVulnerability(object): | |
581 def __init__(self, ordinal): | |
582 self._ordinal = ordinal | |
583 self._title = None | |
584 self._id = None | |
585 self._notes = [] | |
586 self._discoverydate = None | |
587 self._releasedate = None | |
588 self._involvements = [] | |
589 self._cve = None | |
590 self._cwes = [] | |
591 self._productstatuses = [] | |
592 self._threats = [] | |
593 self._cvsss = [] | |
594 self._remediations = [] | |
595 self._references = [] | |
596 self._acknowledgments = [] | |
597 | |
598 def setTitle(self, title): | |
599 self._title = title | |
600 | |
601 def setID(self, _id): | |
602 self._id = _id | |
603 | |
604 def addNote(self, note): | |
605 self._notes.append(note) | |
606 | |
607 def setDiscoveryDate(self, date): | |
608 self._discoverydate = date | |
609 | |
610 def setReleaseDate(self, date): | |
611 self._releasedate = date | |
612 | |
613 def addInvolvement(self, involvement): | |
614 self._involvements.append(involvement) | |
615 | |
616 def setCVE(self, cve): | |
617 self._cve = cve | |
618 | |
619 def addCWE(self, cwe): | |
620 self._cwes.append(cwe) | |
621 | |
622 def addProductStatus(self, productstatus): | |
623 self._productstatuses.append(productstatus) | |
624 | |
625 def addThreat(self, threat): | |
626 self._threats.append(threat) | |
627 | |
628 def addCVSSSet(self, cvss_set): | |
629 self._cvsss.append(cvss_set) | |
630 | |
631 def addRemediation(self, remediation): | |
632 self._remediations.append(remediation) | |
633 | |
634 def addReference(self, ref): | |
635 self._references.append(ref) | |
636 | |
637 def addAcknowledgment(self, ack): | |
638 self._acknowledgments.append(ack) | |
639 | |
640 def getTitle(self): | |
641 """ return something that can be used as a title """ | |
642 if self._title: | |
643 if self._id: | |
644 return "%s (%s)" % (self._title, self._id._value) | |
645 return self._title | |
646 if self._id: | |
647 return self._id._value | |
648 return "#%d" % self._ordinal | |
649 | |
650 def validate(self, productids, groupids): | |
651 if not self._ordinal: | |
652 raise ValidationError('A Vulnerability must have an ordinal') | |
653 if self._id is not None: | |
654 self._id.validate() | |
655 ordinals = set() | |
656 for note in self._notes: | |
657 note.validate() | |
658 if note._ordinal in ordinals: | |
659 raise ValidationError('Vulnerability Note Ordinal %d duplicated' % note._ordinal) | |
660 ordinals.add(note._ordinal) | |
661 for involvement in self._involvements: | |
662 involvement.validate() | |
663 for cwe in self._cwes: | |
664 cwe.validate() | |
665 for status in self._productstatuses: | |
666 status.validate(productids) | |
667 for threat in self._threats: | |
668 threat.validate(productids, groupids) | |
669 for cvss in self._cvsss: | |
670 cvss.validate(productids) | |
671 for remediation in self._remediations: | |
672 remediation.validate(productids, groupids) | |
673 for reference in self._references: | |
674 reference.validate() | |
675 for acknowledgment in self._acknowledgments: | |
676 acknowledgment.validate() | |
677 | |
678 | |
679 | |
680 class CVRFInvolvement(object): | |
681 PARTIES = CVRFPublisher.TYPES | |
682 STATUSES = ('Open', 'Disputed', 'In Progress', 'Completed', | |
683 'Contact Attempted', 'Not Contacted') | |
684 def __init__(self, party, status): | |
685 self._party = party | |
686 self._status = status | |
687 self._description = None | |
688 | |
689 def setDescription(self, description): | |
690 self._description = description | |
691 | |
692 def getTitle(self): | |
693 return "From %s: %s" % (self._party, self._status) | |
694 | |
695 def validate(self): | |
696 if not self._party: | |
697 raise ValidationError('An Involvement must have a Party') | |
698 if self._party not in self.PARTIES: | |
699 raise ValidationError("An Involvement's Party must be one of %s" % ', '.join(self.PARTIES)) | |
700 if not self._status: | |
701 raise ValidationError('An Involvement must have a Status') | |
702 if self._status not in self.STATUSES: | |
703 raise ValidationError("An Involvement's Status must be one of %s" % ', '.join(self.STATUSES)) | |
704 | |
705 | |
706 class CVRFCWE(object): | |
707 def __init__(self, _id, value): | |
708 self._id = _id | |
709 self._value = value | |
710 | |
711 def validate(self): | |
712 if not self._id: | |
713 raise ValidationError('A CWE must have an ID') | |
714 if not self._value: | |
715 raise ValidationError('A CWE must have a description') | |
716 | |
717 | |
718 class CVRFProductStatus(object): | |
719 TYPES = ('First Affected', 'Known Affected', 'Known Not Affected', | |
720 'First Fixed', 'Fixed', 'Recommended', 'Last Affected') | |
721 def __init__(self, _type): | |
722 self._type = _type | |
723 self._productids = [] | |
724 | |
725 def addProductID(self, productid): | |
726 self._productids.append(productid) | |
727 | |
728 def getTitle(self): | |
729 return "%s: %d products" % (self._type, len(self._productids)) | |
730 | |
731 def validate(self, productids): | |
732 if not self._type: | |
733 raise ValidationError('A Product Status must have a Type') | |
734 if self._type not in self.TYPES: | |
735 raise ValidationError("A Product Status' Type must be one of %s" % ', '.join(self.TYPES)) | |
736 if len(self._productids) < 1: | |
737 raise ValidationError('A Product Status must mention at least one Product') | |
738 for productid in self._productids: | |
739 if productid not in productids: | |
740 raise ValidationError('Unknown ProductID: %s' % productid) | |
741 | |
742 | |
743 class CVRFThreat(object): | |
744 TYPES = ('Impact', 'Exploit Status', 'Target Set') | |
745 def __init__(self, _type, description): | |
746 self._type = _type | |
747 self._description = description | |
748 self._date = None | |
749 self._productids = [] | |
750 self._groupids = [] | |
751 | |
752 def setDate(self, date): | |
753 self._date = date | |
754 | |
755 def addProductID(self, productid): | |
756 self._productids.append(productid) | |
757 | |
758 def addGroupID(self, groupid): | |
759 self._groupids.append(groupid) | |
760 | |
761 def getTitle(self): | |
762 return self._type | |
763 | |
764 def validate(self, productids, groupids): | |
765 if not self._type: | |
766 raise ValidationError('A Threat must have a Type') | |
767 if self._type not in self.TYPES: | |
768 raise ValidationError("A Threat's Type must be one of %s" % ', '.join(self.TYPES)) | |
769 if not self._description: | |
770 raise ValidationError('A Threat must have a Description') | |
771 for productid in self._productids: | |
772 if productid not in productids: | |
773 raise ValidationError('Unknown ProductID: %s' % productid) | |
774 for groupid in self._groupids: | |
775 if groupid not in groupids: | |
776 raise ValidationError('Unknown GroupID: %s' % groupid) | |
777 | |
778 | |
779 class CVRFCVSSSet(object): | |
780 # To determine the base Score | |
781 VALUES = {'AV': {'L':0.395, 'A':0.646, 'N':1.0}, | |
782 'AC': {'H':0.35, 'M':0.61 ,'L':0.71}, | |
783 'Au': {'M':0.45, 'S':0.56, 'N':0.704}, | |
784 'C': {'N':0.0, 'P':0.275, 'C':0.66}, | |
785 'I': {'N':0.0, 'P':0.275, 'C':0.66}, | |
786 'A': {'N':0.0, 'P':0.275, 'C':0.66}} | |
787 def __init__(self, basescore): | |
788 self._basescore = basescore | |
789 self._temporalscore = None | |
790 self._environmentalscore = None | |
791 self._vector = None | |
792 self.vector = None | |
793 self._productids = [] | |
794 | |
795 def setTemporalScore(self, tempscore): | |
796 self._temporalscore = tempscore | |
797 | |
798 def setEnvironmentalScore(self, envscore): | |
799 self._environmentalscore = envscore | |
800 | |
801 def setVector(self, vector): | |
802 self._vector = vector | |
803 if vector is None: | |
804 self.vector = vector | |
805 return | |
806 try: | |
807 self.vector = {} | |
808 for component in vector[:26].split('/'): | |
809 name, value = component.split(':') | |
810 self.vector[name] = self.VALUES[name][value] | |
811 except (KeyError, ValueError): | |
812 self.vector = None | |
813 | |
814 def addProductID(self, productid): | |
815 self._productids.append(productid) | |
816 | |
817 def baseScore(self): | |
818 v = self.vector # make an alias for shorter lines | |
819 exploitability = 20 * v['AV'] * v['AC'] * v['Au'] | |
820 impact = 10.41 * (1 - (1 - v['C']) * (1 - v['I']) * (1 - v['A'])) | |
821 def f(i): return 0 if i == 0 else 1.176 | |
822 return ((0.6 * impact) + (0.4 * exploitability) - 1.5) * f(impact) | |
823 | |
824 def validate(self, productids): | |
825 if not self._basescore: | |
826 raise ValidationError('A CVSS Score Set must have a Base Score') | |
827 if self._vector and not self.vector: | |
828 raise ValidationError('Syntax Error in CVSS Vector') | |
829 if abs(self._basescore - self.baseScore()) >= 0.05: | |
830 raise ValidationError('Inconsistency in CVSS Score Set between Vector (%f) and Base Score (%f)' % (self.baseScore(), self._basescore)) | |
831 for productid in self._productids: | |
832 if productid not in productids: | |
833 raise ValidationError('Unknown ProductID: %s' % productid) | |
834 | |
835 | |
836 class CVRFRemediation(object): | |
837 TYPES = ('Workaround', 'Mitigation', 'Vendor Fix', 'None Available', | |
838 'Will Not Fix') | |
839 def __init__(self, _type, description): | |
840 self._type = _type | |
841 self._description = description | |
842 self._date = None | |
843 self._entitlement = None | |
844 self._url = None | |
845 self._productids = [] | |
846 self._groupids = [] | |
847 | |
848 def setDate(self, date): | |
849 self._date = date | |
850 | |
851 def setEntitlement(self, entitlement): | |
852 self._entitlement = entitlement | |
853 | |
854 def setURL(self, url): | |
855 self._url = url | |
856 | |
857 def addProductID(self, productid): | |
858 self._productids.append(productid) | |
859 | |
860 def addGroupID(self, groupid): | |
861 self._groupids.append(groupid) | |
862 | |
863 def getTitle(self): | |
864 return self._type | |
865 | |
866 def validate(self, productids, groupids): | |
867 if not self._type: | |
868 raise ValidationError('A Remediation must have a Type') | |
869 if self._type not in self.TYPES: | |
870 raise ValidationError("A Remediation's Type must be one of %s" % ', '.join(self.TYPES)) | |
871 if not self._description: | |
872 raise ValidationError('A Remediation must have a Description') | |
873 for productid in self._productids: | |
874 if productid not in productids: | |
875 raise ValidationError('Unknown ProductID: %s' % productid) | |
876 for groupid in self._groupids: | |
877 if groupid not in groupids: | |
878 raise ValidationError('Unknown GroupID: %s' % groupid) | |
879 | |
880 class CVRF(object): | |
881 def __init__(self, title, _type): | |
882 self._title = title | |
883 self._type = _type | |
884 self._publisher = None | |
885 self._tracking = None | |
886 self._notes = [] | |
887 self._distribution = None | |
888 self._references = [] | |
889 self._acknowledgments = [] | |
890 self._producttree = None | |
891 self._vulnerabilities = [] | |
892 | |
893 def setPublisher(self, publisher): | |
894 self._publisher = publisher | |
895 | |
896 def setTracking(self, tracking): | |
897 self._tracking = tracking | |
898 | |
899 def addNote(self, note): | |
900 self._notes.append(note) | |
901 | |
902 def setDistribution(self, distribution): | |
903 self._distribution = distribution | |
904 | |
905 def addReference(self, ref): | |
906 self._references.append(ref) | |
907 | |
908 def addAcknowledgment(self, ack): | |
909 self._acknowledgments.append(ack) | |
910 | |
911 def createProductTree(self): | |
912 """ only done if the element is there """ | |
913 self._producttree = CVRFProductTree() | |
914 return self._producttree | |
915 | |
916 def addVulnerability(self, vuln): | |
917 self._vulnerabilities.append(vuln) | |
918 | |
919 def getProductForID(self, productid): | |
920 if self._producttree is None: | |
921 raise ValueError('No ProductTree') | |
922 return self._producttree.getProductForID(productid) | |
923 | |
924 def getGroupForID(self, groupid): | |
925 if self._producttree is None: | |
926 raise ValueError('No ProductTree') | |
927 return self._producttree.getGroupForID(groupid) | |
928 | |
929 def getHighestCVSS(self): | |
930 highestBaseScore = 0 | |
931 highest = None | |
932 for vulnerability in self._vulnerabilities: | |
933 for cvss in vulnerability._cvsss: | |
934 if cvss._basescore <= highestBaseScore: | |
935 continue | |
936 highestBaseScore = cvss._basescore | |
937 highest = cvss | |
938 return highest | |
939 | |
940 def getProductList(self, type_='Fixed'): | |
941 products = set() | |
942 if type_ == 'Fixed': | |
943 # First try through the Remediation | |
944 for vulnerability in self._vulnerabilities: | |
945 for remediation in vulnerability._remediations: | |
946 if remediation._type != 'Vendor Fix': | |
947 continue | |
948 for productid in remediation._productids: | |
949 products.add(productid) | |
950 for groupid in remediation._groupids: | |
951 for productid in self.getGroupForID(groupid)._productids: | |
952 products.add(productid) | |
953 if not products: | |
954 # If nothing there, try through the productstatuses | |
955 for vulnerability in self._vulnerabilities: | |
956 for status in vulnerability._productstatuses: | |
957 if status._type != type_: | |
958 continue | |
959 for productid in status._productids: | |
960 products.add(productid) | |
961 return set(self.getProductForID(p) for p in products) | |
962 | |
963 def getNote(self, ordinal): | |
964 for note in self._notes: | |
965 if note._ordinal == ordinal: | |
966 return note | |
967 return None | |
968 | |
969 def validate(self): | |
970 if not self._title: | |
971 raise ValidationError('Document Title cannot be empty') | |
972 if not self._type: | |
973 raise ValidationError('Document Type cannot be empty') | |
974 if self._publisher is None: | |
975 raise ValidationError('Document Publisher needs to be set') | |
976 self._publisher.validate() | |
977 if self._tracking is None: | |
978 raise ValidationError('Document Tracking needs to be set') | |
979 self._tracking.validate() | |
980 ordinals = set() | |
981 for note in self._notes: | |
982 note.validate() | |
983 if note._ordinal in ordinals: | |
984 raise ValidationError('Document Note ordinal %d is issued twice' % note._ordinal) | |
985 ordinals.add(note._ordinal) | |
986 for reference in self._references: | |
987 reference.validate() | |
988 for acknowledgment in self._acknowledgments: | |
989 acknowledgment.validate() | |
990 productids = set() | |
991 groupids = set() | |
992 if self._producttree: | |
993 productids, groupids = self._producttree.validate() | |
994 ordinals = set() | |
995 for vulnerability in self._vulnerabilities: | |
996 vulnerability.validate(productids, groupids) | |
997 if vulnerability._ordinal in ordinals: | |
998 raise ValidationError('Vulnerability ordinal %d is issued twice' % vulnerability._ordinal) | |
999 ordinals.add(vulnerability._ordinal) | |
1000 | |
1001 def __str__(self): | |
1002 s = [ | |
1003 'Title: %s' % self._title, | |
1004 'Type: %s' % self._type, | |
1005 'Publisher: %s' % self._publisher, | |
1006 'tracking: %s' % self._tracking, | |
1007 '%d Notes: %s' % (len(self._notes), ', '.join( | |
1008 str(n) for n in self._notes)) | |
1009 ] | |
1010 if self._distribution is not None: | |
1011 s.append('Distribution: %s' % self._distribution) | |
1012 s.extend([ | |
1013 '%d Acknowledgments' % len(self._acknowledgments), | |
1014 'Products: %s' % self._producttree, | |
1015 ]) | |
1016 return '\n'.join(s) |