Manual SA Keying

The following sample code demonstrates how to create Security Associations (SA) using Windows Filtering Platform.

#include <windows.h>
#include <fwpmu.h>
#include <stdio.h>
#include <conio.h>

#pragma comment(lib, "fwpuclnt.lib")
#pragma comment(lib, "ws2_32.lib")

// 5fb216a8-e2e8-4024-b853-391a4168641e
const GUID PROVIDER_KEY =
{
   0x5fb216a8,
   0xe2e8,
   0x4024,
   { 0xb8, 0x53, 0x39, 0x1a, 0x41, 0x68, 0x64, 0x1e }
};

#define EXIT_ON_ERROR(fnName) \
   if (result != ERROR_SUCCESS) \
   { \
      printf(#fnName " = 0x%08X\n", result); \
      goto CLEANUP; \
   }

unsigned long inet_addrW(__in PCWSTR cp)
{
   size_t converted;
   char mbstr[sizeof("255.255.255.255")];
   errno_t cerr;

   cerr = wcstombs_s(&converted, mbstr, sizeof(mbstr), cp, wcslen(cp));

   return (cerr == 0) ? inet_addr(mbstr) : INADDR_NONE;

}

// Helper function to delete an SA context and the associated transport
// filters.
void DeleteSaContextAndFilters(
        __in HANDLE engine,
        __in UINT64 inFilterId,
        __in UINT64 outFilterId,
        __in UINT64 saId
        )
{
   DWORD result;

   // Allow the LUIDs to be zero, so we can use this function to cleanup
   // partial results.
   if (saId != 0)
   {
      result = IPsecSaContextDeleteById0(engine, saId);
      if (result != ERROR_SUCCESS)
      {
         // There's not much we can do if delete fails, so continue trying to
         // clean up the remaining objects.
         printf("IPsecSaContextDeleteById0 = 0x%08X\n", result);
      }
   }
   if (outFilterId != 0)
   {
      result = FwpmFilterDeleteById0(engine, outFilterId);
      if (result != ERROR_SUCCESS)
      {
         printf("FwpmFilterDeleteById0 = 0x%08X\n", result);
      }
   }
   if (inFilterId != 0)
   {
      result = FwpmFilterDeleteById0(engine, inFilterId);
      if (result != ERROR_SUCCESS)
      {
         printf("FwpmFilterDeleteById0 = 0x%08X\n", result);
      }
   }
}


// Illustrates the first part of SA establishment using AH. The caller supplies
// the local and remote IP addresses and the inbound authentication key. The
// function returns the inbound SPI.
DWORD AddInboundSa(
         __in HANDLE engine,
         __in PCWSTR filterName,
         __in_opt const GUID* providerKey,
         __in UINT32 localAddr,
         __in UINT32 remoteAddr,
         __in const FWP_BYTE_BLOB* authKey,
         __out UINT64* inFilterId,
         __out UINT64* outFilterId,
         __out UINT64* saId,
         __out IPSEC_SA_SPI* spi
         )
{
   DWORD result = ERROR_SUCCESS;
   UINT64 tmpInFilterId = 0, tmpOutFilterId = 0, tmpSaId = 0;
   FWPM_FILTER_CONDITION0 conds[2];
   FWPM_FILTER0 filter;
   IPSEC_TRAFFIC0 outTraffic;
   IPSEC_GETSPI0 getSpi;
   IPSEC_SA_AUTH_INFORMATION0 info;
   IPSEC_SA0 sa;
   IPSEC_SA_BUNDLE0 bundle;

   //////////
   // Create IPsec filters matching the local and remote IP address at both the
   // inbound and outbound transport layers. This has to be done first since we
   // need the filter LUIDs to create the SA context and get the inbound SPI.
   //////////

   conds[0].fieldKey = FWPM_CONDITION_IP_LOCAL_ADDRESS;
   conds[0].matchType = FWP_MATCH_EQUAL;
   conds[0].conditionValue.type = FWP_UINT32;
   conds[0].conditionValue.uint32 = localAddr;

   conds[1].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
   conds[1].matchType = FWP_MATCH_EQUAL;
   conds[1].conditionValue.type = FWP_UINT32;
   conds[1].conditionValue.uint32 = remoteAddr;

   // Fill in the common fields shared by both filters.
   memset(&filter, 0, sizeof(filter));
   // For MUI compatibility, object names should be indirect strings. See
   // SHLoadIndirectString for details.
   filter.displayData.name = (PWSTR)filterName;
   // Link all objects to our provider. When multiple providers are installed
   // on a computer, this makes it easy to determine who added what.
   filter.providerKey = (GUID*)providerKey;
   filter.numFilterConditions = 2;
   filter.filterCondition = conds;
   filter.action.type = FWP_ACTION_CALLOUT_TERMINATING;

   // Add the inbound filter.
   filter.layerKey = FWPM_LAYER_INBOUND_TRANSPORT_V4;
   filter.action.calloutKey = FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4;
   result = FwpmFilterAdd0(
               engine,
               &filter,
               NULL,
               &tmpInFilterId
               );
   EXIT_ON_ERROR(FwpmFilterAdd0);

   // Add the outbound filter.
   filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V4;
   filter.action.calloutKey = FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4;
   result = FwpmFilterAdd0(
               engine,
               &filter,
               NULL,
               &tmpOutFilterId
               );
   EXIT_ON_ERROR(FwpmFilterAdd0);

   // Create the SA context using the outbound traffic descriptor.
   memset(&outTraffic, 0, sizeof(outTraffic));
   outTraffic.ipVersion = FWP_IP_VERSION_V4;
   outTraffic.localV4Address = localAddr;
   outTraffic.remoteV4Address = remoteAddr;
   outTraffic.trafficType = IPSEC_TRAFFIC_TYPE_TRANSPORT;
   outTraffic.ipsecFilterId = tmpOutFilterId;
   result = IPsecSaContextCreate0(
               engine,
               &outTraffic,
               NULL,
               &tmpSaId
               );
   EXIT_ON_ERROR(IPsecSaContextCreate0);

   // Get the inbound SPI using the inbound traffic descriptor.
   memset(&getSpi, 0, sizeof(getSpi));
   getSpi.inboundIpsecTraffic.ipVersion = FWP_IP_VERSION_V4;
   getSpi.inboundIpsecTraffic.localV4Address = localAddr;
   getSpi.inboundIpsecTraffic.remoteV4Address = remoteAddr;
   getSpi.inboundIpsecTraffic.trafficType = IPSEC_TRAFFIC_TYPE_TRANSPORT;
   getSpi.inboundIpsecTraffic.ipsecFilterId = tmpInFilterId;
   getSpi.ipVersion = FWP_IP_VERSION_V4;
   result = IPsecSaContextGetSpi0(
               engine,
               tmpSaId,
               &getSpi,
               spi
               );
   EXIT_ON_ERROR(result);

   /////////
   // Add the inbound SA using the authentication key supplied by the caller.
   /////////

   memset(&info, 0, sizeof(info));
   info.authTransform.authTransformId = IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96;
   info.authKey = *authKey;

   memset(&sa, 0, sizeof(sa));
   sa.spi = *spi;
   sa.saTransformType = IPSEC_TRANSFORM_AH;
   sa.ahInformation = &info;

   memset(&bundle, 0, sizeof(bundle));
   bundle.numSAs = 1;
   bundle.saList = &sa;
   bundle.ipVersion = FWP_IP_VERSION_V4;

   result = IPsecSaContextAddInbound0(engine, tmpSaId, &bundle);
   EXIT_ON_ERROR(IPsecSaContextAddInbound0);

   // Return the various LUIDs to the caller, so he can clean up.
   *inFilterId = tmpInFilterId;
   *outFilterId = tmpOutFilterId;
   *saId = tmpSaId;

CLEANUP:
   if (result != ERROR_SUCCESS)
   {
      DeleteSaContextAndFilters(
         engine,
         tmpInFilterId,
         tmpOutFilterId,
         tmpSaId
         );
   }
   return result;
}


// After successfully calling AddInboundSa, the caller will use some
// out-of-band mechanism to exchange inbound SPIs and authentication keys with
// the remote peer (who presumably has also called AddInboundSa). The remote
// peer's inbound SPI and key become the outbound SPI and key on the local
// machine.
DWORD AddOutboundSa(
         __in HANDLE engine,
         __in UINT64 saId,
         __in IPSEC_SA_SPI spi,
         __in const FWP_BYTE_BLOB* authKey
         )
{
   DWORD result = ERROR_SUCCESS;
   IPSEC_SA_AUTH_INFORMATION0 info;
   IPSEC_SA0 sa;
   IPSEC_SA_BUNDLE0 bundle;

   memset(&info, 0, sizeof(info));
   info.authTransform.authTransformId = IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96;
   info.authKey = *authKey;

   memset(&sa, 0, sizeof(sa));
   sa.spi = spi;
   sa.saTransformType = IPSEC_TRANSFORM_AH;
   sa.ahInformation = &info;

   memset(&bundle, 0, sizeof(bundle));
   bundle.numSAs = 1;
   bundle.saList = &sa;
   bundle.ipVersion = FWP_IP_VERSION_V4;

   result = IPsecSaContextAddOutbound0(engine, saId, &bundle);
   EXIT_ON_ERROR(IPsecSaContextAddOutbound0);

CLEANUP:
   return result;
}