diff --git a/.gitignore b/.gitignore index a1cedca7..966e25ec 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,7 @@ /test-badsetting /test-bigcrypt /test-byteorder +/test-checksalt /test-compile-strong-alias /test-crypt-badargs /test-crypt-bcrypt diff --git a/LICENSING b/LICENSING index 39c66ce7..152ed4c5 100644 --- a/LICENSING +++ b/LICENSING @@ -51,7 +51,8 @@ source tree. For specific licensing terms consult the files themselves. crypt-scrypt.c * Copyright Björn Esser; 0-clause BSD: - crypt-common.c test-compile-strong-alias.c test-short-outbuf.c + crypt-common.c, test-checksalt.c, test-compile-strong-alias.c, + test-short-outbuf.c * Copyright Michael Bretterklieber, Björn Esser et al.; 2-clause BSD: crypt-nthash.c diff --git a/Makefile.am b/Makefile.am index db04c2e0..9a32d97f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -21,7 +21,8 @@ EXTRA_DIST = \ notrans_dist_man3_MANS = \ crypt.3 crypt_r.3 crypt_ra.3 crypt_rn.3 \ - crypt_gensalt.3 crypt_gensalt_ra.3 crypt_gensalt_rn.3 + crypt_checksalt.3 crypt_gensalt.3 crypt_gensalt_ra.3 \ + crypt_gensalt_rn.3 notrans_dist_man5_MANS = crypt.5 nodist_include_HEADERS = crypt.h @@ -170,7 +171,7 @@ check_PROGRAMS = \ test-alg-des test-alg-gost3411-2012 test-alg-gost3411-2012-hmac \ test-alg-hmac-sha1 test-alg-md4 test-alg-md5 \ test-alg-pbkdf-hmac-sha256 test-alg-sha1 test-alg-sha256 \ - test-alg-sha512 test-crypt-bcrypt test-crypt-des \ + test-alg-sha512 test-checksalt test-crypt-bcrypt test-crypt-des \ test-crypt-gost-yescrypt test-crypt-md5 test-crypt-nthash \ test-crypt-pbkdf1-sha1 test-crypt-scrypt test-crypt-sha256 \ test-crypt-sha512 test-crypt-sunmd5 test-crypt-yescrypt \ @@ -220,6 +221,7 @@ test_crypt_yescrypt_LDADD = crypt-common.lo libcrypt.la test_badsalt_LDADD = crypt-common.lo libcrypt.la test_badsetting_LDADD = crypt-common.lo libcrypt.la test_gensalt_LDADD = crypt-common.lo libcrypt.la +test_checksalt_LDADD = crypt-common.lo libcrypt.la test_des_obsolete_LDADD = crypt-common.lo libcrypt.la test_des_obsolete_r_LDADD = crypt-common.lo libcrypt.la test_crypt_badargs_LDADD = crypt-common.lo libcrypt.la diff --git a/NEWS b/NEWS index 42a35c03..984f9d7d 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,8 @@ Version 4.3.0 output generated by descrypt under the same conditions. This only applies when the descrypt hash method is not selected at compile time. +* Implement crypt_checksalt, which can be used by portable users of + libxcrypt to check whether the desired hash method is supported. Version 4.2.3 * Add bootstrap script. If building from a Git checkout instead of a diff --git a/crypt.c b/crypt.c index fadc2e2c..2c3e94ba 100644 --- a/crypt.c +++ b/crypt.c @@ -334,3 +334,24 @@ crypt_gensalt_ra (const char *prefix, unsigned long count, } SYMVER_crypt_gensalt_ra; #endif + +#if INCLUDE_crypt_checksalt +static_assert(CRYPT_SALT_OK == 0, "CRYPT_SALT_OK does not equal zero"); + +int +crypt_checksalt (const char *setting) +{ + int retval = CRYPT_SALT_INVALID; + + if (!setting) + return retval; + + const struct hashfn *h = get_hashfn (setting); + + if (h) + retval = CRYPT_SALT_OK; + + return retval; +} +SYMVER_crypt_checksalt; +#endif diff --git a/crypt.h.in.in b/crypt.h.in.in index 4fd654b4..8e03a2b6 100644 --- a/crypt.h.in.in +++ b/crypt.h.in.in @@ -185,11 +185,29 @@ extern char *crypt_gensalt_ra (const char *__prefix, unsigned long __count, const char *__rbytes, int __nrbytes) __THROW; +/* Checks whether the given setting is a supported method. + + The return value is 0 if there is nothing wrong with this setting. + Otherwise, it is one of the following constants. */ +extern int crypt_checksalt (const char *__setting); + +/* Constants for checking the return value of the + crypt_checksalt function. */ +#define CRYPT_SALT_OK 0 +#define CRYPT_SALT_INVALID 1 +#define CRYPT_SALT_METHOD_DISABLED 2 /* NOT implemented, yet. */ +#define CRYPT_SALT_METHOD_LEGACY 3 /* NOT implemented, yet. */ +#define CRYPT_SALT_TOO_CHEAP 4 /* NOT implemented, yet. */ + /* These macros could be checked by portable users of crypt_gensalt* functions to find out whether null pointers could be specified as PREFIX and RBYTES arguments. */ #define CRYPT_GENSALT_IMPLEMENTS_DEFAULT_PREFIX 1 -#define CRYPT_GENSALT_IMPLEMENTS_AUTO_ENTROPY 1 +#define CRYPT_GENSALT_IMPLEMENTS_AUTO_ENTROPY 1 + +/* These macros can be checked by portable users of the crypt_checksalt + function to find out whether the function is implemented. */ +#define CRYPT_CHECKSALT_AVAILABLE 1 /* Version number split in single integers. */ #define XCRYPT_VERSION_MAJOR @XCRYPT_VERSION_MAJOR@ diff --git a/crypt_checksalt.3 b/crypt_checksalt.3 new file mode 100644 index 00000000..f9948e8b --- /dev/null +++ b/crypt_checksalt.3 @@ -0,0 +1,106 @@ +.\" 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 November 8, 2018 +.Dt CRYPT_CHECKSALT 3 +.Os "libxcrypt" +.Sh NAME +.Nm crypt_checksalt +.Nd validate a crypt setting string +.Sh LIBRARY +.Lb libcrypt +.Sh SYNOPSIS +.In crypt.h +.Ft int +.Fo crypt_checksalt +.Fa "const char *setting" +.Fc +.Sh DESCRIPTION +.Nm +checks the +.Ar setting +string against the system configuration +and reports whether the hashing method and parameters it specifies +are acceptable. +It is intended to be used by programs +such as +.Xr login 1 +to determine whether the user's passphrase should be re-hashed +using the currently preferred hashing method. +.Sh RETURN VALUES +The return value is 0 if there is nothing wrong with this setting. +Otherwise, it is one of the following constants: +.Bl -tag -width 4n +.It Dv CRYPT_SALT_OK +.Ar setting +is a fully correct setting string. +This constant is guaranteed to equal 0. +.It Dv CRYPT_SALT_INVALID +.Ar setting +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) +.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) +.Ar setting +specifies a hashing method that is no longer considered strong enough +for use with new passphrases. +.Nm crypt +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) +.Ar setting +specifies cost parameters that are considered too cheap for use with +new passphrases. +.Nm crypt +will still authenticate a passphrase against this setting, +but if authentication succeeds, +the passphrase should be re-hashed using the currently preferred method. +.El +.Sh FEATURE TEST MACROS +.In crypt.h +will define the macro +.Dv CRYPT_CHECKSALT_AVAILABLE +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 +is not part of any standard. +It was added to libxcrypt in version 4.3.0. +.Sh ATTRIBUTES +For an explanation of the terms used in this section, see +.Xr attributes 7 . +.TS +allbox; +lb lb lb +l l l. +Interface Attribute Value +T{ +.Nm +T} Thread safety MT-Safe +.TE +.sp +.Sh SEE ALSO +.Xr crypt 3 , +.Xr crypt_gensalt 3 , +.Xr crypt 5 diff --git a/gen-vers.awk b/gen-vers.awk index 1cf8ec2c..8bd9d5e5 100644 --- a/gen-vers.awk +++ b/gen-vers.awk @@ -177,14 +177,14 @@ END { v = VCHAIN[i] if ((v, sym) in symset) { if (seq == 0) { - if (compat_only[sym] || includesym[sym] > 1) { + if (compat_only[sym] || includesym[sym] > 0) { printf("#ifdef PIC\n#define %s _crypt_%s\n#endif\n", sym, sym); } printf("#define SYMVER_%s \\\n", sym) if (compat_only[sym]) { printf(" symver_compat0 (\"%s\", %s, %s)", sym, sym, v) - } else if (includesym[sym] > 1) { + } else if (includesym[sym] > 0) { printf(" symver_default (\"%s\", %s, %s)", sym, sym, v) } else { # Due to what appears to be a bug in GNU ld, diff --git a/libcrypt.map.in b/libcrypt.map.in index 0989990b..9b3364d5 100644 --- a/libcrypt.map.in +++ b/libcrypt.map.in @@ -16,6 +16,9 @@ crypt_gensalt_rn XCRYPT_2.0 GLIBC_2.0:owl:suse GLIBC_2.2.1:alt OW_CRYPT_1.0: crypt_ra XCRYPT_2.0 GLIBC_2.0:owl:suse GLIBC_2.2.2:alt crypt_gensalt_ra XCRYPT_2.0 GLIBC_2.0:owl:suse GLIBC_2.2.2:alt OW_CRYPT_1.0:suse +# Actively supported interfaces from libxcrypt. +crypt_checksalt XCRYPT_4.3 + # Deprecated interfaces, POSIX and otherwise; also present in GNU libc # since 2.0 encrypt - GLIBC_2.0 @@ -31,4 +34,4 @@ fcrypt - GLIBC_2.0 %chain GLIBC_2.0 GLIBC_2.2 GLIBC_2.2.1 GLIBC_2.2.2 GLIBC_2.2.5 GLIBC_2.3 %chain GLIBC_2.4 GLIBC_2.12 GLIBC_2.16 GLIBC_2.17 GLIBC_2.18 GLIBC_2.21 %chain GLIBC_2.27 -%chain OW_CRYPT_1.0 XCRYPT_2.0 +%chain OW_CRYPT_1.0 XCRYPT_2.0 XCRYPT_4.3 diff --git a/test-checksalt.c b/test-checksalt.c new file mode 100644 index 00000000..ea32ef3e --- /dev/null +++ b/test-checksalt.c @@ -0,0 +1,232 @@ +/* Copyright (C) 2018 Björn Esser + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "crypt-port.h" +#include + +struct testcase +{ + const char *prefix; + const int exp_prefix; + const int exp_gensalt; + const int exp_crypt; +}; + +static const struct testcase testcases[] = +{ +#if INCLUDE_descrypt || INCLUDE_bigcrypt + { "", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, + { "..", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, + { "MN", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, + { "..", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, + { "MN", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +#if INCLUDE_bsdicrypt + { "_", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "_", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +#if INCLUDE_md5crypt + { "$1$", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "$1$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +#if INCLUDE_nt + { "$3$", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "$3$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +#if INCLUDE_sunmd5 + { "$md5", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "$md5", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +#if INCLUDE_sha1crypt + { "$sha1", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "$sha1", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +#if INCLUDE_sha256crypt + { "$5$", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "$5$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +#if INCLUDE_sha512crypt + { "$6$", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "$6$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +#if INCLUDE_bcrypt + { "$2a$", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, + { "$2b$", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, + { "$2x$", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, + { "$2y$", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "$2a$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, + { "$2b$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, + { "$2x$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, + { "$2y$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +#if INCLUDE_yescrypt + { "$y$", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "$y$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +#if INCLUDE_scrypt + { "$7$", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "$7$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +#if INCLUDE_gost_yescrypt + { "$gy$", CRYPT_SALT_OK, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { "$gy$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif + + /* All of these are invalid. */ + { "$@", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, + { "%A", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, + { "A%", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, + { "$2$", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, + { "*0", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, + { "*1", CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#if defined HASH_ALGORITHM_DEFAULT + { NULL, CRYPT_SALT_INVALID, CRYPT_SALT_OK, CRYPT_SALT_OK }, +#else + { NULL, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID, CRYPT_SALT_INVALID }, +#endif +}; + +int +main (void) +{ + char gs_out[CRYPT_GENSALT_OUTPUT_SIZE] = ""; + const char *phr = "police saying freeze"; + struct crypt_data cd; + const size_t gs_len = CRYPT_GENSALT_OUTPUT_SIZE; + + int status = 0; + int retval = 0; + + for (size_t i = 0; i < ARRAY_SIZE (testcases); i++) + { + /* crypt_checksalt on prefix. */ + retval = crypt_checksalt (testcases[i].prefix); + if (retval == testcases[i].exp_prefix) + printf ("PASS (prefix): %s, result: %d\n", + testcases[i].prefix, retval); + else + { + status = 1; + printf ("FAIL (prefix): %s, expected: %d, got: %d\n", + testcases[i].prefix, + testcases[i].exp_prefix, retval); + continue; + } + + /* crypt_checksalt on gensalt output. */ + crypt_gensalt_rn (testcases[i].prefix, 0, NULL, 0, gs_out, gs_len); + retval = crypt_checksalt (gs_out); + if (retval == testcases[i].exp_gensalt) + printf ("PASS (gensalt): %s, result: %d\n", + gs_out, retval); + else + { + status = 1; + printf ("FAIL (gensalt): %s, expected: %d, got: %d\n", + gs_out, testcases[i].exp_gensalt, retval); + continue; + } + + /* crypt_checksalt on crypt output. */ + crypt_r (phr, gs_out, &cd); + retval = crypt_checksalt (cd.output); + if (retval == testcases[i].exp_crypt) + printf ("PASS (crypt): %s, result: %d\n", + cd.output, retval); + else + { + status = 1; + printf ("FAIL (crypt): %s, expected: %d, got: %d\n", + cd.output, testcases[i].exp_crypt, retval); + } + +#if INCLUDE_descrypt && INCLUDE_bigcrypt + + /* Test bigcrypt as well. */ + if (testcases[i].prefix && strlen (testcases[i].prefix) == 2) + { + /* Prefix must be at least 14 bytes. */ + char bigcrypt_prefix[CRYPT_GENSALT_OUTPUT_SIZE]; + const char *pad = "............"; + memcpy (bigcrypt_prefix, testcases[i].prefix, 2); + strncpy (bigcrypt_prefix + 2, pad, gs_len - 2); + + /* crypt_checksalt on prefix. */ + retval = crypt_checksalt (bigcrypt_prefix); + if (retval == testcases[i].exp_prefix) + printf ("PASS (prefix): %s, result: %d\n", + bigcrypt_prefix, retval); + else + { + status = 1; + printf ("FAIL (prefix): %s, expected: %d, got: %d\n", + bigcrypt_prefix, + testcases[i].exp_prefix, retval); + continue; + } + + /* crypt_checksalt on gensalt output. */ + crypt_gensalt_rn (bigcrypt_prefix, 0, NULL, 0, gs_out, gs_len); + + /* Add 12 trailing bytes. */ + strncpy (gs_out + 2, pad, gs_len - 2); + + retval = crypt_checksalt (gs_out); + if (retval == testcases[i].exp_gensalt) + printf ("PASS (gensalt): %s, result: %d\n", + gs_out, retval); + else + { + status = 1; + printf ("FAIL (gensalt): %s, expected: %d, got: %d\n", + gs_out, testcases[i].exp_gensalt, retval); + continue; + } + + /* crypt_checksalt on crypt output. */ + crypt_r (phr, gs_out, &cd); + retval = crypt_checksalt (cd.output); + if (retval == testcases[i].exp_crypt) + printf ("PASS (crypt): %s, result: %d\n", + cd.output, retval); + else + { + status = 1; + printf ("FAIL (crypt): %s, expected: %d, got: %d\n", + cd.output, testcases[i].exp_crypt, retval); + } + } +#endif + + } + + return status; +}