1 /** 2 * Copyright © DiamondMVC 2019 3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 */ 6 module diamond.security.validation.sensitive; 7 8 import std.regex : Regex, regex, matchAll; 9 import std.algorithm : filter, canFind; 10 import std.array : array; 11 12 /// Collection of patterns used to create the sensitive data regex. 13 private static __gshared string[][SecurityLevel] _sensitiveDataPatterns; 14 15 // Collection of names to look for in the input which might disclose sensitive data. 16 private static __gshared string[][SecurityLevel] _sensitiveDataNames; 17 18 /// The generated regex from sensitive data patterns. 19 private static __gshared Regex!char[][SecurityLevel] _sensitiveDataRegexes; 20 21 /// Regex for phone numbers. 22 private static const _phoneNumberRegex = `(\+?(?:(?:9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)|\((?:9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\))[0-9. -]{4,14})(?:\b|x\d+)`; 23 24 /// Regex for amex cards. 25 private static const _amexCardRegex = `^3[47][0-9]{13}$`; 26 27 /// Regex for BCGlobal cards. 28 private static const _BCGlobalRegex = `^(6541|6556)[0-9]{12}$`; 29 30 /// Regex for Carte Blanche cards. 31 private static const _carteBlancheCardRegex = `^389[0-9]{11}$`; 32 33 /// Regex for Diners Club cards. 34 private static const _dinersClubCardRegex = `^3(?:0[0-5]|[68][0-9])[0-9]{11}$`; 35 36 /// Regex for Discover cards. 37 private static const _discoverCardRegex = `^65[4-9][0-9]{13}|64[4-9][0-9]{13}|6011[0-9]{12}|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]{10})$`; 38 39 /// Regex for Insta Payment cards. 40 private static const _instaPaymentCardRegex = `^63[7-9][0-9]{13}$`; 41 42 /// Regex for JCB cards. 43 private static const _JCBCardRegex = `^(?:2131|1800|35\d{3})\d{11}$`; 44 45 /// Regex for Korean Local cards. 46 private static const _koreanLocalCardRegex = `^9[0-9]{15}$`; 47 48 /// Regex for Maestro cards. 49 private static const _maestroCardRegex = `^(5018|5020|5038|6304|6759|6761|6763)[0-9]{8,15}$`; 50 51 /// Regex for Mastercard cards. 52 private static const _mastercardRegex = `^5[1-5][0-9]{14}$`; 53 54 /// Regex for Solo cards. 55 private static const _soloCardRegex = `^(6334|6767)[0-9]{12}|(6334|6767)[0-9]{14}|(6334|6767)[0-9]{15}$`; 56 57 /// Regex for Union Pay cards. 58 private static const _unionPayCardRegex = `^(62[0-9]{14,17})$`; 59 60 /// Regex for Visa cards. 61 private static const _visaCardRegex = `^4[0-9]{12}(?:[0-9]{3})?$`; 62 63 /// Regex for Visa master cards. 64 private static const _visaMasterCardRegex = `^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14})$`; 65 66 /// Regex for Mastercards after 2016. 67 private static const _masterCard2016Regex = `^5$|^5[1-5][0-9]{0,14}$|^2[2]?[2]?$|^2221[0-9]{0,12}$|^222[3-9][0-9]{0,12}$|^22[3-9][0-9]{0,13}$|^2[3-6][0-9]{0,14}$|^2[7]?$|^27[2]?$|^27[0-1][0-9]{0,12}$|^2720[0-9]{0,12}$`; 68 69 /// Regex for Mastercard Bin cards. 70 private static const _masterCardBinRegex = `^(?:4[0-9]{12}(?:[0-9]{3})?|[25][1-7][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$`; 71 72 /// Regex for generic credit/debit cards. 73 private static const _genericCardRegex = `^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$`; 74 75 /// Regex for CPR numbers. 76 private static const _cprRegex = `^(?:(?:31(?:0[13578]|1[02])|(?:30|29)(?:0[13-9]|1[0-2])|(?:0[1-9]|1[0-9]|2[0-8])(?:0[1-9]|1[0-2]))[0-9]{2}\s?-?\s?[0-9]|290200\s?-?\s?[4-9]|2902(?:(?!00)[02468][048]|[13579][26])\s?-?\s?[0-3])[0-9]{3}|000000\s?-?\s?0000$`; 77 78 /// Regex for Social Security numbers. 79 private static const _ssnRegex = `^(?!(000|666|9))\d{3}-(?!00)\d{2}-(?!0000)\d{4}$`; 80 81 /// Regex for UK Insurance numbers. 82 private static const _ukInsuranceNumberRegex = `^\s*[a-zA-Z]{2}(?:\s*\d\s*){6}[a-zA-Z]?\s*$`; 83 84 /// Enumeration of security levels. 85 enum SecurityLevel 86 { 87 /// Allows everything under medium, but also phone numbers, emails and addresses. 88 minimum, 89 /// Allows names, authentication (Not password), zip codes / postal codes, job / occupation, age, politics, race and ethnicity. 90 medium, 91 /// Allows no sensitive data. 92 maximum 93 } 94 95 /// Initializing the sensitive data validator. 96 void initializeSensitiveDataValidator() 97 { 98 _sensitiveDataNames = [ 99 SecurityLevel.minimum : 100 [ 101 "password", 102 "cpr", "ssn", "social security", "socialsecurity", "social-security", 103 "social number", "social-number", "socialnumber", 104 "salary", "payment", 105 "money", "cash", "bank", "bank account", "bank-account", 106 "cc", "credit", "credit card", "credit-card", "creditcard", 107 "dc", "debit", "debit card", "debit-card", "debitcard", 108 "credit info", "creditinfo", "credit-info", 109 "debit info", "debitinfo", "debit-info", 110 "debt", "bill", 111 "insurance", "insurance number", "insurance-number" 112 ], 113 SecurityLevel.medium : 114 [ 115 "password", 116 "cpr", "ssn", "social security", "socialsecurity", "social-security", 117 "social number", "social-number", "socialnumber", 118 "address", 119 "email", "e-mail", "mail", "phone", "telephone", "salary", "payment", 120 "money", "cash", "bank", "bank account", "bank-account", 121 "cc", "credit", "credit card", "credit-card", "creditcard", 122 "dc", "debit", "debit card", "debit-card", "debitcard", 123 "credit info", "creditinfo", "credit-info", 124 "debit info", "debitinfo", "debit-info", 125 "debt", "bill", 126 "insurance", "insurance number", "insurance-number" 127 ], 128 SecurityLevel.maximum : 129 [ 130 "name", "account", "username", "password", "auth", "login", 131 "cpr", "ssn", "social security", "socialsecurity", "social-security", 132 "social number", "social-number", "socialnumber", 133 "age", "race", "politic", "address", "zip", "zipcode", "postal", "postalcode", 134 "job", "jobtitle", "occupation", "nick", "nickname", 135 "email", "e-mail", "mail", "phone", "telephone", "salary", "payment", 136 "money", "cash", "bank", "bank account", "bank-account", 137 "cc", "credit", "credit card", "credit-card", "creditcard", 138 "dc", "debit", "debit card", "debit-card", "debitcard", 139 "credit info", "creditinfo", "credit-info", 140 "debit info", "debitinfo", "debit-info", 141 "debt", "bill", 142 "ethnic", "ethnicity", 143 "insurance", "insurance number", "insurance-number" 144 ] 145 ]; 146 147 _sensitiveDataPatterns = [ 148 SecurityLevel.minimum : 149 [ 150 _amexCardRegex, 151 _BCGlobalRegex, 152 _carteBlancheCardRegex, 153 _dinersClubCardRegex, 154 _discoverCardRegex, 155 _instaPaymentCardRegex, 156 _JCBCardRegex, 157 _koreanLocalCardRegex, 158 _maestroCardRegex, 159 _mastercardRegex, 160 _soloCardRegex, 161 _unionPayCardRegex, 162 _visaCardRegex, 163 _visaMasterCardRegex, 164 _masterCard2016Regex, 165 _masterCardBinRegex, 166 _genericCardRegex, 167 168 _cprRegex, 169 _ssnRegex, 170 _ukInsuranceNumberRegex 171 ], 172 SecurityLevel.medium : 173 [ 174 _phoneNumberRegex, 175 176 _amexCardRegex, 177 _BCGlobalRegex, 178 _carteBlancheCardRegex, 179 _dinersClubCardRegex, 180 _discoverCardRegex, 181 _instaPaymentCardRegex, 182 _JCBCardRegex, 183 _koreanLocalCardRegex, 184 _maestroCardRegex, 185 _mastercardRegex, 186 _soloCardRegex, 187 _unionPayCardRegex, 188 _visaCardRegex, 189 _visaMasterCardRegex, 190 _masterCard2016Regex, 191 _masterCardBinRegex, 192 _genericCardRegex, 193 194 _cprRegex, 195 _ssnRegex, 196 _ukInsuranceNumberRegex 197 ], 198 SecurityLevel.maximum : 199 [ 200 _phoneNumberRegex, 201 202 _amexCardRegex, 203 _BCGlobalRegex, 204 _carteBlancheCardRegex, 205 _dinersClubCardRegex, 206 _discoverCardRegex, 207 _instaPaymentCardRegex, 208 _JCBCardRegex, 209 _koreanLocalCardRegex, 210 _maestroCardRegex, 211 _mastercardRegex, 212 _soloCardRegex, 213 _unionPayCardRegex, 214 _visaCardRegex, 215 _visaMasterCardRegex, 216 _masterCard2016Regex, 217 _masterCardBinRegex, 218 _genericCardRegex, 219 220 _cprRegex, 221 _ssnRegex, 222 _ukInsuranceNumberRegex 223 ] 224 ]; 225 226 updateRegexPattern(); 227 } 228 229 /// Updates the regex pattern for sensitive data patterns. 230 private void updateRegexPattern() 231 { 232 foreach (level,patterns; _sensitiveDataPatterns) 233 { 234 foreach (pattern; patterns) 235 { 236 _sensitiveDataRegexes[level] ~= regex(pattern); 237 } 238 } 239 } 240 241 /** 242 * Adds a sensitive data name. 243 * Params: 244 * name = The name to add. 245 * level = The security level to add the name to. 246 */ 247 void addSensitiveDataName(string name, SecurityLevel level) 248 { 249 _sensitiveDataNames[level] ~= name; 250 } 251 252 /** 253 * Adds a sensitive data pattern. 254 * Params: 255 * pattern = The pattern to add. 256 * level = The security level to add the pattern to. 257 * updateRegex = Boolean determining whether the validation regex should be updated. False by default to allow bulk-adds. 258 */ 259 void addSensitiveDataPattern(string pattern, SecurityLevel level, bool updateRegex = false) 260 { 261 _sensitiveDataPatterns[level] ~= pattern; 262 263 if (updateRegex) 264 { 265 updateRegexPattern(); 266 } 267 } 268 269 /** 270 * Removes a sensitive data name. 271 * Params: 272 * name = The name to remove. 273 * level = The security level to remove the name from. 274 */ 275 void removeSensitiveDataName(string name, SecurityLevel level) 276 { 277 _sensitiveDataNames[level] = _sensitiveDataNames[level].filter!(n => n != name).array; 278 } 279 280 /** 281 * Removes a sensitive data pattern. 282 * Params: 283 * pattern = The pattern to remove. 284 * level = The security level to remove the pattern from. 285 */ 286 void removeSensitiveDataPattern(string pattern, SecurityLevel level) 287 { 288 _sensitiveDataPatterns[level] = _sensitiveDataPatterns[level].filter!(p => p != pattern).array; 289 290 updateRegexPattern(); 291 } 292 293 /// Clears all sensitive data names. 294 void clearSensitiveDataNames() 295 { 296 _sensitiveDataNames.clear(); 297 } 298 299 /// Clears all sensitive data patterns. 300 void clearSensitiveDataPatterns() 301 { 302 _sensitiveDataPatterns.clear(); 303 } 304 305 /** 306 * Checks whether a specific string contains sensitive data. 307 * Params: 308 * data = The data to check. 309 * level = The security level for the validation 310 * Returns: 311 * True if the string contains sensitive data, false otherwise. 312 */ 313 bool hasSensitiveData(string data, SecurityLevel level) 314 { 315 if (!data || !data.length) 316 { 317 return false; 318 } 319 320 import diamond.core..string : splitIntoGroupedWords; 321 322 auto words = splitIntoGroupedWords(data); 323 324 foreach (word; words) 325 { 326 if (_sensitiveDataNames && level in _sensitiveDataNames && _sensitiveDataNames[level].length) 327 { 328 foreach (name; _sensitiveDataNames[level]) 329 { 330 if (word.canFind(name)) 331 { 332 return true; 333 } 334 } 335 } 336 337 if (_sensitiveDataPatterns && level in _sensitiveDataPatterns && level in _sensitiveDataRegexes && _sensitiveDataPatterns[level].length) 338 { 339 auto regexes = _sensitiveDataRegexes.get(level, null); 340 341 if (regexes && regexes.length) 342 { 343 foreach (regex; regexes) 344 { 345 auto regexResult = word.matchAll(regex); 346 347 if (!regexResult.empty) 348 { 349 return true; 350 } 351 } 352 } 353 } 354 } 355 356 return false; 357 } 358 359 /** 360 * Checks whether a specific string contains sensitive data. 361 * Params: 362 * data = The data to check. 363 * level = The security level for the validation 364 * Throws: 365 * SensitiveDataException when the string contains sensitive data. 366 */ 367 void validateSensitiveData(string data, SecurityLevel level) 368 { 369 if (hasSensitiveData(data, level)) 370 { 371 import diamond.errors.exceptions : SensitiveDataException; 372 373 throw new SensitiveDataException("The input contains sensitive data. Try to change security policies or exclude the sensitive data from the input."); 374 } 375 }