2021/07/10(Sat)
○[Windows] オレオレ証明書によるコード署名の手順(その3 - New-SelfSignedCertificateコマンドレット編)
これまで makecert編と certreq編でコード署名用のオレオレ証明書を作成する手順を解説したところで力尽きとったのだけど、その続きを思い出したようにやるよ。 つーかチラシの裏に書かなかったせいでまたいちから調べなおす羽目になってな…まぁやることは一緒なのでmakecertでやってた事ををNew-SelfSignedCertificateに翻訳していけばいいだけの話なんだが。
@証明書の出力先の指定
以前のmakecertではファイルに出力できたのだけど、証明書ストアしか指定できなくなってしまったのでちょっとめんどくさい。 ファイルシステム上にそんな重要なもの置くなってことだろうが、どうせPKCS #12形式でエクスポートできるんだし意味あるんですかと以下略
デフォルトの出力先はローカルマシンの証明書ストア(certlm.msc)の「個人」でパスは"Cert:LocalMachine\My"となる。
デフォルトでも別に構わんのだが管理者権限でやりたくない気分なので、現在のユーザーの証明書ストア(certmgr.mcs)、パスは"Cert:CurrentUser\My"に変更するために
-CertStoreLocation "Cert:CurrentUser\My"
を指定する。
鍵ペアが「個人」に作成されるけど、それとは別に公開鍵だけのコピーが「中間証明機関」にも置かれるので、こいつをドラッグアンドドロップで「信頼されたルート証明機関」に移動する。
移動はMove-Itemコマンドレットでもできるんだが、本当にいいか?本物か?指紋確認したか?の警告ダイアログが出てスキップもできないのでスクリプトでの完全自動化はできないことに注意。
@自己署名証明書でオレオレCA(認証局)を建て、そいつで署名したコード署名用証明書を作成する
まず自己署名証明書の発行だけど、makecertの場合は明示的に-rオプション指定する必要あった。 しかしコマンドレット名にSelfが入ってるから察しはつくと思うがNew-SelfSignedCertificateで生成した証明書はデフォルトで自己署名となる。
これを指定のルート証明書で署名された証明書を発行するには
-Signer "Cert:CurrentUser\My\<フィンガープリント>"
をオプションに指定する。
処理をスクリプトで自動化するのならば
$location = 'Cert:CurrentUser\My'
$rootkey = (New-SelfSignedCertificate `
-CertStoreLocation $loc `
...
).Thumbprint
$appskey = (New-SelfSignedCertificate `
-CertStoreLocation $location `
-Signer "$location\$rootkey" `
...
).Thumbprint
という流れになるんやな。
ところで昔からなぜMSはPublic Key Fingerprint(公開鍵指紋)ではなくThmbprint(拇印)と呼ぶのか疑問だったのだが、男はみなボインが大好きだからでは?という結論に達した。
@主体とフレンドリ名の設定
まず最低限必要なオプションは
-Subject "CN=Example Root CA,O=Example Corp.,OU=Example Div.,C=JP,DC=com,DC=example"
これで証明書の主体(Subject)を指定する、サーバー証明書じゃないのでドメイン名である必要も無いのでお好きにどうぞ。
ちなみにmakecertでは証明書のフレンドリ名が指定できなかった(certreqでは可能)のだけど、New-SelfSignedCertificateでは可能になった。
-FriendlyName "Example Root CA 2021"
などと何の目的の証明書か区別し易い文言を指定しておいた方がいい、まぁしょせんはオレオレだから好きにしろ。
@基本制限(basicConstraints)を設定する
まずCAの場合は
- 自己署名証明書がCAである
- このCAからさらに中間CAを建てられないようにする(Path Length Constraint)
という基本制限を与えたいのだけれども、でもmakecertの-cy authorityオプションの同等品が無いのだよね。 ちなみにmakecertだとPath Length Constraintを設定できないという問題もあった。
しゃーないので-TextExtensionを使ってパラメーターを設定する、basicConstraintsのオブジェクト識別子(OID)は2.5.29.19、よって
-TextExtension @("2.5.29.19={critical}{text}ca=1&pathlength=0")
と指定する。
証明書の拡張領域(certificateExtension)のOID一覧を知りたければ ここでも読んでどうぞ。
コード署名用の場合は
- 証明書がEnd Entityである
という基本制限を与えたい、makecertでは-cy endオプションだったけどこれもさっきと同様-TextExtensionで
-TextExtension @("2.5.29.19={critical}{text}ca=0")
と指定すればよい。
@キー使用法(keyUsage)を設定する
お次はキー使用法、デフォルトでは
- DigitalSignature … 署名用
- KeyEncipherment … 鍵交換用
が有効になっているのだけど、署名用のみでいいので
-KeyUsage DigitalSignature
ちうこと、より厳密な運用をしたければCAは
-KeyUsage CertSign, CRLSign
として証明書および失効リストの署名に制限してもよい。
このキー使用法についてはPowerShellのマニュアルだけだと指定できる値の羅列だけで具体的な説明が無いので、 ここあたりを入り口にX.509証明書を完全にマスターしておくように。
@拡張キー使用法(extKeyUsage)を設定する
より厳密に運用するために拡張キー使用法というものが存在する。
こいつは-Typeオプションで指定された証明書テンプレートによって決まる、指定可能なテンプレートは
- CodeSigningCert … コード署名
- Custom … カスタム(テンプレートを使わない)
- DocumentEncryptionCert … ドキュメントの暗号化(署名ハッシュアルゴリズムはSHA256)
- DocumentEncryptionCertLegacyCsp … ドキュメントの暗号化(署名ハッシュアルゴリズムはSHA1)
- SSLServerAuthentication … クライアント認証およびサーバー認証
の5パターンで、指定しない場合のデフォルト値はSSLServerAuthenticationとなってる。
注意すべき点はこのオプションはテンプレでなので全ての拡張キー使用法には対応してない事、なのでクライアント認証およびサーバー認証 + コード署名みたいに細かい指定ができない。 またドキュメントの暗号化のように拡張キー使用法は無関係の署名ハッシュアルゴリズムまで変わってしまうのだよな。
こんなもんまったくもって大きなお世話なので、パラメーターには常に「Custom」を指定してテンプレなぞ無視してしまうことをお勧めする。
-Type Custom
これによって拡張キー使用法は未設定となる。
そんであらためて明示的に拡張キー使用法を指定するには、makecertの場合-ekuオプションというものがあったのだけど、やっぱりこれも-TextExtensionの出番、extKeyUsageのOIDは2.5.29.37となる。
-TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.3")
値はカンマ区切りで列挙する、この例だと
- serverAuth(1.3.6.1.5.5.7.3.1) … サーバー認証
- clientAuth(1.3.6.1.5.5.7.3.2) … クライアント認証
- codeSigning(1.3.6.1.5.5.7.3.3) … コード署名
を指定している、.と,区別しづらくてアレなのだが我慢しろ。
その他の拡張キー使用法として指定可能なOID一覧は ここを参照してくれ。
@署名ハッシュアルゴリズムの指定
次は署名ハッシュアルゴリズムの話が出てきたのでその変更方法、makecertはデフォルトがSHA1だったので明示的にSHA256に変更する必要があったのだけど、 デフォルトはSHA256なので特にいじる必要も無い、変更自体は-HashAlgorithmで可能なので例えばSHA512使いたければ
-HashAlgorithm SHA512
と指定すればいい。
@暗号鍵アルゴリズムの設定
暗号鍵アルゴリズムのデフォルトもRSA 2048bitなので特に急いで変更する必要も無い、もしより強度を上げたいなら
-KeyLength 4096
とビットマシマシ可能。
またRSAではなく楕円曲線暗号を使いたいのなら-KeyAlgorithm使って
-KeyAlgorithm ECDSA_nistP521
などとして指定すりゃおk、使える楕円曲線は
certutil -displayEccCurve
実行して表示されるリストからどうぞ。
まぁコード署名に楕円曲線暗号使うメリットは無いので今回は使わないけどね。
余談になるけどOpenSSHなんかで使われてるCurve25519使えないのね、ECDSAとかNSAがバックドア仕込んでるって噂あるし怖いんですけお!やはりビッグなテックはディープなステートの手先!
@証明書の有効期間の設定
デフォルトでは作成日時の翌年同日同時刻プラス20分となる、これを変更するには-NotAfter/-NotBeforeオプションを指定する。
$since = [DateTime]"2021/07/11 00:00:00"
$until = [DateTime]"9999/07/10 23:59:59"
-NotBefore $since -NotAfter $until
これはPowerShellの問題だけど西暦10000年問題があるやね(ニッコリ)、よし RFC2550の出番だな!
なお四月莫迦ネタでなしにPowerShellの日付関連には和暦が絡むと限りなくバグに近い仕様がある( その1その2)ので注意されたし、まぁ和暦に設定してる輩なんておらんから大丈夫か。
また証明書の期限の表示にも和暦がらみの バグがあるのだがこれ21H1でも直ってないね、まぁこんなの気づくやつ普通はおらんから当然か。
そして文字列からDateTimeへのキャストは現在のタイムゾーンつまりJSTとして解釈されることに注意、UTC使う方法はいずれ別記事おこす。
まぁMSは鍵暗号アルゴリズムがRSA 2048bitは2030年めどに廃止にしろといってるので2029年末までにしておこう。
@本当はCRL配布ポイントも設定したいのだけど
おそらく-TextExtensionで指定すれば可能なはずなんだけど(cRLDistributionPointsのOIDは2.5.29.31)、どうにも上手くいかないので諦めた。 CertEnroll.dllのドキュメントにはちゃんとCrlDistributionPointsについての記述あるんだけどね…
@とりあえず完成
これまでのオプションをまとめると
$location = 'Cert:CurrentUser\My'
$since = [DateTime]"2021/07/11 00:00:00"
$until = [DateTime]"2029/12/31 23:59:59"
$rootkey = (New-SelfSignedCertificate `
-CertStoreLocation $location `
-Subject "CN=Example Root CA,O=Example Corp.,OU=Example Div.,C=JP,DC=com,DC=example"
-FriendlyName 'Example Root Certificate Authority 2021' `
-Type Custom `
-TextExtension @( `
'2.5.29.19={critical}{text}ca=1&pathlength=0' `
) `
-KeyUsage CertSign, CRLSign `
-NotBefore $since -NotAfter $until `
).Thumbprint
$appskey = (New-SelfSignedCertificate `
-CertStoreLocation $location `
-Signer "$location\$rootkey" `
-Subject "CN=Example Apps,O=Example Corp.,OU=Example Div.,C=JP,DC=com,DC=example"
-FriendlyName 'Example Applications Signer 2021' `
-Type Custom `
-TextExtension @( `
'2.5.29.19={critical}{text}ca=0', `
'2.5.29.37={text}1.3.6.1.5.5.7.3.3' `
) `
-KeyUsage DigitalSignature `
-NotBefore $since -NotAfter $until `
).Thumbprint
ちゅー感じですかね。
@次回
作った証明書でドライバのセキュリティカタログとかPowerShellスクリプトに署名する方法を書く、いついなるかはまったくわからん。