Renesas RXファミリ用 FreeRTOS-LTS (iot-reference-rx) でIoT デバイスの証明書を更新する

# はじめに
最近、Renesas RXファミリ用 FreeRTOS-LTSサンプルプログラム (iot-reference-rx)がGitHub上に公開されました (iot-reference-rxには、FreeRTOS-v202210.01-LTSのライブラリを使用した各種デモプログラム/プロジェクトファイルが配置されています)。

* [renesas/iot-reference-rx](https://github.com/renesas/iot-reference-rx)

このiot-reference-rxで試すことができるデモプログラムは以下の3つです
これらのデモプログラムは複数を同時に実行することが可能なようです。

1. PubSub (AWS IoT Coreに簡単なMQTTメッセージを送信する)
2. OTA (AWS IoT Coreを使用してファームウェアアップデートを実行する)
3. Fleet Provisioning (共通の証明書/秘密鍵を使用してデバイス個別の証明書を取得する)

今回はiot-reference-rxを変更してデバイス個別の証明書を更新するデモを追加しました。
**これを使えば有効期限が近い証明書を更新することができます。**

既にAWS公式に証明書更新のためのアプローチが公開されています。
今回はこのアプローチに沿ってデモの実装を進めました。

* [AWS IoT を利用して IoT デバイスの証明書の更新を管理する方法](https://aws.amazon.com/jp/blogs/news/how-to-manage-iot-device-certificate-rotation-using-aws-iot/)

実装したデモは以下GitHubリポジトリに公開しています。

* [nizuki926/iot-reference-rx](https://github.com/nizuki926/iot-reference-rx/tree/main)
* Branch Name : v202210.01-lts-rx-1.1.2-update-cert

上記アプローチはスケジュールを組んで証明書の有効期限を確認しています。
今回は簡易的なデモで単純にデモ実行時に、デバイスからAWS IoT CoreにCSRリクエストを送信して、AWS Lambda上で証明書を更新して、作成された証明書をデバイスに送り返す実装になっています。

```mermaid
sequenceDiagram
  participant CK as CK-RX65N
  participant IOT as AWS IoT Core
  participant LAMBDA as AWS Lambda
  CK->>CK: Generate Key-Pair and CSR
  CK->>IOT: Connect to AWS using exist certificate
  CK->>+IOT: Publish CSR request
  IOT->>LAMBDA: Forward CSR and clientid
  LAMBDA->>IOT: Generate certificate from CSR
  LAMBDA->>IOT: Link the created certificate to thing and policy
  LAMBDA->>IOT: Send created certificate to device
  IOT->>CK: Send created certificate to device
  CK->>IOT: Connect to AWS using created certificate
```

準備した環境は以下の通りです。

1. CK-RX65N (評価ボード)
2. e2 studio 2023-10 (統合開発環境)
3. CC-RX V3.05.00 (RXマイコン用コンパイラ)

# 証明書更新デモの実装
## corePKCS11に更新用の証明書/鍵ペアの項目を増やす
更新された証明書および鍵ペアをデータフラッシュに格納するためcorePKCS11ライブラリに更新された証明書および鍵ペアの項目を増やします。
以下のソースコード変更に合わせて`core_pkcs11_config.h`の各種設定も見直します。

```c:rm_aws_pkcs11_pal_littlefs.c
enum eObjectHandles
{
    /* ... */
#if pkcs11configMAX_NUM_OBJECTS >= 10
    eAwsUpdateDevicePrivateKey,
#endif
#if pkcs11configMAX_NUM_OBJECTS >= 11
    eAwsUpdateDevicePublicKey,
#endif
#if pkcs11configMAX_NUM_OBJECTS >= 12
    eAwsUpdateDeviceCertificate,
#endif
};

uint8_t g_object_handle_dictionary[pkcs11configMAX_NUM_OBJECTS][pkcs11configMAX_LABEL_LENGTH + 1] =
{
    /* ... */
#if pkcs11configMAX_NUM_OBJECTS >= 10
    pkcs11configLABEL_UPDATE_DEVICE_PRIVATE_KEY_FOR_TLS,
#endif
#if pkcs11configMAX_NUM_OBJECTS >= 11
    pkcs11configLABEL_UPDATE_DEVICE_PUBLIC_KEY_FOR_TLS,
#endif
#if pkcs11configMAX_NUM_OBJECTS >= 12
    pkcs11configLABEL_UPDATE_DEVICE_CERTIFICATE_FOR_TLS,
#endif
};
```

またKVStoreの項目にも更新された証明書および鍵ペアの項目を追加します。
ただし、今回更新された証明書および鍵ペアはCLIで入力する必要がないため、この変更は必要ないかもしれません。

```c:store.h
#define KVSTORE_KEYS                                       \
    {                                                      \
        [ KVS_CORE_THING_NAME ] = "thing_name",            \
        [ KVS_CORE_MQTT_ENDPOINT ] = "mqtt_endpoint",      \
        [ KVS_DEVICE_CERT_ID ] = pkcs11configLABEL_DEVICE_CERTIFICATE_FOR_TLS,                \
		[ KVS_DEVICE_PRIVKEY_ID ] = pkcs11configLABEL_DEVICE_PRIVATE_KEY_FOR_TLS,         \
		[ KVS_DEVICE_PUBKEY_ID ] = pkcs11configLABEL_DEVICE_PUBLIC_KEY_FOR_TLS,         \
		[ KVS_ROOT_CA_ID ] = "root_ca_id",         \
		[ KVS_TEMPLATE_NAME ] = "template_name",         \
		[ KVS_CLAIM_CERT_ID ] = pkcs11configLABEL_CLAIM_CERTIFICATE,         \
		[ KVS_CLAIM_PRIVKEY_ID ] = pkcs11configLABEL_CLAIM_PRIVATE_KEY,         \
		[ KVS_CODE_SIGN_CERT_ID ] = "code_sign_cert_id",         \
		[ KVS_UPDATE_DEVICE_CERT_ID ] = pkcs11configLABEL_UPDATE_DEVICE_CERTIFICATE_FOR_TLS,         \
		[ KVS_UPDATE_DEVICE_PRIVKEY_ID ] = pkcs11configLABEL_UPDATE_DEVICE_PRIVATE_KEY_FOR_TLS,         \
		[ KVS_UPDATE_DEVICE_PUBKEY_ID ] = pkcs11configLABEL_UPDATE_DEVICE_PUBLIC_KEY_FOR_TLS,         \
    }
#define CLICMDKEYS                                       \
    {                                                      \
        [ KVS_CORE_THING_NAME ] = "thingname",            \
        [ KVS_CORE_MQTT_ENDPOINT ] = "endpoint",      \
        [ KVS_DEVICE_CERT_ID ] = "cert",                \
		[ KVS_DEVICE_PRIVKEY_ID ] = "key",         \
		[ KVS_DEVICE_PUBKEY_ID ] = "pub",         \
		[ KVS_ROOT_CA_ID ] = "rootca",                \
		[ KVS_TEMPLATE_NAME ] = "template",         \
		[ KVS_CLAIM_CERT_ID ] = "claimcert",                \
		[ KVS_CLAIM_PRIVKEY_ID ] = "claimkey",         \
        [ KVS_CODE_SIGN_CERT_ID ] = "codesigncert",         \
        [ KVS_UPDATE_DEVICE_CERT_ID ] = "updatecert",         \
        [ KVS_UPDATE_DEVICE_PRIVKEY_ID ] = "updatekey",         \
        [ KVS_UPDATE_DEVICE_PUBKEY_ID ] = "updatepub",         \
    }
```

## 各デモタスクを起動するメインタスクを変更する
各デモタスクを起動するメインタスクを変更します。
`iot-reference-rx`では必要に応じて`Fleet Provisioning`デモ起動後、デバイス個別の証明書を取得してから、`PubSub`デモおよび`OTA`デモを起動します。
今回の変更では、`Fleet Provisioning`デモの代わりに`証明書の更新`デモを起動することで、デバイス個別の証明書を更新して、`PubSub`デモおよび`OTA`デモを起動するように仕様を変更しました。
`PubSub`デモおよび`OTA`デモは更新された証明書を使用してAWSに接続します。

```c:main.c
	if(ApplicationCounter(Time2Wait))
	{
		/* Remove CLI task before going to demo. */
		/* CLI and Log tasks use common resources but are not exclusively controlled. */
		/* For this reason, the CLI task must be deleted before executing the Demo. */
		vTaskDelete(xCLIHandle);

		if( !Connect2AP())
		{
			configPRINTF( ( "Cellular init failed" ) );
		}
		else
		{

			vTaskDelay(300);

			configPRINTF( ( "Initialise the RTOS's TCP/IP stack\n" ) );

			configPRINTF( ( "---------STARTING DEMO---------\r\n" ) );

        #if (ENABLE_FLEET_PROVISIONING_DEMO == 1)
           vStartFleetProvisioningDemo();
        #elif (ENABLE_FLEET_PROVISIONING_DEMO == 2)
           vStartUpdateCertificateDemo();
        #else
           xSetMQTTAgentState( MQTT_AGENT_STATE_INITIALIZED );
        #endif

        vStartMQTTAgent (appmainMQTT_AGENT_TASK_STACK_SIZE, appmainMQTT_AGENT_TASK_PRIORITY);

        vStartSimplePubSubDemo ();

        #if (ENABLE_OTA_UPDATE_DEMO == 1)
                  vStartOtaDemo();
        #endif
		}
	}

	while( 1 )
	{
		vTaskSuspend( NULL );
	}
```

## 更新された証明書を使用するようにMQTT-Agentタスクを変更する
MQTT-Agentタスクで更新された証明書がある場合、それを使用してAWSに接続するよう仕様を変更します。
もし更新された証明書がない場合は、既存の証明書を使用してAWSに接続します。

```c:mqtt_agent_task.c
        if ( (xPkcs11Ret == CKR_OK) && ((xClientCertificate == CK_INVALID_HANDLE) || (xPrivateKey == CK_INVALID_HANDLE)) )
        {
            LogInfo( ( "Using first device certificate and key" ) );
            xNetworkCredentials.pClientCertLabel = pkcs11configLABEL_DEVICE_CERTIFICATE_FOR_TLS;
            xNetworkCredentials.pPrivateKeyLabel = pkcs11configLABEL_DEVICE_PRIVATE_KEY_FOR_TLS;
        }
        else if ( (xPkcs11Ret == CKR_OK) && ((xClientCertificate != CK_INVALID_HANDLE) && (xPrivateKey != CK_INVALID_HANDLE)) )
        {
            LogInfo( ( "Using updated device certificate and key" ) );
            xNetworkCredentials.pClientCertLabel = pkcs11configLABEL_UPDATE_DEVICE_CERTIFICATE_FOR_TLS;
            xNetworkCredentials.pPrivateKeyLabel = pkcs11configLABEL_UPDATE_DEVICE_PRIVATE_KEY_FOR_TLS;
        }
        else
        {
            LogError( ( "Failed to get state." ) );
        }
```

## 証明書の更新デモの実装
`UpdateCertOverMqttDemoExample.c`に`証明書の更新`デモタスクを実装します。
どのように上記デモタスクを実装したかは以下の通りです。

### デバイス内部で鍵ペアとCSRを生成する
今回のデモはデバイス内部で鍵ペアとCSRを作成します。
その後、CSRのみMQTTメッセージとしてAWSに送信して、AWS Lambdaで送信されたCSRから証明書を作成して、ポリシーにアタッチします。
そのため、デバイス内部で鍵ペアとCSRを作成する処理を実装します。
これらの処理は`Fleet Provisioning`デモの関数を流用することで簡単に実装することができます。

```c:UpdateCertOverMqttDemoExample.c
        /* Initialize the PKCS #11 module */
        xPkcs11Ret = xInitializePkcs11Session( &xP11Session );

        if( xPkcs11Ret != CKR_OK )
        {
            LogError( ( "Failed to initialize PKCS #11." ) );
            xStatus = false;
        }

        if ( xPkcs11Ret == CKR_OK )
        {
            xStatus = xGenerateKeyAndCsr( xP11Session,
                                          pkcs11configLABEL_UPDATE_DEVICE_PRIVATE_KEY_FOR_TLS,
                                          pkcs11configLABEL_UPDATE_DEVICE_PUBLIC_KEY_FOR_TLS,
                                          pcCsr,
                                          ucdemoCSR_BUFFER_LENGTH,
                                          &xCsrLength,
                                          pcCSRSubjectName );

            if( xStatus == false )
            {
                LogError( ( "Failed to generate Key and Certificate Signing Request." ) );
            }
        }
        else
        {
            LogError( ( "Failed to initialize PKCS #11 or get state." ) );
            xStatus = false;
        }
```

### 既存の証明書を使用してAWSに接続する
更新された証明書を受け取るためには、最初に既存の証明書を使用してAWSに接続してCSRを送信する必要があります。
`xEstablishMqttSession`関数の引数には既存の証明書ラベルを指定します。

```c:UpdateCertOverMqttDemoExample.c
            /**** Connect to AWS IoT Core with exist credentials *****/

            if( xStatus == true )
            {
                LogInfo( ( "Establishing MQTT session with exist certificate..." ) );
                xStatus = xEstablishMqttSession( &xMqttContext,
                                                 &xNetworkContext,
                                                 &xBuffer,
                                                 prvProvisioningPublishCallback,
                                                 pkcs11configLABEL_DEVICE_CERTIFICATE_FOR_TLS,
                                                 pkcs11configLABEL_DEVICE_PRIVATE_KEY_FOR_TLS,
                                                 gKeyValueStore.table[KVS_CORE_THING_NAME].value);

                if( xStatus == false )
                {
                    LogError( ( "Failed to establish MQTT session." ) );
                }
                else
                {
                    LogInfo( ( "Established connection with exist credentials." ) );
                    xConnectionEstablished = true;
                }
            }
```

### 鍵ペアの作成およびCSRをAWSに送信する
`xGenerateKeyAndCsr`関数を使用して作成したCSRをAWSに送信します。
ただし、作成されたCSRは`\n`が存在しています。
念のため、`strtok`関数を使用して`\n`を`\\n`に変更しておきます。
こちらの方がAWS側のMQTTテストクライアント上でのCSRの見栄えが良いです。

```c:UpdateCertOverMqttDemoExample.c
            /**** Update Certificate from CSR ***************************/

            if( xStatus == true )
            {
                xStatus = prvSubscribeToCsrResponseTopics();
            }

            if( xStatus == true )
            {
                token = strtok( pcCsr, "\n");
                while (token != NULL)
                {
                    xConvertCsrLength += snprintf( (pcConvertCsr + xConvertCsrLength), ucdemoCSR_BUFFER_LENGTH, "%s\\n", token);
                    token = strtok(NULL, "\n");
                }
                xConvertCsrLength = 0;

                xPublishMessageLength = ucdemoCSR_BUFFER_LENGTH + 128U;
                pcPublishMessage = pvPortMalloc(xPublishMessageLength + 1);
                xPubPayloadLength = snprintf( pcPublishMessage, xPublishMessageLength + 1, ucdemoCSR_REQUEST_JSON, pcConvertCsr);
            }

            if( xStatus == true )
            {
                /* Publish the CSR to the CreateCertificatefromCsr API. */
                xStatus = xPublishToTopic( &xMqttContext,
                                           pcPublishTopic,
                                           xPubOutTopicLength,
                                           ( char * ) pcPublishMessage,
                                           xPubPayloadLength );

                if( xStatus == false )
                {
                    LogError( ( "Failed to publish to fleet provisioning topic: %.*s.",
                                pcPublishTopic,
                                xPubOutTopicLength ) );
                }
            }
```

### coreJSONを使用して証明書を抽出する
AWS Lambdaで受け取ったCSRから証明書を作成して送信元のデバイスに送り返します。
これはJSON形式で送られてくるため、coreJSONライブラリを使用して証明書データのみを抽出します。
ただし、この証明書データはLF文字が`\\n`になっているため、このままではcorePKCS11ライブラリを使用して証明書データを正しくデータフラッシュに書き込めません。
したがって、`strtok`関数を使用して`\\n`を`\n`に変換してから`xLoadCertificate`関数を呼び出して証明書データをデータフラッシュに書き込みます。

```c:UpdateCertOverMqttDemoExample.c
            if( xStatus == true )
            {
                xjsonresult = JSON_Validate( pucPayloadBuffer, xSubPayloadLength );

                if( xjsonresult == JSONSuccess )
                {
                    xjsonresult = JSON_Search( pucPayloadBuffer, xSubPayloadLength, "certificatePem", (sizeof("certificatePem") - 1),
                                          &jsonvalue, &jsonvalueLength );

                    if ( xjsonresult == JSONSuccess )
                    {
                        pcCert = pvPortMalloc(jsonvalueLength + 1);
                        memcpy(pcCert, jsonvalue, jsonvalueLength);
                        pcConvertCert = pvPortMalloc(jsonvalueLength + 1);
                        token = strtok(pcCert, "\\");
                        while (token != NULL)
                        {
                            if (token == pcCert)
                            {
                                xConvertCertLength += snprintf( (pcConvertCert + xConvertCertLength), jsonvalueLength, "%s\n", token);
                            }
                            else
                            {
                                xConvertCertLength += snprintf( (pcConvertCert + xConvertCertLength), jsonvalueLength, "%s\n", (token + 1));
                            }

                            token = strtok(NULL, "\\");
                        }

                        xStatus = true;
                    }
                    else
                    {
                        LogError( ( "Failed to dumps certificatePem." ) );
                        xStatus = false;
                    }
                }
                else
                {
                    LogError( ( "Failed to dumps certificatePem." ) );
                    xStatus = false;
                }
            }

            if( xStatus == true )
            {
                /* Save the certificate into PKCS #11. */
                xStatus = xLoadCertificate( xP11Session,
                                            pcConvertCert,
                                            pkcs11configLABEL_UPDATE_DEVICE_CERTIFICATE_FOR_TLS,
                                            xConvertCertLength );
            }
```

### 更新された証明書を使用して接続確認する
一度AWSから切断した後に更新された証明書を使用してAWSへの接続を確認します。
`xEstablishMqttSession`関数の引数には更新された証明書ラベルを指定します。

```c:UpdateCertOverMqttDemoExample.c
        /**** Connect to AWS IoT Core with updated certificate ************/

        if( xStatus == true )
        {
            LogInfo( ( "Establishing MQTT session with updated certificate..." ) );
            xStatus = xEstablishMqttSession( &xMqttContext,
                                             &xNetworkContext,
                                             &xBuffer,
                                             prvProvisioningPublishCallback,
                                             pkcs11configLABEL_UPDATE_DEVICE_CERTIFICATE_FOR_TLS,
                                             pkcs11configLABEL_UPDATE_DEVICE_PRIVATE_KEY_FOR_TLS,
                                             gKeyValueStore.table[KVS_CORE_THING_NAME].value );

            if( xStatus != true )
            {
                LogError( ( "Failed to establish MQTT session with provisioned "
                            "credentials. Verify on your AWS account that the "
                            "new certificate is active and has an attached IoT "
                            "Policy that allows the \"iot:Connect\" action." ) );
            }
            else
            {
                LogInfo( ( "Sucessfully established connection with provisioned credentials." ) );
                xConnectionEstablished = true;
            }
        }
```

# デモの動作確認手順
## モノの作成および初回の証明書/秘密鍵のダウンロード
AWSにCSRを送信するために初回接続に使用する証明書/秘密鍵をダウンロードします。
下記`iot-reference-rx`における`Getting_Started_Guide.md`の`Step 4-1: Run PubSub demo`記載の方法に従って、モノの作成および証明書/秘密鍵をダウンロードしてください。

* [Getting_Started_Guide.md - Step 4-1: Run PubSub demo](https://github.com/renesas/iot-reference-rx/blob/main/Getting_Started_Guide.md#step-4-1-run-pubsub-demo)

## 証明書を作成して送り返すAWS Lambda関数の作成
デバイスからMQTTメッセージとして送信されたCSRを用いて証明書を作成してから、デバイスに送り返すLambda関数を以下のように作成します (使用言語はPython 3.12を使用しています)。
`boto3`ライブラリを使用して、受け取ったCSRから証明書を作成、それをポリシーおよびモノに割り当てています。
最後に作成した証明書をMQTTメッセージとしてデバイスに送り返しています。
`ck_rx65n_test_policy`は作成したポリシー名に合わせて変更してください。

```python:lambda_function.py
import json
import boto3
import base64

def lambda_handler(event, context):
    
    clientid = event['clientid']
    CSR = event['CSR']
    
    print(CSR)
    
    client = boto3.client('iot')
    iot = boto3.client('iot-data')
    
    response = client.create_certificate_from_csr(
        certificateSigningRequest = CSR,
        setAsActive = True
    )

    certificatePem = response['certificatePem']
    certificateArn = response['certificateArn']

    client.attach_policy(
        policyName = 'ck_rx65n_test_policy',
        target = certificateArn
    )

    response = client.attach_thing_principal(
        thingName = clientid,
        principal = certificateArn
    )
    
    topic = 'management/topic/%s/crt' % clientid
    payload = {
        "certificatePem": certificatePem
    }
    
    iot.publish(
        topic=topic,
        qos=0,
        payload=json.dumps(payload)
    )
    
    return {
        'statusCode': 200,
        'certificatePem': certificatePem
    }
```

また今回はAWS Lambda関数上でAWS IoT Coreのサービスを使用するため、AWS Lambda関数の`設定` -> `アクセス権限` -> `実行ロール`から`AWS IoT Core`のロールを付与しておきます。
とりあえず、実験としては`AWSIoTFullAccess`で良いと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/2a2a5c72-fb37-e519-b2de-29181536a75d.png)

## AWS IoT CoreのRuleの作成
AWS IoT CoreとAWS Lambdaを繋ぐためのRuleを作成します。
下記のようにSQLステートメントを設定して、トピック名が`management/topic/<clientid>/csr_res`である場合、先ほど作成したAWS Lambda関数にMQTTメッセージを転送するように設定します。

```sql
SELECT clientid() as clientid, * FROM 'management/topic/+/csr_res'
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/4186bf82-c1ad-f751-a122-ee2f3d1ea526.png)

## プログラムの実行
`v202210.01-lts-rx-1.1.2-update-cert`ブランチからプロジェクトおよびソースコードをクローンしてから、e2 studioに`aws_ryz014a_ck_rx65n`をインポートしてください。
使用するSIMカードに応じて、Cellular用の情報を設定してプログラムをビルドします。
その後、プログラムを実行して、下記`step-4-1-6-input-via-cli-for-pubsub-demo`に従って、TeraTermで`モノの名前`、`エンドポイント名`、`証明書`、`秘密鍵`を入力してリセットします。

* [Getting_Started_Guide.md - step-4-1-6-input-via-cli-for-pubsub-demo](https://github.com/renesas/iot-reference-rx/blob/main/Getting_Started_Guide.md#step-4-1-6-input-via-cli-for-pubsub-demo)

リセット後、数秒間経過するとデモが実行されます。
最初に証明書の更新タスクが実行されて、デバイス内部で作成したCSRをAWSに送信します。
その後、AWSから送り返された証明書をcorePKCS11ライブラリを使用してデータフラッシュに書き込みます。
TeraTermのログに`Demo completed successfully.`が表示されれば証明書の更新タスクの実行は成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/7d83e677-9171-82cf-2365-89a9ee2f7791.png)

その後、MQTT-AgentタスクとPubSubタスクが実行されます。
これらのタスクにおけるAWSとの接続には更新された証明書が使用されます。
`Using updated device certificate and key`が表示されれば、更新された証明書が接続に使用されていることを意味します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/332f37d6-7878-0f62-6347-3de5d62747e8.png)

AWS IoT Coreのポリシーを確認すると、CSRから作成された証明書がポリシーにアタッチされていることを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/363822f0-b8bc-4948-b978-a8e9c3d11493.png)

# さいごに
今回はAWSで公開されている証明書更新アプローチをRXファミリ用のFreeRTOS-LTSで実装してみました。
今回の実装では、SNSでスケジュールを組んでAWS Lambdaで証明書の有効期限を定期的に確認するまでの実装は行いませんでしたが、この実装を参考にAWSで公開されている証明書更新アプローチを実装することできるはずです。
また、デモでは既存の証明書をデバイス側でもAWS側でも削除していません。
そのため、必要に応じてデバイス側はプログラムでAWS側はAWS Lambda関数で既存の証明書を削除する処理を実装する必要があります。
さらに今回の実装では`Fleet Provisioning`デモと`証明書の更新`デモは同時に動かすことができません。
ユーザーの中には`Fleet Provisioning`デモで取得したデバイス個別の証明書を`証明書の更新`デモで更新したい人がいるかもしれません。
そのためには、`証明書の更新`デモを`PubSub`デモと`OTA`デモと同じタイミングで動かして、AWS Lambda側でPublishしたMQTTメッセージをトリガーにCSRの作成および証明書の更新処理を実行する必要があります。
証明書更新中は`PubSub`デモと`OTA`デモを動かさずに、証明書更新後にAWSに再接続して`PubSub`デモと`OTA`デモを再開するようにサスペンドしておくとよいでしょう。