1 /* Test crypt() API with "known answer" hashes.
2
3 Written by Zack Weinberg <zackw at panix.com> in 2019.
4 To the extent possible under law, Zack Weinberg has waived all
5 copyright and related or neighboring rights to this work.
6
7 See https://creativecommons.org/publicdomain/zero/1.0/ for further
8 details. */
9
10 #include "crypt-port.h"
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <errno.h>
15
16 /* The precalculated hashes in ka-table.inc, and some of the
17 relationships among groups of test cases (see ka-table-gen.py)
18 are invalidated if the execution character set is not ASCII. */
19 static_assert(' ' == 0x20 && 'C' == 0x43 && '~' == 0x7E,
20 "Execution character set does not appear to be ASCII");
21
22 /* This test verifies three things at once:
23 - crypt, crypt_r, crypt_rn, and crypt_ra
24 all produce the same outputs for the same inputs.
25 - given hash <- crypt(phrase, setting),
26 then hash == crypt(phrase, hash) also.
27 - crypt(phrase, setting) == crypt'(phrase, setting)
28 where crypt' is an independent implementation of the same
29 hashing method. (This is the "known answer" part of the test.)
30
31 The independent implementations come from the Python 'passlib'
32 library: <https://passlib.readthedocs.io/en/stable/>.
33 See ka-table-gen.py for more detail.
34
35 This file is compiled once for each hash, with macros defined that
36 make ka-table.inc expose only the subset of the tests that are
37 relevant to that hash. This allows the test driver to run the
38 known-answer tests for each enabled hash in parallel. */
39
40 struct testcase
41 {
42 const char *salt;
43 const char *expected;
44 const char *input;
45 };
46
47 static const struct testcase tests[] =
48 {
49 #include "ka-table.inc"
50
51 /* Sentinel. */
52 { 0, 0, 0 },
53 };
54
55 /* Print out a string, using \xXX escapes for any characters that are
56 not printable ASCII. Backslash, single quote, and double quote are
57 also escaped, by preceding them with another backslash. If machine-
58 parsing the output, note that we use the Python semantics of \x, not
59 the C semantics: each \x consumes _exactly two_ subsequent hex digits.
60 (For instance, \x123 means 0x12 0x33.) */
61 static void
62 print_escaped (const char *s)
63 {
64 const unsigned char *p = (const unsigned char *)s;
65 for (; *p; p++)
66 {
67 unsigned char c = *p;
68 if (c == '\\' || c == '\"' || c == '\'')
69 {
70 putchar ('\\');
71 putchar (c);
72 }
73 else if (0x20 <= c && c <= 0x7E)
74 putchar (c);
75 else
76 printf ("\\x%02x", (unsigned int)c);
77 }
78 }
79
80 /* Subroutine of report_result. */
81 static void
82 begin_error_report (const struct testcase *tc, const char *tag)
83 {
84 printf ("FAIL: %s/", tc->salt);
85 print_escaped (tc->input);
86 printf (": %s ", tag);
87 }
88
89 /* Summarize the result of a single hashing operation.
90 If everything is as expected, prints nothing and returns 0.
91 Otherwise, prints a diagnostic message to stdout (not stderr!)
92 and returns 1. */
93 static int
94 report_result (const char *tag, const char *hash, int errnm,
95 const struct testcase *tc, bool expect_failure_tokens)
96 {
97 if (hash && hash[0] != '*')
98 {
99 /* We don't look at errno in this branch, because errno is
100 allowed to be set by successful operations. */
101 if (!strcmp (hash, tc->expected))
102 return 0;
103
104 begin_error_report (tc, tag);
105 printf ("mismatch: expected %s got %s\n", tc->expected, hash);
106 return 1;
107 }
108 else
109 {
110 /* Ill-formed setting string arguments to 'crypt' are tested in a
111 different program, so we never _expect_ a failure. However, if
112 we do get a failure, we want to log it in detail. */
113 begin_error_report (tc, tag);
114
115 if (hash == 0)
116 printf ("failure: got (null)");
117 else
118 printf ("failure: got %s", hash);
119
120 /* errno should have been set. */
121 if (errnm)
122 printf (", errno = %s", strerror (errnm));
123 else
124 printf (", errno not set");
125
126 /* Should the API used have generated a NULL or a failure token? */
127 if (hash == 0 && expect_failure_tokens)
128 printf (", failure token not generated");
129 if (hash != 0 && !expect_failure_tokens)
130 printf (", failure token wrongly generated");
131
132 /* A failure token must never compare equal to the setting string
133 that was used in the computation. N.B. recrypt uses crypt_rn,
134 which never produces failure tokens, so in this branch we can
135 safely assume that the setting string used was tc->salt
136 (if it generates one anyway that's an automatic failure). */
137 if (hash != 0 && !strcmp (tc->salt, hash))
138 printf (", failure token == salt");
139
140 putchar ('\n');
141 return 1;
142 }
143 }
144
145 static int
146 calc_hashes_crypt (void)
147 {
148 char *hash;
149 const struct testcase *t;
150 int status = 0;
151
152 for (t = tests; t->input != 0; t++)
153 {
154 errno = 0;
155 hash = crypt (t->input, t->salt);
156 status |= report_result ("crypt", hash, errno, t,
157 ENABLE_FAILURE_TOKENS);
158 }
159
160 return status;
161 }
162
163 static int
164 calc_hashes_crypt_r_rn (void)
165 {
166 char *hash;
167 union
168 {
169 char pass[CRYPT_MAX_PASSPHRASE_SIZE + 1];
170 int aligned;
171 } u;
172 const struct testcase *t;
173 struct crypt_data data;
174 int status = 0;
175
176 memset (&data, 0, sizeof data);
177 memset (u.pass, 0, CRYPT_MAX_PASSPHRASE_SIZE + 1);
178 for (t = tests; t->input != 0; t++)
179 {
180 strncpy(u.pass + 1, t->input, CRYPT_MAX_PASSPHRASE_SIZE);
181 printf("[%zu]: %s %s\n", strlen(t->input),
182 t->input, t->salt);
183 errno = 0;
184 hash = crypt_r (u.pass + 1, t->salt, &data);
185 status |= report_result ("crypt_r", hash, errno, t,
186 ENABLE_FAILURE_TOKENS);
187
188 errno = 0;
189 hash = crypt_rn (u.pass + 1, t->salt, &data, (int)sizeof data);
190 status |= report_result ("crypt_rn", hash, errno, t, false);
191 }
192
193 return status;
194 }
195
196 static int
197 calc_hashes_crypt_ra_recrypt (void)
198 {
199 char *hash;
200 const struct testcase *t;
201 void *datap = 0;
202 int datasz = 0;
203 int status = 0;
204
205 for (t = tests; t->input != 0; t++)
206 {
207 errno = 0;
208 hash = crypt_ra (t->input, t->salt, &datap, &datasz);
209 if (report_result ("crypt_ra", hash, errno, t, false))
210 status = 1;
211 else
212 {
213 /* if we get here, we know hash == t->expected */
214 errno = 0;
215 hash = crypt_ra (t->input, t->expected,
216 &datap, &datasz);
217 status |= report_result ("recrypt", hash, errno, t, false);
218 }
219 }
220
221 free (datap);
222 return status;
223 }
224
225 int
226 main (void)
227 {
228 int status = 0;
229
230 /* Mark this test SKIPPED if the very first entry in the table is the
231 sentinel; this happens only when the hash we would test is disabled. */
232 if (tests[0].input == 0)
233 return 77;
234
235 status |= calc_hashes_crypt ();
236 status |= calc_hashes_crypt_r_rn ();
237 status |= calc_hashes_crypt_ra_recrypt ();
238
239 return status;
240 }