Windows 10 includes all necessary components bar one from being able to act as an EAP authenticator – it does not include the “Microsoft EAPHost Authenticator service” DLL (eapahost.dll). Fortunately, this DLL seems to have a simple task: to isolate the Windows VPN components from custom EAP implementations and to unify the two types of custom EAP implementations: legacy EAP methods (HKLM\SYSTEM\CurrentControlSet\Services\RasMan\PPP\EAP) and EapHost methods (HKLM\SYSTEM\CurrentControlSet\Services\Eaphost\Methods).
A separate article (Establishing a VPN connection from macOS to Windows 10) motivates why it might be useful to develop a replacement for this missing component. Essentially, the component implements just one public interface: IEapHostAuthenticatorSessionApis.
A C# definition of the interface, with parameter types chosen for convenience of implementation, is:
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("5A8371A3-0C6D-487B-B3C8-46D785C4C940")]
interface IEapHostAuthenticatorSessionApis
{
void BeginSession(uint flags, EAP_HOST_AUTHENTICATOR_METHOD_DATA_ARRAY methods, EAP_ATTRIBUTES ea, uint maxpack, [MarshalAs(UnmanagedType.LPWStr)] string identity, out uint session, out EAP_ERROR error);
void UpdateInnerMethodParams(uint session, uint flags, [MarshalAs(UnmanagedType.LPWStr)] string identity, EAP_ATTRIBUTES ea, out EAP_ERROR error);
void IsReplay(uint session, uint n, EapPacket packet, [MarshalAs(UnmanagedType.Bool)] out bool replay, out EAP_ERROR error);
void ReceivePacket(uint session, uint n, IntPtr p, out EAP_METHOD_AUTHENTICATOR_RESPONSE_ACTION action, out EAP_ERROR error);
void SendPacket(uint session, out uint n, out IntPtr p, out EAP_AUTHENTICATOR_SEND_TIMEOUT timeout, out EAP_ERROR error);
void GetAttributes(uint session, [Out] EAP_ATTRIBUTES ea, out EAP_ERROR error);
void SetAttributes(uint session, EAP_ATTRIBUTES ea, out EAP_METHOD_AUTHENTICATOR_RESPONSE_ACTION action, out EAP_ERROR error);
void SetAuthenticateResult(uint session, uint status, [Out] EAP_ATTRIBUTES ea, out EAP_METHOD_AUTHENTICATOR_RESPONSE_ACTION action, out EAP_ERROR error);
void GetResult(uint session, [Out] EAP_METHOD_AUTHENTICATOR_RESULT2 result, out EAP_ERROR error);
void GetFinalPacket(uint session, [MarshalAs(UnmanagedType.Bool)] bool success, out uint n, out IntPtr p, out EAP_ERROR error);
void EndSession(uint session, out EAP_ERROR error);
}
An ad-hoc “journal” (explained in detail later) of the methods called and interesting input/output arguments during a PEAP authentication is:
BeginSession(flags = 0, maxpack = 1396, identity = CHBSGRN02081961\gary)
Method 0 26
Method 1 13
Method 2 25
EA 0 eatFramedMTU 4 78-05-00-00
EA 1 eatAcctSessionId 2 34-34
EA 2 eatNASIdentifier 15 43-48-42-53-47-52-4E-30-32-30-38-31-39-36-31
EA 3 eatNASIPAddress 4 C0-A8-00-02
EA 4 eatServiceType 4 02-00-00-00
EA 5 eatFramedProtocol 4 01-00-00-00
EA 6 eatNASPort 4 04-00-00-00
EA 7 eatNASPortType 4 05-00-00-00
EA 8 eatTunnelType 4 03-00-00-00
EA 9 eatTunnelMediumType 4 01-00-00-00
EA 10 eatCalledStationId 11 31-39-32-2E-31-36-38-2E-30-2E-32
EA 11 eatTunnelServerEndpoint 11 31-39-32-2E-31-36-38-2E-30-2E-32
EA 12 eatCallingStationId 11 31-39-32-2E-31-36-38-2E-30-2E-35
EA 13 eatTunnelClientEndpoint 11 31-39-32-2E-31-36-38-2E-30-2E-35
EA 14 eatEAPMessage 9 02-00-00-09-01-67-61-72-79
EA 15 eatUserName 4 67-61-72-79
EA 16 4108 4 C0-A8-00-02
EA 17 4128 15 43-48-42-53-47-52-4E-30-32-30-38-31-39-36-31
EA 18 8132 4 02-00-00-00
EA 19 4147 4 37-01-00-00
EA 20 4148 10 4D-53-52-41-53-56-35-2E-32-30
EA 21 4160 11 4D-53-52-41-53-56-35-2E-32-30-00
EA 22 4159 13 4D-53-52-41-53-2D-30-2D-47-41-52-59-00
EA 23 8158 39 7B-45-41-41-36-38-39-43-44-2D-38-46-45-41-2D-34-33-30-44-2D-39-35-44-37-2D-31-44-38-46-38-37-41-37-31-44-39-31-7D-00
ReceivePacket(1B1DA2F0, n = 9)
action = SendWithTimeoutInteractive -> EAP_METHOD_AUTHENTICATOR_RESPONSE_SEND, code = 0
SendPacket(1B1DA2F0)
EndSession(1B147190)
IsReplay(1B1DA2F0, n = 166)
2 1 166
ReceivePacket(1B1DA2F0, n = 166)
action = SendWithTimeoutInteractive -> EAP_METHOD_AUTHENTICATOR_RESPONSE_SEND, code = 0
SendPacket(1B1DA2F0)
IsReplay(1B1DA2F0, n = 6)
2 2 6
ReceivePacket(1B1DA2F0, n = 6)
action = SendWithTimeoutInteractive -> EAP_METHOD_AUTHENTICATOR_RESPONSE_SEND, code = 0
SendPacket(1B1DA2F0)
IsReplay(1B1DA2F0, n = 110)
2 3 110
ReceivePacket(1B1DA2F0, n = 110)
action = SendWithTimeoutInteractive -> EAP_METHOD_AUTHENTICATOR_RESPONSE_SEND, code = 0
SendPacket(1B1DA2F0)
IsReplay(1B1DA2F0, n = 6)
2 4 6
ReceivePacket(1B1DA2F0, n = 6)
action = SendWithTimeoutInteractive -> EAP_METHOD_AUTHENTICATOR_RESPONSE_SEND, code = 0
SendPacket(1B1DA2F0)
IsReplay(1B1DA2F0, n = 40)
2 5 40
ReceivePacket(1B1DA2F0, n = 40)
action = Send -> EAP_METHOD_AUTHENTICATOR_RESPONSE_SEND, code = 0
SendPacket(1B1DA2F0)
IsReplay(1B1DA2F0, n = 51)
2 6 51
ReceivePacket(1B1DA2F0, n = 51)
action = SendWithTimeout -> EAP_METHOD_AUTHENTICATOR_RESPONSE_SEND, code = 0
SendPacket(1B1DA2F0)
IsReplay(1B1DA2F0, n = 94)
2 7 94
ReceivePacket(1B1DA2F0, n = 94)
action = SendWithTimeout -> EAP_METHOD_AUTHENTICATOR_RESPONSE_SEND, code = 0
SendPacket(1B1DA2F0)
IsReplay(1B1DA2F0, n = 37)
2 8 37
ReceivePacket(1B1DA2F0, n = 37)
action = IndicateTLV -> EAP_METHOD_AUTHENTICATOR_RESPONSE_RESPOND, code = 0
GetAttributes(1B1DA2F0)
EA 0 eatVendorSpecific 22 00-00-01-37-0A-12-01-43-48-42-53-47-52-4E-30-32-30-38-31-39-36-31
EA 1 eatVendorSpecific 49 00-00-01-37-1A-2D-01-53-3D-33-39-46-34-46-32-35-44-44-34-33-34-45-37-41-46-31-43-46-31-37-38-42-33-41-31-36-45-45-32-38-39-30-41-43-39-35-30-43-36
EA 2 eatEAPTLV 6 80-03-00-02-00-01
EA 3 eatEAPTLV 16 00-07-00-0C-00-00-01-37-00-05-00-04-00-00-00-1A
SetAttributes(1B1DA2F0)
EA 0 eatEAPTLV 6 80-03-00-02-00-01
ReceivePacket(1B1DA2F0, n = 0)
action = SendWithTimeoutInteractive -> EAP_METHOD_AUTHENTICATOR_RESPONSE_SEND, code = 0
SendPacket(1B1DA2F0)
IsReplay(1B1DA2F0, n = 106)
2 10 106
ReceivePacket(1B1DA2F0, n = 106)
action = SendAndDone -> EAP_METHOD_AUTHENTICATOR_RESPONSE_RESULT, code = 0
GetResult(1B1DA2F0)
EA 0 eatVendorSpecific 22 00-00-01-37-0A-12-01-43-48-42-53-47-52-4E-30-32-30-38-31-39-36-31
EA 1 eatVendorSpecific 49 00-00-01-37-1A-2D-01-53-3D-33-39-46-34-46-32-35-44-44-34-33-34-45-37-41-46-31-43-46-31-37-38-42-33-41-31-36-45-45-32-38-39-30-41-43-39-35-30-43-36
EA 2 eatPEAPFastRoamedSession 4 00-00-00-00
EA 3 eatPEAPEmbeddedEAPTypeId 4 1A-00-00-00
EA 4 eatVendorSpecific 56 00-00-01-37-10-34-00-00-20-BC-B2-F4-FB-96-F8-B0-D1-C5-68-C2-9E-DE-02-DF-9B-11-C1-AA-3E-A0-FF-47-68-D4-FC-F1-92-A8-9B-AE-03-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
EA 5 eatVendorSpecific 56 00-00-01-37-11-34-00-00-20-9C-07-6C-93-D0-DC-4F-41-57-97-86-03-6C-4C-56-2A-AA-BD-BD-8D-71-3E-3A-B2-BE-F7-78-B6-2F-92-60-B9-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
GetFinalPacket(1B1DA2F0, success = True)
BeginSession
Each authenticator session starts with a call to BeginSession.
Most of the arguments to BeginSession just need to be saved for later use: the set of EAP_FLAG_* values, the set of EAP_ATTRIBUTE values, the maximum size of an EAP packet and the remote identity (an “unauthenticated” identity, created using information from things like the IKEv2 Identification payload and the initial EAP Identity Response).
The argument containing an array of potential EAP methods needs to be acted upon immediately. This list is presumably constructed by Windows based on the installed EAP methods and the policy settings. A method must be chosen from this list and the location (path to DLL) and type of the EAP method looked up. The DLL is then loaded and, according to its type (legacy or EapHost), the appropriate “initialize” and “begin” methods are called.
The EAP_ATTRIBUTE values are Type/Length/Value (TLV) items, the “Type” indicating the meaning of the value rather than a simple data type (e.g. string, integer), so without knowing the format (or underlying data type) for each “Type” it is easiest to “print” the values as a sequence of bytes. For those types that are in the EAP_ATTRIBUTE_TYPE enumeration, the name is printed. The other type values can be looked up in the sdoias.idl file in the Windows Software Development Kit. For the example above, the meanings of the numeric “Types” are:
4108 IAS_ATTRIBUTE_CLIENT_IP_ADDRESS
4128 IAS_ATTRIBUTE_CLIENT_NAME
8132 MS_ATTRIBUTE_NETWORK_ACCESS_SERVER_TYPE
4147 MS_ATTRIBUTE_RAS_VENDOR
4148 MS_ATTRIBUTE_RAS_VERSION
4160 MS_ATTRIBUTE_RAS_CLIENT_VERSION
4159 MS_ATTRIBUTE_RAS_CLIENT_NAME
8158 MS_ATTRIBUTE_RAS_CORRELATION_ID
For interest only (there is no need to act on the information), one of the EAP_ATTRIBUTE values is the EAP Identity Response:
EA 14 eatEAPMessage 9 02-00-00-09-01-67-61-72-79
02 = Code (Response)
00 = Identifier (0)
00-09 = Length (9)
01 = Type (Identity)
67-61-72-79 = Data (“gary”)
ReceivePacket and SendPacket
The authentication process is advanced by the VPN server making the newly received EAP packet available via a call to ReceivePacket. ReceivePacket must then invoke the EAP method with this input; the EAP method typically returns a new EAP packet to be sent back to the VPN client and an indication of what to do, such as “send with a timeout” or “send with an interactive timeout” (a longer timeout that allows for the possibility that the VPN client might need to interact with the user). The “ready to be sent” EAP packet is cached until the VPN server calls SendPacket to retrieve it.
IsReplay
The VPN server typically calls the IsReplay packet with an EAP packet before making the packet available by another call to ReceivePacket. The EAP methods don’t have any entry point that specifically checks for a replayed packet, so the simplest thing to do is to state that the EAP packet is not a replay and just let the authentication fail if that was a mistake.
GetResult
At some point (after a number of exchanges of EAP packets), the EAP method will indicate that an authentication result has been reached. The VPN server calls GetResult to retrieve the result (including a success/failure indication, a failure reason in the event of a failure and a set of EAP_ATTRIBUTE values derived during the authentication process.
GetFinalPacket
The VPN server finally calls GetFinalPacket to retrieve the packet that will communicate the success/failure of the authentication back to the VPN client.
GetAttributes and SetAttributes
For a simple authentication, such as EAP-MSCHAPv2, GetAttributes and SetAttributes are not needed/called, but the example authentication shown above is a more complex scenario (PEAP) and these two routines bridge the gap between completing the first phase of the authentication (building the protected (TLS) channel) and starting the second phase (MSCHAPv2).
GetAttributes makes a set of EAP_ATTRIBUTE values derived during the first phase of authentication process available to the VPN server and SetAttributes makes a new set of EAP_ATTRIBUTE values available to the next phase of the authentication process.
EndSession
EndSession is called when the authentication session is no longer needed, prior to “releasing” the COM object.
UpdateInnerMethodParams and SetAuthenticateResult
I have not encountered any scenarios where these are called.
eatVendorSpecific and eatEAPTLV TLVs
An IEapHostAuthenticatorSessionApis implementation does not need to understand or act upon the EAP_ATTRIBUTE values that flow through it, but it might be interesting to look in more detail at the contents of the eatVendorSpecific and eatEAPTLV TLVs.
eatVendorSpecific 22 00-00-01-37-0A-12-01-43-48-42-53-47-52-4E-30-32-30-38-31-39-36-31
00-00-01-37 = Vendor-Id (311 (Microsoft) in big-endian format)
0A = Vendor-Type (MS-CHAP-Domain)
12 = Vendor-Length (0x12)
01 = Ident
43-48-42-53-47-52-4E-30-32-30-38-31-39-36-31 = String (CHBSGRN02081961)
eatVendorSpecific 49 00-00-01-37-1A-2D-01-53-3D-33-39-46-34-46-32-35-44-44-34-33-34-45-37-41-46-31-43-46-31-37-38-42-33-41-31-36-45-45-32-38-39-30-41-43-39-35-30-43-36
00-00-01-37 = Vendor-Id (311 (Microsoft) in big-endian format)
1A = Vendor-Type (MS-CHAP2-Success)
2D = Vendor-Length (0x2D)
01 = Ident
53-3D-… = String (S=39F4F25DD434E7AF1CF178B3A16EE2890AC950C6)
eatVendorSpecific 56 00-00-01-37-10-34-00-00-20-BC-B2-F4-FB-96-F8-B0-D1-C5-68-C2-9E-DE-02-DF-9B-11-C1-AA-3E-A0-FF-47-68-D4-FC-F1-92-A8-9B-AE-03-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-01-37 = Vendor-Id (311 (Microsoft) in big-endian format)
10 = Vendor-Type (MS-MPPE-Send-Key)
34 = Vendor-Length (0x34)
00-00 = Salt
20 = Key-Length
BC-B2-… = Key
00-00-… = Padding
eatVendorSpecific 56 00-00-01-37-11-34-00-00-20-9C-07-6C-93-D0-DC-4F-41-57-97-86-03-6C-4C-56-2A-AA-BD-BD-8D-71-3E-3A-B2-BE-F7-78-B6-2F-92-60-B9-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-01-37 = Vendor-Id (311 (Microsoft) in big-endian format)
11 = Vendor-Type (MS-MPPE-Recv-Key)
34 = Vendor-Length (0x34)
00-00 = Salt
20 = Key-Length
9C-07-… = Key
00-00-… = Padding
eatEAPTLV 6 80-03-00-02-00-01
80-03 = Type (Result TLV (mandatory))
00-02 = Length
00-01 = Value (Success)
eatEAPTLV 16 00-07-00-0C-00-00-01-37-00-05-00-04-00-00-00-1A
00-07 = Type (Vendor Specific TLV)
00-0C = Length
00-00-01-37 = Vendor-Id (311 (Microsoft) in big-endian format)
00-05 = Vendor-Type (5)
00-04 = Length
00-00-00-1A = Value (EAP-MSCHAPv2)