diff --git a/src/Cedar/Account.c b/src/Cedar/Account.c index c780d0f7..ba760617 100644 --- a/src/Cedar/Account.c +++ b/src/Cedar/Account.c @@ -897,6 +897,35 @@ USER *AcGetUser(HUB *h, char *name) return u; } +USER* AcGetUserByCert(HUB *h, X *cert) +{ + int i; + + if (cert == NULL) + { + return NULL; + } + + for (i = 0; i < LIST_NUM(h->HubDb->UserList); i++) + { + USER* u = LIST_DATA(h->HubDb->UserList, i); + if (u->AuthType == AUTHTYPE_USERCERT) + { + X* ucert = ((AUTHUSERCERT*)u->AuthData)->UserX; + if (ucert != NULL) + { + if (CompareX(cert, ucert)) + { + AddRef(u->ref); + return u; + } + } + } + } + + return NULL; +} + // Delete the user bool AcDeleteUser(HUB *h, char *name) { diff --git a/src/Cedar/Account.h b/src/Cedar/Account.h index ef66cf9d..a09f367c 100644 --- a/src/Cedar/Account.h +++ b/src/Cedar/Account.h @@ -177,6 +177,7 @@ void FreeAuthData(UINT authtype, void *authdata); bool AcAddUser(HUB *h, USER *u); bool AcAddGroup(HUB *h, USERGROUP *g); USER *AcGetUser(HUB *h, char *name); +USER* AcGetUserByCert(HUB* h, X *cert); USERGROUP *AcGetGroup(HUB *h, char *name); bool AcIsUser(HUB *h, char *name); bool AcIsGroup(HUB *h, char *name); diff --git a/src/Cedar/Hub.c b/src/Cedar/Hub.c index 61a5b4b3..f0006a6c 100644 --- a/src/Cedar/Hub.c +++ b/src/Cedar/Hub.c @@ -616,6 +616,7 @@ void DataToHubOptionStruct(HUB_OPTION *o, RPC_ADMIN_OPTION *ao) GetHubAdminOptionDataAndSet(ao, "NoPhysicalIPOnPacketLog", o->NoPhysicalIPOnPacketLog); GetHubAdminOptionDataAndSet(ao, "UseHubNameAsDhcpUserClassOption", o->UseHubNameAsDhcpUserClassOption); GetHubAdminOptionDataAndSet(ao, "UseHubNameAsRadiusNasId", o->UseHubNameAsRadiusNasId); + GetHubAdminOptionDataAndSet(ao, "AllowEapMatchUserByCert", o->AllowEapMatchUserByCert); } // Convert the contents of the HUB_OPTION to data @@ -690,6 +691,7 @@ void HubOptionStructToData(RPC_ADMIN_OPTION *ao, HUB_OPTION *o, char *hub_name) Add(aol, NewAdminOption("NoPhysicalIPOnPacketLog", o->NoPhysicalIPOnPacketLog)); Add(aol, NewAdminOption("UseHubNameAsDhcpUserClassOption", o->UseHubNameAsDhcpUserClassOption)); Add(aol, NewAdminOption("UseHubNameAsRadiusNasId", o->UseHubNameAsRadiusNasId)); + Add(aol, NewAdminOption("AllowEapMatchUserByCert", o->AllowEapMatchUserByCert)); Zero(ao, sizeof(RPC_ADMIN_OPTION)); diff --git a/src/Cedar/Hub.h b/src/Cedar/Hub.h index f310cf6b..d63f118f 100644 --- a/src/Cedar/Hub.h +++ b/src/Cedar/Hub.h @@ -182,6 +182,7 @@ struct HUB_OPTION bool NoPhysicalIPOnPacketLog; // Disable saving physical IP address on the packet log bool UseHubNameAsDhcpUserClassOption; // Add HubName to DHCP request as User-Class option bool UseHubNameAsRadiusNasId; // Add HubName to Radius request as NAS-Identifier attrioption + bool AllowEapMatchUserByCert; // Allow matching EAP Identity with user certificate CNs }; // MAC table entry diff --git a/src/Cedar/Proto_PPP.c b/src/Cedar/Proto_PPP.c index 1e58b7ef..542bafb8 100644 --- a/src/Cedar/Proto_PPP.c +++ b/src/Cedar/Proto_PPP.c @@ -13,7 +13,6 @@ #include "Hub.h" #include "IPC.h" #include "Logging.h" -#include "Proto_IPsec.h" #include "Radius.h" #include "Server.h" @@ -599,6 +598,9 @@ THREAD *NewPPPSession(CEDAR *cedar, IP *client_ip, UINT client_port, IP *server_ p->AuthProtocol = PPP_UNSPECIFIED; p->MsChapV2_ErrorCode = 691; p->EapClient = NULL; + Zero(&p->Eap_Identity, sizeof(p->Eap_Identity)); + p->Eap_TlsCtx.DisableTls13 = false; + p->Eap_TlsCtx.Tls13SessionTicketsCount = 2; // Default count as per hardcoded in OpenSSL p->DataTimeout = PPP_DATA_TIMEOUT; p->PacketRecvTimeout = PPP_PACKET_RECV_TIMEOUT; @@ -1263,12 +1265,12 @@ bool PPPProcessEAPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req UINT64 offer = 0; PPP_LCP *c; UCHAR ms_chap_v2_code[3]; - ETHERIP_ID d; char username[MAX_SIZE]; char hubname[MAX_SIZE]; HUB *hub; bool found = false; UINT authtype; + UCHAR eapidentitypkt[MAX_SIZE] = { 0 }; WRITE_USHORT(ms_chap_v2_code, PPP_LCP_AUTH_CHAP); ms_chap_v2_code[2] = PPP_CHAP_ALG_MS_CHAP_V2; @@ -1277,24 +1279,23 @@ bool PPPProcessEAPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req { case PPP_EAP_TYPE_IDENTITY: // Parse username - Copy(p->Eap_Identity, eap_packet->Data, MIN(MAX_SIZE, eap_datasize)); - Zero(&d, sizeof(d)); - PPPParseUsername(p->Cedar, p->Eap_Identity, &d); - StrCpy(username, sizeof(username), d.UserName); - StrCpy(hubname, sizeof(hubname), d.HubName); - Debug("EAP: username=%s, hubname=%s\n", username, hubname); + Copy(eapidentitypkt, eap_packet->Data, MIN(MAX_SIZE, eap_datasize)); + + Zero(&p->Eap_Identity, sizeof(p->Eap_Identity)); + PPPParseUsername(p->Cedar, eapidentitypkt, &p->Eap_Identity); + Debug("EAP: username=%s, hubname=%s\n", p->Eap_Identity.UserName, p->Eap_Identity.HubName); // Locate user LockHubList(p->Cedar); { - hub = GetHub(p->Cedar, hubname); + hub = GetHub(p->Cedar, p->Eap_Identity.HubName); } UnlockHubList(p->Cedar); if (hub != NULL) { AcLock(hub); { - USER *user = AcGetUser(hub, username); + USER *user = AcGetUser(hub, p->Eap_Identity.UserName); if (user == NULL) { user = AcGetUser(hub, "*"); @@ -1305,12 +1306,18 @@ bool PPPProcessEAPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req authtype = user->AuthType; ReleaseUser(user); } + else if (hub->Option->AllowEapMatchUserByCert == true) + { + authtype = AUTHTYPE_USERCERT; + Zero(p->Eap_Identity.UserName, sizeof(p->Eap_Identity.UserName)); + p->Eap_MatchUserByCert = true; + } } AcUnlock(hub); ReleaseHub(hub); } - if (found == false) + if (found == false && p->Eap_MatchUserByCert == false) { // User not found, fail immediately PPP_PACKET *pack = ZeroMalloc(sizeof(PPP_PACKET)); @@ -1375,7 +1382,7 @@ bool PPPProcessEAPResponsePacket(PPP_SESSION *p, PPP_PACKET *pp, PPP_PACKET *req // Basically this is just an acknoweldgment that the notification was accepted by the client. Nothing to do here... break; case PPP_EAP_TYPE_NAK: - if (p->Eap_Protocol == PPP_EAP_TYPE_TLS) + if (p->Eap_Protocol == PPP_EAP_TYPE_TLS && p->Eap_MatchUserByCert == false) { // Propose EAP-MSCHAPv2 p->Eap_Protocol = PPP_EAP_TYPE_MSCHAPV2; @@ -3410,7 +3417,7 @@ bool PPPGetIPAddressValueFromLCP(PPP_LCP *c, UINT type, IP *ip) } // EAP packet utilities -bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapTlsSize) +bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapSize) { UCHAR *dataBuffer; UINT dataSize; @@ -3420,8 +3427,8 @@ bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapTlsSi PPP_EAP *eap; UCHAR flags = PPP_EAP_TLS_FLAG_NONE; UINT sizeLeft = 0; - Debug("Got EAP-TLS size=%i\n", eapTlsSize); - if (eapTlsSize == 1) + Debug("Got EAP-TLS size=%i\n", eapSize); + if (eapSize == 1 && eap_packet->Tls.Flags == PPP_EAP_TLS_FLAG_NONE) { // This is an EAP-TLS message ACK if (p->Eap_TlsCtx.CachedBufferSend != NULL) @@ -3459,116 +3466,47 @@ bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapTlsSi p->Eap_TlsCtx.CachedBufferSendPntr = NULL; } } - else + else if (p->AuthOk == true && p->Ipc != NULL && p->PPPStatus == PPP_STATUS_AUTHENTICATING) { - // It probably should be the final ACK on closed SSL pipe - SyncSslPipe(p->Eap_TlsCtx.SslPipe); - if (p->Eap_TlsCtx.ClientCert.X != NULL) + // The handshake terminated and we received the final ACK, the auth is successful + // Just send an EAP-Success + PPP_PACKET* pack; + UINT identificator = p->Eap_PacketId; + + PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS); + pack = ZeroMalloc(sizeof(PPP_PACKET)); + pack->IsControl = true; + pack->Protocol = PPP_PROTOCOL_EAP; + lcp = NewPPPLCP(PPP_EAP_CODE_SUCCESS, identificator); + pack->Lcp = lcp; + Debug("Sent EAP-TLS size=%i SUCCESS\n", lcp->DataSize); + if (PPPSendPacketAndFree(p, pack) == false) { - IPC *ipc; - ETHERIP_ID d; - UINT error_code; - - /*if (!p->Eap_TlsCtx.SslPipe->IsDisconnected) - { - dataSize = FifoSize(p->Eap_TlsCtx.SslPipe->RawOut->RecvFifo); - p->Eap_PacketId = p->NextId++; - lcp = BuildEAPTlsRequest(p->Eap_PacketId, dataSize, 0); - eap = lcp->Data; - ReadFifo(p->Eap_TlsCtx.SslPipe->RawOut->RecvFifo, &(eap->Tls.TlsDataWithoutLength), dataSize); - if (!PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp)) - { - PPPSetStatus(p, PPP_STATUS_FAIL); - WHERE; - return false; - } - Debug("Sent EAP-TLS size=%i type=%i flag=%i\n", lcp->DataSize, eap->Type, eap->Tls.Flags); - return true; - }*/ - - PPPParseUsername(p->Cedar, p->Eap_Identity, &d); - - ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, d.HubName, d.UserName, "", NULL, - &error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort, - p->ClientHostname, p->CryptName, false, p->AdjustMss, NULL, p->Eap_TlsCtx.ClientCert.X, - IPC_LAYER_3); - - if (ipc != NULL) - { - PPP_PACKET *pack; - UINT identificator = p->Eap_PacketId; - - p->Ipc = ipc; - PPPSetStatus(p, PPP_STATUS_AUTH_SUCCESS); - - // Setting user timeouts - p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout - p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000; - if (p->TubeRecv != NULL) - { - p->TubeRecv->DataTimeout = p->DataTimeout; - } - p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000; - p->UserConnectionTick = Tick64(); - - // Just send an EAP-Success - pack = ZeroMalloc(sizeof(PPP_PACKET)); - pack->IsControl = true; - pack->Protocol = PPP_PROTOCOL_EAP; - lcp = NewPPPLCP(PPP_EAP_CODE_SUCCESS, identificator); - pack->Lcp = lcp; - Debug("Sent EAP-TLS size=%i SUCCESS\n", lcp->DataSize); - if (PPPSendPacketAndFree(p, pack) == false) - { - PPPSetStatus(p, PPP_STATUS_FAIL); - WHERE; - return false; - } - return true; - } - else - { - PPP_PACKET *pack; - UINT identificator = p->Eap_PacketId; - - PPPSetStatus(p, PPP_STATUS_AUTH_FAIL); - - pack = ZeroMalloc(sizeof(PPP_PACKET)); - pack->IsControl = true; - pack->Protocol = PPP_PROTOCOL_EAP; - lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, identificator); - pack->Lcp = lcp; - Debug("Sent EAP-TLS size=%i FAILURE\n", lcp->DataSize); - if (PPPSendPacketAndFree(p, pack) == false) - { - PPPSetStatus(p, PPP_STATUS_FAIL); - WHERE; - return false; - } - return false; - } + PPPSetStatus(p, PPP_STATUS_FAIL); + WHERE; + return false; } - else + return true; + } + else if (p->Eap_TlsCtx.ClientCert.X == NULL) + { + // Some clients needs a little help it seems - namely VPN Client Pro on Android + flags |= PPP_EAP_TLS_FLAG_SSLSTARTED; + p->Eap_PacketId = p->NextId++; + lcp = BuildEAPTlsRequest(p->Eap_PacketId, 0, flags); + PPPSetStatus(p, PPP_STATUS_AUTHENTICATING); + if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false) { - // Some clients needs a little help it seems - namely VPN Client Pro on Android - flags |= PPP_EAP_TLS_FLAG_SSLSTARTED; - p->Eap_PacketId = p->NextId++; - lcp = BuildEAPTlsRequest(p->Eap_PacketId, 0, flags); - PPPSetStatus(p, PPP_STATUS_AUTHENTICATING); - if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false) - { - PPPSetStatus(p, PPP_STATUS_FAIL); - WHERE; - return false; - } - Debug("Sent EAP-TLS size=%i\n", lcp->DataSize); - return true; + PPPSetStatus(p, PPP_STATUS_FAIL); + WHERE; + return false; } + Debug("Sent EAP-TLS size=%i\n", lcp->DataSize); } return true; } dataBuffer = eap_packet->Tls.TlsDataWithoutLength; - dataSize = eapTlsSize - 1; + dataSize = eapSize - 1; if (eap_packet->Tls.Flags & PPP_EAP_TLS_FLAG_TLS_LENGTH) { dataBuffer = eap_packet->Tls.TlsDataWithLength.Data; @@ -3593,7 +3531,7 @@ bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapTlsSi if (p->Eap_TlsCtx.SslPipe == NULL) { p->Eap_TlsCtx.Dh = DhNewFromBits(DH_PARAM_BITS_DEFAULT); - p->Eap_TlsCtx.SslPipe = NewSslPipeEx2(true, p->Cedar->ServerX, p->Cedar->ServerK, p->Cedar->ServerChain, p->Eap_TlsCtx.Dh, true, &(p->Eap_TlsCtx.ClientCert)); + p->Eap_TlsCtx.SslPipe = NewSslPipeEx3(true, p->Cedar->ServerX, p->Cedar->ServerK, p->Cedar->ServerChain, p->Eap_TlsCtx.Dh, true, &(p->Eap_TlsCtx.ClientCert), p->Eap_TlsCtx.Tls13SessionTicketsCount, p->Eap_TlsCtx.DisableTls13); } // If the current frame is fragmented, or it is a possible last of a fragmented series, bufferize it @@ -3640,9 +3578,11 @@ bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapTlsSi return false; } Debug("Sent EAP-TLS size=%i\n", lcp->DataSize); + return true; } else { + bool syncOk; /*Debug("=======RECV EAP-TLS FIFO DUMP=======\n"); for (i = 0; i < dataSize; i++) { @@ -3651,7 +3591,8 @@ bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapTlsSi } Debug("\n=======RECV EAP-TLS PACKET FIFO END=======\n");*/ WriteFifo(p->Eap_TlsCtx.SslPipe->RawIn->SendFifo, dataBuffer, dataSize); - SyncSslPipe(p->Eap_TlsCtx.SslPipe); + syncOk = SyncSslPipe(p->Eap_TlsCtx.SslPipe); + // Delete the cached buffer after we fed it into the pipe if (p->Eap_TlsCtx.CachedBufferRecv != NULL) { @@ -3660,6 +3601,153 @@ bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapTlsSi p->Eap_TlsCtx.CachedBufferRecvPntr = NULL; } + // Special case - we attempt to restart downgrading TLS settings + if (!syncOk && (p->Eap_TlsCtx.DisableTls13 == false || p->Eap_TlsCtx.Tls13SessionTicketsCount == 0)) + { + // If we authenticated earlier, deauthenticate back + p->DataTimeout = PPP_DATA_TIMEOUT; + p->PacketRecvTimeout = PPP_PACKET_RECV_TIMEOUT; + p->UserConnectionTimeout = 0; + p->UserConnectionTick = 0; + if (p->Ipc != NULL) + { + FreeIPC(p->Ipc); + p->Ipc = NULL; + p->AuthOk = false; + } + + FreeSslPipe(p->Eap_TlsCtx.SslPipe); + DhFree(p->Eap_TlsCtx.Dh); + p->Eap_TlsCtx.SslPipe = NULL; + p->Eap_TlsCtx.Dh = NULL; + if (p->Eap_TlsCtx.Tls13SessionTicketsCount == 0) + { + p->Eap_TlsCtx.DisableTls13 = true; + } + else + { + p->Eap_TlsCtx.Tls13SessionTicketsCount = 0; + } + flags |= PPP_EAP_TLS_FLAG_SSLSTARTED; + p->Eap_PacketId = p->NextId++; + lcp = BuildEAPTlsRequest(p->Eap_PacketId, 0, flags); + if (PPPSendAndRetransmitRequest(p, PPP_PROTOCOL_EAP, lcp) == false) + { + PPPSetStatus(p, PPP_STATUS_FAIL); + WHERE; + return false; + } + Debug("EAP-TLS: Restarting the handshake! Tls13SessionTicketsCount = %d, DisableTls13 = %d\n", p->Eap_TlsCtx.Tls13SessionTicketsCount, p->Eap_TlsCtx.DisableTls13); + Debug("Sent EAP-TLS size=%i\n", lcp->DataSize); + return false; + } + + // If on the server we have enough data to authenticate, let's do this before we continue with the handshake + // Check if we received the client certificate and the handshake is finished + if (p->Eap_TlsCtx.ClientCert.X != NULL && p->Ipc == NULL) + { + IPC* ipc; + UINT error_code; + + if (p->Eap_MatchUserByCert) + { + HUB* hub = GetHub(p->Cedar, p->Eap_Identity.HubName); + bool found = false; + if (hub != NULL) + { + USER* user = AcGetUserByCert(hub, p->Eap_TlsCtx.ClientCert.X); + if (user != NULL) + { + StrCpy(p->Eap_Identity.UserName, sizeof(p->Eap_Identity.UserName), user->Name); + found = true; + ReleaseUser(user); + } + ReleaseHub(hub); + } + + if (found == false) + { + PPP_PACKET* pack; + UINT identificator = p->Eap_PacketId; + + ReleaseHub(hub); + + PPPSetStatus(p, PPP_STATUS_AUTH_FAIL); + + pack = ZeroMalloc(sizeof(PPP_PACKET)); + pack->IsControl = true; + pack->Protocol = PPP_PROTOCOL_EAP; + lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, identificator); + pack->Lcp = lcp; + Debug("Sent EAP-TLS size=%i FAILURE\n", lcp->DataSize); + if (PPPSendPacketAndFree(p, pack) == false) + { + PPPSetStatus(p, PPP_STATUS_FAIL); + WHERE; + return false; + } + return false; + } + } + + ipc = NewIPC(p->Cedar, p->ClientSoftwareName, p->Postfix, p->Eap_Identity.HubName, p->Eap_Identity.UserName, "", NULL, + &error_code, &p->ClientIP, p->ClientPort, &p->ServerIP, p->ServerPort, + p->ClientHostname, p->CryptName, false, p->AdjustMss, NULL, p->Eap_TlsCtx.ClientCert.X, + IPC_LAYER_3); + + // We use the SAM authentication here, because the handshake can still fail at this point + if (ipc != NULL) + { + // Setting user timeouts + p->Ipc = ipc; + p->PacketRecvTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000 * 3 / 4; // setting to 3/4 of the user timeout + p->DataTimeout = (UINT64)p->Ipc->Policy->TimeOut * 1000; + if (p->TubeRecv != NULL) + { + p->TubeRecv->DataTimeout = p->DataTimeout; + } + p->UserConnectionTimeout = (UINT64)p->Ipc->Policy->AutoDisconnect * 1000; + p->UserConnectionTick = Tick64(); + + p->AuthOk = true; + + if (p->Eap_TlsCtx.SslPipe->SslVersion == TLS1_3_VERSION) + { + // Before starting IPC and sending an EAP-Success in case of TLS 1.3 we need to send a 0x00 data packet as per RFC 9190 + char zeroPacket[1] = { 0 }; + WriteFifo(p->Eap_TlsCtx.SslPipe->SslInOut->SendFifo, zeroPacket, sizeof(zeroPacket)); + if (!SyncSslPipe(p->Eap_TlsCtx.SslPipe)) + { + PPPSetStatus(p, PPP_STATUS_FAIL); + WHERE; + return false; + } + } + } + else + { + PPP_PACKET* pack; + UINT identificator = p->Eap_PacketId; + + PPPSetStatus(p, PPP_STATUS_AUTH_FAIL); + + pack = ZeroMalloc(sizeof(PPP_PACKET)); + pack->IsControl = true; + pack->Protocol = PPP_PROTOCOL_EAP; + lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, identificator); + pack->Lcp = lcp; + Debug("Sent EAP-TLS size=%i FAILURE\n", lcp->DataSize); + if (PPPSendPacketAndFree(p, pack) == false) + { + PPPSetStatus(p, PPP_STATUS_FAIL); + WHERE; + return false; + } + return false; + } + } + + // We continue the TLS handshake if (p->Eap_TlsCtx.SslPipe->IsDisconnected == false) { dataSize = FifoSize(p->Eap_TlsCtx.SslPipe->RawOut->RecvFifo); @@ -3691,8 +3779,9 @@ bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapTlsSi return false; } Debug("Sent EAP-TLS size=%i type=%i flag=%i\n", lcp->DataSize, eap->Type, eap->Tls.Flags); + return true; } - else + else if (dataSize > 0 || p->Eap_TlsCtx.ClientCert.X == NULL) { p->Eap_PacketId = p->NextId++; lcp = BuildEAPTlsRequest(p->Eap_PacketId, dataSize, 0); @@ -3705,8 +3794,34 @@ bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapTlsSi return false; } Debug("Sent EAP-TLS size=%i type=%i flag=%i\n", lcp->DataSize, eap->Type, eap->Tls.Flags); + return true; } } + + + + // If we end up here, we got problems, send an EAP failure + if (p->Eap_TlsCtx.SslPipe->IsDisconnected) + { + PPP_PACKET* pack; + UINT identificator = p->Eap_PacketId; + + PPPSetStatus(p, PPP_STATUS_AUTH_FAIL); + + pack = ZeroMalloc(sizeof(PPP_PACKET)); + pack->IsControl = true; + pack->Protocol = PPP_PROTOCOL_EAP; + lcp = NewPPPLCP(PPP_EAP_CODE_FAILURE, identificator); + pack->Lcp = lcp; + Debug("Sent EAP-TLS size=%i FAILURE\n", lcp->DataSize); + if (PPPSendPacketAndFree(p, pack) == false) + { + PPPSetStatus(p, PPP_STATUS_FAIL); + WHERE; + return false; + } + return false; + } } } else diff --git a/src/Cedar/Proto_PPP.h b/src/Cedar/Proto_PPP.h index c7a333f7..b6011a76 100644 --- a/src/Cedar/Proto_PPP.h +++ b/src/Cedar/Proto_PPP.h @@ -9,6 +9,7 @@ #define PROTO_PPP_H #include "CedarType.h" +#include "Proto_IPsec.h" #include "Mayaqua/TcpIp.h" @@ -229,6 +230,8 @@ struct PPP_EAP_TLS_CONTEXT UCHAR *CachedBufferRecvPntr; UCHAR *CachedBufferSend; UCHAR *CachedBufferSendPntr; + bool DisableTls13; + int Tls13SessionTicketsCount; }; // PPP request resend @@ -302,7 +305,8 @@ struct PPP_SESSION // EAP contexts UINT Eap_Protocol; // Current EAP Protocol used UINT Eap_PacketId; // EAP Packet ID; - UCHAR Eap_Identity[MAX_SIZE]; // Received from client identity + ETHERIP_ID Eap_Identity; // Received from client identity + bool Eap_MatchUserByCert; // Attempt to match the user from it's certificate during EAP-TLS, ignoring the EAP-identification PPP_EAP_TLS_CONTEXT Eap_TlsCtx; // Context information for EAP TLS. May be possibly reused for EAP TTLS? LIST *SentReqPacketList; // Sent requests list @@ -387,7 +391,7 @@ bool PPPSetIPOptionToLCP(PPP_IPOPTION *o, PPP_LCP *c, bool only_modify); bool PPPGetIPAddressValueFromLCP(PPP_LCP *c, UINT type, IP *ip); bool PPPSetIPAddressValueToLCP(PPP_LCP *c, UINT type, IP *ip, bool only_modify); // EAP packet utilities -bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapTlsSize); +bool PPPProcessEAPTlsResponse(PPP_SESSION *p, PPP_EAP *eap_packet, UINT eapSize); PPP_LCP *BuildEAPPacketEx(UCHAR code, UCHAR id, UCHAR type, UINT datasize); PPP_LCP *BuildEAPTlsPacketEx(UCHAR code, UCHAR id, UCHAR type, UINT datasize, UCHAR flags); PPP_LCP *BuildEAPTlsRequest(UCHAR id, UINT datasize, UCHAR flags); diff --git a/src/Cedar/Server.c b/src/Cedar/Server.c index 943b8870..7a83f9a0 100644 --- a/src/Cedar/Server.c +++ b/src/Cedar/Server.c @@ -3932,6 +3932,7 @@ void SiLoadHubOptionCfg(FOLDER *f, HUB_OPTION *o) o->NoPhysicalIPOnPacketLog = CfgGetBool(f, "NoPhysicalIPOnPacketLog"); o->UseHubNameAsDhcpUserClassOption = CfgGetBool(f, "UseHubNameAsDhcpUserClassOption"); o->UseHubNameAsRadiusNasId = CfgGetBool(f, "UseHubNameAsRadiusNasId"); + o->AllowEapMatchUserByCert = CfgGetBool(f, "AllowEapMatchUserByCert"); // Enabled by default if (CfgIsItem(f, "ManageOnlyPrivateIP")) @@ -4037,6 +4038,7 @@ void SiWriteHubOptionCfg(FOLDER *f, HUB_OPTION *o) CfgAddBool(f, "DisableCorrectIpOffloadChecksum", o->DisableCorrectIpOffloadChecksum); CfgAddBool(f, "UseHubNameAsDhcpUserClassOption", o->UseHubNameAsDhcpUserClassOption); CfgAddBool(f, "UseHubNameAsRadiusNasId", o->UseHubNameAsRadiusNasId); + CfgAddBool(f, "AllowEapMatchUserByCert", o->AllowEapMatchUserByCert); } // Write the user @@ -7521,6 +7523,7 @@ void SiCalledUpdateHub(SERVER *s, PACK *p) o.DisableCorrectIpOffloadChecksum = PackGetBool(p, "DisableCorrectIpOffloadChecksum"); o.UseHubNameAsDhcpUserClassOption = PackGetBool(p, "UseHubNameAsDhcpUserClassOption"); o.UseHubNameAsRadiusNasId = PackGetBool(p, "UseHubNameAsRadiusNasId"); + o.AllowEapMatchUserByCert = PackGetBool(p, "AllowEapMatchUserByCert"); save_packet_log = PackGetInt(p, "SavePacketLog"); packet_log_switch_type = PackGetInt(p, "PacketLogSwitchType"); @@ -9355,6 +9358,7 @@ void SiPackAddCreateHub(PACK *p, HUB *h) PackAddData(p, "SecurePassword", h->SecurePassword, SHA1_SIZE); PackAddBool(p, "UseHubNameAsDhcpUserClassOption", h->Option->UseHubNameAsDhcpUserClassOption); PackAddBool(p, "UseHubNameAsRadiusNasId", h->Option->UseHubNameAsRadiusNasId); + PackAddBool(p, "AllowEapMatchUserByCert", h->Option->AllowEapMatchUserByCert); SiAccessListToPack(p, h->AccessList); diff --git a/src/Mayaqua/CMakeLists.txt b/src/Mayaqua/CMakeLists.txt index 422a3f7f..32a121a9 100644 --- a/src/Mayaqua/CMakeLists.txt +++ b/src/Mayaqua/CMakeLists.txt @@ -22,8 +22,10 @@ include(CheckSymbolExists) set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) set(CMAKE_REQUIRED_LIBRARIES OpenSSL::Crypto) +set(CMAKE_REQUIRED_LIBRARIES OpenSSL::SSL) check_symbol_exists(EVP_PKEY_get_raw_public_key "openssl/evp.h" HAVE_EVP_PKEY_GET_RAW_PUBLIC_KEY) +check_symbol_exists(SSL_CTX_set_num_tickets "openssl/ssl.h" HAVE_SSL_CTX_SET_NUM_TICKETS) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) @@ -32,6 +34,12 @@ if(NOT HAVE_EVP_PKEY_GET_RAW_PUBLIC_KEY) message(FATAL_ERROR "Required EVP_PKEY_get_raw_public_key() not found in OpenSSL library!") endif() +if (HAVE_SSL_CTX_SET_NUM_TICKETS) + add_compile_definitions(HAVE_SSL_CTX_SET_NUM_TICKETS) +endif() + + + find_package(ZLIB REQUIRED) # Required because we include in Encrypt.h. diff --git a/src/Mayaqua/Network.c b/src/Mayaqua/Network.c index 7d2ea3db..76c682e1 100644 --- a/src/Mayaqua/Network.c +++ b/src/Mayaqua/Network.c @@ -5713,7 +5713,13 @@ SSL_PIPE *NewSslPipeEx(bool server_mode, X *x, K *k, DH_CTX *dh, bool verify_pee { return NewSslPipeEx2(server_mode, x, k, NULL, dh, verify_peer, clientcert); } -SSL_PIPE *NewSslPipeEx2(bool server_mode, X *x, K *k, LIST *chain, DH_CTX *dh, bool verify_peer, struct SslClientCertInfo *clientcert) + +SSL_PIPE* NewSslPipeEx2(bool server_mode, X* x, K* k, LIST* chain, DH_CTX* dh, bool verify_peer, struct SslClientCertInfo* clientcert) +{ + return NewSslPipeEx3(server_mode, x, k, chain, dh, verify_peer, clientcert, 2, false); // 2 TLS 1.3 tickets is an OpenSSL default hardcoded in the library +} + +SSL_PIPE *NewSslPipeEx3(bool server_mode, X *x, K *k, LIST *chain, DH_CTX *dh, bool verify_peer, struct SslClientCertInfo *clientcert, int tls13ticketscnt, bool disableTls13) { SSL_PIPE *s; SSL *ssl; @@ -5723,10 +5729,6 @@ SSL_PIPE *NewSslPipeEx2(bool server_mode, X *x, K *k, LIST *chain, DH_CTX *dh, b { if (server_mode) { -#ifdef SSL_OP_NO_TLSv1_3 - SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_3); // For some reason pppd under linux doesn't like it -#endif - if (chain == NULL) { AddChainSslCertOnDirectory(ssl_ctx); @@ -5784,6 +5786,16 @@ SSL_PIPE *NewSslPipeEx2(bool server_mode, X *x, K *k, LIST *chain, DH_CTX *dh, b SSL_CTX_set_options(ssl_ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); } +#ifdef SSL_OP_NO_TLSv1_3 + if (disableTls13) + { + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_3); + } +#endif +#ifdef HAVE_SSL_CTX_SET_NUM_TICKETS + SSL_CTX_set_num_tickets(ssl_ctx, tls13ticketscnt); +#endif + ssl = SSL_new(ssl_ctx); SSL_set_ex_data(ssl, GetSslClientCertIndex(), clientcert); @@ -5832,6 +5844,8 @@ SSL_PIPE *NewSslPipeEx2(bool server_mode, X *x, K *k, LIST *chain, DH_CTX *dh, b bool SyncSslPipe(SSL_PIPE *s) { UINT i; + SSL_SESSION* sess; + // Validate arguments if (s == NULL || s->IsDisconnected) { @@ -5862,6 +5876,8 @@ bool SyncSslPipe(SSL_PIPE *s) } } + s->SslVersion = SSL_version(s->ssl); + return true; } @@ -15832,6 +15848,14 @@ DH *TmpDhCallback(SSL *ssl, int is_export, int keylength) return ret; } +// Log SSL keys +void keylog_cb_func(const SSL* ssl, const char* line) +{ + Debug("SSL_KEYLOG_BEGIN\n"); + Debug(line); + Debug("\nSSL_KEYLOG_END\n"); +} + // Create the SSL_CTX struct ssl_ctx_st *NewSSLCtx(bool server_mode) { @@ -15868,6 +15892,8 @@ struct ssl_ctx_st *NewSSLCtx(bool server_mode) SSL_CTX_set_ecdh_auto(ctx, 1); #endif // SSL_CTX_set_ecdh_auto + SSL_CTX_set_keylog_callback(ctx, &keylog_cb_func); + return ctx; } diff --git a/src/Mayaqua/Network.h b/src/Mayaqua/Network.h index c4a04391..202df410 100644 --- a/src/Mayaqua/Network.h +++ b/src/Mayaqua/Network.h @@ -11,6 +11,8 @@ #include "Encrypt.h" #include "Mayaqua.h" +#include // This is needed only for the SSL/TLS version defines + #ifdef OS_UNIX #include @@ -537,6 +539,7 @@ struct SSL_PIPE { bool ServerMode; // Whether it's in the server mode bool IsDisconnected; // Disconnected + int SslVersion; SSL *ssl; // SSL object struct ssl_ctx_st *ssl_ctx; // SSL_CTX SSL_BIO *SslInOut; // I/O BIO for the data in the SSL tunnel @@ -1407,6 +1410,7 @@ struct SslClientCertInfo { SSL_PIPE *NewSslPipe(bool server_mode, X *x, K *k, DH_CTX *dh); SSL_PIPE *NewSslPipeEx(bool server_mode, X *x, K *k, DH_CTX *dh, bool verify_peer, struct SslClientCertInfo *clientcert); SSL_PIPE *NewSslPipeEx2(bool server_mode, X *x, K *k, LIST *chain, DH_CTX *dh, bool verify_peer, struct SslClientCertInfo *clientcert); +SSL_PIPE* NewSslPipeEx3(bool server_mode, X* x, K* k, LIST* chain, DH_CTX* dh, bool verify_peer, struct SslClientCertInfo* clientcert, int tls13ticketscnt, bool disableTls13); void FreeSslPipe(SSL_PIPE *s); bool SyncSslPipe(SSL_PIPE *s); diff --git a/src/bin/hamcore/strtable_cn.stb b/src/bin/hamcore/strtable_cn.stb index 8813d2df..eabcf38f 100644 --- a/src/bin/hamcore/strtable_cn.stb +++ b/src/bin/hamcore/strtable_cn.stb @@ -570,6 +570,7 @@ HUB_AO_DetectDormantSessionInterval If you set this option to non-zero value, HUB_AO_NoPhysicalIPOnPacketLog If you set this option to non-zero value, then the physical IP addresses of VPN clients of either the source VPN session or the destination VPN session will not be recorded on the packet log file. HUB_AO_UseHubNameAsDhcpUserClassOption If you set this option to non-zero value, then the Virtual Hub Name will be added to a DHCP request to an external DHCP server as the "User-Class" option. This allows to use separate pools of IP addresses for each Virtual Hub. (For only L2TP/IPsec and OpenVPN sessions.) HUB_AO_UseHubNameAsRadiusNasId If you set this option to non-zero value, then the NAS-Identifier RADIUS attribute will be set to a name of the Virtual Hub. This allows to determine on RADIUS server whether access to the Virtual Hub should be granted or denied. +HUB_AO_AllowEapMatchUserByCert If you enable (set to 1) this option, the Virtual Hub will attempt to match the EAP Identity not only with usernames, but also with user certificates during the PPP EAP authentication flow. diff --git a/src/bin/hamcore/strtable_en.stb b/src/bin/hamcore/strtable_en.stb index 7bac9175..508960b7 100644 --- a/src/bin/hamcore/strtable_en.stb +++ b/src/bin/hamcore/strtable_en.stb @@ -568,6 +568,7 @@ HUB_AO_DetectDormantSessionInterval If you set this option to non-zero value, HUB_AO_NoPhysicalIPOnPacketLog If you set this option to non-zero value, then the physical IP addresses of VPN clients of either the source VPN session or the destination VPN session will not be recorded on the packet log file. HUB_AO_UseHubNameAsDhcpUserClassOption If you set this option to non-zero value, then the Virtual Hub Name will be added to a DHCP request to an external DHCP server as the "User-Class" option. This allows to use separate pools of IP addresses for each Virtual Hub. (For only L2TP/IPsec and OpenVPN sessions.) HUB_AO_UseHubNameAsRadiusNasId If you set this option to non-zero value, then the NAS-Identifier RADIUS attribute will be set to a name of the Virtual Hub. This allows to determine on RADIUS server whether access to the Virtual Hub should be granted or denied. +HUB_AO_AllowEapMatchUserByCert If you enable (set to 1) this option, the Virtual Hub will attempt to match the EAP Identity not only with usernames, but also with user certificates during the PPP EAP authentication flow. # Concerning failed connection dialogs diff --git a/src/bin/hamcore/strtable_ja.stb b/src/bin/hamcore/strtable_ja.stb index df220efa..5462b57b 100644 --- a/src/bin/hamcore/strtable_ja.stb +++ b/src/bin/hamcore/strtable_ja.stb @@ -588,6 +588,7 @@ HUB_AO_DetectDormantSessionInterval この項目が 0 以外の場合は、指 HUB_AO_NoPhysicalIPOnPacketLog この項目が 1 (有効) の場合は、パケットログに送信元および宛先 VPN セッションの物理的な接続元 VPN クライアントの IP アドレスが記録されないようになります。 HUB_AO_UseHubNameAsDhcpUserClassOption この項目が 1 (有効) の場合は、仮想 HUB は DHCP サーバーに対して IP アドレスの取得を要求する際に仮想 HUB 名を DHCP パケットの "User-Class" オプションに埋め込むようになります。この機能は、複数の仮想 HUB がある場合に、DHCP サーバーがそれぞれの仮想 HUB 用に IP プールを確保する場合に便利です。(L2TP/IPsec および OpenVPN セッションのみ対応。) HUB_AO_UseHubNameAsRadiusNasId この項目が 1 (有効) の場合は、NAS-Identifier RADIUS 属性に仮想 HUB 名が埋め込まれます。この機能は、RADIUS サーバにおいて仮想 HUB ごとにアクセスの許可 / 拒否を設定したい場合に便利です。 +HUB_AO_AllowEapMatchUserByCert If you enable (set to 1) this option, the Virtual Hub will attempt to match the EAP Identity not only with usernames, but also with user certificates during the PPP EAP authentication flow. # Caps 関係 diff --git a/src/bin/hamcore/strtable_ko.stb b/src/bin/hamcore/strtable_ko.stb index 4f544645..96298f5e 100644 --- a/src/bin/hamcore/strtable_ko.stb +++ b/src/bin/hamcore/strtable_ko.stb @@ -589,6 +589,7 @@ HUB_AO_AssignVLanIdByRadiusAttribute VLAN ID의 동적 할당 기능을 활성 HUB_AO_SecureNAT_RandomizeAssignIp 이 항목을 1 (유효)의 경우 SecureNAT 기능의 가상 DHCP 서버는 DHCP 클라이언트에 할당 된 IP 주소를 지정된 IP 주소 풀에서 사용하지 않는 주소에서 임의로 선택하도록합니다. 또한, 기본 동작은 미사용 주소 중 첫 번째 주소를 할당 할 수 있도록되어 있습니다. HUB_AO_DetectDormantSessionInterval 이 항목이 0이 아닌 경우, 지정된 초 비활성이었다 VPN 세션을 드 폰 망토 상태 (최대 절전 모드)로 식별합니다. 드 폰 망토 상태의 VPN 세션에 가상 HUB에서 홍수되어야 패킷이 침수 없습니다. HUB_AO_NoPhysicalIPOnPacketLog 이 항목이 0 (사용)의 경우 패킷 로그에 원본 및 대상 VPN 세션의 물리적 연결 원래 VPN 클라이언트의 IP 주소가 기록되지 않도록합니다. +HUB_AO_AllowEapMatchUserByCert If you enable (set to 1) this option, the Virtual Hub will attempt to match the EAP Identity not only with usernames, but also with user certificates during the PPP EAP authentication flow. # Caps 관계 diff --git a/src/bin/hamcore/strtable_pt_br.stb b/src/bin/hamcore/strtable_pt_br.stb index 9972535f..197b3d8a 100644 --- a/src/bin/hamcore/strtable_pt_br.stb +++ b/src/bin/hamcore/strtable_pt_br.stb @@ -479,7 +479,7 @@ NATT_MSG ** Connected with NAT traversal - might be unstable **\r\n\r\nThis VPN # Virtual HUB Admin Options HUB_AO_CLICK Select an item to view the description here. HUB_AO_UNKNOWN The description of the item was not found. Refer to the documents, or speculate the meaning and purpose of the item from the name of the item. -HUB_AO_allow_hub_admin_change_option This is a special item. If you are enable (set to 1) this option, then not only the VPN Server's global administrator but also the Virtual Hub's administrator will be granted to modify the Virtual Hub Admin Options by himself. +HUB_AO_allow_eap_tls_match_user_by_cert If you enable (set to 1) this option, the Virtual Hub will attempt to match the EAP Identity not only with usernames, but also with user certificate CNs during the PPP EAP authentication flow. HUB_AO_deny_hub_admin_change_ext_option If you are enable (set to 1) this option, the Virtual Hub's administrator will be forbidden to modify any values on the Virtual Hub Extended Options, then only the VPN Server's global administrator can modify them. HUB_AO_no_delay_jitter_packet_loss If you set this option to non-zero value, then all parameters of delay, jitter and packet-loss on the access-list entry will be ignored even if these parameters are set when the administrator adds a new access list entry. Therefore, delay, jitter and packet-loss generating function will be virtually disabled. Because of the delay generating function sometimes make a high volume of load on the CPU and RAM, a Virtual Hub which is shared by several users should have this option enabled. HUB_AO_max_users If you set this option to non-zero value, the maximum number of user objects registered on the Virtual Hub will be limited to this value, then greater number of user objects than this value cannot be added. @@ -581,6 +581,7 @@ HUB_AO_DetectDormantSessionInterval If you set this option to non-zero value, th HUB_AO_NoPhysicalIPOnPacketLog If you set this option to non-zero value, then the physical IP addresses of VPN clients of either the source VPN session or the destination VPN session will not be recorded on the packet log file. HUB_AO_UseHubNameAsDhcpUserClassOption If you set this option to non-zero value, then the Virtual Hub Name will be added to a DHCP request to an external DHCP server as the "User-Class" option. This allows to use separate pools of IP addresses for each Virtual Hub. (For only L2TP/IPsec and OpenVPN sessions.) HUB_AO_UseHubNameAsRadiusNasId If you set this option to non-zero value, then the NAS-Identifier RADIUS attribute will be set to a name of the Virtual Hub. This allows to determine on RADIUS server whether access to the Virtual Hub should be granted or denied. +HUB_AO_AllowEapMatchUserByCert If you enable (set to 1) this option, the Virtual Hub will attempt to match the EAP Identity not only with usernames, but also with user certificates during the PPP EAP authentication flow. # Concerning failed connection dialogs diff --git a/src/bin/hamcore/strtable_ru.stb b/src/bin/hamcore/strtable_ru.stb index 2b737064..a4afbdcf 100644 --- a/src/bin/hamcore/strtable_ru.stb +++ b/src/bin/hamcore/strtable_ru.stb @@ -567,6 +567,7 @@ HUB_AO_DetectDormantSessionInterval If you set this option to non-zero value, HUB_AO_NoPhysicalIPOnPacketLog If you set this option to non-zero value, then the physical IP addresses of VPN clients of either the source VPN session or the destination VPN session will not be recorded on the packet log file. HUB_AO_UseHubNameAsDhcpUserClassOption If you set this option to non-zero value, then the Virtual Hub Name will be added to a DHCP request to an external DHCP server as the "User-Class" option. This allows to use separate pools of IP addresses for each Virtual Hub. (For only L2TP/IPsec and OpenVPN sessions.) HUB_AO_UseHubNameAsRadiusNasId If you set this option to non-zero value, then the NAS-Identifier RADIUS attribute will be set to a name of the Virtual Hub. This allows to determine on RADIUS server whether access to the Virtual Hub should be granted or denied. +HUB_AO_AllowEapMatchUserByCert If you enable (set to 1) this option, the Virtual Hub will attempt to match the EAP Identity not only with usernames, but also with user certificates during the PPP EAP authentication flow. # Concerning failed connection dialogs diff --git a/src/bin/hamcore/strtable_tw.stb b/src/bin/hamcore/strtable_tw.stb index e653f2be..9891eb22 100644 --- a/src/bin/hamcore/strtable_tw.stb +++ b/src/bin/hamcore/strtable_tw.stb @@ -573,6 +573,7 @@ HUB_AO_DetectDormantSessionInterval If you set this option to non-zero value, HUB_AO_NoPhysicalIPOnPacketLog If you set this option to non-zero value, then the physical IP addresses of VPN clients of either the source VPN session or the destination VPN session will not be recorded on the packet log file. HUB_AO_UseHubNameAsDhcpUserClassOption If you set this option to non-zero value, then the Virtual Hub Name will be added to a DHCP request to an external DHCP server as the "User-Class" option. This allows to use separate pools of IP addresses for each Virtual Hub. (For only L2TP/IPsec and OpenVPN sessions.) HUB_AO_UseHubNameAsRadiusNasId If you set this option to non-zero value, then the NAS-Identifier RADIUS attribute will be set to a name of the Virtual Hub. This allows to determine on RADIUS server whether access to the Virtual Hub should be granted or denied. +HUB_AO_AllowEapMatchUserByCert If you enable (set to 1) this option, the Virtual Hub will attempt to match the EAP Identity not only with usernames, but also with user certificates during the PPP EAP authentication flow. #關於失敗連接對話方塊