From 08ef47ca532294eb428238c831616748940e24a2 Mon Sep 17 00:00:00 2001 From: Deomid Ryabkov Date: Sat, 31 Mar 2012 05:14:23 +0100 Subject: [PATCH] pppd: Make MSCHAP-v2 cope better with packet loss This implements response caching for MSCHAP-v2. It caches our responses and the responses we expect from the peer. MSCHAP-v2 is unusual in that the authenticatee's CHAP-Response contains what is effectively a challenge to the authenticator, and the authenticator's CHAP-Success packet contains a response to that challenge. Having the response cache lets us (a) answer challenges consistently and (b) cope with a CHAP-Success packet that corresponds to one of our CHAP-Responses that wasn't the last one we sent. This solves a problem where MSCHAP-v2 does not handle replay/retry properly. Here's what a typical normal session looks like: Mar 31 02:47:40 nbm pppd[12895]: rcvd [CHAP Challenge id=0x37 <7ac9de47e66fc440e4b142e28c1a2064>, name = "jeeves"] Mar 31 02:47:40 nbm pppd[12895]: sent [CHAP Response id=0x37 <12986c68266e0d60e7e0de9c8326073200000000000000005da37272ed71b6743f65bc00f7ae2ca148db9210627b646500>, name = "murka"] Mar 31 02:47:40 nbm pppd[12895]: rcvd [CHAP Success id=0x37 "S=ED8FB5829C8049C331AAE0C570F63F8B558DEA2C M=Access granted"] Mar 31 02:47:40 nbm pppd[12895]: CHAP authentication succeeded however, this breaks down if, for whatever reason - packet loss, reordering or whatnot - server sends a second challenge that arrives before the response - it changes client's expectation and the authentication fails. Here's how it looks in the logs: Mar 31 02:47:47 nbm pppd[13014]: rcvd [CHAP Challenge id=0x8a <5070251e94455e2155d2cf4d698d23c9>, name = "jeeves"] Mar 31 02:47:47 nbm pppd[13014]: sent [CHAP Response id=0x8a <14d788f835add58b60d2aff362c183160000000000000000d780f3849076e9e013272f67bcb8c8cfa0e9b51c0fe3ee2100>, name = "murka"] Mar 31 02:47:48 nbm pppd[13014]: rcvd [CHAP Challenge id=0x8a <5070251e94455e2155d2cf4d698d23c9>, name = "jeeves"] Mar 31 02:47:48 nbm pppd[13014]: sent [CHAP Response id=0x8a , name = "murka"] Mar 31 02:47:48 nbm pppd[13014]: rcvd [CHAP Success id=0x8a "S=ABAEA4DF5601FADF25F8729455D39BF6D971D501 M=Access granted"] Mar 31 02:47:48 nbm pppd[13014]: MS-CHAPv2 mutual authentication failed. Signed-off-by: Paul Mackerras --- pppd/chap-new.c | 2 +- pppd/chap-new.h | 2 +- pppd/chap_ms.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/pppd/chap-new.c b/pppd/chap-new.c index 1386486..2714bff 100644 --- a/pppd/chap-new.c +++ b/pppd/chap-new.c @@ -498,7 +498,7 @@ chap_handle_status(struct chap_client_state *cs, int code, int id, if (code == CHAP_SUCCESS) { /* used for MS-CHAP v2 mutual auth, yuck */ if (cs->digest->check_success != NULL) { - if (!(*cs->digest->check_success)(pkt, len, cs->priv)) + if (!(*cs->digest->check_success)(id, pkt, len)) code = CHAP_FAILURE; } else msg = "CHAP authentication succeeded"; diff --git a/pppd/chap-new.h b/pppd/chap-new.h index 48235d4..665e78f 100644 --- a/pppd/chap-new.h +++ b/pppd/chap-new.h @@ -105,7 +105,7 @@ struct chap_digest_type { void (*make_response)(unsigned char *response, int id, char *our_name, unsigned char *challenge, char *secret, int secret_len, unsigned char *priv); - int (*check_success)(unsigned char *pkt, int len, unsigned char *priv); + int (*check_success)(int id, unsigned char *pkt, int len); void (*handle_failure)(unsigned char *pkt, int len); struct chap_digest_type *next; diff --git a/pppd/chap_ms.c b/pppd/chap_ms.c index aec1226..016b42e 100644 --- a/pppd/chap_ms.c +++ b/pppd/chap_ms.c @@ -320,25 +320,90 @@ chapms_make_response(unsigned char *response, int id, char *our_name, ChapMS(challenge, secret, secret_len, response); } +struct chapms2_response_cache_entry { + int id; + unsigned char challenge[16]; + unsigned char response[MS_CHAP2_RESPONSE_LEN]; + unsigned char auth_response[MS_AUTH_RESPONSE_LENGTH]; +}; + +#define CHAPMS2_MAX_RESPONSE_CACHE_SIZE 10 +static struct chapms2_response_cache_entry + chapms2_response_cache[CHAPMS2_MAX_RESPONSE_CACHE_SIZE]; +static int chapms2_response_cache_next_index = 0; +static int chapms2_response_cache_size = 0; + +static void +chapms2_add_to_response_cache(int id, unsigned char *challenge, + unsigned char *response, + unsigned char *auth_response) +{ + int i = chapms2_response_cache_next_index; + + chapms2_response_cache[i].id = id; + memcpy(chapms2_response_cache[i].challenge, challenge, 16); + memcpy(chapms2_response_cache[i].response, response, + MS_CHAP2_RESPONSE_LEN); + memcpy(chapms2_response_cache[i].auth_response, + auth_response, MS_AUTH_RESPONSE_LENGTH); + chapms2_response_cache_next_index = + (i + 1) % CHAPMS2_MAX_RESPONSE_CACHE_SIZE; + if (chapms2_response_cache_next_index > chapms2_response_cache_size) + chapms2_response_cache_size = chapms2_response_cache_next_index; + dbglog("added response cache entry %d", i); +} + +static struct chapms2_response_cache_entry* +chapms2_find_in_response_cache(int id, unsigned char *challenge, + unsigned char *auth_response) +{ + int i; + + for (i = 0; i < chapms2_response_cache_size; i++) { + if (id == chapms2_response_cache[i].id + && (!challenge + || memcmp(challenge, + chapms2_response_cache[i].challenge, + 16) == 0) + && (!auth_response + || memcmp(auth_response, + chapms2_response_cache[i].auth_response, + MS_AUTH_RESPONSE_LENGTH) == 0)) { + dbglog("response found in cache (entry %d)", i); + return &chapms2_response_cache[i]; + } + } + return NULL; /* not found */ +} + static void chapms2_make_response(unsigned char *response, int id, char *our_name, unsigned char *challenge, char *secret, int secret_len, unsigned char *private) { + const struct chapms2_response_cache_entry *cache_entry; + unsigned char auth_response[MS_AUTH_RESPONSE_LENGTH]; + challenge++; /* skip length, should be 16 */ *response++ = MS_CHAP2_RESPONSE_LEN; + cache_entry = chapms2_find_in_response_cache(id, challenge, NULL); + if (cache_entry) { + memcpy(response, cache_entry->response, MS_CHAP2_RESPONSE_LEN); + return; + } ChapMS2(challenge, #ifdef DEBUGMPPEKEY mschap2_peer_challenge, #else NULL, #endif - our_name, secret, secret_len, response, private, + our_name, secret, secret_len, response, auth_response, MS_CHAP2_AUTHENTICATEE); + chapms2_add_to_response_cache(id, challenge, response, auth_response); } static int -chapms2_check_success(unsigned char *msg, int len, unsigned char *private) +chapms2_check_success(int id, unsigned char *msg, int len) { if ((len < MS_AUTH_RESPONSE_LENGTH + 2) || strncmp((char *)msg, "S=", 2) != 0) { @@ -349,7 +414,7 @@ chapms2_check_success(unsigned char *msg, int len, unsigned char *private) msg += 2; len -= 2; if (len < MS_AUTH_RESPONSE_LENGTH - || memcmp(msg, private, MS_AUTH_RESPONSE_LENGTH)) { + || !chapms2_find_in_response_cache(id, NULL /* challenge */, msg)) { /* Authenticator Response did not match expected. */ error("MS-CHAPv2 mutual authentication failed."); return 0; -- 2.39.2