マイナンバーカードでWindowsアプリケーションにコード署名をする

公的個人認証サービスやAuthenticodeの知識が無かったせいもあって苦戦したので備忘録として残しておきます．

用意するもの：

- マイナンバーカード
- カードリーダー
- Linux環境(※)

※最終的にWindwsで署名するのを諦めてLinux上で作業しました．

# Microsoft Authenticode

[Authenticode](https://docs.microsoft.com/ja-jp/windows-hardware/drivers/install/authenticode)はプログラム(exe, dllなど)を署名して開発元を保証する仕組みです．たとえば，ダウンロードしたアプリケーションの実行時やドライバのインストール時にWindowsが署名を確認するので，場合によっては実行がブロックされたり警告のダイアログが表示されます．コード署名のための証明書は通常どこかの証明機関から購入することになると思います．

利用状況に応じてWindowsが署名を信頼するかどうかが変わったり，EV証明書と通常の証明書で扱いが異なるようですが，この記事では触れません．

手軽に取得できる証明書といえばLet's Encryptが思い浮かびますが，SSL用の証明書を確認するとextendedKeyUsageがserverAuthやclientAuthになっています．用途が制限されているので残念ながらコード署名には使えないようです．(無視して署名した場合にどう表示されるのかは確認していません)


# マイナンバーカードの電子証明書

自分の身元を証明するために定期的に数万円払うのに抵抗があって放置していましたが，日本人なら誰でも入手できる強そうな電子証明書をすでに持っていたので中身を確認してみることにしました．

マイナンバーカードの中身は [公的個人認証サービス プロファイル仕様書](https://www.j-lis.go.jp/data/open/cnt/3/2187/1/13_profile_genkou.pdf) を見ればわかるようです．実際に自分のカードを読み込んでみると説明通りの内容が書き込まれていました(あたりまえですが)．

「署名用電子証明書(Digital Signature Certificate)」と「利用者証明用電子証明書(User Authentication Certificate)」という証明書がありますが，署名に使うのは署名用電子証明書の方です．

用途を表すkeyUsageはdigitalSignatureでcriticalフラグ付いてるのでデジタル署名にのみ利用可能です．extendedKeyUsageは存在しないので暗黙的にcodeSigningを含むすべてのdigitalSignatureが許可されているようです(たぶん)．

署名の検証のためには，日本の公的個人認証サービス(JPKI)を信頼できる認証局として扱う必要がります．e-Tax等でマイナンバーカードを使っていればすでにOSに登録されていると思います．


# マイナンバーカードでコード署名をする


結論としては，OpenSC + osslsigncode の組み合わせで署名できたので，成功例だけ必要なら途中は読み飛ばしてください．

## JPKI利用者ソフト + SignTool

まず，Windowsでマイナンバーカードを使うための[JPKI利用者ソフト](https://www.jpki.go.jp/download/)というのが用意されていて，インストールすればマイナンバーカード使えるようになるらしいです．~~素晴らしい．~~
(ちなみに，JPKI利用者ソフトのインストーラーはDigiCertのEV証明書が使われていました)
![jpkiapp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58515/3b8cd959-05e0-e3b9-5875-c0ee835ecd41.png)

この画面からマイナンバーカード内の証明書を読み込んだりできました．スクリーンのスケーリングが有効な環境だとレイアウトが壊れてるけど気にしない．

あとは Visual Studio に付属している [SignTool](https://docs.microsoft.com/ja-jp/dotnet/framework/tools/signtool-exe) コマンドで署名したら終わりだと思ったのだけど…………JPKI利用者ソフトは他のアプリケーションにAPIを提供するものではないようです(？)

Java用のJNIライブラリとテストプログラムみたいなものが入ってるので，間違ったものをダウンロードしたかと思ったけど，これで合ってるっぽい．

ダメ元でsigntoolを実行してみるとやっぱり失敗．証明書の指定方法色々あるけど，とりあえず確実そうな証明書のハッシュ値(Thumbprint)を指定しました．

```
signtool sign /fd sha256 /sha1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx test.exe
```

結果：

```
SignTool Error: No certificates were found that met all the given criteria.
```
……予想通りです．

とりあえず，JPKI利用者ソフトの存在は一旦忘れます．

## OpenSC + SignTool

次に，ICカードの読み書きをする[OpenSC](https://github.com/OpenSC/OpenSC)がJPKIにも対応していて，Windows用のミニドライバを提供してくれていました．欲しかったやつだ．素晴らしい．

signtoolコマンドでスマートカードを使うためには，/csp(Cryptographic Service Provider)と/kc(Key Container)オプションで対象を指定してあげればよいらしいです．
cspにはOpenSCを，kcオプションには，pkcs15-toolで取得した秘密鍵のGUID(すべてのマイナンバーカードで共通？)を指定しました．

カードを挿しただけではダメで，エクスポートした証明書ファイルをWindowsにインストールするかファイル名を指定する必要があるみたいです．問題がある場合はdebugオプションを付けると証明書がどのように使われたかわかります．

```
signtool sign /fd sha256 /f jpki.cer /csp "OpenSC CSP" /kc e1bc1dae-59f1-16ab-b43f-9dafbb2acc9b /debug /v test.exe
```

結果：

```
SignTool Error: No private key is available.
```

カードリーダーのLEDが点滅しているのでマイナンバーカードにアクセスしているようです．ただ，PIN入力を求められることもなく，秘密鍵が利用できないというエラーになってしまいました．秘密鍵の読み取りは不可なので署名の生成はICカード上で行わないといけないはずですが，何が起きているのかよくわかりません．

`pkcs15-tool  --verify-pin --auth-id 2` でpinを確認した直後に実行すると別のエラーになります．

```
SignTool Error: An unexpected internal error has occurred.
Error information: "Error: SignerSign() failed." (-2146435068/0x80100004)
```

このエラーは，CardAuthenticatePin()がSCARD_E_INVALID_PARAMETERを返しているっぽいので，PINがうまく扱えてないようです．

もう一歩という感じがしますが，しばらく試行錯誤しても解決しないので，SignToolを使うのは諦めます．

# OpenSC + osslsigncode

SignToolと同じことができそうなOSSを探したら，[osslsigncode](https://github.com/mtrojnar/osslsigncode)というのがありました．PKCS#11に対応したモジュールが読み込めるようなのでOpenSCを使えそうです．ソースコードが手元にあれば最悪どうにでもなりそうです．

Windows環境に色々入れたくないので，一旦Linux上で作業しました．WindowsのWSLで良いと思いますが，今回使ったカードリーダーはWSLだと動作しませんでした．(必要なものをインストールすればWindowsやMacOSでも大丈夫なはずです)

とりあえずOpenSCとosslsigncodeを入れて，libengine-pkcs11-opensslなど必要そうなものも入れたら動きました．
実行時に OpenSC の opensc-pkcs11.so と libengine-pkcs11-openssl の pkcs11.so を渡す必要があります．

jpki.pemはWindowsでエクスポートした証明書をOpenSSLでpem形式に変換するか，OpenSCのpkcs11-toolでマイナンバーカードから書き出したものです．

```
pkcs15-tool"  --verify-pin --auth-id 2 --read-certificate 2 > jpki.pem
```

証明書も住所などを含むためPINで保護されていました．PINが二種類あるので`--auth-id 2`が必要です．


```
osslsigncode sign -certs jpki.pem -pkcs11module /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so -pkcs11engine /usr/lib/x86_64-linux-gnu/engines-1.1/pkcs11.so -key 1:2 -h sha256 -t http://timestamp.digicert.com -in test.exe -out test_signed.exe
```

`-key 1:2` オプションで使う鍵を指定しないと利用者証明用電子証明書の鍵が使われてしまって，`User Authentication PIN`が要求されます．気づかずにPINを3回連続で間違うとロックされてしまうので注意が必要です．

```
Engine "pkcs11" set.
Enter PKCS#11 token PIN for JPKI (Digital Signature PIN):
Enter PKCS#11 key PIN for Digital Signature Key:
Succeeded
```

タイムスタンプもつけておいたほうが良さそうなので，とりあえずDigiCertのタイムスタンプサーバを指定してみました．タイムスタンプが署名されているとインストール後に証明書の期限が切れても警告が出ないらしいです(失効前に署名されたことが保証できれば安全ということだと思う)

# デジタル署名の確認

Microsoft Authenticodeの要求をちゃんと満たしているのかは確認してませんが，とりあえずファイルのプロパティ上では証明書が表示されました．

![codesign.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58515/24af7107-86f8-560a-d72b-594b381bd174.png)

発行者は"Japan Agency for Local Authority Information Systems"．

サブジェクトは，おそらく証明書を生成した日付と謎の数字．署名の確認時に表示されるので，ここをもう少し意味のある文字列にしてくれても良いのに．．．証明書はマイナンバーカード受け取り時のPINを入力したときに作られるものと思ってましたが，事前に生成されてるみたいですね．知らないところで誰かが作ってるのはちょっと気持ち悪いです．

あと，なぜか署名の詳細画面ではOtherNameが16進文字列で表示されてしまいます．直接内容を見たらUTF8で名前と住所と生年月日と性別が入っていました．(JPKI利用者ソフトだとちゃんと表示できるらしいです)

# まとめ

無事に署名できたので，どこの誰が作ったものなのかを保証した状態でアプリケーションを配布できるようになりました．逆に言えば，作成者の名前や住所が公開されることになります．

また，証明書のサブジェクト名が謎の数字になっているので，実行時にこれが表示されても信頼できるどころか怪しく感じます．署名の内容を確認すれば素性は確認できますが……．

色々試行錯誤しましたが，マイナンバーカードでWindowsアプリケーションにコード署名をして嬉しい場面は少なそうです．

公的個人認証サービスやAuthenticodeに詳しくないので，勘違いしていることが多いかもしれません．変なことを書いていたらご指摘いただけるとありがたいです．