1#! /usr/bin/env python 2# -*- coding: utf-8 -*- 3 4""" 5This file is part of cpe package. 6 7This module is an implementation of CPE language matching 8algorithm in accordance with version 2.3 of CPE (Common Platform 9Enumeration) specification. 10 11Copyright (C) 2013 Alejandro Galindo García, Roberto Abdelkader Martínez Pérez 12 13This program is free software: you can redistribute it and/or modify 14it under the terms of the GNU Lesser General Public License as published by 15the Free Software Foundation, either version 3 of the License, or 16(at your option) any later version. 17 18This program is distributed in the hope that it will be useful, 19but WITHOUT ANY WARRANTY; without even the implied warranty of 20MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21GNU Lesser General Public License for more details. 22 23You should have received a copy of the GNU Lesser General Public License 24along with this program. If not, see <http://www.gnu.org/licenses/>. 25 26For any problems using the cpe package, or general questions and 27feedback about it, please contact: 28 29- Alejandro Galindo García: galindo.garcia.alejandro@gmail.com 30- Roberto Abdelkader Martínez Pérez: robertomartinezp@gmail.com 31""" 32 33from .cpeset2_3 import CPESet2_3 34from .cpelang import CPELanguage 35from .cpe2_3_wfn import CPE2_3_WFN 36from .cpe2_3_uri import CPE2_3_URI 37from .cpe2_3_fs import CPE2_3_FS 38 39 40class CPELanguage2_3(CPELanguage): 41 """ 42 Represents an expression in the CPE Language. 43 44 This class allows match a CPE element against an expression 45 in the CPE Language, that is, a XML document format for binding 46 descriptive prose and diagnostic test to a CPE name 47 (CPE Description Format). 48 """ 49 50 ############### 51 # CONSTANTS # 52 ############### 53 54 #: Version of CPE Language 55 VERSION = "2.3" 56 57 ################### 58 # CLASS METHODS # 59 ################### 60 61 @classmethod 62 def _fact_ref_eval(cls, cpeset, wfn): 63 """ 64 Returns True if wfn is a non-proper superset (True superset 65 or equal to) any of the names in cpeset, otherwise False. 66 67 :param CPESet cpeset: list of CPE bound Names. 68 :param CPE2_3_WFN wfn: WFN CPE Name. 69 :returns: True if wfn is a non-proper superset any of the names in cpeset, otherwise False 70 :rtype: boolean 71 """ 72 73 for n in cpeset: 74 # Need to convert each n from bound form to WFN 75 if (CPESet2_3.cpe_superset(wfn, n)): 76 return True 77 78 return False 79 80 @classmethod 81 def _check_fact_ref_eval(cls, cpel_dom): 82 """ 83 Returns the result (True, False, Error) of performing the specified 84 check, unless the check isnt supported, in which case it returns 85 False. Error is a catch-all for all results other than True and 86 False. 87 88 :param string cpel_dom: XML infoset for the check_fact_ref element. 89 :returns: result of performing the specified check 90 :rtype: boolean or error 91 """ 92 93 CHECK_SYSTEM = "check-system" 94 CHECK_LOCATION = "check-location" 95 CHECK_ID = "check-id" 96 97 checksystemID = cpel_dom.getAttribute(CHECK_SYSTEM) 98 if (checksystemID == "http://oval.mitre.org/XMLSchema/ovaldefinitions-5"): 99 # Perform an OVAL check. 100 # First attribute is the URI of an OVAL definitions file. 101 # Second attribute is an OVAL definition ID. 102 return CPELanguage2_3._ovalcheck(cpel_dom.getAttribute(CHECK_LOCATION), 103 cpel_dom.getAttribute(CHECK_ID)) 104 105 if (checksystemID == "http://scap.nist.gov/schema/ocil/2"): 106 # Perform an OCIL check. 107 # First attribute is the URI of an OCIL questionnaire file. 108 # Second attribute is OCIL questionnaire ID. 109 return CPELanguage2_3._ocilcheck(cpel_dom.getAttribute(CHECK_LOCATION), 110 cpel_dom.getAttribute(CHECK_ID)) 111 112 # Can add additional check systems here, with each returning a 113 # True, False, or Error value 114 return False 115 116 @classmethod 117 def _ocilcheck(location, ocil_id): 118 """ 119 Perform an OCIL check. 120 121 :param string location: URI of an OCIL questionnaire file 122 :param string ocil_id: OCIL questionnaire ID 123 :rtype: boolean 124 :exception: NotImplementedError - Method not implemented 125 """ 126 127 errmsg = "Method not implemented" 128 raise NotImplementedError(errmsg) 129 130 @classmethod 131 def _ovalcheck(location, oval_id): 132 """ 133 Perform an OVAL check. 134 135 :param string location: URI of an OVAL definitions file 136 :param string oval_id: OVAL definition ID 137 :rtype: boolean 138 :exception: NotImplementedError - Method not implemented 139 """ 140 141 errmsg = "Method not implemented" 142 raise NotImplementedError(errmsg) 143 144 @classmethod 145 def _unbind(cls, boundname): 146 """ 147 Unbinds a bound form to a WFN. 148 149 :param string boundname: CPE name 150 :returns: WFN object associated with boundname. 151 :rtype: CPE2_3_WFN 152 """ 153 154 try: 155 fs = CPE2_3_FS(boundname) 156 except: 157 # CPE name is not formatted string 158 try: 159 uri = CPE2_3_URI(boundname) 160 except: 161 # CPE name is not URI but WFN 162 return CPE2_3_WFN(boundname) 163 else: 164 return CPE2_3_WFN(uri.as_wfn()) 165 else: 166 return CPE2_3_WFN(fs.as_wfn()) 167 168 #################### 169 # OBJECT METHODS # 170 #################### 171 172 def language_match(self, cpeset, cpel_dom=None): 173 """ 174 Accepts a set of known CPE Names and an expression in the CPE language, 175 and delivers the answer True if the expression matches with the set. 176 Otherwise, it returns False. 177 178 :param CPELanguage self: An expression in the CPE Applicability 179 Language, represented as the XML infoset for the platform element. 180 :param CPESet cpeset: CPE set object to match with self expression. 181 :param string cpel_dom: An expression in the CPE Applicability 182 Language, represented as DOM tree. 183 :returns: True if self expression can be satisfied by language matching 184 against cpeset, False otherwise. 185 :rtype: boolean 186 """ 187 188 # Root element tag 189 TAG_ROOT = '#document' 190 # A container for child platform definitions 191 TAG_PLATSPEC = 'cpe:platform-specification' 192 193 # Information about a platform definition 194 TAG_PLATFORM = 'cpe:platform' 195 TAG_LOGITEST = 'cpe:logical-test' 196 TAG_CPE = 'cpe:fact-ref' 197 TAG_CHECK_CPE = 'check-fact-ref' 198 199 # Tag attributes 200 ATT_NAME = 'name' 201 ATT_OP = 'operator' 202 ATT_NEGATE = 'negate' 203 204 # Attribute values 205 ATT_OP_AND = 'AND' 206 ATT_OP_OR = 'OR' 207 ATT_NEGATE_TRUE = 'TRUE' 208 209 # Constant associated with an error in language matching 210 ERROR = 2 211 212 if cpel_dom is None: 213 cpel_dom = self.document 214 215 # Identify the root element 216 if cpel_dom.nodeName == TAG_ROOT or cpel_dom.nodeName == TAG_PLATSPEC: 217 for node in cpel_dom.childNodes: 218 if node.nodeName == TAG_PLATSPEC: 219 return self.language_match(cpeset, node) 220 if node.nodeName == TAG_PLATFORM: 221 return self.language_match(cpeset, node) 222 223 # Identify a platform element 224 elif cpel_dom.nodeName == TAG_PLATFORM: 225 # Parse through E's elements and ignore all but logical-test 226 for node in cpel_dom.childNodes: 227 if node.nodeName == TAG_LOGITEST: 228 # Call the function again, but with logical-test 229 # as the root element 230 return self.language_match(cpeset, node) 231 232 # Identify a CPE element 233 elif cpel_dom.nodeName == TAG_CPE: 234 # fact-ref's name attribute is a bound name, 235 # so we unbind it to a WFN before passing it 236 cpename = cpel_dom.getAttribute(ATT_NAME) 237 wfn = CPELanguage2_3._unbind(cpename) 238 return CPELanguage2_3._fact_ref_eval(cpeset, wfn) 239 240 # Identify a check of CPE names (OVAL, OCIL...) 241 elif cpel_dom.nodeName == TAG_CHECK_CPE: 242 return CPELanguage2_3._check_fact_ref_Eval(cpel_dom) 243 244 # Identify a logical operator element 245 elif cpel_dom.nodeName == TAG_LOGITEST: 246 count = 0 247 len = 0 248 answer = False 249 250 for node in cpel_dom.childNodes: 251 if node.nodeName.find("#") == 0: 252 continue 253 len = len + 1 254 result = self.language_match(cpeset, node) 255 if result: 256 count = count + 1 257 elif result == ERROR: 258 answer = ERROR 259 260 operator = cpel_dom.getAttribute(ATT_OP).upper() 261 262 if operator == ATT_OP_AND: 263 if count == len: 264 answer = True 265 elif operator == ATT_OP_OR: 266 if count > 0: 267 answer = True 268 269 operator_not = cpel_dom.getAttribute(ATT_NEGATE) 270 if operator_not: 271 if ((operator_not.upper() == ATT_NEGATE_TRUE) and 272 (answer != ERROR)): 273 answer = not answer 274 275 return answer 276 else: 277 return False 278 279if __name__ == "__main__": 280 import doctest 281 doctest.testmod() 282 doctest.testfile("tests/testfile_cpelang2_3.txt") 283