27RCSID(
"$Id: de7c072a74d0abc1c5d0d4b0dbd96327426bbdd7 $")
29#include <freeradius-devel/server/base.h>
30#include <freeradius-devel/server/module_rlm.h>
31#include <freeradius-devel/server/tmpl_dcursor.h>
32#include <freeradius-devel/unlang/xlat_func.h>
33#include <freeradius-devel/util/base16.h>
35#include <openssl/ssl.h>
36#include <openssl/evp.h>
37#include <openssl/hmac.h>
166 .inst_type =
"dpsk_autz_call_env_t",
180 .inst_type =
"dpsk_auth_call_env_t",
192 dpsk_auth_call_env_t, key_msg, key_msg_tmpl), .pair.dflt =
"FreeRADIUS-EV5.802_1X-EAPoL-Key-Msg",
234 if (!
inst->auth_type) {
235 WARN(
"No 'authenticate %s {...}' section or 'Auth-Type = %s' set. Cannot setup DPSK authentication.",
247 memcpy(key->
mac, mac,
sizeof(key->
mac));
248 memcpy(&key->
ssid, &ssid->vb_octets,
sizeof(key->
ssid));
261 dpsk_cache_key_set(&my_entry, ssid, mac);
271 RDEBUG3(
"Cache entry has expired");
283 if (PKCS5_PBKDF2_HMAC_SHA1((
const char *) psk, (
int)psk_len, (
const unsigned char *) ssid->vb_strvalue,
284 (
int)ssid->vb_length, 4096, (
int)buflen,
buffer) == 0) {
285 RERROR(
"Failed calling OpenSSL to calculate the PMK");
303 unsigned int digest_len, mic_len;
308 char const *psk_identity = NULL, *psk = NULL;
310 uint8_t const *snonce, *ap_mac;
311 uint8_t const *min_mac, *max_mac;
312 uint8_t const *min_nonce, *max_nonce;
314 uint8_t s_mac[6], message[
sizeof(
"Pairwise key expansion") + 6 + 6 + 32 + 32 + 1], frame[128];
315 uint8_t digest[EVP_MAX_MD_SIZE], mic[EVP_MAX_MD_SIZE];
316 char token_identity[256];
331 if (env->
anonce.vb_length != 32) {
336 if (env->
key_msg.vb_length <
sizeof(*eapol)) {
341 if (env->
key_msg.vb_length >
sizeof(frame)) {
361#if defined(__clang_analyzer__) || defined(__COVERITY__)
374 RERROR(
"User-Name is not a recognizable hex MAC address");
379 RERROR(
"Called-Station-MAC is not a recognizable MAC address");
388 if (memcmp(s_mac, ap_mac, 6) <= 0) {
403 snonce = env->
key_msg.vb_octets + 17;
404 if (memcmp(snonce, env->
anonce.vb_octets, 32) <= 0) {
406 max_nonce = env->
anonce.vb_octets;
408 min_nonce = env->
anonce.vb_octets;
415 memcpy(message,
"Pairwise key expansion",
sizeof(
"Pairwise key expansion"));
416 p = &message[
sizeof(
"Pairwise key expansion")];
418 memcpy(p, min_mac, 6);
419 memcpy(p + 6, max_mac, 6);
422 memcpy(p, min_nonce, 32);
423 memcpy(p + 32, max_nonce, 32);
426 fr_assert(
sizeof(message) == (p + 1 - message));
434 if (
inst->cache_size) {
437 pthread_mutex_lock(&
inst->mutable->mutex);
438 entry = dpsk_cache_find(
request,
inst, pmk,
sizeof(pmk), &env->
ssid, s_mac);
446 pthread_mutex_unlock(&
inst->mutable->mutex);
449 pthread_mutex_unlock(&
inst->mutable->mutex);
460 if (env->
masterkey.vb_length !=
sizeof(pmk)) {
465 memcpy(pmk, env->
masterkey.vb_octets,
sizeof(pmk));
475 if (generate_pmk(
request, pmk,
sizeof(pmk), &env->
ssid, env->
psk.vb_strvalue, env->
psk.vb_length) < 0) {
483 psk_identity = env->
username.vb_strvalue;
486 psk = env->
psk.vb_strvalue;
487 psk_len = env->
psk.vb_length;
500 RERROR(
"No %s was found, and no 'filename' was configured", env->
psk_tmpl->name);
504#if defined(__clang_analyzer__) || defined(__COVERITY__)
511 memset(token_mac, 0,
sizeof(token_mac));
514 RDEBUG3(
"Looking for PSK in file %s", filename);
516 fp = fopen(filename,
"r");
522 fr_sbuff_init_file(&sbuff, &fctx,
buffer,
sizeof(
buffer), fp, SIZE_MAX);
529 SIZE_MAX, quoted ? "ed_terms : &terms, NULL);
531 RERROR(
"%s[%d] Failed reading identity: %s", filename, lineno, fr_sbuff_err_to_str(sberr));
535 RDEBUG(
"Failed to find matching PSK or MAC in %s", filename);
546 RDEBUG(
"%s[%d] Failed to find ',' after identity", filename, lineno);
553 SIZE_MAX, quoted ? "ed_terms : &terms, NULL);
555 RERROR(
"%s[%d] Failed reading PSK: %s", filename, lineno, fr_sbuff_err_to_str(sberr));
559 RDEBUG(
"%s[%d] Failed parsing PSK", filename, lineno);
577 SIZE_MAX, quoted ? "ed_terms : &terms, NULL);
579 RERROR(
"%s[%d] Failed reading MAC: %s", filename, lineno, fr_sbuff_err_to_str(sberr));
583 RERROR(
"%s[%d] Failed parsing MAC", filename, lineno);
594 RERROR(
"%s[%d] Failed parsing MAC", filename, lineno);
602 if (memcmp(s_mac, token_mac, 6) != 0) {
614 psk_len = strlen(token_psk);
615 psk_identity = token_identity;
617 RDEBUG3(
"%s[%d] Trying PSK %s", filename, lineno, token_psk);
618 if (generate_pmk(
request, pmk,
sizeof(pmk), &env->
ssid, psk, psk_len) < 0) {
628 digest_len =
sizeof(digest);
633 memset(digest, 0, digest_len);
635 HMAC(EVP_sha1(), pmk,
sizeof(pmk), message,
sizeof(message), digest, &digest_len);
637 RHEXDUMP3(message,
sizeof(message),
"message:");
646 memset(&zeroed->
frame.
mic[0], 0, 16);
650 mic_len =
sizeof(mic);
655 memset(mic, 0, mic_len);
657 HMAC(EVP_sha1(), digest, 16, frame, env->
key_msg.vb_length, mic, &mic_len);
662 if (memcmp(&eapol->
frame.
mic[0], mic, 16) != 0) {
678 dpsk_cache_key_set(&my_entry, &env->
ssid, s_mac);
684 pthread_mutex_lock(&
inst->mutable->mutex);
686 if (entry && (memcmp(entry->
pmk, pmk,
sizeof(entry->
pmk)) == 0)) {
689 pthread_mutex_unlock(&
inst->mutable->mutex);
701 RDEBUG(
"Checking file %s for PSK and MAC", filename);
705 RWARN(
"No 'filename' was configured.");
715 if (stage == 2)
goto stage2a;
725 RWARN(
"Found matching MAC in %s, but the PSK does not match", filename);
739 fr_assert(psk_identity == token_identity);
750 if (
inst->cache_size && psk && psk_identity) {
759 dpsk_cache_key_set(&my_entry, &env->
ssid, s_mac);
761 pthread_mutex_lock(&
inst->mutable->mutex);
770 if (entry && (memcmp(entry->
pmk, pmk,
sizeof(entry->
pmk)) != 0)) entry = NULL;
783 memcpy(entry->
mac, s_mac,
sizeof(entry->
mac));
784 memcpy(entry->
pmk, pmk,
sizeof(entry->
pmk));
789 MEM(entry->
ssid = talloc_memdup(entry, env->
ssid.vb_octets, env->
ssid.vb_length));
803 pthread_mutex_unlock(&
inst->mutable->mutex);
804 goto update_attributes;
814 pthread_mutex_unlock(&
inst->mutable->mutex);
878 if (PKCS5_PBKDF2_HMAC_SHA1(psk->vb_strvalue, (
int)psk->vb_length,
879 (
const unsigned char *) ssid->vb_strvalue, (
int)ssid->vb_length,
881 RERROR(
"Failed calling OpenSSL to calculate the PMK");
913 rcode = memcmp(a->
mac, b->
mac,
sizeof(a->
mac));
914 if (rcode != 0)
return CMP(rcode, 0);
939 if (!
inst->cache_size)
return 0;
941 pthread_mutex_destroy(&
inst->mutable->mutex);
955 if (!
inst->auth_type) {
956 WARN(
"Failed to find 'authenticate %s {...}' section. DPSK will likely not work",
965 if (!
inst->cache_size)
return 0;
978 if (pthread_mutex_init(&
inst->mutable->mutex, NULL) < 0) {
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
static int const char char buffer[256]
#define fr_base16_decode(_err, _out, _in, _no_trailing)
static uint8_t request[4]
#define L(_str)
Helper for initialising arrays of string literals.
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
fr_cmp_ret_t
Result of an ordering comparison.
#define CALL_ENV_TERMINATOR
#define FR_CALL_ENV_PARSE_OFFSET(_name, _cast_type, _flags, _struct, _field, _parse_field)
Specify a call_env_parser_t which writes out runtime results and the result of the parsing phase to t...
@ CALL_ENV_FLAG_ATTRIBUTE
Tmpl MUST contain an attribute reference.
@ CALL_ENV_FLAG_REQUIRED
Associated conf pair or section is required.
@ CALL_ENV_FLAG_NULLABLE
Tmpl expansions are allowed to produce no output.
size_t inst_size
Size of per call env.
#define FR_CALL_ENV_OFFSET(_name, _cast_type, _flags, _struct, _field)
Specify a call_env_parser_t which writes out runtime results to the specified field.
#define FR_CALL_ENV_PARSE_ONLY_OFFSET(_name, _cast_type, _flags, _struct, _parse_field)
Specify a call_env_parser_t which writes out the result of the parsing phase to the field specified.
#define CONF_PARSER_TERMINATOR
#define FR_INTEGER_BOUND_CHECK(_name, _var, _op, _bound)
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
#define FR_TIME_DELTA_BOUND_CHECK(_name, _var, _op, _bound)
Defines a CONF_PAIR to C data type mapping.
#define cf_log_err(_cf, _fmt,...)
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
static int fr_dcursor_append(fr_dcursor_t *cursor, void *v)
Insert a single item at the end of the list.
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
#define DICT_AUTOLOAD_TERMINATOR
fr_dict_enum_value_t const * fr_dict_enum_by_name(fr_dict_attr_t const *da, char const *name, ssize_t len)
Specifies an attribute which must be present for the module to function.
Specifies a dictionary which must be loaded/loadable for the module to function.
Value of an enumerated attribute.
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
#define fr_dlist_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
static void * fr_dlist_head(fr_dlist_head_t const *list_head)
Return the HEAD item of a list or NULL if the list is empty.
static void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
static bool fr_dlist_entry_in_list(fr_dlist_t const *entry)
Check if a list entry is part of a list.
static void fr_dlist_entry_unlink(fr_dlist_t *entry)
Remove an item from the dlist when we don't have access to the head.
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
Head of a doubly linked list.
Entry in a doubly linked list.
#define RHEXDUMP3(_data, _len, _fmt,...)
int map_to_vp(TALLOC_CTX *ctx, fr_pair_list_t *out, request_t *request, map_t const *map, UNUSED void *uctx)
Convert a map to a fr_pair_t.
int map_to_request(request_t *request, map_t const *map, radius_map_getvalue_t func, void *ctx)
Convert map_t to fr_pair_t (s) and add them to a request_t.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_OCTETS
Raw octets.
fr_sbuff_err_t fr_sbuff_out_bstrncpy_until(size_t *len, fr_sbuff_t *out, fr_sbuff_t *in, size_t max, fr_sbuff_term_t const *tt, fr_sbuff_unescape_rules_t const *u_rules)
void * env_data
Per call environment data.
module_instance_t const * mi
Instance of the module being instantiated.
module_instance_t * mi
Module instance to detach.
module_instance_t * mi
Instance of the module being instantiated.
Temporary structure to hold arguments for module calls.
Temporary structure to hold arguments for detach calls.
Temporary structure to hold arguments for instantiation calls.
bool module_rlm_section_type_set(request_t *request, fr_dict_attr_t const *type_da, fr_dict_enum_value_t const *enumv)
Set the next section type if it's not already set.
module_t common
Common fields presented by all modules.
static const conf_parser_t config[]
static int mod_load(void)
static void mod_unload(void)
uint32_t fr_rb_num_elements(fr_rb_tree_t *tree)
Return how many nodes there are in a tree.
int fr_rb_find(void **found, fr_rb_tree_t const *tree, void const *data)
Find an element in the tree, returning the data, not the node.
int fr_rb_delete(fr_rb_tree_t *tree, void const *data)
Remove node and free data (if a free function was specified)
int fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
#define fr_rb_inline_init(_tree, _type, _field, _data_cmp, _data_free)
Initialises a red black tree.
The main red black tree structure.
#define RETURN_UNLANG_UPDATED
rlm_rcode_t
Return codes indicating the result of the module call.
@ RLM_MODULE_OK
The module is OK, continue.
@ RLM_MODULE_UPDATED
OK (pairs modified).
#define RETURN_UNLANG_FAIL
#define RETURN_UNLANG_REJECT
#define RETURN_UNLANG_NOOP
static int mod_detach(module_detach_ctx_t const *mctx)
static void cache_entry_free(rlm_cache_entry_t *c)
Locate a cache entry in memcached.
static fr_cmp_ret_t cache_entry_cmp(void const *one, void const *two)
Compare two entries by key.
static unlang_action_t mod_authorize(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
static unlang_action_t mod_authenticate(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
tmpl_t * psk_identity_dest_tmpl
struct rlm_dpsk_s rlm_dpsk_t
static fr_dict_t const * dict_freeradius
static fr_dict_attr_t const * attr_auth_type
fr_dict_autoload_t rlm_dpsk_dict[]
fr_value_box_t psk_identity
fr_dict_attr_autoload_t rlm_dpsk_dict_attr[]
pthread_mutex_t mutex
Held for every operation on cache, head, and the entries.
static const conf_parser_t module_config[]
fr_dict_enum_value_t const * auth_type
fr_time_delta_t cache_lifetime
fr_value_box_t calledstation
static int mod_instantiate(module_inst_ctx_t const *mctx)
uint8_t replay_counter[8]
bool fr_sbuff_next_if_char(fr_sbuff_t *sbuff, char c)
Return true if the current char matches, and if it does, advance.
#define FR_SBUFF_IN(_start, _len_or_end)
#define fr_sbuff_adv_past_whitespace(_sbuff, _len, _tt)
#define FR_SBUFF_TERMS(...)
Initialise a terminal structure with a list of sorted strings.
#define fr_sbuff_adv_past_blank(_sbuff, _len, _tt)
#define FR_SBUFF_OUT(_start, _len_or_end)
Set of terminal elements.
File sbuff extension structure.
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
char const * name
Instance name e.g. user_database.
CONF_SECTION * conf
Module's instance configuration.
size_t inst_size
Size of the module's instance data.
void * data
Module's instance data.
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Named methods exported by a module.
@ TMPL_TYPE_DATA
Value in native boxed format.
tmpl_t * tmpl_init_shallow(tmpl_t *vpt, tmpl_type_t type, fr_token_t quote, char const *name, ssize_t len, tmpl_rules_t const *t_rules))
Initialise a tmpl without copying the input name string.
eap_aka_sim_process_conf_t * inst
tmpl_t * lhs
Typically describes the attribute to add, modify or compare.
tmpl_t * rhs
Typically describes a literal value or a src attribute to copy or compare.
Stores an attribute, a value and various bits of other data.
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
char * talloc_bstrndup(TALLOC_CTX *ctx, char const *in, size_t inlen)
Binary safe strndup function.
#define talloc_get_type_abort_const
#define talloc_strdup(_ctx, _str)
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
#define fr_time_add(_a, _b)
Add a time/time delta together.
#define fr_time_gt(_a, _b)
A time delta, a difference in time measured in nanoseconds.
void tmpl_dcursor_clear(tmpl_dcursor_ctx_t *cc)
Clear any temporary state allocations.
#define tmpl_dcursor_init(_err, _ctx, _cc, _cursor, _request, _vpt)
Maintains state between cursor calls.
static char const * fail_file
@ XLAT_ACTION_FAIL
An xlat function failed.
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
#define XLAT_ARGS(_list,...)
Populate local variables with value boxes from the input list.
unsigned int required
Argument must be present, and non-empty.
#define XLAT_ARG_PARSER_TERMINATOR
Definition for a single argument consumed by an xlat function.
void fr_value_box_bstrndup_shallow(fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, size_t len, bool tainted)
Assign a string to to a fr_value_box_t.
int fr_value_box_memdup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, uint8_t const *src, size_t len, bool tainted)
Copy a buffer to a fr_value_box_t.
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
static fr_sbuff_err_t char size_t fr_sbuff_t * in
static fr_sbuff_err_t char ** out
static fr_sbuff_err_t char size_t * len
int xlat_func_args_set(xlat_t *x, xlat_arg_parser_t const args[])
Register the arguments of an xlat.
xlat_t * xlat_func_register(TALLOC_CTX *ctx, char const *name, xlat_func_t func, fr_type_t return_type)
Register an xlat function.
void xlat_func_unregister(char const *name)
Unregister an xlat function.