diff --git a/docs/man.txt b/docs/man.txt index 0e724586..fa4639aa 100644 --- a/docs/man.txt +++ b/docs/man.txt @@ -4610,6 +4610,7 @@ Parameters available: (bool) ignore_support - Enable support for @ignoring players (int) instr_slice - Max. uninterrupted instructions per timeslice (str) leave_mesg - Logoff message for QUIT + (bool) legacy_password_hash - Use Legacy (insecure, compatible) Password Hash (int) link_cost - Cost to link an exit (int) listen_mlev - Mucker Level required for Listener programs (bool) lock_envcheck - Locks check environment for properties diff --git a/docs/mufman.html b/docs/mufman.html index 501907ec..10b41004 100644 --- a/docs/mufman.html +++ b/docs/mufman.html @@ -7337,6 +7337,7 @@

SYSPARM ( s -- s ) (bool) ignore_support - Enable support for @ignoring players (int) instr_slice - Max. uninterrupted instructions per timeslice (str) leave_mesg - Logoff message for QUIT + (bool) legacy_password_hash - Use Legacy (insecure, compatible) Password Hash (int) link_cost - Cost to link an exit (int) listen_mlev - Mucker Level required for Listener programs (bool) lock_envcheck - Locks check environment for properties diff --git a/include/fbmath.h b/include/fbmath.h index 1b7b684a..a06ff809 100644 --- a/include/fbmath.h +++ b/include/fbmath.h @@ -164,6 +164,32 @@ void MD5hex(void *dest, const void *orig, size_t len); */ int no_good(double test); +/** + * Generate a PBKDF2 password hash with the given password and salt. + * + * If salt is passed as NULL, we will generate a random 10 byte salt. + * + * If the MUCK wasn't compiled with SSL, this will transparently + * run MD5base64. + * + * The buffer provided should be at least 40 characters long, but + * bigger is better. 128 should be pretty good. The entire buffer + * will be filled with 1 byte to spare if the hash is run (if not, + * @see MD5base64). If you provide your own salt, the buffer must + * be large enough to contain the seed + 4 bytes. + * + * Seed cannot contain a $ symbol as that is reserved. + * + * @param password the password to hash + * @param password_len the strlen of the password + * @param salt the salt portion of the hash + * @param salt_len the length of the salt + * @param buffer the buffer to put the result into + * @param buffer_len the size of the buffer + */ +void pbkdf2_hash(const char* password, int password_len, const char* salt, + int salt_len, char* buffer, int buffer_len); + /** * Do a seeded random number generation * diff --git a/include/tune.h b/include/tune.h index 9a07d94d..c9402f35 100644 --- a/include/tune.h +++ b/include/tune.h @@ -163,6 +163,7 @@ extern bool tp_ignore_bidirectional; /**< Tune variable */ extern bool tp_ignore_support; /**< Tune variable */ extern int tp_instr_slice; /**< Tune variable */ extern const char *tp_leave_mesg; /**< Tune variable */ +extern bool tp_legacy_password_hash; /**< Tune variable */ extern int tp_link_cost; /**< Tune variable */ extern int tp_listen_mlev; /**< Tune variable */ extern bool tp_lock_envcheck; /**< Tune variable */ diff --git a/include/tunelist.h b/include/tunelist.h index 1033d07d..c47f6494 100644 --- a/include/tunelist.h +++ b/include/tunelist.h @@ -110,6 +110,7 @@ bool tp_ignore_bidirectional; /**> Described below */ bool tp_ignore_support; /**> Described below */ int tp_instr_slice; /**> Described below */ const char *tp_leave_mesg; /**> Described below */ +bool tp_legacy_password_hash; /**> Described below */ int tp_link_cost; /**> Described below */ int tp_listen_mlev; /**> Described below */ bool tp_lock_envcheck; /**> Described below */ @@ -1161,6 +1162,18 @@ struct tune_entry tune_list[] = { MLEV_WIZARD, true }, + { + "legacy_password_hash", + "Use Legacy (insecure, compatible) Password Hash", + "Misc", + "", + TP_TYPE_BOOLEAN, + .defaultval.b=false, + .currentval.b=&tp_legacy_password_hash, + MLEV_WIZARD, + MLEV_WIZARD, + true + }, { "link_cost", "Cost to link an exit", diff --git a/src/fbmath.c b/src/fbmath.c index d86cc4c0..a39451e4 100644 --- a/src/fbmath.c +++ b/src/fbmath.c @@ -18,6 +18,16 @@ #include "fbmath.h" #include "inst.h" +#ifdef USE_SSL +# ifdef HAVE_OPENSSL +# include +# include +# else +# include +# include +# endif +#endif + /** * Generate a random floating point number * @@ -106,6 +116,12 @@ no_good(double test) * **************************************************************************/ +/* + * We will use this in a couple places. + */ +static const unsigned char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /* The four core functions - F1 is optimized somewhat */ /* #define F1(x, y, z) (x & y | ~x & z) */ @@ -388,7 +404,6 @@ MD5hash(void *dest, const void *orig, size_t len) static void Base64Encode(char *outbuf, const void *inbuf, size_t inlen) { - const unsigned char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const unsigned char *inb = inbuf; unsigned char *out = NULL; size_t numb; @@ -562,3 +577,107 @@ rnd(void *buffer) MD5hash(digest, digest, sizeof(digest)); return (digest[0]); } + +/********************************************************************* + * + * PBKDF2 Password Hashing stuff + * + *********************************************************************/ + +#ifdef USE_SSL +static void +PBKDF2_HMAC_SHA_512(const char* pass, const unsigned char* salt, + int32_t iterations, uint32_t outputBytes, + char* hexResult) +{ + unsigned int i; + unsigned char digest[outputBytes]; + PKCS5_PBKDF2_HMAC(pass, strlen(pass), salt, strlen(salt), iterations, + EVP_sha512(), outputBytes, digest); + for (i = 0; i < sizeof(digest); i++) + sprintf(hexResult + (i * 2), "%02x", 255 & digest[i]); +} +#endif + +/** + * Generate a PBKDF2 password hash with the given password and salt. + * + * If salt is passed as NULL, we will generate a random 10 byte salt. + * + * If the MUCK wasn't compiled with SSL, this will transparently + * run MD5base64. + * + * The buffer provided should be at least 40 characters long, but + * bigger is better. 128 should be pretty good. The entire buffer + * will be filled with 1 byte to spare if the hash is run (if not, + * @see MD5base64). If you provide your own salt, the buffer must + * be large enough to contain the seed + 4 bytes. + * + * Seed cannot contain a $ symbol as that is reserved. + * + * @param password the password to hash + * @param password_len the strlen of the password + * @param salt the salt portion of the hash + * @param salt_len the length of the salt + * @param buffer the buffer to put the result into + * @param buffer_len the size of the buffer + */ +void +pbkdf2_hash(const char* password, int password_len, const char* salt, + int salt_len, char* buffer, int buffer_len) +{ +#ifdef USE_SSL + char salt_buf[11]; + unsigned int i, digest_len; + unsigned char* digest; + + /* + * Generate a salt if we need to + */ + if (!salt) { + for (i = 0; i < 10; i++) { + salt_buf[i] = b64[RANDOM()%sizeof(b64)]; + } + + salt_buf[10] = '\0'; + + salt = salt_buf; + salt_len = 10; + } + + /* + * Clear the buffer + */ + memset(buffer, 0, buffer_len); + + /* + * Copy the salt into the buffer along with the markers. + */ + snprintf(buffer, buffer_len, "$1$%s$", salt); + + /* + * Calculate our digest size + */ + digest_len = ((buffer_len - salt_len - 4)/2); + digest = (unsigned char*)malloc(digest_len+1); + + /* + * Generate a hash with the rest of the buffer. Use 1000 iterations. + */ + PKCS5_PBKDF2_HMAC(password, password_len, salt, salt_len, 1000, + EVP_sha512(), digest_len, digest); + + for (i = 0; i < digest_len; i++) { + sprintf(buffer + salt_len + 4 + (i * 2), "%02x", 255 & digest[i]); + } + + free(digest); + + /* + * That should be it! Fingers crossed + */ + +#else + MD5base64(buffer, password, password_len); +#endif +} \ No newline at end of file diff --git a/src/player.c b/src/player.c index a129aad9..e8ef978f 100644 --- a/src/player.c +++ b/src/player.c @@ -79,23 +79,50 @@ lookup_player(const char *name) int check_password(dbref player, const char *password) { - char md5buf[64]; + char md5buf[128]; + int len_hash = 0; + int len_salt = 0; + + const char* salt; + const char *processed = password; const char *pword = PLAYER_PASSWORD(player); - if (password == NULL) { - MD5base64(md5buf, "", 0); + if (!pword || !*pword) { + return 1; + } + + /* + * Get the hash length + */ + len_hash = strlen(pword); + + /* + * Is it a seeded hash? If so, let's extract the seed. + */ + if ((len_hash > 4) && (!strncmp(pword, "$1$", 3))) { + /* Figure out the seed */ + salt = pword + 3; + + for ( ; (salt[len_salt] != '$') && ((len_salt+3) < len_hash); + len_salt++) { } + + pbkdf2_hash(password, strlen(password), salt, len_salt, md5buf, + sizeof(md5buf)); + processed = md5buf; } else { - if (*password) { - MD5base64(md5buf, password, strlen(password)); + if (password == NULL) { + MD5base64(md5buf, "", 0); processed = md5buf; + } else { + if (*password) { + MD5base64(md5buf, password, strlen(password)); + processed = md5buf; + } } } - if (!pword || !*pword) - return 1; - if (!strcmp(pword, processed)) return 1; @@ -128,11 +155,17 @@ set_password_raw(dbref player, const char *password) void set_password(dbref player, const char *password) { - char md5buf[64]; + char md5buf[128]; const char *processed = password; if (*password) { - MD5base64(md5buf, password, strlen(password)); + if (tp_legacy_password_hash) { + MD5base64(md5buf, password, strlen(password)); + } else { + pbkdf2_hash(password, strlen(password), NULL, 0, md5buf, + sizeof(md5buf)); + } + processed = md5buf; } @@ -375,7 +408,13 @@ void delete_player(dbref who) { int result; - char buf[BUFFER_LEN]; + /* + * TODO: I increased the buffer size here to stifle a warning. + * The underlying error is namebuf is actually too large. + * The snprinft below for "Renaming %s(#%d)..." was throwing + * a warning. + */ + char buf[BUFFER_LEN+BUFFER_LEN]; char namebuf[BUFFER_LEN]; dbref found, ren; int j; @@ -398,6 +437,17 @@ delete_player(dbref who) ren = (i == who) ? found : i; j = 0; + /* + * TODO: This, technically, can enable player names + * to exceed the max name length. I'm not sure + * we care since this should be a super rare + * occasion? But exceeding the max name length + * could cause chaos in other areas. + * + * I'm not even sure how to test this, it seems + * like it would be from a catastrophic DB failure. + */ + do { snprintf(namebuf, sizeof(namebuf), "%s%d", NAME(ren), ++j);