and Argon2id) too. If you were using this module on version 7.2, please see
the `dist/atheme.conf.example` file for migration instructions. The names of
the configuration options have changed! You will need libargon2 available at
- configure-time.
+ configure-time (`--with-argon2`).
Security
--------
(where supported, e.g. in WeeChat) to achieve its purpose of preventing the
user's account password from persisting in on-disk log files.
-- Services is now capable of using OpenBSD `crypt_newhash(3)` to encrypt
- and verify passwords.
-
- Services now has a much more rigorous random number generation interface
and will e.g. refuse to use `arc4random(3)` unless we are actually on
OpenBSD (which is the only platform that uses a secure algorithm for it).
---------------------
- The existing crypto modules no longer need OpenSSL (or any crypto library)
- Add support for scrypt password encryption with `modules/crypto/scrypt`.
- This requires libsodium.
+ The scrypt module requires libsodium (`--with-sodium`).
+- Add support for bcrypt password encryption with `modules/crypto/bcrypt`.
- `libathemecore/crypto.c`: log current crypto provider on mod(un/re)load
- `libathemecore/crypto.c`: rip out plaintext fallback implementation
- Make old modules (`ircservices`, `pbkdf2`, `rawmd5`, `rawsha1`) verify-only
done
- for ac_func in crypt_checkpass
-do :
- ac_fn_c_check_func "$LINENO" "crypt_checkpass" "ac_cv_func_crypt_checkpass"
-if test "x$ac_cv_func_crypt_checkpass" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_CRYPT_CHECKPASS 1
-_ACEOF
-
-fi
-done
-
- for ac_func in crypt_newhash
-do :
- ac_fn_c_check_func "$LINENO" "crypt_newhash" "ac_cv_func_crypt_newhash"
-if test "x$ac_cv_func_crypt_newhash" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_CRYPT_NEWHASH 1
-_ACEOF
-
-fi
-done
-
for ac_func in consttime_memequal
do :
ac_fn_c_check_func "$LINENO" "consttime_memequal" "ac_cv_func_consttime_memequal"
* Argon2 (Password Hashing Competition 2015) modules/crypto/argon2
* scrypt (Tarsnap Online Backup Service) modules/crypto/scrypt
* PBKDF2 (Including support for SASL SCRAM-SHA) modules/crypto/pbkdf2v2
- * OpenBSD crypt_newhash(3) a la '$2b$...$...' modules/crypto/crypt3-openbsd
+ * bcrypt (EksBlowfish; from Niels Provos etc.) modules/crypto/bcrypt
* SHA2-512 crypt(3) a la '$6$...' modules/crypto/crypt3-sha2-512
* SHA2-256 crypt(3) a la '$5$...' modules/crypto/crypt3-sha2-256
*
* hashes. However, you should also load PBKDF2 v2 (if you don't decide to use
* anything else), because the PBKDF2 v1 module is now verify-only.
*
+ * The bcrypt module will truncate passwords greater than 72 characters. It is
+ * also capable of verifying the older $2a$ digests that contain an integer
+ * wrap-around bug, as used on e.g. Anope. It is not capable of verifying the
+ * PHP-bcrypt $2x$ and $2y$ digests; but $2y$ can simply be changed to $2b$.
+ * All successfully-verified passwords not using $2b$ will be converted to it.
+ * This is an encryption-capable module, but its use is discouraged unless you
+ * need to use it for interoperability with some other piece of software.
+ *
* The crypt3-* modules depend on your platform crypt(3) supporting the
* respective algorithms. This is not guaranteed to be the case. If you used
* modules/crypto/posix on Linux, you need crypt3-md5. If you used
*/
#loadmodule "modules/crypto/argon2"; /* --with-argon2 */
#loadmodule "modules/crypto/scrypt"; /* --with-sodium */
-#loadmodule "modules/crypto/crypt3-openbsd"; /* Needs OpenBSD */
loadmodule "modules/crypto/pbkdf2v2";
+#loadmodule "modules/crypto/bcrypt"; /* See notes above */
loadmodule "modules/crypto/pbkdf2"; /* Verify-only, see prev. */
#loadmodule "modules/crypto/crypt3-sha2-512"; /* Needs crypt(3) support */
#loadmodule "modules/crypto/crypt3-sha2-256"; /* Needs crypt(3) support */
*/
#pbkdf2v2_saltlen = 32;
- /* (*) crypt3_openbsd_newhash_pref
+ /* (*) bcrypt_cost
+ *
+ * Amount of rounds to perform for new passwords (as a power of 2).
+ * You should raise this as high as is reasonable. A benchmark
+ * program is available alongside this software to aid in this
+ * process.
*
- * The "pref" parameter for OpenBSD crypt_newhash(3).
- * Please see <https://man.openbsd.org/crypt_checkpass.3>
- * The default is "bcrypt,a"
+ * Valid values are 4 to 31 (inclusive)
+ * The default is 7
*/
- #crypt3_openbsd_newhash_pref = "bcrypt,a";
+ #bcrypt_cost = 7;
/* (*) crypt3_sha2_256_rounds
* (*) crypt3_sha2_512_rounds
/* Define to 1 if crypt(3) appears to be usable */
#undef HAVE_CRYPT
-/* Define to 1 if you have the `crypt_checkpass' function. */
-#undef HAVE_CRYPT_CHECKPASS
-
/* Define to 1 if you have the <crypt.h> header file. */
#undef HAVE_CRYPT_H
-/* Define to 1 if you have the `crypt_newhash' function. */
-#undef HAVE_CRYPT_NEWHASH
-
/* Define to 1 if you have the <ctype.h> header file. */
#undef HAVE_CTYPE_H
AC_CHECK_HEADERS([time.h], [], [], [])
AC_CHECK_HEADERS([unistd.h], [], [], [])
- AC_CHECK_FUNCS([crypt_checkpass], [], [])
- AC_CHECK_FUNCS([crypt_newhash], [], [])
AC_CHECK_FUNCS([consttime_memequal], [], [])
AC_CHECK_FUNCS([dup2], [], [ATHEME_REQUIRED_FUNC_MISSING])
AC_CHECK_FUNCS([execve], [], [ATHEME_REQUIRED_FUNC_MISSING])
MODULE = crypto
SRCS = \
argon2.c \
- crypt3-openbsd.c \
+ bcrypt.c \
crypt3-sha2-256.c \
crypt3-sha2-512.c \
main.c \
--- /dev/null
+/*
+ * SPDX-License-Identifier: ISC
+ * SPDX-URL: https://spdx.org/licenses/ISC.html
+ *
+ * Copyright (C) 2020 Aaron M. D. Jones <aaronmdjones@gmail.com>
+ */
+
+#include <atheme.h>
+
+#define ATHEME_BCRYPT_LOADSALT_FORMAT "$2%1[ab]$%2u$%22[" BASE64_ALPHABET_CRYPT3_BLOWFISH "]"
+#define ATHEME_BCRYPT_LOADHASH_FORMAT ATHEME_BCRYPT_LOADSALT_FORMAT "%32[" BASE64_ALPHABET_CRYPT3_BLOWFISH "]"
+#define ATHEME_BCRYPT_PARAMLEN_MIN 60U // The fixed-length salt and output guarantees a minimum MCF length
+
+// The algorithm generates a 24-byte result but almost every implementation only uses 23 of them
+#define ATHEME_BCRYPT_HASHLEN_TRUNC 23U
+
+#define ATHEME_BCRYPT_PREFIXLEN 7U
+#define ATHEME_BCRYPT_SALTLEN_B64 22U // base64-encoded 16 bytes *without padding* == 22 bytes
+#define ATHEME_BCRYPT_HASHLEN_B64 31U // base64-encoded 23 bytes *without padding* == 31 bytes
+
+#define CRYPTO_MODULE_NAME "crypto/bcrypt"
+
+static mowgli_list_t **crypto_conf_table = NULL;
+
+static unsigned int atheme_bcrypt_cost = ATHEME_BCRYPT_ROUNDS_DEF;
+
+static const char *
+atheme_bcrypt_crypt(const char *const restrict password, const char ATHEME_VATTR_UNUSED *const restrict parameters)
+{
+ static char result[PASSLEN + 1];
+ unsigned char salt[ATHEME_BCRYPT_SALTLEN];
+ unsigned char hash[ATHEME_BCRYPT_HASHLEN];
+
+ (void) memset(result, 0x00, sizeof result);
+ (void) atheme_random_buf(salt, sizeof salt);
+
+ if (! atheme_eks_bf_compute(password, ATHEME_BCRYPT_VERSION_MINOR, atheme_bcrypt_cost, salt, hash))
+ {
+ (void) slog(LG_ERROR, "%s: atheme_eks_bf_compute() failed (BUG)", MOWGLI_FUNC_NAME);
+ return NULL;
+ }
+
+ if (snprintf(result, sizeof result, "$2%c$%2.2u$", ATHEME_BCRYPT_VERSION_MINOR, atheme_bcrypt_cost) !=
+ ATHEME_BCRYPT_PREFIXLEN)
+ {
+ (void) slog(LG_ERROR, "%s: snprintf(3) did not write an acceptable amount of data (BUG)",
+ MOWGLI_FUNC_NAME);
+ (void) smemzero(hash, sizeof hash);
+ return NULL;
+ }
+
+ if (base64_encode_table(salt, ATHEME_BCRYPT_SALTLEN, result + ATHEME_BCRYPT_PREFIXLEN,
+ ATHEME_BCRYPT_SALTLEN_B64, BASE64_ALPHABET_CRYPT3_BLOWFISH) != ATHEME_BCRYPT_SALTLEN_B64)
+ {
+ (void) slog(LG_ERROR, "%s: base64_encode_table(salt) failed (BUG)", MOWGLI_FUNC_NAME);
+ (void) smemzero(hash, sizeof hash);
+ return NULL;
+ }
+
+ // We only encode 23 of the 24 bytes for wider compatibility with other implementations
+ if (base64_encode_table(hash, ATHEME_BCRYPT_HASHLEN_TRUNC, result + ATHEME_BCRYPT_PREFIXLEN +
+ ATHEME_BCRYPT_SALTLEN_B64, ATHEME_BCRYPT_HASHLEN_B64, BASE64_ALPHABET_CRYPT3_BLOWFISH) !=
+ ATHEME_BCRYPT_HASHLEN_B64)
+ {
+ (void) slog(LG_ERROR, "%s: base64_encode_table(hash) failed (BUG)", MOWGLI_FUNC_NAME);
+ (void) smemzero(hash, sizeof hash);
+ (void) smemzero(result, sizeof result);
+ return NULL;
+ }
+
+ (void) smemzero(hash, sizeof hash);
+ return result;
+}
+
+static bool ATHEME_FATTR_WUR
+atheme_bcrypt_verify(const char *const restrict password, const char *const restrict parameters,
+ unsigned int *const restrict flags)
+{
+ char minor[2];
+ unsigned int cost;
+ char salt64[BUFSIZE];
+ char hash64[BUFSIZE];
+
+ unsigned char salt[ATHEME_BCRYPT_SALTLEN];
+ unsigned char hash[ATHEME_BCRYPT_HASHLEN];
+ unsigned char cmphash[ATHEME_BCRYPT_HASHLEN];
+
+ bool retval = false;
+
+ if (strlen(parameters) < ATHEME_BCRYPT_PARAMLEN_MIN)
+ {
+ (void) slog(LG_DEBUG, "%s: parameters are not the correct length", MOWGLI_FUNC_NAME);
+ return false;
+ }
+ if (sscanf(parameters, ATHEME_BCRYPT_LOADHASH_FORMAT, minor, &cost, salt64, hash64) != 4)
+ {
+ (void) slog(LG_DEBUG, "%s: sscanf(3) was unsuccessful", MOWGLI_FUNC_NAME);
+ goto cleanup;
+ }
+ if (cost < ATHEME_BCRYPT_ROUNDS_MIN || cost > ATHEME_BCRYPT_ROUNDS_MAX)
+ {
+ (void) slog(LG_DEBUG, "%s: cost parameter '%u' unacceptable", MOWGLI_FUNC_NAME, cost);
+ goto cleanup;
+ }
+ if (base64_decode_table(salt64, salt, sizeof salt, BASE64_ALPHABET_CRYPT3_BLOWFISH) != ATHEME_BCRYPT_SALTLEN)
+ {
+ (void) slog(LG_DEBUG, "%s: base64_decode_table(salt) failed", MOWGLI_FUNC_NAME);
+ goto cleanup;
+ }
+
+ // We can verify 23-byte or 24-byte digests for wider compatibility with other implementations
+ const size_t hashlen = base64_decode_table(hash64, hash, sizeof hash, BASE64_ALPHABET_CRYPT3_BLOWFISH);
+
+ if (hashlen == BASE64_FAIL || hashlen < ATHEME_BCRYPT_HASHLEN_TRUNC)
+ {
+ (void) slog(LG_DEBUG, "%s: base64_decode_table(hash) failed", MOWGLI_FUNC_NAME);
+ goto cleanup;
+ }
+
+ *flags |= PWVERIFY_FLAG_MYMODULE;
+
+ if (! atheme_eks_bf_compute(password, (unsigned int) minor[0], cost, salt, cmphash))
+ {
+ (void) slog(LG_ERROR, "%s: atheme_eks_bf_compute() failed (BUG)", MOWGLI_FUNC_NAME);
+ goto cleanup;
+ }
+ if (smemcmp(hash, cmphash, hashlen) != 0)
+ {
+ (void) slog(LG_DEBUG, "%s: smemcmp() mismatch; incorrect password?", MOWGLI_FUNC_NAME);
+ goto cleanup;
+ }
+
+ retval = true;
+
+ (void) slog(LG_DEBUG, "%s: authentication successful", MOWGLI_FUNC_NAME);
+
+ if (minor[0] != ATHEME_BCRYPT_VERSION_MINOR)
+ {
+ (void) slog(LG_DEBUG, "%s: minor version (%s) is not current (%c)", MOWGLI_FUNC_NAME, minor,
+ ATHEME_BCRYPT_VERSION_MINOR);
+
+ *flags |= PWVERIFY_FLAG_RECRYPT;
+ }
+ if (cost != atheme_bcrypt_cost)
+ {
+ (void) slog(LG_DEBUG, "%s: cost (%u) is not the default (%u)", MOWGLI_FUNC_NAME, cost,
+ atheme_bcrypt_cost);
+
+ *flags |= PWVERIFY_FLAG_RECRYPT;
+ }
+
+cleanup:
+ (void) smemzero(hash, sizeof hash);
+ (void) smemzero(hash64, sizeof hash64);
+ (void) smemzero(cmphash, sizeof cmphash);
+
+ return retval;
+}
+
+static const struct crypt_impl crypto_bcrypt_impl = {
+
+ .id = CRYPTO_MODULE_NAME,
+ .crypt = &atheme_bcrypt_crypt,
+ .verify = &atheme_bcrypt_verify,
+};
+
+static void
+mod_init(struct module *const restrict m)
+{
+ if (! atheme_eks_bf_testsuite_run())
+ {
+ (void) slog(LG_ERROR, "%s: self-test failed (BUG)", m->name);
+
+ m->mflags |= MODFLAG_FAIL;
+ return;
+ }
+
+ MODULE_TRY_REQUEST_SYMBOL(m, crypto_conf_table, "crypto/main", "crypto_conf_table")
+
+ (void) add_uint_conf_item("bcrypt_cost", *crypto_conf_table, 0, &atheme_bcrypt_cost,
+ ATHEME_BCRYPT_ROUNDS_MIN, ATHEME_BCRYPT_ROUNDS_MAX, ATHEME_BCRYPT_ROUNDS_DEF);
+
+ (void) crypt_register(&crypto_bcrypt_impl);
+
+ (void) slog(LG_INFO, "%s: WARNING: Passwords greater than 72 characters are truncated!", m->name);
+
+ m->mflags |= MODFLAG_DBCRYPTO;
+}
+
+static void
+mod_deinit(const enum module_unload_intent ATHEME_VATTR_UNUSED intent)
+{
+ (void) del_conf_item("bcrypt_cost", *crypto_conf_table);
+
+ (void) crypt_unregister(&crypto_bcrypt_impl);
+}
+
+SIMPLE_DECLARE_MODULE_V1(CRYPTO_MODULE_NAME, MODULE_UNLOAD_CAPABILITY_OK)
+++ /dev/null
-/*
- * SPDX-License-Identifier: ISC
- * SPDX-URL: https://spdx.org/licenses/ISC.html
- *
- * Copyright (C) 2019 Atheme Development Group (https://atheme.github.io/)
- *
- * OpenBSD-style crypt_checkpass(3)/crypt_newhash(3) wrapper.
- */
-
-#include <atheme.h>
-
-#define CRYPTO_MODULE_NAME "crypto/crypt3-openbsd"
-
-#if defined(__OpenBSD__) && defined(HAVE_CRYPT_CHECKPASS) && defined(HAVE_CRYPT_NEWHASH)
-
-#define CRYPT3_PREF_DEF "bcrypt,a"
-
-static char *crypt3_pref = NULL;
-
-static mowgli_list_t **crypto_conf_table = NULL;
-
-static const char * ATHEME_FATTR_WUR
-atheme_crypt3_openbsd_crypt(const char *const restrict password,
- const char ATHEME_VATTR_UNUSED *const restrict parameters)
-{
- static char result[PASSLEN + 1];
-
- if (crypt_newhash(password, crypt3_pref, result, sizeof result) != 0)
- {
- (void) slog(LG_ERROR, "%s: crypt_newhash(3): %s", MOWGLI_FUNC_NAME, strerror(errno));
- return NULL;
- }
-
- return result;
-}
-
-static bool ATHEME_FATTR_WUR
-atheme_crypt3_openbsd_verify(const char *const restrict password, const char *const restrict parameters,
- unsigned int ATHEME_VATTR_UNUSED *const restrict flags)
-{
- if (crypt_checkpass(password, parameters) != 0)
- {
- (void) slog(LG_DEBUG, "%s: crypt_checkpass(3): %s", MOWGLI_FUNC_NAME, strerror(errno));
- return false;
- }
-
- return true;
-}
-
-static const struct crypt_impl crypto_crypt3_impl = {
-
- .id = CRYPTO_MODULE_NAME,
- .crypt = &atheme_crypt3_openbsd_crypt,
- .verify = &atheme_crypt3_openbsd_verify,
-};
-
-static void
-mod_init(struct module *const restrict m)
-{
- MODULE_TRY_REQUEST_SYMBOL(m, crypto_conf_table, "crypto/main", "crypto_conf_table")
-
- (void) add_dupstr_conf_item("crypt3_openbsd_newhash_pref", *crypto_conf_table, 0, &crypt3_pref, CRYPT3_PREF_DEF);
-
- (void) crypt_register(&crypto_crypt3_impl);
-
- m->mflags |= MODFLAG_DBCRYPTO;
-}
-
-static void
-mod_deinit(const enum module_unload_intent ATHEME_VATTR_UNUSED intent)
-{
- (void) del_conf_item("crypt3_openbsd_newhash_pref", *crypto_conf_table);
-
- (void) crypt_unregister(&crypto_crypt3_impl);
-}
-
-#else /* __OpenBSD__ && HAVE_CRYPT_CHECKPASS && HAVE_CRYPT_NEWHASH */
-
-static void
-mod_init(struct module *const restrict m)
-{
- (void) slog(LG_ERROR, "Module %s requires a recent OpenBSD system; refusing to load.", m->name);
-
- m->mflags |= MODFLAG_FAIL;
-}
-
-static void
-mod_deinit(const enum module_unload_intent ATHEME_VATTR_UNUSED intent)
-{
- // Nothing To Do
-}
-
-#endif /* !(__OpenBSD__ && HAVE_CRYPT_CHECKPASS && HAVE_CRYPT_NEWHASH) */
-
-SIMPLE_DECLARE_MODULE_V1(CRYPTO_MODULE_NAME, MODULE_UNLOAD_CAPABILITY_OK)
#define SCANFMT_ANOPE_ENC_SHA256 "$anope$enc_sha256$" SCANFMT_BASE64_RFC4648 "$" SCANFMT_BASE64_RFC4648
#define SCANFMT_ARGON2 "$%[A-Za-z0-9]$v=%u$m=%u,t=%u,p=%u$" SCANFMT_BASE64_RFC4648 "$" SCANFMT_BASE64_RFC4648
#define SCANFMT_BASE64 "$base64$" SCANFMT_BASE64_RFC4648
+#define SCANFMT_BCRYPT "$2%1[ab]$%2u$%22[" BASE64_ALPHABET_CRYPT3 "]%31[" BASE64_ALPHABET_CRYPT3 "]"
#define SCANFMT_CRYPT3_DES SCANFMT_BASE64_CRYPT3
#define SCANFMT_CRYPT3_MD5 "$1$" SCANFMT_BASE64_CRYPT3 "$" SCANFMT_BASE64_CRYPT3
#define SCANFMT_CRYPT3_SHA2_256 "$5$" SCANFMT_BASE64_CRYPT3 "$" SCANFMT_BASE64_CRYPT3
TYPE_ARGON2I,
TYPE_ARGON2ID,
TYPE_BASE64,
+ TYPE_BCRYPT,
TYPE_CRYPT3_DES,
TYPE_CRYPT3_MD5,
TYPE_CRYPT3_SHA2_256,
return "crypto/argon2 (Argon2id)";
case TYPE_BASE64:
return "crypto/base64 (\00304PLAIN-TEXT!\003)";
+ case TYPE_BCRYPT:
+ return "crypto/bcrypt (EksBlowfish)";
case TYPE_CRYPT3_DES:
return "crypto/crypt3-des (DES)";
case TYPE_CRYPT3_MD5:
continue_if_fail(mu != NULL);
const char *const pw = mu->pass;
+ const size_t pwlen = strlen(pw);
if (! (mu->flags & MU_CRYPTPASS))
{
{
pwhashes[TYPE_BASE64]++;
}
- else if (sscanf(pw, SCANFMT_CRYPT3_DES, s1) == 1 && strlen(s1) == 13U && strcmp(s1, pw) == 0)
+ else if (pwlen >= 60U && sscanf(pw, SCANFMT_BCRYPT, s1, &i1, s2, s3) == 4)
+ {
+ pwhashes[TYPE_BCRYPT]++;
+ }
+ else if (pwlen == 13U && sscanf(pw, SCANFMT_CRYPT3_DES, s1) == 1 && strcmp(s1, pw) == 0)
{
// Fuzzy (no rigid format)
pwhashes[TYPE_CRYPT3_DES]++;
{
pwhashes[TYPE_IRCSERVICES]++;
}
- else if (sscanf(pw, SCANFMT_PBKDF2, s1) == 1 && strlen(s1) == 140U && strcmp(s1, pw) == 0)
+ else if (pwlen == 140U && sscanf(pw, SCANFMT_PBKDF2, s1) == 1 && strcmp(s1, pw) == 0)
{
// Fuzzy (no rigid format)
pwhashes[TYPE_PBKDF2]++;