本文讲解如何使用X.509认证方式建立MQTT连接,从而完成设备的接入。

背景说明

物联网平台支持基于X.509格式的证书对设备进行TLS握手时的双向认证,对安全有特殊性要求的用户, 可以用这个功能, 换用X509证书而不是共享秘钥向云端认证身份。

如何获取证书请参见使用X.509证书认证 页面。

注意
  • 仅支持基于MQTT协议连接物联网平台的设备可以使用该方式进行设备认证, 如HTTP上云设备就用不了这种方式。
  • 仅华东2(上海)站点支持X.509认证连接。
  • 连网方式为LoRaWAN的产品不支持。
  • X.509证书的设备认证方式与常用的共享密钥互斥,不支持使用证书与deviceSecret字符串同时认证连接。

例程讲解

现对照demos/mqtt_x509_auth_demo.c例程演示如何使用X.509认证方式建立MQTT连接。

本文只关注X.509连接时需要设置的参数和步骤,对MQTT的API如何使用不做讲解,需要用户关注的部分, 都已在注释中用 TODO 标明。

  1. 获取证书

    在配置demo前, 用户可先按照以下步骤创建X.509认证设备并下载设备证书和私钥。

    • 进入物联网平台产品页面, 点击创建产品
    • 认证模式一栏选择 X.509证书
    • 使用私有证书一栏选择默认的
    • 需使用私有证书的用户参见CA证书管理教程。
    • 在刚创建的产品下添加新设备, 进入该设备的设备详情页, 单击X.509证书对应的下载按钮, 下载证书和私钥。
  2. MQTT参数配置

    设备端只需要调用aiot_mqtt_setopt接口正确配置MQTT会话实例的几个参数, 即可切换到X.509认证模式。

    1. 配置域名和端口号

      X.509认证模式连接的服务器域名和端口如下。

      • 域名: x509.itls.cn-shanghai.aliyuncs.com
      • 端口号: 1883

      配置MQTT连接服务的域名和端口号的示例代码。

      /* TODO: 使用X509双向认证时, MQTT连接的服务器域名与通常情况不同 */
          char       *host = "x509.itls.cn-shanghai.aliyuncs.com";
      
          uint16_t    port = 1883; /* X.509服务器目的端口是1883 */
      
          /* 配置MQTT服务器地址 */
          aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)host);
          /* 配置MQTT服务器端口 */
          aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
    2. 配置安全凭证

      将从物联网平台下载的设备端证书和私钥配置到cred结构体中, 并配置MQTT的AIOT_MQTTOPT_NETWORK_CRED选项。

      /*
       * TODO: 用户需按照以下格式, 将从控制台下载到的证书, 写入替换以下字符串
       *
       * 实际量产时, 设备上应该把证书存储到特定区域以方便产线烧写和确保数据安全
       *
       */
      const char client_cert[] = {
          "-----BEGIN CERTIFICATE-----\r\n" \
          "Your X.509 certificate\r\n" \
          "-----END CERTIFICATE-----\r\n" \
      };
      
      /*
       * TODO: 用户需按照以下格式, 将从控制台下载到的私钥, 写入替换以下字符串
       *
       * 实际量产时, 设备上应该把私钥存储到特定区域以方便产线烧写和确保数据安全
       *
       */
      const char client_private_key[] = {
          "-----BEGIN RSA PRIVATE KEY-----\r\n" \
          "Your X.509 PrivateKey\r\n" \
          "-----END RSA PRIVATE KEY-----\r\n" \
      };
      
      ...
      ...
      
      int main(int argc, char *argv[])
      {
          ...
          /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
          aiot_sysdep_network_cred_t cred;
      
          /* 创建SDK的安全凭据, 用于建立TLS连接 */
          memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
          cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA;  /* 使用RSA证书校验MQTT服务端 */
          cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
          cred.sni_enabled = 1;                               /* TLS建连时, 支持Server Name Indicator */
          cred.x509_server_cert = ali_ca_crt;                 /* 用来验证MQTT服务端的RSA根证书 */
          cred.x509_server_cert_len = strlen(ali_ca_crt);     /* 用来验证MQTT服务端的RSA根证书长度 */
      
          /* TODO: 留意以下4行, 使用X509双向认证时, 用户对安全凭据的设置就只要增加这一部分 */
          cred.x509_client_cert = client_cert;
          cred.x509_client_cert_len = strlen(client_cert);
          cred.x509_client_privkey = client_private_key;
          cred.x509_client_privkey_len = strlen(client_private_key);
      
          /* 配置网络连接的安全凭据, 上面已经创建好了 */
          aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
    3. 配置设备设备证书为空字符串
      /* TODO: 使用X.509认证模式建连的设备, 三元组信息都需用空字符串进行配置 */
          char *product_key       = "";
          char *device_name       = "";
          char *device_secret     = "";
      
          /* 配置设备productKey */
          aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
          /* 配置设备deviceName */
          aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
          /* 配置设备deviceSecret */
          aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
  3. 从云端获取设备productKey和deviceName并保存

    设备通过X.509认证方式连上云平台后, 云端会立即通过MQTT通道, 下发设备的productKeydeviceName到设备端。

    消息在/ext/auth/identity/response这个Topic下发, Payload格式为:

    {
        "productKey":"***",
        "deviceName":"***"
    }

    demo使用以下函数解析出productKeydeviceName并保存到全局变量中。

    /* 获取云端下发的设备信息, 包括 productKey 和 deviceName */
    
    /* TODO: 用户需要在这个回调函数中把云端下推的 productKey 和 deviceName 做持久化的保存, 后面使用SDK的其它接口都会用到 */
    static void demo_get_device_info(const char *topic, uint16_t topic_len, const char *payload, uint32_t payload_len)
    {
        const char *target_topic = "/ext/auth/identity/response";
        char *p_product_key = NULL;
        uint32_t product_key_len = 0;
        char *p_device_name = NULL;
        uint32_t device_name_len = 0;
        int32_t res = STATE_SUCCESS;
    
        if (topic_len != strlen(target_topic) || memcmp(topic, target_topic, topic_len) != 0) {
            return;
        }
    
        /* TODO: 此处为说明上的方便, 使用了SDK内部接口 core_json_value(), 这不是一个官方API, 未来有可能变化
    
                 用户实际使用时, 需要换成用自己设备上可用的JSON解析函数库的接口处理payload, 比如流行的 cJSON 等
        */
        res = core_json_value(payload, payload_len, "productKey", strlen("productKey"), &p_product_key, &product_key_len);
        if (res < 0) {
            return;
        }
        res = core_json_value(payload, payload_len, "deviceName", strlen("deviceName"), &p_device_name, &device_name_len);
        if (res < 0) {
            return;
        }
    
        if (g_product_key == NULL) {
            g_product_key = malloc(product_key_len + 1);
            if (NULL == g_product_key) {
                return;
            }
    
            memset(g_product_key, 0, product_key_len + 1);
            memcpy(g_product_key, p_product_key, product_key_len);
        }
        if (g_device_name == NULL) {
            g_device_name = malloc(device_name_len + 1);
            if (NULL == g_product_key) {
                return;
            }
    
            memset(g_device_name, 0, device_name_len + 1);
            memcpy(g_device_name, p_device_name, device_name_len);
        }
    
        printf("device productKey: %s\r\n", g_product_key);
        printf("device deviceName: %s\r\n", g_device_name);
    }
  4. 运行日志

    以下是例程的运行日志, 演示了一次符合预期的X.509连接认证过程。

    [1581328272.011][LK-0313] MQTT user calls aiot_mqtt_connect api, connect
    [1581328272.011][LK-0317] &
    [1581328272.011][LK-0318] B36DABAE2EBB7B8FA6FFD92B217E13D97C1AB196441561B0F71B3C3BF1DACA05
    [1581328272.011][LK-0319] .|timestamp=2524608000000,_ss=1,_v=sdk-c-4.0.0,securemode=2,signmethod=hmacsha256,ext=1,|
    establish mbedtls connection with server(host='x509.itls.cn-shanghai.aliyuncs.com', port=[1883])
    success to establish mbedtls connection, fd = 3(cost 50782 bytes in total, max used 56870 bytes)
    [1581328272.377][LK-0313] MQTT connect success in 353 ms
    AIOT_MQTTEVT_CONNECT
    [1581328272.377][LK-0309] pub: /ext/auth/identity/response
    
    [LK-030A] < 7B 22 70 72 6F 64 75 63  74 4B 65 79 22 3A 22 61 | {"productKey":"a
    [LK-030A] < 31 41 6A 6F 49 33 36 4D  64 39 22 2C 22 64 65 76 | 1AjoXXXXXX","dev
    [LK-030A] < 69 63 65 4E 61 6D 65 22  3A 22 78 35 30 39 5F 61 | iceName":"x509_a
    [LK-030A] < 75 74 68 5F 6D 6F 64 65  5F 64 65 6D 6F 22 7D    | uth_mode_demo"}
    
    pub, qos: 0, topic: /ext/auth/identity/response
    pub, payload: {"productKey":"a1AjoXXXXXX","deviceName":"x509_auth_mode_demo"}
    device productKey: a1AjoXXXXXX
    device deviceName: x509_auth_mode_demo
    heartbeat response

    例程日志显示设备建连成功,并打印出设备得到的productKeydeviceName