summaryrefslogtreecommitdiff
path: root/test/ka-tester.c
blob: 7d1ad15f816d3b6dacc745faf08aa809398651f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/* Test crypt() API with "known answer" hashes.

   Written by Zack Weinberg <zackw at panix.com> in 2019.
   To the extent possible under law, Zack Weinberg has waived all
   copyright and related or neighboring rights to this work.

   See https://creativecommons.org/publicdomain/zero/1.0/ for further
   details.  */

#include "crypt-port.h"

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

/* The precalculated hashes in ka-table.inc, and some of the
   relationships among groups of test cases (see ka-table-gen.py)
   are invalidated if the execution character set is not ASCII.  */
static_assert(' ' == 0x20 && 'C' == 0x43 && '~' == 0x7E,
              "Execution character set does not appear to be ASCII");

/* This test verifies three things at once:
    - crypt, crypt_r, crypt_rn, and crypt_ra
      all produce the same outputs for the same inputs.
    - given hash <- crypt(phrase, setting),
       then hash == crypt(phrase, hash) also.
    - crypt(phrase, setting) == crypt'(phrase, setting)
      where crypt' is an independent implementation of the same
      hashing method.  (This is the "known answer" part of the test.)

   The independent implementations come from the Python 'passlib'
   library: <https://passlib.readthedocs.io/en/stable/>.
   See ka-table-gen.py for more detail.

   This file is compiled once for each hash, with macros defined that
   make ka-table.inc expose only the subset of the tests that are
   relevant to that hash.  This allows the test driver to run the
   known-answer tests for each enabled hash in parallel.  */

struct testcase
{
  const char *salt;
  const char *expected;
  const char *input;
};

static const struct testcase tests[] =
{
#include "ka-table.inc"

  /* Sentinel.  */
  { 0, 0, 0 },
};

/* Print out a string, using \xXX escapes for any characters that are
   not printable ASCII.  Backslash, single quote, and double quote are
   also escaped, by preceding them with another backslash.  If machine-
   parsing the output, note that we use the Python semantics of \x, not
   the C semantics: each \x consumes _exactly two_ subsequent hex digits.
   (For instance, \x123 means 0x12 0x33.)  */
static void
print_escaped (const char *s)
{
  const unsigned char *p = (const unsigned char *)s;
  for (; *p; p++)
    {
      unsigned char c = *p;
      if (c == '\\' || c == '\"' || c == '\'')
        {
          putchar ('\\');
          putchar (c);
        }
      else if (0x20 <= c && c <= 0x7E)
        putchar (c);
      else
        printf ("\\x%02x", (unsigned int)c);
    }
}

/* Subroutine of report_result.  */
static void
begin_error_report (const struct testcase *tc, const char *tag)
{
  printf ("FAIL: %s/", tc->salt);
  print_escaped (tc->input);
  printf (": %s ", tag);
}

/* Summarize the result of a single hashing operation.
   If everything is as expected, prints nothing and returns 0.
   Otherwise, prints a diagnostic message to stdout (not stderr!)
   and returns 1.  */
static int
report_result (const char *tag, const char *hash, int errnm,
               const struct testcase *tc, bool expect_failure_tokens)
{
  if (hash && hash[0] != '*')
    {
      /* We don't look at errno in this branch, because errno is
         allowed to be set by successful operations.  */
      if (!strcmp (hash, tc->expected))
        return 0;

      begin_error_report (tc, tag);
      printf ("mismatch: expected %s got %s\n", tc->expected, hash);
      return 1;
    }
  else
    {
      /* Ill-formed setting string arguments to 'crypt' are tested in a
         different program, so we never _expect_ a failure.  However, if
         we do get a failure, we want to log it in detail.  */
      begin_error_report (tc, tag);

      if (hash == 0)
        printf ("failure: got (null)");
      else
        printf ("failure: got %s", hash);

      /* errno should have been set.  */
      if (errnm)
        printf (", errno = %s", strerror (errnm));
      else
        printf (", errno not set");

      /* Should the API used have generated a NULL or a failure token?  */
      if (hash == 0 && expect_failure_tokens)
        printf (", failure token not generated");
      if (hash != 0 && !expect_failure_tokens)
        printf (", failure token wrongly generated");

      /* A failure token must never compare equal to the setting string
         that was used in the computation.  N.B. recrypt uses crypt_rn,
         which never produces failure tokens, so in this branch we can
         safely assume that the setting string used was tc->salt
         (if it generates one anyway that's an automatic failure).  */
      if (hash != 0 && !strcmp (tc->salt, hash))
        printf (", failure token == salt");

      putchar ('\n');
      return 1;
    }
}

static int
calc_hashes_crypt (void)
{
  char *hash;
  const struct testcase *t;
  int status = 0;

  for (t = tests; t->input != 0; t++)
    {
      errno = 0;
      hash = crypt (t->input, t->salt);
      status |= report_result ("crypt", hash, errno, t,
                               ENABLE_FAILURE_TOKENS);
    }

  return status;
}

static int
calc_hashes_crypt_r_rn (void)
{
  char *hash;
  union
  {
    char pass[CRYPT_MAX_PASSPHRASE_SIZE + 1];
    int aligned;
  } u;
  const struct testcase *t;
  struct crypt_data data;
  int status = 0;

  memset (&data, 0, sizeof data);
  memset (u.pass, 0, CRYPT_MAX_PASSPHRASE_SIZE + 1);
  for (t = tests; t->input != 0; t++)
    {
      strncpy(u.pass + 1, t->input, CRYPT_MAX_PASSPHRASE_SIZE);
      printf("[%zu]: %s %s\n", strlen(t->input),
             t->input, t->salt);
      errno = 0;
      hash = crypt_r (u.pass + 1, t->salt, &data);
      status |= report_result ("crypt_r", hash, errno, t,
                               ENABLE_FAILURE_TOKENS);

      errno = 0;
      hash = crypt_rn (u.pass + 1, t->salt, &data, (int)sizeof data);
      status |= report_result ("crypt_rn", hash, errno, t, false);
    }

  return status;
}

static int
calc_hashes_crypt_ra_recrypt (void)
{
  char *hash;
  const struct testcase *t;
  void *datap = 0;
  int datasz = 0;
  int status = 0;

  for (t = tests; t->input != 0; t++)
    {
      errno = 0;
      hash = crypt_ra (t->input, t->salt, &datap, &datasz);
      if (report_result ("crypt_ra", hash, errno, t, false))
        status = 1;
      else
        {
          /* if we get here, we know hash == t->expected */
          errno = 0;
          hash = crypt_ra (t->input, t->expected,
                           &datap, &datasz);
          status |= report_result ("recrypt", hash, errno, t, false);
        }
    }

  free (datap);
  return status;
}

int
main (void)
{
  int status = 0;

  /* Mark this test SKIPPED if the very first entry in the table is the
     sentinel; this happens only when the hash we would test is disabled.  */
  if (tests[0].input == 0)
    return 77;

  status |= calc_hashes_crypt ();
  status |= calc_hashes_crypt_r_rn ();
  status |= calc_hashes_crypt_ra_recrypt ();

  return status;
}