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@0: class ValidationError(Exception): pass 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@0: class CVRFNote(object): benoit@0: TYPES = ('General', 'Details', 'Description', 'Summary', 'FAQ', benoit@0: 'Legal Disclaimer', 'Other') benoit@0: def __init__(self, _type, ordinal, note, title=None, audience=None): benoit@0: self._type = _type benoit@0: self._ordinal = ordinal benoit@0: self._note = note benoit@0: self._title = title benoit@0: self._audience = audience benoit@0: benoit@0: def getTitle(self): benoit@0: """ returns something that can be used as a title """ benoit@0: if self._title is None: benoit@0: return "%s (#%d)" % (self._type, self._ordinal) benoit@0: return "%s (%s)" % (self._title, self._type) benoit@0: benoit@0: def validate(self): benoit@0: if not self._type: benoit@0: raise ValidationError('A Note needs to have a Type set') benoit@0: if self._type not in self.TYPES: benoit@0: raise ValidationError('A Note Type needs to be one of %s' % ', '.join(self.TYPES)) benoit@0: if self._ordinal < 0: benoit@0: raise ValidationError('A Note ordinal must be a positive integer') benoit@0: if not self._note: benoit@0: raise ValidationError('A Note must contain some text') benoit@0: benoit@0: benoit@0: def __str__(self): benoit@0: return self._note 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@0: class CVRFReference(object): benoit@0: TYPES = ('Self', 'External') benoit@0: def __init__(self, url, description, _type=None): benoit@0: self._url = url benoit@0: self._description = description benoit@0: self._type = _type benoit@0: benoit@0: def validate(self): benoit@0: if (self._type is not None) and (self._type not in self.TYPES): benoit@0: raise ValidationError('If a Reference type is set, it mist be one of %s' % ', '.join(self.TYPES)) benoit@0: if not self._url: benoit@0: raise ValidationError('A Reference must contain an URL') benoit@0: if not self._description: benoit@0: raise ValidationError('A Reference must contain a description') benoit@0: benoit@0: benoit@0: class CVRFAcknowledgment(object): benoit@8: def __init__(self, names=[], organizations=[], description=None, benoit@0: url=None): benoit@8: self._names = names benoit@8: self._organizations = organizations benoit@0: self._description = description benoit@0: self._url = url benoit@0: benoit@0: def getTitle(self): benoit@8: return "%s - %s" % (', '.join(self._names), benoit@8: ', '.join(self._organizations)) benoit@0: benoit@0: def validate(self): benoit@8: if (not self._names) and (not self._organizations) and (not self._description): benoit@0: raise ValidationError('An Acknowledgment must have at least a Name, an Organization or a Description') benoit@0: benoit@0: benoit@0: class CVRFProductTree(object): benoit@0: def __init__(self): benoit@0: # All the branches, they can be order with their `parent` attribute benoit@0: self._branches = [] benoit@0: self._groups = [] benoit@0: self._relationships = [] benoit@0: self._products = [] benoit@0: self._groups = [] benoit@0: benoit@0: def addBranch(self, branch): benoit@0: parent = self.getBranch(branch.getParent().getPath()) benoit@0: if parent is self: benoit@0: self._branches.append(branch) benoit@0: else: benoit@0: parent._childs.append(branch) benoit@0: benoit@0: def addProduct(self, product): benoit@0: if product not in self._products: benoit@0: self._products.append(product) benoit@0: if product._parent is not self: benoit@0: product._parent._product = product benoit@0: benoit@0: def addRelationship(self, rel): benoit@0: self._relationships.append(rel) benoit@0: benoit@0: def addGroup(self, group): benoit@0: self._groups.append(group) benoit@0: benoit@0: def getProductForID(self, productid): benoit@0: for product in self._products: benoit@0: if product._productid == productid: benoit@0: return product benoit@0: raise KeyError(productid) benoit@0: benoit@0: def getGroupForID(self, groupid): benoit@0: for group in self._groups: benoit@0: if group._groupid == groupid: benoit@0: return group benoit@0: raise KeyError(groupid) benoit@0: benoit@0: def decomposeProduct(self, productid): benoit@0: """ In case of product defined as a relationship (product X installed benoit@0: on OS Y), this gives us the following tuple: (OS, product). """ benoit@0: product = self.getProductForID(productid) benoit@0: parent = product._parent benoit@0: if parent is None: benoit@0: return (None, None) benoit@0: if not isinstance(parent, CVRFRelationship): benoit@0: return (None, None) benoit@0: relationtype = parent._relationtype.replace(' ', '').lower() benoit@0: if relationtype not in ('defaultcomponentof', 'installedon'): benoit@0: return (None, None) benoit@0: return ( benoit@0: self.getProductForID(parent._relatestoproductreference), benoit@0: self.getProductForID(parent._productreference) benoit@0: ) benoit@0: benoit@0: def getBranch(self, path): benoit@0: if len(path) == 0: benoit@0: return self benoit@0: branches = self._branches benoit@0: node = None benoit@0: for idx in path: benoit@0: node = branches[idx] benoit@0: branches = node._childs benoit@0: return node benoit@0: benoit@0: def getBranches(self): benoit@0: for branch in self._branches: benoit@0: yield branch benoit@0: for sub_branch in branch.getBranches(): benoit@0: yield sub_branch benoit@0: benoit@0: def getPath(self): benoit@0: return () benoit@0: benoit@0: def getNameOfRelationship(self, relationship): benoit@0: if relationship is None: benoit@0: return '' benoit@0: return ' '.join((self.getProductForID(relationship._productreference)._name, 'as', benoit@0: relationship._relationtype.lower(), benoit@0: self.getProductForID(relationship._relatestoproductreference)._name)) benoit@0: benoit@0: def getOrphanedBranches(self, product=None): benoit@0: """ The branches that could accept `product` as Product Definition """ benoit@0: white_list = [] benoit@0: if product is not None: benoit@0: white_list = [product._parent] benoit@0: for branch in self.getBranches(): benoit@0: if (branch in white_list) or branch.isOrphaned(): benoit@0: yield branch benoit@0: benoit@0: def getNotTerminalBranches(self, b2=None): benoit@0: """\ benoit@0: The branches that could accept `b2` as new sub-branches benoit@0: Note that b2 and all its sub-branches cannot be listed benoit@0: """ benoit@0: black_list = [] benoit@0: if b2 is not None: benoit@0: black_list = [b2] + list(b2.getBranches()) benoit@0: for branch in self.getBranches(): benoit@0: if branch in black_list: benoit@0: continue benoit@0: if branch._product is None: benoit@0: yield branch benoit@0: benoit@0: def getOrphanedRelationships(self, product=None): benoit@0: """ The relationships that need a product defninition """ benoit@0: white_list = [] benoit@0: if product is not None: benoit@0: white_list = [product.getCurrentRelationship()] benoit@0: for i, relationship in enumerate(self._relationships): benoit@0: if (relationship in white_list) or relationship.isOrphaned(): benoit@0: yield (i, relationship) benoit@0: benoit@0: def nbProducts(self): benoit@0: """ Amount of 'raw' Products """ benoit@0: return len([p for p in self._products if p._parent is self]) benoit@0: benoit@0: def validate(self): benoit@0: for branch in self._branches: benoit@0: branch.validate() benoit@0: productids = set() benoit@0: for product in self._products: benoit@0: product.validate() benoit@0: if product._productid in productids: benoit@0: raise ValidationError('Each ProductID must be unique (%s)' % product._productid) benoit@0: productids.add(product._productid) benoit@0: for relationship in self._relationships: benoit@0: relationship.validate() benoit@0: for productid in (relationship._productreference, benoit@0: relationship._relatestoproductreference): benoit@0: if productid not in productids: benoit@0: raise ValidationError('ProductID %s is unknown' % productid) benoit@0: groupids = set() benoit@0: for group in self._groups: benoit@0: group.validate() benoit@0: if group._groupid in groupids: benoit@0: raise ValidationError('Duplicated GroupID: %s' % group._groupid) benoit@0: groupids.add(group._groupid) benoit@0: for productid in group._productids: benoit@0: if productid not in productids: benoit@0: raise ValidationError('ProductID %s is unknown' % productid) benoit@0: return productids, groupids benoit@0: benoit@0: def __str__(self): benoit@0: return 'Products: %s' % '\n'.join(str(p) for p in self._products) benoit@0: benoit@0: benoit@0: class CVRFProductBranch(object): benoit@0: TYPES = ('Vendor', 'Product Family', 'Product Name', 'Product Version', benoit@0: 'Patch Level', 'Service Pack', 'Architecture', 'Language', benoit@0: 'Legacy', 'Specification') benoit@0: def __init__(self, _type, name, parentbranch): benoit@0: self._type = _type benoit@0: self._name = name benoit@0: self._parentbranch = parentbranch benoit@0: self._childs = [] benoit@0: self._product = None benoit@0: benoit@0: def getParent(self): benoit@0: return self._parentbranch benoit@0: benoit@0: def getPath(self, string=False): benoit@0: """ return the path to that branch element as a tuple """ benoit@0: if self.isRoot(): benoit@0: for i, b in enumerate(self._parentbranch._branches): benoit@0: if b is self: benoit@0: if string: benoit@0: return '%d' % i benoit@0: return (i, ) benoit@0: else: benoit@0: for i, b in enumerate(self._parentbranch._childs): benoit@0: if b is self: benoit@0: if string: benoit@0: return '/'.join([self._parentbranch.getPath(string), '%d' % i]) benoit@0: return self._parentbranch.getPath(string) + (i,) benoit@0: if string: benoit@0: return '' benoit@0: return () benoit@0: benoit@0: def getTree(self): benoit@0: """ this returns a list of tuples (type, name) leading to here""" benoit@0: if self.isRoot(): benoit@0: return [(self._type, self._name)] benoit@0: return self._parentbranch.getTree() + [(self._type, self._name)] benoit@0: benoit@0: def getName(self): benoit@0: return ' / '.join("%s: %s" % (type_, name) for type_, name in self.getTree()) benoit@0: benoit@0: def getParentPath(self): benoit@0: """ return as string the path to the parent """ benoit@0: return '/'.join('%s' % p for p in self.getPath()[:-1]) benoit@0: benoit@0: def isRoot(self): benoit@0: return isinstance(self._parentbranch, CVRFProductTree) benoit@0: benoit@0: def isOrphaned(self): benoit@0: """ Has no childs and no product """ benoit@0: return len(self._childs) == 0 and (self._product is None) benoit@0: benoit@0: def getBranches(self): benoit@0: for branch in self._childs: benoit@0: yield branch benoit@0: for sub_branch in branch.getBranches(): benoit@0: yield sub_branch benoit@0: benoit@0: def unlink(self): benoit@0: """ Unset our _parent, and remove us from the _parent._childs """ benoit@0: if self.isRoot(): benoit@0: self.getParent()._branches.remove(self) benoit@0: else: benoit@0: self.getParent()._childs.remove(self) benoit@0: self._parentbranch = None benoit@0: benoit@0: def validate(self): benoit@0: if not self._type: benoit@0: raise ValidationError('A Branch must have a Type') benoit@0: if self._type not in self.TYPES: benoit@0: raise ValidationError('A Branch Type must be one of %s' % ', '.join(self.TYPES)) benoit@0: if not self._name: benoit@0: raise ValidationError('A Branch must have a Name') benoit@0: for branch in self._childs: benoit@0: branch.validate() benoit@0: if self.isOrphaned(): benoit@0: raise ValidationError('A Branch must have at least a sub-product or sub-branches') benoit@0: benoit@0: def __str__(self): benoit@0: return "%s: %s" % (self._type, self._name) benoit@0: benoit@0: benoit@0: class CVRFFullProductName(object): benoit@0: def __init__(self, productid, name, parent, cpe=None): benoit@0: self._productid = productid benoit@0: self._name = name benoit@0: # Can be None (directly under the tree), a ProductBranch, or a benoit@0: # Relationship benoit@0: self._parent = parent benoit@0: self._cpe = cpe benoit@0: benoit@0: def isRoot(self): benoit@0: return isinstance(self._parent, CVRFProductTree) benoit@0: benoit@0: def isRelationship(self): benoit@0: return isinstance(self._parent, CVRFRelationship) benoit@0: benoit@0: def getTree(self): benoit@0: if not isinstance(self._parent, CVRFProductBranch): benoit@0: return [] benoit@0: return self._parent.getTree() benoit@0: benoit@0: def getParentPath(self): benoit@0: if self.isRoot() or self.isRelationship(): benoit@0: return '' benoit@0: return self._parent.getPath(True) benoit@0: benoit@0: def getCurrentRelationship(self): benoit@0: if self.isRelationship(): benoit@0: return self._parent benoit@0: return None benoit@0: benoit@0: def unlink(self): benoit@0: """ Unset our _parent, and remove us from the _parent._childs """ benoit@0: if self.isRoot(): benoit@0: self._parent._products.remove(self) benoit@0: else: benoit@0: self._parent._product = None benoit@0: self._parent = None benoit@0: benoit@0: def validate(self): benoit@0: if not self._productid: benoit@0: raise ValidationError('A Product must have a ProductID') benoit@0: if not self._name: benoit@0: raise ValidationError('A Product must have a Name') benoit@0: benoit@0: def __str__(self): benoit@0: return "%s (%s)" % (self._productid, self._name) benoit@0: benoit@0: benoit@0: class CVRFRelationship(object): benoit@0: TYPES = ('Default Component Of', 'Optional Component Of', benoit@0: 'External Component Of', 'Installed On', 'Installed With') benoit@0: def __init__(self, productref, reltype, relatestoproductref): benoit@0: self._productreference = productref benoit@0: self._relationtype = reltype benoit@0: self._relatestoproductreference = relatestoproductref benoit@0: self._product = None benoit@0: benoit@0: def getParent(self): benoit@0: """ All parent element of a FullProductName should implement that benoit@0: method """ benoit@0: return None benoit@0: benoit@0: def isOrphaned(self): benoit@0: return self._product is None benoit@0: benoit@0: def validate(self): benoit@0: if not self._productreference: benoit@0: raise ValidationError('A Relationship must have a Product Reference') benoit@0: if not self._relationtype: benoit@0: raise ValidationError('A Relationship must have a Relation Type') benoit@0: if self._relationtype not in self.TYPES: benoit@0: raise ValidationError('Relation Type must be one of %s' % ', '.join(self.TYPES)) benoit@0: if not self._relatestoproductreference: benoit@0: raise ValidationError('A Relationship must have a "Relates To product Reference"') benoit@0: if self._productreference == self._relatestoproductreference: benoit@0: raise ValidationError('A Relationship cannot reference twice the same Product') benoit@0: benoit@0: benoit@0: class CVRFGroup(object): benoit@0: def __init__(self, groupid): benoit@0: self._groupid = groupid benoit@0: self._description = None benoit@0: self._productids = [] benoit@0: benoit@0: def setDescription(self, description): benoit@0: self._description = description benoit@0: benoit@0: def addProductID(self, productid): benoit@0: self._productids.append(productid) benoit@0: benoit@0: def getTitle(self): benoit@0: if self._description: benoit@0: return "%s (%d products)" % (self._description, len(self._productids)) benoit@0: return "#%s (%d products)" % (self._groupid, len(self._productids)) benoit@0: benoit@0: def validate(self): benoit@0: if not self._groupid: benoit@0: raise ValidationError('A Group must have a GroupID') benoit@0: if not self._productids or len(self._productids) < 2: benoit@0: raise ValidationError('A Group must contain at least two products') benoit@0: benoit@0: benoit@0: class CVRFVulnerabilityID(object): benoit@0: def __init__(self, systemname, value): benoit@0: self._systemname = systemname benoit@0: self._value = value benoit@0: benoit@0: def validate(self): benoit@0: if not self._systemname: benoit@0: raise ValidationError('A Vulnerability ID must have a System Name') benoit@0: if not self._value: benoit@0: raise ValidationError('A Vulnerability ID must have a value') benoit@0: benoit@0: benoit@0: class CVRFVulnerability(object): benoit@0: def __init__(self, ordinal): benoit@0: self._ordinal = ordinal benoit@0: self._title = None benoit@0: self._id = None benoit@0: self._notes = [] benoit@0: self._discoverydate = None benoit@0: self._releasedate = None benoit@0: self._involvements = [] benoit@0: self._cve = None benoit@0: self._cwes = [] benoit@0: self._productstatuses = [] benoit@0: self._threats = [] benoit@0: self._cvsss = [] benoit@0: self._remediations = [] benoit@0: self._references = [] benoit@0: self._acknowledgments = [] benoit@0: benoit@0: def setTitle(self, title): benoit@0: self._title = title benoit@0: benoit@0: def setID(self, _id): benoit@0: self._id = _id benoit@0: benoit@0: def addNote(self, note): benoit@0: self._notes.append(note) benoit@0: benoit@0: def setDiscoveryDate(self, date): benoit@0: self._discoverydate = date benoit@0: benoit@0: def setReleaseDate(self, date): benoit@0: self._releasedate = date benoit@0: benoit@0: def addInvolvement(self, involvement): benoit@0: self._involvements.append(involvement) benoit@0: benoit@0: def setCVE(self, cve): benoit@0: self._cve = cve benoit@0: benoit@0: def addCWE(self, cwe): benoit@0: self._cwes.append(cwe) benoit@0: benoit@0: def addProductStatus(self, productstatus): benoit@0: self._productstatuses.append(productstatus) benoit@0: benoit@0: def addThreat(self, threat): benoit@0: self._threats.append(threat) benoit@0: benoit@0: def addCVSSSet(self, cvss_set): benoit@0: self._cvsss.append(cvss_set) benoit@0: benoit@0: def addRemediation(self, remediation): benoit@0: self._remediations.append(remediation) benoit@0: 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 getTitle(self): benoit@0: """ return something that can be used as a title """ benoit@0: if self._title: benoit@0: if self._id: benoit@0: return "%s (%s)" % (self._title, self._id._value) benoit@0: return self._title benoit@0: if self._id: benoit@0: return self._id._value benoit@0: return "#%d" % self._ordinal benoit@0: benoit@7: def getNote(self, ordinal): benoit@7: for note in self._notes: benoit@7: if note._ordinal == ordinal: benoit@7: return note benoit@7: return None benoit@7: benoit@0: def validate(self, productids, groupids): benoit@0: if not self._ordinal: benoit@0: raise ValidationError('A Vulnerability must have an ordinal') benoit@0: if self._id is not None: benoit@0: self._id.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('Vulnerability Note Ordinal %d duplicated' % note._ordinal) benoit@0: ordinals.add(note._ordinal) benoit@0: for involvement in self._involvements: benoit@0: involvement.validate() benoit@0: for cwe in self._cwes: benoit@0: cwe.validate() benoit@0: for status in self._productstatuses: benoit@0: status.validate(productids) benoit@0: for threat in self._threats: benoit@0: threat.validate(productids, groupids) benoit@0: for cvss in self._cvsss: benoit@0: cvss.validate(productids) benoit@0: for remediation in self._remediations: benoit@0: remediation.validate(productids, groupids) benoit@0: for reference in self._references: benoit@0: reference.validate() benoit@0: for acknowledgment in self._acknowledgments: benoit@0: acknowledgment.validate() benoit@0: benoit@0: benoit@0: benoit@0: class CVRFInvolvement(object): benoit@0: PARTIES = CVRFPublisher.TYPES benoit@0: STATUSES = ('Open', 'Disputed', 'In Progress', 'Completed', benoit@0: 'Contact Attempted', 'Not Contacted') benoit@0: def __init__(self, party, status): benoit@0: self._party = party benoit@0: self._status = status benoit@0: self._description = None benoit@0: benoit@0: def setDescription(self, description): benoit@0: self._description = description benoit@0: benoit@0: def getTitle(self): benoit@0: return "From %s: %s" % (self._party, self._status) benoit@0: benoit@0: def validate(self): benoit@0: if not self._party: benoit@0: raise ValidationError('An Involvement must have a Party') benoit@0: if self._party not in self.PARTIES: benoit@0: raise ValidationError("An Involvement's Party must be one of %s" % ', '.join(self.PARTIES)) benoit@0: if not self._status: benoit@0: raise ValidationError('An Involvement must have a Status') benoit@0: if self._status not in self.STATUSES: benoit@0: raise ValidationError("An Involvement's Status must be one of %s" % ', '.join(self.STATUSES)) benoit@0: benoit@0: benoit@0: class CVRFCWE(object): benoit@0: def __init__(self, _id, value): benoit@0: self._id = _id benoit@0: self._value = value benoit@0: benoit@0: def validate(self): benoit@0: if not self._id: benoit@0: raise ValidationError('A CWE must have an ID') benoit@0: if not self._value: benoit@0: raise ValidationError('A CWE must have a description') benoit@0: benoit@0: benoit@0: class CVRFProductStatus(object): benoit@0: TYPES = ('First Affected', 'Known Affected', 'Known Not Affected', benoit@0: 'First Fixed', 'Fixed', 'Recommended', 'Last Affected') benoit@0: def __init__(self, _type): benoit@0: self._type = _type benoit@0: self._productids = [] benoit@0: benoit@0: def addProductID(self, productid): benoit@0: self._productids.append(productid) benoit@0: benoit@0: def getTitle(self): benoit@0: return "%s: %d products" % (self._type, len(self._productids)) benoit@0: benoit@0: def validate(self, productids): benoit@0: if not self._type: benoit@0: raise ValidationError('A Product Status must have a Type') benoit@0: if self._type not in self.TYPES: benoit@0: raise ValidationError("A Product Status' Type must be one of %s" % ', '.join(self.TYPES)) benoit@0: if len(self._productids) < 1: benoit@0: raise ValidationError('A Product Status must mention at least one Product') benoit@0: for productid in self._productids: benoit@0: if productid not in productids: benoit@0: raise ValidationError('Unknown ProductID: %s' % productid) benoit@0: benoit@0: benoit@0: class CVRFThreat(object): benoit@0: TYPES = ('Impact', 'Exploit Status', 'Target Set') benoit@0: def __init__(self, _type, description): benoit@0: self._type = _type benoit@0: self._description = description benoit@0: self._date = None benoit@0: self._productids = [] benoit@0: self._groupids = [] benoit@0: benoit@0: def setDate(self, date): benoit@0: self._date = date benoit@0: benoit@0: def addProductID(self, productid): benoit@0: self._productids.append(productid) benoit@0: benoit@0: def addGroupID(self, groupid): benoit@0: self._groupids.append(groupid) benoit@0: benoit@0: def getTitle(self): benoit@0: return self._type benoit@0: benoit@0: def validate(self, productids, groupids): benoit@0: if not self._type: benoit@0: raise ValidationError('A Threat must have a Type') benoit@0: if self._type not in self.TYPES: benoit@0: raise ValidationError("A Threat's Type must be one of %s" % ', '.join(self.TYPES)) benoit@0: if not self._description: benoit@0: raise ValidationError('A Threat must have a Description') benoit@0: for productid in self._productids: benoit@0: if productid not in productids: benoit@0: raise ValidationError('Unknown ProductID: %s' % productid) benoit@0: for groupid in self._groupids: benoit@0: if groupid not in groupids: benoit@0: raise ValidationError('Unknown GroupID: %s' % groupid) benoit@0: benoit@0: benoit@0: class CVRFCVSSSet(object): benoit@0: # To determine the base Score benoit@0: VALUES = {'AV': {'L':0.395, 'A':0.646, 'N':1.0}, benoit@0: 'AC': {'H':0.35, 'M':0.61 ,'L':0.71}, benoit@0: 'Au': {'M':0.45, 'S':0.56, 'N':0.704}, benoit@0: 'C': {'N':0.0, 'P':0.275, 'C':0.66}, benoit@0: 'I': {'N':0.0, 'P':0.275, 'C':0.66}, benoit@0: 'A': {'N':0.0, 'P':0.275, 'C':0.66}} benoit@0: def __init__(self, basescore): benoit@0: self._basescore = basescore benoit@0: self._temporalscore = None benoit@0: self._environmentalscore = None benoit@0: self._vector = None benoit@0: self.vector = None benoit@0: self._productids = [] benoit@0: benoit@0: def setTemporalScore(self, tempscore): benoit@0: self._temporalscore = tempscore benoit@0: benoit@0: def setEnvironmentalScore(self, envscore): benoit@0: self._environmentalscore = envscore benoit@0: benoit@0: def setVector(self, vector): benoit@0: self._vector = vector benoit@0: if vector is None: benoit@0: self.vector = vector benoit@0: return benoit@0: try: benoit@0: self.vector = {} benoit@0: for component in vector[:26].split('/'): benoit@0: name, value = component.split(':') benoit@0: self.vector[name] = self.VALUES[name][value] benoit@0: except (KeyError, ValueError): benoit@0: self.vector = None benoit@0: benoit@0: def addProductID(self, productid): benoit@0: self._productids.append(productid) benoit@0: benoit@0: def baseScore(self): benoit@0: v = self.vector # make an alias for shorter lines benoit@0: exploitability = 20 * v['AV'] * v['AC'] * v['Au'] benoit@0: impact = 10.41 * (1 - (1 - v['C']) * (1 - v['I']) * (1 - v['A'])) benoit@0: def f(i): return 0 if i == 0 else 1.176 benoit@0: return ((0.6 * impact) + (0.4 * exploitability) - 1.5) * f(impact) benoit@0: benoit@0: def validate(self, productids): benoit@0: if not self._basescore: benoit@0: raise ValidationError('A CVSS Score Set must have a Base Score') benoit@0: if self._vector and not self.vector: benoit@0: raise ValidationError('Syntax Error in CVSS Vector') benoit@0: if abs(self._basescore - self.baseScore()) >= 0.05: benoit@0: raise ValidationError('Inconsistency in CVSS Score Set between Vector (%f) and Base Score (%f)' % (self.baseScore(), self._basescore)) benoit@0: for productid in self._productids: benoit@0: if productid not in productids: benoit@0: raise ValidationError('Unknown ProductID: %s' % productid) benoit@0: benoit@0: benoit@0: class CVRFRemediation(object): benoit@0: TYPES = ('Workaround', 'Mitigation', 'Vendor Fix', 'None Available', benoit@0: 'Will Not Fix') benoit@0: def __init__(self, _type, description): benoit@0: self._type = _type benoit@0: self._description = description benoit@0: self._date = None benoit@0: self._entitlement = None benoit@0: self._url = None benoit@0: self._productids = [] benoit@0: self._groupids = [] benoit@0: benoit@0: def setDate(self, date): benoit@0: self._date = date benoit@0: benoit@0: def setEntitlement(self, entitlement): benoit@0: self._entitlement = entitlement benoit@0: benoit@0: def setURL(self, url): benoit@0: self._url = url benoit@0: benoit@0: def addProductID(self, productid): benoit@0: self._productids.append(productid) benoit@0: benoit@0: def addGroupID(self, groupid): benoit@0: self._groupids.append(groupid) benoit@0: benoit@0: def getTitle(self): benoit@0: return self._type benoit@0: benoit@0: def validate(self, productids, groupids): benoit@0: if not self._type: benoit@0: raise ValidationError('A Remediation must have a Type') benoit@0: if self._type not in self.TYPES: benoit@0: raise ValidationError("A Remediation's Type must be one of %s" % ', '.join(self.TYPES)) benoit@0: if not self._description: benoit@0: raise ValidationError('A Remediation must have a Description') benoit@0: for productid in self._productids: benoit@0: if productid not in productids: benoit@0: raise ValidationError('Unknown ProductID: %s' % productid) benoit@0: for groupid in self._groupids: benoit@0: if groupid not in groupids: benoit@0: raise ValidationError('Unknown GroupID: %s' % groupid) benoit@0: 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@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)