LoginSignup
2
1

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-01-09

以前の投稿 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には以下を追加すればよいです。最後の行です。

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

/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)

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1