Sunday, 12 May 2019

Setting a pre-shared key for an L2TP over IPsec Incoming Connection

Windows 10 (i.e. a non-server version of Windows) can act as a VPN server. However not all of the configuration options available to a Routing and Remote Access Server are available via a built-in user interface – in particular the option to set a pre-shared key for incoming L2TP over IPsec connections.

The underlying functionality and API to set and use a pre-shared key is present and is simple to use, although the API documentation is rather brief. The main routine is MprAdminInterfaceSetCredentialsEx and this need only be preceded by a call to MprAdminServerConnect and (strictly speaking) eventually be followed by a call to MprAdminServerDisconnect.

The documentation on docs.microsoft.com for MprAdminInterfaceSetCredentialsEx only mentions a pre-shared key in the description of its third parameter (“dwLevel”): “A value of 1 indicates the information is a pre-shared key for the interface”. The Microsoft Open Specification document “[MS-RRASM]: Routing and Remote Access Server (RRAS) Management Protocol” contains additional (and useful) information. For example, in section “3.1.4.41 RRouterInterfaceSetCredentialsEx”, it mentions that: “If dwLevel is 0x0000002 and hInterface is NULL, the preshared key is used for L2TP”.

The documentation of the companion routine MprAdminInterfaceGetCredentialsEx mentions that: “A value of 1 indicates the information is a pre-shared key for the interface, which is in an encrypted format”. Empirically, the routine always seems to return the string “****************” (16 asterisks) – which corresponds with how a pre-shared key is displayed in the Routing and Remote Access Properties page Security tab.

The L2TP pre-shared key set by MprAdminInterfaceSetCredentialsEx is persisted in the registry as an LSA secret at HKLM\Security\Policy\Secrets\L$_RasServerCredentials#0.

A C# code extract demonstrating the process is shown below.

if (MprAdminServerConnect(null, out IntPtr mpr) == 0)
{
    string key = args[0];

    MPR_CREDENTIALSEX_1 creds = new MPR_CREDENTIALSEX_1 { Size = key.Length, CredentialsInfo = Marshal.StringToHGlobalAnsi(key) };
    MprAdminInterfaceSetCredentialsEx(mpr, IntPtr.Zero, 2, creds);
    MprAdminServerDisconnect(mpr);
}


In a “live” system, the L2TP pre-shared key is an element in a providerContext in the Windows Filtering Platform (WFP) Base Filtering Engine (BFE) configuration. More precisely, it is a presharedKeyAuthentication sub-element of the FWPM_IPSEC_IKE_MM_CONTEXT providerContext named "L2TP Main Mode Policy". The complete BFE configuration can be viewed with the command “netsh wfp show state”.

Setting a pre-shared key via the WFP API


It is possible to set a pre-shared key on a “temporary” basis (until the next reboot or the end of the WFP session) using the WFP API.

A recipe for doing this is to search for the FWPM_IPSEC_IKE_MM_CONTEXT providerContext named "L2TP Main Mode Policy" and then add a slightly modified copy, changing the providerContextKey to a new (unique) value, optionally changing the displayData to something appropriate and changing/extending the ikeMmPolicy to contain a presharedKeyAuthentication method (with the desired pre-shared key value). A new FWPM_LAYER_IKEEXT_V4 filter must also be added to utilise the new providerContext. A C# code extract demonstrating the process is shown below.

FWPM_SESSION0 session = new FWPM_SESSION0();
session.DisplayData.Name = "Gary";
session.Flags = FWPM_SESSION_FLAG_DYNAMIC;

if (FwpmEngineOpen0(null, RPC_C_AUTHN_DEFAULT, null, session, out IntPtr engine) == 0)
{
    var x = new FWPM_PROVIDER_CONTEXT_ENUM_TEMPLATE0 { ProviderContextType = FWPM_PROVIDER_CONTEXT_TYPE.FWPM_IPSEC_IKE_MM_CONTEXT };

    if (FwpmProviderContextCreateEnumHandle0(engine, x, out IntPtr h) == 0)
    {
        if (FwpmProviderContextEnum2(engine, h, 2, out IntPtr entries, out uint m) == 0)
        {
            for (uint i = 0; i < m; i++)
            {
                FWPM_PROVIDER_CONTEXT2 ctx = Marshal.PtrToStructure<FWPM_PROVIDER_CONTEXT2>(Marshal.ReadIntPtr(entries, (int)i * IntPtr.Size));

                Console.WriteLine(ctx.DisplayData.Name);
                if (ctx.DisplayData.Name == "L2TP Main Mode Policy")
                {
                    uint n = ctx.Policy.IkeMmPolicy->NumAuthenticationMethods;

                    IKEEXT_AUTHENTICATION_METHOD2* methods = stackalloc IKEEXT_AUTHENTICATION_METHOD2[(int)n + 1];
                    for (uint j = 0; j < n; j++) methods[j] = ctx.Policy.IkeMmPolicy->AuthenticationMethods[j];
                    string preshared = args[0];
                    methods[n].AuthenticationMethodType = IKEEXT_AUTHENTICATION_METHOD_TYPE.IKEEXT_PRESHARED_KEY;
                    methods[n].PresharedKeyAuthentication.PresharedKey.Size = (uint)preshared.Length;
                    methods[n].PresharedKeyAuthentication.PresharedKey.Data = (byte*)Marshal.StringToHGlobalAnsi(preshared);

                    ctx.ProviderContextKey = Guid.NewGuid();
                    ctx.DisplayData.Name = "Gary Context";
                    ctx.Policy.IkeMmPolicy->NumAuthenticationMethods = n + 1;
                    ctx.Policy.IkeMmPolicy->AuthenticationMethods = methods;

                    if (FwpmProviderContextAdd2(engine, ctx, null, out ulong _) == 0)
                    {
                        ulong weight = 1;

                        FWPM_FILTER0 filter = new FWPM_FILTER0();
                        filter.DisplayData.Name = "Gary Filter";
                        filter.Flags = FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT;
                        filter.LayerKey = FWPM_LAYER_IKEEXT_V4;
                        filter.SubLayerKey = FWPM_SUBLAYER_UNIVERSAL;
                        filter.Weight.Type = FWP_DATA_TYPE.FWP_UINT64;
                        filter.Weight.uint64 = &weight;
                        filter.Action.Type = FWP_ACTION_TYPE.FWP_ACTION_PERMIT;
                        filter.U.ProviderContextKey = ctx.ProviderContextKey;

                        if (FwpmFilterAdd0(engine, filter, null, out ulong _) == 0)
                        {
                            Console.Write("Waiting..."); Console.ReadKey();
                        }
                    }
                }
            }

            FwpmFreeMemory0(&entries);
        }

        FwpmProviderContextDestroyEnumHandle0(engine, h);
    }

    FwpmEngineClose0(engine);
}


The only constraint on the filter subLayerKey and weight is that the filter should have a higher effective weight than the existing FWPM_LAYER_IKEEXT_V4 filter.

Simple trick to set a pre-shared key


It is straightforward to set a pre-shared key with MprAdminInterfaceSetCredentialsEx, but it does require a custom program. There is however a trick, using built-in tools, that seems to work: define a “dummy” IPsec tunnel with “Preshared key” authentication; any tool can be used to do this (e.g. the “Windows Defender Firewall with Advanced Security” control panel applet, the “netsh advfirewall consec add rule” command or the “New-NetIPsecRule” PowerShell cmdlet).

Here are examples of creating suitable “dummy” IPsec tunnels (the endpoint value can be any address that does not apply to any potential traffic) with a shared secret of “XXX”:

netsh advfirewall consec add rule name="Dummy" endpoint1=192.168.3.1/32 endpoint2=any mode=tunnel action=requireinrequireout auth1=computerpsk auth1psk="XXX"

New-NetIPsecRule -DisplayName "Dummy" -Phase1AuthSet (New-NetIPsecPhase1AuthSet -DisplayName "Dummy" -Proposal (New-NetIPsecAuthProposal -Machine -PreSharedKey "XXX")).InstanceID -InboundSecurity Require -OutboundSecurity Require -KeyModule IKEv1 -Mode Tunnel -LocalAddress 192.168.3.1/32 -RemoteAddress Any

The pre-shared key created by this method is persisted in the registry under HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\Phase1AuthenticationSets.

Similar to the L2TP pre-shared key, in a “live” system the pre-shared key created by this method is a presharedKeyAuthentication sub-element of an FWPM_IPSEC_IKE_MM_CONTEXT providerContext named after the Phase1AuthenticationSet ("Dummy", in the examples above). The filter that references this providerContext has a subLayerKey of FWPM_SUBLAYER_IPSEC_TUNNEL which gives the filter a higher effective weight than the L2TP filter.

On an “out-of-the-box” system, the ikeProposals in the L2TP providerContext and the new providerContext are usually identical; if testing (or a review of the “netsh wfp show state” output) indicates discrepancies then the commands to create the “Dummy” tunnel can be extended to compensate for the differences.



No comments:

Post a Comment