Creating MPPE Attributes in an EAP Extension

When an EAP extension succesfully completes authentication, it can fill in the PPP_EAP_OUTPUT pUserAttributes field with MPPE key information for use by other networking components. For example, RAS can use those keys for data encryption, and WPA/WPA2 will use them for the 4-way handshake on an 802.11 link.

The PPP_EAP_OUTPUT pUserAttribute field points to an array of RAS_AUTH_ATTRIBUTE structures, terminated by an array element with raaType = raatMinimum. Documentation on the format of the RAS_AUTH_ATTRIBUTE structure can be found at https://msdn2.microsoft.com/en-gb/library/aa363535.aspx

For MPPE Keys, there will be 2 elements in the array of RAS_AUTH_ATTRIBUTES: one for the send key and one for the receive key. For the MPPE Key attributes, the PVOID Value field of the RAS_AUTH_ATTRIBUTE will point to a structure that is described in https://msdn2.microsoft.com/en-us/library/aa363636.aspx. See the section labeled "eatVendorSpecific".

Below are some sample utility functions that can assist in the creation of the data structures that contain these keys.

 struct RAS_AUTH_ATTRIBUTE_VALUE
{
    BYTE VendorId[4];    // network byte order
    BYTE VendorType;
    BYTE Length;         // number of bytes from VendorType to the end of the value
    BYTE VendorValue[1]; // Length-2 bytes long
};
      
DWORD
MyEapUtilAuthAttributeInsertVSA(
    OUT RAS_AUTH_ATTRIBUTE *    pAttribute,
    IN  DWORD                   VendorId,
    IN  BYTE                    VendorType,
    IN  PBYTE                   pVendorValue,
    IN  BYTE                    cbVendorValue)
//
//    Insert a vendor specific attribute into the location pointed to by pAttribute.
//
{
      DWORD  dwResult = NO_ERROR;

      ASSERT(cbVendorValue < 254);

      pAttribute->raaType = raatVendorSpecific;
      pAttribute->dwLength = offsetof(struct RAS_AUTH_ATTRIBUTE_VALUE, VendorValue) + cbVendorValue;
      pAttribute->Value = LocalAlloc(LPTR, pAttribute->dwLength);
      if (pAttribute->Value == NULL)
      {
            dwResult = ERROR_OUTOFMEMORY;
      }
      else
      {
            struct RAS_AUTH_ATTRIBUTE_VALUE *pAttributeValue = (struct RAS_AUTH_ATTRIBUTE_VALUE *)pAttribute->Value;

            VendorId = htonl(VendorId);
            memcpy(&pAttributeValue->VendorId[0], &VendorId, sizeof(VendorId));
            pAttributeValue->VendorType = VendorType;
            pAttributeValue->Length = 2 + cbVendorValue; // 1 byte for VendorType, 1 byte for Length, then cbVendorValue bytes of VendorValue
            memcpy(&pAttributeValue->VendorValue[0], pVendorValue, cbVendorValue );
      }
      return dwResult;
}

RAS_AUTH_ATTRIBUTE *
MyEapUtilAuthAttributeArrayAlloc(
      DWORD nAttrs)
//
//  Allocate an array of sufficient size to hold the requested number
//  of authentication attributes.
//
{
      RAS_AUTH_ATTRIBUTE *pAttributes;

      nAttrs += 1; // Add one for the terminator
      pAttributes = (RAS_AUTH_ATTRIBUTE *)(LocalAlloc(LPTR, nAttrs * sizeof(RAS_AUTH_ATTRIBUTE)));
      if (pAttributes)
      {
            // Set the terminator array element
            pAttributes[nAttrs - 1].raaType = raatMinimum;
      }
      return pAttributes;
}

void
MyEapUtilAuthAttributeArrayFree(
      RAS_AUTH_ATTRIBUTE *pAttributes)
//
//  Free an array of authentication attribute values.
//
{
    if (pAttributes)
    {
        for (int i=0; pAttributes[i].raaType != raatMinimum; i++)
        {
            if (pAttributes[i].Value)
            {
                SecureZeroMemory(pAttributes[i].Value, pAttributes[i].dwLength);
                LocalFree(pAttributes[i].Value);
                pAttributes[i].Value = NULL;
            }
        }
        LocalFree(pAttributes);
    }
}

#define VENDOR_MICROSOFT     311
#define MS_VSA_MPPE_Send_Key 16
#define MS_VSA_MPPE_Recv_Key 17

#define MAX_MPPEKEY_LENGTH   32

// Format of the MPPE Key
// Byte 1-2:       Salt
// Byte 3:         Key Length (this is the length of the Actual Key and does not include Salt, Key Length and Padding fields) 
// Byte 4-35:      Actual Key  (Most access points require 32 byte keys for WPA)
// Byte 35-50:     Padding (required to make the length of the MPPE Key (not including the Salt) a multiple of 16 bytes)

struct MPPEKey
{
    BYTE Salt[2];
    BYTE KeyLength;
    BYTE Key[MAX_MPPEKEY_LENGTH];
    BYTE Padding[15];
};

DWORD
MyEapUtilAuthAttributeInsertMPPEKeyVSA(
    OUT RAS_AUTH_ATTRIBUTE *    pAttribute,
    IN  BYTE                    VendorType, // MS_VSA_MPPE_Send_Key or MS_VSA_MPPE_Recv_Key
    IN  BYTE const * const      pKey,
    IN  size_t                  cbKey)
//
//  Insert an MPPEKey VSA into the location specified by pAttribute.
//
{
    DWORD  dwResult;
    struct MPPEKey MPPEKey;
 
    if (cbKey > MAX_MPPEKEY_LENGTH)
        return ERROR_INVALID_PARAMETER;

    memset(&MPPEKey, 0, sizeof(MPPEKey));
    MPPEKey.KeyLength = cbKey;
    memcpy(&MPPEKey.Key[0], pKey, cbKey);
    dwResult = MyEapUtilAuthAttributeInsertVSA(pAttribute, VENDOR_MICROSOFT, VendorType, (PBYTE)&MPPEKey, sizeof(MPPEKey) - MAX_MPPEKEY_LENGTH + cbKey);  
    SecureZeroMemory(&MPPEKey, sizeof(MPPEKey));
    return dwResult;
}
    
DWORD 
MyEapUtilCreateMPPEAuthAttributes(
    BYTE const * const pSendKey,
    size_t             cbSendKey,
    BYTE const * const pRecvKey,
    size_t             cbRecvKey,
    RAS_AUTH_ATTRIBUTE **ppSendRecvKeyAttr)
//
// Save the MPPE Send and Recv Session Keys as Auth Attributes
//
{
    DWORD dwResult;
    RAS_AUTH_ATTRIBUTE* pSendRecvKeyAttr;

    // Allocate an auth attribute array able to hold 2 attributes (send key and recv key)
    pSendRecvKeyAttr = MyEapUtilAuthAttributeArrayAlloc(2); 
    if ( NULL == pSendRecvKeyAttr )
    {
        dwResult = ERROR_OUTOFMEMORY;
        goto done;
    }

    // Add the send key to the attribute array at index 0
    dwResult = MyEapUtilAuthAttributeInsertMPPEKeyVSA(&pSendRecvKeyAttr[0], MS_VSA_MPPE_Send_Key, pSendKey, cbSendKey);
    if (dwResult != NO_ERROR)
          goto done;

    // Add the receive key to the attribute array at index 1
    dwResult = MyEapUtilAuthAttributeInsertMPPEKeyVSA(&pSendRecvKeyAttr[1], MS_VSA_MPPE_Recv_Key, pRecvKey, cbRecvKey);
    if (dwResult != NO_ERROR)
          goto done;
          
done:
    if (dwResult != NO_ERROR)
    {
        // Free any resources
        MyEapUtilAuthAttributeArrayFree(pSendRecvKeyAttr);
        pSendRecvKeyAttr = NULL;
    }

    *ppSendRecvKeyAttr = pSendRecvKeyAttr;
    return dwResult;
}