M5Stack用暗号認証ユニットでFIDOデバイスを作る：の続き

以前の投稿 [M5Stack用暗号認証ユニットでFIDOデバイスを作る]( https://qiita.com/poruruba/items/7e74ff3cdf27d4937f2d) では、M5Stack用暗号認証ユニットでFIDO認証できるところまではできていたのですが、X.509証明書の作成だけは、Node.jsの力を借りていました。

そこで、もう少し頑張って、ESP32の中でX.509の証明書を作成するようにしました。
これによって、ESP32（今回はM5StickC）単体で、FIDOデバイスにすることができました。

ソースコードはGitHubに上げています。

poruruba/FidoU2fEmulator

https://github.com/poruruba/FidoU2fEmulator

#やったこと

以下のライブラリに、自己署名のX.509証明書を作成する実装があるので、それを参考にFIDO用のX.509証明書を作成いたしました。（これがなかったら無理でした。。。）

https://github.com/arduino-libraries/ArduinoECCX08/blob/master/examples/Tools/ECCX08SelfSignedCert/ECCX08SelfSignedCert.ino

＜変更点＞
・ATECC608Bは複数の公開鍵ペアをもてるので、うち1つを発行者用の鍵として使いました。
・X.509証明書のSubject名とIssuer名を別々に設定できるようにすることで、自己署名ではなく通常の証明書としました。
・X.509証明書のSubjectKeyIdentifierの生成のために、SHA1を使うようにしました。すでに上記ライブラリには含まれていたのですが、ESP32のライブラリとバッティングしていたので、関数名を変えました。
・X.509証明書にFIDO U2F certificate transports extensionを加えました。

なんだかんだArduinoECCX08ライブラリを直しているうちに、修正規模が多くなってきたので、forkしました。こちらを取り込むようにしてください。変更箇所は、そちらのコミット内容をご参照ください。

poruruba/ArduinoECCX08

https://github.com/poruruba/ArduinoECCX08

platformio.iniには以下を追加すればよいです。最後の行です。

```ini: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デバイス登録時の処理は以下の通りになりました。抜粋です。

```cpp:/src/main.cpp
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)