/* * ccp_mppc.c * * Written by Archie Cobbs * Copyright (c) 1998-1999 Whistle Communications, Inc. All rights reserved. * See ``COPYRIGHT.whistle'' */ #include "ppp.h" #include "ccp.h" #include "msoft.h" #include "ngfunc.h" #include "bund.h" #include "radius.h" #include #include #include #include /* * This implements both MPPC compression and MPPE encryption. */ /* * DEFINITIONS */ /* #define DEBUG_KEYS */ #ifdef ENCRYPTION_MPPE #define MPPC_SUPPORTED (MPPC_BIT | MPPE_BITS | MPPE_STATELESS) #else #define MPPC_SUPPORTED (MPPC_BIT | MPPE_STATELESS) #endif /* * INTERNAL FUNCTIONS */ static int MppcInit(int dir); static char *MppcDescribe(int xmit); static int MppcSubtractBloat(int size); static void MppcCleanup(int dir); static u_char *MppcBuildConfigReq(u_char *cp); static void MppcDecodeConfigReq(Fsm fp, FsmOption opt, int mode); static Mbuf MppcRecvResetReq(int id, Mbuf bp, int *noAck); static char *MppcDescribeBits(u_int32_t bits); static int MppcNegotiated(int xmit); /* Encryption stuff */ #ifdef ENCRYPTION_MPPE static void MppeInitKey(MppcInfo mppc, int dir); static int MppeGetKeyInfo(char **secretp, u_char **challengep); static void MppeInitKeyv2(MppcInfo mppc, int dir); static int MppeGetKeyInfov2(char **secretp, u_char **responsep); static short MppcEnabledMppeType(short type); static short MppcAcceptableMppeType(short type); #ifdef DEBUG_KEYS static void KeyDebug(const u_char *data, int len, const char *fmt, ...); #define KEYDEBUG(x) KeyDebug x #else #define KEYDEBUG(x) #endif #endif /* ENCRYPTION_MPPE */ /* * GLOBAL VARIABLES */ const struct comptype gCompMppcInfo = { "mppc", CCP_TY_MPPC, MppcInit, NULL, MppcDescribe, MppcSubtractBloat, MppcCleanup, MppcBuildConfigReq, MppcDecodeConfigReq, NULL, MppcRecvResetReq, NULL, MppcNegotiated, }; /* * MppcInit() */ static int MppcInit(int dir) { MppcInfo const mppc = &bund->ccp.mppc; struct ng_mppc_config conf; struct ngm_mkpeer mp; char path[NG_PATHLEN + 1]; const char *mppchook, *ppphook; int mschap; int cmd; /* Which type of MS-CHAP did we do? */ if (bund->links[0]->originate == LINK_ORIGINATE_LOCAL) mschap = lnk->lcp.peer_chap_alg; else mschap = lnk->lcp.want_chap_alg; /* Initialize configuration structure */ memset(&conf, 0, sizeof(conf)); conf.enable = 1; switch (dir) { case COMP_DIR_XMIT: cmd = NGM_MPPC_CONFIG_COMP; ppphook = NG_PPP_HOOK_COMPRESS; mppchook = NG_MPPC_HOOK_COMP; conf.bits = mppc->xmit_bits; #ifdef ENCRYPTION_MPPE if (mschap == CHAP_ALG_MSOFTv2) { MppeInitKeyv2(mppc, dir); } else { MppeInitKey(mppc, dir); } memcpy(conf.startkey, mppc->xmit_key0, sizeof(conf.startkey)); #endif break; case COMP_DIR_RECV: cmd = NGM_MPPC_CONFIG_DECOMP; ppphook = NG_PPP_HOOK_DECOMPRESS; mppchook = NG_MPPC_HOOK_DECOMP; conf.bits = mppc->recv_bits; #ifdef ENCRYPTION_MPPE if (mschap == CHAP_ALG_MSOFTv2) { MppeInitKeyv2(mppc, dir); } else { MppeInitKey(mppc, dir); } memcpy(conf.startkey, mppc->recv_key0, sizeof(conf.startkey)); #endif break; default: assert(0); return(-1); } /* Attach a new MPPC node to the PPP node */ snprintf(mp.type, sizeof(mp.type), "%s", NG_MPPC_NODE_TYPE); snprintf(mp.ourhook, sizeof(mp.ourhook), "%s", ppphook); snprintf(mp.peerhook, sizeof(mp.peerhook), "%s", mppchook); if (NgSendMsg(bund->csock, MPD_HOOK_PPP, NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) { Log(LG_ERR, ("[%s] can't create %s node: %s", bund->name, mp.type, strerror(errno))); return(-1); } /* Configure MPPC node */ snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, ppphook); if (NgSendMsg(bund->csock, path, NGM_MPPC_COOKIE, cmd, &conf, sizeof(conf)) < 0) { Log(LG_ERR, ("[%s] can't config %s node at %s: %s", bund->name, NG_MPPC_NODE_TYPE, path, strerror(errno))); NgFuncDisconnect(MPD_HOOK_PPP, ppphook); return(-1); } /* Done */ return(0); } /* * MppcDescribe() */ static char * MppcDescribe(int dir) { MppcInfo const mppc = &bund->ccp.mppc; switch (dir) { case COMP_DIR_XMIT: return(MppcDescribeBits(mppc->xmit_bits)); case COMP_DIR_RECV: return(MppcDescribeBits(mppc->recv_bits)); default: assert(0); return(NULL); } } /* * MppcSubtractBloat() */ static int MppcSubtractBloat(int size) { /* Account for MPPC header */ size -= 2; /* Account for possible expansion with MPPC compression */ if ((bund->ccp.mppc.xmit_bits & MPPC_BIT) != 0) { int l, h, size0 = size; while (1) { l = MPPC_MAX_BLOWUP(size0); h = MPPC_MAX_BLOWUP(size0 + 1); if (l > size) { size0 -= 20; } else if (h > size) { size = size0; break; } else { size0++; } } } /* Done */ return(size); } /* * MppcNegotiated() */ static int MppcNegotiated(int dir) { MppcInfo const mppc = &bund->ccp.mppc; switch (dir) { case COMP_DIR_XMIT: return(mppc->xmit_bits != 0); case COMP_DIR_RECV: return(mppc->recv_bits != 0); default: assert(0); return(0); } } /* * MppcCleanup() */ static void MppcCleanup(int dir) { const char *ppphook; char path[NG_PATHLEN + 1]; /* Remove node */ switch (dir) { case COMP_DIR_XMIT: ppphook = NG_PPP_HOOK_DECOMPRESS; break; case COMP_DIR_RECV: ppphook = NG_PPP_HOOK_COMPRESS; break; default: assert(0); return; } snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, ppphook); (void)NgFuncShutdownNode(bund, bund->name, path); } /* * MppcBuildConfigReq() */ static u_char * MppcBuildConfigReq(u_char *cp) { CcpState const ccp = &bund->ccp; MppcInfo const mppc = &ccp->mppc; u_int32_t bits = 0; /* Compression */ if (Enabled(&ccp->options, gMppcCompress) && !CCP_PEER_REJECTED(ccp, gMppcCompress)) bits |= MPPC_BIT; #ifdef ENCRYPTION_MPPE /* Encryption */ if (MppcEnabledMppeType(40)) bits |= MPPE_40; #ifndef MPPE_56_UNSUPPORTED if (MppcEnabledMppeType(56)) bits |= MPPE_56; #endif if (MppcEnabledMppeType(128)) bits |= MPPE_128; #endif /* Stateless mode */ if (Enabled(&ccp->options, gMppcStateless) && !CCP_PEER_REJECTED(ccp, gMppcStateless) && bits != 0) bits |= MPPE_STATELESS; /* Ship it */ mppc->recv_bits = bits; if (bits != 0) cp = FsmConfValue(cp, CCP_TY_MPPC, -4, &bits); return(cp); } /* * MppcDecodeConfigReq() */ static void MppcDecodeConfigReq(Fsm fp, FsmOption opt, int mode) { CcpState const ccp = &bund->ccp; MppcInfo const mppc = &ccp->mppc; u_int32_t orig_bits; u_int32_t bits; /* Get bits */ memcpy(&orig_bits, opt->data, 4); orig_bits = ntohl(orig_bits); bits = orig_bits; /* Sanity check */ if (opt->len != 6) { Log(LG_CCP, (" bogus length %d", opt->len)); if (mode == MODE_REQ) FsmRej(fp, opt); return; } /* Display it */ Log(LG_CCP, (" 0x%08x:%s", bits, MppcDescribeBits(bits))); /* Deal with it */ switch (mode) { case MODE_REQ: /* Check for supported bits */ if (bits & ~MPPC_SUPPORTED) { Log(LG_CCP, (" Bits 0x%08x not supported", bits & ~MPPC_SUPPORTED)); bits &= MPPC_SUPPORTED; } /* Check compression */ if ((bits & MPPC_BIT) && !Acceptable(&ccp->options, gMppcCompress)) bits &= ~MPPC_BIT; #ifdef ENCRYPTION_MPPE /* Check encryption */ if ((bits & MPPE_40) && !MppcAcceptableMppeType(40)) bits &= ~MPPE_40; #ifndef MPPE_56_UNSUPPORTED if ((bits & MPPE_56) && !MppcAcceptableMppeType(56)) #endif bits &= ~MPPE_56; if ((bits & MPPE_128) && !MppcAcceptableMppeType(128)) bits &= ~MPPE_128; /* Choose the strongest encryption available */ if (bits & MPPE_128) bits &= ~(MPPE_40|MPPE_56); else if (bits & MPPE_56) bits &= ~(MPPE_40|MPPE_128); else if (bits & MPPE_40) bits &= ~(MPPE_56|MPPE_128); /* It doesn't really make sense to encrypt in only one direction. Also, Win95/98 PPTP can't handle uni-directional encryption. So if the remote side doesn't request encryption, try to prompt it. This is broken wrt. normal PPP negotiation: typical Microsoft. */ if ((bits & MPPE_BITS) == 0) { if (MppcEnabledMppeType(40)) bits |= MPPE_40; #ifndef MPPE_56_UNSUPPORTED if (MppcEnabledMppeType(56)) bits |= MPPE_56; #endif if (MppcEnabledMppeType(128)) bits |= MPPE_128; } #endif /* Stateless mode */ if ((bits & MPPE_STATELESS) && !Acceptable(&ccp->options, gMppcStateless)) bits &= ~MPPE_STATELESS; /* See if what we want equals what was sent */ mppc->xmit_bits = bits; if (bits != orig_bits) { bits = htonl(bits); memcpy(opt->data, &bits, 4); FsmNak(fp, opt); } else FsmAck(fp, opt); break; case MODE_NAK: if (!(bits & MPPC_BIT)) CCP_PEER_REJ(ccp, gMppcCompress); #ifdef ENCRYPTION_MPPE if (!(bits & MPPE_40)) CCP_PEER_REJ(ccp, gMppe40); if (!(bits & MPPE_56)) CCP_PEER_REJ(ccp, gMppe56); if (!(bits & MPPE_128)) CCP_PEER_REJ(ccp, gMppe128); #endif if (!(bits & MPPE_STATELESS)) CCP_PEER_REJ(ccp, gMppcStateless); break; } } /* * MppcRecvResetReq() */ static Mbuf MppcRecvResetReq(int id, Mbuf bp, int *noAck) { char path[NG_PATHLEN + 1]; /* Forward ResetReq to the MPPC compression node */ snprintf(path, sizeof(path), "%s.%s", MPD_HOOK_PPP, NG_PPP_HOOK_COMPRESS); if (NgSendMsg(bund->csock, path, NGM_MPPC_COOKIE, NGM_MPPC_RESETREQ, NULL, 0) < 0) { Log(LG_ERR, ("[%s] reset-req to %s node: %s", bund->name, NG_MPPC_NODE_TYPE, strerror(errno))); } /* No ResetAck required for MPPC */ if (noAck) *noAck = 1; return(NULL); } /* * MppcDescribeBits() */ static char * MppcDescribeBits(u_int32_t bits) { static char buf[100]; *buf = 0; if (bits & MPPC_BIT) snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " MPPC"); if (bits & MPPE_BITS) { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " MPPE"); if (bits & MPPE_40) snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ", 40 bit"); if (bits & MPPE_56) snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ", 56 bit"); if (bits & MPPE_128) snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ", 128 bit"); if (bits & MPPE_STATELESS) snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ", stateless"); } return(buf); } static short MppcEnabledMppeType(short type) { CcpState const ccp = &bund->ccp; struct radius *rad = &bund->radius; short ret, radius = FALSE; switch (type) { case 40: if (Enabled(&ccp->options, gCcpRadius) && rad->valid) { radius = TRUE; ret = (rad->mppe.types & MPPE_TYPE_40BIT) && !CCP_PEER_REJECTED(ccp, gMppe40); } else { ret = Enabled(&ccp->options, gMppe40) && !CCP_PEER_REJECTED(ccp, gMppe40); } break; #ifndef MPPE_56_UNSUPPORTED case 56: if (Enabled(&ccp->options, gCcpRadius) && rad->valid) { radius = TRUE; ret = (rad->mppe.types & MPPE_TYPE_56BIT) && !CCP_PEER_REJECTED(ccp, gMppe56); } else { ret = Enabled(&ccp->options, gMppe56) && !CCP_PEER_REJECTED(ccp, gMppe56); } break; #endif case 128: default: if (Enabled(&ccp->options, gCcpRadius) && rad->valid) { radius = TRUE; ret = (rad->mppe.types & MPPE_TYPE_128BIT) && !CCP_PEER_REJECTED(ccp, gMppe128); } else { ret = Enabled(&ccp->options, gMppe128) && !CCP_PEER_REJECTED(ccp, gMppe128); } } Log(LG_CCP, ("[%s] CCP: Checking whether %d bits are enabled -> %s%s", lnk->name, type, ret ? "yes" : "no", radius ? " (RADIUS)" : "" )); return ret; } static short MppcAcceptableMppeType(short type) { CcpState const ccp = &bund->ccp; struct radius *rad = &bund->radius; short ret, radius = FALSE; switch (type) { case 40: if (Enabled(&ccp->options, gCcpRadius) && rad->valid) { radius = TRUE; ret = rad->mppe.types & MPPE_TYPE_40BIT; } else { ret = Acceptable(&ccp->options, gMppe40); } break; #ifndef MPPE_56_UNSUPPORTED case 56: if (Enabled(&ccp->options, gCcpRadius) && rad->valid) { radius = TRUE; ret = rad->mppe.types & MPPE_TYPE_56BIT; } else { ret = Acceptable(&ccp->options, gMppe56); } break; #endif case 128: default: if (Enabled(&ccp->options, gCcpRadius) && rad->valid) { radius = TRUE; ret = rad->mppe.types & MPPE_TYPE_128BIT; } else { ret = Acceptable(&ccp->options, gMppe128); } } Log(LG_CCP, ("[%s] CCP: Checking whether %d bits are acceptable -> %s%s", lnk->name, type, ret ? "yes" : "no", radius ? " (RADIUS)" : "" )); return ret; } #ifdef ENCRYPTION_MPPE #define KEYLEN(b) (((b) & MPPE_128) ? 16 : 8) /* * MppeInitKey() */ static void MppeInitKey(MppcInfo mppc, int dir) { u_int32_t const bits = (dir == COMP_DIR_XMIT) ? mppc->xmit_bits : mppc->recv_bits; u_char *const key0 = (dir == COMP_DIR_XMIT) ? mppc->xmit_key0 : mppc->recv_key0; u_char hash[16]; char *pass; u_char *chal; /* Get credential info */ if (MppeGetKeyInfo(&pass, &chal) < 0) return; /* Compute basis for the session key (ie, "start key" or key0) */ if (bits & MPPE_128) { MD4_CTX c; if (bund->radius.valid) memcpy(hash, bund->radius.mppe.nt_hash, sizeof(hash)); else { NTPasswordHash(pass, hash); KEYDEBUG((hash, sizeof(hash), "NTPasswordHash")); MD4Init(&c); MD4Update(&c, hash, 16); MD4Final(hash, &c); KEYDEBUG((hash, sizeof(hash), "MD4 of that")); KEYDEBUG((chal, CHAP_MSOFT_CHAL_LEN, "Challenge")); } MsoftGetStartKey(chal, hash); KEYDEBUG((hash, sizeof(hash), "NT StartKey")); } else { if (bund->radius.valid) memcpy(hash, bund->radius.mppe.lm_key, 8); else LMPasswordHash(pass, hash); KEYDEBUG((hash, sizeof(hash), "LM StartKey")); } memcpy(key0, hash, MPPE_KEY_LEN); KEYDEBUG((key0, (bits & MPPE_128) ? 16 : 8, "InitialKey")); } /* * MppeGetKeyInfo() * * This is described in: * draft-ietf-pppext-mschapv1-keys-00.txt */ static int MppeGetKeyInfo(char **secretp, u_char **challengep) { CcpState const ccp = &bund->ccp; u_char *challenge; /* The secret comes from the originating caller's credentials */ switch (lnk->originate) { case LINK_ORIGINATE_LOCAL: if (lnk->lcp.peer_auth != PROTO_CHAP || (lnk->lcp.peer_chap_alg != CHAP_ALG_MSOFT && lnk->lcp.peer_chap_alg != CHAP_ALG_MSOFTv2)) { Log(LG_ERR, ("[%s] \"%s chap\" required for MPPE", lnk->name, "accept")); goto fail; } challenge = bund->peer_msChal; break; case LINK_ORIGINATE_REMOTE: if (lnk->lcp.want_auth != PROTO_CHAP || (lnk->lcp.want_chap_alg != CHAP_ALG_MSOFT && lnk->lcp.want_chap_alg != CHAP_ALG_MSOFTv2)) { Log(LG_ERR, ("[%s] \"%s chap\" required for MPPE", lnk->name, "enable")); goto fail; } challenge = bund->self_msChal; break; case LINK_ORIGINATE_UNKNOWN: default: Log(LG_ERR, ("[%s] can't determine link direction for MPPE", lnk->name)); goto fail; } /* Return info */ *secretp = bund->msPassword; *challengep = challenge; return(0); fail: Log(LG_ERR, ("[%s] can't determine credentials for MPPE", bund->name)); FsmFailure(&ccp->fsm, FAIL_CANT_ENCRYPT); return(-1); } /* * MppeInitKeyv2() */ static void MppeInitKeyv2(MppcInfo mppc, int dir) { u_char *const key0 = (dir == COMP_DIR_XMIT) ? mppc->xmit_key0 : mppc->recv_key0; u_char hash[16]; char *pass; u_char *resp; MD4_CTX c; /* If using RADIUS, key info comes from the server */ if (bund->radius.valid) { if (dir == COMP_DIR_XMIT) { memcpy(mppc->xmit_key0, bund->radius.mppe.sendkey, MPPE_KEY_LEN); } else { memcpy(mppc->recv_key0, bund->radius.mppe.recvkey, MPPE_KEY_LEN); } return; } /* Get credential info */ if (MppeGetKeyInfov2(&pass, &resp) < 0) return; /* Compute basis for the session key (ie, "start key" or key0) */ NTPasswordHash(pass, hash); KEYDEBUG((hash, sizeof(hash), "NTPasswordHash")); MD4Init(&c); MD4Update(&c, hash, 16); MD4Final(hash, &c); KEYDEBUG((hash, sizeof(hash), "MD4 of that")); KEYDEBUG((resp, CHAP_MSOFTv2_CHAL_LEN, "Response")); MsoftGetMasterKey(resp, hash); KEYDEBUG((hash, sizeof(hash), "GetMasterKey")); MsoftGetAsymetricStartKey(hash, (dir == COMP_DIR_RECV) ^ (bund->links[0]->originate == LINK_ORIGINATE_LOCAL)); KEYDEBUG((hash, sizeof(hash), "GetAsymmetricKey")); memcpy(key0, hash, MPPE_KEY_LEN); KEYDEBUG((key0, MPPE_KEY_LEN, "InitialKey")); } /* * MppeGetKeyInfov2() */ static int MppeGetKeyInfov2(char **secretp, u_char **responsep) { CcpState const ccp = &bund->ccp; u_char *response; /* The secret comes from the originating caller's credentials */ switch (lnk->originate) { case LINK_ORIGINATE_LOCAL: if (lnk->lcp.peer_auth != PROTO_CHAP || (lnk->lcp.peer_chap_alg != CHAP_ALG_MSOFT && lnk->lcp.peer_chap_alg != CHAP_ALG_MSOFTv2)) { Log(LG_ERR, ("[%s] \"%s chap\" required for MPPE", lnk->name, "accept")); goto fail; } response = bund->self_ntResp; break; case LINK_ORIGINATE_REMOTE: if (lnk->lcp.want_auth != PROTO_CHAP || (lnk->lcp.want_chap_alg != CHAP_ALG_MSOFT && lnk->lcp.want_chap_alg != CHAP_ALG_MSOFTv2)) { Log(LG_ERR, ("[%s] \"%s chap\" required for MPPE", lnk->name, "enable")); goto fail; } response = bund->peer_ntResp; break; case LINK_ORIGINATE_UNKNOWN: default: Log(LG_ERR, ("[%s] can't determine link direction for MPPE", lnk->name)); goto fail; } /* Return info */ *secretp = bund->msPassword; *responsep = response; return(0); fail: Log(LG_ERR, ("[%s] can't determine credentials for MPPE", bund->name)); FsmFailure(&ccp->fsm, FAIL_CANT_ENCRYPT); return(-1); } #endif /* ENCRYPTION_MPPE */ #ifdef DEBUG_KEYS /* * KeyDebug() */ static void KeyDebug(const u_char *data, int len, const char *fmt, ...) { char buf[100]; int k; va_list args; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ":"); for (k = 0; k < len; k++) { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %02x", (u_char) data[k]); } Log(LG_ERR, ("%s", buf)); } #endif /* DEBUG_KEYS */