本文へスキップ
ecdsa.com
← すべての記事

Authenticode コード署名:鍵・タイムスタンプ・検証

小さな Windows 実行ファイルに osslsigncode 2.14 で署名して DigiCert のタイムスタンプを付け、3 つのテストを行いました。対象は、タイムスタンプなしで署名したコピー、コードを 1 バイト変更したファイル、証明書テーブルにデータを隠したファイルです。規則は CA/Browser Forum のコード署名ベースライン要件 3.11.0 と Microsoft 自身のドキュメントに基づくもので、いずれも 2026年10月に確認しました。

要点:Authenticode は、Windows PE ファイルの末尾にあるテーブルに PKCS #7 の SignedData を埋め込みます。署名の対象は、チェックサム、そのテーブルを指すポインター、テーブル自体を除いて計算したファイルのハッシュです。2023年6月1日以降、公的に信頼されるコード署名鍵は、ハードウェアモジュール(トークン、オンプレミスの HSM、または完全な監査ログを備えたクラウド HSM サービス)で生成し、使用しなければなりません。そのため、ビルドサーバーに置いたおなじみの .pfx ファイルでは、もう証明書を取得できません。2026年3月1日以降に発行される証明書の有効期間は最長 460 日で、Microsoft の Artifact Signing が発行する証明書の有効期間は 72 時間です。そのため、署名を有効に保つ役割を担うのは RFC 3161 タイムスタンプです。署名には必ず /fd SHA256 /tr <TSA URL> /td SHA256 を指定してください。EV 証明書を使っても、SmartScreen のレピュテーションをすぐに得られることはなくなりました。検証には、Windows では signtool verify /pa /tw、それ以外の環境では osslsigncode verify を使います。

署名の対象

PE ファイル(.exe、.dll、.sys)のオプションヘッダーにはデータディレクトリがあります。そのエントリ 4 である証明書テーブル(Certificate Table)は、ファイルの末尾に追加された WIN_CERTIFICATE 構造体のリストについて、ファイルオフセットとサイズを保持しています。Authenticode のエントリは、リビジョンが 0x0200、種類が WIN_CERT_TYPE_PKCS_SIGNED_DATA(0x0002)で、PKCS #7 の SignedData を格納しています(PE 形式)。

署名されるのはファイルそのものではなく、ファイルのハッシュを保持する SpcIndirectDataContent です。このハッシュには署名自体を含められないため、Microsoft の説明によれば「PE イメージの特定のフィールド(オプションヘッダーのデータディレクトリにあるチェックサムや証明書テーブルのエントリなど)を除外しなければならない」とされ、さらに証明書テーブル自体も除外されます。それ以外のヘッダー、セクション、最後のセクションより後ろのデータは、すべてハッシュの対象です。

サンプルは、Go 1.27.3 でクロスコンパイルした 1.6 MB のプログラムです(GOOS=windows GOARCH=amd64)。短い Python スクリプトで、署名の前後に証明書テーブルのエントリを読み取ります。

python3 pe.py hello.exe show; python3 pe.py hello-signed.exe show の出力
file size 1651712, certificate table at offset 0, size 0
file size 1660952, certificate table at offset 1651712, size 9240
WIN_CERTIFICATE: dwLength 9240, wRevision 0x0200, wCertificateType 0x0002

この 9,240 バイトが、署名、証明書チェーン、タイムスタンプトークンです。抽出した署名に含まれるオブジェクト識別子を一覧すると、通常の CMS の要素と並んで、Authenticode 固有の要素があることがわかります。

openssl asn1parse -inform DER -in sig.der | grep -o 'OBJECT *:[^ ]*' | sort | uniq -c(抜粋)
      1 OBJECT            :1.3.6.1.4.1.311.2.1.11
      1 OBJECT            :1.3.6.1.4.1.311.2.1.12
      1 OBJECT            :1.3.6.1.4.1.311.2.1.15
      2 OBJECT            :1.3.6.1.4.1.311.2.1.4
      1 OBJECT            :1.3.6.1.4.1.311.3.3.1
      2 OBJECT            :id-smime-ct-TSTInfo
      2 OBJECT            :messageDigest
      2 OBJECT            :signingTime

1.3.6.1.4.1.311.2.1.4 は SpcIndirectDataContent、…2.1.15 はその中の PE イメージデータ、…2.1.11 と …2.1.12 はそれぞれステートメントの種類と、プログラム名および URL です。1.3.6.1.4.1.311.3.3.1 は非署名属性として格納された RFC 3161 タイムスタンプトークンで、そのコンテンツタイプは id-smime-ct-TSTInfo です。

2026年の証明書と鍵

公的に信頼されるコード署名証明書には、CA/Browser Forum のコード署名ベースライン要件(CSBR)が適用されます(バージョン 3.11.0、2026年6月16日)。ビルドの方法に影響する規則は次のとおりです。

  • ハードウェア鍵の全面義務化:「2023年6月1日発効」の規定により、加入者の秘密鍵は、少なくとも FIPS 140-2 レベル 2 または Common Criteria EAL 4+ の認定を受けたハードウェア暗号モジュールで生成、保管、使用しなければなりません(§6.2.7.4.1)。認められる方法は、自分で運用するハードウェアモジュール、クラウドの鍵サービス(HSM がその基準を満たし、サブスクリプションが「すべてのアクセス、操作、設定変更を記録するよう構成されている」もの)、署名サービスのいずれかです。CA はこれを確認しなければなりません。たとえば、事前に生成した鍵を格納したトークンを発送する方法や、鍵のアテステーションを確認する方法があります(§6.2.7.4.2)。2023年より前は、これは EV 証明書だけに適用されていました。
  • 鍵長:RSA 鍵は 3072 ビット以上、ECDSA は P-256、P-384、P-521 のいずれかです(§6.1.5.2)。
  • 有効期間:2026年3月1日以降に発行される証明書は最長 460 日、それより前は 39 か月です(§6.3.2)。
  • タイムスタンプ局:TSA 証明書の有効期間は最長 135 か月で、その鍵は notBefore から 15 か月間使用できます。SHA-1 のタイムスタンプトークンは、2022年4月以降禁止されています。

Microsoft はさらに独自の要件を上乗せしています。Windows 10 バージョン 1607 以降、カーネルモードドライバーには Windows Hardware Dev Center を通じた Microsoft の署名が必要なため、商用の証明書だけではドライバーを読み込めません(ドライバー署名ポリシー)。また、EV はもはや「SmartScreen の警告が出ない」ことを意味しません。Microsoft の配布機能のステータスのページには、2024年の Trusted Root Program の変更以降、「OV と EV のどちらの種類の証明書でも、即座に回避できることはない」と書かれています。レピュテーションは、ファイルのハッシュごと、署名者の ID ごとに蓄積されます。

Microsoft のマネージドサービスである Artifact Signing(旧称 Trusted Signing)は、Microsoft が運用する HSM に鍵を保管し、「毎日更新され、有効期間はわずか 72 時間」の証明書を発行します(証明書の管理)。ビルドマシンの外に出るのはダイジェストだけです。証明書の有効期間がこれほど短いと、タイムスタンプのない署名は 3 日後には役に立たなくなります。同じページには、ほかに 2 つの詳細が記されています。1 つは、証明書が毎日変わるため、証明書プロファイルごとに 1.3.6.1.4.1.311.97. 配下の永続的なカスタム EKU が割り当てられ、信頼をこれに固定できることです(たとえばアプリケーション制御ポリシーで)。もう 1 つは、その CA が CCADB から削除されつつあり、Microsoft Trusted Root Program にはもう含まれていないものの、「Windows Platform Trust List には残っている」ことです。そのため Windows は引き続きこれらの CA を信頼しますが、ほかのプラットフォームは信頼しない可能性があります。

osslsigncode による署名と検証

osslsigncode は、Linux や macOS で使える Authenticode の実装です。実験(debian:forky-20261005-slim 上の Debian パッケージ 2.14)では、プライベートなテスト用の階層(ルート、発行 CA、EKU codeSigning を持つ RSA 3072 のコード署名証明書)を作成し、プログラムに署名して、DigiCert の公開 TSA にタイムスタンプを要求しました。

lab.sh(抜粋):RFC 3161 タイムスタンプ付きで署名し、検証
osslsigncode sign -certs chain.pem -key codesign.key -h sha256 -n "Hello" -i "https://example.test/hello" \
  -ts http://timestamp.digicert.com -in hello.exe -out hello-signed.exe
osslsigncode verify -CAfile root.pem -TSA-CAfile /etc/ssl/certs/ca-certificates.crt -in hello-signed.exe

TSA に送られるのは署名のハッシュだけです。検証結果は次のとおりです(繰り返されるチェーンの一覧は短縮しています)。

osslsigncode verify(一部省略)
Message digest algorithm  : SHA256
Current message digest    : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7 
Calculated message digest : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7 

Signer's certificate:
	------------------
	Signer #0:
		Subject: CN=Example Software GmbH,O=Example Software GmbH,C=DE
		Issuer : CN=Example Test Code Signing CA,O=Example Test
		Serial : 20ADC33D7219F573885B44765077E96312F72F6D
		Certificate expiration date:
			notBefore : Oct 22 06:32:28 2026 GMT
			notAfter : Nov 21 06:32:28 2026 GMT

Authenticated attributes:
	Signing time: Oct 22 06:32:28 2026 GMT
	Microsoft Individual Code Signing purpose
	Message digest: DE669CE86A064FAEB4DFE8E43E14090CE5912FA036C527C13D5558B0BF110BB7 
	URL description: https://example.test/hello
	Text description: Hello

Countersignatures:
	Timestamp time: Oct 22 06:32:29 2026 GMT
	Signing time: Oct 22 06:32:29 2026 GMT
	Hash Algorithm: sha256
	Issuer: CN=DigiCert Trusted G4 TimeStamping RSA4096 SHA256 2025 CA1,O=DigiCert\, Inc.,C=US
	Serial: 084FDC334F7E454EDBC30F8FF9921835
...
Timestamp Server Signature CRL verification: ok
Timestamp Server Signature verification: ok
Signature verification time: Oct 22 06:32:29 2026 GMT
...
Signature verification: ok

Number of verified signatures: 1
Succeeded

「Current message digest」は署名に格納されたハッシュ、「Calculated」はファイルから再計算したハッシュです。署名者自身の「Signing time」は、署名者の時計に基づく自己申告にすぎません。「Timestamp time」は TSA が署名したもので、検証者が頼りにするのはこちらです。そのうえで osslsigncode は、署名をタイムスタンプの時刻の時点で検証しました。これこそが、この仕組み全体の狙いです。

タイムスタンプが重要な理由

これを確かめるため、実験では同じプログラムに -ts なしでもう一度署名し、有効期間 30 日のテスト証明書の有効期限が切れている 60 日後を想定して、両方のファイルを検証しました(osslsigncode verify -time)。

現在から 60 日後の時点での検証(行を抽出、OpenSSL のエラー位置は短縮)
hello-signed.exe   Signature verification time: Oct 22 06:32:29 2026 GMT Signature verification: ok Succeeded
hello-nots.exe     ...:PKCS7_verify:certificate verify error:...:Verify error: certificate has expired Signature verification time: Dec 21 06:32:29 2026 GMT Error: certificate has expired ... Signature verification: failed Failed

タイムスタンプがあれば、チェックは証明された署名時刻の時点で行われるため、署名は署名証明書の有効期限が切れた後も有効なままです(今回使用した DigiCert のタイムスタンプレスポンダー証明書自体は 2037年11月まで有効です)。タイムスタンプがなければ、検証には現在時刻が使われ、証明書の有効期限が切れた日に失敗します。これは、どの TLS クライアントでも起きる certificate has expired の失敗と同じです。Microsoft はさらに踏み込んでいます。タイムスタンプが署名証明書と TSA 証明書の両方の有効期間内にあれば署名は有効であり、「署名証明書と TSA 証明書の有効期限が切れたずっと後でも有効です(いずれかが失効した場合を除く)」としています。

失効も時刻に紐づいています。CSBR では、CA はコード署名証明書の revocationDate を、その証明書が最初に無効と見なされた日(鍵の危殆化の場合は「危殆化が疑われる最も早い日」)に設定でき、「ソフトウェアは、失効日より前の日付のタイムスタンプを含むオブジェクトを、引き続き有効として扱うべきである」とされています(§4.9.6)。したがって、鍵が漏洩しても無効になるのは危殆化の後にタイムスタンプが付けられた署名であり、これまでに出荷したすべてのリリースではありません。

署名を壊す変更と壊さない変更

次の 2 つの変更を加え、同じコマンドで検証しました。

コードの 1 バイトを変更(オフセット 4096)、続いて証明書テーブル内に 64 バイトを追加
### one byte of code changed
Signature verification: failed
Message digest algorithm  : SHA256
Current message digest    : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7 
Calculated message digest : E754D0A7C1AB0CA1716866C67046BA54D2C195CDCBA4A182D56DD5C57FCD37B6     MISMATCH!!!
Failed
### extra bytes inside the certificate table
appended 64 bytes inside the certificate table: size 9240 -> 9304
Warning: invalid PE checksum
Calculated message digest : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7 
Timestamp Server Signature verification: ok
Signature verification: ok
Succeeded

2 つ目のファイルでは、証明書テーブルのエントリと WIN_CERTIFICATE の長さを 64 バイト増やし、そこにデータを入れました。テーブルはハッシュの対象外なので、署名は検証に通ります。正しくないのは、同じくハッシュの対象外である PE チェックサムだけです。これが CVE-2013-3900 の背景にある構造です。自分自身のファイルの末尾から設定を読み込むインストーラーは、署名を無効にすることなく、攻撃者のペイロードを渡される可能性がありました。Microsoft は 2013年に、オプトイン方式のレジストリ設定 EnableCertPaddingCheck としてこれを修正し、2022年の再公開では「より厳格な検証動作を既定として強制する予定はない」としています。CISA は 2022年1月に、この CVE を既知の悪用された脆弱性(KEV)カタログに追加しました。開発者への教訓:自分の実行ファイルの証明書テーブル領域からは決してデータを読み取らず、管理するマシンでは EnableCertPaddingCheck を設定してください。

Windows の場合:signtool

Windows SDK に含まれる SignTool がリファレンス実装です。現行のバージョンでは、ダイジェストアルゴリズムを明示的に指定する必要があります。「SignTool の sign コマンドには、ファイルダイジェストアルゴリズムのオプション(/fd)とタイムスタンプダイジェストアルゴリズムのオプション(/td)が必要です」(SignTool)。証明書を Windows の証明書ストアに置き、鍵をトークンに格納している場合、一般的なコマンドは次のとおりです。

signtool(Microsoft のドキュメントより。ここでは未実行)
signtool sign /n "Example Software GmbH" /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /d "Hello" MyApp.exe
signtool verify /pa /tw /v MyApp.exe
  • /n または /sha1 で証明書を選択します。鍵は、CSP または KSP の背後にあるトークンや HSM に置かれたままです(/csp と /kc で明示的に指定します)。
  • /tr は RFC 3161 サーバーに問い合わせます。古い /t は、従来の Authenticode タイムスタンププロトコルを使います。
  • verify /pa は既定の Authenticode ポリシーを使います。これを指定しないと、SignTool はドライバーのポリシーを適用し、通常の署名を拒否します。/tw は署名にタイムスタンプがない場合に警告を出します。すべてのリリースパイプラインに入れておくべきオプションです。
  • /dlib と /dmdf で、ダイジェストにリモートで署名するライブラリを組み込めます。Artifact Signing はそのようなライブラリを提供しており、オープンソースの Jsign(Apache-2.0、バージョン 7.6)は、AWS KMS、Google Cloud KMS、Azure Key Vault、PKCS #11 トークン、YubiKey を使って、任意の OS から同じことを行えます。

検証する側では、PowerShell の Get-AuthenticodeSignature が状態と署名者を返し、Windows エクスプローラーの「デジタル署名」タブにタイムスタンプが表示されます。

ビルドパイプラインでの署名

鍵がハードウェアに結び付けられたことで、ビルドサーバーが鍵ファイルを持つことはなくなりました。次の 3 つの構成が使えます。

  1. クラウドの鍵サービス:AWS KMS、Google Cloud HSM、Azure Key Vault Premium で鍵を生成し、そのサービスの鍵のアテステーションまたは監査報告書を受け入れる CA から証明書を取得して、Jsign やベンダーのツールでダイジェストにリモートで署名します。監査ログの要件は CSBR の一部です。どのログが既定で無効になっているかは、クラウド KMS による署名で説明しています。
  2. マネージド署名サービス(Artifact Signing や CA 独自のサービスなど):サービスが鍵と証明書を保持し、利用者はダイジェストを送信します。
  3. 専用の署名ホストに置いたトークン:ホストにはリリースジョブからのみアクセスできるようにします。シンプルですが、単一のマシンがクリティカルパス上に置かれることになります。

いずれの場合も、署名はリリースアーティファクトだけを対象に、保護されたブランチから、タイムスタンプ付きで行ってください。そして、ユーザーや EDR ツールが出荷されたものを確認できるよう、ファイルのハッシュと署名者の ID をリリースノートに記録してください。

よくある間違い

  • タイムスタンプがない:証明書の有効期限が切れるまではすべて正常に動作しますが、その後はインストール済みのすべてのコピーで署名が無効と表示されます。有効期間 72 時間や 460 日の証明書では、その日はすぐにやって来ます。
  • SHA-1 がどこかに残っている:以前は /fd を省略すると SHA-1 が既定になっていました。現在の SignTool はこれを拒否しますが、フォーラムからコピーした古いスクリプトには、今も /fd sha1 や /t が含まれています。
  • 検証ポリシーの誤り:/pa を付けない signtool verify は、有効な商用の署名を信頼されていないと報告します。
  • codeSigning EKU のない証明書、または流用した TLS 証明書:検証者はこれを拒否します。OpenSSL が unsuitable certificate purpose と報告するのと同じです。
  • 中間証明書の欠落:署名には完全なチェーンを含めてください(-certs chain.pem、または署名ホストに CA の中間証明書をインストールしておく)。そうしないと、検証の成否はユーザーのマシンにたまたま何があるかに左右されます。
  • EV で SmartScreen の警告が出なくなると期待する:今ではレピュテーションは時間をかけて蓄積されます。レピュテーションが引き継がれるよう、すべてのリリースに同じ ID で署名してください。
  • 署名済みの自分のファイルの末尾からデータを読み取る(インストーラー、自己解凍ファイル):その領域は署名の対象外です。

出典