X-Git-Url: https://git.ozlabs.org/?a=blobdiff_plain;f=pppd%2Fpeap.c;h=d6e5fcff9012edc68f5e902808b44a9164e91a1b;hb=509f04959ad891d7f981f035ed461d51bd1f74b0;hp=e8d1a19cd30b345c37d8462d3089f81fe7b26cc8;hpb=4e2c49755175d05f7f4a3c1c70a42d2eef9d7839;p=ppp.git diff --git a/pppd/peap.c b/pppd/peap.c index e8d1a19..d6e5fcf 100644 --- a/pppd/peap.c +++ b/pppd/peap.c @@ -1,14 +1,36 @@ /* - * Copyright (c) 2011 + * Copyright (c) 2011 Rustam Kovhaev. All rights reserved. + * Copyright (c) 2021 Eivind Næss. All rights reserved. * - * Authors: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * Rustam Kovhaev + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name(s) of the authors of this software must not be used to + * endorse or promote products derived from this software without + * prior written permission. + * + * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * NOTES: * * PEAP has 2 phases, * 1 - Outer EAP, where TLS session gets established - * 2 - Inner EAP, where inside TLS session with EAP MSCHAPV2 auth, or any - * other auth + * 2 - Inner EAP, where inside TLS session with EAP MSCHAPV2 auth, or any other auth * * And so protocols encapsulation looks like this: * Outer EAP -> TLS -> Inner EAP -> MSCHAPV2 @@ -21,6 +43,8 @@ * b) Inner EAP fragmentation * c) Any other auth other than MSCHAPV2 * + * For details on the PEAP protocol, look to Microsoft: + * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-peap */ #include @@ -32,18 +56,26 @@ #include #include #include + #include "pppd.h" #include "eap.h" +#include "tls.h" #include "chap-new.h" #include "chap_ms.h" +#include "mppe.h" #include "peap.h" +#ifdef UNIT_TEST +#define novm(x) +#endif + struct peap_state { SSL_CTX *ctx; SSL *ssl; BIO *in_bio; BIO *out_bio; + int phase; int written, read; u_char *in_buf; u_char *out_buf; @@ -51,21 +83,11 @@ struct peap_state { u_char ipmk[PEAP_TLV_IPMK_LEN]; u_char tk[PEAP_TLV_TK_LEN]; u_char nonce[PEAP_TLV_NONCE_LEN]; -}; - -static struct peap_state *psm; -static int peap_phase; -extern bool tls_verify_cert; -static bool init; - -static void ssl_init() -{ -#if OPENSSL_VERSION_NUMBER < 0x10100000L - SSL_library_init(); - SSL_load_error_strings(); + struct tls_info *info; +#ifdef PPP_WITH_CHAPMS + struct chap_digest_type *chap; #endif - init = 1; -} +}; /* * K = Key, S = Seed, LEN = output length @@ -85,11 +107,11 @@ static void peap_prfplus(u_char *seed, size_t seed_len, u_char *key, size_t key_ size_t max_iter, i, j, k; u_int len; - max_iter = (pfr_len + SHA_HASH_LEN - 1) / SHA_HASH_LEN; - buf = malloc(seed_len + max_iter * SHA_HASH_LEN); + max_iter = (pfr_len + SHA_DIGEST_LENGTH - 1) / SHA_DIGEST_LENGTH; + buf = malloc(seed_len + max_iter * SHA_DIGEST_LENGTH); if (!buf) novm("pfr buffer"); - hash = malloc(pfr_len + SHA_HASH_LEN); + hash = malloc(pfr_len + SHA_DIGEST_LENGTH); if (!hash) novm("hash buffer"); @@ -98,7 +120,7 @@ static void peap_prfplus(u_char *seed, size_t seed_len, u_char *key, size_t key_ k = 0; if (i > 0) - j = SHA_HASH_LEN; + j = SHA_DIGEST_LENGTH; for (k = 0; k < seed_len; k++) buf[j + k] = seed[k]; pos = j + k; @@ -108,17 +130,17 @@ static void peap_prfplus(u_char *seed, size_t seed_len, u_char *key, size_t key_ pos++; buf[pos] = 0x00; pos++; - if (!HMAC(EVP_sha1(), key, key_len, buf, pos, (hash + i * SHA_HASH_LEN), &len)) + if (!HMAC(EVP_sha1(), key, key_len, buf, pos, (hash + i * SHA_DIGEST_LENGTH), &len)) fatal("HMAC() failed"); - for (j = 0; j < SHA_HASH_LEN; j++) - buf[j] = hash[i * SHA_HASH_LEN + j]; + for (j = 0; j < SHA_DIGEST_LENGTH; j++) + buf[j] = hash[i * SHA_DIGEST_LENGTH + j]; } BCOPY(hash, out_buf, pfr_len); free(hash); free(buf); } -static void generate_cmk(u_char *tempkey, u_char *nonce, u_char *tlv_response_out, int client) +static void generate_cmk(u_char *ipmk, u_char *tempkey, u_char *nonce, u_char *tlv_response_out, int client) { const char *label = PEAP_TLV_IPMK_SEED_LABEL; u_char data_tlv[PEAP_TLV_DATA_LEN] = {0}; @@ -129,8 +151,6 @@ static void generate_cmk(u_char *tempkey, u_char *nonce, u_char *tlv_response_ou u_char compound_mac[PEAP_TLV_COMP_MAC_LEN] = {0}; u_int len; - if (debug) - info("PEAP CB: generate compound mac"); /* format outgoing CB TLV response packet */ data_tlv[1] = PEAP_TLV_TYPE; data_tlv[3] = PEAP_TLV_LENGTH_FIELD; @@ -141,15 +161,17 @@ static void generate_cmk(u_char *tempkey, u_char *nonce, u_char *tlv_response_ou BCOPY(nonce, (data_tlv + PEAP_TLV_HEADERLEN), PEAP_TLV_NONCE_LEN); data_tlv[60] = EAPT_PEAP; - BCOPY(mppe_send_key, isk, MPPE_MAX_KEY_LEN); - BCOPY(mppe_recv_key, isk + MPPE_MAX_KEY_LEN, MPPE_MAX_KEY_LEN); +#ifdef PPP_WITH_MPPE + mppe_get_send_key(isk, MPPE_MAX_KEY_LEN); + mppe_get_recv_key(isk + MPPE_MAX_KEY_LEN, MPPE_MAX_KEY_LEN); +#endif BCOPY(label, ipmkseed, strlen(label)); BCOPY(isk, ipmkseed + strlen(label), PEAP_TLV_ISK_LEN); peap_prfplus(ipmkseed, PEAP_TLV_IPMKSEED_LEN, tempkey, PEAP_TLV_TEMPKEY_LEN, buf, PEAP_TLV_CMK_LEN + PEAP_TLV_IPMK_LEN); - BCOPY(buf, psm->ipmk, PEAP_TLV_IPMK_LEN); + BCOPY(buf, ipmk, PEAP_TLV_IPMK_LEN); BCOPY(buf + PEAP_TLV_IPMK_LEN, cmk, PEAP_TLV_CMK_LEN); if (!HMAC(EVP_sha1(), cmk, PEAP_TLV_CMK_LEN, data_tlv, PEAP_TLV_DATA_LEN, compound_mac, &len)) fatal("HMAC() failed"); @@ -158,28 +180,30 @@ static void generate_cmk(u_char *tempkey, u_char *nonce, u_char *tlv_response_ou BCOPY(data_tlv, tlv_response_out, PEAP_TLV_DATA_LEN - 1); } -static void verify_compound_mac(u_char *in_buf) +static void verify_compound_mac(struct peap_state *psm, u_char *in_buf) { u_char nonce[PEAP_TLV_NONCE_LEN] = {0}; u_char out_buf[PEAP_TLV_LEN] = {0}; BCOPY(in_buf, nonce, PEAP_TLV_NONCE_LEN); - generate_cmk(psm->tk, nonce, out_buf, 0); + generate_cmk(psm->ipmk, psm->tk, nonce, out_buf, 0); if (memcmp((in_buf + PEAP_TLV_NONCE_LEN), (out_buf + PEAP_TLV_HEADERLEN + PEAP_TLV_NONCE_LEN), PEAP_TLV_CMK_LEN)) fatal("server's CMK does not match client's CMK, potential MiTM"); } -static void generate_mppe_keys(void) +#ifdef PPP_WITH_MPPE +#define PEAP_MPPE_KEY_LEN 32 + +static void generate_mppe_keys(u_char *ipmk, int client) { const char *label = PEAP_TLV_CSK_SEED_LABEL; u_char csk[PEAP_TLV_CSK_LEN] = {0}; size_t len; - if (debug) - info("PEAP CB: generate mppe keys"); + dbglog("PEAP CB: generate mppe keys"); len = strlen(label); len++; /* CSK requires NULL byte in seed */ - peap_prfplus((u_char *)label, len, psm->ipmk, PEAP_TLV_IPMK_LEN, csk, PEAP_TLV_CSK_LEN); + peap_prfplus((u_char *)label, len, ipmk, PEAP_TLV_IPMK_LEN, csk, PEAP_TLV_CSK_LEN); /* * The first 64 bytes of the CSK are split into two MPPE keys, as follows. @@ -190,19 +214,16 @@ static void generate_mppe_keys(void) * | MS-MPPE-Send-Key | MS-MPPE-Recv-Key | * +-----------------------+------------------------+ */ - BCOPY(csk, mppe_send_key, MPPE_MAX_KEY_LEN); - BCOPY(csk + 32, mppe_recv_key, MPPE_MAX_KEY_LEN); + if (client) { + mppe_set_keys(csk, csk + PEAP_MPPE_KEY_LEN, PEAP_MPPE_KEY_LEN); + } else { + mppe_set_keys(csk + PEAP_MPPE_KEY_LEN, csk, PEAP_MPPE_KEY_LEN); + } } -static void dump(u_char *buf, int len) -{ - int i = 0; +#endif - dbglog("len: %d bytes", len); - for (i = 0; i < len; i++) - printf("%02x ", buf[i]); - printf("\n"); -} +#ifndef UNIT_TEST static void peap_ack(eap_state *esp, u_char id) { @@ -221,6 +242,7 @@ static void peap_ack(eap_state *esp, u_char id) static void peap_response(eap_state *esp, u_char id, u_char *buf, int len) { + struct peap_state *psm = esp->ea_peap; u_char *outp; int peap_len; @@ -230,7 +252,7 @@ static void peap_response(eap_state *esp, u_char id, u_char *buf, int len) PUTCHAR(id, outp); esp->es_client.ea_id = id; - if (peap_phase == PEAP_PHASE_1) + if (psm->phase == PEAP_PHASE_1) peap_len = PEAP_HEADERLEN + PEAP_FRAGMENT_LENGTH_FIELD + len; else peap_len = PEAP_HEADERLEN + len; @@ -238,7 +260,7 @@ static void peap_response(eap_state *esp, u_char id, u_char *buf, int len) PUTSHORT(peap_len, outp); PUTCHAR(EAPT_PEAP, outp); - if (peap_phase == PEAP_PHASE_1) { + if (psm->phase == PEAP_PHASE_1) { PUTCHAR(PEAP_L_FLAG_SET, outp); PUTLONG(len, outp); } else @@ -248,24 +270,20 @@ static void peap_response(eap_state *esp, u_char id, u_char *buf, int len) output(esp->es_unit, outpacket_buf, PPP_HDRLEN + peap_len); } -void do_inner_eap(u_char *in_buf, int in_len, eap_state *esp, int id, - char *rhostname, u_char *out_buf, int *out_len) +void peap_do_inner_eap(u_char *in_buf, int in_len, eap_state *esp, int id, + u_char *out_buf, int *out_len) { - if (debug) - dump(in_buf, in_len); - int used; - u_char *outp; + struct peap_state *psm = esp->ea_peap; + int used = 0; + int typenum; + int secret_len; + char secret[MAXSECRETLEN + 1]; + char rhostname[MAXWORDLEN]; + u_char *outp = out_buf; - used = 0; - outp = out_buf; + dbglog("PEAP: EAP (in): %.*B", in_len, in_buf); - if (*in_buf == EAPT_IDENTITY && in_len == 1) { - PUTCHAR(EAPT_IDENTITY, outp); - used++; - BCOPY(esp->es_client.ea_name, outp, - esp->es_client.ea_namelen); - used += esp->es_client.ea_namelen; - } else if (*(in_buf + EAP_HEADERLEN) == PEAP_CAPABILITIES_TYPE && + if (*(in_buf + EAP_HEADERLEN) == PEAP_CAPABILITIES_TYPE && in_len == (EAP_HEADERLEN + PEAP_CAPABILITIES_LEN)) { /* use original packet as template for response */ BCOPY(in_buf, outp, EAP_HEADERLEN + PEAP_CAPABILITIES_LEN); @@ -274,57 +292,15 @@ void do_inner_eap(u_char *in_buf, int in_len, eap_state *esp, int id, /* change last byte to 0 to disable fragmentation */ *(outp + PEAP_CAPABILITIES_LEN + 1) = 0x00; used = EAP_HEADERLEN + PEAP_CAPABILITIES_LEN; - } else if (*in_buf == EAPT_TLS && in_len == 2) { - /* send NAK to EAP_TLS request */ - PUTCHAR(EAPT_NAK, outp); - used++; - PUTCHAR(EAPT_MSCHAPV2, outp); - used++; - } else if (*in_buf == EAPT_MSCHAPV2 && *(in_buf + 1) == CHAP_CHALLENGE) { - /* MSCHAPV2 auth */ - int secret_len; - char secret[MAXSECRETLEN + 1]; - char *user; - u_char user_len; - u_char response[MS_CHAP2_RESPONSE_LEN]; - u_char auth_response[MS_AUTH_RESPONSE_LENGTH + 1]; - u_char chap_id; - u_char rchallenge[MS_CHAP2_PEER_CHAL_LEN]; - - user = esp->es_client.ea_name; - user_len = esp->es_client.ea_namelen; - chap_id = *(in_buf + 2); - BCOPY((in_buf + 6), rchallenge, MS_CHAP2_PEER_CHAL_LEN); - if (!get_secret(esp->es_unit, esp->es_client.ea_name, - rhostname, secret, &secret_len, 0)) - fatal("Can't read password file"); - /* MSCHAPV2 response */ - ChapMS2(rchallenge, NULL, esp->es_client.ea_name, - secret, secret_len, response, auth_response, MS_CHAP2_AUTHENTICATEE); - PUTCHAR(EAPT_MSCHAPV2, outp); - PUTCHAR(CHAP_RESPONSE, outp); - PUTCHAR(chap_id, outp); - PUTCHAR(0, outp); - PUTCHAR(5 + user_len + MS_CHAP2_RESPONSE_LEN, outp); - PUTCHAR(MS_CHAP2_RESPONSE_LEN, outp) - BCOPY(response, outp, MS_CHAP2_RESPONSE_LEN); - outp = outp + MS_CHAP2_RESPONSE_LEN; - BCOPY(user, outp, user_len); - used = 5 + user_len + MS_CHAP2_RESPONSE_LEN + 1; - } else if (*in_buf == EAPT_MSCHAPV2 && *(in_buf + 1) == CHAP_SUCCESS) { - PUTCHAR(EAPT_MSCHAPV2, outp); - used++; - PUTCHAR(CHAP_SUCCESS, outp); - used++; - auth_peer_success(esp->es_unit, PPP_CHAP, CHAP_MICROSOFT_V2, - esp->es_server.ea_peer, esp->es_server.ea_peerlen); - } else if (*(in_buf + EAP_HEADERLEN + PEAP_TLV_HEADERLEN) == PEAP_TLV_TYPE && + goto done; + } + if (*(in_buf + EAP_HEADERLEN + PEAP_TLV_HEADERLEN) == PEAP_TLV_TYPE && in_len == PEAP_TLV_LEN) { /* PEAP TLV message, do cryptobinding */ SSL_export_keying_material(psm->ssl, psm->tk, PEAP_TLV_TK_LEN, PEAP_TLV_TK_SEED_LABEL, strlen(PEAP_TLV_TK_SEED_LABEL), NULL, 0, 0); /* verify server's CMK */ - verify_compound_mac(in_buf + EAP_HEADERLEN + PEAP_TLV_RESULT_LEN + PEAP_TLV_HEADERLEN); + verify_compound_mac(psm, in_buf + EAP_HEADERLEN + PEAP_TLV_RESULT_LEN + PEAP_TLV_HEADERLEN); /* generate client's CMK with new nonce */ PUTCHAR(EAP_RESPONSE, outp); PUTCHAR(id, outp); @@ -332,26 +308,169 @@ void do_inner_eap(u_char *in_buf, int in_len, eap_state *esp, int id, BCOPY(in_buf + EAP_HEADERLEN, outp, PEAP_TLV_RESULT_LEN); outp = outp + PEAP_TLV_RESULT_LEN; RAND_bytes(psm->nonce, PEAP_TLV_NONCE_LEN); - generate_cmk(psm->tk, psm->nonce, outp, 1); + generate_cmk(psm->ipmk, psm->tk, psm->nonce, outp, 1); +#ifdef PPP_WITH_MPPE /* set mppe keys */ - generate_mppe_keys(); + generate_mppe_keys(psm->ipmk, 1); +#endif used = PEAP_TLV_LEN; - } else { + goto done; + } + + GETCHAR(typenum, in_buf); + in_len--; + + switch (typenum) { + case EAPT_IDENTITY: + /* Respond with our identity to the peer */ + PUTCHAR(EAPT_IDENTITY, outp); + BCOPY(esp->es_client.ea_name, outp, + esp->es_client.ea_namelen); + used += (esp->es_client.ea_namelen + 1); + break; + + case EAPT_TLS: + /* Send NAK to EAP_TLS request */ + PUTCHAR(EAPT_NAK, outp); + PUTCHAR(EAPT_MSCHAPV2, outp); + used += 2; + break; + +#if PPP_WITH_CHAPMS + case EAPT_MSCHAPV2: { + + // Must have at least 4 more bytes to process CHAP header + if (in_len < 4) { + error("PEAP: received invalid MSCHAPv2 packet, too short"); + break; + } + + u_char opcode; + GETCHAR(opcode, in_buf); + + u_char chap_id; + GETCHAR(chap_id, in_buf); + + short mssize; + GETSHORT(mssize, in_buf); + + // Validate the CHAP packet (including header) + if (in_len != mssize) { + error("PEAP: received invalid MSCHAPv2 packet, invalid length"); + break; + } + in_len -= 4; + + switch (opcode) { + case CHAP_CHALLENGE: { + + u_char *challenge = in_buf; // VLEN + VALUE + u_char vsize; + + GETCHAR(vsize, in_buf); + in_len -= 1; + + if (vsize != MS_CHAP2_PEER_CHAL_LEN || in_len < MS_CHAP2_PEER_CHAL_LEN) { + error("PEAP: received invalid MSCHAPv2 packet, invalid value-length: %d", vsize); + goto done; + } + + INCPTR(MS_CHAP2_PEER_CHAL_LEN, in_buf); + in_len -= MS_CHAP2_PEER_CHAL_LEN; + + // Copy the provided remote host name + rhostname[0] = '\0'; + if (in_len > 0) { + if (in_len >= sizeof(rhostname)) { + dbglog("PEAP: trimming really long peer name down"); + in_len = sizeof(rhostname) - 1; + } + BCOPY(in_buf, rhostname, in_len); + rhostname[in_len] = '\0'; + } + + // In case the remote doesn't give us his name, or user explictly specified remotename is config + if (explicit_remote || (remote_name[0] != '\0' && in_len == 0)) + strlcpy(rhostname, remote_name, sizeof(rhostname)); + + // Get the scrert for authenticating ourselves with the specified host + if (get_secret(esp->es_unit, esp->es_client.ea_name, + rhostname, secret, &secret_len, 0)) { + + u_char response[MS_CHAP2_RESPONSE_LEN+1]; + u_char user_len = esp->es_client.ea_namelen; + char *user = esp->es_client.ea_name; + + psm->chap->make_response(response, chap_id, user, + challenge, secret, secret_len, NULL); + + PUTCHAR(EAPT_MSCHAPV2, outp); + PUTCHAR(CHAP_RESPONSE, outp); + PUTCHAR(chap_id, outp); + PUTCHAR(0, outp); + PUTCHAR(5 + user_len + MS_CHAP2_RESPONSE_LEN, outp); + BCOPY(response, outp, MS_CHAP2_RESPONSE_LEN+1); // VLEN + VALUE + INCPTR(MS_CHAP2_RESPONSE_LEN+1, outp); + BCOPY(user, outp, user_len); + used = 5 + user_len + MS_CHAP2_RESPONSE_LEN + 1; + + } else { + dbglog("PEAP: no CHAP secret for auth to %q", rhostname); + PUTCHAR(EAPT_NAK, outp); + ++used; + } + break; + } + case CHAP_SUCCESS: { + + u_char status = CHAP_FAILURE; + if (psm->chap->check_success(chap_id, in_buf, in_len)) { + info("Chap authentication succeeded! %.*v", in_len, in_buf); + status = CHAP_SUCCESS; + } + + PUTCHAR(EAPT_MSCHAPV2, outp); + PUTCHAR(status, outp); + used += 2; + break; + } + case CHAP_FAILURE: { + + psm->chap->handle_failure(in_buf, in_len); + PUTCHAR(EAPT_MSCHAPV2, outp); + PUTCHAR(status, outp); + used += 2; + break; + } + default: + break; + } + break; + } // EAPT_MSCHAPv2 +#endif + default: + /* send compressed EAP NAK for any unknown packet */ PUTCHAR(EAPT_NAK, outp); ++used; } - if (debug) - dump(psm->out_buf, used); +done: + + dbglog("PEAP: EAP (out): %.*B", used, psm->out_buf); *out_len = used; } -void allocate_buffers(void) +int peap_init(struct peap_state **ctx, const char *rhostname) { const SSL_METHOD *method; - psm = malloc(sizeof(*psm)); + if (!ctx) + return -1; + + tls_init(); + + struct peap_state *psm = malloc(sizeof(*psm)); if (!psm) novm("peap psm struct"); psm->in_buf = malloc(TLS_RECORD_MAX_SIZE); @@ -360,20 +479,31 @@ void allocate_buffers(void) psm->out_buf = malloc(TLS_RECORD_MAX_SIZE); if (!psm->out_buf) novm("peap tls buffer"); - method = TLS_method(); + method = tls_method(); if (!method) novm("TLS_method() failed"); psm->ctx = SSL_CTX_new(method); if (!psm->ctx) novm("SSL_CTX_new() failed"); - if (!tls_verify_cert) - SSL_CTX_set_verify(psm->ctx, SSL_VERIFY_NONE, NULL); - else - SSL_CTX_set_verify(psm->ctx, SSL_VERIFY_PEER, NULL); - info("PEAP: SSL certificate validation is %s", tls_verify_cert ? "enabled" : "disabled"); + /* Configure the default options */ + tls_set_opts(psm->ctx); + + /* Configure the max TLS version */ + tls_set_version(psm->ctx, max_tls_version); - SSL_CTX_set_options(psm->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION); + /* Configure the peer certificate callback */ + tls_set_verify(psm->ctx, 5); + + /* Configure CA locations */ + if (tls_set_ca(psm->ctx, ca_path, cacert_file)) { + fatal("Could not set CA verify locations"); + } + + /* Configure CRL check (if any) */ + if (tls_set_crl(psm->ctx, crl_dir, crl_file)) { + fatal("Could not set CRL verify locations"); + } psm->out_bio = BIO_new(BIO_s_mem()); psm->in_bio = BIO_new(BIO_s_mem()); @@ -382,27 +512,49 @@ void allocate_buffers(void) psm->ssl = SSL_new(psm->ctx); SSL_set_bio(psm->ssl, psm->in_bio, psm->out_bio); SSL_set_connect_state(psm->ssl); - peap_phase = PEAP_PHASE_1; + psm->phase = PEAP_PHASE_1; + tls_set_verify_info(psm->ssl, explicit_remote ? rhostname : NULL, NULL, 1, &psm->info); + psm->chap = chap_find_digest(CHAP_MICROSOFT_V2); + *ctx = psm; + return 0; +} + +void peap_finish(struct peap_state **psm) { + + if (psm && *psm) { + struct peap_state *tmp = *psm; + + if (tmp->ssl) + SSL_free(tmp->ssl); + + if (tmp->ctx) + SSL_CTX_free(tmp->ctx); + + if (tmp->info) + tls_free_verify_info(&tmp->info); + + // NOTE: BIO and memory is freed as a part of SSL_free() + + free(*psm); + *psm = NULL; + } } -void peap_process(eap_state *esp, u_char id, u_char *inp, int len, char *rhostname) +int peap_process(eap_state *esp, u_char id, u_char *inp, int len) { int ret; int out_len; - if (!init) - ssl_init(); + struct peap_state *psm = esp->ea_peap; if (esp->es_client.ea_id == id) { info("PEAP: retransmits are not supported.."); - return; + return -1; } switch (*inp) { case PEAP_S_FLAG_SET: - allocate_buffers(); - if (debug) - info("PEAP: S bit is set, starting PEAP phase 1"); + dbglog("PEAP: S bit is set, starting PEAP phase 1"); ret = SSL_do_handshake(psm->ssl); if (ret != 1) { ret = SSL_get_error(psm->ssl, ret); @@ -415,16 +567,14 @@ void peap_process(eap_state *esp, u_char id, u_char *inp, int len, char *rhostna break; case PEAP_LM_FLAG_SET: - if (debug) - info("PEAP TLS: LM bits are set, need to get more TLS fragments"); + dbglog("PEAP TLS: LM bits are set, need to get more TLS fragments"); inp = inp + PEAP_FRAGMENT_LENGTH_FIELD + PEAP_FLAGS_FIELD; psm->written = BIO_write(psm->in_bio, inp, len - PEAP_FRAGMENT_LENGTH_FIELD - PEAP_FLAGS_FIELD); peap_ack(esp, id); break; case PEAP_M_FLAG_SET: - if (debug) - info("PEAP TLS: M bit is set, need to get more TLS fragments"); + dbglog("PEAP TLS: M bit is set, need to get more TLS fragments"); inp = inp + PEAP_FLAGS_FIELD; psm->written = BIO_write(psm->in_bio, inp, len - PEAP_FLAGS_FIELD); peap_ack(esp, id); @@ -433,20 +583,17 @@ void peap_process(eap_state *esp, u_char id, u_char *inp, int len, char *rhostna case PEAP_L_FLAG_SET: case PEAP_NO_FLAGS: if (*inp == PEAP_L_FLAG_SET) { - if (debug) - info("PEAP TLS: L bit is set"); + dbglog("PEAP TLS: L bit is set"); inp = inp + PEAP_FRAGMENT_LENGTH_FIELD + PEAP_FLAGS_FIELD; psm->written = BIO_write(psm->in_bio, inp, len - PEAP_FRAGMENT_LENGTH_FIELD - PEAP_FLAGS_FIELD); } else { - if (debug) - info("PEAP TLS: all bits are off"); + dbglog("PEAP TLS: all bits are off"); inp = inp + PEAP_FLAGS_FIELD; psm->written = BIO_write(psm->in_bio, inp, len - PEAP_FLAGS_FIELD); } - if (peap_phase == PEAP_PHASE_1) { - if (debug) - info("PEAP TLS: continue handshake"); + if (psm->phase == PEAP_PHASE_1) { + dbglog("PEAP TLS: continue handshake"); ret = SSL_do_handshake(psm->ssl); if (ret != 1) { ret = SSL_get_error(psm->ssl, ret); @@ -454,7 +601,7 @@ void peap_process(eap_state *esp, u_char id, u_char *inp, int len, char *rhostna fatal("SSL_do_handshake(): %s", ERR_error_string(ret, NULL)); } if (SSL_is_init_finished(psm->ssl)) - peap_phase = PEAP_PHASE_2; + psm->phase = PEAP_PHASE_2; if (BIO_ctrl_pending(psm->out_bio) == 0) { peap_ack(esp, id); break; @@ -468,12 +615,153 @@ void peap_process(eap_state *esp, u_char id, u_char *inp, int len, char *rhostna psm->read = SSL_read(psm->ssl, psm->in_buf, TLS_RECORD_MAX_SIZE); out_len = TLS_RECORD_MAX_SIZE; - do_inner_eap(psm->in_buf, psm->read, esp, id, rhostname, + peap_do_inner_eap(psm->in_buf, psm->read, esp, id, psm->out_buf, &out_len); - psm->written = SSL_write(psm->ssl, psm->out_buf, out_len); - psm->read = BIO_read(psm->out_bio, psm->out_buf, + if (out_len > 0) { + psm->written = SSL_write(psm->ssl, psm->out_buf, out_len); + psm->read = BIO_read(psm->out_bio, psm->out_buf, TLS_RECORD_MAX_SIZE); - peap_response(esp, id, psm->out_buf, psm->read); + peap_response(esp, id, psm->out_buf, psm->read); + } break; } + return 0; +} + +#else + +u_char outpacket_buf[255]; +int debug = 1; +int error_count = 0; +int unsuccess = 0; + +/** + * Using the example in MS-PEAP, section 4.4.1. + * see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-peap/5308642b-90c9-4cc4-beec-fb367325c0f9 + */ +int test_cmk(u_char *ipmk) { + u_char nonce[PEAP_TLV_NONCE_LEN] = { + 0x6C, 0x6B, 0xA3, 0x87, 0x84, 0x23, 0x74, 0x57, + 0xCC, 0xC9, 0x0B, 0x1A, 0x90, 0x8C, 0xBD, 0xF4, + 0x71, 0x1B, 0x69, 0x99, 0x4D, 0x0C, 0xFE, 0x8D, + 0x3D, 0xB4, 0x4E, 0xCB, 0xCD, 0xAD, 0x37, 0xE9 + }; + + u_char tmpkey[PEAP_TLV_TEMPKEY_LEN] = { + 0x73, 0x8B, 0xB5, 0xF4, 0x62, 0xD5, 0x8E, 0x7E, + 0xD8, 0x44, 0xE1, 0xF0, 0x0D, 0x0E, 0xBE, 0x50, + 0xC5, 0x0A, 0x20, 0x50, 0xDE, 0x11, 0x99, 0x77, + 0x10, 0xD6, 0x5F, 0x45, 0xFB, 0x5F, 0xBA, 0xB7, + 0xE3, 0x18, 0x1E, 0x92, 0x4F, 0x42, 0x97, 0x38, + // 0xDE, 0x40, 0xC8, 0x46, 0xCD, 0xF5, 0x0B, 0xCB, + // 0xF9, 0xCE, 0xDB, 0x1E, 0x85, 0x1D, 0x22, 0x52, + // 0x45, 0x3B, 0xDF, 0x63 + }; + + u_char expected[60] = { + 0x00, 0x0C, 0x00, 0x38, 0x00, 0x00, 0x00, 0x01, + 0x6C, 0x6B, 0xA3, 0x87, 0x84, 0x23, 0x74, 0x57, + 0xCC, 0xC9, 0x0B, 0x1A, 0x90, 0x8C, 0xBD, 0xF4, + 0x71, 0x1B, 0x69, 0x99, 0x4D, 0x0C, 0xFE, 0x8D, + 0x3D, 0xB4, 0x4E, 0xCB, 0xCD, 0xAD, 0x37, 0xE9, + 0x42, 0xE0, 0x86, 0x07, 0x1D, 0x1C, 0x8B, 0x8C, + 0x8E, 0x45, 0x8F, 0x70, 0x21, 0xF0, 0x6A, 0x6E, + 0xAB, 0x16, 0xB6, 0x46 + }; + + u_char inner_mppe_keys[32] = { + 0x67, 0x3E, 0x96, 0x14, 0x01, 0xBE, 0xFB, 0xA5, + 0x60, 0x71, 0x7B, 0x3B, 0x5D, 0xDD, 0x40, 0x38, + 0x65, 0x67, 0xF9, 0xF4, 0x16, 0xFD, 0x3E, 0x9D, + 0xFC, 0x71, 0x16, 0x3B, 0xDF, 0xF2, 0xFA, 0x95 + }; + + u_char response[60] = {}; + + // Set the inner MPPE keys (e.g. from CHAPv2) + mppe_set_keys(inner_mppe_keys, inner_mppe_keys + 16, 16); + + // Generate and compare the response + generate_cmk(ipmk, tmpkey, nonce, response, 1); + if (memcmp(expected, response, sizeof(response)) != 0) { + dbglog("Failed CMK key generation\n"); + dbglog("%.*B", sizeof(response), response); + dbglog("%.*B", sizeof(expected), expected); + return -1; + } + + return 0; +} + +int test_mppe(u_char *ipmk) { + u_char outer_mppe_send_key[MPPE_MAX_KEY_SIZE] = { + 0x6A, 0x02, 0xD7, 0x82, 0x20, 0x1B, 0xC7, 0x13, + 0x8B, 0xF8, 0xEF, 0xF7, 0x33, 0xB4, 0x96, 0x97, + 0x0D, 0x7C, 0xAB, 0x30, 0x0A, 0xC9, 0x57, 0x72, + 0x78, 0xE1, 0xDD, 0xD5, 0xAE, 0xF7, 0x66, 0x97 + }; + + u_char outer_mppe_recv_key[MPPE_MAX_KEY_SIZE] = { + 0x17, 0x52, 0xD4, 0xE5, 0x84, 0xA1, 0xC8, 0x95, + 0x03, 0x9B, 0x4D, 0x05, 0xE3, 0xBC, 0x9A, 0x84, + 0x84, 0xDD, 0xC2, 0xAA, 0x6E, 0x2C, 0xE1, 0x62, + 0x76, 0x5C, 0x40, 0x68, 0xBF, 0xF6, 0x5A, 0x45 + }; + + u_char result[MPPE_MAX_KEY_SIZE]; + int len; + + mppe_clear_keys(); + + generate_mppe_keys(ipmk, 1); + + len = mppe_get_recv_key(result, sizeof(result)); + if (len != sizeof(result)) { + dbglog("Invalid length of resulting MPPE recv key"); + return -1; + } + + if (memcmp(result, outer_mppe_recv_key, len) != 0) { + dbglog("Invalid result for outer mppe recv key"); + return -1; + } + + len = mppe_get_send_key(result, sizeof(result)); + if (len != sizeof(result)) { + dbglog("Invalid length of resulting MPPE send key"); + return -1; + } + + if (memcmp(result, outer_mppe_send_key, len) != 0) { + dbglog("Invalid result for outer mppe send key"); + return -1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + u_char ipmk[PEAP_TLV_IPMK_LEN] = { + 0x3A, 0x91, 0x1C, 0x25, 0x54, 0x73, 0xE8, 0x3E, + 0x9A, 0x0C, 0xC3, 0x33, 0xAE, 0x1F, 0x8A, 0x35, + 0xCD, 0xC7, 0x41, 0x63, 0xE7, 0xF6, 0x0F, 0x6C, + 0x65, 0xEF, 0x71, 0xC2, 0x64, 0x42, 0xAA, 0xAC, + 0xA2, 0xB6, 0xF1, 0xEB, 0x4F, 0x25, 0xEC, 0xA3, + }; + int ret = -1; + + ret = test_cmk(ipmk); + if (ret != 0) { + return -1; + } + + ret = test_mppe(ipmk); + if (ret != 0) { + return -1; + } + + return 0; } + +#endif