この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
マルツオンライン様のGWセールでreTeminalが特価でしたので、購入してみました。reTeminalの詳細は、以下をご参照ください。
reTerminalには、ハードウェアベースの安全なキー・ストレージが付いています。こちらを利用したブログネタを探していたところ、aws-iot-device-sdk-python-v2でPKCS#11のサポートがされていましたので、試すことにしました。
PKCS#11とは
PKCS(Public Key Cryptography Standards)は、RSAセキュリティにより考案され公開された公開鍵暗号標準です。中でもPKCS#11は、HSM(Hardware Security Module)のためのAPIとなります。 IoTでは、認証情報を不正に取得されないように、認証情報をTPM(Trusted Platform Module)やHardware Security Module(HSM)といったハードウェア保護モジュールを利用することがあります。
冒頭で述べた通り、reTerminalのATECC608Aが、このハードウェア保護モジュールにあたります。本記事では、各種ツールのPKCS#11のAPIを利用して、秘密鍵を安全にデバイス側で格納しつつAWS IoTとMQTT通信を実現します。
環境
reTerminalのOSはRaspbianです。ただ、Raspberry Piを持っている方が本記事をそのまま利用することはできません。参考程度にとどめてください。
$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description: Raspbian GNU/Linux 10 (buster)
Release: 10
Codename: buster
$ openssl version
OpenSSL 1.1.1n 15 Mar 2022
reTerminalの初期設定
起動からwifi設定
電源をつけて、wifi設定まで行ってください。Zennでスクラップを書いたので、以下をご参照ください。
ATECC608Aのプロビジョニング
seeed様の記事、項目「デバイス秘密鍵とデバイス公開鍵を確認する」まで進めてください。
以下のshared objectファイルが存在するか確認します。後述するopensslの設定で利用します。(もしかしたら環境差異があるかもしれません)
- lib11 ->
/usr/lib/arm-linux-gnueabihf/engines-1.1/libpkcs11.so
- cryptoauthlib ->
/usr/lib/libcryptoauth.so
p11tool
で、以下のコマンドを実行し、Object 0にPrivate key、Object 1にPublic keyが割り当てられているのが確認できましたら、次の項目に進みます。
$ p11tool --provider /usr/lib/libcryptoauth.so --list-all
$ p11tool --list-all "pkcs11:token=MCHP"
(余談) 紹介した記事にもある通り、reTerminalのATECC608Aは未プロビジョニング品となっています。私は業務でプロビジョニング済みの製品を利用していたため、R/W可能なスロットにデータが書き込めず混乱しました。結果この記事の存在に気付くことなく、自己流でプロビジョニングしました。。プロビジョニング状態の確認は、記事の項「ロック状態を確認」を参考にしてください。
MQTT接続準備
cryptoauthlibの再ビルド
項「ATECC608Aのプロビジョニング」で実施したcryptoauthlibをビルドしなおします。v3.3.3(リビジョン:055dd4af)の場合、MQTT通信時以下のようなエラーが発生するためです。原因については分かり次第、追記したいと思います。
Loading PKCS#11 library '/usr/lib/libcryptoauth.so' ...
Loaded!
Traceback (most recent call last):
File "pkcs.py", line 39, in <module>
connect_future.result()
File "/usr/lib/python3.7/concurrent/futures/_base.py", line 432, in result
return self.__get_result()
File "/usr/lib/python3.7/concurrent/futures/_base.py", line 384, in __get_result
raise self._exception
awscrt.exceptions.AwsCrtError: AWS_ERROR_PKCS11_ENCODING_ERROR: A PKCS#11 (Cryptoki) library function was unable to ASN.1 (DER) encode a data structure. See log for more details.
筆者の環境では、リビジョン:9a37b8d6では動作したため、こちらを利用します。前の作業で利用したものを再利用しても良いですが、本手順ではクローンし直して、再ビルドします。
$ git clone https://github.com/MicrochipTech/cryptoauthlib
$ cd cryptoauthlib
$ git reset --hard 9a37b8d6
$ mkdir build
$ cd build
$ cmake \
-DATCA_HAL_I2C=ON \
-DATCA_PKCS11=ON \
-DATCA_TNGTLS_SUPPORT=ON \
-DATCA_ATECC608A_SUPPORT=ON \
-DATCA_OPENSSL=ON ../
$ cmake --build .
$ sudo make install
以下のコマンドでファイルの生成時刻を確認し、再度インストールされていることを確認します。
$ ls -al /usr/lib/ | grep "libcryptoauth.so"
opensslの設定
MicrochipTech様のPKCS11 Linux Setup|Without using p11-kit-proxyを参考にしました。
OpenSSLがlibp11
とcryptoauthlib
を使用するように手動で設定します。前述したパスをopenssl.cnfに追記します。openssl_init
は先頭に記述する必要があるため注意してください。
デフォルトのコンフィグとのdiffは以下の通りです。
$ diff /usr/lib/ssl/openssl.cnf /usr/lib/ssl/openssl.cnf_bakup
1,14d0
< openssl_conf = openssl_init
< [openssl_init]
< engines=engine_section
<
< [engine_section]
< pkcs11 = pkcs11_section
<
< [pkcs11_section]
< engine_id = pkcs11
< # Wherever the engine installed by libp11 is. For example it could be:
< # dynamic_path = /usr/lib/ssl/engines/libpkcs11.so
< dynamic_path = /usr/lib/arm-linux-gnueabihf/engines-1.1/libpkcs11.so
< MODULE_PATH = /usr/lib/libcryptoauth.so
< init = 0
CSRの作成
CSRを作成します。よくある手順だと、秘密鍵のファイルを指定しますが、ATECC608Aは秘密鍵を読み出せないため、PKCS#11のツールを利用します。
sudo openssl req -engine pkcs11 -key "pkcs11:token=MCHP;object=device;type=private" -keyform engine -new -out new_device.csr -subj "/C=JP/ST=Tokyo/L=Tokyo/O=MyCompany/CN=12345"
以下のコマンドで、CSRの内容を確認できます。
$ openssl req -in new_device.csr -text
Certificate Request:
Data:
Version: 1 (0x0)
Subject: C = JP, ST = Tokyo, L = Tokyo, O = MyCompany, CN = 12345
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
...
ASN1 OID: prime256v1
NIST CURVE: P-256
Attributes:
a0:00
Signature Algorithm: ecdsa-with-SHA256
...
-----BEGIN CERTIFICATE REQUEST-----
...
-----END CERTIFICATE REQUEST-----
証明書の作成
AWS IoTでは、CSRから証明書を作成する方法があります。今回はそちらを活用します。
マネジメントコンソールで行う場合 CSRによる証明書の作成を参考にしてください。
AWS CLIで行う場合
# 登録したいAWS環境にassume roleする
aws iot create-certificate-from-csr \
--certificate-signing-request=file://new_device.csr \
--set-as-active
{
"certificateArn": "arn:aws:iot:ap-northeast-1:111111111111:cert/<certificateId>",
"certificateId": "<certificateId>",
"certificatePem": "-----BEGIN CERTIFICATE-----***-----END CERTIFICATE-----\n"
}
JSONの証明書部分(certificatePem)を、device.crt
として保存しておきます。
# 制御文字を反映させるため-eオプションをつけます。
echo -e "-----BEGIN CERTIFICATE-----***-----END CERTIFICATE-----\n">device.cert
証明書にポリシーをアタッチ
ポリシー定義
echo '{"Version": "2012-10-17", "Statement": [{"Effect": "Allow","Action":["iot:*"],"Resource": ["*"]}]}'|jq>policy.json
ポリシー作成
aws iot create-policy --policy-name reteminal-test \
--policy-document file://policy.json
実行結果
{
"policyName": "reteminal-test",
"policyArn": "arn:aws:iot:ap-northeast-1:111111111111:policy/reteminal-test",
"policyDocument": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"iot:*\"\n ],\n \"Resource\": [\n \"*\"\n ]\n }\n ]\n}\n",
"policyVersionId": "1"
}
証明書にポリシーをアタッチ。(何も出力されなければ成功です)
aws iot attach-policy \
--policy-name reteminal-test \
--target arn:aws:iot:ap-northeast-1:111111111111:cert/<certificateId>
最終的に以下のようになっていることを確認します。
MQTT通信確認
MQTT通信するために必要なsdkのインストール、通信時に利用するルートCAを取得します。
# awsiotsdkのインストール
pip3 install awsiotsdk
# ルートCAのダウンロード
wget https://www.amazontrust.com/repository/AmazonRootCA1.pem
以下のような配置にします
.
├── AmazonRootCA1.pem # wgetで取得
├── pkcs.py # 後述するpythonスクリプト
└── device.crt # 証明書作成の項目で作成
ソースコードは以下となります。ポイントは、通常秘密鍵を平文で設定していたところを、本コードでは設定していないことです。前述しましたがATECC608AのSLOT0番は、公開鍵は取得出来ても、秘密鍵の書き込み、読み出しは不可能です。故に安全に秘密鍵を管理することが可能です。
pkcs.py
from awsiot import mqtt_connection_builder
from awscrt import io, http, auth, mqtt
import time
import json
def on_connection_interrupted(connection, error, **kwargs):
print("Connection interrupted. error: {}".format(error))
def on_connection_resumed(connection, return_code, session_present, **kwargs):
print("Connection resumed. return_code: {} session_present: {}".format(return_code, session_present))
if __name__ == '__main__':
pkcs11_lib_path = "/usr/lib/libcryptoauth.so"
print(f"Loading PKCS#11 library '{pkcs11_lib_path}' ...")
pkcs11_lib = io.Pkcs11Lib(
file=pkcs11_lib_path,
behavior=io.Pkcs11Lib.InitializeFinalizeBehavior.STRICT)
print("Loaded!")
mqtt_connection = mqtt_connection_builder.mtls_with_pkcs11(
pkcs11_lib=pkcs11_lib, # cryptoauthlibのコンパイル済みshared objectファイルを指定
user_pin="1234", # 適当
token_label="MCHP", # /var/lib/cryptoauthlib/0.confのlabel値を指定
private_key_label="device", # /var/lib/cryptoauthlib/0.confから指定
cert_filepath="./device.crt", # 項「証明書の作成」で作成した証明書
endpoint="***-ats.iot.ap-northeast-1.amazonaws.com", # AWS IoT Coreエンドポイント
port=8883,
ca_filepath="./AmazonRootCA1.pem", # wgetで取得
on_connection_interrupted=on_connection_interrupted, # 接続時コールバック
on_connection_resumed=on_connection_resumed, # 切断時コールバック
client_id="myid", # モノに一意は値を指定
clean_session=False,
keep_alive_secs=30
)
connect_future = mqtt_connection.connect()
connect_future.result()
print("Connected!")
while True:
PUBLISH_TOPIC_NAME = "sample/test"
mqtt_connection.publish(
topic=PUBLISH_TOPIC_NAME,
payload=json.dumps({"hoo": "hoge"}),
qos=mqtt.QoS.AT_LEAST_ONCE,
)
time.sleep(5)
$ python3 pkcs.py
Loading PKCS#11 library '/usr/lib/libcryptoauth.so' ...
Loaded!
Connected!
publish出来ていることが確認できたらOKです。
最後に
今回はスロット0で試しましたが、スロット2,3,4では、フリーの秘密鍵を設定可能です。フリーの秘密鍵を利用するパターンでも試してみようと思います。恐らく/var/lib/cryptoauthlib/0.conf
の設定を変更すればいけると思います。。