以前の投稿 M5Stack用暗号認証ユニットでFIDOデバイスを作る では、M5Stack用暗号認証ユニットでFIDO認証できるところまではできていたのですが、X.509証明書の作成だけは、Node.jsの力を借りていました。
そこで、もう少し頑張って、ESP32の中でX.509の証明書を作成するようにしました。
これによって、ESP32(今回はM5StickC)単体で、FIDOデバイスにすることができました。
ソースコードはGitHubに上げています。
poruruba/FidoU2fEmulator
#やったこと
以下のライブラリに、自己署名のX.509証明書を作成する実装があるので、それを参考にFIDO用のX.509証明書を作成いたしました。(これがなかったら無理でした。。。)
<変更点>
・ATECC608Bは複数の公開鍵ペアをもてるので、うち1つを発行者用の鍵として使いました。
・X.509証明書のSubject名とIssuer名を別々に設定できるようにすることで、自己署名ではなく通常の証明書としました。
・X.509証明書のSubjectKeyIdentifierの生成のために、SHA1を使うようにしました。すでに上記ライブラリには含まれていたのですが、ESP32のライブラリとバッティングしていたので、関数名を変えました。
・X.509証明書にFIDO U2F certificate transports extensionを加えました。
なんだかんだArduinoECCX08ライブラリを直しているうちに、修正規模が多くなってきたので、forkしました。こちらを取り込むようにしてください。変更箇所は、そちらのコミット内容をご参照ください。
poruruba/ArduinoECCX08
platformio.iniには以下を追加すればよいです。最後の行です。
lib_deps =
m5stack/M5StickC@^0.2.5
lovyan03/LovyanGFX@^0.4.10
https://github.com/poruruba/ArduinoECCX08.git@^1.3.5
ESP32単体でX.509証明書を作成できるようになったので、Node.jsとの通信も不要となり、WiFiを利用を削除しました。
一応形になったので、BLEのボンディング時のPINを乱数生成に戻しました。ペアリング先のPCには、M5StickCのLCDに表示される数字6桁をPINとして入力するようにしてください。
#ソースコード
結局、FIDOデバイス登録時の処理は以下の通りになりました。抜粋です。
int process_register(const uint8_t *challenge, const uint8_t *application)
{
Serial.println("process_register");
long ret = auth_prepare();
if( ret != 0 ){
Serial.println("auth_prepare error");
payload[0] = 0x6a;
payload[1] = 0x80;
return 2;
}
uint8_t serial[SERIAL_LENGTH];
ret = ECCX08.serialNumber(serial);
if( !ret ){
Serial.println("serialNumber error");
payload[0] = 0x6a;
payload[1] = 0x80;
return 2;
}
if (!ECCX08FidoCert.beginStorage(DEFAULT_KEY_SLOT, DEFAULT_ISSUER_KEY_SLOT)){
Serial.println("Error starting cert generation!");
payload[0] = 0x6a;
payload[1] = 0x80;
return 2;
}
unsigned long counter;
ret = ECCX08.countUp(DEFAULT_COUNTER_SLOT, &counter);
if( !ret ){
Serial.println("ECCX08.countUp Error");
while (1);
}
uint8_t fido_serial[SERIAL_LENGTH + 4];
memmove(fido_serial, serial, SERIAL_LENGTH);
fido_serial[SERIAL_LENGTH] = (counter >> 24) & 0xff;
fido_serial[SERIAL_LENGTH + 1] = (counter >> 16) & 0xff;
fido_serial[SERIAL_LENGTH + 2] = (counter >> 8) & 0xff;
fido_serial[SERIAL_LENGTH + 3] = (counter) & 0xff;
ECCX08FidoCert.setSerialNumber(fido_serial, sizeof(fido_serial));
ECCX08FidoCert.setIssuerName(FIDO_ISSUER);
ECCX08FidoCert.setCommonName(String(FIDO_ISSUER) + ECCX08.serialNumber());
ECCX08FidoCert.setIssueYear(2022);
ECCX08FidoCert.setIssueMonth(1);
ECCX08FidoCert.setIssueDay(1);
ECCX08FidoCert.setIssueHour(0);
ECCX08FidoCert.setExpireYears(10);
uint8_t *cert;
int cert_len = ECCX08FidoCert.endStorage(&cert);
if( cert_len < 0 ){
Serial.println("ECCX08FidoCert.endStorage error");
payload[0] = 0x6a;
payload[1] = 0x80;
return 2;
}
uint8_t publicKey[PUBLICKEY_LENGTH];
publicKey[0] = 0x04;
ret = ECCX08.generatePublicKey(DEFAULT_KEY_SLOT, &publicKey[1]);
if( !ret ){
Serial.println("ECCX08.generatePublicKey Error");
payload[0] = 0x6a;
payload[1] = 0x80;
return 2;
}
uint8_t keyHandle[KEYHANDLE_LENGTH];
unsigned long sequence_no = counter;
ret = make_keyHandle(sequence_no, serial, keyHandle);
if( ret != 0 ){
Serial.println("make_keyHandle Error");
payload[0] = 0x6a;
payload[1] = 0x80;
return 2;
}
int index = 0;
payload[index++] = 0x05;
memmove(&payload[index], publicKey, PUBLICKEY_LENGTH);
index += PUBLICKEY_LENGTH;
payload[index++] = KEYHANDLE_LENGTH;
memmove(&payload[index], keyHandle, KEYHANDLE_LENGTH);
index += KEYHANDLE_LENGTH;
memmove(&payload[index], cert, cert_len);
index += cert_len;
uint8_t input[1 + APPLICATION_LENGTH + CHALLENGE_LENGTH + KEYHANDLE_LENGTH + PUBLICKEY_LENGTH];
input[0] = 0x00;
memmove(&input[1], application, APPLICATION_LENGTH);
memmove(&input[1 + APPLICATION_LENGTH], challenge, CHALLENGE_LENGTH);
memmove(&input[1 + APPLICATION_LENGTH + CHALLENGE_LENGTH], keyHandle, KEYHANDLE_LENGTH);
memmove(&input[1 + APPLICATION_LENGTH + CHALLENGE_LENGTH + KEYHANDLE_LENGTH], publicKey, PUBLICKEY_LENGTH);
unsigned char hash[HASH_LENGTH];
ret = make_sha256(input, sizeof(input), hash);
if( ret != 0 ){
Serial.println("make_sha256 Error");
payload[0] = 0x6a;
payload[1] = 0x80;
return 2;
}
unsigned char signature[RAW_SIGNATURE_LENGTH];
ret = ECCX08.ecSign(DEFAULT_KEY_SLOT, hash, signature);
if( !ret ){
Serial.println("ECCX08.ecSign Error");
payload[0] = 0x6a;
payload[1] = 0x80;
return 2;
}
ret = set_signature(signature, &payload[index]);
index += ret;
payload[index++] = 0x90;
payload[index++] = 0x00;
return index;
}
#参考
X.509証明書の作成には以下を参考にさせていただきました。非常にわかりやすかったです。
https://blog.engelke.com/2014/10/17/parsing-ber-and-der-encoded-asn-1-objects/
https://blog.engelke.com/2014/10/21/web-crypto-and-x-509-certificates/
https://en.wikipedia.org/wiki/X.690
https://docs.microsoft.com/ja-jp/windows/win32/seccertenroll/about-der-encoding-of-asn-1-types
#終わりに
・なぜ今まで動いていたのに、今回に限ってAWS管理コンソールでの利用ではじかれるのかわからない。。。
⇒ 解決しました!AWSでも使えるようになりました(2022/1/30)