diff --git a/.gitignore b/.gitignore index 9cc63a34..a58f63d9 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ /crypt.h /crypt-hashes.h /crypt-symbol-vers.h +/crypt-tune-costs /libcrypt.map gen-des-tables test/alg-des diff --git a/Makefile.am b/Makefile.am index c0d31439..a40f45c1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -52,7 +52,11 @@ notrans_dist_man3_MANS = \ doc/crypt_ra.3 \ doc/crypt_rn.3 notrans_dist_man5_MANS = \ - doc/crypt.5 + doc/crypt.5 \ + doc/crypt.conf.5 +notrans_dist_man8_MANS = \ + doc/crypt-checkconf.8 \ + doc/crypt-tune-costs.8 nodist_include_HEADERS = \ crypt.h @@ -124,6 +128,9 @@ libcrypt_la_SOURCES = \ lib/crypt.c \ lib/randombytes.c +sbin_PROGRAMS = crypt-tune-costs +crypt_tune_costs_LDADD = libcrypt.la -lm + pkgconfig_DATA = libxcrypt.pc # Install libcrypt.pc symlink to libxcrypt.pc file. diff --git a/crypt-tune-costs.c b/crypt-tune-costs.c new file mode 100644 index 00000000..161c7054 --- /dev/null +++ b/crypt-tune-costs.c @@ -0,0 +1,740 @@ +/* Benchmark hashing methods and set cost parameters appropriately. + * + * Written by Zack Weinberg in 2018. + * To the extent possible under law, the named authors have 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 +#include +#include +#include +#include +#include +#include + +enum hash_usage +{ + HU_PREFER, HU_ENABLED, HU_LEGACY, HU_DISABLED +}; + +static const char *const usage_keyword[] = { + "preferred", "enabled", "legacy", "disabled" +}; + +enum hash_cost_type +{ + HCT_EXPON, HCT_LINEAR, HCT_FIXED +}; + +struct hash_method +{ + const char *name; + const char *prefix; + enum hash_cost_type hct; + unsigned int minrounds; + unsigned int maxrounds; + enum hash_usage usage; + unsigned int nrounds; + double elapsed; +}; + +/* The 'usage', 'nrounds', and 'elapsed' fields of this table are + written to during execution. */ +static struct hash_method hash_methods[] = { + { "yescrypt", "$y$", HCT_EXPON, 1, 11, HU_PREFER, 0, 0 }, + { "gost-yescrypt", "$gy$", HCT_EXPON, 1, 11, HU_ENABLED, 0, 0 }, + { "scrypt", "$7$", HCT_EXPON, 6, 11, HU_ENABLED, 0, 0 }, + { "bcrypt", "$2b$", HCT_EXPON, 4, 31, HU_ENABLED, 0, 0 }, + { "bcrypt_a", "$2a$", HCT_EXPON, 4, 31, HU_LEGACY, 0, 0 }, + { "bcrypt_x", "$2x$", HCT_EXPON, 4, 31, HU_LEGACY, 0, 0 }, + { "bcrypt_y", "$2y$", HCT_EXPON, 4, 31, HU_LEGACY, 0, 0 }, + { "sha512crypt", "$6$", HCT_LINEAR, 1000, 999999999, HU_ENABLED, 0, 0 }, + { "sha256crypt", "$5$", HCT_LINEAR, 1000, 999999999, HU_ENABLED, 0, 0 }, + { "sha1crypt", "$sha1", HCT_LINEAR, 4, 4294967295, HU_LEGACY, 0, 0 }, + { "sunmd5", "$md5", HCT_LINEAR, 4096, 4294963199, HU_LEGACY, 0, 0 }, + { "md5crypt", "$1$", HCT_FIXED, 0, 0, HU_LEGACY, 0, 0 }, + { "bsdicrypt", "_", HCT_LINEAR, 1, 16777215, HU_DISABLED, 0, 0 }, + { "bigcrypt", "", HCT_FIXED, 0, 0, HU_DISABLED, 0, 0 }, + { "descrypt", "", HCT_FIXED, 0, 0, HU_DISABLED, 0, 0 }, + { "nt", "$3$", HCT_FIXED, 0, 0, HU_DISABLED, 0, 0 }, +}; + +static const char *program_name; +static int verbosity = 0; + +static double +time_crypt (struct hash_method *method, unsigned int nrounds) +{ + /* We use 32 bytes of zeroes for the randomness because some hash + methods' gensalt routines use the randomness to perturb the + rounds parameter as well, which makes linear approximation not + work like it ought to. */ + static const char rbytes[32] = { 0 }; + static const char phrase[] = + "it has been 39 days since our last pie-related accident"; + + char setting[CRYPT_GENSALT_OUTPUT_SIZE]; + if (crypt_gensalt_rn (method->prefix, nrounds, rbytes, (int) sizeof rbytes, + setting, (int) sizeof setting) != setting) + { + fprintf (stderr, "%s: crypt_gensalt_rn: %s\n", + method->name, strerror (errno)); + return 0.0; + } + + if (verbosity >= 3) + fprintf (stderr, "# %s: setting %s\n", method->name, setting); + + struct crypt_data data; + memset (&data, 0, sizeof data); + + /* Reduce jitter for small rounds parameters by repeating the + crypt_rn call until we have consumed at least 10ms total. */ + double elapsed = 0.0; + unsigned int iterations = 0; + do + { + struct timespec start, stop; + + if (clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &start)) + { + fprintf (stderr, "%s: clock_gettime: %s\n", + method->name, strerror (errno)); + return 0.0; + } + + if (crypt_rn (phrase, setting, &data, (int) sizeof data) == 0) + { + fprintf (stderr, "%s: crypt_rn: %s\n", + method->name, strerror (errno)); + return 0.0; + } + + if (clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &stop)) + { + fprintf (stderr, "%s: clock_gettime: %s\n", + method->name, strerror (errno)); + return 0.0; + } + + /* explicit casts silence -Wconversion */ + elapsed += ((double)(stop.tv_sec - start.tv_sec) + + ((double)stop.tv_nsec) * 1e-9 + - ((double)start.tv_nsec) * 1e-9) * 1000; + iterations++; + } + while (elapsed < 10.0); + elapsed /= iterations; + + if (verbosity >= 1) + fprintf (stderr, "# %s: %6.2fms for %u rounds (%u iteration%s)\n", + method->name, elapsed, nrounds, iterations, + iterations == 1 ? "" : "s"); + + return elapsed; +} + +/* Given vectors X and Y, find scalars m and b that robustly fit a + line through all of the points (x_i, y_i). It is assumed that + there are no duplicates among the x_i. Since this function is only + ever called with n <= 10 (see below), we can get away with the + naive quadratic algorithm for computing the Thiel-Sen estimator. + + Simpler methods (basic successive approximation, ordinary least + squares) were tried but found to converge too slowly, because the + measurement for a small cost parameter (near minrounds) tends to be + noisy and thus a poor estimate of the required cost parameter. The + primary design goal here is to minimize the number of approximation + iterations using a large cost parameter, since that's what dominates + runtime. */ + +static int +compar_double(const void *a, const void *b) +{ + double x = *(const double *)a; + double y = *(const double *)b; + if (x < y) return -1; + if (x > y) return 1; + return 0; +} + +static void +robust_linear_approx(const double *restrict xs, + const double *restrict ys, + size_t n, + double *restrict m_out, + double *restrict b_out) +{ + assert (1 <= n && n <= 10); + double m, b; + if (n == 1) + { + /* If we only have one point in the sample, make the line go + through the origin. */ + m = ys[0] / xs[0]; + b = 0; + } + else + { + double slopes[100]; + size_t nslopes = 0; + for (size_t i = 0; i < n; i++) + for (size_t j = i + 1; j < n; j++) + slopes[nslopes++] = (ys[j] - ys[i]) / (xs[j] - xs[i]); + qsort (slopes, nslopes, sizeof (double), compar_double); + + m = slopes[nslopes / 2]; + + double resid[10]; + for (size_t i = 0; i < n; i++) + resid[i] = ys[i] - m*xs[i]; + qsort (resid, n, sizeof (double), compar_double); + b = resid[n / 2]; + } + + if (verbosity >= 2) + { + double tau = 0.0; + if (n >= 2) + { + for (size_t i = 0; i < n; i++) + for (size_t j = i + 1; j < n; j++) + { + double ri = ys[i] - (m * xs[i] + b); + double rj = ys[j] - (m * xs[j] + b); + double sr = (ri == rj) ? 0 : (ri < rj) ? -1 : 1; + double sx = (xs[i] == xs[j]) ? 0 : (xs[i] < xs[j]) ? -1 : 1; + tau += sr * sx; + } + tau = (tau * 2) / (double)(n * (n - 1)); + } + fprintf (stderr, + "# T-S (%zu point%s): y = %6.2f * x + %6.2f, resid. tau = %.4f\n", + n, n == 1 ? "" : "s", m, b, tau); + } + + *m_out = m; + *b_out = b; +} + +static void +tune_linear_cost (struct hash_method *method, double elapsed_target) +{ + /* One of the linear methods requires the number of rounds to be + odd; rather than special case it, we use only odd numbers for + all methods. */ + unsigned int minrounds, maxrounds, nrounds; + minrounds = method->minrounds; + if (minrounds % 2 == 0) + minrounds++; + + maxrounds = method->maxrounds; + if (maxrounds % 2 == 0) + maxrounds--; + + /* Start from near, but not actually at, the bottom. */ + nrounds = 10001; + if (nrounds < minrounds) + nrounds = minrounds; + if (nrounds > maxrounds) + nrounds = maxrounds; + + /* Record up to the previous ten measurements in a circular + buffer. */ + size_t npoints = 0, ipoints = 0; + double ns[10], es[10]; + double elapsed; + unsigned int new_nrounds; + + for (;;) + { + elapsed = time_crypt (method, nrounds); + if (elapsed < elapsed_target) + minrounds = nrounds; + else if (elapsed > 1.025 * elapsed_target) + maxrounds = nrounds; + else + /* We're within 2.5%, that's good enough. */ + break; + + /* If there is no remaining room for adjustment, stop. */ + if (minrounds + 2 >= maxrounds) + break; + + ns[ipoints] = nrounds; + es[ipoints] = elapsed; + ipoints = (ipoints + 1) % ARRAY_SIZE (ns); + npoints = MIN (npoints + 1, ARRAY_SIZE (ns)); + + /* Predicting nrounds as a function of elapsed, instead of the + other way around, means we don't need to invert the result to + pick the next value of nrounds. */ + double m, b; + robust_linear_approx (es, ns, npoints, &m, &b); + + new_nrounds = (unsigned int)(m * elapsed_target + b); + new_nrounds |= 1; + + /* If the new estimate is the same as the value we just tried, + go up or down by two, depending on whether we're below or + above the target. */ + if (new_nrounds == nrounds) + { + if (elapsed < elapsed_target) + new_nrounds += 2; + else + new_nrounds -= 2; + } + if (new_nrounds > maxrounds) + new_nrounds = maxrounds; + if (new_nrounds < minrounds) + new_nrounds = minrounds; + + nrounds = new_nrounds; + } + + method->nrounds = nrounds; + method->elapsed = elapsed; +} + +static void +tune_expon_cost (struct hash_method *method, double elapsed_target) +{ + unsigned int minrounds, maxrounds, nrounds; + minrounds = method->minrounds; + maxrounds = method->maxrounds; + + /* Start from the bottom. */ + nrounds = minrounds; + + /* Record up to the previous ten measurements in a circular buffer. + Log-transform the elapsed times so we can fit the cost-time + relationship linearly. (The base of the logarithm doesn't + matter; it just changes the slope of the line, and that cancels + back out when we predict the desired cost value.) */ + size_t npoints = 0, ipoints = 0; + double ns[10], es[10]; + double elapsed; + double log_elapsed_target = log (elapsed_target); + unsigned int new_nrounds; + + for (;;) + { + /* If we overshoot by too much the program may appear to hang, so + report the number of rounds first to give the user an idea of + what might be wrong. */ + if (verbosity >= 1) + fprintf (stderr, "# %s: trying %u rounds\n", method->name, nrounds); + elapsed = time_crypt (method, nrounds); + double log_elapsed = log (elapsed); + if (log_elapsed < log_elapsed_target) + minrounds = nrounds; + else if (log_elapsed > 1.025 * log_elapsed_target) + maxrounds = nrounds; + else + /* We're within 2.5%, that's good enough. */ + break; + + /* If there is no remaining room for adjustment, stop. */ + if (minrounds + 1 >= maxrounds) + break; + + ns[ipoints] = nrounds; + es[ipoints] = log (elapsed); + ipoints = (ipoints + 1) % ARRAY_SIZE (ns); + npoints = MIN (npoints + 1, ARRAY_SIZE (ns)); + + if (npoints == 1) + { + /* Just go up by one; the prediction with a single point + will be garbage, because we can't estimate the intercept. */ + new_nrounds = nrounds + 1; + } + else + { + /* Predicting nrounds as a function of elapsed, instead of the + other way around, means we don't need to invert the result to + pick the next value of nrounds. */ + double m, b; + robust_linear_approx (es, ns, npoints, &m, &b); + + new_nrounds = (unsigned int)(m * log_elapsed_target + b); + } + + /* If the new estimate is the same as the value we just tried, + go up or down by one, depending on whether we're below or + above the target. */ + if (new_nrounds == nrounds) + { + if (elapsed < elapsed_target) + new_nrounds++; + else + new_nrounds--; + } + if (new_nrounds > maxrounds) + new_nrounds = maxrounds; + if (new_nrounds < minrounds) + new_nrounds = minrounds; + + nrounds = new_nrounds; + } + + method->elapsed = elapsed; + method->nrounds = nrounds; +} + +static void +tune_cost (struct hash_method *method, double elapsed_target, bool strict) +{ + if (method->usage == HU_LEGACY || method->usage == HU_DISABLED) + { + if (verbosity >= 1) + fprintf (stderr, "# %s: %s, skipping\n", + method->name, usage_keyword[method->usage]); + return; + } + if (method->hct == HCT_FIXED) + { + if (verbosity >= 1) + fprintf (stderr, "# %s: fixed cost\n", method->name); + method->nrounds = 0; + method->elapsed = time_crypt (method, 0); + } + else if (method->hct == HCT_LINEAR) + { + if (verbosity >= 1) + fprintf (stderr, "# %s: linear cost\n", method->name); + tune_linear_cost (method, elapsed_target); + } + else /* HCT_EXPON */ + { + if (verbosity >= 1) + fprintf (stderr, "# %s: exponential cost\n", method->name); + tune_expon_cost (method, elapsed_target); + } + + if (method->elapsed < elapsed_target) + method->usage = strict ? HU_DISABLED : HU_LEGACY; +} + +static void +tune_all_costs (double elapsed_target, bool strict) +{ + size_t i; + for (i = 0; i < ARRAY_SIZE (hash_methods); i++) + tune_cost (&hash_methods[i], elapsed_target, strict); + + /* Make sure that at least one hashing method is still enabled, and + one hashing method is preferred. */ + static_assert (ARRAY_SIZE (hash_methods) <= INT_MAX, + "too many hash methods for 'int'"); + int first_enabled = -1; + + for (i = 0; i < ARRAY_SIZE (hash_methods); i++) + if (hash_methods[i].usage == HU_PREFER) + return; + else if (hash_methods[i].usage == HU_ENABLED && first_enabled == -1) + first_enabled = (int)i; + + /* If we get here, no method was preferred. + Are there any enabled methods at all? */ + if (first_enabled == -1) + { + fprintf (stderr, "%s: no enabled hashing method can take %6.2fms\n", + program_name, elapsed_target); + exit (1); + } + + /* The hash_methods table is in descending order of cryptographic + strength, so when no explicit selection was made, use the first + method that's enabled as the preferred method. */ + hash_methods[first_enabled].usage = HU_PREFER; +} + +static void +write_config (void) +{ + fputs ("# crypt.conf generated by crypt-tune-costs.\n" + "# Rounds settings are tuned for this computer.\n\n", + stdout); + for (size_t i = 0; i < ARRAY_SIZE (hash_methods); i++) + if (hash_methods[i].nrounds != 0) + printf ("%-12s%-12srounds=%u\t# %6.2fms\n", + hash_methods[i].name, + usage_keyword[hash_methods[i].usage], + hash_methods[i].nrounds, + hash_methods[i].elapsed); + else + printf ("%-12s%s\n", + hash_methods[i].name, + usage_keyword[hash_methods[i].usage]); + + if (fflush (stdout) || fclose (stdout)) + { + fprintf (stderr, "%s: stdout: %s\n", program_name, strerror (errno)); + exit (1); + } +} + +static NORETURN PRINTF_FMT(1,0) +print_usage (const char *errmsg, ...) +{ + FILE *dest; + if (errmsg) + { + va_list ap; + fprintf (stderr, "%s: ", program_name); + va_start (ap, errmsg); + vfprintf (stderr, errmsg, ap); + va_end (ap); + fputc ('\n', stderr); + dest = stderr; + } + else + dest = stdout; + + fprintf (dest, "Usage: %s [OPTION]...\n", program_name); + fputs ("Choose cost parameters for passphrase hashing.\n" +"Writes a tuned crypt.conf to stdout.\n" +"\n" +" -t MS, --time=MS Try to make each hashing method take MS\n" +" milliseconds (default 250).\n" +" -p METHOD, --preferred=METHOD Use METHOD as the preferred method for\n" +" hashing new passphrases.\n" +" -e M,M,...; --enabled=M,M,... Allow each method M both for hashing new\n" +" passphrases and for authentication against\n" +" existing hashes.\n" +" -l M,M,...; --legacy=M,M,... Allow each method M only for authentication\n" +" against existing hashes, and don't bother\n" +" choosing cost parameters for them.\n" +" -d M,M,...; --disabled=M,M,... Don't allow each method M to be used at all.\n" +" -s, --strict Disable methods that cannot be made to take\n" +" the specified amount of time, instead of\n" +" allowing them for authentication against\n" +" existing hashes.\n" +" -v, --verbose Report on the process of searching for\n" +" appropriate cost parameters, to stderr.\n" +" Repeat -v to increase verbosity level.\n" +"\n" +" -h, --help Display this help message and exit.\n" +" -V, --version Output version information and exit.\n" +"\n" +"For complete documentation, 'man crypt-tune-costs'.\n" + , dest); + + fprintf (dest, "xcrypt homepage: %s\n", PACKAGE_URL); + exit (errmsg ? 1 : 0); +} + +static NORETURN +print_version (void) +{ + printf ("%s (%s) %s\n" + "Homepage: %s\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n", + program_name, PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_URL); + exit (0); +} + +static void +parse_time (char *text, double *value_p) +{ + char *endp; + double result; + + errno = 0; + result = strtod (text, &endp); + if (endp == text || *endp != '\0') + print_usage ("malformed argument for '--time' (must be a decimal number)"); + if (errno || result <= 0 || result >= 10 * 1000 || result != result) + print_usage ("argument for '--time' out of range (> 0, < 10,000 ms)"); + + *value_p = result; +} + +static void +parse_hash_usage (char *text, enum hash_usage usage) +{ + if (!strcmp (text, "all")) + { + for (size_t i = 0; i < ARRAY_SIZE (hash_methods); i++) + hash_methods[i].usage = usage; + } + else + for (char *tok = strtok (text, ","); tok; tok = strtok (0, ",")) + { + bool found = false; + for (size_t i = 0; i < ARRAY_SIZE (hash_methods); i++) + if (!strcmp (tok, hash_methods[i].name)) + { + found = true; + hash_methods[i].usage = usage; + break; + } + if (!found) + print_usage ("unrecognized hash method name '%s'", tok); + } +} + +/* Macro subroutines of parse_command_line. */ + +#define LONG_OPTION(name, action) \ + do { \ + if (!strcmp (argv[i] + 2, name)) \ + { \ + action; \ + goto next_arg; \ + } \ + } while (0) + +#define LONG_OPTION_WITH_ARG(name, parse_arg, ...) \ + do { \ + if (!strncmp (argv[i] + 2, name, sizeof name - 1)) \ + { \ + char *optarg = argv[i] + 2 + sizeof name - 1; \ + if (optarg[0] == '=' && optarg[1] != '\0') \ + { \ + parse_arg (optarg + 1, __VA_ARGS__); \ + goto next_arg; \ + } \ + else if (optarg[0] == '\0' && i + 1 < argc) \ + { \ + i++; \ + parse_arg (argv[i], __VA_ARGS__); \ + goto next_arg; \ + } \ + else if ((optarg[0] == '=' && optarg[1] == '\0') || \ + (optarg[0] == '\0' && i + 1 >= argc)) \ + { \ + print_usage ("'--%s' requires an argument", name); \ + } \ + else \ + print_usage ("unrecognized option '%s'", argv[i]); \ + } \ + } while (0) + +#define SHORT_OPTION(letter, action) \ + do { \ + if (argv[i][j] == letter) \ + { \ + action; \ + goto next_char; \ + } \ + } while (0) + +#define SHORT_OPTION_WITH_ARG(letter, parse_arg, ...) \ + do { \ + if (argv[i][j] == letter) \ + { \ + if (argv[i][j+1] != '\0') \ + { \ + parse_arg (argv[i] + j + 1, __VA_ARGS__); \ + goto next_arg; \ + } \ + else if (i + 1 < argc) \ + { \ + i++; \ + parse_arg (argv[i], __VA_ARGS__); \ + goto next_arg; \ + } \ + else \ + print_usage ("'-%c' requires an argument", letter); \ + } \ + } while (0) + +static void +parse_command_line (int argc, char **argv, + double *elapsed_target_p, bool *strict_p) +{ + for (int i = 1; i < argc; i++) + { + if (argv[i][0] != '-' || argv[i][1] == '\0') + print_usage ("no non-option arguments are accepted"); + + if (argv[i][1] == '-') + { + LONG_OPTION ("strict", (*strict_p = true)); + LONG_OPTION ("verbose", (verbosity++)); + LONG_OPTION ("version", (print_version ())); + LONG_OPTION ("help", (print_usage (0))); + + LONG_OPTION_WITH_ARG ("time", parse_time, elapsed_target_p); + LONG_OPTION_WITH_ARG ("preferred", parse_hash_usage, HU_PREFER); + LONG_OPTION_WITH_ARG ("enabled", parse_hash_usage, HU_ENABLED); + LONG_OPTION_WITH_ARG ("legacy", parse_hash_usage, HU_LEGACY); + LONG_OPTION_WITH_ARG ("disabled", parse_hash_usage, HU_DISABLED); + + print_usage ("unrecognized option '%s'", argv[i]); + } + else + for (int j = 1; argv[i][j]; j++) + { + SHORT_OPTION ('s', (*strict_p = true)); + SHORT_OPTION ('v', (verbosity++)); + SHORT_OPTION ('V', (print_version ())); + SHORT_OPTION ('h', (print_usage (0))); + + SHORT_OPTION_WITH_ARG ('t', parse_time, elapsed_target_p); + SHORT_OPTION_WITH_ARG ('p', parse_hash_usage, HU_PREFER); + SHORT_OPTION_WITH_ARG ('e', parse_hash_usage, HU_ENABLED); + SHORT_OPTION_WITH_ARG ('l', parse_hash_usage, HU_LEGACY); + SHORT_OPTION_WITH_ARG ('d', parse_hash_usage, HU_DISABLED); + + print_usage ("unrecognized option '-%c'", argv[i][j]); + + next_char:; + } + next_arg:; + } + + /* Sanity check the effects of the various method-configuration options. */ + int n_preferred = 0; + int n_enabled = 0; + for (size_t i = 0; i < ARRAY_SIZE (hash_methods); i++) + { + if (hash_methods[i].usage == HU_PREFER) + { + n_preferred++; + n_enabled++; + } + else if (hash_methods[i].usage == HU_ENABLED) + n_enabled++; + } + if (n_enabled == 0) + print_usage ("no hashing methods are enabled"); + if (n_preferred > 1) + print_usage ("only one hashing method can be preferred"); +} +#undef LONG_OPTION +#undef LONG_OPTION_WITH_ARG +#undef SHORT_OPTION +#undef SHORT_OPTION_WITH_ARG + +int +main (int argc, char **argv) +{ + if (argc > 0) + { + program_name = strrchr (argv[0], '/'); + if (program_name) + program_name += 1; + else + program_name = argv[0]; + } + if (program_name[0] == 0) + program_name = "crypt-tune-costs"; + + double elapsed_target = 250.0; /* milliseconds */ + bool strict = false; + parse_command_line (argc, argv, &elapsed_target, &strict); + tune_all_costs (elapsed_target, strict); + write_config (); + return 0; +} diff --git a/doc/crypt-checkconf.8 b/doc/crypt-checkconf.8 new file mode 100644 index 00000000..ac50112b --- /dev/null +++ b/doc/crypt-checkconf.8 @@ -0,0 +1,96 @@ +.\" Written by Zack Weinberg in 2018. +.\" +.\" To the extent possible under law, the authors have waived +.\" all copyright and related or neighboring rights to this work. +.\" See https://creativecommons.org/publicdomain/zero/1.0/ for further +.\" details. +.\" +.Dd August 14, 2018 +.Dt CRYPT-CHECKCONF 8 +.Os "libxcrypt" +.Sh NAME +.Nm crypt-checkconf +.Nd validate crypt.conf +.Sh SYNOPSIS +.Nm +.Op Fl m +.Op Ar file +.Sh DESCRIPTION +The +.Nm +utility validates a file in the format of +.Xr crypt.conf 5 . +If a +.Ar file +is given on the command line, it validates that file; +otherwise, it validates +.Pa /etc/security/crypt.conf . +.Ar file +may be specified as +.Sq Li \&- +to read standard input. +Problems found are reported to standard error. +.Pp +The following problems are detected: +.Bl -bullet +.It +Lines containing syntactic garbage or unprintable characters. +Characters with the 8th bit set are allowed, but only in comments. +.It +Unrecognized hashing method names. +.It +Unrecognized values for the +.Ar allowed use +field. +.It +.Ar key Ns = Ns Ar value +fields with an unrecognized +.Ar key . +.It +Invalid values for the +.Ar rounds +parameter, either syntactically or because they do not meet the +restrictions for the hashing method. +.El +.Sh OPTIONS +.Bl -tag -width 2m +.It Fl m , \-merge\-defaults +Merge all of the valid lines in the input file +with the compiled-in defaults, +and write the result to standard output. +This produces a configuration file +with the same behavior as the original, +but having explicit settings for each hashing method. +Comments are preserved. +Lines with problems are echoed as comments. +.Pp +To print out just the compiled-in defaults, +ignoring whatever might be in +.Pa /etc/security/crypt.conf , +use +.Pp +.Dl crypt-checkconf -m /dev/null +.El +.Sh EXIT STATUS +The exit status is 0 if no problems were found, 1 if at least one problem +was found, and 2 if a system error occurred. +.Pp +Note that if no +.Ar file +is given on the command line, and +.Pa /etc/crypt.conf +does not exist, that is +.Em not +considered to be an error, because +.Fn crypt +and +.Fn crypt_gensalt +treat a nonexistent +.Pa /etc/security/crypt.conf +the same as if it were empty. +.Sh SEE ALSO +.Xr crypt 3 , +.Xr crypt_gensalt 3 , +.Xr crypt 5 , +.Xr crypt.conf 5 , +.Xr crypt\-tune\-costs 8 diff --git a/doc/crypt-tune-costs.8 b/doc/crypt-tune-costs.8 new file mode 100644 index 00000000..dc2678b0 --- /dev/null +++ b/doc/crypt-tune-costs.8 @@ -0,0 +1,161 @@ +.\" Written by Zack Weinberg in 2018. +.\" +.\" To the extent possible under law, the authors have waived +.\" all copyright and related or neighboring rights to this work. +.\" See https://creativecommons.org/publicdomain/zero/1.0/ for further +.\" details. +.\" +.Dd August 14, 2018 +.Dt CRYPT-TUNE-COSTS 8 +.Os "libxcrypt" +.Sh NAME +.Nm crypt-tune-costs +.Nd choose cost parameters for passphrase hashing +.Sh SYNOPSIS +.Nm +.Op Fl sv +.Op Fl t Ar milliseconds +.Op Fl T Ar milliseconds +.Op Fl p Ar method +.Op Fl e Ar method , Ns Ar method , Ns .\|.\|. +.Op Fl l Ar method , Ns Ar method , Ns .\|.\|. +.Op Fl d Ar method , Ns Ar method , Ns .\|.\|. +.Sh DESCRIPTION +The +.Nm +utility measures the speed of all the hashing methods supported by +.Xr crypt 3 , +and selects cost parameters that will make each of them slow enough +to impose significant costs on brute-force attackers, +while not annoying legitimate users. +Hashing methods that cannot be made to be slow enough +will be marked as +.Em legacy , +to be used only for authentication against existing hashes. +.Pp +.Nm +writes a configuration file suitable for use as +.Pa /etc/security/crypt.conf +(see +.Xr crypt.conf 5 ) +to its standard output. +.Sh OPTIONS +.Bl -tag -width 2m +.It Fl s , \-strict +Disable hashing methods that cannot be made to take the +specified amount of time, instead of marking them as legacy. +.It Fl v , \-verbose +Write detailed information about the process of searching for +appropriate cost parameters to standard error. +.It Fl t Ar milliseconds ; Fl \-time Ns = Ns Ar milliseconds +Set the +.Sy defcost +parameter for each hashing method so that it will take approximately +.Ar milliseconds +milliseconds to hash a passphrase of typical length. +The number supplied must be positive, and may be either an integer or +a decimal fraction. +The default is 250 milliseconds. +.It Fl T Ar milliseconds ; Fl \-maxtime Ns = Ns Ar milliseconds +Set the +.Sy maxcost +parameter for each hashing method so that it will not be allowed to +take longer than +.Ar milliseconds +milliseconds to hash a passphrase of typical length. +This prevents accidental or malicious denial of service due to +over-large cost parameters. +The default is 10,000 milliseconds (10 seconds). +.It Fl p Ar method ; Fl \-preferred Ns = Ns Ar method +Use +.Ar method +as the preferred hashing method for new passphrases; that is, +.Nm crypt_gensalt +will generate a setting string for this method when its +.Ar prefix +argument is NULL. +.Fl p Ar method +implies +.Fl e Ar method . +.It Fl e Ar method , Ns Ar method , Ns .\|.\|. ; Fl \-enabled Ns = Ns Ar method , Ns Ar method , Ns .\|.\|. +Use each +.Ar method +for authentication against existing hashes, +and allow them to be used in new hashes. +.Pp +.Fl e Ar all +means to enable all known hashing methods. +.It Fl l Ar method , Ns Ar method , Ns .\|.\|. ; Fl \-legacy Ns = Ns Ar method , Ns Ar method , Ns .\|.\|. +Use each +.Ar method +only for authentication against existing passphrase hashes; +.Nm crypt_gensalt +will refuse to generate new setting strings for them. +.Nm +will not bother selecting cost parameters for these hashes. +.Pp +.Fl l Ar all +means to mark all known hashing methods as legacy. +.It Fl d Ar method , Ns Ar method , Ns .\|.\|. ; Fl \-disabled Ns = Ns Ar method , Ns Ar method , Ns .\|.\|. +Do not allow any of the +.Ar method Ns s +to be used at all; both +.Nm crypt_gensalt +and +.Nm crypt +will fail if directed to use them. +.Nm +will not bother selecting cost parameters for these hashes. +.Pp +.Fl d Ar all +means to disable all known hashing methods. +.Pp +.Em Caution: +Users whose passphrases were hashed using a disabled method +will not be able to log in with a passphrase. +If they log in some other way +(e.g.\& an SSH key) +they will not be able to +.Em change +their passphrase, because +.Xr passwd 1 +will not be able to validate their old passphrase. +.El +.Pp +The +.Fl p , +.Fl e , +.Fl l , +and +.Fl d +options may be given any number of times and in any order. +If more than one of these options is given for the same +.Ar method , +the last one on the command line wins. +.Sh EXIT STATUS +The exit status is 0 if the generated +.Pa crypt.conf +has at least one hashing method enabled for new passphrases. +It is 1 if it does not, or if a system error occurred. +.Sh EXAMPLES +Select cost parameters for all hashes that are enabled by default: +.Pp +.Dl # crypt-tune-costs > /etc/crypt.conf +.Pp +Select cost parameters for yescrypt, which will be used as the +default hash; mark all other hashes as disabled: +.Pp +.Dl # crypt-tune-costs -d all -p yescrypt > /etc/crypt.conf +.Pp +Find cost parameters that will make all hashes take approximately 2 seconds. +Disable all hashes that cannot be made to take this long. +Display detailed progress information, and dump the generated +.Pa crypt.conf +to the terminal: +.Pp +.Dl $ crypt-tune-costs -e all -m 2000 -vs +.Sh SEE ALSO +.Xr crypt 3 , +.Xr crypt_gensalt 3 , +.Xr crypt 5 , +.Xr crypt.conf 5 diff --git a/doc/crypt.3 b/doc/crypt.3 index f93004fc..636eacc0 100644 --- a/doc/crypt.3 +++ b/doc/crypt.3 @@ -282,7 +282,10 @@ when they fail. .Bl -tag -width Er .It Er EINVAL .Fa setting -is invalid, or requests a hashing method that is not supported. +is invalid, +or requests a hashing method that is not supported +or has been disabled in +.Xr crypt.conf 5 . .It Er ERANGE .Fa phrase is too long @@ -469,6 +472,7 @@ originate with the Openwall project. .Xr login 1 , .Xr passwd 1 , .Xr crypt 5 , +.Xr crypt.conf 5 , .Xr passwd 5 , .Xr shadow 5 , .Xr pam 8 diff --git a/doc/crypt.5 b/doc/crypt.5 index 3dbab962..d6be8cb5 100644 --- a/doc/crypt.5 +++ b/doc/crypt.5 @@ -33,7 +33,7 @@ However, with a strong hashing method, guessing will be too slow for the attacker to discover a strong passphrase. .Pp -All of the hashing methods use a +Most of the hashing methods use a .Dq salt to perturb the hash function, so that the same passphrase may produce many possible hashes. @@ -132,6 +132,12 @@ the hashing methods supported by in decreasing order of strength. Many of the older methods are now considered too weak to use for new passphrases. +.Pp +The name given in the subheading for each method +is the name to use in +.Xr crypt.conf 5 +to set local configuration for that method. +.Pp The hashed passphrase format is expressed with extended regular expressions (see .Xr regex 7 ) @@ -167,8 +173,9 @@ and does not show the division into prefix, options, salt, and hash. .El .. .Ss yescrypt -yescrypt is a scalable passphrase hashing scheme designed by Solar Designer, -which is based on Colin Percival's scrypt. +An improved version of scrypt (see below), +with greater resistance to offline attacks. +Developed by Solar Designer. Recommended for new hashes. .hash "$y$" "\e$y\e$[./A-Za-z0-9]+\e$[./A-Za-z0-9]{,86}\e$[./A-Za-z0-9]{43}" unlimited 8 256 256 "up to 512" "1 to 11 (logarithmic)" .Ss gost-yescrypt @@ -183,9 +190,9 @@ as RFC 6986. Recommended for new hashes. .hash "$gy$" "\e$gy\e$[./A-Za-z0-9]+\e$[./A-Za-z0-9]{,86}\e$[./A-Za-z0-9]{43}" unlimited 8 256 256 "up to 512" "1 to 11 (logarithmic)" .Ss scrypt -scrypt is a password-based key derivation function created by Colin Percival, +A hash created by Colin Percival, originally for the Tarsnap online backup service. -The algorithm was specifically designed to make it costly to perform +The algorithm was designed to make it costly to perform large-scale custom hardware attacks by requiring large amounts of memory. In 2016, the scrypt algorithm was published by IETF as RFC 7914. .hash "$7$" "\e$7\e$[./A-Za-z0-9]{11,97}\e$[./A-Za-z0-9]{43}" unlimited 8 256 256 "up to 512" "6 to 11 (logarithmic)" @@ -202,6 +209,14 @@ It exists for historical reasons only. The alternative prefixes "$2a$" and "$2x$" provide bug-compatibility with crypt_blowfish 1.0.4 and earlier, which incorrectly processed characters with the 8th bit set. +In +.Pa crypt.conf , +the treatment of alternative prefixes can be configured using the +names +.Li bcrypt_a , +.Li bcrypt_x , +and +.Li bcrypt_y . .Ss sha512crypt A hash based on SHA-2 with 512-bit output, originally developed by Ulrich Drepper for GNU libc. @@ -225,7 +240,7 @@ Not as weak as the DES-based hashes below, but SHA1 is so cheap on modern hardware that it should not be used for new hashes. .hash "$sha1" "\e$sha1\e$[1-9][0-9]+\e$[./0-9A-Za-z]{1,64}\e$[./0-9A-Za-z]{8,64}[./0-9A-Za-z]{32}" unlimited 8 160 160 "6 to 384" "4 to 4,294,967,295" -.Ss SunMD5 +.Ss sunmd5 A hash based on the MD5 algorithm, with additional cleverness to make precomputation difficult, originally developed by Alec David Muffet for Solaris. diff --git a/doc/crypt.conf.5 b/doc/crypt.conf.5 new file mode 100644 index 00000000..a97fec36 --- /dev/null +++ b/doc/crypt.conf.5 @@ -0,0 +1,303 @@ +.\" Written by Zack Weinberg in 2018. +.\" +.\" To the extent possible under law, the authors have waived +.\" all copyright and related or neighboring rights to this work. +.\" See https://creativecommons.org/publicdomain/zero/1.0/ for further +.\" details. +.\" +.Dd August 14, 2018 +.Dt CRYPT.CONF 5 +.Os "libxcrypt" +.Sh NAME +.Nm crypt.conf +.Nd configuration of passphrase hashing +.Sh DESCRIPTION +The file +.Nm +(normally installed in +.Pa /etc/security ) +determines which of several +.Dq hashing methods +may be used by +.Xr crypt 3 , +.Xr crypt_gensalt 3 , +and related functions to hash user passphrases. +For hashing methods that have tunable parameters, +it also allows adjustment of the default values for those parameters. +.Pp +Each line of +.Nm +configures a single hashing method. +If a hashing method is not mentioned in +.Nm , +compiled-in defaults are used for that method. +(The utility +.Nm crypt\-checkconf +can print out these defaults.) +If +.Nm +does not exist, +that is the same as if it were empty: +the compiled-in defaults are used for all methods. +.Pp +Fields on each line are separated by tabs or spaces. +Comments begin with a +.Sq Li \&# +and extend to the end of the line. +(Comments can begin in the middle of a line.) +Blank lines are ignored. +All identifiers (method names, allowed uses, etc.) are parsed +case-insensitively. +.Pp +Two fields are required for each hashing method: +.Bl -tag -width 3n +.It Sy Method name +This field identifies the hashing method to be configured. +It is a short, C-style identifier +such as +.Dq Sy bcrypt +or +.Dq Sy sha256crypt . +The names for each supported hashing method are given in +.Xr crypt 5 . +Unrecognized hashing methods are ignored. +.It Sy Allowed use +This field indicates how this hashing method is allowed to be used +on this system. +It has four possible settings: +.Bl -tag -width 3n +.It Ar preferred +This is the preferred method for new hashes: it will be used when a +.Ar prefix +is not supplied to +.Nm crypt_gensalt . +.It Ar enabled No (also Ar new , on , true , yes ) +This method may be used for new hashes: that is, +.Nm crypt_gensalt +will generate +.Ar setting +strings specifying this method, +when directed to do so by its +.Ar prefix +argument. +.It Ar legacy No (also Ar old , existing ) +This method may not be used for new hashes: +.Nm crypt_gensalt +will fail if directed to use this method. +However, +.Nm crypt +will accept +.Nm setting +strings that specify this method, +so it can still be used +to authenticate users against stored hashes. +.It Ar disabled No (also Ar off , false , no ) +This method may not be used at all. +Both +.Nm crypt_gensalt +and +.Nm crypt +will fail if directed to use this method. +.Pp +.Em Caution: +Users whose passphrases were hashed using a disabled method +will not be able to log in with a passphrase. +If they log in some other way +(e.g.\& an SSH key) +they will not be able to +.Em change +their passphrase, because +.Xr passwd 1 +will not be able to validate their old passphrase. +.El +.Pp +An unrecognized value in this field is treated as +.Ar disabled . +.Pp +The function +.Xr crypt_checksalt 3 +reports the +.Sy allowed use +of a setting string; +this can be used by programs such as +.Xr login 1 +to determine whether a user\(aqs passphrase should be re-hashed +using a newer method. +.El +.Pp +All subsequent fields are optional. +They must all have the form +.Bk +.Sy key Ns = Ns Ar value , +.Ek +where +.Sy key +is a C-style identifier, and +.Ar value +contains no spaces or tabs. +(Quotation marks, backslashes, etc.\& are not significant.) +Unrecognized +.Sy key Ns s +are ignored. +Presently, two keys are recognized: +.Bl -tag -width 3n +.It Sy defcost Ns = Ns Ar n +Set the default cost parameter for this hashing method to +.Ar n . +.Nm crypt_gensalt +will use +.Ar n +instead of its compiled-in default for this hashing method, +when its +.Ar count +argument is zero. +.Nm crypt_checksalt +will report that the cost parameter is +.Dq too cheap +for any existing hash that specifies a smaller cost parameter. +.Pp +.It Sy maxcost Ns = Ns Ar n +Set the maximum cost parameter for this hashing method to +.Ar n . +.Nm crypt_gensalt +will fail when its +.Ar count +argument is greater than +.Ar n , +and +.Nm crypt +will fail when its +.Ar setting +argument encodes a cost greater than +.Ar n . +.Pp +This can be used to prevent accidental or deliberate denial of service +by users who have the ability to set cost parameters +for their own passphrases +(e.g. in +.Xr htpasswd 5 ) . +.El +.Pp +The +.Nm +syntax allows any positive, decimal integer for +.Ar n , +but each hashing method has its own restrictions +on the values that may be used for +.Sy defcost +and +.Sy maxcost . +These restrictions are documented in +.Xr crypt 5 , +as are the compiled-in defaults used when these keys are not present. +Invalid values are ignored. +If +.Sy defcost +is set greater than +.Sy maxcost , +it will be lowered to equal +.Sy maxcost . +.Pp +The utility +.Nm crypt\-tune\-costs +can be used to select +.Sy defcost +and +.Sy maxcost +parameters that are appropriate for the machine it is run on. +.Sh EXAMPLES +This +.Nm +fragment specifies that +.Nm bcrypt +is the preferred method for new hashes, +but cost parameters greater than 15 are not allowed; +.Nm sha256crypt +is acceptable for new hashes, +but an increased cost parameter should be used; +.Nm md5crypt +is allowed only for old hashes; +and +.Nm descrypt +may not be used at all. +.Bd -literal -offset indent +bcrypt preferred maxcost=15 +sha256crypt yes defcost=50000 # compiled-in default 5000 +md5crypt legacy +descrypt off +.Ed +.Sh ERROR HANDLING +In general, syntax errors and unrecognized material in +.Nm +cause the malformed line or field to be ignored. +Any unrecognized value for the +.Sy allowed use +field is treated as +.Ar disabled , +to ensure that authentication fails closed. +A warning message is logged with +.Xr syslog 3 +under these circumstances: +.Bl -bullet +.It +.Nm crypt +is called with a +.Ar setting +that specifies a +.Ar disabled +hashing method +(including a method with an unrecognized +.Sy allowed use ) . +.It +.Nm crypt_gensalt +is called with a +.Ar prefix +explicitly requesting the use of a +.Ar disabled +or +.Ar legacy +hashing method. +.It +An unrecognized +.Sy key Ns = Ns Ar value +field appears on the line configuring the hashing method that +.Nm crypt_gensalt +is about to generate a +.Ar setting +string for. +.It +The +.Sy rounds +parameter for the hashing method that +.Nm crypt_gensalt +is about to generate a +.Ar setting +string for +is either syntactically invalid +or does not meet the restrictions for that hashing method. +.El +.Pp +The utility +.Nm crypt\-checkconf +can be used to scan +.Nm +for errors. +.Sh BUGS +The +.Dq Ar legacy +use is enforced only by +.Nm crypt_gensalt . +Applications that generate +.Ar setting +strings themselves may continue to create new hashes using methods whose +allowed use is set to +.Ar legacy . +.Sh FILES +.Pa /etc/crypt.conf +.Sh SEE ALSO +.Xr crypt 5 , +.Xr crypt 3 , +.Xr crypt_gensalt 3 , +.Xr crypt_checksalt 3 , +.Xr crypt\-checkconf 8 , +.Xr crypt\-tune\-costs 8 diff --git a/doc/crypt_checksalt.3 b/doc/crypt_checksalt.3 index f9948e8b..57bb44c1 100644 --- a/doc/crypt_checksalt.3 +++ b/doc/crypt_checksalt.3 @@ -23,7 +23,8 @@ .Nm checks the .Ar setting -string against the system configuration +string against the configuration in +.Pa /etc/crypt.conf and reports whether the hashing method and parameters it specifies are acceptable. It is intended to be used by programs @@ -44,14 +45,14 @@ This constant is guaranteed to equal 0. is not a valid setting string; either it specifies a hashing method that is not known to this version of libxcrypt, or it specifies invalid parameters for the method. -.It Dv CRYPT_SALT_METHOD_DISABLED (Not implemented, yet) +.It Dv CRYPT_SALT_METHOD_DISABLED .Ar setting specifies a hashing method that is no longer allowed to be used at all; .Nm crypt will fail if passed this .Ar setting . Manual intervention will be required to reactivate the user's account. -.It Dv CRYPT_SALT_METHOD_LEGACY (Not implemented, yet) +.It Dv CRYPT_SALT_METHOD_LEGACY .Ar setting specifies a hashing method that is no longer considered strong enough for use with new passphrases. @@ -59,7 +60,7 @@ for use with new passphrases. will still authenticate a passphrase against this setting, but if authentication succeeds, the passphrase should be re-hashed using the currently preferred method. -.It Dv CRYPT_SALT_TOO_CHEAP (Not implemented, yet) +.It Dv CRYPT_SALT_TOO_CHEAP .Ar setting specifies cost parameters that are considered too cheap for use with new passphrases. @@ -75,13 +76,6 @@ will define the macro if .Nm is available in the current version of libxcrypt. -.Sh BUGS -Since full configurability is not yet implemented, the current -implementation will only ever return -.Nm CRYPT_SALT_OK (0) -or -.Nm CRYPT_SALT_INVALID -when invoked. .Sh PORTABILITY NOTES The function .Nm @@ -103,4 +97,5 @@ T} Thread safety MT-Safe .Sh SEE ALSO .Xr crypt 3 , .Xr crypt_gensalt 3 , -.Xr crypt 5 +.Xr crypt 5 , +.Xr crypt.conf 5 diff --git a/doc/crypt_gensalt.3 b/doc/crypt_gensalt.3 index 3b3dab5c..b570fa0b 100644 --- a/doc/crypt_gensalt.3 +++ b/doc/crypt_gensalt.3 @@ -173,11 +173,15 @@ and will not be equal to .Bl -tag -width Er .It Er EINVAL .Fa prefix -is invalid or not supported by this implementation; +is invalid, +or requests a hashing method that is not supported +or has been disabled for new hashes in +.Xr crypt.conf 5 . +.br .Fa count is invalid for the requested -.Fa prefix ; -the input +.Fa prefix . +.br .Fa nrbytes is insufficient for the smallest valid salt with the requested .Fa prefix . diff --git a/lib/crypt-port.h b/lib/crypt-port.h index 1e4d4b6e..92f7eed4 100644 --- a/lib/crypt-port.h +++ b/lib/crypt-port.h @@ -54,6 +54,19 @@ #define __THROW /* nothing */ #endif +/* C2011 added a keyword _Noreturn with the same effect as + gcc's longstanding __attribute__ ((noreturn)) extension, + except that it is required to appear _before_ the function + type specifier (which must be 'void'). */ +#if (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) >= 201112 || \ + defined _Noreturn +# define NORETURN _Noreturn void +#elif defined __GNUC__ && __GNUC__ >= 3 +# define NORETURN void __attribute__ ((noreturn)) +#else +# define NORETURN void +#endif + /* Suppression of unused-argument warnings. */ #if defined __GNUC__ && __GNUC__ >= 3 # define ARG_UNUSED(x) x __attribute__ ((__unused__)) @@ -61,6 +74,17 @@ # define ARG_UNUSED(x) x #endif +/* Functions that are in some way wrappers around vfprintf or + vsnprintf, and that take a printf-like format string and variable + argument list, should be declared with this annotation to enable + gcc and clang to check their arguments, and to avoid spurious + format-string-is-not-a-string-literal warnings. */ +#if defined __GNUC__ && __GNUC__ >= 3 +# define PRINTF_FMT(x,y) __attribute__ ((__format__ (__printf__, x, y))) +#else +# define PRINTF_FMT(x,y) +#endif + /* C99 Static array indices in function parameter declarations. Syntax such as: void bar(int myArray[static 10]); is allowed in C99, but not all compiler support it properly. Define MIN_SIZE appropriately