From: Arran Cudbard-Bell Date: Thu, 29 Aug 2019 01:37:08 +0000 (-0400) Subject: Various fixes in password.c, rlm_pap and rlm_chap X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e5daf0d423223ca816c5d3abe8ffa3ff4267ab64;p=thirdparty%2Ffreeradius-server.git Various fixes in password.c, rlm_pap and rlm_chap - Move password attributes under Password-Root - Apply tighter bounds checks on hash length in password.c - Failover between "known good" password types in password.c if one type can't be normalised or is the wrong length. - Merge password info arrays into one table in password.c - Provide two public functions in password.c, one provides a cannonical version of a password attribute, the other rewrites all the password attributes in the request->control list to their canonical form. - s/(S?)SHA-Password/\1SHA1-Password/ - Support multiple levels of preprocessing callbacks - Support restricting password types password.c can return - Use Password-Root TLV to be more efficient about locating candidate "known good" passwords (should be MUCH faster, and no longer quadratic). - Use proper hash length macros in the hash info table in password.c - Split SHA2-Password and SHA3-Password values into SHA[23]-(224|256|348|512)-Password attributes automatically. - Provide macro wrappers to allow us to easily add additional EVP based hashing mechanisms. - Fix pap_auth_func signature so known_good is also constified. - Prevent normalisation of Cleartext-Password, that's probably always the wrong thing to do. --- diff --git a/share/dictionary/freeradius/dictionary.freeradius.internal.password b/share/dictionary/freeradius/dictionary.freeradius.internal.password index 68f0db540ac..35a37126349 100644 --- a/share/dictionary/freeradius/dictionary.freeradius.internal.password +++ b/share/dictionary/freeradius/dictionary.freeradius.internal.password @@ -13,33 +13,52 @@ # FLAGS internal -# -# Password with header *MUST* always be first, and all attributes that -# come after it should use consecutive numbers. -# -# If you change this, the server will refuse to compile. -# -ATTRIBUTE Password-With-Header 5000 string -ATTRIBUTE Cleartext-Password 5001 string -ATTRIBUTE Crypt-Password 5002 string -ATTRIBUTE LM-Password 5003 octets -ATTRIBUTE MD5-Password 5004 octets -ATTRIBUTE MS-CHAP-Password 5005 string -ATTRIBUTE NS-MTA-MD5-Password 5006 string -ATTRIBUTE NT-Password 5007 octets -ATTRIBUTE PBKDF2-Password 5008 octets -ATTRIBUTE SHA-Password 5009 octets -ATTRIBUTE SHA1-Password 5010 octets -ATTRIBUTE SHA2-Password 5011 octets -ATTRIBUTE SHA3-Password 5012 octets -ATTRIBUTE SMD5-Password 5013 octets -ATTRIBUTE SSHA-Password 5014 octets -ATTRIBUTE SSHA1-Password 5015 octets -ATTRIBUTE SSHA2-224-Password 5016 octets -ATTRIBUTE SSHA2-256-Password 5017 octets -ATTRIBUTE SSHA2-384-Password 5018 octets -ATTRIBUTE SSHA2-512-Password 5019 octets -ATTRIBUTE SSHA3-224-Password 5020 octets -ATTRIBUTE SSHA3-256-Password 5021 octets -ATTRIBUTE SSHA3-384-Password 5022 octets -ATTRIBUTE SSHA3-512-Password 5023 octets +ATTRIBUTE Password-Root 2004 tlv +BEGIN-TLV Password-Root +ATTRIBUTE Password-With-Header 1 string + +ATTRIBUTE Cleartext-Password 2 string + +ATTRIBUTE Crypt-Password 3 string + +ATTRIBUTE LM-Password 4 octets + +ATTRIBUTE MD5-Password 5 octets + +ATTRIBUTE MS-CHAP-Password 6 string + +ATTRIBUTE NS-MTA-MD5-Password 7 string + +ATTRIBUTE NT-Password 8 octets + +ATTRIBUTE PBKDF2-Password 9 octets + +ATTRIBUTE SHA1-Password 10 octets + +ATTRIBUTE SHA2-Password 11 octets +ATTRIBUTE SHA2-224-Password 12 octets +ATTRIBUTE SHA2-256-Password 13 octets +ATTRIBUTE SHA2-384-Password 14 octets +ATTRIBUTE SHA2-512-Password 15 octets + +ATTRIBUTE SHA3-Password 16 octets +ATTRIBUTE SHA3-224-Password 17 octets +ATTRIBUTE SHA3-256-Password 18 octets +ATTRIBUTE SHA3-384-Password 19 octets +ATTRIBUTE SHA3-512-Password 20 octets + +ATTRIBUTE SMD5-Password 21 octets + +ATTRIBUTE SSHA1-Password 22 octets + +ATTRIBUTE SSHA2-224-Password 23 octets +ATTRIBUTE SSHA2-256-Password 24 octets +ATTRIBUTE SSHA2-384-Password 25 octets +ATTRIBUTE SSHA2-512-Password 26 octets + +ATTRIBUTE SSHA3-224-Password 27 octets +ATTRIBUTE SSHA3-256-Password 28 octets +ATTRIBUTE SSHA3-384-Password 29 octets +ATTRIBUTE SSHA3-512-Password 30 octets + +END-TLV Password-Root diff --git a/src/lib/server/password.c b/src/lib/server/password.c index 18b497d1d55..a6ed3e5fb04 100644 --- a/src/lib/server/password.c +++ b/src/lib/server/password.c @@ -25,34 +25,80 @@ RCSID("$Id$") #include -#include -#include #include +#include +#include +#include +#include +#include #include #ifdef HAVE_OPENSSL_EVP_H # include +# include #endif +typedef enum { + PASSWORD_CLEARTEXT = 0, //!< Variable length. + PASSWORD_HASH, //!< Fixed lenth. + PASSWORD_HASH_SALTED, //!< Fixed length hash, variable length salt. + PASSWORD_HASH_VARIABLE //!< Variable length everything. +} password_type_t; + +/** Apply preprocessing logic to a password value + * + * @param[in] ctx to allocate returned value in. + * @ + */ +typedef VALUE_PAIR *(*password_preprocess_t)(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *in); + +/** Password information + * + */ +typedef struct { + password_type_t type; //!< What type of password value this is. + fr_dict_attr_t const **da; //!< Dictionary attribute representing this type of password. + password_preprocess_t func; //!< Preprocessing function. + size_t min_hash_len; //!< Minimum length of the decoded string if normifying. + ///< If 0, will be ignored. + size_t max_hash_len; //!< Maximum length of the decoded string if normifying. + ///< If 0, will be ignored. + bool no_normify; //!< Don't attempt to normalise the contents of this + ///< attribute using the hex/base64 decoders. +} password_info_t; + static fr_dict_t *dict_freeradius = NULL; +static fr_dict_t *dict_radius = NULL; static fr_dict_attr_t const *attr_cleartext_password; static fr_dict_attr_t const *attr_password_with_header; +static fr_dict_attr_t const *attr_password_root; static fr_dict_attr_t const *attr_md5_password; static fr_dict_attr_t const *attr_smd5_password; static fr_dict_attr_t const *attr_crypt_password; -static fr_dict_attr_t const *attr_sha_password; -static fr_dict_attr_t const *attr_ssha_password; + +static fr_dict_attr_t const *attr_sha1_password; +static fr_dict_attr_t const *attr_ssha1_password; static fr_dict_attr_t const *attr_sha2_password; +static fr_dict_attr_t const *attr_sha2_224_password; +static fr_dict_attr_t const *attr_sha2_256_password; +static fr_dict_attr_t const *attr_sha2_384_password; +static fr_dict_attr_t const *attr_sha2_512_password; + static fr_dict_attr_t const *attr_ssha2_224_password; static fr_dict_attr_t const *attr_ssha2_256_password; static fr_dict_attr_t const *attr_ssha2_384_password; static fr_dict_attr_t const *attr_ssha2_512_password; static fr_dict_attr_t const *attr_sha3_password; +static fr_dict_attr_t const *attr_sha3_224_password; +static fr_dict_attr_t const *attr_sha3_256_password; +static fr_dict_attr_t const *attr_sha3_384_password; +static fr_dict_attr_t const *attr_sha3_512_password; + static fr_dict_attr_t const *attr_ssha3_224_password; static fr_dict_attr_t const *attr_ssha3_256_password; static fr_dict_attr_t const *attr_ssha3_384_password; @@ -63,9 +109,12 @@ static fr_dict_attr_t const *attr_lm_password; static fr_dict_attr_t const *attr_nt_password; static fr_dict_attr_t const *attr_ns_mta_md5_password; +static fr_dict_attr_t const *attr_user_password; + extern fr_dict_autoload_t password_dict[]; fr_dict_autoload_t password_dict[] = { { .out = &dict_freeradius, .proto = "freeradius" }, + { .out = &dict_radius, .proto = "radius" }, { NULL } }; @@ -73,20 +122,31 @@ extern fr_dict_attr_autoload_t password_dict_attr[]; fr_dict_attr_autoload_t password_dict_attr[] = { { .out = &attr_cleartext_password, .name = "Cleartext-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, { .out = &attr_password_with_header, .name = "Password-With-Header", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, + { .out = &attr_password_root, .name = "Password-Root", .type = FR_TYPE_TLV, .dict = &dict_freeradius }, { .out = &attr_md5_password, .name = "MD5-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, { .out = &attr_smd5_password, .name = "SMD5-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, { .out = &attr_crypt_password, .name = "Crypt-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, - { .out = &attr_sha_password, .name = "SHA-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_ssha_password, .name = "SSHA-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_sha1_password, .name = "SHA1-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_ssha1_password, .name = "SSHA1-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, { .out = &attr_sha2_password, .name = "SHA2-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_sha2_224_password, .name = "SHA2-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_sha2_256_password, .name = "SHA2-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_sha2_384_password, .name = "SHA2-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_sha2_512_password, .name = "SHA2-512-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_ssha2_224_password, .name = "SSHA2-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, { .out = &attr_ssha2_256_password, .name = "SSHA2-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, { .out = &attr_ssha2_384_password, .name = "SSHA2-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, { .out = &attr_ssha2_512_password, .name = "SSHA2-512-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, { .out = &attr_sha3_password, .name = "SHA3-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_sha3_224_password, .name = "SHA3-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_sha3_256_password, .name = "SHA3-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_sha3_384_password, .name = "SHA3-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_sha3_512_password, .name = "SHA3-512-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, + { .out = &attr_ssha3_224_password, .name = "SSHA3-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, { .out = &attr_ssha3_256_password, .name = "SSHA3-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, { .out = &attr_ssha3_384_password, .name = "SSHA3-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, @@ -97,10 +157,11 @@ fr_dict_attr_autoload_t password_dict_attr[] = { { .out = &attr_nt_password, .name = "NT-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, { .out = &attr_ns_mta_md5_password, .name = "NS-MTA-MD5-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, + { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius }, + { NULL } }; - typedef enum { NORMALISED_NOTHING = 0, NORMALISED_B64, @@ -114,9 +175,228 @@ static fr_table_num_sorted_t const normalise_table[] = { }; static size_t normalise_table_len = NUM_ELEMENTS(normalise_table); +/* + * Headers for the Password-with-Header attribute + * + * @note Header comparison is case insensitive. + */ +static fr_table_num_sorted_t const password_header_table[] = { + { "{base64_md5}", FR_MD5_PASSWORD }, + { "{clear}", FR_CLEARTEXT_PASSWORD }, + { "{cleartext}", FR_CLEARTEXT_PASSWORD }, + { "{crypt}", FR_CRYPT_PASSWORD }, + { "{md4}", FR_NT_PASSWORD }, + { "{md5}", FR_MD5_PASSWORD }, + { "{ns-mta-md5}", FR_NS_MTA_MD5_PASSWORD }, + { "{nt}", FR_NT_PASSWORD }, + { "{nthash}", FR_NT_PASSWORD }, + +#ifdef HAVE_OPENSSL_EVP_H + { "{sha224}", FR_SHA2_PASSWORD }, + { "{sha256}", FR_SHA2_PASSWORD }, + { "{sha2}", FR_SHA2_PASSWORD }, + { "{sha384}", FR_SHA2_384_PASSWORD }, + { "{sha512}", FR_SHA2_512_PASSWORD }, +#endif + { "{sha}", FR_SHA1_PASSWORD }, + { "{smd5}", FR_SMD5_PASSWORD }, +#ifdef HAVE_OPENSSL_EVP_H + { "{ssha224}", FR_SSHA2_224_PASSWORD }, + { "{ssha256}", FR_SSHA2_256_PASSWORD }, +# if OPENSSL_VERSION_NUMBER >= 0x10101000L + { "{ssha3-224}", FR_SSHA3_224_PASSWORD }, + { "{ssha3-256}", FR_SSHA3_256_PASSWORD }, + { "{ssha3-384}", FR_SSHA3_384_PASSWORD }, + { "{ssha3-512}", FR_SSHA3_512_PASSWORD }, +# endif + { "{ssha384}", FR_SSHA2_384_PASSWORD }, + { "{ssha512}", FR_SSHA2_512_PASSWORD }, +#endif + { "{ssha}", FR_SSHA1_PASSWORD }, + { "{x- orcllmv}", FR_LM_PASSWORD }, + { "{x- orclntv}", FR_NT_PASSWORD }, + { "{x-nthash}", FR_NT_PASSWORD }, + { "{x-pbkdf2}", FR_PBKDF2_PASSWORD }, +}; +static size_t password_header_table_len = NUM_ELEMENTS(password_header_table); + +#ifdef HAVE_OPENSSL_EVP_H +static VALUE_PAIR *password_process_sha2(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good); +# if OPENSSL_VERSION_NUMBER >= 0x10101000L +static VALUE_PAIR *password_process_sha3(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good); +# endif +#endif +static VALUE_PAIR *password_process_header(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good); + +/** Metdata for various password attributes + * + */ +static password_info_t password_info[] = { + [FR_CLEARTEXT_PASSWORD] = { + .type = PASSWORD_CLEARTEXT, + .da = &attr_cleartext_password, + .no_normify = true + }, + [FR_CRYPT_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_crypt_password + }, + [FR_LM_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_lm_password, + .min_hash_len = MD4_DIGEST_LENGTH + }, + [FR_MD5_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_md5_password, + .min_hash_len = MD5_DIGEST_LENGTH + }, + [FR_NS_MTA_MD5_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_ns_mta_md5_password + }, + [FR_NT_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_nt_password, + .min_hash_len = MD4_DIGEST_LENGTH + }, + [FR_PASSWORD_WITH_HEADER] = { + .type = PASSWORD_HASH_VARIABLE, + .da = &attr_password_with_header, + .func = password_process_header + }, + [FR_PBKDF2_PASSWORD] = { + .type = PASSWORD_HASH_VARIABLE, + .da = &attr_pbkdf2_password + }, + [FR_SHA1_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_sha1_password, + .min_hash_len = SHA1_DIGEST_LENGTH + }, +#ifdef HAVE_OPENSSL_EVP_H + [FR_SHA2_PASSWORD] = { + .type = PASSWORD_HASH_VARIABLE, + .da = &attr_sha2_password, + .func = password_process_sha2, + .min_hash_len = SHA224_DIGEST_LENGTH, + .max_hash_len = SHA512_DIGEST_LENGTH + }, + [FR_SHA2_224_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_sha2_224_password, + .min_hash_len = SHA224_DIGEST_LENGTH, + }, + [FR_SHA2_256_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_sha2_256_password, + .min_hash_len = SHA256_DIGEST_LENGTH, + }, + [FR_SHA2_384_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_sha2_384_password, + .min_hash_len = SHA384_DIGEST_LENGTH, + }, + [FR_SHA2_512_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_sha2_512_password, + .min_hash_len = SHA512_DIGEST_LENGTH, + }, +# if OPENSSL_VERSION_NUMBER >= 0x10101000L + [FR_SHA3_PASSWORD] = { + .type = PASSWORD_HASH_VARIABLE, + .da = &attr_sha3_password, + .func = password_process_sha3, + .min_hash_len = SHA224_DIGEST_LENGTH, + }, + [FR_SHA3_224_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_sha3_224_password, + .min_hash_len = SHA224_DIGEST_LENGTH, + }, + [FR_SHA3_256_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_sha3_256_password, + .min_hash_len = SHA256_DIGEST_LENGTH, + }, + [FR_SHA3_384_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_sha3_384_password, + .min_hash_len = SHA384_DIGEST_LENGTH, + }, + [FR_SHA3_512_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_sha3_512_password, + .min_hash_len = SHA512_DIGEST_LENGTH + }, +# endif +#endif + [FR_SMD5_PASSWORD] = { + .type = PASSWORD_HASH, + .da = &attr_smd5_password, + .min_hash_len = MD5_DIGEST_LENGTH + }, + [FR_SSHA1_PASSWORD] = { + .type = PASSWORD_HASH_SALTED, + .da = &attr_ssha1_password, + .min_hash_len = SHA1_DIGEST_LENGTH + }, +#ifdef HAVE_OPENSSL_EVP_H + [FR_SSHA2_224_PASSWORD] = { + .type = PASSWORD_HASH_SALTED, + .da = &attr_ssha2_224_password, + .min_hash_len = SHA224_DIGEST_LENGTH + }, + [FR_SSHA2_256_PASSWORD] = { + .type = PASSWORD_HASH_SALTED, + .da = &attr_ssha2_256_password, + .min_hash_len = SHA256_DIGEST_LENGTH + }, + [FR_SSHA2_384_PASSWORD] = { + .type = PASSWORD_HASH_SALTED, + .da = &attr_ssha2_384_password, + .min_hash_len = SHA384_DIGEST_LENGTH + }, + [FR_SSHA2_512_PASSWORD] = { + .type = PASSWORD_HASH_SALTED, + .da = &attr_ssha2_512_password, + .min_hash_len = SHA512_DIGEST_LENGTH + }, +# if OPENSSL_VERSION_NUMBER >= 0x10101000L + [FR_SSHA3_224_PASSWORD] = { + .type = PASSWORD_HASH_SALTED, + .da = &attr_ssha3_224_password, + .min_hash_len = SHA224_DIGEST_LENGTH, + }, + [FR_SSHA3_256_PASSWORD] = { + .type = PASSWORD_HASH_SALTED, + .da = &attr_ssha3_256_password, + .min_hash_len = SHA256_DIGEST_LENGTH + }, + [FR_SSHA3_384_PASSWORD] = { + .type = PASSWORD_HASH_SALTED, + .da = &attr_ssha3_384_password, + .min_hash_len = SHA384_DIGEST_LENGTH + }, + [FR_SSHA3_512_PASSWORD] = { + .type = PASSWORD_HASH_SALTED, + .da = &attr_ssha3_512_password, + .min_hash_len = SHA512_DIGEST_LENGTH + } +# endif +#endif +}; + +#define MIN_LEN(_info) (info->type == PASSWORD_HASH_SALTED ? (info->min_hash_len + 1) : info->min_hash_len) + static ssize_t normify(normalise_t *action, uint8_t *buffer, size_t bufflen, char const *known_good, size_t len, size_t min_len) { + /* + * Else unknown encoding, or already binary. Leave it. + */ + if (action) *action = NORMALISED_NOTHING; + if (min_len >= bufflen) return 0; /* paranoia */ /* @@ -148,10 +428,6 @@ static ssize_t normify(normalise_t *action, uint8_t *buffer, size_t bufflen, } } - /* - * Else unknown encoding, or already binary. Leave it. - */ - if (action) *action = NORMALISED_NOTHING; return 0; } @@ -170,18 +446,23 @@ static ssize_t normify(normalise_t *action, uint8_t *buffer, size_t bufflen, * @param[in] ctx to allocate new pairs in. * @param[in] request The current request. * @param[in] known_good password to normify. - * @param[in] min_len we expect the decoded version to be. * @return - * - NULL if known_good was already normalised. + * - NULL if known_good was already normalised, or couldn't be normalised. * - A new normalised password pair. */ -VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const *known_good, size_t min_len) +static VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const *known_good) { uint8_t buffer[256]; ssize_t decoded; VALUE_PAIR *out; - normalise_t normalised = NORMALISED_NOTHING; + normalise_t normalised; + password_info_t *info; + size_t min_len; + + if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return NULL; + info = &password_info[known_good->da->attr]; + min_len = MIN_LEN(info); if (min_len >= sizeof(buffer)) return NULL; /* paranoia */ switch (known_good->da->type) { @@ -205,6 +486,7 @@ VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const known_good->vp_length, decoded); MEM(out = fr_pair_afrom_da(ctx, known_good->da)); fr_pair_value_memcpy(out, buffer, decoded, known_good->vp_tainted); + return out; } /* @@ -213,38 +495,127 @@ VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const return NULL; } +#ifdef HAVE_OPENSSL_EVP_H +/** Split SHA2 hashes into separate attributes based on their length + * + * @param[in] ctx to allocate attributes in. + * @param[in] request The current request. + * @param[in] known_good attribute to split. + * @return + * - A SHA2 length specific attribute. + * - NULL on error. + */ +static VALUE_PAIR *password_process_sha2(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good) +{ + VALUE_PAIR *out, *normalised; + + switch (known_good->vp_length) { + case SHA224_DIGEST_LENGTH: + out = fr_pair_afrom_da(ctx, attr_sha2_224_password); + fr_pair_value_copy(out, known_good); + return out; + + case SHA256_DIGEST_LENGTH: + out = fr_pair_afrom_da(ctx, attr_sha2_256_password); + fr_pair_value_copy(out, known_good); + return out; + + case SHA384_DIGEST_LENGTH: + out = fr_pair_afrom_da(ctx, attr_sha2_384_password); + fr_pair_value_copy(out, known_good); + return out; + + case SHA512_DIGEST_LENGTH: + out = fr_pair_afrom_da(ctx, attr_sha2_512_password); + fr_pair_value_copy(out, known_good); + return out; + + default: + out = password_normify(ctx, request, known_good); + if (!out) return NULL; + + normalised = password_process_sha2(ctx, request, out); + talloc_list_free(&out); + + return normalised; + } +} + +# if OPENSSL_VERSION_NUMBER >= 0x10101000L +/** Split SHA3 hashes into separate attributes based on their length + * + * @param[in] ctx to allocate attributes in. + * @param[in] request The current request. + * @param[in] known_good attribute to split. + * @return + * - A SHA3 length specific attribute. + * - NULL on error. + */ +static VALUE_PAIR *password_process_sha3(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good) +{ + VALUE_PAIR *out, *normalised; + + switch (known_good->vp_length) { + case SHA224_DIGEST_LENGTH: + out = fr_pair_afrom_da(ctx, attr_sha3_224_password); + fr_pair_value_copy(out, known_good); + return out; + + case SHA256_DIGEST_LENGTH: + out = fr_pair_afrom_da(ctx, attr_sha3_256_password); + fr_pair_value_copy(out, known_good); + return out; + + case SHA384_DIGEST_LENGTH: + out = fr_pair_afrom_da(ctx, attr_sha3_384_password); + fr_pair_value_copy(out, known_good); + return out; + + case SHA512_DIGEST_LENGTH: + out = fr_pair_afrom_da(ctx, attr_sha3_512_password); + fr_pair_value_copy(out, known_good); + return out; + + default: + out = password_normify(ctx, request, known_good); + if (!out) return NULL; + + normalised = password_process_sha3(ctx, request, out); + talloc_list_free(out); + + return normalised; + } +} +# endif +#endif + /** Convert a Password-With-Header attribute to the correct type * * Attribute may be base64 encoded, in which case it will be decoded * first, then evaluated. * - * @note The buffer for octets type attributes is extended by one byte + * @note The buffer for octets types\ attributes is extended by one byte * and '\0' terminated, to allow it to be used as a char buff. * * @param[in] ctx to allocate new pairs in. * @param[in] request Current request. * @param[in] known_good Password-With-Header attribute to convert. - * @param[in] func to convert header strings to fr_dict_attr_t. - * @param[in] def Default attribute to copy value to if we - * don't recognise the header. * @return - * - New #VALUE_PAIR on success. + * - Buffer containing normified value on success. * - NULL on error. */ -VALUE_PAIR *password_normify_with_header(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good, - password_header_lookup_t func, fr_dict_attr_t const *def) +static VALUE_PAIR *password_process_header(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good) { - char const *p, *q, *end; + char const *p, *q, *end; - uint8_t n1[256], n2[256]; - ssize_t decoded; + uint8_t n1[256], n2[256]; + ssize_t decoded; - char header[128]; - normalise_t normalised; + char header[128]; + normalise_t normalised; - int i; - - VALUE_PAIR *new; + VALUE_PAIR *new; + fr_dict_attr_t const *def = attr_cleartext_password; VP_VERIFY(known_good); @@ -258,315 +629,323 @@ VALUE_PAIR *password_normify_with_header(TALLOC_CTX *ctx, REQUEST *request, VALU end = p + known_good->vp_length; /* - * Only allow one additional level of - * normification and header parsing. + * Has a header {...} prefix */ - for (i = 0; i <= 1; i++) { - /* - * Has a header {...} prefix - */ - if ((*p == '{') && (q = memchr(p, '}', end - p))) { - size_t hlen; - fr_dict_attr_t const *da; - ssize_t slen; - - hlen = (q - p) + 1; - if (hlen >= sizeof(header)) { - REDEBUG("Password header too long. Got %zu bytes must be less than %zu bytes", - hlen, sizeof(header)); - return NULL; - } - - memcpy(header, p, hlen); - header[hlen] = '\0'; - - slen = func(&da, header); - if (slen <= 0) { - /* - * header buffer retains { and } - */ - if (RDEBUG_ENABLED3) { - RDEBUG3("Unknown header %s in %pP, re-writing to %s", - header, known_good, def->name); - } else { - RDEBUG2("Unknown header %s in %s, re-writing to %s", - header, known_good->da->name, def->name); - } - goto unknown_header; - } +do_header: + if ((*p == '{') && (q = memchr(p, '}', end - p))) { + size_t hlen; + int attr; + password_info_t *info; + + hlen = (q - p) + 1; + if (hlen >= sizeof(header)) { + REDEBUG("Password header too long. Got %zu bytes must be less than %zu bytes", + hlen, sizeof(header)); + return NULL; + } - p = q + 1; + memcpy(header, p, hlen); + header[hlen] = '\0'; + attr = fr_table_value_by_substr(password_header_table, header, hlen, -1); + if (attr < 0) { /* - * Try and base64 decode, and if we can - * use the decoded value. - * - * FIXME: Should pass in min length for - * password hash da represents. + * header buffer retains { and } */ - decoded = normify(&normalised, n1, sizeof(n1), p, end - p, 1); - if (decoded > 0) { - RDEBUG2("Normalizing %s %s encoding, %zu bytes -> %zu bytes", - da->name, fr_table_str_by_value(normalise_table, normalised, 0), - (end - p), decoded); - p = (char const *)n1; - end = p + decoded; + if (RDEBUG_ENABLED3) { + RDEBUG3("Unknown header %s in %pP, re-writing to %s", + header, known_good, def->name); + } else { + RDEBUG2("Unknown header %s in %s, re-writing to %s", + header, known_good->da->name, def->name); } + p = q + 1; + goto bad_header; + } - new = fr_pair_afrom_da(ctx, da); - switch (da->type) { - case FR_TYPE_OCTETS: - fr_pair_value_memcpy(new, (uint8_t const *)p, end - p, true); - break; + p = q + 1; - case FR_TYPE_STRING: - fr_pair_value_bstrncpy(new, (uint8_t const *)p, end - p); - break; + if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return NULL; + info = &password_info[attr]; - default: - if (!fr_cond_assert(0)) return NULL; - } - return new; - } + new = fr_pair_afrom_da(ctx, *(info->da)); + switch ((*(info->da))->type) { + case FR_TYPE_OCTETS: + fr_pair_value_memcpy(new, (uint8_t const *)p, end - p, true); + break; - /* - * Doesn't have a header {...} prefix - * - * See if it's base64 or hex, if it is, decode it and check again! - */ - decoded = normify(&normalised, n1, sizeof(n1), p, end - p, 1); - if (decoded > 0) { - if ((n1[0] == '{') && (memchr(n1, '}', decoded) != NULL)) { - RDEBUG2("Normalizing %s %s encoding, %zu bytes -> %zu bytes", - known_good->da->name, fr_table_str_by_value(normalise_table, normalised, 0), - known_good->vp_length, decoded); + case FR_TYPE_STRING: + fr_pair_value_bstrncpy(new, (uint8_t const *)p, end - p); + break; - /* - * Password-With-Header is a string attribute. - * Even though we're handling binary data, the header - * must be \0 terminated. - */ - memcpy(n2, n1, decoded); - p = (char const *)n2; - end = p + decoded; - continue; - } + default: + if (!fr_cond_assert(0)) return NULL; } + return new; + } - break; + /* + * Doesn't have a header {...} prefix + * + * See if it's base64 or hex, if it is, decode it and check again! + * + * We ignore request not to normify, as curly braces aren't + * in either of the character sets for the encoding schemes + * we're normifying, so there's not the possibility for error + * as there is normifying other password hashes. + */ + decoded = normify(&normalised, n1, sizeof(n1), p, end - p, 4); /* { + + } + */ + if (decoded > 0) { + if ((n1[0] == '{') && (memchr(n1, '}', decoded) != NULL)) { + RDEBUG2("Normalizing %s %s encoding, %zu bytes -> %zu bytes", + known_good->da->name, fr_table_str_by_value(normalise_table, normalised, 0), + known_good->vp_length, decoded); + + /* + * Password-With-Header is a string attribute. + * Even though we're handling binary data, the header + * must be \0 terminated. + */ + memcpy(n2, n1, decoded); + p = (char const *)n2; + end = p + decoded; + goto do_header; + } } + /* + * Rewrite to the default attribute type + * currently Cleartext-Password. + * + * This is usually correct if there's no + * header to indicate hash type. + */ if (RDEBUG_ENABLED3) { - RDEBUG3("No {...} in &%pP, re-writing to %s", known_good, def->name); + RDEBUG3("No {...} in &control:%pP, re-writing to %s", known_good, def->name); } else { - RDEBUG2("No {...} in &%s, re-writing to %s", known_good->da->name, def->name); + RDEBUG2("No {...} in &control:%s, re-writing to %s", known_good->da->name, def->name); } -unknown_header: +bad_header: new = fr_pair_afrom_da(request, def); fr_pair_value_bstrncpy(new, p, end - p); return new; } - -/* - * For auto-header discovery. - * - * @note Header comparison is case insensitive. - * - * We don't put the *value* of "attr_foo" here, as those - * values are loaded at run time. Instead, we point to - * the attr_foo definition, which is then a static pointer - * to a known variable. - */ -static fr_table_ptr_ordered_t const header_names[] = { - { "X- orclntv}", &attr_nt_password }, - { "{base64_md5}", &attr_md5_password }, - { "{cleartext}", &attr_cleartext_password }, - { "{clear}", &attr_cleartext_password }, - { "{crypt}", &attr_crypt_password }, - { "{md4}", &attr_nt_password }, - { "{md5}", &attr_md5_password }, - { "{ns-mta-md5}", &attr_ns_mta_md5_password }, - { "{nthash}", &attr_nt_password }, - { "{nt}", &attr_nt_password }, -#ifdef HAVE_OPENSSL_EVP_H - { "{sha224}", &attr_sha2_password }, - { "{sha256}", &attr_sha2_password }, - { "{sha2}", &attr_sha2_password }, - { "{sha384}", &attr_sha2_password }, - { "{sha512}", &attr_sha2_password }, -#endif - { "{sha}", &attr_sha_password }, - { "{smd5}", &attr_smd5_password }, -#ifdef HAVE_OPENSSL_EVP_H - { "{ssha224}", &attr_ssha2_224_password }, - { "{ssha256}", &attr_ssha2_256_password }, -# if OPENSSL_VERSION_NUMBER >= 0x10101000L - { "{ssha3-224}", &attr_ssha3_224_password }, - { "{ssha3-256}", &attr_ssha3_256_password }, - { "{ssha3-384}", &attr_ssha3_384_password }, - { "{ssha3-512}", &attr_ssha3_512_password }, -# endif - { "{ssha384}", &attr_ssha2_384_password }, - { "{ssha512}", &attr_ssha2_512_password }, -#endif - { "{ssha}", &attr_ssha_password }, - { "{x- orcllmv}", &attr_lm_password }, - { "{x-nthash}", &attr_nt_password }, - { "{x-pbkdf2}", &attr_pbkdf2_password }, -}; -static size_t header_names_len = NUM_ELEMENTS(header_names); - - -static ssize_t known_password_header(fr_dict_attr_t const **out, char const *header) -{ - fr_dict_attr_t const **da; - - da = fr_table_value_by_str(header_names, header, NULL); - if (!da || !*da) return -1; - - *out = *da; - return strlen(header); -} - -static const size_t fr_password_length[] = { - [FR_LM_PASSWORD - 5000 ] = 16, - [FR_MD5_PASSWORD - 5000 ] = 16, - [FR_MS_CHAP_PASSWORD - 5000 ] = 0, /* used only by radclient */ - [FR_NS_MTA_MD5_PASSWORD - 5000 ] = 0, /* not used at all by anyone/// */ - [FR_NT_PASSWORD - 5000 ] = 16, - [FR_PBKDF2_PASSWORD - 5000 ] = 0, /* already normalized */ - [FR_SHA_PASSWORD - 5000 ] = 20, - [FR_SHA1_PASSWORD - 5000 ] = 20, - [FR_SHA2_PASSWORD - 5000 ] = 28, - [FR_SHA3_PASSWORD - 5000 ] = 28, - [FR_SMD5_PASSWORD - 5000 ] = 16, - [FR_SSHA_PASSWORD - 5000 ] = 20, - [FR_SSHA1_PASSWORD - 5000 ] = 20, - [FR_SSHA2_224_PASSWORD - 5000 ] = 28, - [FR_SSHA2_256_PASSWORD - 5000 ] = 32, - [FR_SSHA2_384_PASSWORD - 5000 ] = 48, - [FR_SSHA2_512_PASSWORD - 5000 ] = 64, - [FR_SSHA3_224_PASSWORD - 5000 ] = 28, - [FR_SSHA3_256_PASSWORD - 5000 ] = 32, - [FR_SSHA3_384_PASSWORD - 5000 ] = 48, - [FR_SSHA3_512_PASSWORD - 5000 ] = 64, -}; -#define MAX_KNOWN_PASSWORD (sizeof(fr_password_length) / sizeof(fr_password_length[0])) - - -/** Normalise passwords. - * - * @param request the request to process - * @param normalise whether or not we normalise the passwords +/** Apply any processing and normification * */ -VALUE_PAIR *password_normalise(REQUEST *request, bool normalise) +static VALUE_PAIR *password_process(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good, bool normify) { - VALUE_PAIR *vp, *found_pw = NULL; - fr_cursor_t cursor; - fr_dict_attr_t const *root; - - root = fr_dict_root(dict_freeradius); - if (!root) return NULL; + password_info_t *info; + VALUE_PAIR *out; - for (vp = fr_cursor_init(&cursor, &request->control); - vp; - vp = fr_cursor_next(&cursor)) { - VP_VERIFY(vp); - VALUE_PAIR *new; + info = &password_info[known_good->da->attr]; + if (info->func) { + VALUE_PAIR *from_func, *from_recurse; - next: /* - * Look only for out attributes. Note that we no - * longer complain about User-Password being in - * the control list. That functionality has been - * deprecated for 10 years. If the admins still - * do it, too bad. + * Pass our input attribute to a custom preprocessing + * function to manipulate it. */ - if (vp->da->parent != root) continue; + from_func = info->func(ctx, request, known_good); + if (!from_func) return NULL; - if ((vp->da->attr < 5000) || (vp->da->attr >= (5000 + MAX_KNOWN_PASSWORD))) continue; + /* + * Processing function may have produced a different + * password type, recurse to deal with it... + */ + from_recurse = password_process(ctx, request, from_func, normify); /* - * Remove the header, and convert it to something sane. + * Cleanup any intermediary password attributes created + * from running the different normalisation and parsing + * operations. */ - if (vp->da->attr == FR_PASSWORD_WITH_HEADER) { - /* - * Password already exists: use that instead of this one. - */ - if (fr_pair_find_by_da(request->control, attr_cleartext_password, TAG_ANY)) { - RWDEBUG("Config already contains a \"known good\" password " - "(&control:%s). Ignoring &control:%s", - attr_cleartext_password->name, vp->da->name); - break; - } + if (!from_recurse) { + if (from_func != known_good) talloc_list_free(&from_func); + return NULL; + } + if ((from_func != known_good) && (from_recurse != from_func)) talloc_list_free(&from_func); - MEM(new = password_normify_with_header(request, request, vp, - known_password_header, - attr_cleartext_password)); - if (RDEBUG_ENABLED3) { - RDEBUG3("Normalized &control:%pP -> &control:%pP", vp, new); - } else { - RDEBUG2("Normalized &control:%s -> &control:%s", vp->da->name, new->da->name); - } + return from_recurse; + } - RDEBUG2("Removing &control:%s", vp->da->name); - fr_cursor_free_item(&cursor); /* advances the cursor for us */ - fr_cursor_append(&cursor, new); /* inserts at the end of the list */ + /* + * Only normify if we're told to, and we have more data + * than the minimum length. + */ + if (normify && !info->no_normify && (known_good->vp_length > info->min_hash_len)) { + VALUE_PAIR *from_normify; - found_pw = new; + from_normify = password_normify(ctx, request, known_good); + out = from_normify ? from_normify : known_good; + } else { + out = known_good; + } - vp = fr_cursor_current(&cursor); - if (vp) goto next; - break; + /* + * Sanity checks - Too short + */ + if (info->min_hash_len && (out->vp_length < MIN_LEN(info))) { + if (RDEBUG_ENABLED3) { + RWDEBUG3("&control:%pP too short, expected %zu bytes, got %zu bytes", + out, MIN_LEN(info), out->vp_length); + } else { + RWDEBUG2("&control:%s too short, expected %zu bytes, got %zu bytes", + out->da->name, MIN_LEN(info), out->vp_length); } + invalid: + if (out != known_good) talloc_list_free(&out); /* Free attribute we won't be returning */ + return NULL; + } - found_pw = vp; + /* + * Sanity checks - Too long + */ + if (info->max_hash_len && (out->vp_length > info->max_hash_len)) { + if (RDEBUG_ENABLED3) { + RWDEBUG3("&control:%pP too long, expected %zu bytes, got %zu bytes", + out, info->max_hash_len, out->vp_length); + } else { + RWDEBUG2("&control:%s too long, expected %zu bytes, got %zu bytes", + out->da->name, info->max_hash_len, out->vp_length); + } + goto invalid; + } - /* - * Don't normalise Cleartext Passwords - */ - if (vp->da->attr == FR_CLEARTEXT_PASSWORD) break; + /* + * Sanity checks - Hashes are a fixed length + */ + if ((info->type == PASSWORD_HASH) && (out->vp_length != info->min_hash_len)) { - if (!normalise) break; + if (RDEBUG_ENABLED3) { + RWDEBUG3("&control:%pP incorrect length, expected %zu bytes, got %zu bytes", + out, info->min_hash_len, out->vp_length); + } else { + RWDEBUG2("&control:%s incorrect length, expected %zu bytes, got %zu bytes", + out->da->name, info->min_hash_len, out->vp_length); + } + goto invalid; + } - if (!fr_password_length[vp->da->attr - 5000]) break; + return out; +} - new = password_normify(request, request, vp, fr_password_length[vp->da->attr - 5000]); - if (new) { - fr_cursor_free_item(&cursor); /* free the old pasword */ - fr_cursor_append(&cursor, new); /* inserts at the end of the list */ +/** Find all password attributes in the control list of a request and normalise them + * + * @param[in] request The current request. + * @param[in] normify Apply hex/base64 normalisation to attributes. + * @return the number of attributes normalised. + */ +int password_normalise_and_replace(REQUEST *request, bool normify) +{ + fr_cursor_t cursor; + int replaced = 0; + VALUE_PAIR *known_good, *new; - vp = fr_cursor_current(&cursor); - if (vp) goto next; - break; - } + for (known_good = fr_cursor_iter_by_ancestor_init(&cursor, &request->control, attr_password_root); + known_good; + known_good = fr_cursor_next(&cursor)) { + if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return -1; /* - * Can't normalise this one, but there might be - * another one that we can normalise. + * Apply preprocessing steps and normalisation. */ - } + new = password_process(request, request, known_good, normify); + if (!new) break; /* Process next input attribute */ - /* - * Print helpful warnings if there was no password. - */ - if (found_pw) { if (RDEBUG_ENABLED3) { - RDEBUG("Found \"known good\" password in &control:%s = \"%pV\"", found_pw->da->name, &found_pw->data); + RDEBUG3("Replacing &control:%pP with &control:%pP", + known_good, new); + } else { - RDEBUG("Found \"known good\" password in &control:%s", found_pw->da->name); + RDEBUG2("Replacing &control:%s with &control:%s", + known_good->da->name, new->da->name); } + fr_cursor_free_item(&cursor); + fr_cursor_prepend(&cursor, new); + replaced++; + } - } else { - RWDEBUG("No \"known good\" password found for the user."); - RWDEBUG("Authentication may fail unless a \"known good\" password is available"); + return replaced; +} + +/** Find a "known good" password in the control list of a request + * + * Searches for a "known good" password attribute, and applies any processing + * and normification operations to it, returning a new mormalised VALUE_PAIR. + * + * The ctx passed in should be freed when the caller is done with the returned + * VALUE_PAIR. The returned pair *MUST NOT BE FREED*, it may be an attribute + * in the request->control list. + * + * @param[in] ctx Ephemeral ctx to allocate new attributes in. + * @param[in] request The current request. + * @param[in] allowed_attrs Optional list of allowed attributes. + * @param[in] allowed_attrs_len Length of allowed attributes list. + * @param[in] normify Apply hex/base64 normalisation to attributes. + * @return + * - A VALUE_PAIR containing a "known good" password. + * - NULL on error, or if no usable password attributes were found. + */ +VALUE_PAIR const *password_find(TALLOC_CTX *ctx, REQUEST *request, + fr_dict_attr_t const *allowed_attrs[], size_t allowed_attrs_len, bool normify) +{ + fr_cursor_t cursor; + size_t i, j; + VALUE_PAIR *known_good; + + for (known_good = fr_cursor_iter_by_ancestor_init(&cursor, &request->control, attr_password_root); + known_good; + known_good = fr_cursor_next(&cursor)) { + if (known_good->da == attr_user_password) { + RWDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RWDEBUG("!!! Ignoring control:User-Password. Update your !!!"); + RWDEBUG("!!! configuration so that the \"known good\" clear text !!!"); + RWDEBUG("!!! password is in Cleartext-Password and NOT in !!!"); + RWDEBUG("!!! User-Password. !!!"); + RWDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + continue; + } + + for (i = 0; i < allowed_attrs_len; i++) { + VALUE_PAIR *new; + + if (allowed_attrs[i] != known_good->da) continue; + + if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return NULL; + + /* + * Apply preprocessing steps and normalisation. + */ + new = password_process(ctx, request, known_good, normify); + if (!new) break; /* Process next input attribute */ + + /* + * If new != known_good, then we need + * to check what was produced is still + * acceptable. + */ + if (new->da != known_good->da) { + for (j = 0; j < allowed_attrs_len; j++) if (allowed_attrs[j] == new->da) return new; + + /* + * New attribute not in our allowed list + */ + talloc_list_free(&new); /* da didn't match, treat as ephemeral */ + break; /* Process next input attribute */ + } + + /* + * Return attribute for processing + */ + return new; + } } - return found_pw; + return NULL; } /** Load our dictionaries diff --git a/src/lib/server/password.h b/src/lib/server/password.h index f9a24b5f4cd..5b58e205036 100644 --- a/src/lib/server/password.h +++ b/src/lib/server/password.h @@ -30,17 +30,15 @@ extern "C" { #include -typedef ssize_t(*password_header_lookup_t)(fr_dict_attr_t const **out, char const *header); +int password_normalise_and_replace(REQUEST *request, bool normify); -VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const *known_good, size_t min_len); +VALUE_PAIR const *password_find(TALLOC_CTX *ctx, REQUEST *request, + fr_dict_attr_t const *allowed_attrs[], + size_t allowed_attrs_len, bool normify); -VALUE_PAIR *password_normify_with_header(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good, - password_header_lookup_t func, fr_dict_attr_t const *def); +int password_init(void); -VALUE_PAIR *password_normalise(REQUEST *request, bool normalise); - -int password_init(void); -void password_free(void); +void password_free(void); #ifdef __cplusplus } diff --git a/src/modules/rlm_chap/rlm_chap.c b/src/modules/rlm_chap/rlm_chap.c index d16ffc669fc..39a5e71abb0 100644 --- a/src/modules/rlm_chap/rlm_chap.c +++ b/src/modules/rlm_chap/rlm_chap.c @@ -29,6 +29,7 @@ RCSID("$Id$") typedef struct { char const *name; //!< Auth-Type value for this module instance. + bool normify; fr_dict_enum_t *auth_type; } rlm_chap_t; @@ -44,6 +45,7 @@ fr_dict_autoload_t rlm_chap_dict[] = { static fr_dict_attr_t const *attr_auth_type; static fr_dict_attr_t const *attr_cleartext_password; +static fr_dict_attr_t const *attr_password_with_header; static fr_dict_attr_t const *attr_chap_password; static fr_dict_attr_t const *attr_chap_challenge; @@ -53,6 +55,7 @@ extern fr_dict_attr_autoload_t rlm_chap_dict_attr[]; fr_dict_attr_autoload_t rlm_chap_dict_attr[] = { { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius }, { .out = &attr_cleartext_password, .name = "Cleartext-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, + { .out = &attr_password_with_header, .name = "Password-With-Header", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, { .out = &attr_chap_password, .name = "Chap-Password", .type = FR_TYPE_OCTETS, .dict = &dict_radius }, { .out = &attr_chap_challenge, .name = "Chap-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_radius }, @@ -60,6 +63,11 @@ fr_dict_attr_autoload_t rlm_chap_dict_attr[] = { { NULL } }; +static const CONF_PARSER module_config[] = { + { FR_CONF_OFFSET("normalise", FR_TYPE_BOOL, rlm_chap_t, normify), .dflt = "yes" }, + CONF_PARSER_TERMINATOR +}; + static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_chap_t *inst = instance; @@ -104,8 +112,12 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *t */ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, UNUSED void *thread, REQUEST *request) { - VALUE_PAIR *known_good, *chap, *username; - uint8_t pass_str[FR_MAX_STRING_LEN]; + rlm_chap_t *inst = instance; + VALUE_PAIR const *known_good; + VALUE_PAIR *chap, *username; + uint8_t pass_str[FR_MAX_STRING_LEN]; + TALLOC_CTX *tmp_ctx; + fr_dict_attr_t const *allowed_passwords[] = { attr_password_with_header, attr_cleartext_password }; username = fr_pair_find_by_da(request->packet->vps, attr_user_name, TAG_ANY); if (!username) { @@ -131,18 +143,24 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, UNUS } /* - * Normalise passwords, if it hasn't already been done. - * - * This function returns the "known good" password, and - * prefers to return Cleartext-Password over everything - * else. + * Holds any ephemeral attributes + */ + MEM(tmp_ctx = talloc_named_const(request, 0, "password_tmp_ctx")); + + /* + * Retrieve the normalised version of + * the known_good password, without + * mangling the current password attributes + * in the request. */ - known_good = password_normalise(request, true); - if (!known_good || (known_good->da != attr_cleartext_password)) { - REDEBUG("&control:Cleartext-Password is required for authentication"); + known_good = password_find(tmp_ctx, request, + allowed_passwords, NUM_ELEMENTS(allowed_passwords), + inst->normify); + if (!known_good) { + REDEBUG("No \"known good\" password found for user"); + talloc_free(tmp_ctx); return RLM_MODULE_FAIL; } - fr_radius_encode_chap_password(pass_str, request->packet, chap->vp_octets[0], known_good); if (RDEBUG_ENABLED3) { @@ -174,11 +192,14 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, UNUS if (fr_digest_cmp(pass_str + 1, chap->vp_octets + 1, RADIUS_CHAP_CHALLENGE_LENGTH) != 0) { REDEBUG("Password comparison failed: password is incorrect"); + talloc_free(tmp_ctx); return RLM_MODULE_REJECT; } RDEBUG2("CHAP user \"%pV\" authenticated successfully", &username->data); + talloc_free(tmp_ctx); + return RLM_MODULE_OK; } @@ -213,6 +234,7 @@ module_t rlm_chap = { .magic = RLM_MODULE_INIT, .name = "chap", .inst_size = sizeof(rlm_chap_t), + .config = module_config, .bootstrap = mod_bootstrap, .dict = &dict_radius, .methods = { diff --git a/src/modules/rlm_pap/rlm_pap.c b/src/modules/rlm_pap/rlm_pap.c index 1bd237a3aff..052057ba33f 100644 --- a/src/modules/rlm_pap/rlm_pap.c +++ b/src/modules/rlm_pap/rlm_pap.c @@ -57,6 +57,8 @@ typedef struct { bool normify; } rlm_pap_t; +typedef rlm_rcode_t (*pap_auth_func_t)(rlm_pap_t const *, REQUEST *, VALUE_PAIR const *, VALUE_PAIR const *); + static const CONF_PARSER module_config[] = { { FR_CONF_OFFSET("normalise", FR_TYPE_BOOL, rlm_pap_t, normify), .dflt = "yes" }, CONF_PARSER_TERMINATOR @@ -65,72 +67,20 @@ static const CONF_PARSER module_config[] = { static fr_dict_t *dict_freeradius; static fr_dict_t *dict_radius; -extern fr_dict_autoload_t rlm_pap_dict[]; -fr_dict_autoload_t rlm_pap_dict[] = { +static fr_dict_autoload_t rlm_pap_dict[] = { { .out = &dict_freeradius, .proto = "freeradius" }, { .out = &dict_radius, .proto = "radius" }, { NULL } }; static fr_dict_attr_t const *attr_auth_type; - -static fr_dict_attr_t const *attr_cleartext_password; -static fr_dict_attr_t const *attr_password_with_header; - -static fr_dict_attr_t const *attr_md5_password; -static fr_dict_attr_t const *attr_smd5_password; -static fr_dict_attr_t const *attr_crypt_password; -static fr_dict_attr_t const *attr_sha_password; -static fr_dict_attr_t const *attr_ssha_password; - -static fr_dict_attr_t const *attr_sha2_password; -static fr_dict_attr_t const *attr_ssha2_224_password; -static fr_dict_attr_t const *attr_ssha2_256_password; -static fr_dict_attr_t const *attr_ssha2_384_password; -static fr_dict_attr_t const *attr_ssha2_512_password; - -static fr_dict_attr_t const *attr_sha3_password; -static fr_dict_attr_t const *attr_ssha3_224_password; -static fr_dict_attr_t const *attr_ssha3_256_password; -static fr_dict_attr_t const *attr_ssha3_384_password; -static fr_dict_attr_t const *attr_ssha3_512_password; - -static fr_dict_attr_t const *attr_pbkdf2_password; -static fr_dict_attr_t const *attr_lm_password; -static fr_dict_attr_t const *attr_nt_password; -static fr_dict_attr_t const *attr_ns_mta_md5_password; +static fr_dict_attr_t const *attr_password_root; static fr_dict_attr_t const *attr_user_password; -extern fr_dict_attr_autoload_t rlm_pap_dict_attr[]; -fr_dict_attr_autoload_t rlm_pap_dict_attr[] = { +static fr_dict_attr_autoload_t rlm_pap_dict_attr[] = { { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius }, - - { .out = &attr_cleartext_password, .name = "Cleartext-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, - { .out = &attr_password_with_header, .name = "Password-With-Header", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, - - { .out = &attr_md5_password, .name = "MD5-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_smd5_password, .name = "SMD5-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_crypt_password, .name = "Crypt-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, - { .out = &attr_sha_password, .name = "SHA-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_ssha_password, .name = "SSHA-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - - { .out = &attr_sha2_password, .name = "SHA2-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_ssha2_224_password, .name = "SSHA2-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_ssha2_256_password, .name = "SSHA2-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_ssha2_384_password, .name = "SSHA2-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_ssha2_512_password, .name = "SSHA2-512-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - - { .out = &attr_sha3_password, .name = "SHA3-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_ssha3_224_password, .name = "SSHA3-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_ssha3_256_password, .name = "SSHA3-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_ssha3_384_password, .name = "SSHA3-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_ssha3_512_password, .name = "SSHA3-512-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - - { .out = &attr_pbkdf2_password, .name = "PBKDF2-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_lm_password, .name = "LM-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_nt_password, .name = "NT-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, - { .out = &attr_ns_mta_md5_password, .name = "NS-MTA-MD5-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, + { .out = &attr_password_root, .name = "Password-Root", .type = FR_TYPE_TLV, .dict = &dict_freeradius }, { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius }, @@ -139,7 +89,7 @@ fr_dict_attr_autoload_t rlm_pap_dict_attr[] = { #ifdef HAVE_OPENSSL_EVP_H static fr_table_num_sorted_t const pbkdf2_crypt_names[] = { - { "HMACSHA1", FR_SSHA_PASSWORD }, + { "HMACSHA1", FR_SSHA1_PASSWORD }, { "HMACSHA2+224", FR_SSHA2_224_PASSWORD }, { "HMACSHA2+256", FR_SSHA2_256_PASSWORD }, { "HMACSHA2+384", FR_SSHA2_384_PASSWORD }, @@ -154,13 +104,15 @@ static fr_table_num_sorted_t const pbkdf2_crypt_names[] = { static size_t pbkdf2_crypt_names_len = NUM_ELEMENTS(pbkdf2_crypt_names); static fr_table_num_sorted_t const pbkdf2_passlib_names[] = { - { "sha1", FR_SSHA_PASSWORD }, + { "sha1", FR_SSHA1_PASSWORD }, { "sha256", FR_SSHA2_256_PASSWORD }, { "sha512", FR_SSHA2_512_PASSWORD } }; static size_t pbkdf2_passlib_names_len = NUM_ELEMENTS(pbkdf2_passlib_names); #endif +static fr_dict_attr_t const **pap_allowed_passwords; + /* * Authorize the user for PAP authentication. * @@ -193,7 +145,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *t */ static rlm_rcode_t CC_HINT(nonnull) pap_auth_clear(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, VALUE_PAIR const *password) + VALUE_PAIR const *known_good, VALUE_PAIR const *password) { if ((known_good->vp_length != password->vp_length) || (fr_digest_cmp(known_good->vp_octets, password->vp_octets, known_good->vp_length) != 0)) { @@ -207,7 +159,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_clear(UNUSED rlm_pap_t const *inst, #ifdef HAVE_CRYPT static rlm_rcode_t CC_HINT(nonnull) pap_auth_crypt(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, VALUE_PAIR const *password) + VALUE_PAIR const *known_good, VALUE_PAIR const *password) { if (fr_crypt_check(password->vp_strvalue, known_good->vp_strvalue) != 0) { REDEBUG("Crypt digest does not match \"known good\" digest"); @@ -218,7 +170,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_crypt(UNUSED rlm_pap_t const *inst, #endif static rlm_rcode_t CC_HINT(nonnull) pap_auth_md5(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, VALUE_PAIR const *password) + VALUE_PAIR const *known_good, VALUE_PAIR const *password) { uint8_t digest[MD5_DIGEST_LENGTH]; @@ -242,7 +194,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_md5(UNUSED rlm_pap_t const *inst, R static rlm_rcode_t CC_HINT(nonnull) pap_auth_smd5(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, VALUE_PAIR const *password) + VALUE_PAIR const *known_good, VALUE_PAIR const *password) { fr_md5_ctx_t *md5_ctx; uint8_t digest[MD5_DIGEST_LENGTH]; @@ -272,8 +224,8 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_smd5(UNUSED rlm_pap_t const *inst, return RLM_MODULE_OK; } -static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, VALUE_PAIR const *password) +static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha1(UNUSED rlm_pap_t const *inst, REQUEST *request, + VALUE_PAIR const *known_good, VALUE_PAIR const *password) { fr_sha1_ctx sha1_context; uint8_t digest[SHA1_DIGEST_LENGTH]; @@ -298,9 +250,8 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha(UNUSED rlm_pap_t const *inst, R return RLM_MODULE_OK; } -#ifdef HAVE_OPENSSL_EVP_H -static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, VALUE_PAIR const *password) +static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha1(UNUSED rlm_pap_t const *inst, REQUEST *request, + VALUE_PAIR const *known_good, VALUE_PAIR const *password) { fr_sha1_ctx sha1_context; uint8_t digest[SHA1_DIGEST_LENGTH]; @@ -329,94 +280,15 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha(UNUSED rlm_pap_t const *inst, return RLM_MODULE_OK; } -static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha_evp(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, VALUE_PAIR const *password) +#ifdef HAVE_OPENSSL_EVP_H +static rlm_rcode_t CC_HINT(nonnull) pap_auth_evp_md(UNUSED rlm_pap_t const *inst, REQUEST *request, + VALUE_PAIR const *known_good, VALUE_PAIR const *password, + char const *name, EVP_MD const *md) { EVP_MD_CTX *ctx; - EVP_MD const *md; - char const *name; uint8_t digest[EVP_MAX_MD_SIZE]; unsigned int digest_len; - if (known_good->da == attr_sha2_password) { - /* - * All the SHA-2 algorithms produce digests of different lengths, - * so it's trivial to determine which EVP_MD to use. - */ - switch (known_good->vp_length) { - /* SHA2-224 */ - case SHA224_DIGEST_LENGTH: - name = "SHA2-224"; - md = EVP_sha224(); - break; - - /* SHA2-256 */ - case SHA256_DIGEST_LENGTH: - name = "SHA2-256"; - md = EVP_sha256(); - break; - - /* SHA2-384 */ - case SHA384_DIGEST_LENGTH: - name = "SHA2-384"; - md = EVP_sha384(); - break; - - /* SHA2-512 */ - case SHA512_DIGEST_LENGTH: - name = "SHA2-512"; - md = EVP_sha512(); - break; - - default: - REDEBUG("\"known good\" digest length (%zu) does not match output length of any SHA-2 digests", - known_good->vp_length); - return RLM_MODULE_INVALID; - } - } -# if OPENSSL_VERSION_NUMBER >= 0x10101000L - else if (known_good->da == attr_sha3_password) { - /* - * All the SHA-3 algorithms produce digests of different lengths, - * so it's trivial to determine which EVP_MD to use. - */ - switch (known_good->vp_length) { - /* SHA3-224 */ - case SHA224_DIGEST_LENGTH: - name = "SHA3-224"; - md = EVP_sha3_224(); - break; - - /* SHA3-256 */ - case SHA256_DIGEST_LENGTH: - name = "SHA3-256"; - md = EVP_sha3_256(); - break; - - /* SHA3-384 */ - case SHA384_DIGEST_LENGTH: - name = "SHA3-384"; - md = EVP_sha3_384(); - break; - - /* SHA3-512 */ - case SHA512_DIGEST_LENGTH: - name = "SHA3-512"; - md = EVP_sha3_512(); - break; - - default: - REDEBUG("\"known good\" digest length (%zu) does not match output length of any SHA-3 digests", - known_good->vp_length); - return RLM_MODULE_INVALID; - } - } -# endif - else { - rad_assert(0); - return RLM_MODULE_INVALID; - } - ctx = EVP_MD_CTX_create(); EVP_DigestInit_ex(ctx, md, NULL); EVP_DigestUpdate(ctx, password->vp_octets, password->vp_length); @@ -436,62 +308,15 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha_evp(UNUSED rlm_pap_t const *ins return RLM_MODULE_OK; } -static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha_evp(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, VALUE_PAIR const *password) +static rlm_rcode_t CC_HINT(nonnull) pap_auth_evp_md_salted(UNUSED rlm_pap_t const *inst, REQUEST *request, + VALUE_PAIR const *known_good, VALUE_PAIR const *password, + char const *name, EVP_MD const *md) { EVP_MD_CTX *ctx; - EVP_MD const *md = NULL; - char const *name = NULL; uint8_t digest[EVP_MAX_MD_SIZE]; - unsigned int digest_len, min_len = 0; - - if (known_good->da == attr_ssha2_224_password) { - name = "SSHA2-224"; - md = EVP_sha224(); - min_len = SHA224_DIGEST_LENGTH; - } else if (known_good->da == attr_ssha2_256_password) { - name = "SSHA2-256"; - md = EVP_sha256(); - min_len = SHA256_DIGEST_LENGTH; - } else if (known_good->da == attr_ssha2_384_password) { - name = "SSHA2-384"; - md = EVP_sha384(); - min_len = SHA384_DIGEST_LENGTH; - } else if (known_good->da == attr_ssha2_512_password) { - name = "SSHA2-512"; - min_len = SHA512_DIGEST_LENGTH; - md = EVP_sha512(); - } -#if OPENSSL_VERSION_NUMBER >= 0x10101000L - else if (known_good->da == attr_ssha3_224_password) { - name = "SSHA3-224"; - md = EVP_sha3_224(); - min_len = SHA224_DIGEST_LENGTH; - } else if (known_good->da == attr_ssha3_256_password) { - name = "SSHA3-256"; - md = EVP_sha3_256(); - min_len = SHA256_DIGEST_LENGTH; - } else if (known_good->da == attr_ssha3_384_password) { - name = "SSHA3-384"; - md = EVP_sha3_384(); - min_len = SHA384_DIGEST_LENGTH; - } else if (known_good->da == attr_ssha3_512_password) { - name = "SSHA3-512"; - min_len = SHA512_DIGEST_LENGTH; - md = EVP_sha3_512(); - } -#endif - else { - rad_assert(0); - return RLM_MODULE_INVALID; - } - - if (known_good->vp_length <= min_len) { - REDEBUG("\"known-good\" %s-Password has incorrect length, got %zu bytes, need at least %u bytes", - name, known_good->vp_length, min_len + 1); - return RLM_MODULE_INVALID; - } + unsigned int digest_len, min_len; + min_len = EVP_MD_size(md); ctx = EVP_MD_CTX_create(); EVP_DigestInit_ex(ctx, md, NULL); EVP_DigestUpdate(ctx, password->vp_octets, password->vp_length); @@ -517,6 +342,36 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha_evp(UNUSED rlm_pap_t const *in return RLM_MODULE_OK; } +/** Define a new OpenSSL EVP based password hashing function + * + */ +#define PAP_AUTH_EVP_MD(_func, _new_func, _name, _md) \ +static rlm_rcode_t CC_HINT(nonnull) _new_func(rlm_pap_t const *inst, REQUEST *request, \ + VALUE_PAIR const *known_good, VALUE_PAIR const *password) \ +{ \ + return _func(inst, request, known_good, password, _name, _md); \ +} + +PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha2_224, "SHA2-224", EVP_sha224()); +PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha2_256, "SHA2-256", EVP_sha256()); +PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha2_384, "SHA2-384", EVP_sha384()); +PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha2_512, "SHA2-512", EVP_sha512()); +PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha2_224, "SSHA2-224", EVP_sha224()); +PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha2_256, "SSHA2-256", EVP_sha256()); +PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha2_384, "SSHA2-384", EVP_sha384()); +PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha2_512, "SSHA2-512", EVP_sha512()); + +# if OPENSSL_VERSION_NUMBER >= 0x10101000L +PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha3_224, "SHA3-224", EVP_sha3_224()); +PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha3_256, "SHA3-256", EVP_sha3_256()); +PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha3_384, "SHA3-384", EVP_sha3_384()); +PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha3_512, "SHA3-512", EVP_sha3_512()); +PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha3_224, "SSHA3-224", EVP_sha3_224()); +PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha3_256, "SSHA3-256", EVP_sha3_256()); +PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha3_384, "SSHA3-384", EVP_sha3_384()); +PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha3_512, "SSHA3-512", EVP_sha3_512()); +# endif + /** Validates Crypt::PBKDF2 LDAP format strings * * @param[in] request The current request. @@ -568,7 +423,7 @@ static inline rlm_rcode_t CC_HINT(nonnull) pap_auth_pbkdf2_parse(REQUEST *reques digest_type = fr_table_value_by_substr(hash_names, (char const *)p, q - p, -1); switch (digest_type) { - case FR_SSHA_PASSWORD: + case FR_SSHA1_PASSWORD: evp_md = EVP_sha1(); digest_len = SHA1_DIGEST_LENGTH; break; @@ -742,7 +597,7 @@ finish: static inline rlm_rcode_t CC_HINT(nonnull) pap_auth_pbkdf2(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, VALUE_PAIR const *password) + VALUE_PAIR const *known_good, VALUE_PAIR const *password) { uint8_t const *p = known_good->vp_octets, *q, *end = p + known_good->vp_length; @@ -803,7 +658,7 @@ static inline rlm_rcode_t CC_HINT(nonnull) pap_auth_pbkdf2(UNUSED rlm_pap_t cons #endif static rlm_rcode_t CC_HINT(nonnull) pap_auth_nt(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, VALUE_PAIR const *password) + VALUE_PAIR const *known_good, VALUE_PAIR const *password) { ssize_t len; uint8_t digest[MD4_DIGEST_LENGTH]; @@ -838,7 +693,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_nt(UNUSED rlm_pap_t const *inst, RE } static rlm_rcode_t CC_HINT(nonnull) pap_auth_lm(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, UNUSED VALUE_PAIR const *password) + VALUE_PAIR const *known_good, UNUSED VALUE_PAIR const *password) { uint8_t digest[MD4_DIGEST_LENGTH]; char charbuf[32 + 1]; @@ -866,7 +721,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_lm(UNUSED rlm_pap_t const *inst, RE } static rlm_rcode_t CC_HINT(nonnull) pap_auth_ns_mta_md5(UNUSED rlm_pap_t const *inst, REQUEST *request, - VALUE_PAIR *known_good, VALUE_PAIR const *password) + VALUE_PAIR const *known_good, VALUE_PAIR const *password) { uint8_t digest[128]; uint8_t buff[FR_MAX_STRING_LEN]; @@ -875,7 +730,8 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_ns_mta_md5(UNUSED rlm_pap_t const * RDEBUG2("Using NT-MTA-MD5-Password"); if (known_good->vp_length != 64) { - REDEBUG("\"known good\" NS-MTA-MD5-Password has incorrect length, expected 64 got %zu", known_good->vp_length); + REDEBUG("\"known good\" NS-MTA-MD5-Password has incorrect length, expected 64 got %zu", + known_good->vp_length); return RLM_MODULE_INVALID; } @@ -923,53 +779,70 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_ns_mta_md5(UNUSED rlm_pap_t const * return RLM_MODULE_OK; } -typedef rlm_rcode_t (*pap_auth_func_t)(rlm_pap_t const *, REQUEST *, VALUE_PAIR *, VALUE_PAIR const *); +/** Auth func for password types that should have been normalised away + * + */ +static rlm_rcode_t CC_HINT(nonnull) pap_auth_dummy(UNUSED rlm_pap_t const *inst, UNUSED REQUEST *request, + UNUSED VALUE_PAIR const *known_good, UNUSED VALUE_PAIR const *password) +{ + return RLM_MODULE_FAIL; +} +/** Table of password types we can process + * + */ static const pap_auth_func_t auth_func_table[] = { - [FR_CLEARTEXT_PASSWORD - 5000 ] = pap_auth_clear, - [FR_LM_PASSWORD - 5000 ] = pap_auth_lm, - [FR_MD5_PASSWORD - 5000 ] = pap_auth_md5, - [FR_SMD5_PASSWORD - 5000 ] = pap_auth_smd5, + [FR_CLEARTEXT_PASSWORD] = pap_auth_clear, + [FR_LM_PASSWORD] = pap_auth_lm, + [FR_MD5_PASSWORD] = pap_auth_md5, + [FR_SMD5_PASSWORD] = pap_auth_smd5, #ifdef HAVE_CRYPT - [FR_CRYPT_PASSWORD - 5000 ] = pap_auth_crypt, + [FR_CRYPT_PASSWORD] = pap_auth_crypt, #endif - [FR_NS_MTA_MD5_PASSWORD - 5000 ] = pap_auth_ns_mta_md5, - [FR_NT_PASSWORD - 5000 ] = pap_auth_nt, - [FR_SHA_PASSWORD - 5000 ] = pap_auth_sha, - [FR_SHA1_PASSWORD - 5000 ] = pap_auth_sha, + [FR_NS_MTA_MD5_PASSWORD] = pap_auth_ns_mta_md5, + [FR_NT_PASSWORD] = pap_auth_nt, + [FR_PASSWORD_WITH_HEADER] = pap_auth_dummy, + [FR_SHA1_PASSWORD] = pap_auth_sha1, #ifdef HAVE_OPENSSL_EVP_H - [FR_SHA2_PASSWORD - 5000 ] = pap_auth_sha_evp, - [FR_SSHA_PASSWORD - 5000 ] = pap_auth_ssha, - [FR_SSHA1_PASSWORD - 5000 ] = pap_auth_ssha, - [FR_SSHA2_224_PASSWORD - 5000 ] = pap_auth_ssha_evp, - [FR_SSHA2_256_PASSWORD - 5000 ] = pap_auth_ssha_evp, - [FR_SSHA2_384_PASSWORD - 5000 ] = pap_auth_ssha_evp, - [FR_SSHA2_512_PASSWORD - 5000 ] = pap_auth_ssha_evp, - - [FR_PBKDF2_PASSWORD - 5000 ] = pap_auth_pbkdf2, + [FR_PBKDF2_PASSWORD] = pap_auth_pbkdf2, + [FR_SHA2_PASSWORD] = pap_auth_dummy, + [FR_SHA2_224_PASSWORD] = pap_auth_sha2_224, + [FR_SHA2_256_PASSWORD] = pap_auth_sha2_256, + [FR_SHA2_384_PASSWORD] = pap_auth_sha2_384, + [FR_SHA2_512_PASSWORD] = pap_auth_sha2_512, + [FR_SSHA1_PASSWORD] = pap_auth_ssha1, + [FR_SSHA2_224_PASSWORD] = pap_auth_ssha2_224, + [FR_SSHA2_256_PASSWORD] = pap_auth_ssha2_256, + [FR_SSHA2_384_PASSWORD] = pap_auth_ssha2_384, + [FR_SSHA2_512_PASSWORD] = pap_auth_ssha2_512, # if OPENSSL_VERSION_NUMBER >= 0x10101000L - [FR_SHA3_PASSWORD - 5000 ] = pap_auth_sha_evp, - [FR_SSHA3_224_PASSWORD - 5000 ] = pap_auth_ssha_evp, - [FR_SSHA3_256_PASSWORD - 5000 ] = pap_auth_ssha_evp, - [FR_SSHA3_384_PASSWORD - 5000 ] = pap_auth_ssha_evp, - [FR_SSHA3_512_PASSWORD - 5000 ] = pap_auth_ssha_evp, -#endif + [FR_SHA3_PASSWORD] = pap_auth_dummy, + [FR_SHA3_224_PASSWORD] = pap_auth_sha3_224, + [FR_SHA3_256_PASSWORD] = pap_auth_sha3_256, + [FR_SHA3_384_PASSWORD] = pap_auth_sha3_384, + [FR_SHA3_512_PASSWORD] = pap_auth_sha3_512, + [FR_SSHA3_224_PASSWORD] = pap_auth_ssha3_224, + [FR_SSHA3_256_PASSWORD] = pap_auth_ssha3_256, + [FR_SSHA3_384_PASSWORD] = pap_auth_ssha3_384, + [FR_SSHA3_512_PASSWORD] = pap_auth_ssha3_512, +# endif #endif /* HAVE_OPENSSL_EVP_H */ }; -#define MAX_KNOWN_PASSWORD (sizeof(auth_func_table) / sizeof(auth_func_table[0])) /* * Authenticate the user via one of any well-known password. */ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED void *thread, REQUEST *request) { - rlm_pap_t const *inst = instance; - VALUE_PAIR *known_good, *password; - rlm_rcode_t rcode = RLM_MODULE_INVALID; - pap_auth_func_t auth_func; + rlm_pap_t const *inst = instance; + VALUE_PAIR const *known_good; + VALUE_PAIR *password; + rlm_rcode_t rcode = RLM_MODULE_INVALID; + pap_auth_func_t auth_func; + TALLOC_CTX *tmp_ctx; password = fr_pair_find_by_da(request->packet->vps, attr_user_password, TAG_ANY); if (!password) { @@ -986,37 +859,35 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED void } if (RDEBUG_ENABLED3) { - RDEBUG3("Login attempt with password \"%pV\" (%zd)", - fr_box_strvalue_len(password->vp_strvalue, password->vp_length), - password->vp_length); + RDEBUG3("Login attempt with %pP (%zd)", password, password->vp_length); } else { RDEBUG2("Login attempt with password"); } /* - * Normalise passwords, if it hasn't already been done. - * - * This function returns the "known good" password, and - * prefers to return Cleartext-Password over everything - * else. + * Holds any ephemeral attributes */ - known_good = password_normalise(request, inst->normify); - if (!known_good) { - REDEBUG("No \"known good\" password found for user."); - REDEBUG("Some other module must authenticate the user."); - return RLM_MODULE_NOOP; - } + MEM(tmp_ctx = talloc_named_const(request, 0, "password_tmp_ctx")); - if ((known_good->da->attr < 5000) || (known_good->da->attr >= (5000 + MAX_KNOWN_PASSWORD)) || - !auth_func_table[known_good->da->attr - 5000]) { - /* - * It's a known password, but we don't know what it is. - */ - REDEBUG("No \"known good\" password was found for user"); + /* + * Retrieve the normalised version of + * the known_good password, without + * mangling the current password attributes + * in the request. + */ + known_good = password_find(tmp_ctx, request, + pap_allowed_passwords, talloc_array_length(pap_allowed_passwords), + inst->normify); + if (!known_good) { + REDEBUG("No \"known good\" password found for user"); + talloc_free(tmp_ctx); return RLM_MODULE_FAIL; } - auth_func = auth_func_table[known_good->da->attr - 5000]; + rad_assert(known_good->da->attr < NUM_ELEMENTS(auth_func_table)); + + auth_func = auth_func_table[known_good->da->attr]; + rad_assert(auth_func); if (RDEBUG_ENABLED3) { RDEBUG3("Comparing with \"known good\" %pP (%zu)", known_good, known_good->vp_length); @@ -1040,6 +911,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED void default: break; } + talloc_free(tmp_ctx); return rcode; } @@ -1066,6 +938,62 @@ static int mod_bootstrap(void *instance, CONF_SECTION *conf) return 0; } +static int mod_load(void) +{ + size_t i, j = 0; + size_t allowed = 0; + + /* + * Load the dictionaries early + */ + if (fr_dict_autoload(rlm_pap_dict) < 0) { + PERROR("%s", __FUNCTION__); + return -1; + } + if (fr_dict_attr_autoload(rlm_pap_dict_attr) < 0) { + PERROR("%s", __FUNCTION__); + fr_dict_autofree(rlm_pap_dict); + return -1; + } + + /* + * Figure out how many password types we allow + */ + for (i = 0; i < NUM_ELEMENTS(auth_func_table); i++) { + if (auth_func_table[i] == NULL) continue; + + allowed++; + } + + /* + * Get a list of the DAs that match are allowed + * functions. + */ + pap_allowed_passwords = talloc_array(NULL, fr_dict_attr_t const *, allowed); + for (i = 0; i < NUM_ELEMENTS(auth_func_table); i++) { + fr_dict_attr_t const *password_da; + + if (auth_func_table[i] == NULL) continue; + + password_da = fr_dict_attr_child_by_num(attr_password_root, i); + if (!fr_cond_assert(password_da)) { + ERROR("Could not resolve password attribute %zu", i); + talloc_free(pap_allowed_passwords); + return -1; + } + + pap_allowed_passwords[j++] = password_da; + } + + return 0; +} + +static void mod_unload(void) +{ + talloc_free(pap_allowed_passwords); + fr_dict_autofree(rlm_pap_dict); +} + /* * The module name should be the only globally exported symbol. * That is, everything else should be 'static'. @@ -1080,6 +1008,8 @@ module_t rlm_pap = { .magic = RLM_MODULE_INIT, .name = "pap", .inst_size = sizeof(rlm_pap_t), + .onload = mod_load, + .unload = mod_unload, .config = module_config, .bootstrap = mod_bootstrap, .methods = { diff --git a/src/tests/keywords/pap-ssha2 b/src/tests/keywords/pap-ssha2 index ecf23b8fab3..b0325b2b751 100644 --- a/src/tests/keywords/pap-ssha2 +++ b/src/tests/keywords/pap-ssha2 @@ -102,6 +102,68 @@ update { &control: !* ANY } +# +# Base64 of SHA2-384 password (in SHA2-Password) +# +update control { + &control:SHA2-Password := "%{hex:%{sha2_384:%{request:User-Password}}}" +} + +pap.authorize +pap.authenticate { + reject = 1 +} +if (reject) { + test_fail +} + +update { + &control: !* ANY +} + +update control { + &Auth-Type := Accept +} + +# +# Base64 of SHA2-256 password (in SHA2-256-Password) +# +update control { + &control:SHA2-256-Password := "%{hex:%{sha2_256:%{request:User-Password}}}" +} + +pap.authorize +pap.authenticate { + reject = 1 +} +if (reject) { + test_fail +} + +update { + &control: !* ANY +} + +# +# Base64 of SHA2-224 password (in SHA2-224-Password - No hex armour) +# +update control { + &control:SHA2-224-Password := "%{sha2_224:%{request:User-Password}}" +} + +pap.authorize +pap.authenticate { + reject = 1 +} +if (reject) { + test_fail +} + +update { + &control: !* ANY +} + + update control { &Auth-Type := Accept }