1 /** 2 * Portions of this software was developed by employees of the National Institute 3 * of Standards and Technology (NIST), an agency of the Federal Government and is 4 * being made available as a public service. Pursuant to title 17 United States 5 * Code Section 105, works of NIST employees are not subject to copyright 6 * protection in the United States. This software may be subject to foreign 7 * copyright. Permission in the United States and in foreign countries, to the 8 * extent that NIST may hold copyright, to use, copy, modify, create derivative 9 * works, and distribute this software and its documentation without fee is hereby 10 * granted on a non-exclusive basis, provided that this notice and disclaimer 11 * of warranty appears in all copies. 12 * 13 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 14 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 15 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 17 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 18 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 19 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 20 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 21 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 22 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 23 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 24 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 25 */ 26 // Copyright (c) 2011, The MITRE Corporation 27 28 // All rights reserved. 29 // 30 // Redistribution and use in source and binary forms, with or without modification, are 31 // permitted provided that the following conditions are met: 32 // 33 // * Redistributions of source code must retain the above copyright notice, this list 34 // of conditions and the following disclaimer. 35 // * Redistributions in binary form must reproduce the above copyright notice, this 36 // list of conditions and the following disclaimer in the documentation and/or other 37 // materials provided with the distribution. 38 // * Neither the name of The MITRE Corporation nor the names of its contributors may be 39 // used to endorse or promote products derived from this software without specific 40 // prior written permission. 41 // 42 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 43 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 44 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 45 // SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 47 // OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 48 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 49 // TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 50 // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 51 52 package gov.nist.secauto.cpe.matching; 53 54 import gov.nist.secauto.cpe.common.LogicalValue; 55 import gov.nist.secauto.cpe.common.Utilities; 56 import gov.nist.secauto.cpe.common.WellFormedName; 57 import gov.nist.secauto.cpe.common.WellFormedName.Attribute; 58 59 import java.util.LinkedHashMap; 60 import java.util.Map; 61 62 /** 63 * The CPENameMatcher is an implementation of the CPE Matching algorithm, as specified in the CPE 64 * Matching Standard version 2.3. 65 * 66 * @see <a href= "https://doi.org/10.6028/NIST.IR.7696">NISTIR 7696 Section 7</a> 67 * 68 * @author <a href="mailto:jkraunelis@mitre.org">Joshua Kraunelis</a> 69 * @author <a href="mailto:david.waltermire@nist.gov">David Waltermire</a> 70 */ 71 public class CPENameMatcher { 72 private CPENameMatcher() { 73 // disable construction 74 } 75 76 /** 77 * Tests two Well Formed Names for disjointness. 78 * 79 * @param source 80 * Source WFN 81 * @param target 82 * Target WFN 83 * @return true if the names are disjoint, false otherwise 84 */ 85 public static boolean isDisjoint(WellFormedName../../../gov/nist/secauto/cpe/common/WellFormedName.html#WellFormedName">WellFormedName source, WellFormedName target) { 86 // if any pairwise comparison is disjoint, the names are disjoint. 87 Map<WellFormedName.Attribute, Relation> resultList = compareWFNs(source, target); 88 for (Relation result : resultList.values()) { 89 if (Relation.DISJOINT.equals(result)) { 90 return true; 91 } 92 } 93 return false; 94 } 95 96 /** 97 * Tests two Well Formed Names for equality. 98 * 99 * @param source 100 * Source WFN 101 * @param target 102 * Target WFN 103 * @return true if the names are equal, false otherwise 104 */ 105 public static boolean isEqual(WellFormedName../../../gov/nist/secauto/cpe/common/WellFormedName.html#WellFormedName">WellFormedName source, WellFormedName target) { 106 // if every pairwise comparison is equal, the names are equal. 107 Map<WellFormedName.Attribute, Relation> resultList = compareWFNs(source, target); 108 for (Relation result : resultList.values()) { 109 if (!(Relation.EQUAL.equals(result))) { 110 return false; 111 } 112 } 113 return true; 114 } 115 116 /** 117 * Tests if the target Well Formed Name is a subset of the source Well Formed Name. 118 * 119 * @param source 120 * Source WFN 121 * @param target 122 * Target WFN 123 * @return true if the target is a subset of the source, false otherwise 124 */ 125 public static boolean isSubset(WellFormedName../../../gov/nist/secauto/cpe/common/WellFormedName.html#WellFormedName">WellFormedName source, WellFormedName target) { 126 // if any comparison is anything other than subset or equal, then target is 127 // not a subset of source. 128 Map<WellFormedName.Attribute, Relation> resultList = compareWFNs(source, target); 129 for (Relation result : resultList.values()) { 130 if (!(Relation.SUBSET.equals(result)) && !(Relation.EQUAL.equals(result))) { 131 return false; 132 } 133 } 134 return true; 135 } 136 137 /** 138 * Tests if the target Well Formed name is a superset of the source Well Formed Name. 139 * 140 * @param source 141 * Source WFN 142 * @param target 143 * Target WFN 144 * @return true if the target is a superset of the source, false otherwise 145 */ 146 public static boolean isSuperset(WellFormedName../../../gov/nist/secauto/cpe/common/WellFormedName.html#WellFormedName">WellFormedName source, WellFormedName target) { 147 // if any comparison is anything other than superset or equal, then target is 148 // not 149 // a superset of source. 150 Map<WellFormedName.Attribute, Relation> resultList = compareWFNs(source, target); 151 for (Relation result : resultList.values()) { 152 if ((!Relation.SUPERSET.equals(result)) && (!Relation.EQUAL.equals(result))) { 153 return false; 154 } 155 } 156 return true; 157 } 158 159 /** 160 * Compares each attribute value pair in two Well Formed Names. 161 * 162 * @param source 163 * Source WFN 164 * @param target 165 * Target WFN 166 * @return A Hashtable mapping attribute string to attribute value Relation 167 */ 168 public static Map<WellFormedName.Attribute, Relation> compareWFNs(WellFormedName../../../gov/nist/secauto/cpe/common/WellFormedName.html#WellFormedName">WellFormedName source, WellFormedName target) { 169 Map<WellFormedName.Attribute, Relation> result 170 = new LinkedHashMap<WellFormedName.Attribute, Relation>(Attribute.values().length); 171 for (WellFormedName.Attribute attribute : Attribute.values()) { 172 result.put(attribute, compare(source.get(attribute), target.get(attribute))); 173 } 174 return result; 175 } 176 177 /** 178 * Compares an attribute value pair. 179 * 180 * @param source 181 * Source attribute value. 182 * @param target 183 * Target attribute value. 184 * @return The relation between the two attribute values. 185 */ 186 private static Relation compare(Object source, Object target) { 187 // matching is case insensitive, convert strings to lowercase. 188 if (isString(source)) { 189 source = Utilities.toLowercase((String) source); 190 } 191 if (isString(target)) { 192 target = Utilities.toLowercase((String) target); 193 } 194 195 // Unquoted wildcard characters yield an undefined result. 196 if (isString(target) && Utilities.containsWildcards((String) target)) { 197 return Relation.UNDEFINED; 198 } 199 // If source and target values are equal, then result is equal. 200 if (source.equals(target)) { 201 return Relation.EQUAL; 202 } 203 204 // Check to see if source or target are Logical Values. 205 LogicalValue lvSource = null; 206 LogicalValue lvTarget = null; 207 if (source instanceof LogicalValue) { 208 lvSource = (LogicalValue) source; 209 } 210 if (target instanceof LogicalValue) { 211 lvTarget = (LogicalValue) target; 212 } 213 214 // If Logical Values are equal, result is equal. 215 if (lvSource != null && lvTarget != null && lvSource.equals(lvTarget)) { 216 return Relation.EQUAL; 217 } 218 219 // If source value is ANY, result is a superset. 220 if (lvSource != null && LogicalValue.ANY.equals(lvSource)) { 221 return Relation.SUPERSET; 222 } 223 // If target value is ANY, result is a subset. 224 if (lvTarget != null && LogicalValue.ANY.equals(lvTarget)) { 225 return Relation.SUBSET; 226 } 227 // If source or target is NA, result is disjoint. 228 if (lvSource != null && LogicalValue.NA.equals(lvSource)) { 229 return Relation.DISJOINT; 230 } 231 232 if (lvTarget != null && LogicalValue.NA.equals(lvTarget)) { 233 return Relation.DISJOINT; 234 } 235 // only Strings will get to this point, not LogicalValues 236 return compareStrings((String) source, (String) target); 237 } 238 239 /** 240 * Compares a source string to a target string, and addresses the condition in which the source 241 * string includes unquoted special characters. It performs a simple regular expression match, with 242 * the assumption that (as required) unquoted special characters appear only at the beginning and/or 243 * the end of the source string. It also properly differentiates between unquoted and quoted special 244 * characters. 245 * 246 * @return Relation between source and target Strings. 247 */ 248 private static Relation compareStrings(String source, String target) { 249 int start = 0; 250 int end = Utilities.strlen(source); 251 int begins = 0; 252 int ends = 0; 253 254 if (Utilities.substr(source, 0, 1).equals("*")) { 255 start = 1; 256 begins = -1; 257 } else { 258 while ((start < Utilities.strlen(source)) && (Utilities.substr(source, start, start + 1).equals("?"))) { 259 start = start + 1; 260 begins = begins + 1; 261 } 262 } 263 if ((Utilities.substr(source, end - 1, end).equals("*")) && (isEvenWildcards(source, end - 1))) { 264 end = end - 1; 265 ends = -1; 266 } else { 267 while ((end > 0) && Utilities.substr(source, end - 1, end).equals("?") && (isEvenWildcards(source, end - 1))) { 268 end = end - 1; 269 ends = ends + 1; 270 } 271 } 272 273 source = Utilities.substr(source, start, end); 274 275 int index = -1; 276 int leftover = Utilities.strlen(target); 277 Relation retval = Relation.DISJOINT; 278 while (leftover > 0) { 279 index = Utilities.indexOf(target, source, index + 1); 280 if (index == -1) { 281 break; 282 } 283 int escapes = Utilities.countEscapeCharacters(target, 0, index); 284 if ((index > 0) && (begins != -1) && (begins < (index - escapes))) { 285 break; 286 } 287 escapes = Utilities.countEscapeCharacters(target, index + 1, Utilities.strlen(target)); 288 leftover = Utilities.strlen(target) - index - escapes - Utilities.strlen(source); 289 if ((leftover > 0) && ((ends != -1) && (leftover > ends))) { 290 continue; 291 } else { 292 retval = Relation.SUPERSET; 293 break; 294 } 295 } 296 return retval; 297 } 298 299 /** 300 * Searches a string for the backslash character. 301 * 302 * @param str 303 * string to search in 304 * @param idx 305 * end index 306 * @return true if the number of backslash characters is even, false if odd 307 */ 308 private static boolean isEvenWildcards(String str, int idx) { 309 int result = 0; 310 while ((idx > 0) && (Utilities.strchr(str, '\\', idx - 1)) != -1) { 311 idx = idx - 1; 312 result = result + 1; 313 } 314 return Utilities.isEvenNumber(result); 315 } 316 317 /** 318 * Tests if an Object is an instance of the String class. 319 * 320 * @param arg 321 * the Object to test 322 * @return true if arg is a String, false if not 323 */ 324 private static boolean isString(Object arg) { 325 if (arg instanceof String) { 326 return true; 327 } else { 328 return false; 329 } 330 } 331 }