/*
* chap.c
*
* Written by Toshiharu OHNO <tony-o@iij.ad.jp>
* Copyright (c) 1993, Internet Initiative Japan, Inc. All rights reserved.
* See ``COPYRIGHT.iij''
*
* Rewritten by Archie Cobbs <archie@freebsd.org>
* Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
* See ``COPYRIGHT.whistle''
*/
#include "ppp.h"
#include "auth.h"
#include "msoft.h"
#include "util.h"
#include <openssl/md5.h>
/*
* INTERNAL FUNCTIONS
*/
static int ChapHash(Link l, int alg, u_char *hash_value, u_char id,
const char *username, const char *secret,
const u_char *challenge, int clen, int local);
static int ChapHashAgree(int alg, const u_char *self, int slen,
const u_char *peer, int plen);
static int ChapParsePkt(const char *pref, const u_char *pkt, const int pkt_len,
char *peer_name, u_char *chap_value,
int *chap_value_size);
static char *ChapGetSecret(Link l, int alg, char *password);
static void ChapGenRandom(Link l, u_char *buf, int len);
/*
* INTERNAL VARIABLES
*/
static const u_char gIdBytes[] = { 0x3b, 0x1e, 0x68 };
/*
* ChapStart()
*/
void
ChapStart(Link l, int which)
{
Auth a = &l->lcp.auth;
ChapInfo chap = &l->lcp.auth.chap;
ChapParams cp = &l->lcp.auth.params.chap;
chap->proto = PROTO_CHAP;
if (l->originate == LINK_ORIGINATE_LOCAL)
a->params.msoft.chap_alg = a->self_to_peer_alg;
else
a->params.msoft.chap_alg = a->peer_to_self_alg;
switch (which)
{
case AUTH_SELF_TO_PEER: /* Just wait for peer's challenge */
break;
case AUTH_PEER_TO_SELF:
/* Invalidate any old challenge data */
cp->chal_len = 0;
/* Initialize retry counter and timer */
chap->next_id = 1;
chap->retry = AUTH_RETRIES;
TimerInit(&chap->chalTimer, "ChalTimer",
l->conf.retry_timeout * SECONDS, ChapChalTimeout, l);
TimerStart(&chap->chalTimer);
/* Send first challenge */
ChapSendChallenge(l);
break;
default:
assert(0);
}
}
/*
* ChapStop()
*/
void
ChapStop(ChapInfo chap)
{
TimerStop(&chap->chalTimer);
TimerStop(&chap->respTimer);
if (chap->resp) {
Freee(chap->resp);
chap->resp = NULL;
}
}
/*
* ChapSendChallenge()
*/
void
ChapSendChallenge(Link l)
{
Auth const a = &l->lcp.auth;
ChapInfo chap = &a->chap;
ChapParams cp = &a->params.chap;
u_char *pkt;
/* don't generate new challenges on re-transmit */
if (cp->chal_len)
goto send_pkt;
/* Put random challenge data in buffer (only once for Microsoft CHAP) */
switch (a->peer_to_self_alg) {
case CHAP_ALG_MSOFT: {
cp->chal_len = CHAP_MSOFT_CHAL_LEN;
ChapGenRandom(l, cp->chal_data, cp->chal_len);
if (l->originate == LINK_ORIGINATE_REMOTE) {
memcpy(a->params.msoft.msChal, cp->chal_data, cp->chal_len);
}
}
break;
case CHAP_ALG_MSOFTv2:
cp->chal_len = CHAP_MSOFTv2_CHAL_LEN;
ChapGenRandom(l, cp->chal_data, cp->chal_len);
if (l->originate == LINK_ORIGINATE_REMOTE) {
memcpy(a->params.msoft.msChal, cp->chal_data, cp->chal_len);
}
break;
case CHAP_ALG_MD5:
cp->chal_len = random() % 32 + 16;
ChapGenRandom(l, cp->chal_data, cp->chal_len);
break;
default:
assert(0);
}
assert(cp->chal_len <= sizeof(cp->chal_data));
send_pkt:
/* Build a challenge packet */
pkt = Malloc(MB_AUTH, 1 + cp->chal_len + strlen(a->conf.authname) + 1);
pkt[0] = cp->chal_len;
memcpy(pkt + 1, cp->chal_data, cp->chal_len);
memcpy(pkt + 1 + cp->chal_len,
a->conf.authname, strlen(a->conf.authname));
/* Send it off */
AuthOutput(l, chap->proto,
chap->proto == PROTO_CHAP ? CHAP_CHALLENGE : EAP_REQUEST,
chap->next_id++, pkt,
1 + cp->chal_len + strlen(a->conf.authname), 0,
EAP_TYPE_MD5CHAL);
Freee(pkt);
}
/*
* ChapSendResponse()
*/
static void
ChapSendResponse(Link l)
{
ChapInfo chap = &l->lcp.auth.chap;
/* Stop response timer */
TimerStop(&chap->respTimer);
/* Send response (possibly again) */
assert(chap->resp);
AuthOutput(l, chap->proto,
chap->proto == PROTO_CHAP ? CHAP_RESPONSE : EAP_RESPONSE,
chap->resp_id, chap->resp, chap->resp_len, 0, EAP_TYPE_MD5CHAL);
/* Start re-send timer (only during authenticate phase where the
authentication timer is still running) */
if (l->lcp.phase == PHASE_AUTHENTICATE) {
TimerInit(&chap->respTimer, "RespTimer",
l->conf.retry_timeout * SECONDS,
(void (*)(void *)) ChapSendResponse, (void *) l);
TimerStart(&chap->respTimer);
}
}
/*
* ChapParsePkt()
*
*/
static int
ChapParsePkt(const char *pref, const u_char *pkt, const int pkt_len,
char *peer_name, u_char *chap_value, int *chap_value_size)
{
int val_len, name_len;
/* Compute and check lengths */
if (pkt == NULL
|| pkt_len < 1
|| (val_len = pkt[0]) < 1
|| val_len > CHAP_MAX_VAL
|| (name_len = (pkt_len - val_len - 1)) < 0
|| name_len > CHAP_MAX_NAME) {
Log(LG_AUTH, ("[%s] Bogus packet", pref));
return(-1);
}
/* Extract stuff */
memcpy(peer_name, pkt + 1 + val_len, name_len);
peer_name[name_len] = 0;
memcpy(chap_value, pkt + 1, val_len);
*chap_value_size = val_len;
Log(LG_AUTH, ("[%s] Name: \"%s\"", pref, peer_name));
#if 0
Log(LG_AUTH, ("[%s] Value: %d bytes", pref, *chap_value_size));
#endif
return(0);
}
/*
* ChapChalTimeout()
*
* Timer expired for reply to challenge packet
*/
void
ChapChalTimeout(void *ptr)
{
Link const l = (Link) ptr;
ChapInfo const chap = &l->lcp.auth.chap;
if (--chap->retry > 0) {
TimerStart(&chap->chalTimer);
ChapSendChallenge(l);
}
}
/*
* ChapInput()
*/
void
ChapInput(Link l, AuthData auth, const u_char *pkt, u_short len)
{
Auth const a = &l->lcp.auth;
ChapInfo const chap = &a->chap;
char peer_name[CHAP_MAX_NAME + 1];
char password[AUTH_MAX_PASSWORD];
u_char hash_value[CHAP_MAX_VAL];
int hash_value_size;
chap->proto = auth->proto;
switch (auth->code) {
case CHAP_CHALLENGE:
{
u_char value[CHAP_MAX_VAL]; /* Chap packet */
int value_len; /* Packet length */
char *name, *secret;
int name_len, idFail;
auth->alg = a->self_to_peer_alg;
/* Check packet */
if ((a->self_to_peer != PROTO_CHAP && a->self_to_peer != PROTO_EAP)
|| l->lcp.phase != PHASE_AUTHENTICATE)
Log(LG_AUTH, ("[%s] CHAP: Not expected, but that's OK", l->name));
if (ChapParsePkt(l->name, pkt, len, peer_name, value, &value_len) < 0)
break;
/* Never respond to our own outstanding challenge */
if (value_len == auth->params.chap.chal_len
&& !memcmp(value, auth->params.chap.chal_data, auth->params.chap.chal_len)) {
Log(LG_AUTH, ("[%s] CHAP: SECURITY: peer sent same challenge! Ignoring.", l->name));
break;
}
/* Don't respond to a challenge that looks like it came from
us and has the wrong origination value embedded in it. This
avoids a security hole associated with using the same CHAP
password to authenticate in both directions on a link. */
idFail = 0;
do {
char buf[sizeof(gIdBytes)];
int chalOrig;
/* Check challenge length */
if (value_len < sizeof(buf))
break;
/* Copy challenge bits and extract origination value */
memcpy(buf, value, sizeof(buf));
chalOrig = (buf[0] >> 6) & 0x03;
buf[0] &= 0x3f;
/* Check for same ID bytes in the challenge */
if (memcmp(buf, gIdBytes, sizeof(gIdBytes)) != 0)
break;
/* ID bytes match; origination value must be opposite. Note this
assumes that if we can tell the origination direction of a link,
then so can the peer. */
switch (l->originate) {
case LINK_ORIGINATE_LOCAL:
idFail = (chalOrig != LINK_ORIGINATE_REMOTE);
break;
case LINK_ORIGINATE_REMOTE:
idFail = (chalOrig != LINK_ORIGINATE_LOCAL);
break;
case LINK_ORIGINATE_UNKNOWN:
default:
idFail = 0; /* XXX assumes leased line, etc is secure */
break;
}
/* Log failure */
if (idFail) {
Log(LG_AUTH,
("[%s] CHAP: SECURITY: origination value check failed (%s,%s). Ignoring.",
l->name,
LINK_ORIGINATION(l->originate),
LINK_ORIGINATION(chalOrig)));
break;
}
} while (0);
if (idFail)
break;
/*
* Name we use to authenticate ourselves:
*
* 1. The manually configured authname ("set authname ...")
* 2. The peer's supplied name
*/
if (*auth->conf.authname)
name = auth->conf.authname;
else
name = peer_name;
name_len = strlen(name);
Log(LG_AUTH, ("[%s] CHAP: Using authname \"%s\"", l->name, name));
strlcpy(auth->params.authname, name, sizeof(auth->params.authname));
/* Get the corresponding secret */
if ((strcmp(auth->conf.authname, auth->params.authname) == 0) &&
auth->conf.password[0] != 0) {
strlcpy(password, auth->conf.password, sizeof(password));
} else if (AuthGetData(auth->params.authname, password,
sizeof(password), NULL, NULL) < 0) {
Log(LG_AUTH, ("[%s] CHAP: Warning: no secret for \"%s\" found",
l->name,
auth->params.authname));
break;
}
secret = ChapGetSecret(l, a->self_to_peer_alg, password);
/* Get hash value */
if ((hash_value_size = ChapHash(l, a->self_to_peer_alg, hash_value,
auth->id, name, secret, value, value_len, 1)) < 0) {
Log(LG_AUTH, ("[%s] CHAP: Hash failure", l->name));
break;
}
/* Need to remember MS-CHAP stuff for use with MPPE encryption */
switch (a->self_to_peer_alg) {
case CHAP_ALG_MSOFT:
if (l->originate == LINK_ORIGINATE_LOCAL)
memcpy(a->params.msoft.msChal, value, CHAP_MSOFT_CHAL_LEN);
break;
case CHAP_ALG_MSOFTv2:
if (l->originate == LINK_ORIGINATE_LOCAL) {
memcpy(a->params.msoft.msChal, value, CHAP_MSOFTv2_CHAL_LEN);
memcpy(a->params.msoft.ntResp,
hash_value + offsetof(struct mschapv2value, ntHash),
CHAP_MSOFTv2_RESP_LEN);
}
break;
}
/* Build response packet */
if (chap->resp)
Freee(chap->resp);
chap->resp = Malloc(MB_AUTH, 1 + hash_value_size + name_len);
chap->resp[0] = hash_value_size;
memcpy(&chap->resp[1], hash_value, hash_value_size);
memcpy(&chap->resp[1 + hash_value_size], name, name_len);
chap->resp_len = 1 + hash_value_size + name_len;
chap->resp_id = auth->id;
/* Send response to peer */
ChapSendResponse(l);
}
break;
case CHAP_RESPONSE:
auth->alg = a->peer_to_self_alg;
/* Stop challenge timer */
TimerStop(&chap->chalTimer);
/* Check response */
if ((a->peer_to_self != PROTO_CHAP && a->peer_to_self != PROTO_EAP)
|| l->lcp.phase != PHASE_AUTHENTICATE)
Log(LG_AUTH, ("[%s] CHAP: Not expected, but that's OK", l->name));
if (ChapParsePkt(l->name, pkt, len,
peer_name, auth->params.chap.value, &auth->params.chap.value_len) < 0) {
auth->why_fail = AUTH_FAIL_INVALID_PACKET;
ChapInputFinish(l, auth);
return;
}
/* Strip MS domain if any */
if (!Enabled(&l->conf.options, LINK_CONF_MSDOMAIN))
if (a->peer_to_self_alg == CHAP_ALG_MSOFT
|| a->peer_to_self_alg == CHAP_ALG_MSOFTv2) {
char *s;
if ((s = strrchr(peer_name, '\\')))
memmove(peer_name, s + 1, strlen(s) + 1);
}
strlcpy(auth->params.authname, peer_name, sizeof(auth->params.authname));
auth->finish = ChapInputFinish;
AuthAsyncStart(l, auth);
return;
case CHAP_SUCCESS:
case CHAP_FAILURE:
auth->alg = a->self_to_peer_alg;
/* Stop response timer */
TimerStop(&chap->respTimer);
if (chap->resp) {
Freee(chap->resp);
chap->resp = NULL;
}
/* Appropriate? */
if (a->self_to_peer != PROTO_CHAP
|| l->lcp.phase != PHASE_AUTHENTICATE) {
Log(LG_AUTH, ("[%s] CHAP: Not expected, but that's OK", l->name));
break;
}
/* Log message */
ShowMesg(LG_AUTH, l->name, (char *) pkt, len);
AuthFinish(l, AUTH_SELF_TO_PEER, auth->code == CHAP_SUCCESS);
break;
case CHAP_MS_V1_CHANGE_PW:
Log(LG_AUTH, ("[%s] CHAP: Sorry changing passwords using MS-CHAPv1 is not yet implemented",
l->name));
goto badResponse;
case CHAP_MS_V2_CHANGE_PW:
Log(LG_AUTH, ("[%s] CHAP: Sorry changing passwords using MS-CHAPv2 is not yet implemented",
l->name));
goto badResponse;
default:
Log(LG_AUTH, ("[%s] CHAP: unknown code %d", l->name, auth->code));
break;
}
AuthDataDestroy(auth);
return;
badResponse:
{
char failMesg[64];
auth->why_fail = AUTH_FAIL_NOT_EXPECTED;
AuthFailMsg(auth, failMesg, sizeof(failMesg));
AuthOutput(l, auth->proto, auth->proto == PROTO_CHAP ? CHAP_FAILURE : EAP_FAILURE,
auth->id, (u_char *)failMesg, strlen(failMesg), 0, EAP_TYPE_MD5CHAL);
AuthFinish(l, AUTH_PEER_TO_SELF, FALSE);
AuthDataDestroy(auth);
}
}
/*
* ChapInputFinish()
*
* Possible return point from the asynch auth handler.
*
*/
void
ChapInputFinish(Link l, AuthData auth)
{
Auth a = &l->lcp.auth;
ChapInfo chap = &a->chap;
u_char hash_value[CHAP_MAX_VAL];
int hash_value_size;
char ackMesg[128], *secret;
Log(LG_AUTH, ("[%s] CHAP: Auth return status: %s",
l->name, AuthStatusText(auth->status)));
if (auth->status == AUTH_STATUS_BUSY) {
AuthDataDestroy(auth);
return;
}
if (a->peer_to_self_alg == CHAP_ALG_MSOFTv2 &&
auth->mschapv2resp != NULL) {
strlcpy(ackMesg, auth->mschapv2resp, sizeof(ackMesg));
} else if (auth->reply_message != NULL) {
strlcpy(ackMesg, auth->reply_message, sizeof(ackMesg));
} else {
strlcpy(ackMesg, AUTH_MSG_WELCOME, sizeof(ackMesg));
}
if (auth->status == AUTH_STATUS_FAIL)
goto badResponse;
else if (auth->status == AUTH_STATUS_SUCCESS)
goto goodResponse;
/* Copy in peer challenge for MS-CHAPv2 */
if (a->peer_to_self_alg == CHAP_ALG_MSOFTv2)
memcpy(hash_value, a->params.chap.value, 16);
secret = ChapGetSecret(l, a->peer_to_self_alg, a->params.password);
/* Get expected hash value */
if ((hash_value_size = ChapHash(l, a->peer_to_self_alg, hash_value, auth->id,
a->params.authname, secret, a->params.chap.chal_data, a->params.chap.chal_len,
0)) < 0) {
Log(LG_AUTH, ("[%s] CHAP: Hash failure", l->name));
auth->why_fail = AUTH_FAIL_INVALID_PACKET;
goto badResponse;
}
/* Compare with peer's response */
if (a->params.chap.chal_len == 0
|| !ChapHashAgree(a->peer_to_self_alg, hash_value, hash_value_size,
a->params.chap.value, a->params.chap.value_len)) {
Log(LG_AUTH, ("[%s] CHAP: Invalid response", l->name));
auth->why_fail = AUTH_FAIL_INVALID_LOGIN;
goto badResponse;
}
/* Response is good */
Log(LG_AUTH, ("[%s] CHAP: Response is valid", l->name));
if (a->peer_to_self_alg == CHAP_ALG_MSOFTv2) {
struct mschapv2value *const pv = (struct mschapv2value *)a->params.chap.value;
char hex[41];
u_char authresp[20];
int i;
/* Generate MS-CHAPv2 'authenticator response' */
GenerateAuthenticatorResponse(a->params.msoft.nt_hash, pv->ntHash,
pv->peerChal, a->params.chap.chal_data, a->params.authname, authresp);
for (i = 0; i < 20; i++)
sprintf(hex + (i * 2), "%02X", authresp[i]);
/* If we have reply message, send it. */
if (auth->reply_message != NULL) {
snprintf(ackMesg, sizeof(ackMesg), "S=%s M=%s",
hex, auth->reply_message);
} else
snprintf(ackMesg, sizeof(ackMesg), "S=%s", hex);
}
goodResponse:
#ifdef USE_OPIE
/* make a dummy verify to force an update of the opiekeys database */
if (a->params.authentic == AUTH_CONF_OPIE)
opieverify(&auth->opie.data, a->params.password);
#endif
/* Need to remember MS-CHAP stuff for use with MPPE encryption */
if (l->originate == LINK_ORIGINATE_REMOTE &&
a->peer_to_self_alg == CHAP_ALG_MSOFTv2 &&
!memcmp(a->params.msoft.ntResp, gMsoftZeros, CHAP_MSOFTv2_RESP_LEN)) {
memcpy(a->params.msoft.ntResp,
a->params.chap.value + offsetof(struct mschapv2value, ntHash),
CHAP_MSOFTv2_RESP_LEN);
}
Log(LG_AUTH, ("[%s] CHAP: Reply message: %s", l->name, ackMesg));
AuthOutput(l, chap->proto, chap->proto == PROTO_CHAP ? CHAP_SUCCESS : EAP_SUCCESS,
auth->id, (u_char *)ackMesg, strlen(ackMesg), 0, EAP_TYPE_MD5CHAL);
AuthFinish(l, AUTH_PEER_TO_SELF, TRUE);
AuthDataDestroy(auth);
return;
badResponse:
{
char failMesg[64];
AuthFailMsg(auth, failMesg, sizeof(failMesg));
Log(LG_AUTH, ("[%s] CHAP: Reply message: %s", l->name, failMesg));
AuthOutput(l, chap->proto, chap->proto == PROTO_CHAP ? CHAP_FAILURE : EAP_FAILURE,
auth->id, (u_char *)failMesg, strlen(failMesg), 0, EAP_TYPE_MD5CHAL);
AuthFinish(l, AUTH_PEER_TO_SELF, FALSE);
AuthDataDestroy(auth);
}
}
/*
* ChapGetSecret()
*
* returns either the plaintext pass for CHAP-MD5
* or the NT-Hash for MS-CHAP. Set's credentials for
* MPPE-Key derivation
*/
static char *
ChapGetSecret(Link l, int alg, char *password)
{
Auth a = &l->lcp.auth;
char *pw;
if (alg == CHAP_ALG_MD5)
pw = password;
else {
if (!a->params.msoft.has_nt_hash)
{
NTPasswordHash(password, a->params.msoft.nt_hash);
NTPasswordHashHash(a->params.msoft.nt_hash, a->params.msoft.nt_hash_hash);
LMPasswordHash(password, a->params.msoft.lm_hash);
a->params.msoft.has_nt_hash = TRUE;
a->params.msoft.has_lm_hash = TRUE;
}
pw = (char *) a->params.msoft.nt_hash;
}
return pw;
}
/*
* ChapGenRandom()
*/
static void
ChapGenRandom(Link l, u_char *buf, int len)
{
int k;
/* Prefix with our unique ID plus origination value */
for (k = 0; k < sizeof(gIdBytes) && k < len; k++)
buf[k] = gIdBytes[k];
buf[0] |= (l->originate & 0x03) << 6;
/* Fill the rest with semi-random bytes */
for (; k < len; k++)
buf[k] = random() & 0xff;
}
/*
* ChapHash()
*/
static int
ChapHash(Link l, int alg, u_char *hash_value, u_char id, const char *username,
const char *secret, const u_char *challenge, int clen, int local)
{
int hash_size;
switch (alg) {
case CHAP_ALG_MD5:
{
MD5_CTX md5ctx;
MD5_Init(&md5ctx);
MD5_Update(&md5ctx, &id, 1);
MD5_Update(&md5ctx, secret, strlen(secret));
MD5_Update(&md5ctx, challenge, clen);
MD5_Final(hash_value, &md5ctx);
hash_size = 16;
}
break;
case CHAP_ALG_MSOFT:
{
struct mschapvalue *const val = (struct mschapvalue *) hash_value;
/* We don't generate the LANManager hash because it's too insecure */
memset(val->lmHash, 0, sizeof(val->lmHash));
NTChallengeResponse(challenge, secret, val->ntHash);
val->useNT = 1;
hash_size = 49;
}
break;
case CHAP_ALG_MSOFTv2:
{
struct mschapv2value *const val = (struct mschapv2value *) hash_value;
const char *s;
if ((s = strrchr(username, '\\')) != NULL)
username = s + 1;
if (local) { /* generate reverse 'peer challenge' */
ChapGenRandom(l, val->peerChal, sizeof(val->peerChal));
memset(val->reserved, 0, sizeof(val->reserved));
val->flags = 0; /* rfc2759, paragraph 4 says that flags MUST be zero */
}
GenerateNTResponse(challenge,
val->peerChal, username, secret, val->ntHash);
hash_size = 49;
}
break;
default:
return(-1);
}
/* Done */
return(hash_size);
}
/*
* ChapHashAgree()
*/
static int
ChapHashAgree(int alg, const u_char *self, int slen,
const u_char *peer, int plen)
{
switch (alg)
{
case CHAP_ALG_MD5:
return(slen == plen && !memcmp(self, peer, slen));
case CHAP_ALG_MSOFT:
{
struct mschapvalue *const sv = (struct mschapvalue *) self;
struct mschapvalue *const pv = (struct mschapvalue *) peer;
if (slen != 49 || plen != 49)
return(0);
if (sv->useNT != 1 || pv->useNT != 1)
return(0);
return(!memcmp(&sv->ntHash, &pv->ntHash, sizeof(sv->ntHash)));
}
case CHAP_ALG_MSOFTv2:
{
struct mschapv2value *const sv =(struct mschapv2value *) self;
struct mschapv2value *const pv =(struct mschapv2value *) peer;
if (slen != 49 || plen != 49)
return(0);
return(!memcmp(&sv->ntHash, &pv->ntHash, sizeof(sv->ntHash)));
}
default:
return(0);
}
}
/*
* ChapCode()
*/
const char *
ChapCode(int code, char *buf, size_t len)
{
switch (code) {
case CHAP_CHALLENGE:
strlcpy(buf, "CHALLENGE", len);
break;
case CHAP_RESPONSE:
strlcpy(buf, "RESPONSE", len);
break;
case CHAP_SUCCESS:
strlcpy(buf, "SUCCESS", len);
break;
case CHAP_FAILURE:
strlcpy(buf, "FAILURE", len);
break;
default:
snprintf(buf, len, "code%d", code);
}
return(buf);
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>