/work/workdir/UnpackedTarball/zxcvbn-c/zxcvbn.c
Line | Count | Source (jump to first uncovered line) |
1 | | /********************************************************************************** |
2 | | * C implementation of the zxcvbn password strength estimation method. |
3 | | * Copyright (c) 2015-2017 Tony Evans |
4 | | * |
5 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
6 | | * of this software and associated documentation files (the "Software"), to deal |
7 | | * in the Software without restriction, including without limitation the rights |
8 | | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
9 | | * copies of the Software, and to permit persons to whom the Software is |
10 | | * furnished to do so, subject to the following conditions: |
11 | | * |
12 | | * The above copyright notice and this permission notice shall be included in |
13 | | * all copies or substantial portions of the Software. |
14 | | * |
15 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
18 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
20 | | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
21 | | * THE SOFTWARE. |
22 | | * |
23 | | **********************************************************************************/ |
24 | | |
25 | | #include <zxcvbn.h> |
26 | | #include <ctype.h> |
27 | | #include <string.h> |
28 | | #include <stdint.h> |
29 | | #include <math.h> |
30 | | #include <float.h> |
31 | | |
32 | | /* printf */ |
33 | | #ifdef __cplusplus |
34 | | #include <cstdio> |
35 | | #else |
36 | | #include <stdio.h> |
37 | | #endif |
38 | | |
39 | | #ifdef USE_DICT_FILE |
40 | | #if defined(USE_FILE_IO) || !defined(__cplusplus) |
41 | | #include <stdio.h> |
42 | | #else |
43 | | #include <fstream> |
44 | | #endif |
45 | | #endif |
46 | | |
47 | | /* Minimum number of characters in a incrementing/decrementing sequence match */ |
48 | 0 | #define MIN_SEQUENCE_LEN 3 |
49 | | |
50 | | /* Maximum number of characters to perform full entropy calculation */ |
51 | | #ifndef ZXCVBN_DETAIL_LEN |
52 | 0 | #define ZXCVBN_DETAIL_LEN 100 |
53 | | #endif |
54 | | |
55 | | /* Year range for data matching */ |
56 | 0 | #define MIN_YEAR 1901 |
57 | 0 | #define MAX_YEAR 2050 |
58 | | |
59 | | /* Minimum number of characters in a spatial matching sequence */ |
60 | 0 | #define MIN_SPATIAL_LEN 3 |
61 | | |
62 | | /* Minimum number of characters in a repeat sequence match */ |
63 | 0 | #define MIN_REPEAT_LEN 2 |
64 | | |
65 | | /* Additional entropy to add when password is made of multiple matches. Use different |
66 | | * amounts depending on whether the match is at the end of the password, or in the |
67 | | * middle. If the match is at the begining then there is no additional entropy. |
68 | | */ |
69 | 0 | #define MULTI_END_ADDITION 1.0 |
70 | 0 | #define MULTI_MID_ADDITION 1.75 |
71 | | |
72 | | /*################################################################################* |
73 | | *################################################################################* |
74 | | * Begin utility function code |
75 | | *################################################################################* |
76 | | *################################################################################*/ |
77 | | |
78 | | /********************************************************************************** |
79 | | * Binomial coefficient function. Uses method described at |
80 | | * http://blog.plover.com/math/choose.html |
81 | | */ |
82 | | static double nCk(int n, int k) |
83 | 0 | { |
84 | 0 | int d; |
85 | 0 | double r; |
86 | 0 | if (k > n) |
87 | 0 | return 0.0; |
88 | 0 | if (!k) |
89 | 0 | return 1.0; |
90 | 0 | r = 1.0; |
91 | 0 | for(d = 1; d <= k; ++d) |
92 | 0 | { |
93 | 0 | r *= n--; |
94 | 0 | r /= d; |
95 | 0 | } |
96 | 0 | return r; |
97 | 0 | } |
98 | | |
99 | | /********************************************************************************** |
100 | | * Binary search function to find a character in a string. |
101 | | * Parameters: |
102 | | * Ch The character to find |
103 | | * Ents The string to search |
104 | | * NumEnts The number character groups in the string Ents |
105 | | * SizeEnt The size of each character group. |
106 | | * Returns a pointer to the found character, or null if not found. |
107 | | */ |
108 | | static const uint8_t *CharBinSearch(uint8_t Ch, const uint8_t *Ents, unsigned int NumEnts, unsigned int SizeEnt) |
109 | 0 | { |
110 | 0 | while(NumEnts > 0) |
111 | 0 | { |
112 | 0 | const uint8_t *Mid = Ents + (NumEnts >> 1) * SizeEnt; |
113 | 0 | int Dif = Ch - *Mid; |
114 | 0 | if (!Dif) |
115 | 0 | { |
116 | 0 | return Mid; |
117 | 0 | } |
118 | 0 | if (Dif > 0) |
119 | 0 | { |
120 | 0 | Ents = Mid + SizeEnt; |
121 | 0 | --NumEnts; |
122 | 0 | } |
123 | 0 | NumEnts /= 2; |
124 | 0 | } |
125 | 0 | return 0; |
126 | 0 | } |
127 | | |
128 | | /********************************************************************************** |
129 | | * Calculate potential number of different characters in the passed string. |
130 | | * Parameters: |
131 | | * Str The string of characters |
132 | | * Len The maximum number of characters to scan |
133 | | * Returns the potential number of different characters in the string. |
134 | | */ |
135 | | static int Cardinality(const uint8_t *Str, int Len) |
136 | 0 | { |
137 | 0 | int Card=0, Types=0; |
138 | 0 | int c; |
139 | 0 | while(Len > 0) |
140 | 0 | { |
141 | 0 | c = *Str++ & 0xFF; |
142 | 0 | if (!c) |
143 | 0 | break; |
144 | 0 | if (islower(c)) Types |= 1; /* Lowercase letter */ |
145 | 0 | else if (isupper(c)) Types |= 2; /* Uppercase letter */ |
146 | 0 | else if (isdigit(c)) Types |= 4; /* Numeric digit */ |
147 | 0 | else if (c <= 0x7F) Types |= 8; /* Punctuation character */ |
148 | 0 | else Types |= 16; /* Other (Unicode?) */ |
149 | 0 | --Len; |
150 | 0 | } |
151 | 0 | if (Types & 1) Card += 26; |
152 | 0 | if (Types & 2) Card += 26; |
153 | 0 | if (Types & 4) Card += 10; |
154 | 0 | if (Types & 8) Card += 33; |
155 | 0 | if (Types & 16) Card += 100; |
156 | 0 | return Card; |
157 | 0 | } |
158 | | |
159 | | /********************************************************************************** |
160 | | * Allocate a ZxcMatch_t struct, clear it to zero |
161 | | */ |
162 | | static ZxcMatch_t *AllocMatch() |
163 | 0 | { |
164 | 0 | ZxcMatch_t *p = MallocFn(ZxcMatch_t, 1); |
165 | 0 | memset(p, 0, sizeof *p); |
166 | 0 | return p; |
167 | 0 | } |
168 | | |
169 | | /********************************************************************************** |
170 | | * Add new match struct to linked list of matches. List ordered with shortest at |
171 | | * head of list. Note: passed new match struct in parameter Nu may be de allocated. |
172 | | */ |
173 | | static void AddResult(ZxcMatch_t **HeadRef, ZxcMatch_t *Nu, int MaxLen) |
174 | 0 | { |
175 | | /* Adjust the entropy to be used for calculations depending on whether the passed match is |
176 | | * at the begining, middle or end of the password |
177 | | */ |
178 | 0 | if (Nu->Begin) |
179 | 0 | { |
180 | 0 | if (Nu->Length >= MaxLen) |
181 | 0 | Nu->MltEnpy = Nu->Entrpy + MULTI_END_ADDITION * log(2.0); |
182 | 0 | else |
183 | 0 | Nu->MltEnpy = Nu->Entrpy + MULTI_MID_ADDITION * log(2.0); |
184 | 0 | } |
185 | 0 | else |
186 | 0 | Nu->MltEnpy = Nu->Entrpy; |
187 | | |
188 | | /* Find the correct insert point */ |
189 | 0 | while(*HeadRef && ((*HeadRef)->Length < Nu->Length)) |
190 | 0 | HeadRef = &((*HeadRef)->Next); |
191 | | |
192 | | /* Add new entry or replace existing */ |
193 | 0 | if (*HeadRef && ((*HeadRef)->Length == Nu->Length)) |
194 | 0 | { |
195 | | /* New entry has same length as existing, so one of them needs discarding */ |
196 | 0 | if ((*HeadRef)->MltEnpy <= Nu->MltEnpy) |
197 | 0 | { |
198 | | /* Existing entry has lower entropy - keep it, discard new entry */ |
199 | 0 | FreeFn(Nu); |
200 | 0 | } |
201 | 0 | else |
202 | 0 | { |
203 | | /* New entry has lower entropy - replace existing entry */ |
204 | 0 | Nu->Next = (*HeadRef)->Next; |
205 | 0 | FreeFn(*HeadRef); |
206 | 0 | *HeadRef = Nu; |
207 | 0 | } |
208 | 0 | } |
209 | 0 | else |
210 | 0 | { |
211 | | /* New entry has different length, so add it */ |
212 | 0 | Nu->Next = *HeadRef; |
213 | 0 | *HeadRef = Nu; |
214 | 0 | } |
215 | 0 | } |
216 | | |
217 | | /********************************************************************************** |
218 | | * See if the match is repeated. If it is then add a new repeated match to the results. |
219 | | */ |
220 | | static void AddMatchRepeats(ZxcMatch_t **Result, ZxcMatch_t *Match, const uint8_t *Passwd, int MaxLen) |
221 | 0 | { |
222 | 0 | int Len = Match->Length; |
223 | 0 | const uint8_t *Rpt = Passwd + Len; |
224 | 0 | int RepeatCount = 2; |
225 | |
|
226 | 0 | while(MaxLen >= (Len * RepeatCount)) |
227 | 0 | { |
228 | 0 | if (strncmp((const char *)Passwd, (const char *)Rpt, Len) == 0) |
229 | 0 | { |
230 | | /* Found a repeat */ |
231 | 0 | ZxcMatch_t *p = AllocMatch(); |
232 | 0 | p->Entrpy = Match->Entrpy + log(RepeatCount); |
233 | 0 | p->Type = (ZxcTypeMatch_t)(Match->Type + MULTIPLE_MATCH); |
234 | 0 | p->Length = Len * RepeatCount; |
235 | 0 | p->Begin = Match->Begin; |
236 | 0 | AddResult(Result, p, MaxLen); |
237 | 0 | } |
238 | 0 | else |
239 | 0 | break; |
240 | 0 | ++RepeatCount; |
241 | 0 | Rpt += Len; |
242 | 0 | } |
243 | 0 | } |
244 | | |
245 | | /*################################################################################* |
246 | | *################################################################################* |
247 | | * Begin dictionary matching code |
248 | | *################################################################################* |
249 | | *################################################################################*/ |
250 | | |
251 | | #ifdef USE_DICT_FILE |
252 | | /* Use dictionary data from file */ |
253 | | |
254 | | #if defined(USE_FILE_IO) || !defined(__cplusplus) |
255 | | /* Use the FILE streams from stdio.h */ |
256 | | |
257 | | typedef FILE *FileHandle; |
258 | | |
259 | | #define MyOpenFile(f, name) (f = fopen(name, "rb")) |
260 | | #define MyReadFile(f, buf, bytes) (fread(buf, 1, bytes, f) == (bytes)) |
261 | | #define MyCloseFile(f) fclose(f) |
262 | | |
263 | | #else |
264 | | |
265 | | /* Use the C++ iostreams */ |
266 | | typedef std::ifstream FileHandle; |
267 | | |
268 | | static inline void MyOpenFile(FileHandle & f, const char *Name) |
269 | | { |
270 | | f.open(Name, std::ifstream::in | std::ifstream::binary); |
271 | | } |
272 | | static inline bool MyReadFile(FileHandle & f, void *Buf, unsigned int Num) |
273 | | { |
274 | | return (bool)f.read((char *)Buf, Num); |
275 | | } |
276 | | static inline void MyCloseFile(FileHandle & f) |
277 | | { |
278 | | f.close(); |
279 | | } |
280 | | |
281 | | #endif |
282 | | |
283 | | /* Include file contains the CRC of the dictionary data file. Used to detect corruption */ |
284 | | /* of the file. */ |
285 | | #include "dict-crc.h" |
286 | | |
287 | | #define MAX_DICT_FILE_SIZE (100+WORD_FILE_SIZE) |
288 | | #define CHK_INIT 0xffffffffffffffffULL |
289 | | |
290 | | /* Static table used for the crc implementation. */ |
291 | | static const uint64_t CrcTable[16] = |
292 | | { |
293 | | 0x0000000000000000ULL, 0x7d08ff3b88be6f81ULL, 0xfa11fe77117cdf02ULL, 0x8719014c99c2b083ULL, |
294 | | 0xdf7adabd7a6e2d6fULL, 0xa2722586f2d042eeULL, 0x256b24ca6b12f26dULL, 0x5863dbf1e3ac9decULL, |
295 | | 0x95ac9329ac4bc9b5ULL, 0xe8a46c1224f5a634ULL, 0x6fbd6d5ebd3716b7ULL, 0x12b5926535897936ULL, |
296 | | 0x4ad64994d625e4daULL, 0x37deb6af5e9b8b5bULL, 0xb0c7b7e3c7593bd8ULL, 0xcdcf48d84fe75459ULL |
297 | | }; |
298 | | |
299 | | static const unsigned int MAGIC = 'z' + ('x'<< 8) + ('c' << 16) + ('v' << 24); |
300 | | |
301 | | static unsigned int NumNodes, NumChildLocs, NumRanks, NumWordEnd, NumChildMaps; |
302 | | static unsigned int SizeChildMapEntry, NumLargeCounts, NumSmallCounts, SizeCharSet; |
303 | | |
304 | | static unsigned int *DictNodes; |
305 | | static uint8_t *WordEndBits; |
306 | | static unsigned int *ChildLocs; |
307 | | static unsigned short *Ranks; |
308 | | static uint8_t *ChildMap; |
309 | | static uint8_t *EndCountLge; |
310 | | static uint8_t *EndCountSml; |
311 | | static char *CharSet; |
312 | | |
313 | | /********************************************************************************** |
314 | | * Calculate the CRC-64 of passed data. |
315 | | * Parameters: |
316 | | * Crc The initial or previous CRC value |
317 | | * v Pointer to the data to add to CRC calculation |
318 | | * Len Length of the passed data |
319 | | * Returns the updated CRC value. |
320 | | */ |
321 | | static uint64_t CalcCrc64(uint64_t Crc, const void *v, unsigned int Len) |
322 | | { |
323 | | const uint8_t *Data = (const unsigned char *)v; |
324 | | while(Len--) |
325 | | { |
326 | | Crc = CrcTable[(Crc ^ (*Data >> 0)) & 0x0f] ^ (Crc >> 4); |
327 | | Crc = CrcTable[(Crc ^ (*Data >> 4)) & 0x0f] ^ (Crc >> 4); |
328 | | ++Data; |
329 | | } |
330 | | return Crc; |
331 | | } |
332 | | |
333 | | /********************************************************************************** |
334 | | * Read the dictionary data from file. |
335 | | * Parameters: |
336 | | * Filename Name of the file to read. |
337 | | * Returns 1 on success, 0 on error |
338 | | */ |
339 | | int ZxcvbnInit(const char *Filename) |
340 | | { |
341 | | FileHandle f; |
342 | | uint64_t Crc = CHK_INIT; |
343 | | if (DictNodes) |
344 | | return 1; |
345 | | MyOpenFile(f, Filename); |
346 | | if (f) |
347 | | { |
348 | | unsigned int i, DictSize; |
349 | | |
350 | | /* Get magic number */ |
351 | | if (!MyReadFile(f, &i, sizeof i)) |
352 | | i = 0; |
353 | | |
354 | | /* Get header data */ |
355 | | if (!MyReadFile(f, &NumNodes, sizeof NumNodes)) |
356 | | i = 0; |
357 | | if (!MyReadFile(f, &NumChildLocs, sizeof NumChildLocs)) |
358 | | i = 0; |
359 | | if (!MyReadFile(f, &NumRanks, sizeof NumRanks)) |
360 | | i = 0; |
361 | | if (!MyReadFile(f, &NumWordEnd, sizeof NumWordEnd)) |
362 | | i = 0; |
363 | | if (!MyReadFile(f, &NumChildMaps, sizeof NumChildMaps)) |
364 | | i = 0; |
365 | | if (!MyReadFile(f, &SizeChildMapEntry, sizeof SizeChildMapEntry)) |
366 | | i = 0; |
367 | | if (!MyReadFile(f, &NumLargeCounts, sizeof NumLargeCounts)) |
368 | | i = 0; |
369 | | if (!MyReadFile(f, &NumSmallCounts, sizeof NumSmallCounts)) |
370 | | i = 0; |
371 | | if (!MyReadFile(f, &SizeCharSet, sizeof SizeCharSet)) |
372 | | i = 0; |
373 | | |
374 | | /* Validate the header data */ |
375 | | if (NumNodes >= (1<<17)) |
376 | | i = 1; |
377 | | if (NumChildLocs >= (1<<BITS_CHILD_MAP_INDEX)) |
378 | | i = 2; |
379 | | if (NumChildMaps >= (1<<BITS_CHILD_PATT_INDEX)) |
380 | | i = 3; |
381 | | if ((SizeChildMapEntry*8) < SizeCharSet) |
382 | | i = 4; |
383 | | if (NumLargeCounts >= (1<<9)) |
384 | | i = 5; |
385 | | if (NumSmallCounts != NumNodes) |
386 | | i = 6; |
387 | | |
388 | | if (i != MAGIC) |
389 | | { |
390 | | MyCloseFile(f); |
391 | | return 0; |
392 | | } |
393 | | Crc = CalcCrc64(Crc, &i, sizeof i); |
394 | | Crc = CalcCrc64(Crc, &NumNodes, sizeof NumNodes); |
395 | | Crc = CalcCrc64(Crc, &NumChildLocs, sizeof NumChildLocs); |
396 | | Crc = CalcCrc64(Crc, &NumRanks, sizeof NumRanks); |
397 | | Crc = CalcCrc64(Crc, &NumWordEnd, sizeof NumWordEnd); |
398 | | Crc = CalcCrc64(Crc, &NumChildMaps, sizeof NumChildMaps); |
399 | | Crc = CalcCrc64(Crc, &SizeChildMapEntry, sizeof SizeChildMapEntry); |
400 | | Crc = CalcCrc64(Crc, &NumLargeCounts, sizeof NumLargeCounts); |
401 | | Crc = CalcCrc64(Crc, &NumSmallCounts, sizeof NumSmallCounts); |
402 | | Crc = CalcCrc64(Crc, &SizeCharSet, sizeof SizeCharSet); |
403 | | |
404 | | DictSize = NumNodes*sizeof(*DictNodes) + NumChildLocs*sizeof(*ChildLocs) + NumRanks*sizeof(*Ranks) + |
405 | | NumWordEnd + NumChildMaps*SizeChildMapEntry + NumLargeCounts + NumSmallCounts + SizeCharSet; |
406 | | if (DictSize < MAX_DICT_FILE_SIZE) |
407 | | { |
408 | | DictNodes = MallocFn(unsigned int, DictSize / sizeof(unsigned int) + 1); |
409 | | if (!MyReadFile(f, DictNodes, DictSize)) |
410 | | { |
411 | | FreeFn(DictNodes); |
412 | | DictNodes = 0; |
413 | | } |
414 | | } |
415 | | MyCloseFile(f); |
416 | | |
417 | | if (!DictNodes) |
418 | | return 0; |
419 | | /* Check crc */ |
420 | | Crc = CalcCrc64(Crc, DictNodes, DictSize); |
421 | | if (memcmp(&Crc, WordCheck, sizeof Crc)) |
422 | | { |
423 | | /* File corrupted */ |
424 | | FreeFn(DictNodes); |
425 | | DictNodes = 0; |
426 | | return 0; |
427 | | } |
428 | | fflush(stdout); |
429 | | /* Set pointers to the data */ |
430 | | ChildLocs = DictNodes + NumNodes; |
431 | | Ranks = (unsigned short *)(ChildLocs + NumChildLocs); |
432 | | WordEndBits = (unsigned char *)(Ranks + NumRanks); |
433 | | ChildMap = (unsigned char*)(WordEndBits + NumWordEnd); |
434 | | EndCountLge = ChildMap + NumChildMaps*SizeChildMapEntry; |
435 | | EndCountSml = EndCountLge + NumLargeCounts; |
436 | | CharSet = (char *)EndCountSml + NumSmallCounts; |
437 | | CharSet[SizeCharSet] = 0; |
438 | | return 1; |
439 | | } |
440 | | return 0; |
441 | | } |
442 | | /********************************************************************************** |
443 | | * Free the data allocated by ZxcvbnInit(). |
444 | | */ |
445 | | void ZxcvbnUnInit() |
446 | | { |
447 | | if (DictNodes) |
448 | | FreeFn(DictNodes); |
449 | | DictNodes = 0; |
450 | | } |
451 | | |
452 | | #else |
453 | | |
454 | | /* Include the source file containing the dictionary data */ |
455 | | #include "dict-src.h" |
456 | | |
457 | | #endif |
458 | | |
459 | | /********************************************************************************** |
460 | | * Leet conversion strings |
461 | | */ |
462 | | /* String of normal chars that could be given as leet chars in the password */ |
463 | | static const uint8_t L33TChr[] = "abcegilostxz"; |
464 | | |
465 | | /* String of leet,normal,normal char triples. Used to convert supplied leet char to normal. */ |
466 | | static const uint8_t L33TCnv[] = "!i $s %x (c +t 0o 1il2z 3e 4a 5s 6g 7lt8b 9g <c @a [c {c |il"; |
467 | 0 | #define LEET_NORM_MAP_SIZE 3 |
468 | | |
469 | | /* Struct holding additional data on the word match */ |
470 | | typedef struct |
471 | | { |
472 | | int Rank; /* Rank of word in dictionary */ |
473 | | int Caps; /* Number of capital letters */ |
474 | | int Lower; /* Number of lower case letters */ |
475 | | int NumLeet; /* Total number of leeted characters */ |
476 | | uint8_t Leeted[sizeof L33TChr]; /* Number of leeted chars for each char found in L33TChr */ |
477 | | uint8_t UnLeet[sizeof L33TChr]; /* Number of normal chars for each char found in L33TChr */ |
478 | | } DictMatchInfo_t; |
479 | | |
480 | | /* Struct holding working data for the word match */ |
481 | | typedef struct |
482 | | { |
483 | | uint32_t StartLoc; |
484 | | int Ordinal; |
485 | | int PwdLength; |
486 | | int Begin; |
487 | | int Caps; |
488 | | int Lower; |
489 | | int NumPossChrs; |
490 | | uint8_t Leeted[sizeof L33TChr]; |
491 | | uint8_t UnLeet[sizeof L33TChr]; |
492 | | uint8_t LeetCnv[sizeof L33TCnv / LEET_NORM_MAP_SIZE + 1]; |
493 | | uint8_t First; |
494 | | uint8_t PossChars[CHARSET_SIZE]; |
495 | | } DictWork_t; |
496 | | |
497 | | /********************************************************************************** |
498 | | * Given a map entry create a string of all possible characters for following to |
499 | | * a child node |
500 | | */ |
501 | | static int ListPossibleChars(uint8_t *List, const uint8_t *Map) |
502 | 0 | { |
503 | 0 | unsigned int i, j, k; |
504 | 0 | int Len = 0; |
505 | 0 | for(k = i = 0; i < SizeChildMapEntry; ++i, ++Map) |
506 | 0 | { |
507 | 0 | if (!*Map) |
508 | 0 | { |
509 | 0 | k += 8; |
510 | 0 | continue; |
511 | 0 | } |
512 | 0 | for(j = 0; j < 8; ++j) |
513 | 0 | { |
514 | 0 | if (*Map & (1<<j)) |
515 | 0 | { |
516 | 0 | *List++ = CharSet[k]; |
517 | 0 | ++Len; |
518 | 0 | } |
519 | 0 | ++k; |
520 | 0 | } |
521 | 0 | } |
522 | 0 | *List=0; |
523 | 0 | return Len; |
524 | 0 | } |
525 | | |
526 | | /********************************************************************************** |
527 | | * Increment count of each char that could be leeted. |
528 | | */ |
529 | | static void AddLeetChr(uint8_t c, int IsLeet, uint8_t *Leeted, uint8_t *UnLeet) |
530 | 0 | { |
531 | 0 | const uint8_t *p = CharBinSearch(c, L33TChr, sizeof L33TChr - 1, 1); |
532 | 0 | if (p) |
533 | 0 | { |
534 | 0 | int i = p - L33TChr; |
535 | 0 | if (IsLeet > 0) |
536 | 0 | { |
537 | 0 | Leeted[i] += 1; |
538 | 0 | } |
539 | 0 | else if (IsLeet < 0) |
540 | 0 | { |
541 | 0 | Leeted[i] += 1; |
542 | 0 | UnLeet[i] -= 1; |
543 | 0 | } |
544 | 0 | else |
545 | 0 | { |
546 | 0 | UnLeet[i] += 1; |
547 | 0 | } |
548 | 0 | } |
549 | 0 | } |
550 | | |
551 | | /********************************************************************************** |
552 | | * Given details of a word match, update it with the entropy (as natural log of |
553 | | * number of possiblities) |
554 | | */ |
555 | | static void DictionaryEntropy(ZxcMatch_t *m, DictMatchInfo_t *Extra, const uint8_t *Pwd) |
556 | 0 | { |
557 | 0 | double e = 0.0; |
558 | | /* Add allowance for uppercase letters */ |
559 | 0 | if (Extra->Caps) |
560 | 0 | { |
561 | 0 | if (Extra->Caps == m->Length) |
562 | 0 | { |
563 | | /* All uppercase, common case so only 1 bit */ |
564 | 0 | e += log(2.0); |
565 | 0 | } |
566 | 0 | else if ((Extra->Caps == 1) && (isupper(*Pwd) || isupper(Pwd[m->Length - 1]))) |
567 | 0 | { |
568 | | /* Only first or last uppercase, also common so only 1 bit */ |
569 | 0 | e += log(2.0); |
570 | 0 | } |
571 | 0 | else |
572 | 0 | { |
573 | | /* Get number of combinations of lowercase, uppercase letters */ |
574 | 0 | int Up = Extra->Caps; |
575 | 0 | int Lo = Extra->Lower; |
576 | 0 | int i = Up; |
577 | 0 | if (i > Lo) |
578 | 0 | i = Lo; |
579 | 0 | for(Lo += Up; i >= 0; --i) |
580 | 0 | e += nCk(Lo, i); |
581 | 0 | if (e > 0.0) |
582 | 0 | e = log(e); |
583 | 0 | } |
584 | 0 | } |
585 | | /* Add allowance for using leet substitution */ |
586 | 0 | if (Extra->NumLeet) |
587 | 0 | { |
588 | 0 | int i; |
589 | 0 | double d = 0.0; |
590 | 0 | for(i = sizeof Extra->Leeted - 1; i >= 0; --i) |
591 | 0 | { |
592 | 0 | int Sb = Extra->Leeted[i]; |
593 | 0 | if (Sb) |
594 | 0 | { |
595 | 0 | int Un = Extra->UnLeet[i]; |
596 | 0 | int j = m->Length - Extra->NumLeet; |
597 | 0 | if ((j >= 0) && (Un > j)) |
598 | 0 | Un = j; |
599 | 0 | j = Sb; |
600 | 0 | if (j > Un) |
601 | 0 | j = Un; |
602 | 0 | for(Un += Sb; j >= 0; --j) |
603 | 0 | { |
604 | 0 | double z = nCk(Un, j); |
605 | 0 | d += z; |
606 | 0 | } |
607 | 0 | } |
608 | 0 | } |
609 | 0 | if (d > 0.0) |
610 | 0 | d = log(d); |
611 | 0 | if (d < log(2.0)) |
612 | 0 | d = log(2.0); |
613 | 0 | e += d; |
614 | 0 | } |
615 | | /* Add entropy due to word's rank */ |
616 | 0 | e += log((double)Extra->Rank); |
617 | 0 | m->Entrpy = e; |
618 | 0 | } |
619 | | |
620 | | /********************************************************************************** |
621 | | * Function that does the word matching |
622 | | */ |
623 | | static void DoDictMatch(const uint8_t *Passwd, int Start, int MaxLen, DictWork_t *Wrk, ZxcMatch_t **Result, DictMatchInfo_t *Extra, int Lev) |
624 | 0 | { |
625 | 0 | int Len; |
626 | 0 | uint8_t TempLeet[LEET_NORM_MAP_SIZE]; |
627 | 0 | int Ord = Wrk->Ordinal; |
628 | 0 | int Caps = Wrk->Caps; |
629 | 0 | int Lower = Wrk->Lower; |
630 | 0 | unsigned int NodeLoc = Wrk->StartLoc; |
631 | 0 | uint8_t *PossChars = Wrk->PossChars; |
632 | 0 | int NumPossChrs = Wrk->NumPossChrs; |
633 | 0 | const uint8_t *Pwd = Passwd; |
634 | 0 | uint32_t NodeData = DictNodes[NodeLoc]; |
635 | 0 | Passwd += Start; |
636 | 0 | for(Len = 0; *Passwd && (Len < MaxLen); ++Len, ++Passwd) |
637 | 0 | { |
638 | 0 | uint8_t c; |
639 | 0 | int w, x, y, z; |
640 | 0 | const uint8_t *q; |
641 | 0 | z = 0; |
642 | 0 | if (!Len && Wrk->First) |
643 | 0 | { |
644 | 0 | c = Wrk->First; |
645 | 0 | } |
646 | 0 | else |
647 | 0 | { |
648 | | /* Get char and set of possible chars at current point in word. */ |
649 | 0 | const uint8_t *Bmap; |
650 | 0 | c = *Passwd; |
651 | 0 | Bmap = ChildMap + (NodeData & ((1<<BITS_CHILD_PATT_INDEX)-1)) * SizeChildMapEntry; |
652 | 0 | NumPossChrs = ListPossibleChars(PossChars, Bmap); |
653 | | |
654 | | /* Make it lowercase and update lowercase, uppercase counts */ |
655 | 0 | if (isupper(c)) |
656 | 0 | { |
657 | 0 | c = tolower(c); |
658 | 0 | ++Caps; |
659 | 0 | } |
660 | 0 | else if (islower(c)) |
661 | 0 | { |
662 | 0 | ++Lower; |
663 | 0 | } |
664 | | /* See if current char is a leet and can be converted */ |
665 | 0 | q = CharBinSearch(c, L33TCnv, sizeof L33TCnv / LEET_NORM_MAP_SIZE, LEET_NORM_MAP_SIZE); |
666 | 0 | if (q) |
667 | 0 | { |
668 | | /* Found, see if used before */ |
669 | 0 | unsigned int j; |
670 | 0 | unsigned int i = (q - L33TCnv ) / LEET_NORM_MAP_SIZE; |
671 | 0 | if (Wrk->LeetCnv[i]) |
672 | 0 | { |
673 | | /* Used before, so limit characters to try */ |
674 | 0 | TempLeet[0] = c; |
675 | 0 | TempLeet[1] = Wrk->LeetCnv[i]; |
676 | 0 | TempLeet[2] = 0; |
677 | 0 | q = TempLeet; |
678 | 0 | } |
679 | 0 | for(j = 0; (*q > ' ') && (j < LEET_NORM_MAP_SIZE); ++j, ++q) |
680 | 0 | { |
681 | 0 | const uint8_t *r = CharBinSearch(*q, PossChars, NumPossChrs, 1); |
682 | 0 | if (r) |
683 | 0 | { |
684 | | /* valid conversion from leet */ |
685 | 0 | DictWork_t w; |
686 | 0 | w = *Wrk; |
687 | 0 | w.StartLoc = NodeLoc; |
688 | 0 | w.Ordinal = Ord; |
689 | 0 | w.PwdLength += Len; |
690 | 0 | w.Caps = Caps; |
691 | 0 | w.Lower = Lower; |
692 | 0 | w.First = *r; |
693 | 0 | w.NumPossChrs = NumPossChrs; |
694 | 0 | memcpy(w.PossChars, PossChars, sizeof w.PossChars); |
695 | 0 | if (j) |
696 | 0 | { |
697 | 0 | w.LeetCnv[i] = *r; |
698 | 0 | AddLeetChr(*r, -1, w.Leeted, w.UnLeet); |
699 | 0 | } |
700 | 0 | DoDictMatch(Pwd, Passwd - Pwd, MaxLen - Len, &w, Result, Extra, Lev+1); |
701 | 0 | } |
702 | 0 | } |
703 | 0 | return; |
704 | 0 | } |
705 | 0 | } |
706 | 0 | q = CharBinSearch(c, PossChars, NumPossChrs, 1); |
707 | 0 | if (q) |
708 | 0 | { |
709 | | /* Found the char as a normal char */ |
710 | 0 | if (CharBinSearch(c, L33TChr, sizeof L33TChr - 1, 1)) |
711 | 0 | { |
712 | | /* Char matches, but also a normal equivalent to a leet char */ |
713 | 0 | AddLeetChr(c, 0, Wrk->Leeted, Wrk->UnLeet); |
714 | 0 | } |
715 | 0 | } |
716 | 0 | if (!q) |
717 | 0 | { |
718 | | /* No match for char - return */ |
719 | 0 | return; |
720 | 0 | } |
721 | | /* Add all the end counts of the child nodes before the one that matches */ |
722 | 0 | x = (q - Wrk->PossChars); |
723 | 0 | y = (NodeData >> BITS_CHILD_PATT_INDEX) & ((1 << BITS_CHILD_MAP_INDEX) - 1); |
724 | 0 | NodeLoc = ChildLocs[x+y]; |
725 | 0 | for(w=0; w<x; ++w) |
726 | 0 | { |
727 | 0 | unsigned int Cloc = ChildLocs[w+y]; |
728 | 0 | z = EndCountSml[Cloc]; |
729 | 0 | if (Cloc < NumLargeCounts) |
730 | 0 | z += EndCountLge[Cloc]*256; |
731 | 0 | Ord += z; |
732 | 0 | } |
733 | | |
734 | | /* Move to next node */ |
735 | 0 | NodeData = DictNodes[NodeLoc]; |
736 | 0 | if (WordEndBits[NodeLoc >> 3] & (1<<(NodeLoc & 7))) |
737 | 0 | { |
738 | | /* Word matches, save result */ |
739 | 0 | unsigned int v; |
740 | 0 | ZxcMatch_t *p; |
741 | 0 | v = Ranks[Ord]; |
742 | 0 | if (v & (1<<15)) |
743 | 0 | v = (v & ((1 << 15) - 1)) * 4 + (1 << 15); |
744 | 0 | Extra->Caps = Caps; |
745 | 0 | Extra->Rank = v; |
746 | 0 | Extra->Lower = Lower; |
747 | 0 | for(x = 0, y = sizeof Extra->Leeted - 1; y >= 0; --y) |
748 | 0 | x += Wrk->Leeted[y]; |
749 | 0 | Extra->NumLeet = x; |
750 | |
|
751 | 0 | memcpy(Extra->UnLeet, Wrk->UnLeet, sizeof Extra->UnLeet); |
752 | 0 | memcpy(Extra->Leeted, Wrk->Leeted, sizeof Extra->Leeted); |
753 | |
|
754 | 0 | p = AllocMatch(); |
755 | 0 | if (x) |
756 | 0 | p->Type = DICT_LEET_MATCH; |
757 | 0 | else |
758 | 0 | p->Type = DICTIONARY_MATCH; |
759 | 0 | p->Length = Wrk->PwdLength + Len + 1; |
760 | 0 | p->Begin = Wrk->Begin; |
761 | 0 | DictionaryEntropy(p, Extra, Pwd); |
762 | 0 | AddMatchRepeats(Result, p, Pwd, MaxLen); |
763 | 0 | AddResult(Result, p, MaxLen); |
764 | 0 | ++Ord; |
765 | 0 | } |
766 | 0 | } |
767 | 0 | } |
768 | | |
769 | | /********************************************************************************** |
770 | | * Try to match password part with user supplied dictionary words |
771 | | * Parameters: |
772 | | * Result Pointer head of linked list used to store results |
773 | | * Words Array of pointers to dictionary words |
774 | | * Passwd The start of the password |
775 | | * Start Where in the password to start attempting to match |
776 | | * MaxLen Maximum number characters to consider |
777 | | */ |
778 | | static void UserMatch(ZxcMatch_t **Result, const char *Words[], const uint8_t *Passwd, int Start, int MaxLen) |
779 | 0 | { |
780 | 0 | int Rank; |
781 | 0 | if (!Words) |
782 | 0 | return; |
783 | 0 | Passwd += Start; |
784 | 0 | for(Rank = 0; Words[Rank]; ++Rank) |
785 | 0 | { |
786 | 0 | DictMatchInfo_t Extra; |
787 | 0 | uint8_t LeetChr[sizeof L33TCnv / LEET_NORM_MAP_SIZE + 1]; |
788 | 0 | uint8_t TempLeet[3]; |
789 | 0 | int Len = 0; |
790 | 0 | int Caps = 0; |
791 | 0 | int Lowers = 0; |
792 | 0 | int Leets = 0; |
793 | 0 | const uint8_t *Wrd = (const uint8_t *)(Words[Rank]); |
794 | 0 | const uint8_t *Pwd = Passwd; |
795 | 0 | memset(Extra.Leeted, 0, sizeof Extra.Leeted); |
796 | 0 | memset(Extra.UnLeet, 0, sizeof Extra.UnLeet); |
797 | 0 | memset(LeetChr, 0, sizeof LeetChr); |
798 | 0 | while(*Wrd) |
799 | 0 | { |
800 | 0 | const uint8_t *q; |
801 | 0 | uint8_t d = tolower(*Wrd++); |
802 | 0 | uint8_t c = *Pwd++; |
803 | 0 | if (isupper(c)) |
804 | 0 | { |
805 | 0 | c = tolower(c); |
806 | 0 | ++Caps; |
807 | 0 | } |
808 | 0 | else if (islower(c)) |
809 | 0 | { |
810 | 0 | ++Lowers; |
811 | 0 | } |
812 | | /* See if current char is a leet and can be converted */ |
813 | 0 | q = CharBinSearch(c, L33TCnv, sizeof L33TCnv / LEET_NORM_MAP_SIZE, LEET_NORM_MAP_SIZE); |
814 | 0 | if (q) |
815 | 0 | { |
816 | | /* Found, see if used before */ |
817 | 0 | unsigned int j; |
818 | 0 | unsigned int i = (q - L33TCnv ) / LEET_NORM_MAP_SIZE; |
819 | 0 | if (LeetChr[i]) |
820 | 0 | { |
821 | | /* Used before, so limit characters to try */ |
822 | 0 | TempLeet[0] = c; |
823 | 0 | TempLeet[1] = LeetChr[i]; |
824 | 0 | TempLeet[2] = 0; |
825 | 0 | q = TempLeet; |
826 | 0 | } |
827 | 0 | c = d+1; |
828 | 0 | for(j = 0; (*q > ' ') && (j < LEET_NORM_MAP_SIZE); ++j, ++q) |
829 | 0 | { |
830 | 0 | if (d == *q) |
831 | 0 | { |
832 | 0 | c = d; |
833 | 0 | if (i) |
834 | 0 | { |
835 | 0 | LeetChr[i] = c; |
836 | 0 | AddLeetChr(c, 1, Extra.Leeted, Extra.UnLeet); |
837 | 0 | ++Leets; |
838 | 0 | } |
839 | 0 | break; |
840 | 0 | } |
841 | 0 | } |
842 | 0 | if (c != d) |
843 | 0 | { |
844 | 0 | Len = 0; |
845 | 0 | break; |
846 | 0 | } |
847 | 0 | } |
848 | 0 | else if (c == d) |
849 | 0 | { |
850 | | /* Found the char as a normal char */ |
851 | 0 | if (CharBinSearch(c, L33TChr, sizeof L33TChr - 1, 1)) |
852 | 0 | { |
853 | | /* Char matches, but also a normal equivalent to a leet char */ |
854 | 0 | AddLeetChr(c, 0, Extra.Leeted, Extra.UnLeet); |
855 | 0 | } |
856 | 0 | } |
857 | 0 | else |
858 | 0 | { |
859 | | /* No Match */ |
860 | 0 | Len = 0; |
861 | 0 | break; |
862 | 0 | } |
863 | 0 | if (++Len > MaxLen) |
864 | 0 | { |
865 | 0 | Len = 0; |
866 | 0 | break; |
867 | 0 | } |
868 | 0 | } |
869 | 0 | if (Len) |
870 | 0 | { |
871 | 0 | ZxcMatch_t *p = AllocMatch(); |
872 | 0 | if (!Leets) |
873 | 0 | p->Type = USER_MATCH; |
874 | 0 | else |
875 | 0 | p->Type = USER_LEET_MATCH; |
876 | 0 | p->Length = Len; |
877 | 0 | p->Begin = Start; |
878 | | /* Add Entrpy */ |
879 | 0 | Extra.Caps = Caps; |
880 | 0 | Extra.Lower = Lowers; |
881 | 0 | Extra.NumLeet = Leets; |
882 | 0 | Extra.Rank = Rank+1; |
883 | 0 | DictionaryEntropy(p, &Extra, Passwd); |
884 | 0 | AddMatchRepeats(Result, p, Passwd, MaxLen); |
885 | 0 | AddResult(Result, p, MaxLen); |
886 | 0 | } |
887 | 0 | } |
888 | 0 | } |
889 | | |
890 | | /********************************************************************************** |
891 | | * Try to match password part with the dictionary words |
892 | | * Parameters: |
893 | | * Result Pointer head of linked list used to store results |
894 | | * Passwd The start of the password |
895 | | * Start Where in the password to start attempting to match |
896 | | * MaxLen Maximum number characters to consider |
897 | | */ |
898 | | static void DictionaryMatch(ZxcMatch_t **Result, const uint8_t *Passwd, int Start, int MaxLen) |
899 | 0 | { |
900 | 0 | DictWork_t Wrk; |
901 | 0 | DictMatchInfo_t Extra; |
902 | |
|
903 | 0 | memset(&Extra, 0, sizeof Extra); |
904 | 0 | memset(&Wrk, 0, sizeof Wrk); |
905 | 0 | Wrk.Ordinal = 1; |
906 | 0 | Wrk.StartLoc = ROOT_NODE_LOC; |
907 | 0 | Wrk.Begin = Start; |
908 | 0 | DoDictMatch(Passwd+Start, 0, MaxLen, &Wrk, Result, &Extra, 0); |
909 | 0 | } |
910 | | |
911 | | |
912 | | /*################################################################################* |
913 | | *################################################################################* |
914 | | * Begin keyboard spatial sequence matching code |
915 | | *################################################################################* |
916 | | *################################################################################*/ |
917 | | |
918 | | /* Struct to hold information on a keyboard layout */ |
919 | | typedef struct Keyboard |
920 | | { |
921 | | const uint8_t *Keys; |
922 | | const uint8_t *Shifts; |
923 | | int NumKeys; |
924 | | int NumNear; |
925 | | int NumShift; |
926 | | int NumBlank; |
927 | | } Keyboard_t; |
928 | | |
929 | | /* Struct to hold information on the match */ |
930 | | typedef struct |
931 | | { |
932 | | int Keyb; |
933 | | int Turns; |
934 | | int Shifts; |
935 | | } SpatialMatchInfo_t; |
936 | | |
937 | | /* Shift mapping, characters in pairs: first is shifted, second un-shifted. Ordered for increasing shifted character code.*/ |
938 | | /* Note: on a UK keyboard \243 is the £ (Pound stirling), \244 is the ¤ (Euro), \254 is the ¬ (Not sign) */ |
939 | | static const uint8_t UK_Shift[] = "!1\"2$4%5&7(9)0*8:;<,>.?/@'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz^6_-{[|\\}]~#\2433\2444\254`"; |
940 | | static const uint8_t US_Shift[] = "!1\"'#3$4%5&7(9)0*8:;<,>.?/@2AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz^6_-{[|\\}]~`"; |
941 | | |
942 | | |
943 | | /* Neighbour tables */ |
944 | | static const uint8_t UK_Qwerty[48*7] = |
945 | | { |
946 | | /* key, left, up-left, up-right, right, down-right, down-left */ |
947 | | '#', '\'',']', 0, 0, 0, 0, '\'',';', '[', ']', '#', 0, '/', |
948 | | ',', 'm', 'k', 'l', '.', 0, 0, '-', '0', 0, 0, '=', '[', 'p', |
949 | | '.', ',', 'l', ';', '/', 0, 0, '/', '.', ';', '\'', 0, 0, 0, |
950 | | '0', '9', 0, 0, '-', 'p', 'o', '1', '`', 0, 0, '2', 'q', 0, |
951 | | '2', '1', 0, 0, '3', 'w', 'q', '3', '2', 0, 0, '4', 'e', 'w', |
952 | | '4', '3', 0, 0, '5', 'r', 'e', '5', '4', 0, 0, '6', 't', 'r', |
953 | | '6', '5', 0, 0, '7', 'y', 't', '7', '6', 0, 0, '8', 'u', 'y', |
954 | | '8', '7', 0, 0, '9', 'i', 'u', '9', '8', 0, 0, '0', 'o', 'i', |
955 | | ';', 'l', 'p', '[','\'', '/', '.', '=', '-', 0, 0, 0, ']', '[', |
956 | | '[', 'p', '-', '=', ']', '\'',';', '\\', 0, 0, 'a', 'z', 0, 0, |
957 | | ']', '[', '=', 0, 0, '#','\'', '`', 0, 0, 0, '1', 0, 0, |
958 | | 'a', 0, 'q', 'w', 's', 'z','\\', 'b', 'v', 'g', 'h', 'n', 0, 0, |
959 | | 'c', 'x', 'd', 'f', 'v', 0, 0, 'd', 's', 'e', 'r', 'f', 'c', 'x', |
960 | | 'e', 'w', '3', '4', 'r', 'd', 's', 'f', 'd', 'r', 't', 'g', 'v', 'c', |
961 | | 'g', 'f', 't', 'y', 'h', 'b', 'v', 'h', 'g', 'y', 'u', 'j', 'n', 'b', |
962 | | 'i', 'u', '8', '9', 'o', 'k', 'j', 'j', 'h', 'u', 'i', 'k', 'm', 'n', |
963 | | 'k', 'j', 'i', 'o', 'l', ',', 'm', 'l', 'k', 'o', 'p', ';', '.', ',', |
964 | | 'm', 'n', 'j', 'k', ',', 0, 0, 'n', 'b', 'h', 'j', 'm', 0, 0, |
965 | | 'o', 'i', '9', '0', 'p', 'l', 'k', 'p', 'o', '0', '-', '[', ';', 'l', |
966 | | 'q', 0, '1', '2', 'w', 'a', 0, 'r', 'e', '4', '5', 't', 'f', 'd', |
967 | | 's', 'a', 'w', 'e', 'd', 'x', 'z', 't', 'r', '5', '6', 'y', 'g', 'f', |
968 | | 'u', 'y', '7', '8', 'i', 'j', 'h', 'v', 'c', 'f', 'g', 'b', 0, 0, |
969 | | 'w', 'q', '2', '3', 'e', 's', 'a', 'x', 'z', 's', 'd', 'c', 0, 0, |
970 | | 'y', 't', '6', '7', 'u', 'h', 'g', 'z', '\\','a', 's', 'x', 0, 0 |
971 | | }; |
972 | | |
973 | | static const uint8_t US_Qwerty[47*7] = |
974 | | { |
975 | | /* key, left, up-left, up-right, right, down-right, down-left */ |
976 | | '\'',';', '[', ']', 0, 0, '/', ',', 'm', 'k', 'l', '.', 0, 0, |
977 | | '-', '0', 0, 0, '=', '[', 'p', '.', ',', 'l', ';', '/', 0, 0, |
978 | | '/', '.', ';','\'', 0, 0, 0, '0', '9', 0, 0, '-', 'p', 'o', |
979 | | '1', '`', 0, 0, '2', 'q', 0, '2', '1', 0, 0, '3', 'w', 'q', |
980 | | '3', '2', 0, 0, '4', 'e', 'w', '4', '3', 0, 0, '5', 'r', 'e', |
981 | | '5', '4', 0, 0, '6', 't', 'r', '6', '5', 0, 0, '7', 'y', 't', |
982 | | '7', '6', 0, 0, '8', 'u', 'y', '8', '7', 0, 0, '9', 'i', 'u', |
983 | | '9', '8', 0, 0, '0', 'o', 'i', ';', 'l', 'p', '[','\'', '/', '.', |
984 | | '=', '-', 0, 0, 0, ']', '[', '[', 'p', '-', '=', ']','\'', ';', |
985 | | '\\',']', 0, 0, 0, 0, 0, ']', '[', '=', 0,'\\', 0,'\'', |
986 | | '`', 0, 0, 0, '1', 0, 0, 'a', 0, 'q', 'w', 's', 'z', 0, |
987 | | 'b', 'v', 'g', 'h', 'n', 0, 0, 'c', 'x', 'd', 'f', 'v', 0, 0, |
988 | | 'd', 's', 'e', 'r', 'f', 'c', 'x', 'e', 'w', '3', '4', 'r', 'd', 's', |
989 | | 'f', 'd', 'r', 't', 'g', 'v', 'c', 'g', 'f', 't', 'y', 'h', 'b', 'v', |
990 | | 'h', 'g', 'y', 'u', 'j', 'n', 'b', 'i', 'u', '8', '9', 'o', 'k', 'j', |
991 | | 'j', 'h', 'u', 'i', 'k', 'm', 'n', 'k', 'j', 'i', 'o', 'l', ',', 'm', |
992 | | 'l', 'k', 'o', 'p', ';', '.', ',', 'm', 'n', 'j', 'k', ',', 0, 0, |
993 | | 'n', 'b', 'h', 'j', 'm', 0, 0, 'o', 'i', '9', '0', 'p', 'l', 'k', |
994 | | 'p', 'o', '0', '-', '[', ';', 'l', 'q', 0, '1', '2', 'w', 'a', 0, |
995 | | 'r', 'e', '4', '5', 't', 'f', 'd', 's', 'a', 'w', 'e', 'd', 'x', 'z', |
996 | | 't', 'r', '5', '6', 'y', 'g', 'f', 'u', 'y', '7', '8', 'i', 'j', 'h', |
997 | | 'v', 'c', 'f', 'g', 'b', 0, 0, 'w', 'q', '2', '3', 'e', 's', 'a', |
998 | | 'x', 'z', 's', 'd', 'c', 0, 0, 'y', 't', '6', '7', 'u', 'h', 'g', |
999 | | 'z', 0, 'a', 's', 'x', 0, 0, |
1000 | | }; |
1001 | | static const uint8_t Dvorak[47*7] = |
1002 | | { |
1003 | | '\'', 0, '1', '2', ',', 'a', 0, ',','\'', '2', '3', '.', 'o', 'a', |
1004 | | '-', 's', '/', '=', 0, 0, 'z', '.', ',', '3', '4', 'p', 'e', 'o', |
1005 | | '/', 'l', '[', ']', '=', '-', 's', '0', '9', 0, 0, '[', 'l', 'r', |
1006 | | '1', '`', 0, 0, '2','\'', 0, '2', '1', 0, 0, '3', ',','\'', |
1007 | | '3', '2', 0, 0, '4', '.', ',', '4', '3', 0, 0, '5', 'p', '.', |
1008 | | '5', '4', 0, 0, '6', 'y', 'p', '6', '5', 0, 0, '7', 'f', 'y', |
1009 | | '7', '6', 0, 0, '8', 'g', 'f', '8', '7', 0, 0, '9', 'c', 'g', |
1010 | | '9', '8', 0, 0, '0', 'r', 'c', ';', 0, 'a', 'o', 'q', 0, 0, |
1011 | | '=', '/', ']', 0,'\\', 0, '-', '[', '0', 0, 0, ']', '/', 'l', |
1012 | | '\\','=', 0, 0, 0, 0, 0, ']', '[', 0, 0, 0, '=', '/', |
1013 | | '`', 0, 0, 0, '1', 0, 0, 'a', 0,'\'', ',', 'o', ';', 0, |
1014 | | 'b', 'x', 'd', 'h', 'm', 0, 0, 'c', 'g', '8', '9', 'r', 't', 'h', |
1015 | | 'd', 'i', 'f', 'g', 'h', 'b', 'x', 'e', 'o', '.', 'p', 'u', 'j', 'q', |
1016 | | 'f', 'y', '6', '7', 'g', 'd', 'i', 'g', 'f', '7', '8', 'c', 'h', 'd', |
1017 | | 'h', 'd', 'g', 'c', 't', 'm', 'b', 'i', 'u', 'y', 'f', 'd', 'x', 'k', |
1018 | | 'j', 'q', 'e', 'u', 'k', 0, 0, 'k', 'j', 'u', 'i', 'x', 0, 0, |
1019 | | 'l', 'r', '0', '[', '/', 's', 'n', 'm', 'b', 'h', 't', 'w', 0, 0, |
1020 | | 'n', 't', 'r', 'l', 's', 'v', 'w', 'o', 'a', ',', '.', 'e', 'q', ';', |
1021 | | 'p', '.', '4', '5', 'y', 'u', 'e', 'q', ';', 'o', 'e', 'j', 0, 0, |
1022 | | 'r', 'c', '9', '0', 'l', 'n', 't', 's', 'n', 'l', '/', '-', 'z', 'v', |
1023 | | 't', 'h', 'c', 'r', 'n', 'w', 'm', 'u', 'e', 'p', 'y', 'i', 'k', 'j', |
1024 | | 'v', 'w', 'n', 's', 'z', 0, 0, 'w', 'm', 't', 'n', 'v', 0, 0, |
1025 | | 'x', 'k', 'i', 'd', 'b', 0, 0, 'y', 'p', '5', '6', 'f', 'i', 'u', |
1026 | | 'z', 'v', 's', '-', 0, 0, 0 |
1027 | | }; |
1028 | | |
1029 | | static const uint8_t PC_Keypad[15*9] = |
1030 | | { |
1031 | | /*Key, left, up-left, up, up-right, right, down-right, down, down-left */ |
1032 | | '*', '/', 0, 0, 0, '-', '+', '9', '8', |
1033 | | '+', '9', '*', '-', 0, 0, 0, 0, '6', |
1034 | | '-', '*', 0, 0, 0, 0, 0, '+', '9', |
1035 | | '.', '0', '2', '3', 0, 0, 0, 0, 0, |
1036 | | '/', 0, 0, 0, 0, '*', '9', '8', '7', |
1037 | | '0', 0, '1', '2', '3', '.', 0, 0, 0, |
1038 | | '1', 0, 0, '4', '5', '2', '0', 0, 0, |
1039 | | '2', '1', '4', '5', '6', '3', '.', '0', 0, |
1040 | | '3', '2', '5', '6', 0, 0, 0, '.', '0', |
1041 | | '4', 0, 0, '7', '8', '5', '2', '1', 0, |
1042 | | '5', '4', '7', '8', '9', '6', '3', '2', '1', |
1043 | | '6', '5', '8', '9', '+', 0, 0, '3', '2', |
1044 | | '7', 0, 0, 0, '/', '8', '5', '4', 0, |
1045 | | '8', '7', 0, '/', '*', '9', '6', '5', '4', |
1046 | | '9', '8', '/', '*', '-', '+', 0, '6', '5' |
1047 | | }; |
1048 | | |
1049 | | static const uint8_t MacKeypad[16*9] = |
1050 | | { |
1051 | | '*', '/', 0, 0, 0, 0, 0, '-', '9', |
1052 | | '+', '6', '9', '-', 0, 0, 0, 0, '3', |
1053 | | '-', '9', '/', '*', 0, 0, 0, '+', '6', |
1054 | | '.', '0', '2', '3', 0, 0, 0, 0, 0, |
1055 | | '/', '=', 0, 0, 0, '*', '-', '9', '8', |
1056 | | '0', 0, '1', '2', '3', '.', 0, 0, 0, |
1057 | | '1', 0, 0, '4', '5', '2', '0', 0, 0, |
1058 | | '2', '1', '4', '5', '6', '3', '.', '0', 0, |
1059 | | '3', '2', '5', '6', '+', 0, 0, '.', '0', |
1060 | | '4', 0, 0, '7', '8', '5', '2', '1', 0, |
1061 | | '5', '4', '7', '8', '9', '6', '3', '2', '1', |
1062 | | '6', '5', '8', '9', '-', '+', 0, '3', '2', |
1063 | | '7', 0, 0, 0, '=', '8', '5', '4', 0, |
1064 | | '8', '7', 0, '=', '/', '9', '6', '5', '4', |
1065 | | '9', '8', '=', '/', '*', '-', '+', '6', '5', |
1066 | | '=', 0, 0, 0, 0, '/', '9', '8', '7' |
1067 | | }; |
1068 | | |
1069 | | static const Keyboard_t Keyboards[] = |
1070 | | { |
1071 | | { US_Qwerty, US_Shift, sizeof US_Qwerty / 7, 7, sizeof US_Shift / 2, 66 }, |
1072 | | { Dvorak, US_Shift, sizeof Dvorak / 7, 7, sizeof US_Shift / 2, 66 }, |
1073 | | { UK_Qwerty, UK_Shift, sizeof UK_Qwerty / 7, 7, sizeof UK_Shift / 2, 66 }, |
1074 | | { MacKeypad, 0, sizeof MacKeypad / 9, 9, 0, 44 }, |
1075 | | { PC_Keypad, 0, sizeof PC_Keypad / 9, 9, 0, 44 } |
1076 | | }; |
1077 | | |
1078 | | /********************************************************************************** |
1079 | | * Match password for the given keyboard layout |
1080 | | */ |
1081 | | static int DoSptlMatch(const uint8_t *Passwd, int MaxLen, const Keyboard_t *Keyb, SpatialMatchInfo_t *Extra) |
1082 | 0 | { |
1083 | 0 | int i; |
1084 | 0 | int ShiftCount = 0; |
1085 | 0 | int Turns = 0; |
1086 | 0 | int Dir = -1; |
1087 | 0 | int Len = 0; |
1088 | 0 | uint8_t PrevChar = 0; |
1089 | 0 | for( ; *Passwd && (Len < MaxLen); ++Passwd, ++Len) |
1090 | 0 | { |
1091 | 0 | const uint8_t *Key; |
1092 | 0 | int s = 0; |
1093 | 0 | uint8_t CurChar = *Passwd; |
1094 | | /* Try to unshift the character */ |
1095 | 0 | if (Keyb->Shifts) |
1096 | 0 | { |
1097 | 0 | Key = CharBinSearch(CurChar, Keyb->Shifts, Keyb->NumShift, 2); |
1098 | 0 | if (Key) |
1099 | 0 | { |
1100 | | /* Shifted char */ |
1101 | 0 | CurChar = Key[1]; |
1102 | 0 | s = 1; |
1103 | 0 | } |
1104 | 0 | } |
1105 | 0 | if (PrevChar) |
1106 | 0 | { |
1107 | | /* See if the pattern can be extended */ |
1108 | 0 | i = 0; |
1109 | 0 | Key = CharBinSearch(PrevChar, Keyb->Keys, Keyb->NumKeys, Keyb->NumNear); |
1110 | 0 | if (Key) |
1111 | 0 | { |
1112 | 0 | for(i = Keyb->NumNear - 1; i > 0; --i) |
1113 | 0 | { |
1114 | 0 | if (Key[i] == CurChar) |
1115 | 0 | break; |
1116 | 0 | } |
1117 | 0 | } |
1118 | 0 | if (i) |
1119 | 0 | { |
1120 | 0 | Turns += (i != Dir); |
1121 | 0 | Dir = i; |
1122 | 0 | ShiftCount += s; |
1123 | 0 | } |
1124 | 0 | else |
1125 | 0 | { |
1126 | 0 | break; |
1127 | 0 | } |
1128 | 0 | } |
1129 | 0 | PrevChar = CurChar; |
1130 | 0 | } |
1131 | 0 | if (Len >= MIN_SPATIAL_LEN) |
1132 | 0 | { |
1133 | 0 | Extra->Turns = Turns; |
1134 | 0 | Extra->Shifts = ShiftCount; |
1135 | 0 | return Len; |
1136 | 0 | } |
1137 | 0 | return 0; |
1138 | 0 | } |
1139 | | |
1140 | | /********************************************************************************** |
1141 | | * Try to match spatial patterns on the keyboard |
1142 | | * Parameters: |
1143 | | * Result Pointer head of linked list used to store results |
1144 | | * Passwd The start of the password |
1145 | | * Start Where in the password to start attempting to match |
1146 | | * MaxLen Maximum number characters to consider |
1147 | | */ |
1148 | | static void SpatialMatch(ZxcMatch_t **Result, const uint8_t *Passwd, int Start, int MaxLen) |
1149 | 0 | { |
1150 | 0 | unsigned int Indx; |
1151 | 0 | int Len, CurLen; |
1152 | 0 | SpatialMatchInfo_t Extra; |
1153 | 0 | const Keyboard_t *k; |
1154 | 0 | Passwd += Start; |
1155 | |
|
1156 | 0 | for(CurLen = MaxLen; CurLen >= MIN_SPATIAL_LEN;CurLen = Len - 1) |
1157 | 0 | { |
1158 | 0 | Len = 0; |
1159 | 0 | for(k = Keyboards, Indx = 0; Indx < (sizeof Keyboards / sizeof Keyboards[0]); ++Indx, ++k) |
1160 | 0 | { |
1161 | 0 | memset(&Extra, 0, sizeof Extra); |
1162 | 0 | Len = DoSptlMatch(Passwd, CurLen, k, &Extra); |
1163 | 0 | if (Len > 0) |
1164 | 0 | { |
1165 | | /* Got a sequence of required length so add to result list */ |
1166 | 0 | int i, j, s; |
1167 | 0 | double Degree, Entropy; |
1168 | 0 | ZxcMatch_t *p; |
1169 | 0 | Degree = (k->NumNear-1) - (double)k->NumBlank / (double)k->NumKeys; |
1170 | 0 | s = k->NumKeys; |
1171 | 0 | if (k->Shifts) |
1172 | 0 | s *= 2; |
1173 | | |
1174 | | /* Estimate the number of possible patterns with length ranging 2 to match length and */ |
1175 | | /* with turns ranging from 0 to match turns */ |
1176 | 0 | Entropy = 0.0; |
1177 | 0 | for(i = 2; i <= Len; ++i) |
1178 | 0 | { |
1179 | 0 | int PossTurns = Extra.Turns; |
1180 | 0 | if (PossTurns >= i) |
1181 | 0 | PossTurns = i-1; |
1182 | 0 | for(j = 1; j <= PossTurns; ++j) |
1183 | 0 | Entropy += nCk(i-1, j-1) * pow(Degree, j) * s; |
1184 | 0 | } |
1185 | 0 | if (Entropy > 0.0) |
1186 | 0 | Entropy = log(Entropy); |
1187 | 0 | if (Extra.Shifts) |
1188 | 0 | { |
1189 | | /* Add extra entropy for shifted keys. (% instead of 5, A instead of a etc.) */ |
1190 | | /* Math is similar to extra entropy from uppercase letters in dictionary matches. */ |
1191 | 0 | int Shift = Extra.Shifts; |
1192 | 0 | int Unshift = Len - Shift; |
1193 | |
|
1194 | 0 | Degree = 0.0; |
1195 | 0 | j = Shift; |
1196 | 0 | if (j > Unshift) |
1197 | 0 | j = Unshift; |
1198 | 0 | for(i = 0; i <= j; ++i) |
1199 | 0 | { |
1200 | 0 | Degree += nCk(Len, i); |
1201 | 0 | } |
1202 | 0 | if (Degree > 0.0) |
1203 | 0 | Entropy += log(Degree); |
1204 | 0 | } |
1205 | 0 | p = AllocMatch(); |
1206 | 0 | p->Type = SPATIAL_MATCH; |
1207 | 0 | p->Begin = Start; |
1208 | 0 | p->Entrpy = Entropy; |
1209 | 0 | p->Length = Len; |
1210 | 0 | AddMatchRepeats(Result, p, Passwd, MaxLen); |
1211 | 0 | AddResult(Result, p, MaxLen); |
1212 | 0 | } |
1213 | 0 | } |
1214 | 0 | } |
1215 | 0 | } |
1216 | | |
1217 | | |
1218 | | /*################################################################################* |
1219 | | *################################################################################* |
1220 | | * Begin date matching code |
1221 | | *################################################################################* |
1222 | | *################################################################################*/ |
1223 | | |
1224 | | /* The possible date formats ordered by length (d for day, m for month, */ |
1225 | | /* y for year, ? for separator) */ |
1226 | | static const char *Formats[] = |
1227 | | { |
1228 | | "yyyy", |
1229 | | "d?m?yy", |
1230 | | "ddmmyy", |
1231 | | "dmyyyy", |
1232 | | "dd?m?yy", |
1233 | | "d?mm?yy", |
1234 | | "ddmyyyy", |
1235 | | "dmmyyyy", |
1236 | | "yyyymmd", |
1237 | | "yyyymdd", |
1238 | | "d?m?yyyy", |
1239 | | "dd?mm?yy", |
1240 | | "ddmmyyyy", |
1241 | | "yyyy?m?d", |
1242 | | "yyyymmdd", |
1243 | | "dd?m?yyyy", |
1244 | | "d?mm?yyyy", |
1245 | | "yyyy?mm?d", |
1246 | | "yyyy?m?dd", |
1247 | | "dd?mm?yyyy", |
1248 | | "yyyy?mm?dd", |
1249 | | 0 |
1250 | | }; |
1251 | | /* Possible separator characters that could be used */ |
1252 | | static const char DateSeperators[] = "/\\-_. "; |
1253 | | |
1254 | | /********************************************************************************** |
1255 | | * Try to match the password with the formats above. |
1256 | | */ |
1257 | | static void DateMatch(ZxcMatch_t **Result, const uint8_t *Passwd, int Start, int MaxLen) |
1258 | 0 | { |
1259 | 0 | int CurFmt; |
1260 | 0 | int YrLen = 0; |
1261 | 0 | int PrevLen = 0; |
1262 | 0 | uint8_t Sep = 0; |
1263 | 0 | Passwd += Start; |
1264 | |
|
1265 | 0 | for(CurFmt = 0; Formats[CurFmt]; ++CurFmt) |
1266 | 0 | { |
1267 | 0 | int Len = 0; |
1268 | 0 | int Year = 0; |
1269 | 0 | int Mon = 0; |
1270 | 0 | int Day = 0; |
1271 | 0 | int Fail = 0; |
1272 | 0 | const uint8_t *p = Passwd; |
1273 | 0 | const char *Fmt; |
1274 | 0 | YrLen = 0; |
1275 | 0 | Sep = 0; |
1276 | | /* Scan along the format, trying to match the password */ |
1277 | 0 | for(Fmt = Formats[CurFmt]; *Fmt && !Fail; ++Fmt) |
1278 | 0 | { |
1279 | 0 | if (*Fmt == '?') |
1280 | 0 | { |
1281 | 0 | if (!Sep && strchr(DateSeperators, *p)) |
1282 | 0 | Sep = *p; |
1283 | 0 | Fail = (*p != Sep); |
1284 | 0 | } |
1285 | 0 | else if (isdigit(*p)) |
1286 | 0 | { |
1287 | 0 | if (*Fmt == 'd') |
1288 | 0 | { |
1289 | 0 | Day = 10 * Day + *p - '0'; |
1290 | 0 | } |
1291 | 0 | else if (*Fmt == 'm') |
1292 | 0 | { |
1293 | 0 | Mon = 10 * Mon + *p - '0'; |
1294 | 0 | } |
1295 | 0 | else |
1296 | 0 | { |
1297 | 0 | Year = 10 * Year + *p - '0'; |
1298 | 0 | ++YrLen; |
1299 | 0 | } |
1300 | 0 | } |
1301 | 0 | else |
1302 | 0 | { |
1303 | 0 | Fail = 1; |
1304 | 0 | } |
1305 | 0 | ++p; |
1306 | 0 | ++Len; |
1307 | 0 | if (Len >= MaxLen) |
1308 | 0 | break; |
1309 | 0 | } |
1310 | 0 | if (Len < 4) |
1311 | 0 | Fail = 1; |
1312 | 0 | if (!Fail) |
1313 | 0 | { |
1314 | | /* Character matching is OK, now check to see if valid date */ |
1315 | 0 | if (((YrLen > 3) || (Len <= 4)) && ((Year < MIN_YEAR) || (Year > MAX_YEAR))) |
1316 | 0 | Fail = 1; |
1317 | 0 | else if (Len > 4) |
1318 | 0 | { |
1319 | 0 | if ((Mon > 12) && (Day < 13)) |
1320 | 0 | { |
1321 | | /* Swap day,month to try to make both in range */ |
1322 | 0 | int i = Mon; |
1323 | 0 | Mon = Day; |
1324 | 0 | Day = i; |
1325 | 0 | } |
1326 | | /* Check for valid day, month. Currently assumes all months have 31 days. */ |
1327 | 0 | if ((Mon < 1) || (Mon > 12)) |
1328 | 0 | Fail = 1; |
1329 | 0 | else if ((Day < 1) || (Day > 31)) |
1330 | 0 | Fail = 1; |
1331 | 0 | } |
1332 | 0 | } |
1333 | 0 | if (!Fail && (Len > PrevLen)) |
1334 | 0 | { |
1335 | | /* String matched the date, store result */ |
1336 | 0 | double e; |
1337 | 0 | ZxcMatch_t *p = AllocMatch(); |
1338 | |
|
1339 | 0 | if (Len <= 4) |
1340 | 0 | e = log(MAX_YEAR - MIN_YEAR + 1.0); |
1341 | 0 | else if (YrLen > 3) |
1342 | 0 | e = log(31 * 12 * (MAX_YEAR - MIN_YEAR + 1.0)); |
1343 | 0 | else |
1344 | 0 | e = log(31 * 12 * 100.0); |
1345 | 0 | if (Sep) |
1346 | 0 | e += log(4.0); /* Extra 2 bits for separator */ |
1347 | 0 | p->Entrpy = e; |
1348 | 0 | p->Type = DATE_MATCH; |
1349 | 0 | p->Length = Len; |
1350 | 0 | p->Begin = Start; |
1351 | 0 | AddMatchRepeats(Result, p, Passwd, MaxLen); |
1352 | 0 | AddResult(Result, p, MaxLen); |
1353 | 0 | PrevLen = Len; |
1354 | 0 | } |
1355 | 0 | } |
1356 | 0 | } |
1357 | | |
1358 | | |
1359 | | /*################################################################################* |
1360 | | *################################################################################* |
1361 | | * Begin repeated character matching code |
1362 | | *################################################################################* |
1363 | | *################################################################################*/ |
1364 | | |
1365 | | /********************************************************************************** |
1366 | | * Try to match password part as a set of repeated characters. |
1367 | | * Parameters: |
1368 | | * Result Pointer head of linked list used to store results |
1369 | | * Passwd The start of the password |
1370 | | * Start Where in the password to start attempting to match |
1371 | | * MaxLen Maximum number characters to consider |
1372 | | */ |
1373 | | static void RepeatMatch(ZxcMatch_t **Result, const uint8_t *Passwd, int Start, int MaxLen) |
1374 | 0 | { |
1375 | 0 | int Len, i; |
1376 | 0 | uint8_t c; |
1377 | 0 | Passwd += Start; |
1378 | | /* Remember first char and the count its occurances */ |
1379 | 0 | c = *Passwd; |
1380 | 0 | for(Len = 1; (Len < MaxLen) && (c == Passwd[Len]); ++Len) |
1381 | 0 | { } |
1382 | 0 | if (Len >= MIN_REPEAT_LEN) |
1383 | 0 | { |
1384 | | /* Enough repeated char, so create results from number found down to min acceptable repeats */ |
1385 | 0 | double Card = Cardinality(&c, 1); |
1386 | 0 | for(i = Len; i >= MIN_REPEAT_LEN; --i) |
1387 | 0 | { |
1388 | 0 | ZxcMatch_t *p = AllocMatch(); |
1389 | 0 | p->Type = REPEATS_MATCH; |
1390 | 0 | p->Begin = Start; |
1391 | 0 | p->Length = i; |
1392 | 0 | p->Entrpy = log(Card * i); |
1393 | 0 | AddResult(Result, p, MaxLen); |
1394 | 0 | } |
1395 | 0 | } |
1396 | | |
1397 | | /* Try to match a repeated sequence e.g. qxno6qxno6 */ |
1398 | 0 | for(Len = MaxLen/2; Len >= MIN_REPEAT_LEN; --Len) |
1399 | 0 | { |
1400 | 0 | const uint8_t *Rpt = Passwd + Len; |
1401 | 0 | int RepeatCount = 2; |
1402 | 0 | while(MaxLen >= (Len * RepeatCount)) |
1403 | 0 | { |
1404 | 0 | if (strncmp((const char *)Passwd, (const char *)Rpt, Len) == 0) |
1405 | 0 | { |
1406 | | /* Found a repeat */ |
1407 | 0 | int c = Cardinality(Passwd, Len); |
1408 | 0 | ZxcMatch_t *p = AllocMatch(); |
1409 | 0 | p->Entrpy = log((double)c) * Len + log(RepeatCount); |
1410 | 0 | p->Type = (ZxcTypeMatch_t)(BRUTE_MATCH + MULTIPLE_MATCH); |
1411 | 0 | p->Length = Len * RepeatCount; |
1412 | 0 | p->Begin = Start; |
1413 | 0 | AddResult(Result, p, MaxLen); |
1414 | 0 | } |
1415 | 0 | else |
1416 | 0 | break; |
1417 | 0 | ++RepeatCount; |
1418 | 0 | Rpt += Len; |
1419 | 0 | } |
1420 | 0 | } |
1421 | 0 | } |
1422 | | |
1423 | | /********************************************************************************** |
1424 | | ********************************************************************************** |
1425 | | * Begin character sequence matching code |
1426 | | ********************************************************************************** |
1427 | | *********************************************************************************/ |
1428 | | |
1429 | 0 | #define MAX_SEQUENCE_STEP 5 |
1430 | | /********************************************************************************** |
1431 | | * Try to match password part as a set of incrementing or decrementing characters. |
1432 | | * Parameters: |
1433 | | * Result Pointer head of linked list used to store results |
1434 | | * Passwd The start of the password |
1435 | | * Start Where in the password to start attempting to match |
1436 | | * MaxLen Maximum number characters to consider |
1437 | | */ |
1438 | | static void SequenceMatch(ZxcMatch_t **Result, const uint8_t *Passwd, int Start, int MaxLen) |
1439 | 0 | { |
1440 | 0 | int Len=0; |
1441 | 0 | int SetLow, SetHigh, Dir; |
1442 | 0 | uint8_t First, Next, IsDigits; |
1443 | 0 | const uint8_t *Pwd; |
1444 | 0 | Passwd += Start; |
1445 | 0 | Pwd = Passwd; |
1446 | 0 | First = Passwd[0]; |
1447 | 0 | Dir = Passwd[1] - First; |
1448 | 0 | Len = 0; |
1449 | 0 | IsDigits = 0; |
1450 | | /* Decide on min and max character code for sequence */ |
1451 | 0 | if (islower(*Passwd)) |
1452 | 0 | { |
1453 | 0 | SetLow = 'a'; |
1454 | 0 | SetHigh = 'z'; |
1455 | 0 | } |
1456 | 0 | else if (isupper(*Passwd)) |
1457 | 0 | { |
1458 | 0 | SetLow = 'A'; |
1459 | 0 | SetHigh = 'Z'; |
1460 | 0 | } |
1461 | 0 | else if (isdigit(*Passwd)) |
1462 | 0 | { |
1463 | 0 | SetLow = '0'; |
1464 | 0 | SetHigh = '9'; |
1465 | 0 | if ((First == '0') && isdigit(Passwd[1]) && (Dir > MAX_SEQUENCE_STEP)) |
1466 | 0 | { |
1467 | | /* Special case for decrementing sequence of digits, treat '0 as a 'ten' character */ |
1468 | 0 | Dir = Passwd[1] - ('9' + 1); |
1469 | 0 | } |
1470 | 0 | IsDigits = 1; |
1471 | 0 | } |
1472 | 0 | else |
1473 | 0 | return; |
1474 | | |
1475 | | /* Only consider it a sequence if the character increment is not too large */ |
1476 | 0 | if (Dir && (Dir <= MAX_SEQUENCE_STEP) && (Dir >= -MAX_SEQUENCE_STEP)) |
1477 | 0 | { |
1478 | 0 | ++Len; |
1479 | 0 | while(1) |
1480 | 0 | { |
1481 | 0 | Next = Passwd[0] + Dir; |
1482 | 0 | if (IsDigits && (Dir > 0) && (Next == ('9' + 1)) && (Passwd[1] == '0')) |
1483 | 0 | { |
1484 | | /* Incrementing digits, consider '0' to be same as a 'ten' character */ |
1485 | 0 | ++Len; |
1486 | 0 | ++Passwd; |
1487 | 0 | break; |
1488 | 0 | } |
1489 | 0 | if (IsDigits && (Dir < 0) && (Passwd[0] == '0') && (Passwd[1] == ('9'+1 + Dir))) |
1490 | 0 | { |
1491 | 0 | ++Len; |
1492 | 0 | ++Passwd; |
1493 | 0 | break; |
1494 | 0 | } |
1495 | 0 | if ((Next > SetHigh) || (Next < SetLow) || (Passwd[1] != Next)) |
1496 | 0 | break; |
1497 | 0 | ++Len; |
1498 | 0 | ++Passwd; |
1499 | 0 | if (Len >= MaxLen) |
1500 | 0 | break; |
1501 | 0 | } |
1502 | 0 | } |
1503 | 0 | if (Len >= MIN_SEQUENCE_LEN) |
1504 | 0 | { |
1505 | | /* Enough repeated char, so create results from number found down to min acceptable length */ |
1506 | 0 | int i; |
1507 | 0 | double e; |
1508 | 0 | if ((First == 'a') || (First == 'A') || (First == 'z') || (First == 'Z') || |
1509 | 0 | (First == '0') || (First == '1') || (First == '9')) |
1510 | 0 | e = log(2.0); |
1511 | 0 | else if (IsDigits) |
1512 | 0 | e = log(10.0); |
1513 | 0 | else if (isupper(First)) |
1514 | 0 | e = log(26*2.0); |
1515 | 0 | else |
1516 | 0 | e = log(26.0); |
1517 | 0 | if (Dir < 0) |
1518 | 0 | e += log(2.0); |
1519 | |
|
1520 | 0 | for(i = Len; i >= MIN_SEQUENCE_LEN; --i) |
1521 | 0 | { |
1522 | 0 | ZxcMatch_t *p = AllocMatch(); |
1523 | | /* Add new result to head of list as it has lower entropy */ |
1524 | 0 | p->Type = SEQUENCE_MATCH; |
1525 | 0 | p->Begin = Start; |
1526 | 0 | p->Length = i; |
1527 | 0 | p->Entrpy = e + log((double)i); |
1528 | 0 | AddMatchRepeats(Result, p, Pwd, MaxLen); |
1529 | 0 | AddResult(Result, p, MaxLen); |
1530 | 0 | } |
1531 | 0 | } |
1532 | 0 | } |
1533 | | |
1534 | | /********************************************************************************** |
1535 | | ********************************************************************************** |
1536 | | * Begin top level zxcvbn code |
1537 | | ********************************************************************************** |
1538 | | *********************************************************************************/ |
1539 | | |
1540 | | /********************************************************************************** |
1541 | | * Matching a password is treated as a problem of finding the minimum distance |
1542 | | * between two vertexes in a graph. This is solved using Dijkstra's algorithm. |
1543 | | * |
1544 | | * There are a series of nodes (or vertexes in graph terminology) which correspond |
1545 | | * to points between each character of the password. Also there is a start node |
1546 | | * before the first character and an end node after the last character. |
1547 | | * |
1548 | | * The paths between the nodes (or edges in graph terminology) correspond to the |
1549 | | * matched parts of the password (e.g. dictionary word, repeated characters etc). |
1550 | | * The distance of the path is equal to the entropy of the matched part. A default |
1551 | | * single character bruteforce match path is also added for all nodes except the |
1552 | | * end node. |
1553 | | * |
1554 | | * Dijkstra's algorithm finds the combination of these part matches (or paths) |
1555 | | * which gives the lowest entropy (or smallest distance) from begining to end |
1556 | | * of the password. |
1557 | | */ |
1558 | | |
1559 | | /* Struct to hold the data of a node (imaginary point between password characters) */ |
1560 | | typedef struct |
1561 | | { |
1562 | | ZxcMatch_t *Paths; /* Partial matches that lead to a following node */ |
1563 | | double Dist; /* Distance (or entropy) from start of password to this node */ |
1564 | | ZxcMatch_t *From; /* Which path was used to get to this node with lowest distance/entropy */ |
1565 | | int Visit; /* Non zero when node has been visited during Dijkstra evaluation */ |
1566 | | } Node_t; |
1567 | | |
1568 | | /********************************************************************************** |
1569 | | * Main function of the zxcvbn password entropy estimation |
1570 | | */ |
1571 | | double ZxcvbnMatch(const char *Pwd, const char *UserDict[], ZxcMatch_t **Info) |
1572 | 0 | { |
1573 | 0 | int i, j; |
1574 | 0 | ZxcMatch_t *Zp; |
1575 | 0 | Node_t *Np; |
1576 | 0 | double e; |
1577 | 0 | int FullLen = strlen(Pwd); |
1578 | 0 | int Len = FullLen; |
1579 | 0 | const uint8_t *Passwd = (const uint8_t *)Pwd; |
1580 | 0 | uint8_t *RevPwd; |
1581 | | /* Create the paths */ |
1582 | 0 | Node_t *Nodes = MallocFn(Node_t, Len+2); |
1583 | 0 | memset(Nodes, 0, (Len+2) * sizeof *Nodes); |
1584 | 0 | i = Cardinality(Passwd, Len); |
1585 | 0 | e = log((double)i); |
1586 | | |
1587 | | /* Limit length used to full entropy estimation to prevent excessive calculation time */ |
1588 | 0 | if (Len > ZXCVBN_DETAIL_LEN) |
1589 | 0 | Len = ZXCVBN_DETAIL_LEN; |
1590 | | |
1591 | | /* Do matching for all parts of the password */ |
1592 | 0 | for(i = 0; i < Len; ++i) |
1593 | 0 | { |
1594 | 0 | int MaxLen = Len - i; |
1595 | | /* Add all the 'paths' between groups of chars in the password, for current starting char */ |
1596 | 0 | UserMatch(&(Nodes[i].Paths), UserDict, Passwd, i, MaxLen); |
1597 | 0 | DictionaryMatch(&(Nodes[i].Paths), Passwd, i, MaxLen); |
1598 | 0 | DateMatch(&(Nodes[i].Paths), Passwd, i, MaxLen); |
1599 | 0 | SpatialMatch(&(Nodes[i].Paths), Passwd, i, MaxLen); |
1600 | 0 | SequenceMatch(&(Nodes[i].Paths), Passwd, i, MaxLen); |
1601 | 0 | RepeatMatch(&(Nodes[i].Paths), Passwd, i, MaxLen); |
1602 | | |
1603 | | /* Initially set distance to nearly infinite */ |
1604 | 0 | Nodes[i].Dist = DBL_MAX; |
1605 | 0 | } |
1606 | | |
1607 | | /* Reverse dictionary words check */ |
1608 | 0 | RevPwd = MallocFn(uint8_t, Len+1); |
1609 | 0 | for(i = Len-1, j = 0; i >= 0; --i, ++j) |
1610 | 0 | RevPwd[j] = Pwd[i]; |
1611 | 0 | RevPwd[j] = 0; |
1612 | 0 | for(i = 0; i < Len; ++i) |
1613 | 0 | { |
1614 | 0 | ZxcMatch_t *Path = 0; |
1615 | 0 | int MaxLen = Len - i; |
1616 | 0 | DictionaryMatch(&Path, RevPwd, i, MaxLen); |
1617 | 0 | UserMatch(&Path, UserDict, RevPwd, i, MaxLen); |
1618 | | |
1619 | | /* Now transfer any reverse matches to the normal results */ |
1620 | 0 | while(Path) |
1621 | 0 | { |
1622 | 0 | ZxcMatch_t *Nxt = Path->Next; |
1623 | 0 | Path->Next = 0; |
1624 | 0 | Path->Begin = Len - (Path->Begin + Path->Length); |
1625 | 0 | AddResult(&(Nodes[Path->Begin].Paths), Path, MaxLen); |
1626 | 0 | Path = Nxt; |
1627 | 0 | } |
1628 | 0 | } |
1629 | | |
1630 | | /* Add a set of brute force matches. Start by getting all the start points and all */ |
1631 | | /* points at character position after end of the matches. */ |
1632 | 0 | memset(RevPwd, 0, Len+1); |
1633 | 0 | for(i = 0; i < Len; ++i) |
1634 | 0 | { |
1635 | 0 | ZxcMatch_t *Path = Nodes[i].Paths; |
1636 | 0 | while(Path) |
1637 | 0 | { |
1638 | 0 | RevPwd[Path->Begin] |= 1; |
1639 | 0 | RevPwd[Path->Begin + Path->Length] |= 2; |
1640 | 0 | Path = Path->Next; |
1641 | 0 | } |
1642 | 0 | } |
1643 | 0 | RevPwd[0] = 1; |
1644 | 0 | RevPwd[Len] = 2; |
1645 | | |
1646 | | /* Add the brute force matches */ |
1647 | 0 | for(i = 0; i < Len; ++i) |
1648 | 0 | { |
1649 | 0 | int MaxLen = Len - i; |
1650 | 0 | int j; |
1651 | 0 | if (!RevPwd[i]) |
1652 | 0 | continue; |
1653 | 0 | for(j = i+1; j <= Len; ++j) |
1654 | 0 | { |
1655 | 0 | if (RevPwd[j]) |
1656 | 0 | { |
1657 | 0 | Zp = AllocMatch(); |
1658 | 0 | Zp->Type = BRUTE_MATCH; |
1659 | 0 | Zp->Begin = i; |
1660 | 0 | Zp->Length = j - i; |
1661 | 0 | Zp->Entrpy = e * (j - i); |
1662 | 0 | AddResult(&(Nodes[i].Paths), Zp, MaxLen); |
1663 | 0 | } |
1664 | 0 | } |
1665 | 0 | } |
1666 | 0 | FreeFn(RevPwd); |
1667 | 0 | if (FullLen > Len) |
1668 | 0 | { |
1669 | | /* Only the first MAX_DETAIL_LEN characters are used for full entropy estimation, for */ |
1670 | | /* very long passwords the remainding characters are treated as being a incrementing */ |
1671 | | /* sequence. This will give a low (and safe) entropy value for them. */ |
1672 | 0 | Nodes[Len].Dist = DBL_MAX; |
1673 | 0 | Zp = AllocMatch(); |
1674 | 0 | Zp->Type = LONG_PWD_MATCH; |
1675 | 0 | Zp->Begin = Len; |
1676 | | /* Length is negative as only one extra node to represent many extra characters */ |
1677 | 0 | Zp->Length = Len - FullLen; |
1678 | 0 | Zp->Entrpy = log(2 * (FullLen - Len)); |
1679 | 0 | AddResult(&(Nodes[i].Paths), Zp, FullLen - Len); |
1680 | 0 | ++Len; |
1681 | 0 | } |
1682 | | /* End node has infinite distance/entropy, start node has 0 distance */ |
1683 | 0 | Nodes[Len].Dist = DBL_MAX; |
1684 | 0 | Nodes[0].Dist = 0.0; |
1685 | | |
1686 | | /* Reduce the paths using Dijkstra's algorithm */ |
1687 | 0 | for(i = 0; i < Len; ++i) |
1688 | 0 | { |
1689 | 0 | int j; |
1690 | 0 | double MinDist = DBL_MAX; |
1691 | 0 | int MinIdx = 0; |
1692 | | /* Find the unvisited node with minimum distance or entropy */ |
1693 | 0 | for(Np = Nodes, j = 0; j < Len; ++j, ++Np) |
1694 | 0 | { |
1695 | 0 | if (!Np->Visit && (Np->Dist < MinDist)) |
1696 | 0 | { |
1697 | 0 | MinIdx = j; |
1698 | 0 | MinDist = Np->Dist; |
1699 | 0 | } |
1700 | 0 | } |
1701 | | /* Mark the minimum distance node as visited */ |
1702 | 0 | Np = Nodes + MinIdx; |
1703 | 0 | Np->Visit = 1; |
1704 | 0 | e = Np->Dist; |
1705 | | /* Update all unvisited neighbouring nodes with their new distance. A node is a */ |
1706 | | /* neighbour if there is a path/match from current node Np to it. The neighbour */ |
1707 | | /* distance is the current node distance plus the path distance/entropy. Only */ |
1708 | | /* update if the new distance is smaller. */ |
1709 | 0 | for(Zp = Np->Paths; Zp; Zp = Zp->Next) |
1710 | 0 | { |
1711 | 0 | Node_t *Ep; |
1712 | 0 | double d = e + Zp->MltEnpy; |
1713 | 0 | if (Zp->Length >= 0) |
1714 | 0 | Ep = Np + Zp->Length; |
1715 | 0 | else |
1716 | 0 | Ep = Np + 1; |
1717 | 0 | if (!Ep->Visit && (d < Ep->Dist)) |
1718 | 0 | { |
1719 | | /* Update as lower dist, also remember the 'from' node */ |
1720 | 0 | Ep->Dist = d; |
1721 | 0 | Ep->From = Zp; |
1722 | 0 | } |
1723 | 0 | } |
1724 | 0 | } |
1725 | | /* Make e hold entropy result and adjust to log base 2 */ |
1726 | 0 | e = Nodes[Len].Dist / log(2.0); |
1727 | |
|
1728 | 0 | if (Info) |
1729 | 0 | { |
1730 | | /* Construct info on password parts */ |
1731 | 0 | *Info = 0; |
1732 | 0 | for(Zp = Nodes[Len].From; Zp; ) |
1733 | 0 | { |
1734 | 0 | ZxcMatch_t *Xp; |
1735 | 0 | i = Zp->Begin; |
1736 | | |
1737 | | /* Remove all but required path */ |
1738 | 0 | Xp = Nodes[i].Paths; |
1739 | 0 | Nodes[i].Paths = 0; |
1740 | 0 | while(Xp) |
1741 | 0 | { |
1742 | 0 | ZxcMatch_t *p = Xp->Next; |
1743 | 0 | if (Xp == Zp) |
1744 | 0 | { |
1745 | | /* Adjust the entropy to log to base 2 */ |
1746 | 0 | Xp->Entrpy /= log(2.0); |
1747 | 0 | Xp->MltEnpy /= log(2.0); |
1748 | 0 | if (Xp->Length < 0) |
1749 | 0 | Xp->Length = -Xp->Length; |
1750 | | |
1751 | | /* Put previous part at head of info list */ |
1752 | 0 | Xp->Next = *Info; |
1753 | 0 | *Info = Xp; |
1754 | 0 | } |
1755 | 0 | else |
1756 | 0 | { |
1757 | | /* Not going on info list, so free it */ |
1758 | 0 | FreeFn(Xp); |
1759 | 0 | } |
1760 | 0 | Xp = p; |
1761 | 0 | } |
1762 | 0 | Zp = Nodes[i].From; |
1763 | 0 | } |
1764 | 0 | } |
1765 | | /* Free all paths. Any being returned to caller have already been freed */ |
1766 | 0 | for(i = 0; i <= Len; ++i) |
1767 | 0 | { |
1768 | 0 | Zp = Nodes[i].Paths; |
1769 | 0 | while(Zp) |
1770 | 0 | { |
1771 | 0 | ZxcMatch_t *p = Zp->Next; |
1772 | 0 | FreeFn(Zp); |
1773 | 0 | Zp = p; |
1774 | 0 | } |
1775 | 0 | } |
1776 | 0 | FreeFn(Nodes); |
1777 | 0 | return e; |
1778 | 0 | } |
1779 | | |
1780 | | /********************************************************************************** |
1781 | | * Free the path info returned by ZxcvbnMatch(). |
1782 | | */ |
1783 | | void ZxcvbnFreeInfo(ZxcMatch_t *Info) |
1784 | 0 | { |
1785 | 0 | ZxcMatch_t *p; |
1786 | 0 | while(Info) |
1787 | 0 | { |
1788 | 0 | p = Info->Next; |
1789 | 0 | FreeFn(Info); |
1790 | 0 | Info = p; |
1791 | 0 | } |
1792 | 0 | } |