最近の電子署名事情ってどうよ

# はじめに

テレワークのご時世、問題の一つは「印鑑」です。
押印のためだけに物理出勤なんかしてらんねーよ！
ということで…

2020年、たぶん使える電子署名3つ。

|  |プロトコル・フォーマット|対象|トラストチェーン|アイデンティティ|公開鍵配布方法|
|---|---|---|---|---|---|
|メール|S/MIME| 電子メール|SSLと同じ|CAに出した内容|最初にメール, DNS SMIMEA|
|gpg| PGP | any | Web of Trust, DNS | 自分で入れた内容 | 鍵サーバ, DNS CERT| 
|マイナンバーカード署名PDF | adbe.pkcs7.detached, SHA256withRSA | PDF | 日本国政府JPKI | X509v3 Subject Alternative Nameに住所・氏名・性別・生年月日 （マンナンバーは見えません） | ルート署名書はgo.jpから持ってくる |

# メール （S/MIME）

かつてはCoMoDoで無料で個人S/MIME証明書というのがありましたが、今は売り物となっています。
一応無料で証明書を出してくれるサービスもまだあることはありますが鍵ペアの生成が相手側サーバ上で行われるタイプなので、どこまで安全かというと疑問は残ります。

[Certificates for secure electronic mail \| Actalis S\.p\.A\.](https://www.actalis.it/products/certificates-for-secure-electronic-mail.aspx)

上記を順番にやっていくと、pfxという名前のpkcs12が届きます。
あとはmacOSのキーチェーンやiOSのプロファイルに突っ込めば標準のメーラで使用できるようになりますし、firefoxなどでも簡単に入ります。

後述のgpgと同じようにDNSの上に公開鍵をいれる方法もあるのですが、UDPのDNSSECにいれるにはデカイ等の課題があります（このRFCにはTCPを使え(SHOULD)と書いてあります。53/tcpを塞いでいるところって結構あるんですよね）。

最大の課題は、**対応しているメールクライアントが無い**んですね...

[RFC 8162 \- Using Secure DNS to Associate Certificates with Domain Names for S/MIME](https://tools.ietf.org/html/rfc8162)
[global end\-to\-end dnssec secured email](https://www.ends2ends.com/)

試してみる場合、[SMIMEA](https://www.co.tt/smimea.cgi) を使って生成できます。
自分でopensslとxxdを使って作る場合は、

```sh:
ローカルパート (alice@example.comならaliceの部分のsha256の先頭28オクテット)
$ echo -n alice | openssl sha256 | awk '{print substr($2, 1, 28*2)}'

レコード本体 (der形式をhexdump)
$ openssl x509 -in mysmime.crt -outform DER | xxd -ps
```

となります。
参考: [メールセキュリティとDNSの蜜月関係 - janog47_maildns_hirano_1.pdf](https://www.janog.gr.jp/meeting/janog47/wp-content/uploads/2020/11/janog47_maildns_hirano_1.pdf "メールセキュリティとDNSの蜜月関係 - janog47_maildns_hirano_1.pdf")

例えばこのサイトの`checkme at ends2ends.com`さんの場合はこんな感じです。

```
$ dig +dnssec -t type53  784fec98da493ee792a4e9ccfa72fe9e61f7eb0261bb279ecb29a401._smimecert.ends2ends.com @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10150
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;784fec98da493ee792a4e9ccfa72fe9e61f7eb0261bb279ecb29a401._smimecert.ends2ends.com. IN SMIMEA

;; ANSWER SECTION:
784fec98da493ee792a4e9ccfa72fe9e61f7eb0261bb279ecb29a401._smimecert.ends2ends.com. 299 IN SMIMEA 3 0 0 308203DF30820384A00302010202011C300A06082A8648CE3D040302
305D3117301506035504030C0E5874634E20534D494D454120434131
（中略）
46022100D612B0100A676DF52899007C7928626CA2916C65C9F38851 
4DB51D8708367B15022100E631E51A941EEDB80C6F24C13875DBBF89 
F231D80AFF1EAACD73A2C2438FFF74

784fec98da493ee792a4e9ccfa72fe9e61f7eb0261bb279ecb29a401._smimecert.ends2ends.com. 299 IN RRSIG SMIMEA 13 4 300 20200705223108 
20200703213108 13918 ends2ends.com. rRqPYPpe6bdAEuLMbsgNxiWRQEciuKpmS/29Jl5hCKT9NZ9wAUdPxis6 
LwbCuSDxlePf+udB+eoC6lNgH+kBbw==

;; Query time: 13 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
```

ポイントは `IN SMIMEA 3 0 0` になっていることでして、最後の値が1や2ですとDNSに入るのは証明書のハッシュだけなのですが、0は証明書そのものがすっぽりはいるんですね（RFC8162のSMIMEAレコードの中身はRFC6698のTLSAレコードと同じです）。

参考: [SMTPのDANEについて - Qiita](https://qiita.com/hirachan/items/0fa76f5dd9d641140d63 "SMTPのDANEについて - Qiita")

自分のドメインに貼り付けて`dig type53 なんとか._smimecert.example.com @8.8.8.8`や上記検証サイトを通ることを確認した後、`rdns querylog`しつつ、このアドレスにメールを送る操作をいくつかのMUAで行ったのですか、type53のqueryは来ませんでした😂

# gpg

みんな大好きgpg!

wotやら使い方の話は今更なのでここでは割愛して、自分のDNSサーバに鍵を入れる話です。
今日、公開鍵サーバに公開鍵を送るというのは個人情報やSPAMの問題が大きいですのでDNSの上でやります。
言うまでもありませんが、DNSSECは有効にしましょう。

参考: [PGP and DNS](https://slxh.nl/blog/2016/pgp-and-dns/)

DNSレコード長の問題が発生しにくい方法であるhttps併用でいきます。

## wkd

```sh:
$ gpg --with-wkd-hash -k ${自分のメールアドレス}
```

とやると、`ハッシュ値@ドメイン名`が表示されますので、公開鍵（asc）を

```
https://ドメイン名/.well-known/openpgpgkey/hu/ハッシュ
```

におきます。
公開鍵をとってきたい人は

```sh:
$ gpg --auto-key-locate wkd --locate-keys ${メールアドレス}
```

とすればgpgが勝手に公開鍵をhttpsでとってきてくれるという塩梅です。

## pka

こちらはDNSのレコード上に、公開鍵のhttpsの場所を載せる方法です。
DNSには鍵そのものは載りませんので、大きな鍵でも大丈夫ですし、よそのhttpsの上に載せる事もできます。

[PGP and DNS](https://slxh.nl/blog/2016/pgp-and-dns/) にある`ipgp.sh`を実行します。

```sh:ipgp.sh
#!/usr/bin/env bash
# Creates a CERT IPGP record for a key

key="$1"
url="$2"
origin=''

# Let GPG generate most of the info
data=$(gpg2 --export-options=export-pka --export "${key}")

while read -r line; do
  # Do not parse lines that do not contain a record
  if [[ "$line" != *"TYPE37"* ]]; then
    # Save the origin
    if [[ "$line" == "\$ORIGIN"* ]]; then
      origin=$(cut -d' ' -f2 <<<"${line}")
    else
      # Output all other lines
      echo "${line}"
    fi
    continue
  fi

  # Get useful data
  name=$(cut -d' ' -f1 <<<"${line}")
  len=$(cut -d' ' -f8 <<<"${line}")
  fpr=$(cut -d' ' -f9 <<<"${line}")

  # Decode the hexadecimal length and fingerprint
  fp_hex=$(xxd -r -p <<<"${len}${fpr}")

  # Encode the above together with the url as base64
  rdata=$(echo -n ${fp_hex}${url} | base64 -w0)

  # Show the record
  echo "${name}.${origin} 3600 IN CERT 6 0 0 ${rdata}"
done <<< "$data"
```

```sh:
$ ./ipgp.sh ${鍵ID} ${https上においた鍵のURL}
```

とすると、DNSにいれるCERTレコードが出てくるのでDNSにはりつけます。

```
ハッシュ値._pka.ドメイン名. 3600 IN CERT 6 0 0 うんぬんかんぬん
```

もちろん、上記のwkdの場所をそのまま入れても動きます。

公開鍵をとってくる方法は

```sh:
$ gpg --auto-key-locate pka --locate-keys ${メールアドレス}
```

です。

# マイナンバーカードで署名したPDF

建前としては最強！となるはずです。
このスマートカードには二種類の鍵ペアがありまして、以下は本名が漏れるタイプの鍵ペア（署名用証明書）を使います。
利用者認証用のほうではありません。

以下macOSを想定しますが、linuxでも同じです。

## PINについて

以下で利用するのは「６桁〜16桁」の電子署名を行うためのPINです。
5回間違えると出頭です。

[パスワードの失念 \| 公的個人認証サービス ポータルサイト](https://www.jpki.go.jp/procedure/password.html)

ちなみに

> アルファベットは大文字のみ使うことができます。 小文字で入力された場合は、大文字に変換されます。

という謎仕様です。
mynaはこの仕様にしたがって`toupper(3)`してくれますが、当然ながらOpenSCは勝手に`toupper(3)`したりはしませんので大文字で入れましょう。
小文字で入れると「mynaでは動くのにOpenSCのpkcs11経由で動かない」という現象が発生します。

## カードリーダとマイナンバーカード

PCSCを話してくれるカードリーダを持ってきてマイナンバーカードをセットします。
Amazonで1000円〜2000円くらいです。
本記事で唯一の金銭出費...

[Amazon | 【国内正規品】Gemalto ジェムアルト ICカードリーダ・ライタ IDBridge CT30 電子申告(e-Tax)対応マイナンバー・住基カード用PC USB-TR HWP119316 | ジェムアルト | 外付メモリカードリーダー 通販](https://www.amazon.co.jp/gp/product/B003XF2JJY "Amazon | 【国内正規品】Gemalto ジェムアルト ICカードリーダ・ライタ IDBridge CT30 電子申告(e-Tax)対応マイナンバー・住基カード用PC USB-TR HWP119316 | ジェムアルト | 外付メモリカードリーダー 通販")


## mynaで証明書を確認

まず自分の証明書を確認します。

[jpki/myna: マイナンバーカード・ユーティリティ・JPKI署名ツール](https://github.com/jpki/myna)

mynaをいれて

```sh:
$ myna jpki cert sign -p ${署名PIN} -f pem > my.crt
```

として証明書を作り、

```sh:
$ openssl x509 -in my.crt -text -noout
```

で確認します。

しかし、肝心の日本語（utf8）表記の本名や住所は、

```
 X509v3 Subject Alternative Name:
                othername:<unsupported>, othername:<unsupported>, othername:<unsupported>, othername:<unsupported>, othername:<unsupported>, othername:<unsupported>
```

となってしまって見えません。
普通の`CN`はシリアル番号らしき数値で、`L`には市町村名がローマ字表記で入っています。
SANの中身を見るには、

```sh:
$ openssl asn1parse -in my.crt
```

とするとhexで出ます。
たとえば、

```
  613:d=4  hl=3 l= 229 cons: SEQUENCE
  616:d=5  hl=2 l=   3 prim: OBJECT            :X509v3 Subject Alternative Name
  621:d=5  hl=3 l= 221 prim: OCTET STRING      [HEX] DEADBEEF01234…
```

となればoffsetが621とのことなので

```sh:
$ openssl asn1parse -in my.crt -strparse 621
```

とすると、

```
   0:d=0  hl=3 l= 218 cons: SEQUENCE
    3:d=1  hl=2 l=  31 cons: cont [ 0 ]
    5:d=2  hl=2 l=  10 prim: OBJECT            :1.2.392.200149.8.5.5.1
   17:d=2  hl=2 l=  17 cons: cont [ 0 ]
   19:d=3  hl=2 l=  15 prim: UTF8STRING        :${お名前}
   36:d=1  hl=2 l=  25 cons: cont [ 0 ]
   38:d=2  hl=2 l=  10 prim: OBJECT            :1.2.392.200149.8.5.5.4
   50:d=2  hl=2 l=  11 cons: cont [ 0 ]
   52:d=3  hl=2 l=   9 prim: UTF8STRING        :${生年月日}
   63:d=1  hl=2 l=  17 cons: cont [ 0 ]
   65:d=2  hl=2 l=  10 prim: OBJECT            :1.2.392.200149.8.5.5.3
   77:d=2  hl=2 l=   3 cons: cont [ 0 ]
   79:d=3  hl=2 l=   1 prim: UTF8STRING        :${性別}
   82:d=1  hl=2 l=  76 cons: cont [ 0 ]
   84:d=2  hl=2 l=  10 prim: OBJECT            :1.2.392.200149.8.5.5.5
   96:d=2  hl=2 l=  62 cons: cont [ 0 ]
   98:d=3  hl=2 l=  60 prim: UTF8STRING        :${住所}
```

と出てきます。

### 2021/04/06 追記

[戸籍の氏名に読みがな 再来年までに改正案提出 上川法相 | NHKニュース](https://www3.nhk.or.jp/news/html/20210406/k10012958561000.html "戸籍の氏名に読みがな 再来年までに改正案提出 上川法相 | NHKニュース")

> 上川法務大臣は記者会見で「マイナンバーカードに氏名をローマ字で表記できるよう、個人のカナ氏名を戸籍の記載事項とするための法案を提出し、デジタル化に向けた取り組みをしっかりと進めていく」と述べ再来年の通常国会までに、戸籍法など必要な法律の改正案を提出する考えを明らかにしました。

だそうです。
今後はCNにローマ字表記の名前がはいるようになるかもしれませんね。
しかしどうやって初期登録するのでしょうね。
全員にお伺いをたてるのか、一部の難読名の人に調査票を送るのか、マイナンバーカードの交付・更新のときに登録するのか...戸籍をみたらマイナンバーカードを発行しているか否かがバレますけどいいのでしょうか。

## jsignpdfによる署名

OpenSCを入れた後、

[JSignPdf download \| SourceForge\.net](https://sourceforge.net/projects/jsignpdf/)

を使います。
macの場合

```sh:
$ cd /Applications/JSignPdf.app/Contents/Resources
$ echo "pkcs11config.path=conf/pkcs11.cfg" >> conf/conf.properties
$ echo -e "name=JPKI-SIGN\nlibrary=/Library/OpenSC/lib/opensc-pkcs11.so\nslot=1" >> conf/pkcs11.cfg
$ java -Djava.security.debug=sunpkcs11 -jar Java/JSignPdf.jar -lkt
（PKCS11が出てることを確認する）
$ java -Djava.security.debug=sunpkcs11 -jar Java/JSignPdf.jar \
    -kst PKCS11 -ksp ${署名PIN} -lk
```

とします。
出力が

```
DEBUG Relaxing SSL security.
DEBUG Registering SunPKCS11 provider from configuration in conf/pkcs11.cfg
SunPKCS11 loading /Applications/JSignPdf.app/Contents/Resources/conf/pkcs11.cfg
sunpkcs11: Initializing PKCS#11 library /Library/OpenSC/lib/opensc-pkcs11.so
Information for provider SunPKCS11-JPKI-SIGN
Library info:
中略
DEBUG SunPKCS11 provider registered with name SunPKCS11-JPKI-SIGN
INFO  Getting keystore type instance: PKCS11
sunpkcs11: login succeeded
INFO  Getting key alias
INFO  Key aliases in the keystore:
Digital Signature Certificate
DEBUG Removing security provider with name SunPKCS11-JPKI-SIGN
```

となれば成功です。
最後から2行目が使える鍵の名前ですね。

エラーが出ているときはmynaで確認をしておきましょう。
PINリトライ回数が減っているのに気づかずにやっていると、役所出頭になります。

```
$ myna pin status
券面事項PIN(A):    のこり10回
券面事項PIN(B):    のこり10回
入力補助PIN:    のこり 3回
入力補助PIN(A):    のこり10回
入力補助PIN(B):    のこり10回
JPKI認証用PIN:    のこり 3回
JPKI署名用PIN:    のこり 5回
```

0になるとロックです。

うまく行かない時は、上記の`pkcs11.conf`を`pkcs11-spy.so`にして、

```sh:
$ env PKCS11SPY=/Library/OpenSC/lib/opensc-pkcs11.so \
  PKCS11SPY_OUTPUT=logfile \
  java -Djava.security.debug=sunpkcs11 -jar Java/JSignPdf.jar ...
```

とやるとデバック情報を得ることが出来ます。

PDFへの署名は、

```sh:
$ java -jar Java/JSignPdf.jar -kst PKCS11 \
  -ksp ${署名PIN} -d ${出力ディレクトリ} ${署名したいpdf} 
```

で、`${出力ディレクトリ}/${もとのPDFの名前}_sigined.pdf`の名前で署名されたpdfが出来ます。

FreeTSAによる時刻の証明をつける場合は、

```
-ts https://freetsa.org/tsr -tsh SHA-512
```
も追加です。

参考: 
[FreeTSA \- How to sign documents with time stamp > JSignPdf](https://freetsa.org/guide/JSignPdf-time-stamping.html)
[フリーで使えるタイムスタンプサーバ | InfraWare, Inc.](https://www.infra-ware.net/support/freetsa/ "フリーで使えるタイムスタンプサーバ | InfraWare, Inc.")
[akr/pdftimestamp: pdftimestamp add a document timestamp signature for a PDF file.](https://github.com/akr/pdftimestamp "akr/pdftimestamp: pdftimestamp add a document timestamp signature for a PDF file.")

## 検証

以下は自分のマイナンバーカードを用いてやりましょう。

[総務省｜マイナンバー制度とマイナンバーカード｜公的個人認証サービスによる電子証明書（民間事業者向け）](https://www.soumu.go.jp/kojinbango_card/kojinninshou-02.html "総務省｜マイナンバー制度とマイナンバーカード｜公的個人認証サービスによる電子証明書（民間事業者向け）")

> 公的個人認証法が改正され、平成28年1月より、民間事業者においても電子証明書の有効性を確認する者（署名検証者）となり、公的個人認証サービスを活用できることとなりました。

[公的個人認証サービス利用のための民間事業者向けガイドライン1．1版](https://www.soumu.go.jp/main_content/000400619.pdf "000400619.pdf")

> ただし、民間事業者は、公的個人認証サービスを用いた公的個人認証サービスの利用に際し、総務大臣による認定を受ける必要がある。

多分自分で署名して自分で検証するだけなら合法です（自分でveirfyできなければ自分の署名が正しいか確認できない）。

また、JPKIのCA証明書は普通のOSの`/etc/ssl/certs/`にはありませんので、自分で追加します。

### acrobat

以下を参照してください（丸投げ！）。

[マイナンバーカードを使ってPDFに電子署名をしよう！](https://jpki.osdn.jp/validation.html)
[公証人に認証された電子署名済みの電磁的記録を検証する \- Qiita](https://qiita.com/data9824/items/e18fc54e2a47945e9730)
[PDF の電子署名\(certificate, digitally sign\)の仕組みと電子締結運用のメモ \- Qiita](https://qiita.com/syoyo/items/180b32ca0ec81d13c26b)

### poppler

macの場合はhomebrewで入ります。

```sh:
$ pdfsig ${署名済みpdf}
Digital Signature Info of: ${署名済みpdf}
Signature #1:
  - Signer Certificate Common Name: シリアル番号
  - Signer full Distinguished Name: CN=シリアル番号,L=市町村名,L=都道府県名,C=JP
  - Signing Time: Jul 01 2020 00:00:00
  - Signing Hash Algorithm: SHA1
  - Signature Type: adbe.pkcs7.detached
  - Signed Ranges: [0 - 160], [30162 - 762262]
  - Total document signed
  - Signature Validation: Signature is Valid.
  - Certificate Validation: Certificate issuer is unknown.
```

popplerはnssを使っているので、

```sh:
$ certutil -A -t c,c,c -i my.crt -n JPKI -d ${適当なディレクトリ}
$ certutil -A -t c,c,c -i ${JPKIのCA証明書} -n JPKIROOT -d ${適当なディレクトリ}
```

で`${適当なディレクトリ}/cert9.db`に証明書を登録できますが、登録したところでSANのutf8までは表示してくれません。
最後の部分が

```
  - Certificate Validation: Certificate is Trusted.
```

になるだけです。

#### SAN値チェック

popplerのpdfsigにはダンプオプションがありますので、それを使ってSANを確認できます。

```sh:
$ pdfsig -dump ${署名済みpdf}
$ openssl asn1parse -in ${署名済みpdf}.sig0 -inform der | grep -A2 "X509v3 Subject Alternative Name"
  672:d=9  hl=2 l=   3 prim: OBJECT            :X509v3 Subject Alternative Name
  677:d=9  hl=3 l= 221 prim: OCTET STRING      [HEX DUMP]:DEADBEEF01234...
（二行目の先頭の数値を使う）
$ openssl asn1parse -in ${署名済みpdf}.sig0 -inform der -strparse 677
    0:d=0  hl=3 l= 218 cons: SEQUENCE
    3:d=1  hl=2 l=  31 cons: cont [ 0 ]
    5:d=2  hl=2 l=  10 prim: OBJECT            :1.2.392.200149.8.5.5.1
   17:d=2  hl=2 l=  17 cons: cont [ 0 ]
   19:d=3  hl=2 l=  15 prim: UTF8STRING        :${お名前}
...
```

参考:
[php \- How to extract and verify PDF signature \(PKCS7\) with openssl? \- Stack Overflow](https://stackoverflow.com/questions/54356538/how-to-extract-and-verify-pdf-signature-pkcs7-with-openssl)

# 結論

メールはS/MIMEで署名しましょう。PPAPしてくるヤツを見かけたら殺しましょう。

普通のファイルはgpgで署名しましょう。ただし公開鍵を鍵サーバにあげてはいけません。自分のhttpsとdnssecの上においておいて、相手には`gpg --auto-key-locate`でとってもらうようにお願いしましょう。

ガチで本名で署名したいときは、PDFにマイナンバーカードで署名しましょう。
PDFを受け取った相手が「なんか不明な署名がついている」「名前が出てこない」と言い出したら、本記事のURLを告げて、それで面倒だと言い出したら「名前をSANにしか入れない上にJPKIを広めていない日本国政府が悪い」といっておきましょう（笑）。

では皆様、よいテレワーク電子署名ライフを！