Index: include/unistd.h =================================================================== --- include/unistd.h +++ include/unistd.h @@ -496,6 +496,7 @@ int check_utility_compat(const char *); const char * crypt_get_format(void); +int crypt_makesalt(char *, const char *, size_t *); char *crypt_r(const char *, const char *, struct crypt_data *); int crypt_set_format(const char *); int dup3(int, int, int); Index: lib/libcrypt/Makefile =================================================================== --- lib/libcrypt/Makefile +++ lib/libcrypt/Makefile @@ -17,7 +17,9 @@ crypt-sha256.c sha256c.c \ crypt-sha512.c sha512c.c MAN= crypt.3 -MLINKS= crypt.3 crypt_get_format.3 crypt.3 crypt_r.3 \ +MLINKS= crypt.3 crypt_get_format.3 \ + crypt.3 crypt_makesalt.3 \ + crypt.3 crypt_r.3 \ crypt.3 crypt_set_format.3 CFLAGS+= -I${SRCTOP}/lib/libmd -I${SRCTOP}/lib/libutil \ -I${SRCTOP}/sys/crypto/sha2 Index: lib/libcrypt/crypt.3 =================================================================== --- lib/libcrypt/crypt.3 +++ lib/libcrypt/crypt.3 @@ -1,6 +1,7 @@ .\" FreeSec: libcrypt for NetBSD .\" .\" Copyright (c) 1994 David Burren +.\" Copyright (c) 2015 Derek Marcotte .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -15,10 +16,10 @@ .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" -.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS 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 +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS 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) @@ -33,218 +34,241 @@ .Dt CRYPT 3 .Os .Sh NAME -.Nm crypt -.Nd Trapdoor encryption +.Nm crypt , +.Nm crypt_makesalt , +.Nm crypt_get_format , +.Nm crypt_set_format +.Nd "library for password hashing" .Sh LIBRARY .Lb libcrypt .Sh SYNOPSIS .In unistd.h -.Ft char * +.Ft "char *" .Fn crypt "const char *key" "const char *salt" .Ft char * .Fn crypt_r "const char *key" "const char *salt" "struct crypt_data *data" -.Ft const char * +.Ft "int" +.Fn crypt_makesalt "char *output" "const char *format" "size_t *out_len" +.Ft "const char *" .Fn crypt_get_format "void" -.Ft int +.Ft "int" .Fn crypt_set_format "const char *string" .Sh DESCRIPTION -The -.Fn crypt -function performs password hashing with additional code added to -deter key search attempts. -Different algorithms can be used to -in the hash. -.\" -.\" NOTICE: -.\" If you add more algorithms, make sure to update this list -.\" and the default used for the Traditional format, below. -.\" -Currently these include the -.Tn NBS -.Tn Data Encryption Standard (DES) , -.Tn MD5 -hash, -.Tn NT-Hash -.Pq compatible with Microsoft's NT scheme -and -.Tn Blowfish . -The algorithm used will depend upon the format of the Salt -.Po -following -the Modular Crypt Format -.Pq MCF -.Pc , -if -.Tn DES -and/or -.Tn Blowfish -is installed or not, and whether +The +.Nm libcrypt +library covers functions related to password hashing. +.Pp +Password hashing is different than a standard one way hash function. Password hashing tries to make it difficult to recover a low-entropy key +.Pq e.g. a typed password +from the resultant output hash. Offline recovery by exhaustive key search +.Pq brute-force and dictionary attacks +is stymied by the password hashing being one-way, and often computationally expensive. Different algorithms can be selected to produce the password hash, with varying ability to effectively deter key search attempts. +.Pp +The hashing algorithm to be used for an invocation of crypt is chosen by supplying a salt parameter of a particular format. The algorithms currently supported are: +.Sx bcrypt , +.Sx sha512-crypt , +.Sx sha256-crypt , +.Sx md5-crypt , +.Sx DES Extended Format , +.Sx Traditional DES Format , +and +.Sx NT-Hash . +.Pp +Current computational capabilities make all but the bcrypt and sha-crypt families known to be susceptible to exhaustive key searches. The tuneable work factor of the bcrypt and sha-crypt families has allowed them to be set to be more computationally intensive over time. The use of the other families is not recommended at this time, but are maintained for legacy compatibility. +.Pp +The first argument to +.Fn crypt +is the +.Fa key +to hash +.Pq usually a password , +as a NUL-terminated string. The second is the +.Fa salt . +A salt is a random value that assists in complicating offline recovery. The length and format of the salt, and how it will be used is algorithm dependent. For best results, specify at least sixteen characters of salt. +.Pp +If the salt begins with the string +.Qq $digit , +then the +.Sx Modular Crypt Format +is used. If it begins with an underscore +.Qq _ , +then the +.Sx DES Extended Format +is used. If the salt is 2 or 13 characters and includes only 0-9, a-z, A-Z a dot . or a slash /, then +.Sx Traditional DES Format +is used. If none of these conditions are true, crypt will use the default hash function set by crypt_set_format, +.Pq or Traditional DES Format if no default has been set, for systems with DES support . +.Pp +The +.Fn crypt_makesalt +function populates an +.Fa output +buffer with a newly generated salt for the specified valid +.Fa format . +The size of the pre-allocated buffer is passed via +.Fa out_len . +If the output buffer is too small, the required size is set in +.Fa out_len . +.Pp +The +.Fn crypt_get_format +function returns a constant string that represents the name of the algorithm set as the default. Its use is deprecated. +.Pp +The .Fn crypt_set_format -has been called to change the default. -.Pp -The first argument to -.Nm -is the data to hash -.Pq usually a password , -in a -.Dv NUL Ns -terminated -string. -The second is the salt, in one of three forms: -.Pp -.Bl -tag -width Traditional -compact -offset indent -.It Extended -If it begins with an underscore -.Pq Dq _ -then the -.Tn DES -Extended Format -is used in interpreting both the key and the salt, as outlined below. -.It Modular -If it begins with the string -.Dq $digit$ -then the Modular Crypt Format is used, as outlined below. -.It Traditional -If neither of the above is true, it assumes the Traditional Format, -using the entire string as the salt -.Pq or the first portion . +function sets the default encoding +.Fa format +according to the supplied string. Its use is deprecated. +.Pp +The salts, formats, and algorithms are outlined below. + +.Sh Modular Crypt Format +.Pp +Modular Crypt Format is the current standard way to describe which algorithm and parameters to use for a particular crypt invocation. Salts that are prefixed with $digit are using the modular crypt format. The prefix selects the algorithm to use. Valid prefixes are: +.Bl -column -offset indent ".Sy Prefix" ".Sy Algorithm" +.It Sy Prefix Ta Sy Algorithm +.It Li $1$ Ta +.Sx md5-crypt +.It Li $2 Ta +.Sx bcrypt +.It Li $3$ Ta +.Sx NT-Hash +.It Li $5$ Ta +.Sx sha256-crypt +.It Li $6$ Ta +.Sx sha512-crypt .El .Pp -All routines are designed to be time-consuming. -.Ss DES Extended Format: -The -.Ar key -is divided into groups of 8 characters -.Pq the last group is NUL-padded -and the low-order 7 bits of each character -.Pq 56 bits per group -are used to form the -.Tn DES -key as follows: -the first group of 56 bits becomes the initial -.Tn DES -key. -For each additional group, the XOR of the encryption of the current -.Tn DES -key with itself and the group bits becomes the next -.Tn DES -key. -.Pp -The salt is a 9-character array consisting of an underscore followed -by 4 bytes of iteration count and 4 bytes of salt. -These are encoded as printable characters, 6 bits per character, -least significant character first. -The values 0 to 63 are encoded as -.Dq ./0-9A-Za-z . -This allows 24 bits for both -.Fa count -and -.Fa salt . +Everything following the prefix in the salt parameter is algorithm dependant, but includes things like work factor, and an actual salt +.Pq random value for that particular invocation . + +.Ss md5-crypt +.Bl -column ".Sy Output Hash Example:" ".Sy 8 characters from the set [a-zA-Z0-9./]" +.It Li Format Strings: Ta $1$, md5 +.It Li Salt Format: Ta 8 characters from the set [a-zA-Z0-9./] +.It Li Full Salt Example: Ta $1$deadbeef$ +.It Li Output Hash Example: Ta $1$deadbeef$0Huu6KHrKLVWfqa4WljDE0 +.El .Pp -The -.Fa salt -introduces disorder in the -.Tn DES -algorithm in one of 16777216 or 4096 possible ways -.Po -i.e., with 24 or 12 bits: if bit -.Em i -of the -.Ar salt -is set, then bits -.Em i -and -.Em i+24 -are swapped in the -.Tn DES -E-box output -.Pc . +md5-crypt was the default crypt format in FreeBSD for many years, developed by Poul-Henning Kamp. .Pp -The -.Tn DES -key is used to encrypt a 64-bit constant using -.Ar count -iterations of -.Tn DES . -The value returned is a -.Dv NUL Ns -terminated -string, 20 or 13 bytes -.Pq plus NUL -in length, consisting of the -.Ar salt -followed by the encoded 64-bit encryption. -.Ss Modular crypt: -If the salt begins with the string -.Fa $digit$ -then the Modular Crypt Format is used. -The -.Fa digit -represents which algorithm is used in encryption. -Following the token is -the actual salt to use in the encryption. -The maximum length of the salt used depends upon the module. -The salt must be terminated with the end of the string character -.Pq NUL -or a dollar sign. -Any characters after the dollar sign are ignored. -.Pp -Currently supported algorithms are: -.Pp -.Bl -enum -compact -offset indent -.It -MD5 -.It -Blowfish -.It -NT-Hash -.It -(unused) -.It -SHA-256 -.It -SHA-512 +It has seen widespread use in many commercial products, and for a long time was good enough. It has a fixed work factor, which means that as computers became faster, the ability to attack it has increased proportionally. +.Pp +Its use is no longer recommended. + +.Ss bcrypt +.Bl -column ".Sy Output Hash Example:" ".Sy $2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK" +.It Li Format Strings: Ta $2$[rounds]$, $2a$[rounds]$, $2b$[rounds]$, blf +.It Li Salt Format: Ta 16 bytes then base-64 encoded from the set [a-zA-Z0-9./] +.It Li Full Salt Example: Ta $2a$05$CCCCCCCCCCCCCCCCCCCCC. +.It Li Output Hash Example: Ta $2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK .El .Pp -Other crypt formats may be easily added. -An example salt would be: -.Bl -tag -width 6n -offset indent -.It Cm "$4$thesalt$rest" +bcrypt is the first algorithm to support a tuneable work factor. It was initially introduced in OpenBSD, by Niels Provos, based on the Usenix paper "A Future-Adaptable Password Scheme" by Niels Provos and David Mazi\(`eres. +.Pp +There are three variations supported, each with minor bug fixes. 2b is the recommended variation. +.Pp +The work factor is specified by the rounds parameter in the format string. These rounds are logarithmic. That is, +.Qq $2b$08$ +will take approximately twice as long as +.Qq $2b$07$ +for the same salt and key. 04 is the lowest supported work factor, and 31 is the highest. +.Pp +When the format string +.Qq blf +is specified, it is equivalent to specifying +.Qq $2b$04$ . + +.Ss NT-Hash +.Bl -column ".Sy Output Hash Example:" ".Sy $3$sdlksjfdlksjdlfk" +.It Li Format Strings: Ta $3$, nth +.It Li Salt Format: Ta no salt +.It Li Full Salt Example: Ta $3$ +.It Li Output Hash Example: Ta $3$$cc0fb8a290eebbfe74e7207f2ace5927 +.El +.Pp +nt-hash is supported for compatibility reasons only. It is strongly discouraged for any production uses. + +.Ss sha256-crypt +.Bl -column ".Sy Output Hash Example:" ".Sy $5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5, $5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2.opqey6IcA " +.It Li Format Strings: Ta $5$, $5$rounds=[rounds]$, sha256 +.It Li Salt Format: Ta 16 characters from the set [a-zA-Z0-9./] +.It Li Full Salt Examples: Ta $5$saltstring$, $5$rounds=10000$saltstringsaltst$ +.It Li Output Hash Examples: Ta $5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5, $5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2.opqey6IcA .El -.Ss Traditional crypt: -The algorithm used will depend upon whether -.Fn crypt_set_format -has been called and whether a global default format has been specified. -Unless a global default has been specified or -.Fn crypt_set_format -has set the format to something else, the built-in default format is -used. -This is currently -.\" -.\" NOTICE: Also make sure to update this -.\" -DES -if it is available, or MD5 if not. .Pp -How the salt is used will depend upon the algorithm for the hash. -For -best results, specify at least eight characters of salt. +sha256 supports a tunable work factor. It was developed by Ulrich Drepper of Red Hat, and is detailed in +.Qq Unix crypt using SHA-256 and SHA-512 . .Pp -The -.Fn crypt_get_format -function returns a constant string that represents the name of the -algorithm currently used. -Valid values are -.\" -.\" NOTICE: Also make sure to update this, too, as well -.\" -.Ql des , -.Ql blf , -.Ql md5 , -.Ql sha256 , -.Ql sha512 -and -.Ql nth . +From that document: .Pp -The +.Qo +Security departments in companies are trying to phase out all uses of MD5. They demand a method which is officially sanctioned. For US-based users this means tested by the NIST. +.Pp +This rules out the use of another already implemented method with limited spread: the use of the Blowfish encryption method. The choice comes down to tested encryption +.Pq 3DES, AES +or hash sums +.Pq the SHA family . +.Qc +.Pp +The prepositions in the above statement are misleading. Blowfish as a primitive, like 3DES or AES, has stood up to years of scrutinty by the cryptographic communinty. bcrypt, the password hashing function, is ubiquitous. It also currently provides greater resilience against pipe-lined or GPU-based attacks for the approximate same CPU workload as the sha-crypt family, based on its limited memory requirements. This is not expected to remain true with future improvements to GPUs. An additional subtle difference between bcrypt and the sha families is that bcrypt's salt is 2^128 bits, while the sha family is 2^96 bits. +.Pp +If you require a algorithm that includes NIST sanctioned primitives, choose one of the sha-crypt methods. +.Pp +The work factor is specified by the rounds parameter in the format string. These rounds are linear. That is, +.Qq $5$rounds=20000$ +will take approximately twice as long as +.Qq $5$rounds=10000$ +for the same salt and key. 1000 is the minimum number of rounds, 999999999 is the maximum. +.Pp +When the format string +.Qq sha256 , +or +.Qq $5$ +is specified, it is equivalent to specifying +.Qq $5$rounds=5000$ . + + +.Ss sha512-crypt +.Bl -column ".Sy Output Hash Example:" ".Sy $6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1, $6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sbHbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v." +.It Li Format Strings: Ta $6$, $6$rounds=[rounds]$, sha512 +.It Li Salt Format: Ta 16 characters from the set [a-zA-Z0-9./] +.It Li Full Salt Examples: Ta $6$saltstring$, $6$rounds=10000$saltstringsaltst$ +.It Li Output Hash Examples: Ta $6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1, $6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sbHbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v. +.El +sha512 is nearly equivalent to sha256, except that it uses SHA512 as a primitive. See Unix crypt using SHA-256 and SHA-512 for more details. + +The details provided in sha256 apply here as well. + +When the format string +.Qq sha512 , +or +.Qq $6$ +is specified, it is equivalent to specifying +.Qq $6$rounds=5000$ . + +.Sh DES Extended Format: +.Pp +The key is divided into groups of 8 characters +.Pq the last group is NUL-padded +and the low-order 7 bits of each character +.Pq 56 bits per group +are used to form the DES key as follows: the first group of 56 bits becomes the initial DES key. For each additional group, the XOR of the encryption of the current DES key with itself and the group bits becomes the next DES key. +.Pp +The salt is a 9-character array consisting of an underscore followed by 4 bytes of iteration count and 4 bytes of salt. These are encoded as printable characters, 6 bits per character, least significant character first. The values 0 to 63 are encoded as +.Qq ./0-9A-Za-z . +This allows 24 bits for both count and salt. +.Pp +The salt introduces disorder in the DES algorithm in one of 16777216 or 4096 possible ways +.Pq i.e., with 24 or 12 bits: if bit i of the salt is set, then bits i and i+24 are swapped in the DES E-box output . +.Pp +The DES key is used to encrypt a 64-bit constant using count iterations of DES. The value returned is a NUL-terminated string, 20 or 13 bytes +.Pq plus NUL +in length, consisting of the salt followed by the encoded 64-bit encryption. + +.Sh Traditional DES Format: +.Pp +The algorithm used will depend upon whether .Fn crypt_set_format function sets the default encoding format according to the supplied .Fa string . @@ -257,7 +281,8 @@ .Fa data , making it thread-safe. .Sh RETURN VALUES -The +.Pp +The .Fn crypt and .Fn crypt_r @@ -268,23 +293,131 @@ will always return a pointer to a string. .Pp The +.Fn crypt_makesalt +function will return a 0 on success, or non-zero on failure. It may fail in one of two +ways. If +.Fa out_len +has changed, the +.Fa output +buffer was not large enough to store the salt. The required size will be stored +in +.Fa out_len . +If +.Fa out_len +has not changed, then the +.Fa format +passed was invalid. +.Pp +The .Fn crypt_set_format -function will return 1 if the supplied encoding format was valid. -Otherwise, a value of 0 is returned. +function will return 1 if the supplied encoding format was valid. Otherwise, a value of 0 is returned. + + +.Sh EXAMPLES +.Bd -literal +#include +#include +#include + +#include +#include + +#include + +int +main() +{ + char *global_static_buffer; + char final[_PASSWORD_LEN + 1]; + char test[_PASSWORD_LEN + 1]; + char salt[256]; + size_t salt_sz = sizeof(salt); + + int exit_code = 0; + + /* threaded implementations should use a mutex around crypt */ + pthread_mutex_t mtx; + if (pthread_mutex_init(&mtx, NULL) ) { + exit_code = 1; + goto exit1; + } + + if (crypt_makesalt(salt, "$2b$08$", &salt_sz) ) { + if ( salt_sz != sizeof(salt) ) { + printf("Destination buffer too small for algorithm salt\\n"); + exit_code = 2; + goto exit; + } + + printf("Invalid format specified\\n"); + exit_code = 3; + goto exit; + } + printf("crypt_makesalt result: %s\\n", salt); + + /* + * Generate a crypt for storage + */ + + /* crypt buffer is global static */ + pthread_mutex_lock(&mtx); + global_static_buffer = crypt("you'll never guess me!", salt); + strncpy(final, global_static_buffer, sizeof(final) ); + pthread_mutex_unlock(&mtx); + + if (global_static_buffer == NULL) { + printf("crypt failed\\n"); + exit_code = 4; + goto exit; + } + + printf("crypt result: %s\\n", final); + + /* + * Compare a string against a stored crypt + */ + + pthread_mutex_lock(&mtx); + global_static_buffer = crypt("is this it?", final); + strncpy(test, global_static_buffer, sizeof(test) ); + pthread_mutex_unlock(&mtx); + + if (global_static_buffer == NULL) { + printf("second crypt failed\\n"); + exit_code = 5; + goto exit; + } + + printf("test result: %s\\n", test); + + /* a timing-safe string compare would be better */ + if ( strncmp(test, final, MAX(sizeof(test), sizeof(final) ) ) == 0 ) { + printf("The two buffers match.\\n"); + } else { + printf("The two buffers do not match.\\n"); + } + +exit: + pthread_mutex_destroy(&mtx); +exit1: + return exit_code; +} +.Ed .Sh SEE ALSO -.Xr login 1 , -.Xr passwd 1 , -.Xr getpass 3 , -.Xr passwd 5 +.Xr login 1 , +.Xr passwd 1 , +.Xr getpass 3 , +.Xr login_getcapstr 3 , +.Xr passwd 5 , + .Sh HISTORY -A rotor-based +.Pp +A rotor-based .Fn crypt -function appeared in -.At v6 . -The current style +function appeared in Version 6 AT&T UNIX. The +current style .Fn crypt -first appeared in -.At v7 . +first appeared in Version 7 AT&T UNIX. .Pp The .Tn DES @@ -298,30 +431,36 @@ function was added in .Fx 12.0 . .Sh AUTHORS -.An -nosplit -Originally written by -.An David Burren Aq Mt davidb@werj.com.au , -later additions and changes by -.An Poul-Henning Kamp , -.An Mark R V Murray , -.An Michael Bretterklieber , -.An Kris Kennaway , -.An Brian Feldman , -.An Paul Herman -and -.An Niels Provos . +Originally written by David Burren +.Mt davidb@werj.com.au , +later additions and changes by Poul-Henning Kamp, Mark R V Murray, Michael +Bretterklieber, Kris Kennaway, Brian Feldman, Paul Herman, Niels Provos, and +Derek Marcotte. + .Sh BUGS -The +The .Fn crypt -function returns a pointer to static data, and subsequent calls to +function returns a pointer to static data, and subsequent +calls to .Fn crypt -will modify the same data. -Likewise, +will modify the same data. Likewise, .Fn crypt_set_format modifies static data. + +.Sh SECURITY CONSIDERATIONS +The following algorithms are considered insecure, and are not recommended +for new implementations: +.Sx md5-crypt , +.Sx DES Extended Format , +.Sx Traditional DES Format , +and +.Sx NT-Hash . .Pp -The NT-hash scheme does not use a salt, -and is not hard -for a competent attacker -to break. -Its use is not recommended. +.Sx bcrypt +is preferred over +.Sx sha512-crypt , +or +.Sx sha256-crypt , +because of +its resiliance to pipelined, and GPU based attacks - unless having a +NIST-approved algorithm is a requirement. Index: lib/libcrypt/crypt.c =================================================================== --- lib/libcrypt/crypt.c +++ lib/libcrypt/crypt.c @@ -3,6 +3,7 @@ * * Copyright (c) 1999 Mark Murray * Copyright (c) 2014 Dag-Erling Smørgrav + * Copyright (c) 2015 Derek Marcotte * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,8 +32,13 @@ __FBSDID("$FreeBSD$"); #include +#include +#include #include +#include +#include +#include #include #include @@ -41,38 +47,130 @@ /* * List of supported crypt(3) formats. * - * The default algorithm is the last entry in the list (second-to-last - * array element since the last is a sentinel). The reason for placing - * the default last rather than first is that DES needs to be at the - * bottom for the algorithm guessing logic in crypt(3) to work correctly, - * and it needs to be the default for backward compatibility. + * Ordered from most probable to least probable[1], for the find algorithm to + * preform a little better in some cases. Generally, order is not important. + * + * 1. as guessed by a random person + * */ static const struct crypt_format { const char *name; int (*func)(const char *, const char *, char *); const char *magic; + const char *const default_format; + const char *const format_regex; + + const uint8_t salt_bytes; + const bool salt_trailing_sign; /* Do we tack on a $ at the end of the salt */ } crypt_formats[] = { - { "md5", crypt_md5, "$1$" }, + { "md5", crypt_md5, "$1$", "$1$", "^\\$1\\$$", 8, true }, + { "sha512", crypt_sha512, "$6$", "$6$", "^\\$6\\$(rounds=[0-9]{0,9}\\$)?$", 16, true }, #ifdef HAS_BLOWFISH - { "blf", crypt_blowfish, "$2" }, + { "blf", crypt_blowfish, "$2", "$2b$04$", "^\\$2[ab]?\\$[0-9]{2}\\$$", 22 /* 16 * 1.333 */, false }, #endif - { "nth", crypt_nthash, "$3$" }, - { "sha256", crypt_sha256, "$5$" }, - { "sha512", crypt_sha512, "$6$" }, #ifdef HAS_DES - { "des", crypt_des, "_" }, + { "des", crypt_des, NULL, "", NULL, 2, false }, + { "des-ext", crypt_des, "_", "_..T.", "^_[A-Za-z0-9./]{4}$", 4, false }, #endif - - /* sentinel */ - { NULL, NULL, NULL } + { "nth", crypt_nthash, "$3$", "$3$", "^\\$3\\$$", 0, false }, + { "sha256", crypt_sha256, "$5$", "$5$", "^\\$5\\$(rounds=[0-9]{0,9}\\$)?$", 16, true }, + + /* Sentinel */ + { NULL, NULL, NULL, NULL, NULL, 0, NULL } }; -static const struct crypt_format *crypt_format = - &crypt_formats[(sizeof crypt_formats / sizeof *crypt_formats) - 2]; +#ifdef HAS_DES +/* Must be des if system has des. */ +static char default_format[256] = "des"; +#else +static char default_format[256] = "sha512"; +#endif + +/* Local-scope only. */ +static const struct crypt_format *crypt_find_format(const char *); +static bool crypt_validate_format_regex(const char *, const char *); +static bool crypt_format_is_modular(const char*); #define DES_SALT_ALPHABET \ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +/* + * Fill a buffer with a new salt conforming to a particular crypt format + * + * We're breaking the API convention established by crypt_set_format (return 0 + * on success) * because it might be nice to behave like the rest of C + * libraries, rather than the one deprecated function. + * + */ +int +crypt_makesalt(char *out, const char *format, size_t *outlen) +{ + const struct crypt_format *cf; + uint8_t rand_buf[4]; + size_t reqsz; + const char *prefix; + size_t prefix_len; + + /* Diff really is a size, but keeping it api compatible with + * b64_from_24bit. Only up to 4 bytes anyways, shouldn't be a problem, + * right? + */ + int diff; + unsigned int i; + + /* Find the appropriate format entry. */ + cf = crypt_find_format(format); + if (cf == NULL) + return (EINVAL); + + /* Calculate required output size. */ + if (crypt_format_is_modular(format)) { + prefix = format; + } else { + prefix = cf->default_format; + } + prefix_len = strlen(prefix); + reqsz = prefix_len + (size_t) cf->salt_bytes; + + if (cf->salt_trailing_sign) + reqsz++; + + /* Trailing '\0' */ + reqsz++; + + /* Does the output buffer have enough. */ + if (reqsz > *outlen) { + *outlen = reqsz; + return (ENOMEM); + } + + /* Start building our output. strncpy will fill the remaining buffer + * with zeros. We know we have enough due to reqsz calulation above. + */ + strncpy(out, prefix, *outlen); + out += prefix_len; + + for (i = 0; i < cf->salt_bytes; i += 4 ) { + arc4random_buf(rand_buf, 3); + + diff = MIN(cf->salt_bytes - i, 4); + b64_from_24bit(rand_buf[2], rand_buf[1], rand_buf[0], diff, &out); + } + + /* Cleanup random buffer. */ + explicit_bzero(rand_buf, sizeof(rand_buf) ); + + if (cf->salt_trailing_sign) { + out[0] = '$'; + out++; + } + + /* Don't need to add trailing '\0', strncpy above will have set it + * already. + */ + return (0); +} + /* * Returns the name of the currently selected format. */ @@ -80,7 +178,7 @@ crypt_get_format(void) { - return (crypt_format->name); + return (default_format); } /* @@ -89,15 +187,82 @@ int crypt_set_format(const char *format) { - const struct crypt_format *cf; - for (cf = crypt_formats; cf->name != NULL; ++cf) { - if (strcasecmp(cf->name, format) == 0) { - crypt_format = cf; - return (1); + if (crypt_find_format(format) == NULL) { + return (0); + } + + strncpy(default_format, format, sizeof(default_format) ); + return (1); +} + +/* + * is the crypt format a recognized as valid + */ +static bool +crypt_format_validate_regex(const char* regex, const char *format) +{ + regex_t regex_c; + int res = 0; + + /* We could cache these, but they are simple, and this function won't be + * called often. + */ + if (regcomp(®ex_c, regex, REG_EXTENDED) != 0) { + return (false); + } + + res = regexec(®ex_c, format, 0, NULL, 0); + regfree(®ex_c); + + return (!res); +} + +/* + * Is the crypt format a fancy-dancy modular format. + */ +static bool +crypt_format_is_modular(const char* format) +{ + + /* We'll treat 'new des' as modular, because they can set 24 bits of + * count via salt. + */ + return (format[0] == '$' || format[0] == '_'); +} + +/* + * Lookup our format in our internal table for a matching crypt_format + * structure. + */ +static const struct crypt_format * +crypt_find_format(const char *format) +{ + const struct crypt_format *cf; + + if (strcmp(format, "default") == 0 ) { + format = default_format; + } + + if (crypt_format_is_modular(format) ) { + /* modular crypt magic lookup, force full syntax */ + for (cf = crypt_formats; cf->name != NULL; ++cf) { + if (cf->magic != NULL && + strstr(format, cf->magic) == format && + crypt_format_validate_regex(cf->format_regex, format) ) { + return (cf); + } + } + } else { + /* name lookup */ + for (cf = crypt_formats; cf->name != NULL; ++cf) { + if (strcasecmp(cf->name, format) == 0) { + return (cf); + } } } - return (0); + + return (NULL); } /* @@ -113,20 +278,23 @@ #ifdef HAS_DES int len; #endif - + + /* Use the magic in the salt for lookup. */ for (cf = crypt_formats; cf->name != NULL; ++cf) if (cf->magic != NULL && strstr(salt, cf->magic) == salt) { func = cf->func; goto match; } #ifdef HAS_DES + /* Check if it's standard des. */ len = strlen(salt); if ((len == 13 || len == 2) && strspn(salt, DES_SALT_ALPHABET) == len) { func = crypt_des; goto match; } #endif - func = crypt_format->func; + cf = crypt_find_format(default_format); + func = cf->func; match: if (func(passwd, salt, data->__buf) != 0) return (NULL); Index: lib/libpam/modules/pam_unix/pam_unix.c =================================================================== --- lib/libpam/modules/pam_unix/pam_unix.c +++ lib/libpam/modules/pam_unix/pam_unix.c @@ -45,6 +45,7 @@ #include #include +#include #include #include #include @@ -69,17 +70,11 @@ #include #include -#define PASSWORD_HASH "md5" #define DEFAULT_WARN (2L * 7L * 86400L) /* Two weeks */ -#define SALTSIZE 32 #define LOCKED_PREFIX "*LOCKED*" #define LOCKED_PREFIX_LEN (sizeof(LOCKED_PREFIX) - 1) -static void makesalt(char []); - -static char password_hash[] = PASSWORD_HASH; - #define PAM_OPT_LOCAL_PASS "local_pass" #define PAM_OPT_NIS_PASS "nis_pass" @@ -276,13 +271,17 @@ struct ypclnt *ypclnt; const void *yp_domain, *yp_server; #endif - char salt[SALTSIZE + 1]; + int i; + char *salt; + size_t salt_sz; + int salt_err; login_cap_t *lc; struct passwd *pwd, *old_pwd; const char *user, *old_pass, *new_pass; char *encrypted; time_t passwordtime; int pfd, tfd, retval; + const char *passwd_format; if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) user = getlogin(); @@ -386,10 +385,44 @@ return (PAM_BUF_ERR); lc = login_getclass(pwd->pw_class); - if (login_setcryptfmt(lc, password_hash, NULL) == NULL) - openpam_log(PAM_LOG_ERROR, - "can't set password cipher, relying on default"); - + passwd_format = login_getcapstr(lc, "passwd_format", "", NULL); + + salt = NULL; + salt_err = 0; + salt_sz = 64 * sizeof(char); + /* We might need more memory than we guessed at initialization. + * Let's retry up to one time with new information. + */ + for (i = 0; i < 2 && salt_err == 0 && salt == NULL; i++ ) { + if ((salt = malloc(salt_sz)) == NULL) { + return (PAM_BUF_ERR); + } + + salt_err = crypt_makesalt(salt, passwd_format, &salt_sz); + switch(salt_err) { + case ENOMEM: + /* Try allocating a larger amount if this is + * the first time trying + */ + if (i == 0) + salt_err = 0; + + /* Fallthrough */ + case EINVAL: + /* Terminate loop with salt_err */ + free(salt); + salt = NULL; + + break; + } + } + + if (salt == NULL) { + login_close(lc); + PAM_LOG("Unable to create salt for crypt(3) format: %s", passwd_format); + return (PAM_SERVICE_ERR); + } + /* set password expiry date */ pwd->pw_change = 0; passwordtime = login_getcaptime(lc, "passwordtime", 0, 0); @@ -397,8 +430,8 @@ pwd->pw_change = time(NULL) + passwordtime; login_close(lc); - makesalt(salt); pwd->pw_passwd = crypt(new_pass, salt); + free(salt); #ifdef YP switch (old_pwd->pw_fields & _PWF_SOURCE) { case _PWF_FILES: @@ -451,35 +484,7 @@ } return (retval); -} - -/* Mostly stolen from passwd(1)'s local_passwd.c - markm */ -static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ - "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - -static void -to64(char *s, long v, int n) -{ - while (--n >= 0) { - *s++ = itoa64[v&0x3f]; - v >>= 6; - } -} - -/* Salt suitable for traditional DES and MD5 */ -static void -makesalt(char salt[SALTSIZE + 1]) -{ - int i; - - /* These are not really random numbers, they are just - * numbers that change to thwart construction of a - * dictionary. - */ - for (i = 0; i < SALTSIZE; i += 4) - to64(&salt[i], arc4random(), 4); - salt[SALTSIZE] = '\0'; } PAM_MODULE_ENTRY("pam_unix"); Index: lib/libutil/login.conf.5 =================================================================== --- lib/libutil/login.conf.5 +++ lib/libutil/login.conf.5 @@ -278,7 +278,14 @@ allowed before the login fails. .It "passwd_format string sha512 The encryption format that new or" changed passwords will use. -Valid values include "des", "md5", "blf", "sha256" and "sha512"; see +Valid values include Modular Crypt Format strings, +.Qq des , +.Qq md5 , +.Qq blf , +.Qq sha256 +and +.Qq sha512 ; +see .Xr crypt 3 for details. NIS clients using a Index: lib/libutil/login_cap.3 =================================================================== --- lib/libutil/login_cap.3 +++ lib/libutil/login_cap.3 @@ -569,6 +569,13 @@ on the specifier fails, .Fa error is returned to indicate this. +.Fn login_setcryptfmt +and +.Xr crypt_set_format 3 +are both deprecated in favour of +.Xr login_getcapstr 3 +and +.Xr crypt_makesalt 3 . .El .Sh SEE ALSO .Xr login 1 , Index: usr.sbin/pw/pw.h =================================================================== --- usr.sbin/pw/pw.h +++ usr.sbin/pw/pw.h @@ -107,7 +107,7 @@ char *newstr(char const * p); void pw_log(struct userconf * cnf, int mode, int which, char const * fmt,...) __printflike(4, 5); -char *pw_pwcrypt(char *password); +char *pw_pwcrypt(const char *password, const char *format); extern const char *Modes[]; extern const char *Which[]; Index: usr.sbin/pw/pw_group.c =================================================================== --- usr.sbin/pw/pw_group.c +++ usr.sbin/pw/pw_group.c @@ -93,7 +93,7 @@ errx(EX_DATAERR, "wrong encrypted passwrd"); grp->gr_passwd = line; } else - grp->gr_passwd = pw_pwcrypt(line); + grp->gr_passwd = pw_pwcrypt(line, "default"); } int Index: usr.sbin/pw/pw_user.c =================================================================== --- usr.sbin/pw/pw_user.c +++ usr.sbin/pw/pw_user.c @@ -81,10 +81,11 @@ const char *user); static char *pw_shellpolicy(struct userconf * cnf); static char *pw_password(struct userconf * cnf, char const * user, - bool dryrun); + char const * format, bool dryrun); static char *shell_path(char const * path, char *shells[], char *sh); static void rmat(uid_t uid); static void rmopie(char const * name); +static void copy_passwd_format(char *passwd_format, login_cap_t *lc, size_t max); static void mkdir_home_parents(int dfd, const char *dir) @@ -179,6 +180,7 @@ login_cap_t *lc; char line[_PASSWORD_LEN+1]; char *p; + char passwd_format[256]; if (fd == '-') { if (!pwd->pw_passwd || *pwd->pw_passwd != '*') { @@ -224,15 +226,36 @@ pwd->pw_passwd = strdup(line); } else { lc = login_getpwclass(pwd); - if (lc == NULL || - login_setcryptfmt(lc, "sha512", NULL) == NULL) - warn("setting crypt(3) format"); + copy_passwd_format(passwd_format, lc, sizeof(passwd_format) ); login_close(lc); - pwd->pw_passwd = pw_pwcrypt(line); + pwd->pw_passwd = pw_pwcrypt(line, passwd_format); } return (1); } +void +copy_passwd_format(char *passwd_format, login_cap_t *lc, size_t max) { + const char *cap; + const char *def = "sha512"; + + if (lc == NULL) { + warn("setting crypt(3) format"); + strncpy(passwd_format, def, max); + return; + } + + + cap = login_getcapstr(lc, "passwd_format", def, NULL); + if (cap == NULL) { + warn("setting crypt(3) format"); + strncpy(passwd_format, def, max); + return; + } + + strncpy(passwd_format, cap, max); + return; +} + static void perform_chgpwent(const char *name, struct passwd *pwd, char *nispasswd) { @@ -482,27 +505,53 @@ return shell_path(cnf->shelldir, cnf->shells, cnf->shell_default); } -#define SALTSIZE 32 - -static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./"; - -char * -pw_pwcrypt(char *password) +char * +pw_pwcrypt(const char *password, const char *format) { - int i; - char salt[SALTSIZE + 1]; + int i; + char *salt; + size_t salt_sz; + int salt_err; char *cryptpw; static char buf[256]; size_t pwlen; - /* - * Calculate a salt value + salt = NULL; + salt_err = 0; + salt_sz = 64 * sizeof(char); + /* We might need more memory than we guessed at initialization. + * Let's retry up to one time with new information. */ - for (i = 0; i < SALTSIZE; i++) - salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)]; - salt[SALTSIZE] = '\0'; + for (i = 0; i < 2 && salt_err == 0 && salt == NULL; i++ ) { + if ((salt = malloc(salt_sz)) == NULL) { + errx(EX_UNAVAILABLE, "out of memory"); + } + + salt_err = crypt_makesalt(salt, format, &salt_sz); + switch(salt_err) { + case ENOMEM: + /* Try allocating a larger amount if this is + * the first time trying + */ + if (i == 0) + salt_err = 0; + + /* Fallthrough */ + case EINVAL: + /* Terminate loop with salt_err */ + free(salt); + salt = NULL; + + break; + } + } + + if (salt == NULL) { + errx(EX_CONFIG, "Unable to create salt for crypt(3) format: %s", format); + } cryptpw = crypt(password, salt); + free(salt); if (cryptpw == NULL) errx(EX_CONFIG, "crypt(3) failure"); pwlen = strlcpy(buf, cryptpw, sizeof(buf)); @@ -511,8 +560,9 @@ } static char * -pw_password(struct userconf * cnf, char const * user, bool dryrun) +pw_password(struct userconf * cnf, char const * user, const char * format, bool dryrun) { + static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./!@#$%^&*()-+=<>?"; int i, l; char pwbuf[32]; @@ -543,7 +593,7 @@ default: return "*"; } - return pw_pwcrypt(pwbuf); + return pw_pwcrypt(pwbuf, format); } static int @@ -1198,6 +1248,7 @@ int rc, ch, fd = -1; size_t i; bool dryrun, nis, pretty, quiet, createhome, precrypted, genconf; + char passwd_format[256]; dryrun = nis = pretty = quiet = createhome = precrypted = false; genconf = false; @@ -1395,10 +1446,9 @@ pwd->pw_dir = pw_homepolicy(cmdcnf, homedir, pwd->pw_name); pwd->pw_shell = pw_shellpolicy(cmdcnf); lc = login_getpwclass(pwd); - if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) - warn("setting crypt(3) format"); + copy_passwd_format(passwd_format, lc, sizeof(passwd_format)); login_close(lc); - pwd->pw_passwd = pw_password(cmdcnf, pwd->pw_name, dryrun); + pwd->pw_passwd = pw_password(cmdcnf, pwd->pw_name, passwd_format, dryrun); if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0) warnx("WARNING: new account `%s' has a uid of 0 " "(superuser access!)", pwd->pw_name); @@ -1525,6 +1575,7 @@ bool precrypted; mode_t homemode = 0; time_t expire_time, password_time, now; + char passwd_format[256]; expire_time = password_time = -1; gecos = homedir = grname = name = newname = skel = shell =NULL; @@ -1740,12 +1791,11 @@ if (passwd && conf.fd == -1) { lc = login_getpwclass(pwd); - if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL) - warn("setting crypt(3) format"); + copy_passwd_format(passwd_format, lc, sizeof(passwd_format) ); login_close(lc); cnf->default_password = passwd_val(passwd, cnf->default_password); - pwd->pw_passwd = pw_password(cnf, pwd->pw_name, dryrun); + pwd->pw_passwd = pw_password(cnf, pwd->pw_name, passwd_format, dryrun); edited = true; }