benoit@0: # -*- coding: utf-8 -*- benoit@0: # benoit@0: # Authors: benoit@0: # BenoƮt Allard benoit@0: # benoit@0: # Copyright: benoit@0: # Copyright (C) 2014 Greenbone Networks GmbH benoit@0: # benoit@0: # This program is free software; you can redistribute it and/or benoit@0: # modify it under the terms of the GNU General Public License benoit@0: # as published by the Free Software Foundation; either version 2 benoit@0: # of the License, or (at your option) any later version. benoit@0: # benoit@0: # This program is distributed in the hope that it will be useful, benoit@0: # but WITHOUT ANY WARRANTY; without even the implied warranty of benoit@0: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the benoit@0: # GNU General Public License for more details. benoit@0: # benoit@0: # You should have received a copy of the GNU General Public License benoit@0: # along with this program; if not, write to the Free Software benoit@0: # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. benoit@0: benoit@0: """\ benoit@0: Objects related to CVRF Documents benoit@0: """ benoit@0: benoit@26: from .common import ValidationError benoit@26: from .producttree import CVRFProductTree, CVRFRelationship benoit@0: benoit@0: class CVRFPublisher(object): benoit@0: TYPES = ('Vendor', 'Discoverer', 'Coordinator', 'User', 'Other') benoit@0: def __init__(self, _type, vendorid=None): benoit@0: self._type = _type benoit@0: self._vendorid = vendorid benoit@0: self._contact = None benoit@0: self._authority = None benoit@0: benoit@0: def setContact(self, contact): benoit@0: self._contact = contact benoit@0: benoit@0: def setAuthority(self, authority): benoit@0: self._authority = authority benoit@0: benoit@0: def validate(self): benoit@0: if not self._type: benoit@0: raise ValidationError('Document Publisher needs to have a type') benoit@0: if self._type not in self.TYPES: benoit@0: raise ValidationError('Document Publisher Type needs to be one of %s' % ', '.join(self.TYPES)) benoit@0: benoit@0: def __str__(self): benoit@0: s = 'CVRFPublisher: %s' % self._type benoit@0: if self._vendorid is not None: benoit@0: s += ' ID: %s' % self._vendorid benoit@0: if self._contact is not None: benoit@0: s += ' Contact: "%s"' % self._contact benoit@0: if self._authority is not None: benoit@0: s += ' Authority: "%s"' % self._authority benoit@0: return s benoit@0: benoit@0: benoit@0: class CVRFTrackingID(object): benoit@0: def __init__(self, _id): benoit@0: self._id = _id benoit@0: self._aliases = [] benoit@0: benoit@0: def addAlias(self, alias): benoit@0: self._aliases.append(alias) benoit@0: benoit@6: def getId(self): benoit@6: return self._id benoit@6: benoit@0: def validate(self): benoit@0: if not self._id: benoit@0: raise ValidationError('Document ID cannot be left empty') benoit@0: benoit@0: def __str__(self): benoit@0: if self._aliases: benoit@0: return "%s (%s)" % (self._id, ', '.join(self._aliases)) benoit@0: return self._id benoit@0: benoit@0: benoit@0: class CVRFTracking(object): benoit@0: STATUSES = ('Draft', 'Interim', 'Final') benoit@0: def __init__(self, _id, status, version, initial, current): benoit@0: self._identification = _id benoit@0: self._status = status benoit@0: self._version = version benoit@0: self._history = [] benoit@0: self._initialDate = initial benoit@0: self._currentDate = current benoit@0: self._generator = None benoit@0: benoit@0: def addRevision(self, revision): benoit@0: self._history.append(revision) benoit@0: benoit@0: def setGenerator(self, generator): benoit@0: self._generator = generator benoit@0: benoit@6: def getId(self): benoit@6: return self._identification.getId() benoit@6: benoit@0: def validate(self): benoit@0: if self._identification is None: benoit@0: raise ValidationError('Document Tracking needs to have an Identification') benoit@0: self._identification.validate() benoit@0: if not self._status: benoit@0: raise ValidationError('Document status must be set') benoit@0: if self._status not in self.STATUSES: benoit@0: raise ValidationError('Document Status must be one of %s' % ', '.join(self.STATUSES)) benoit@0: if not self._version: benoit@0: raise ValidationError('Document Version must be set') benoit@0: if len(self._version) > 4: benoit@0: raise ValidationError('Document Version must be comprised between `nn` and `nn.nn.nn.nn`') benoit@0: if not self._history: benoit@0: raise ValidationError('Document must have at least a revision') benoit@0: if not self._initialDate: benoit@0: raise ValidationError('Document must have an initial Release date set') benoit@0: prev_date = self._initialDate benoit@0: if self._history[0]._date < self._initialDate: benoit@0: # Documents could have revisions before being released benoit@0: prev_date = self._history[0]._date benoit@0: prev = () benoit@0: for revision in self._history: benoit@0: revision.validate() benoit@0: if revision._number <= prev: benoit@0: raise ValidationError('Revision numbers must always be increasing') benoit@0: if revision._date < prev_date: benoit@0: raise ValidationError('Revision dates must always be increasing') benoit@0: prev = revision._number benoit@0: prev_date = revision._date benoit@0: if not self._currentDate: benoit@0: raise ValidationError('Document must have a Current Release Date set') benoit@0: if self._currentDate != self._history[-1]._date: benoit@0: raise ValidationError('Current Release Date must be the same as the Date from the last Revision') benoit@0: if self._initialDate > self._currentDate: benoit@0: raise ValidationError('Initial date must not be after current Date') benoit@0: if self._version != self._history[-1]._number: benoit@0: raise ValidationError('Document version must be the same as the number of the last Revision') benoit@0: benoit@0: def __str__(self): benoit@0: s = "ID: %s" % self._identification benoit@0: s += " Status: %s" % self._status benoit@0: s += " v%s" % '.'.join('%d' % i for i in self._version) benoit@0: s += " %d revisions" % len(self._history) benoit@0: s += " Initial release: %s" % self._initialDate.isoformat() benoit@0: return s benoit@0: benoit@0: benoit@0: class CVRFRevision(object): benoit@0: def __init__(self, number, date, description): benoit@0: self._number = number benoit@0: self._date = date benoit@0: self._description = description benoit@0: benoit@0: def validate(self): benoit@0: if not self._number: benoit@0: raise ValidationError('A Revision must have a Number') benoit@0: if not self._date: benoit@0: raise ValidationError('A Revision must have a Date') benoit@0: if not self._description: benoit@0: raise ValidationError('A Revision must have a Description') benoit@0: benoit@0: class CVRFGenerator(object): benoit@0: def __init__(self): benoit@0: self._engine = None benoit@0: self._date = None benoit@0: benoit@0: def setEngine(self, engine): benoit@0: self._engine = engine benoit@0: benoit@0: def setDate(self, date): benoit@0: self._date = date benoit@0: benoit@0: def validate(self): benoit@0: if (not self._engine) and (not self._date): benoit@0: raise ValidationError('The Generator must have at least an Engine or a Date') benoit@0: benoit@0: benoit@1: class CVRFAggregateSeverity(object): benoit@1: def __init__(self, severity): benoit@1: self._severity = severity benoit@1: self._namespace = None benoit@1: benoit@1: def setNamespace(self, namespace): benoit@1: self._namespace = namespace benoit@1: benoit@22: benoit@0: class CVRF(object): benoit@0: def __init__(self, title, _type): benoit@0: self._title = title benoit@0: self._type = _type benoit@0: self._publisher = None benoit@0: self._tracking = None benoit@0: self._notes = [] benoit@0: self._distribution = None benoit@1: self._aggregateseverity = None benoit@0: self._references = [] benoit@0: self._acknowledgments = [] benoit@0: self._producttree = None benoit@0: self._vulnerabilities = [] benoit@0: benoit@0: def setPublisher(self, publisher): benoit@0: self._publisher = publisher benoit@0: benoit@0: def setTracking(self, tracking): benoit@0: self._tracking = tracking benoit@0: benoit@0: def addNote(self, note): benoit@0: self._notes.append(note) benoit@0: benoit@0: def setDistribution(self, distribution): benoit@0: self._distribution = distribution benoit@0: benoit@1: def setAggregateSeverity(self, aggregateseverity): benoit@1: self._aggregateseverity = aggregateseverity benoit@1: benoit@0: def addReference(self, ref): benoit@0: self._references.append(ref) benoit@0: benoit@0: def addAcknowledgment(self, ack): benoit@0: self._acknowledgments.append(ack) benoit@0: benoit@0: def createProductTree(self): benoit@0: """ only done if the element is there """ benoit@0: self._producttree = CVRFProductTree() benoit@0: return self._producttree benoit@0: benoit@0: def addVulnerability(self, vuln): benoit@0: self._vulnerabilities.append(vuln) benoit@0: benoit@0: def getProductForID(self, productid): benoit@0: if self._producttree is None: benoit@0: raise ValueError('No ProductTree') benoit@0: return self._producttree.getProductForID(productid) benoit@0: benoit@0: def getGroupForID(self, groupid): benoit@0: if self._producttree is None: benoit@0: raise ValueError('No ProductTree') benoit@0: return self._producttree.getGroupForID(groupid) benoit@0: benoit@0: def getHighestCVSS(self): benoit@0: highestBaseScore = 0 benoit@0: highest = None benoit@0: for vulnerability in self._vulnerabilities: benoit@0: for cvss in vulnerability._cvsss: benoit@0: if cvss._basescore <= highestBaseScore: benoit@0: continue benoit@0: highestBaseScore = cvss._basescore benoit@0: highest = cvss benoit@0: return highest benoit@0: benoit@0: def getProductList(self, type_='Fixed'): benoit@0: products = set() benoit@0: if type_ == 'Fixed': benoit@0: # First try through the Remediation benoit@0: for vulnerability in self._vulnerabilities: benoit@0: for remediation in vulnerability._remediations: benoit@0: if remediation._type != 'Vendor Fix': benoit@0: continue benoit@0: for productid in remediation._productids: benoit@0: products.add(productid) benoit@0: for groupid in remediation._groupids: benoit@0: for productid in self.getGroupForID(groupid)._productids: benoit@0: products.add(productid) benoit@0: if not products: benoit@0: # If nothing there, try through the productstatuses benoit@0: for vulnerability in self._vulnerabilities: benoit@0: for status in vulnerability._productstatuses: benoit@0: if status._type != type_: benoit@0: continue benoit@0: for productid in status._productids: benoit@0: products.add(productid) benoit@0: return set(self.getProductForID(p) for p in products) benoit@0: benoit@22: def mentionsProductId(self, productid): benoit@17: # We first look at the ProductTree benoit@17: ptree = self._producttree benoit@17: for relation in ptree._relationships: benoit@17: if productid == relation._productreference: benoit@22: yield relation benoit@22: elif productid == relation._relatestoproductreference: benoit@22: yield relation benoit@22: # Then go through the groups benoit@22: for group in ptree._groups: benoit@22: if productid in group._productids: benoit@22: yield group benoit@22: # Finally, go through all the Vulnerabilities benoit@17: for vulnerability in self._vulnerabilities: benoit@22: for item in vulnerability.mentionsProdId(productid): benoit@22: yield item benoit@22: benoit@22: def isProductOrphan(self, productid): benoit@22: """ Returns if a productid is mentioned nowhere in the document """ benoit@30: for _ in self.mentionsProductId(productid): benoit@30: return False benoit@30: return True benoit@22: benoit@22: def changeProductID(self, old, new): benoit@22: for item in self.mentionsProductId(old): benoit@22: if isinstance(item, CVRFRelationship): benoit@22: if old == item._productreference: benoit@22: item._productreference = new benoit@22: elif old == item._relatestoproductreference: benoit@22: item._relatestoproductreference = new benoit@22: else: benoit@22: item._productids.remove(old) benoit@22: item._productids.append(new) benoit@17: benoit@31: def mentionsGroupId(self, groupid): benoit@31: for vulnerability in self._vulnerabilities: benoit@31: for item in vulnerability.mentionsGroupId(groupid): benoit@31: yield item benoit@31: benoit@19: def isGroupOrphan(self, groupid): benoit@19: """ Returns if a group can be safely deleted """ benoit@31: for _ in self.mentionsGroupId(groupid): benoit@31: return False benoit@19: return True benoit@19: benoit@31: def changeGroupID(self, old, new): benoit@31: for item in self.mentionsGroupId(old): benoit@31: item._groupids.remove(old) benoit@31: item._groupids.append(new) benoit@31: benoit@19: def isProductTreeOrphan(self): benoit@19: """ Difference with the previous method is that we don;t care about benoit@19: inter-producttree references """ benoit@19: for vulnerability in self._vulnerabilities: benoit@19: for product in self._producttree._products: benoit@19: if vulnerability.isMentioningProdId(product._productid): benoit@19: return False benoit@19: for group in self._producttree._groups: benoit@19: if vulnerability.isMentioningGroupId(group._groupid): benoit@19: return False benoit@19: return True benoit@19: benoit@0: def getNote(self, ordinal): benoit@0: for note in self._notes: benoit@0: if note._ordinal == ordinal: benoit@0: return note benoit@0: return None benoit@0: benoit@6: def getDocId(self): benoit@6: if self._tracking is not None: benoit@6: return self._tracking.getId() benoit@6: # Make up something ... benoit@6: return self._title.lower() benoit@6: benoit@0: def validate(self): benoit@0: if not self._title: benoit@0: raise ValidationError('Document Title cannot be empty') benoit@0: if not self._type: benoit@0: raise ValidationError('Document Type cannot be empty') benoit@0: if self._publisher is None: benoit@0: raise ValidationError('Document Publisher needs to be set') benoit@0: self._publisher.validate() benoit@0: if self._tracking is None: benoit@0: raise ValidationError('Document Tracking needs to be set') benoit@0: self._tracking.validate() benoit@0: ordinals = set() benoit@0: for note in self._notes: benoit@0: note.validate() benoit@0: if note._ordinal in ordinals: benoit@0: raise ValidationError('Document Note ordinal %d is issued twice' % note._ordinal) benoit@0: ordinals.add(note._ordinal) benoit@0: for reference in self._references: benoit@0: reference.validate() benoit@0: for acknowledgment in self._acknowledgments: benoit@0: acknowledgment.validate() benoit@0: productids = set() benoit@0: groupids = set() benoit@0: if self._producttree: benoit@0: productids, groupids = self._producttree.validate() benoit@0: ordinals = set() benoit@0: for vulnerability in self._vulnerabilities: benoit@0: vulnerability.validate(productids, groupids) benoit@0: if vulnerability._ordinal in ordinals: benoit@0: raise ValidationError('Vulnerability ordinal %d is issued twice' % vulnerability._ordinal) benoit@0: ordinals.add(vulnerability._ordinal) benoit@0: benoit@0: def __str__(self): benoit@0: s = [ benoit@0: 'Title: %s' % self._title, benoit@0: 'Type: %s' % self._type, benoit@0: 'Publisher: %s' % self._publisher, benoit@0: 'tracking: %s' % self._tracking, benoit@0: '%d Notes: %s' % (len(self._notes), ', '.join( benoit@0: str(n) for n in self._notes)) benoit@0: ] benoit@0: if self._distribution is not None: benoit@0: s.append('Distribution: %s' % self._distribution) benoit@0: s.extend([ benoit@0: '%d Acknowledgments' % len(self._acknowledgments), benoit@0: 'Products: %s' % self._producttree, benoit@0: ]) benoit@0: return '\n'.join(s)