【用法】Marvell 88W8801 WiFi模块中CMD_802_11_KEY_MATERIAL命令的用法

一、WPA/WPA2认证命令
88W8801通过固件内部自带embedded supplicant关联WPA/WPA2热点的方法是,先发送CMD_SUPPLICANT_PMK命令设置路由器密码,然后发送CMD_802_11_ASSOCIATE命令连接路由器。

发送CMD_SUPPLICANT_PMK(0x00c4)命令时,action字段设为WIFI_ACT_SET(1),cache_result字段设为0,然后后面紧跟一个MrvlIETypes_Passphrase_t的TLV参数。

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint16_t action;
  uint16_t cache_result;
} WiFi_Cmd_SupplicantPMK;

typedef __packed struct
{
  MrvlIEHeader header;
  uint8_t passphrase[64];
} MrvlIETypes_Passphrase_t;

void test_supplicant(void)
{
  uint8_t buffer[100];
  WiFi_Cmd_SupplicantPMK *cmd = (WiFi_Cmd_SupplicantPMK *)buffer;
  MrvlIETypes_Passphrase_t *passphrase;
  
  cmd->action = WIFI_ACT_SET;
  cmd->cache_result = 0;
  passphrase = (MrvlIETypes_Passphrase_t *)(cmd + 1);
  passphrase->header.type = WIFI_MRVLIETYPES_WPAPASSPHRASE;
  strcpy((char *)passphrase->passphrase, "password");
  passphrase->header.length = strlen((char *)passphrase->passphrase);
  
  WiFi_SendCommand(CMD_SUPPLICANT_PMK, cmd, TLV_NEXT(passphrase) - buffer, test_supplicant_callback, NULL, WIFI_TIMEOUT_SCAN, WIFI_DEFAULT_MAXRETRY);
}

MrvlIETypes_Passphrase_t参数的passphrase字段就是路由器密码,header.length为密码长度。但是要注意,该结构中的header.type字段应该设为0x013c,而不是0x0145,否则将不能认证成功!
收到的命令回应中,如果cache_result=0,则设置成功。如果cache_result=1,则表明设置失败。

typedef enum
{
  WIFI_MRVLIETYPES_WPAPASSPHRASE = 0x013c,
  WIFI_MRVLIETYPES_PASSPHRASE = 0x0145
} WiFi_MrvlIETypes;

执行CMD_802_11_ASSOCIATE命令关联热点时,注意根据认证类型添加相应的TLV参数。例如,对于WPA/WPA2混合模式的热点,有两种认证方式可以选择。一种是WPA认证方式,包括4-way handshake和group key handshake,添加的是MrvlIETypes_VendorParamSet_t类型的TLV。另一种是WPA2认证方式,只含4-way handshake,添加的是MrvlIETypes_RsnParamSet_t类型的TLV。添加哪种TLV就选择哪种认证方式。这两个TLV的内容都可以在CMD_802_11_SCAN命令扫描热点时直接获取,把内容复制过来就行。

二、密钥的获取

(1) MrvlIETypes_KeyParamSet_t
和Marvell 88W8686不同,在88W8801中用CMD_802_11_KEY_MATERIAL命令获取密钥时,必须要带上一个MrvlIETypes_KeyParamSet_t类型的TLV参数,否则收到的命令回应将不包含任何密钥信息!
使用固件自带的WPA/WPA2认证功能(embedded supplicant)连上路由器后,可以用这个命令获取PTK和GTK密钥的内容。

void materials_callback(void *arg, void *data, WiFi_Status status)
{
  WiFi_Cmd_KeyMaterial *resp = (WiFi_Cmd_KeyMaterial *)data;
  MrvlIETypes_KeyParamSet_t *key = &resp->keys;
  MrvlIETypes_StaMacAddr_t *mac;
  
  if (status == WIFI_STATUS_OK)
  {
    // 输出收到的整个回应内容
    dump_data(data, resp->header.frame_header.length);
    
    // 输出所有获取到的密钥
    // info=0x6为单播密钥(PTK), info=0x5为广播密钥(GTK)
    while ((uint8_t *)key < (uint8_t *)&resp->header + resp->header.size)
    {
      printf("[Key] type=%d, info=0x%x, len=%d\n", key->key_type_id, key->key_info, key->key_len);
      dump_data(key->key, key->key_len);
      
      // 转到下一对密钥+MAC地址组合
      mac = (MrvlIETypes_StaMacAddr_t *)TLV_NEXT(key);
      key = (MrvlIETypes_KeyParamSet_t *)TLV_NEXT(mac);
    }
  }
}

void materials(void)
{
  uint8_t data[100];
  WiFi_Cmd_KeyMaterial *cmd = (WiFi_Cmd_KeyMaterial *)data;
  
  cmd->action = WIFI_ACT_GET; // 0
  cmd->keys.header.type = WIFI_MRVLIETYPES_KEYPARAMSET; // 0x0100
  cmd->keys.header.length = 6; // 这个TLV参数只能包含下面三个字段
  cmd->keys.key_type_id = 0;
  cmd->keys.key_info = WIFI_KEYINFO_UNICASTKEY | WIFI_KEYINFO_MULTICASTKEY; // 同时获取PTK和GTK密钥
  cmd->keys.key_len = 0;
  
  WiFi_SendCommand(CMD_802_11_KEY_MATERIAL, cmd, cmd->keys.key - data, materials_callback, NULL, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}

发送命令时,命令里面的MrvlIETypes_KeyParamSet_t参数只包含了三个字段:key_type_id、key_info和key_len。key_type_id和key_len都为0,key_info指明了要获取的密钥类型,这里设置为同时获取单播密钥和广播密钥。

程序运行结果:

CMDRESP 0x805e at 0ms
560001005E8052000900000000000001160002000600100083EB837A1A6E1F03E3D85F0594F451C320010600B0958E042A06000116000200050010000736331101156B1484B483759599AFD120010600B0958E042A06
[Key] type=2, info=0x6, len=16
83EB837A1A6E1F03E3D85F0594F451C3
[Key] type=2, info=0x5, len=16
0736331101156B1484B483759599AFD1

收到的命令回应是两个MrvlIETypes_KeyParamSet_t+MrvlIETypes_StaMacAddr_t组合。这个组合被称为KeyParamSet_v1_t结构。

程序用到的结构体和宏定义如下:

#define WIFI_KEYINFO_KEYENABLED _BV(2)
#define WIFI_KEYINFO_UNICASTKEY _BV(1)
#define WIFI_KEYINFO_MULTICASTKEY _BV(0)

// 已知数据域大小, 求整个结构体的大小
// 例如定义一个很大的buffer, 然后定义一个IEEEType *的指针p指向该buffer
// buffer接收到数据后, 要求出接收到的IEEEType数据的实际大小显然不能用sizeof(IEEEType), 因为定义IEEEType结构体时data的长度定义的是1
// 此时就应该使用TLV_STRUCTLEN(*p)
#define TLV_STRUCTLEN(tlv) (sizeof((tlv).header) + (tlv).header.length)

// 已知本TLV的地址和大小, 求下一个TLV的地址
#define TLV_NEXT(tlv) ((uint8_t *)(tlv) + TLV_STRUCTLEN(*(tlv)))

typedef __packed struct
{
  uint16_t type;
  uint16_t length;
} MrvlIEHeader;

typedef __packed struct
{
  MrvlIEHeader header;
  uint16_t key_type_id;
  uint16_t key_info;
  uint16_t key_len;
  uint8_t key[32];
} MrvlIETypes_KeyParamSet_t;

typedef __packed struct
{
  MrvlIEHeader header;
  uint8_t mac_address[6];
} MrvlIETypes_StaMacAddr_t;

typedef __packed struct
{
  uint16_t length;
  uint16_t type;
} WiFi_SDIOFrameHeader;

typedef __packed struct
{
  WiFi_SDIOFrameHeader frame_header;
  uint16_t cmd_code;
  uint16_t size;
  uint16_t seq_num;
  uint16_t result;
} WiFi_CommandHeader;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint16_t action;
  MrvlIETypes_KeyParamSet_t keys;
} WiFi_Cmd_KeyMaterial;

(2) MrvlIETypes_KeyParamSet_v2_t
88W8801还新增了第二版本的密钥参数结构,使用该结构体还可以获取GTK peer side Rx packet number(PN)。

// 密钥TLV参数 (版本2)
typedef __packed struct
{
  MrvlIEHeader header;
  uint8_t mac_address[6];
  uint8_t key_index;
  uint8_t key_type;
  uint16_t key_info;
} MrvlIETypes_KeyParamSet_v2_t;
// 该结构体后面紧跟下面两个结构体中的一种

// TKIP, AES, WAPI和AES CMAC类型的密钥都可以用此结构体表示
typedef __packed struct
{
  uint8_t PN[8];
  uint16_t key_len;
  uint8_t key[32];
} WiFi_KeyParam_TKIP_AES;

// WEP密钥用此结构体表示
typedef __packed struct
{
  uint16_t key_len; // 5或13
  uint8_t key[13];
} WiFi_KeyParam_WEP;

void materials_callback(void *arg, void *data, WiFi_Status status)
{
  WiFi_Cmd_KeyMaterial *resp = (WiFi_Cmd_KeyMaterial *)data;
  MrvlIETypes_KeyParamSet_v2_t *key = (MrvlIETypes_KeyParamSet_v2_t *)&resp->keys;
  WiFi_KeyParam_TKIP_AES *key_param;
  WiFi_KeyParam_WEP *wep;
  
  if (status == WIFI_STATUS_OK)
  {
    // 输出收到的整个回应内容
    dump_data(data, resp->header.frame_header.length);
    
    // 输出所有获取到的密钥
    // info=0x6为单播密钥(PTK), info=0x5为广播密钥(GTK)
    while ((uint8_t *)key < (uint8_t *)&resp->header + resp->header.size)
    {
      printf("[Key] MAC=%02X:%02X:%02X:%02X:%02X:%02X", key->mac_address[0], key->mac_address[1], key->mac_address[2], key->mac_address[3], key->mac_address[4], key->mac_address[5]);
      printf(", index=%d, type=0x%x, info=%d", key->key_index, key->key_type, key->key_info);
      
      // 转到KeyParam部分
      key_param = (WiFi_KeyParam_TKIP_AES *)(key + 1);
      if (key->key_type == WIFI_KEYTYPE_WEP)
      {
        wep = (WiFi_KeyParam_WEP *)key_param;
        printf(", len=%d\n", wep->key_len);
        dump_data(wep->key, wep->key_len);
      }
      else
      {
        printf(", len=%d\n", key_param->key_len);
        printf("PN: ");
        dump_data(key_param->PN, sizeof(key_param->PN));
        printf("Key: ");
        dump_data(key_param->key, key_param->key_len);
      }
      
      // 转到下一对密钥+KeyParam组合
      key = (MrvlIETypes_KeyParamSet_v2_t *)TLV_NEXT(key);
    }
  }
}

void materials(void)
{
  uint8_t data[100];
  WiFi_Cmd_KeyMaterial *cmd = (WiFi_Cmd_KeyMaterial *)data;
  MrvlIETypes_KeyParamSet_v2_t *keyv2 = (MrvlIETypes_KeyParamSet_v2_t *)&cmd->keys; // 用版本2的结构体表示密钥参数
  
  cmd->action = WIFI_ACT_GET; // 0
  keyv2->header.type = WIFI_MRVLIETYPES_KEYPARAMSETV2; // 0x019c
  keyv2->header.length = TLV_PAYLOADLEN(*keyv2); // 10
  memset(keyv2->mac_address, 0, sizeof(keyv2->mac_address)); // MAC地址可为空
  keyv2->key_index = 0;
  keyv2->key_type = 0;
  keyv2->key_info = WIFI_KEYINFO_UNICASTKEY | WIFI_KEYINFO_MULTICASTKEY; // 同时获取PTK和GTK密钥
  
  WiFi_SendCommand(CMD_802_11_KEY_MATERIAL, cmd, sizeof(WiFi_Cmd_KeyMaterial) - sizeof(cmd->keys) + TLV_STRUCTLEN(*keyv2), materials_callback, NULL, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}

程序的运行结果如下:

CMDRESP 0x805e at 0ms
5E0001005E805A000900000000009C012400B0958E042A06000206009FE50000000000001000F70686E4FD0A2FAC9738A55D39990A119C012400B0958E042A0601020500207300000000000010000736331101156B1484B483759599AFD1
[Key] MAC=B0:95:8E:04:2A:06, index=0, type=0x2, info=6, len=16
PN: 9FE5000000000000
Key: F70686E4FD0A2FAC9738A55D39990A11
[Key] MAC=B0:95:8E:04:2A:06, index=1, type=0x2, info=5, len=16
PN: 2073000000000000
Key: 0736331101156B1484B483759599AFD1

在Marvell提供的官方Linux驱动程序的头文件里面的mlan_fw.h中,就只有MrvlIEtype_KeyParamSetV2_t这个版本的结构体。

三、密钥的手动设置
设置密钥必须使用V2版本的TLV,设置密钥后可以用V1或V2版本的TLV来查看(GET)密钥。

如果将mac_address字段设为0,则不连接热点也能设置成功,连接热点后,MAC地址会自动变为所连热点的MAC地址,并可以正常使用。

如果是在已经连接了热点的情况下设置的密钥(mac_address=0),那么mac_address字段自动变为所连热点的MAC地址,可以正常使用。

如果mac_address字段不为0,则必须要连接了相应的热点之后才能设置成功!

只要密钥可以正常使用,断开热点后,密钥不会被自动清除掉,但MAC地址会被清零。

设置WEP密钥不能使用CMD_802_11_SET_WEP命令,必须使用CMD_802_11_KEY_MATERIAL命令结合MrvlIETypes_KeyParamSet_v2_t参数设置。key_info字段中,KeyInfo可用的选项为:
Bit3 isDefaultKey(是否为默认密钥),Bit2 isKeyEnabled(是否启用),Bit1 isUnicastKey(必须为1),Bit0 isMulticastKey(必须为0)
必须为1的位,如果被设置成了0,会被自动置为1。必须为0的位,如果被设置成了1,会被自动清0。
key_param字段共有7或15个字节,前两个字节表示WEP密钥的大小(5或13,小端序),后面为WEP密钥内容。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值