diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/md5.c | 73 | ||||
-rw-r--r-- | src/md5.h | 4 | ||||
-rw-r--r-- | src/pam_radius_auth.c | 144 | ||||
-rw-r--r-- | src/pam_radius_auth.h | 5 | ||||
-rw-r--r-- | src/radius.h | 60 |
5 files changed, 181 insertions, 105 deletions
@@ -173,6 +173,79 @@ void MD5Final(unsigned char digest[16], struct MD5Context *ctx) memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ } +/** Calculate HMAC using internal MD5 implementation + * + * @param digest Caller digest to be filled in. + * @param text Pointer to data stream. + * @param text_len length of data stream. + * @param key Pointer to authentication key. + * @param key_len Length of authentication key. + * + */ +void hmac_md5(uint8_t digest[16], uint8_t const *text, size_t text_len, + uint8_t const *key, size_t key_len) +{ + MD5_CTX context; + uint8_t k_ipad[65]; /* inner padding - key XORd with ipad */ + uint8_t k_opad[65]; /* outer padding - key XORd with opad */ + uint8_t tk[16]; + int i; + + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (key_len > 64) { + MD5_CTX tctx; + + MD5Init(&tctx); + MD5Update(&tctx, key, key_len); + MD5Final(tk, &tctx); + + key = tk; + key_len = 16; + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + memset( k_ipad, 0, sizeof(k_ipad)); + memset( k_opad, 0, sizeof(k_opad)); + memcpy( k_ipad, key, key_len); + memcpy( k_opad, key, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + /* + * perform inner MD5 + */ + MD5Init(&context); /* init context for 1st + * pass */ + MD5Update(&context, k_ipad, 64); /* start with inner pad */ + MD5Update(&context, text, text_len); /* then text of datagram */ + MD5Final(digest, &context); /* finish up 1st pass */ + /* + * perform outer MD5 + */ + MD5Init(&context); /* init context for 2nd + * pass */ + MD5Update(&context, k_opad, 64); /* start with outer pad */ + MD5Update(&context, digest, 16); /* then results of 1st + * hash */ + MD5Final(digest, &context); /* finish up 2nd pass */ +} + + #ifndef ASM_MD5 /* The four core functions - F1 is optimized somewhat */ @@ -39,6 +39,7 @@ #define MD5Transform pra_MD5Transform #include <inttypes.h> +#include <stdint.h> struct MD5Context { uint32_t buf[4]; @@ -51,6 +52,9 @@ void MD5Update(struct MD5Context *, unsigned const char *, unsigned); void MD5Final(unsigned char digest[16], struct MD5Context *); void MD5Transform(uint32_t buf[4], uint32_t const in[16]); +void hmac_md5(uint8_t digest[16], uint8_t const *text, size_t text_len, + uint8_t const *key, size_t key_len); + /* * This is needed to make RSAREF happy on some MS-DOS compilers. */ diff --git a/src/pam_radius_auth.c b/src/pam_radius_auth.c index ed6c5a0..cc7bf2d 100644 --- a/src/pam_radius_auth.c +++ b/src/pam_radius_auth.c @@ -131,6 +131,9 @@ static int _pam_parse(pam_handle_t * pamh, int argc, CONST char **argv, } else if (!strncmp(*argv, "max_challenge=", 14)) { conf->max_challenge = atoi(*argv + 14); + } else if (!strcmp(*argv, "require_message_authenticator")) { + conf->require_message_authenticator = TRUE; + } else { _pam_log(pamh, LOG_WARNING, "unrecognized option '%s'", *argv); @@ -379,11 +382,44 @@ static void get_accounting_vector(AUTH_HDR * request, radius_server_t * server) /* * Verify the response from the server */ -static int verify_packet(char *secret, AUTH_HDR * response, AUTH_HDR * request) +static int verify_packet(radius_server_t *server, AUTH_HDR *response, AUTH_HDR *request, radius_conf_t *conf) { MD5_CTX my_md5; - unsigned char calculated[AUTH_VECTOR_LEN]; - unsigned char reply[AUTH_VECTOR_LEN]; + uint8_t calculated[AUTH_VECTOR_LEN]; + uint8_t reply[AUTH_VECTOR_LEN]; + uint8_t *message_authenticator = NULL; + CONST uint8_t *attr, *end; + size_t secret_len = strlen(server->secret); + + attr = response->data; + end = (uint8_t *) response + ntohs(response->length); + + /* + * Check that the packet is well-formed, and find the Message-Authenticator. + */ + while (attr < end) { + size_t remaining = end - attr; + + if (remaining < 2) return FALSE; + + if (attr[1] < 2) return FALSE; + + if (attr[1] > remaining) return FALSE; + + if (attr[0] == PW_MESSAGE_AUTHENTICATOR) { + if (attr[1] != 18) return FALSE; + + if (message_authenticator) return FALSE; + + message_authenticator = (uint8_t *) response + (attr - (uint8_t *) response) + 2; + } + + attr += attr[1]; + } + + if ((request->code == PW_AUTHENTICATION_REQUEST) && conf->require_message_authenticator && !message_authenticator) { + return FALSE; + } /* * We could dispense with the memcpy, and do MD5's of the packet @@ -394,27 +430,30 @@ static int verify_packet(char *secret, AUTH_HDR * response, AUTH_HDR * request) /* MD5(response packet header + vector + response packet data + secret) */ MD5Init(&my_md5); - MD5Update(&my_md5, (unsigned char *)response, ntohs(response->length)); + MD5Update(&my_md5, (uint8_t *) response, ntohs(response->length)); + MD5Update(&my_md5, (CONST uint8_t *) server->secret, secret_len); + MD5Final(calculated, &my_md5); /* set the final vector */ + + /* Did he use the same random vector + shared secret? */ + if (memcmp(calculated, reply, AUTH_VECTOR_LEN) != 0) return FALSE; + + if (!message_authenticator) return TRUE; /* - * This next bit is necessary because of a bug in the original Livingston - * RADIUS server. The authentication vector is *supposed* to be MD5'd - * with the old password (as the secret) for password changes. - * However, the old password isn't used. The "authentication" vector - * for the server reply packet is simply the MD5 of the reply packet. - * Odd, the code is 99% there, but the old password is never copied - * to the secret! + * RFC2869 Section 5.14. + * + * Message-Authenticator is calculated with the Request + * Authenticator (copied into the packet above), and with + * the Message-Authenticator attribute contents set to + * zero. */ - if (*secret) { - MD5Update(&my_md5, (unsigned char *)secret, strlen(secret)); - } + memcpy(reply, message_authenticator, AUTH_VECTOR_LEN); + memset(message_authenticator, 0, AUTH_VECTOR_LEN); - MD5Final(calculated, &my_md5); /* set the final vector */ + hmac_md5(calculated, (uint8_t *) response, ntohs(response->length), (const uint8_t *) server->secret, secret_len); + + if (memcmp(calculated, reply, AUTH_VECTOR_LEN) != 0) return FALSE; - /* Did he use the same random vector + shared secret? */ - if (memcmp(calculated, reply, AUTH_VECTOR_LEN) != 0) { - return FALSE; - } return TRUE; } @@ -969,10 +1008,25 @@ static void build_radius_packet(AUTH_HDR * request, CONST char *user, hostname[0] = '\0'; gethostname(hostname, sizeof(hostname) - 1); - request->length = htons(AUTH_HDR_LEN); + /* + * For Access-Request, create a random authentication + * vector, and always add a Message-Authenticator + * attribute. + */ + if (request->code == PW_AUTHENTICATION_REQUEST) { + uint8_t *attr = (uint8_t *) request + AUTH_HDR_LEN; - if (password) { /* make a random authentication req vector */ - get_random_vector(request->vector); + get_random_vector(request->vector); + + attr[0] = PW_MESSAGE_AUTHENTICATOR; + attr[1] = 18; + memset(attr + 2, 0, AUTH_VECTOR_LEN); + conf->message_authenticator = attr + 2; + + request->length = htons(AUTH_HDR_LEN + 18); + } else { + request->length = htons(AUTH_HDR_LEN); + conf->message_authenticator = NULL; } add_attribute(request, PW_USER_NAME, (unsigned char *)user, @@ -1065,7 +1119,23 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, /* clear the response */ memset(response, 0, sizeof(AUTH_HDR)); - if (!password) { /* make an RFC 2139 p6 request authenticator */ + /* only look up IP information as necessary */ + retval = host2server(pamh, server); + if (retval != 0) { + _pam_log(pamh, LOG_ERR, + "Failed looking up IP address for RADIUS server %s (error=%s)", + server->hostname, gai_strerror(retval)); + ok = FALSE; + goto next; /* skip to the next server */ + } + + if (request->code == PW_AUTHENTICATION_REQUEST) { + memset(conf->message_authenticator, 0, AUTH_VECTOR_LEN); + hmac_md5(conf->message_authenticator, (uint8_t *) request, ntohs(request->length), + (const uint8_t *) server->secret, strlen(server->secret)); + + } else { + /* make an RFC 2139 p6 request authenticator */ get_accounting_vector(request, server); } @@ -1172,8 +1242,6 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, /* there's data, see if it's valid */ } else { - char *p = server->secret; - if ((ntohs(response->length) != total_length) || (ntohs(response->length) > @@ -1187,32 +1255,8 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, break; } - /* Check if we have the data OK. - * We should also check request->id */ - if (password) { - if (old_password) { -#ifdef LIVINGSTON_PASSWORD_VERIFY_BUG_FIXED - /* what it should be */ - p = old_password; -#else - /* what it really is */ - p = ""; -#endif - } - /* - * RFC 2139 p.6 says not do do this, but - * the Livingston 1.16 server disagrees. - * If the user says he wants the bug, - * give in. - */ - } else { /* authentication request */ - if (conf->accounting_bug) { - p = ""; - } - } - if (!verify_packet - (p, response, request)) { + (server, response, request, conf)) { _pam_log(pamh, LOG_ERR, "response from server" " %s failed" diff --git a/src/pam_radius_auth.h b/src/pam_radius_auth.h index b1a3173..5f056c5 100644 --- a/src/pam_radius_auth.h +++ b/src/pam_radius_auth.h @@ -7,6 +7,7 @@ #include <errno.h> #include <sys/time.h> #include <sys/types.h> +#include <stdint.h> #include <sys/stat.h> #include <sys/resource.h> #include <sys/param.h> @@ -155,6 +156,10 @@ typedef struct radius_conf_t { char prompt[MAXPROMPT]; char vrfname[64]; char privusrmap[64]; + int prompt_attribute; + int privilege_level; + int require_message_authenticator; + uint8_t *message_authenticator; } radius_conf_t; void __write_mapfile(pam_handle_t * p, const char *usr, uid_t uid, int priv, diff --git a/src/radius.h b/src/radius.h index 2ee11dc..0fd2731 100644 --- a/src/radius.h +++ b/src/radius.h @@ -119,7 +119,11 @@ typedef struct pw_auth_hdr { #define PW_NAS_PORT_TYPE 61 /* integer */ #define PW_PORT_LIMIT 62 /* integer */ #define PW_LOGIN_LAT_PORT 63 /* string */ -#define PW_PROMPT 64 /* integer */ +#define PW_PROMPT 76 /* integer */ + +#define PW_MESSAGE_AUTHENTICATOR 80 /* octets */ + +#define PW_MANAGEMENT_PRIVILEGE_LEVEL 136 /* integer */ #define PW_NAS_IPV6_ADDRESS 95 /* octets */ @@ -175,58 +179,4 @@ typedef struct pw_auth_hdr { #define PW_STATUS_STOP 2 #define PW_STATUS_ALIVE 3 -/* Default Database File Names */ - -#define RADIUS_DIR "/etc/raddb" -#define RADACCT_DIR "/usr/adm/radacct" - -#define RADIUS_DICTIONARY "dictionary" -#define RADIUS_CLIENTS "clients" -#define RADIUS_USERS "users" -#define RADIUS_HOLD "holdusers" -#define RADIUS_LOG "logfile" - -/* Server data structures */ - -typedef struct dict_attr { - char name[32]; - int value; - int type; - struct dict_attr *next; -} DICT_ATTR; - -typedef struct dict_value { - char attrname[32]; - char name[32]; - int value; - struct dict_value *next; -} DICT_VALUE; - -typedef struct value_pair { - char name[32]; - int attribute; - int type; - uint32_t lvalue; - char strvalue[AUTH_STRING_LEN]; - struct value_pair *next; -} VALUE_PAIR; - -typedef struct auth_req { - uint32_t ipaddr; - uint16_t udp_port; - uint8_t id; - uint8_t code; - uint8_t vector[16]; - uint8_t secret[16]; - VALUE_PAIR *request; - int child_pid; /* Process ID of child */ - uint32_t timestamp; - struct auth_req *next; /* Next active request */ -} AUTH_REQ; - -#define SECONDS_PER_DAY 86400 -#define MAX_REQUEST_TIME 30 -#define CLEANUP_DELAY 5 -#define MAX_REQUESTS 100 - #endif /* RADIUS_H */ |