「オンライン資格確認の端末更新で、クライアント証明書を新しいPCに入れ直したら接続できなくなった」「開発機では銀行APIにつながるのに、Windowsサービスにしたら『証明書が見つかりません』と言われる」「そもそも certmgr.msc で見えている証明書と certlm.msc で見えている証明書、どっちが本物なのか分からない」──クライアント証明書つきのWeb API連携を受託で作っていると、この種の相談が定期的にやってきます。
医療機関のオンライン資格確認、電子申請、銀行系API、取引先とのEDI。かつては大企業のインフラ担当だけが触っていたクライアント証明書を、いまは中小企業の情シス担当者や業務アプリ開発者が扱う時代です。そして証明書まわりの事故は、実はほんの数パターンに集約されます。入れる場所を間違える、秘密キーの権限を忘れる、期限を忘れる──この3つです。
この記事では、クライアント証明書を使う業務アプリの開発者と、証明書の入れ替え作業を任される情シス担当者を対象に、「ユーザーストアとコンピューターストアのどちらに入れるか」という判断を軸として、Windowsの証明書ストアの構造から、秘密キーの権限付与、PowerShellでの期限棚卸し、.NETからの利用コードまでを一気に整理します。内容は2026年8月時点のMicrosoft Learnの一次情報にもとづいています。
1. まず結論
- Windowsの証明書ストアは「ユーザー(CurrentUser)」と「コンピューター(LocalMachine)」の2系統です。ユーザーストアはアカウントごとに別物(レジストリのHKEY_CURRENT_USER配下)、コンピューターストアはPC全体で共通(HKEY_LOCAL_MACHINE配下)です。12
- 管理ツールも2つあります。certmgr.msc が現在のユーザー、certlm.msc がローカルコンピューターのストアを開きます。PowerShellからは
Cert:\CurrentUserとCert:\LocalMachineです。34 - どちらに入れるかは「その証明書を使うプログラムが誰として動くか」で決めます。対話ユーザーのアプリならユーザーストア、Windowsサービス・IIS・タスクスケジューラの無人実行ならコンピューターストアが原則です(3章の判断表)。
- 「開発中は動いたのにサービス化したら見つからない」の原因はほぼ一つです。開発者が自分のユーザーストアに入れた証明書は、別アカウントで動くサービスの CurrentUser からは見えません(3章)。
- 証明書と秘密キーは別物です。コンピューターストアに入れただけでは、サービスアカウントは秘密キーを読めないことが普通です。certlm.msc の「秘密キーの管理」で実行アカウントに読み取り権限を付与します。5
- pfxのインポート時、秘密キーは既定でエクスポート不可になります。
Import-PfxCertificateは-Exportableを指定しない限り秘密キーを再エクスポートできない形で取り込みます。これは事故ではなく、望ましい既定値です。6 - 期限切れは棚卸しの自動化で防ぎます。
Get-ChildItem Cert:\LocalMachine\My -ExpiringInDays 60のように、指定日数以内に切れる証明書を機械的に抽出できます。4 - 拇印(サムプリント)をコードや構成にハードコードすると、証明書更新のたびに死にます。新しい証明書は拇印が必ず変わるためです。構成外出し+新旧並行期間が設計の基本です(5章・7章)。
この記事の知識マップ
Windowsの証明書ストアはユーザー(CurrentUser)とコンピューター(LocalMachine)の2系統に分かれ、クライアント証明書をどちらに入れるかは「そのプログラムが誰として動くか」で決まります。無人実行のWindowsサービスはコンピューターストアと秘密キーのアクセス権付与がワンセットで、証明書の期限切れとハードコードされた拇印が、証明書起因の接続障害の定番原因です。
flowchart LR
accTitle: Windows証明書ストア実務ガイドの知識マップ
accDescr: 証明書ストア(ユーザー/コンピューター)、クライアント証明書、秘密キー、証明書チェーン、ルートCA・中間CA、期限切れ・拇印と接続障害、入れ替え手順・台帳・自己署名証明書のリスクの関係を示す図
certificate_store["証明書ストア"]
client_certificate["クライアント証明書"]
windows_service["Windowsサービス"]
localmachine_store["コンピューターの証明書ストア"]
private_key["秘密キー"]
private_key_acl["秘密キーのアクセス権"]
certificate_chain["証明書チェーン"]
intermediate_ca["中間CA証明書"]
root_ca["ルートCA証明書"]
certificate_expiry["証明書の期限切れ"]
certificate_failure["証明書起因の接続障害"]
thumbprint_hardcode["拇印のハードコード"]
group_policy["グループポリシー"]
intune["Microsoft Intune"]
currentuser_store["ユーザーの証明書ストア"]
certmgr_msc["certmgr.msc"]
certlm_msc["certlm.msc"]
cert_drive["Cert:ドライブ"]
pfx["PFXファイル"]
certutil["certutil"]
code_signing_cert["コード署名証明書"]
trusted_publisher_store["信頼された発行元ストア"]
desktop_app["対話ユーザーのデスクトップアプリ"]
cert_renewal["証明書の入れ替え"]
cert_ledger["証明書台帳"]
self_signed_cert["アドホックな自己署名証明書"]
trust_anchor_risk["信頼の起点の悪用リスク"]
cert_selection["証明書の選択(検索)"]
personal_store["「個人」ストア(My)"]
store_mismatch["ストアの取り違え"]
iis_apppool["IISアプリケーションプール"]
task_scheduler["タスクスケジューラの無人実行"]
exportable_import["エクスポート可能でのインポート"]
key_exfiltration_risk["秘密キーの持ち出しリスク"]
everyone_full_control["Everyoneへのフルコントロール付与"]
partner_registration["相手先への証明書事前登録"]
cert_switchover["新証明書への切り替え"]
x509store["X509Storeクラス"]
validonly_search["validOnly指定の証明書検索"]
key_usage["キー使用法(Key Usage)属性"]
config_externalization["拇印の構成外出し"]
cert_choice_logging["選択証明書のログ出力"]
windows_service -->|"利用する"| localmachine_store
client_certificate -->|"前提とする"| private_key
windows_service -->|"前提とする"| private_key_acl
certificate_chain -.->|"前提とする"| intermediate_ca
certificate_chain -->|"前提とする"| root_ca
certificate_expiry -.->|"原因になり得る"| certificate_failure
thumbprint_hardcode -.->|"原因になり得る"| certificate_failure
certificate_failure -->|"で確認できる"| certificate_chain
root_ca -->|"で構成できる"| group_policy
root_ca -->|"で構成できる"| intune
currentuser_store -->|"で確認できる"| certmgr_msc
localmachine_store -->|"で確認できる"| certlm_msc
certificate_store -->|"で確認できる"| cert_drive
certificate_expiry -->|"で確認できる"| cert_drive
private_key -.->|"に保存される"| pfx
certificate_chain -->|"で確認できる"| certutil
code_signing_cert -.->|"に保存される"| trusted_publisher_store
desktop_app -->|"利用する"| currentuser_store
root_ca -.->|"に保存される"| localmachine_store
private_key_acl -->|"で構成できる"| certlm_msc
cert_renewal -.->|"防止する"| certificate_failure
cert_ledger -.->|"防止する"| certificate_expiry
self_signed_cert -.->|"原因になり得る"| trust_anchor_risk
thumbprint_hardcode -->|"用いるのは非推奨"| cert_selection
self_signed_cert -->|"用いるのは非推奨"| root_ca
cert_ledger -->|"推奨される対応"| certificate_expiry
currentuser_store -->|"内容を継承する"| localmachine_store
client_certificate -->|"に保存される"| personal_store
store_mismatch -->|"原因になり得る"| certificate_failure
iis_apppool -->|"利用する"| localmachine_store
iis_apppool -->|"前提とする"| private_key_acl
task_scheduler -.->|"利用する"| localmachine_store
exportable_import -->|"原因になり得る"| key_exfiltration_risk
everyone_full_control -->|"用いるのは非推奨"| private_key_acl
cert_renewal -->|"前提とする"| private_key_acl
partner_registration -.->|"より先に行うべき"| cert_switchover
x509store -->|"利用する"| certificate_store
validonly_search -.->|"軽減する"| certificate_failure
client_certificate -.->|"前提とする"| key_usage
config_externalization -->|"推奨される対応"| cert_selection
cert_choice_logging -->|"推奨される対応"| certificate_failure
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全41件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. 証明書ストアの全体像 ── 2つの場所と論理ストア
2.1. ユーザーとコンピューターの2系統
Windowsの証明書ストアは、大きく2つの「場所」に分かれます。1
- コンピューター(ローカルコンピューター、LocalMachine)の証明書ストア: そのPCに1つで、PC上のすべてのユーザーとサービスに共通です。実体はレジストリの
HKEY_LOCAL_MACHINE\Software\Microsoft\SystemCertificates配下にあります。2 - ユーザー(現在のユーザー、CurrentUser)の証明書ストア: ユーザーアカウントごとに別物です。実体は
HKEY_CURRENT_USER\Software\Microsoft\SystemCertificates配下、つまりユーザープロファイルの一部です。2
このほかにサービスアカウント単位のストアもあり3、実体はサービス名ごとのレジストリキーです。2 実務でまず押さえるべきは最初の2つです。
重要な仕様がひとつあります。ユーザーストアの各論理ストアは、「個人」を除いて、コンピューターストアの同名ストアの内容を継承して見せます。1 たとえばコンピューターストアの「信頼されたルート証明機関」に社内CAの証明書を入れれば、全ユーザーの「信頼されたルート証明機関」にもその証明書が現れます。逆に言えば、「個人」ストアだけは継承されないので、クライアント証明書(=個人ストアに入れるもの)は「誰から見える必要があるか」を自分で決めなければなりません。この非対称性が、この記事全体の主役です。
flowchart TB
subgraph LM["コンピューター(LocalMachine)<br/>PCに1つ・全ユーザーとサービスに共通"]
LMMY["個人(My)"]
LMROOT["信頼されたルート証明機関(Root)"]
LMCA["中間証明機関(CA)"]
LMTP["信頼された発行元(TrustedPublisher)"]
end
subgraph CU["ユーザー(CurrentUser)<br/>アカウントごとに別物"]
CUMY["個人(My)<br/>※継承されない=自分で入れる場所を決める"]
CUROOT["信頼されたルート証明機関(Root)"]
CUCA["中間証明機関(CA)"]
CUTP["信頼された発行元(TrustedPublisher)"]
end
LMROOT -.->|"内容を継承して見える"| CUROOT
LMCA -.->|"継承"| CUCA
LMTP -.->|"継承"| CUTP
2.2. 主要な論理ストア
各場所の中は、役割別の論理ストアに分かれています。certmgr.msc / certlm.msc で見えるフォルダーがそれで、PowerShellやコマンドから見るときは英語の内部名を使います。24
| 表示名 | 内部名 | 何を入れる場所か |
|---|---|---|
| 個人 | My | 自分(このPC・このユーザー)が使う証明書。クライアント証明書・サーバー証明書はここ。秘密キーと関連付くのもここ |
| 信頼されたルート証明機関 | Root | 信頼の起点になるルートCA証明書。ここに入れたCAの配下は「信頼される」 |
| 中間証明機関 | CA | ルートと末端の間をつなぐ中間CA証明書。チェーン構築の材料 |
| 信頼された発行元 | TrustedPublisher | 署名済みソフトウェアの発行元として信頼する証明書(8章) |
2.3. 3つの覗き窓 ── certmgr.msc / certlm.msc / Cert: ドライブ
- certmgr.msc: 現在のユーザーのストアを開く管理コンソール。
- certlm.msc: ローカルコンピューターのストアを開く管理コンソール。
- PowerShellの
Cert:ドライブ:Cert:\CurrentUser\...とCert:\LocalMachine\...の階層でストアをファイルシステムのように操作できます。証明書は拇印で識別されます。
なお、mmc.exe に証明書スナップインを手動で追加する場合は「ユーザーアカウント」「コンピューターアカウント」「サービスアカウント」の3種類から対象を選びます。管理者でないユーザーが管理できるのは自分のユーザーアカウントのストアだけです。3
トラブル調査の第一歩は、「アプリがどちらのストアを見ているか」と「自分がどちらのストアを見ているか」を一致させることです。certmgr.msc を眺めながらサービスの障害を調査しても、見ている場所が違うので永遠に答えは出ません。
3. どちらに入れるか ── プログラムの実行形態で決める判断表
判断基準はひとつです。その証明書を使うプログラムは、誰のアカウントで動くのか。
| 実行形態 | 実行アカウント | 入れるストア | 備考 |
|---|---|---|---|
| 対話ユーザーが起動するデスクトップアプリ | ログオンユーザー本人 | ユーザー(Cert:\CurrentUser\My) | 使う人のアカウントごとに導入が必要。共有PCで複数人が使うならコンピューターストアも検討 |
| Windowsサービス | LocalSystem / NETWORK SERVICE / 専用サービスアカウント | コンピューター(Cert:\LocalMachine\My) | LocalSystem以外(NETWORK SERVICE・専用アカウント等)は秘密キーの読み取り権限付与が必須(4章)。LocalSystemは既定のSYSTEM権限で読める |
| IIS上のWebアプリ | アプリケーションプールのID | コンピューター | 同上 |
| タスクスケジューラの無人実行(ユーザーのログオン有無に関わらず実行) | タスクに指定したアカウント | コンピューター推奨 | 実行アカウントのユーザーストアでも動かせるが、プロファイルやストアの見え方の検証が増えるだけで利点が乏しい |
| ブラウザーでの電子申請・Web認証 | ログオンユーザー本人 | ユーザー | 配布した本人以外に使わせない、という意味でも自然 |
迷ったら、無人で動くものはコンピューターストア、人が操作するものはユーザーストアです。
3.1. 定番事故の解剖 ── 「開発中は動いたのにサービス化したら見つからない」
この事故は、次の手順で正確に再現できます。
- 開発者が自分のPCで pfx をダブルクリックしてインポートする。ウィザードの既定は「現在のユーザー」なので、証明書は開発者アカウントのユーザーストアに入る。
- 開発中のアプリはVisual Studioから、つまり開発者アカウントで動くので、
StoreLocation.CurrentUserを開けば証明書が見つかる。動く。 - 本番サーバーでWindowsサービスとして登録する。サービスは NETWORK SERVICE や専用アカウントで動く。
- サービスのコードが開く
CurrentUserは、サービス実行アカウントのユーザーストア。そこは空。「証明書が見つかりません」。
flowchart TB
subgraph DEV["開発機"]
D1["pfxをダブルクリックでインポート<br/>ウィザードの既定は「現在のユーザー」"] --> D2["開発者アカウントの<br/>ユーザーストアに入る"]
D2 --> D3["Visual Studioから実行<br/>=開発者アカウントで動く"]
D3 --> D4["CurrentUser を開けば見つかる<br/>→ 動く"]
end
subgraph PROD["本番サーバー"]
P1["Windowsサービスとして登録<br/>実行アカウントは NETWORK SERVICE 等"] --> P2["コードが開く CurrentUser は<br/>サービスアカウントのユーザーストア"]
P2 --> P3["そこは空<br/>→「証明書が見つかりません」"]
end
D4 -.->|"同じプログラムを配置"| P1
ポイントは、ユーザーストアが「アカウントの数だけ存在する」ことです。管理者が certmgr.msc を開いて「ちゃんと入っていますが?」と確認しても、それは管理者自身のストアであって、サービスアカウントのストアではありません。対処は場当たりのコピーではなく、コンピューターストアに入れ直し、コードも StoreLocation.LocalMachine に揃えることです。そして次章の権限付与までがワンセットです。
4. 秘密キーとアクセス権 ── 第二の定番事故
4.1. 証明書と秘密キーは別物
証明書ストアの一覧に見えているのは証明書(公開情報)であって、秘密キーそのものではありません。クライアント認証で実際に必要なのは秘密キーを使った署名処理なので、「一覧に見える」ことと「使える」ことは別問題です。ここを混同すると、「証明書はあるのにTLSハンドシェイクで失敗する」「Access Denied系の内部エラーが出る」という、見た目に分かりにくい障害になります。
4.2. pfxインポートの実務 ── エクスポート可否は意思決定
証明書と秘密キーのペアはpfx(PKCS #12)ファイルで受け渡しされ、Import-PfxCertificate でストアに取り込めます。6
$pwd = Get-Credential -UserName '(パスワードを下に入力)' -Message 'PFXのパスワード'
Import-PfxCertificate -FilePath C:\certs\client.pfx `
-CertStoreLocation Cert:\LocalMachine\My -Password $pwd.Password
ここで重要なのが、-Exportable を付けない限り、取り込まれた秘密キーは再エクスポートできないという既定の挙動です。6 「後で移行できるように」と何でもエクスポート可能で入れるのは、秘密キーの持ち出し経路を1本増やす行為です。原本のpfxを安全に保管する運用にして、ストア上の秘密キーはエクスポート不可が基本──これが当社の推奨です。なお、原本pfxとそのパスワードの保管こそ平文で放置されがちです。考え方は「Windowsアプリの機密情報保存 ── DPAPIで平文設定を避ける」と「PowerShellでの資格情報の安全な扱い」で整理しています。
4.3. サービスアカウントへの秘密キー権限付与
コンピューターストアに入れた証明書の秘密キーは、既定では管理者とSYSTEM以外から読めない構成が普通です。このため、LocalSystemで動くサービスは既定のままで秘密キーを読めますが、NETWORK SERVICE・専用サービスアカウント・IISアプリプールIDなどそれ以外のアカウントで動かす場合は、実行アカウントへ明示的に読み取り権限を付与します。手順は証明書スナップインのUIから行えます。5
- certlm.msc(またはコンピューターアカウント対象の証明書スナップイン)を開く。
- 「個人」→「証明書」で対象の証明書を右クリックし、「すべてのタスク」から「秘密キーの管理」を開く。
- 「セキュリティ」タブで実行アカウント(NETWORK SERVICE、専用サービスアカウント、IISのアプリプールIDなど)を追加し、「読み取り」を許可する。5
フルコントロールは不要です。署名に使うだけなら読み取りで足ります。逆に、動かないからと Everyone にフルコントロールを与えるのは、秘密キーを平文パスワード並みの扱いに落とす行為なので絶対に避けてください。コンピューターストアへの配置と秘密キーの権限付与は、常にワンセット──これを手順書に書いておくだけで、この系統の事故は消えます。
5. 期限切れ事故を防ぐ ── 棚卸し・入れ替え・台帳
5.1. PowerShellで棚卸しする
証明書の有効期限は NotAfter プロパティに入っています。Cert: ドライブへの Get-ChildItem で機械的に棚卸しできます。4
# コンピューターストアの「個人」を有効期限順に一覧
Get-ChildItem Cert:\LocalMachine\My |
Sort-Object NotAfter |
Format-Table Thumbprint, Subject, NotAfter
# 60日以内に期限が切れるものだけ抽出(0を指定すると期限切れ済み)
Get-ChildItem -Path Cert:\LocalMachine\My -ExpiringInDays 60
-ExpiringInDays は「指定日数以内に期限が切れる証明書」を返すパラメーターで、0なら期限切れ済みの証明書が出ます。4 これを月次のスケジュールタスクにして全サーバーを回し、結果をメールなり台帳なりに集約する──それだけで「期限切れで月曜の朝から資格確認が通らない」型の事故はほぼ防げます。
5.2. 入れ替えの手順 ── 新旧並行期間と拇印の罠
証明書の更新は「削除して入れる」ではなく「追加してから切り替え、確認してから消す」です。
- 新しい証明書(pfx)を同じストアにインポートする。拇印が違うので、新旧は同じストアに共存できます。
- 新しい証明書の秘密キー権限を付与する(4章)。更新時に忘れやすいのはここです。権限は証明書の秘密キーごとに付くので、証明書を入れ替えたら付与もやり直しです。
- 相手先システムへの届け出(証明書の事前登録が必要なAPIの場合)を、旧証明書で運用を続けたまま先に済ませ、新旧どちらでも受け付けられる並行期間を確保する。切り替えを先にすると、相手側が新証明書を拒否して本番の通信が止まります。
- アプリの構成を新しい証明書に切り替え、動作確認する。
- 十分な期間の後、古い証明書を削除する。
flowchart LR
I["1. 新pfxを同じストアへ<br/>インポート(新旧共存)"] --> P["2. 新証明書の<br/>秘密キー権限を付与"]
P --> R["3. 相手先へ事前登録<br/>(旧証明書のまま運用継続)"]
R --> SW["4. 構成の拇印を書き換えて<br/>切り替え・動作確認"]
SW --> DEL["5. 並行期間の後に<br/>旧証明書を削除"]
このとき最大の罠が、構成ファイルやコードに書かれた拇印です。拇印は証明書ごとに一意なので、更新すれば必ず変わります。どこか1か所でも古い拇印を参照したままだと、「証明書は更新したのに接続できない」が起きます。拇印がどこに書かれているか(アプリ構成、IISバインド、スクリプト、相手先への届け出)を台帳で管理するのが確実です。
5.3. 証明書台帳のすすめ
台帳といっても、まずはExcel1枚で十分です。最低限、用途/発行元/サブジェクト/拇印/入っている場所(サーバー名+ストア)/秘密キー権限を持つアカウント/有効期限/更新手順へのリンク/担当者の列を作り、5.1の棚卸し結果と突き合わせます。証明書事故の実態は技術の問題ではなく「誰も一覧を持っていない」問題なので、台帳が一番効きます。
6. 検証と失敗の読み解き ── チェーンとルート配布
6.1. チェーン検証の基本と certutil
「この証明書は信頼されていません」系のエラーは、末端の証明書からルートCAまでのチェーン(証明のパス)がどこかで切れている状態です。切り分けには certutil が便利です。7
flowchart TB
LEAF["末端の証明書<br/>(クライアント証明書・サーバー証明書)"] --> INT["中間CA証明書<br/>置き場所: 中間証明機関(CA)ストア"]
INT --> ROOT["ルートCA証明書<br/>置き場所: 信頼されたルート証明機関(Root)"]
INT -.->|"入手できない<br/>(提示・AIA・ストアのいずれも)"| E1["チェーンが構築できない<br/>(定番原因1)"]
ROOT -.->|"配布されていない"| E2["「信頼されていません」エラー<br/>(定番原因2)"]
LEAF -.->|"期限切れ"| E3["有効期間エラー<br/>(定番原因3)"]
:: 証明書ファイルのチェーンを構築・検証する(失効確認のURL取得つき)
certutil -urlfetch -verify client.cer
:: 対象アプリがユーザーストアを使うなら -user を付けて同じ文脈で検証する
certutil -user -urlfetch -verify client.cer
:: ストアの中身をダンプする(-user を付けるとユーザーストア)
certutil -store My
certutil -user -store My
certutil -verify は証明書・CRL・チェーンの検証を行い、CACertFile を指定しなければ完全なチェーンを構築して検証します。7 出力は長いですが、どの階層で信頼が切れたのか、失効情報が取れているのかが読み取れます。典型的な原因は、(1)中間CA証明書が入手できない(TLSの相手が送ってこず、証明書のAIA情報からも取得できず、「中間証明機関」ストアにも入っていない)、(2)社内CAのルートが「信頼されたルート証明機関」に配布されていない、(3)証明書自体の期限切れ、の3つです。中間CAは相手からの提示やAIA経由の自動取得でも解決されるため、ストア配置は「確実にする手段の1つ」と捉えてください。
6.2. 社内CA・自己署名のルート配布はGPO/Intuneで
社内CAや検証用の自己署名証明書を使う場合、そのルート証明書を各PCに配る必要があります。1台ずつ手作業で入れるのではなく、配布の仕組みに乗せます。
- Active Directory環境(GPO): グループポリシーの
コンピューターの構成\ポリシー\Windowsの設定\セキュリティの設定\公開キーのポリシーにある「信頼されたルート証明機関」へ証明書をインポートすると、対象のPCに配布されます。8 - Intune管理の環境: 「信頼された証明書」プロファイルでルート/中間CA証明書を配布します。Windowsでは配布先ストア(コンピューターのルート/中間、ユーザーの中間)を選べます。9
2.1で述べたとおり、コンピューターストアのルートに入れれば全ユーザーから信頼されます。1 だからこそ逆のリスクも直視してください。自己署名証明書を「信頼されたルート証明機関」に入れる運用は、そのPCに新しい信頼の起点を植える行為です。その秘密キーが漏れれば、任意のサイトやソフトウェアになりすます証明書を発行される足場になります。恒久運用にするなら、秘密キーを適切に保護した社内CAを立てるか、パブリックCAの証明書に寄せるのが筋で、自己署名ルートは「検証環境限定・期限を切って」が原則です。
7. 開発者の視点 ── .NETからストアを正しく使う
7.1. X509Store で拇印検索する
.NETからは X509Store でストアを開き、Find で証明書を取得します。1011
using System.Security.Cryptography.X509Certificates;
static X509Certificate2 GetClientCertificate(string thumbprint)
{
using var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
var found = store.Certificates.Find(
X509FindType.FindByThumbprint, thumbprint, validOnly: true);
if (found.Count == 0)
throw new InvalidOperationException(
$"証明書が見つかりません: 拇印={thumbprint}, " +
$"場所={store.Location}\\{store.Name}");
var cert = found[0];
if (!cert.HasPrivateKey)
throw new InvalidOperationException(
$"証明書はありますが秘密キーが関連付いていません(.cerからの" +
$"インポート等): 拇印={thumbprint}, 場所={store.Location}\\{store.Name}");
return cert;
}
3章の判断がここに直結します。サービスで動くコードなら StoreLocation.LocalMachine、対話アプリなら StoreLocation.CurrentUser です。もうひとつ、Find の第3引数 validOnly に注意してください。true は検証を通った有効な証明書だけを返します。11 期限切れの証明書を掴まない保険になる一方、チェーンが信頼されていないテスト用自己署名証明書も「見つからない」側に落ちるので、「入っているのに見つからない」ときはここも疑ってください。また、見つからないときのエラーメッセージには、上の例のようにどのストアを探したかを必ず入れます。3章の事故の調査時間が桁で変わります。
7.2. HttpClient にクライアント証明書を載せる
取得した証明書は、HttpClientHandler.ClientCertificates に追加してサーバーへ提示します。このコレクションが、証明書ベースのクライアント認証でサーバーに提示される証明書の集合です。12
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(GetClientCertificate(thumbprint));
var client = new HttpClient(handler);
// 以降は通常のHttpClientとして使う
なお.NET Core系では、証明書にキー使用法(Key Usage)属性がある場合、「Digital Signature」を含んでいないとリクエスト送信に使われない点がドキュメントに明記されています。12 クライアント証明書の発行を依頼する立場になったら、用途(クライアント認証)を正しく伝えてください。また、HttpClient は生成パターンを誤るとソケット枯渇やDNS追従の問題を起こします。ハンドラーごと長寿命にする設計は「HttpClientをusingで囲んではいけない」で扱ったとおりです。
7.3. ハードコードした拇印が入れ替えで死ぬ問題
拇印検索は確実ですが、拇印をコードに埋め込むと証明書更新のたびにビルドとリリースが必要になります。設計での対処は次の3段階です。
- 最低限: 拇印を構成ファイル(appsettings等)に外出しし、リリースなしで差し替えられるようにする。構成箇所は5.3の台帳に載せる。
- 一歩進める: サブジェクト名や発行元で検索し、
validOnly: trueと組み合わせて「その名前で現在有効なもののうちNotAfterが最も先のもの」を選ぶ。新旧並行期間に新しい証明書へ自動で乗り替わります。ただし同名の意図しない証明書を掴むリスクがあるので、発行元の確認とログ出力をセットにします。また、この自動乗り替えが成立するのは相手先が証明書の事前登録を必要としない場合だけです。事前登録が必要なAPI(5.2)では、インポートしただけの未登録証明書へ勝手に切り替わって通信が止まり得るため、登録完了を確認してから切り替える構成外出し方式にとどめてください。 - 運用で締める: どの方式でも、起動時に「どの証明書(拇印・期限)を選んだか」をログに残す。障害調査でも台帳突き合わせでも、この1行が効きます。
8. コード署名証明書との関係 ── 「信頼された発行元」ストア
ここまで扱ったのは通信(TLS)のための証明書ですが、証明書ストアにはもうひとつの世界──コード署名──が同居しています。2.2の表に出てきた「信頼された発行元(TrustedPublisher)」ストアがその接点で、署名済みソフトウェアの発行元証明書を信頼済みとして登録する場所です。ユーザー・コンピューター両方の場所に存在し10、社内配布アプリの発行元をGPOで各PCの TrustedPublisher に配る、といった運用に使われます。
アプリを「配る側」として、コード署名やSmartScreen警告(「WindowsによってPCが保護されました」)への対処が必要な方は、別記事「Windowsで「Windows によって PC が保護されました」が出る理由」にまとめています。本記事の知識(ストアの2系統、ルート配布)はそのまま前提知識として使えます。
9. まとめ
- 証明書ストアはユーザー(CurrentUser)とコンピューター(LocalMachine)の2系統。certmgr.msc / certlm.msc /
Cert:ドライブは同じものを見る3つの窓です。調査の第一歩は「どのストアの話をしているか」を揃えること。 - 入れる場所は「プログラムが誰として動くか」で決めます。無人実行(サービス・IIS・タスク)はコンピューターストア、対話アプリはユーザーストアが原則です。
- 「開発中は動いたのに本番で見つからない」は、開発者のユーザーストアとサービスアカウントのユーザーストアが別物であることが原因。コンピューターストア+
StoreLocation.LocalMachineに揃えて解決します。 - コンピューターストアへの配置と「秘密キーの管理」での読み取り権限付与はワンセット。更新時の付与し直しも忘れずに。
- pfxインポートは既定でエクスポート不可。
-Exportableは本当に必要なときだけ。原本pfxとパスワードの保管も設計に含めます。 - 期限切れは
Get-ChildItem Cert: ... -ExpiringInDaysの定期棚卸しと証明書台帳で防ぎます。入れ替えは「追加→切り替え→確認→削除」の順で、構成中の拇印の更新漏れに注意。 - チェーンの切り分けは
certutil -urlfetch -verify。社内CAのルートはGPO/Intuneで配布し、自己署名をルートに入れる運用は検証環境限定・期限つきで。 - コードでは拇印を構成に外出しし、選んだ証明書をログに残す。それだけで証明書起因の障害対応は見違えます。
関連記事
- Windowsで「Windows によって PC が保護されました」が出る理由
- Windowsアプリの機密情報保存 ── DPAPIで平文設定を避ける
- PowerShellでの資格情報の安全な扱い ── 平文パスワードをスクリプトから追放する
- HttpClientをusingで囲んではいけない ── C#業務アプリのHTTP通信実務
- マイナ保険証をかざすと何が起きるのか ── オンライン資格確認とレセコンの連携をORCAのソースコードから読む
関連する相談領域
合同会社小村ソフトでは、クライアント証明書つきWeb API連携(銀行系API、オンライン資格確認等)を組み込んだ業務アプリの開発、「証明書が見つからない」「更新したら接続できない」型の障害調査、証明書の入れ替え手順の整備を扱っています。ストアのどこを見ればよいか分からない、という段階のご相談で構いません。
参考リンク
-
Microsoft Learn, Local Machine and Current User Certificate Stores. コンピューターの証明書ストアがPCに対してローカルかつ全ユーザーに共通でHKEY_LOCAL_MACHINE配下にあること、ユーザーの証明書ストアがユーザーアカウントごとでHKEY_CURRENT_USER配下にあること、ユーザーストアが「個人」ストアを除いてコンピューターストアの内容を継承すること(コンピューターの「信頼されたルート証明機関」に追加した証明書が各ユーザーの同ストアにも現れること)について。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, System Store Locations. CERT_SYSTEM_STORE_CURRENT_USER / CERT_SYSTEM_STORE_LOCAL_MACHINE のレジストリ位置(それぞれHKEY_CURRENT_USER / HKEY_LOCAL_MACHINE の Software\Microsoft\SystemCertificates)、定義済みの論理ストアがMY・Root・Trust・CAであること、サービス用ストアがサービス名ごとのレジストリキー(Software\Microsoft\Cryptography\Services\ServiceName\SystemCertificates)にあること、グループポリシー配布用のストアが別途存在することについて。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, How to: View certificates with the MMC snap-in. certlm.msc がローカルデバイス(ローカルコンピューター)の証明書を、certmgr.msc が現在のユーザーの証明書を管理するツールであること、証明書スナップインの対象として「コンピューターアカウント」「ユーザーアカウント」「サービスアカウント」の3種類があること、管理者でないユーザーは自分のユーザーアカウントの証明書のみ管理できることについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_Certificate_Provider. PowerShellの Cert: ドライブが CurrentUser と LocalMachine の2つのストア場所を持つ階層名前空間であること、Get-ChildItem でストアと証明書を列挙できること、-ExpiringInDays パラメーターが指定日数以内に期限切れになる証明書(0で期限切れ済み)を返すこと、-CodeSigningCert などの動的パラメーター、NotAfter プロパティに有効期限が格納されること、証明書が拇印で識別されることについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, How to Modify Private Key Permissions to Support Management Server or Streaming Server. ローカルコンピューターの証明書ストアを対象にした証明書スナップインで「秘密キーの管理」(Manage Private Keys)を開き、「セキュリティ」タブでサービスの実行アカウント(例: Network Service)に「読み取り」アクセス許可を追加する手順について。 ↩ ↩2 ↩3
-
Microsoft Learn, Import-PfxCertificate. Import-PfxCertificate がPFXファイルから証明書と秘密キーを指定ストアへ取り込むこと、-Exportable スイッチを指定しない場合は取り込まれた秘密キーをエクスポートできないこと、-CertStoreLocation・-Password・-FilePath の各パラメーターの構文と使用例について。 ↩ ↩2 ↩3
-
Microsoft Learn, certutil. certutil -verify が証明書・CRL・証明書チェーンを検証し、CA証明書ファイルを指定しない場合は完全なチェーンを構築して検証すること、-urlfetch オプションが利用できること、certutil -store が証明書ストアをダンプし、-user オプションでコンピューターストアの代わりにユーザーストアへアクセスできることについて。 ↩ ↩2
-
Microsoft Learn, Distribute Certificates to Client Computers by Using Group Policy. グループポリシーの「コンピューターの構成\ポリシー\Windowsの設定\セキュリティの設定\公開キーのポリシー」配下の「信頼されたルート証明機関」に証明書をインポートして、ドメイン内のクライアントコンピューターへ配布する手順と、必要な権限(Domain Admins / Enterprise Admins相当)について。 ↩
-
Microsoft Learn, Create trusted certificate profiles in Microsoft Intune. Intuneの「信頼された証明書」プロファイルがルートまたは中間CA証明書を管理対象デバイスへ配布する仕組みであること、SCEP/PKCS証明書プロファイルの前提としてルートCAへの信頼の確立に使われること、Windowsでは配布先ストアとして「コンピューター証明書ストア - ルート」「コンピューター証明書ストア - 中間」「ユーザー証明書ストア - 中間」を選択できることについて。 ↩
-
Microsoft Learn, X509Store Class. X509Store が StoreName と StoreLocation(CurrentUser / LocalMachine)を指定して構築でき、Open メソッドと OpenFlags(ReadOnly、OpenExistingOnly等)でストアを開き、Certificates プロパティで証明書コレクションを取得できること、標準ストア名にMy・Root・CA・TrustedPublisher等が含まれ、TrustedPublisherストアがCurrentUser・LocalMachine双方に存在することについて。 ↩ ↩2
-
Microsoft Learn, X509Certificate2Collection.Find(X509FindType, Object, Boolean) Method. Find メソッドが X509FindType(FindByThumbprint等)と検索値で証明書を検索すること、第3引数 validOnly に true を指定すると検証を通った有効な証明書のみが返されることについて。 ↩ ↩2
-
Microsoft Learn, HttpClientHandler.ClientCertificates Property. ClientCertificates プロパティが証明書ベースのクライアント認証でサーバーに提示される X509CertificateCollection であること、.NET Coreでは証明書にキー使用法属性が存在する場合「Digital Signature」を含む必要があることについて。 ↩ ↩2
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Windowsファイアウォールと業務アプリ ── 受信規則はインストーラーで登録する
「開発機では動くのに客先で通信できない」の定番原因がWindowsファイアウォールです。受信既定ブロックとプロファイル、通知ダイアログに本番を任せてはいけない理由、インストーラーでの受信規則の登録と切り分け手順を解説します。
SMB署名とLDAPチャネルバインディング ── NTLM対策の「残り半分」を実務で締める
NTLMを止めるまでの間、リレー攻撃の被害を抑える防御がSMB署名とLDAP署名・チャネルバインディングです。OSごとの既定値、監査イベントの読み方、強制へ進める手順、業務アプリと機器の直し方までを実務目線で整理します。
NTLM廃止で業務アプリは止まるか ── 監査ログの取り方と、依存を潰す順番
NTLM廃止に向けて、自社のWindows環境と業務アプリがどこでNTLMに依存しているかを洗い出す手順をまとめます。監査ポリシー、NTLM/Operationalログのイベント8001〜8004の追い方、NTLMに落ちる典型パターンと直し方、SMBのNTLMブロックまでを...
PowerShellのセキュリティ強化 ── ログ・AMSI・言語モード・JEA
PowerShellを禁止せずに安全に使うための実務をまとめます。スクリプトブロックログとトランスクリプションの有効化、AMSIと古いバージョンの無効化、言語モードによる制限、JEAによる権限委任までを解説します。
BitLocker実務ガイド ── 回復キーの管理から始めるドライブ暗号化
Windows 11 24H2以降、クリーンインストールではデバイスの暗号化が既定で有効になり「気づいたら暗号化されていた」事故が現実に起きています。回復キーの保存先判断表を軸に、仕組み・組織での運用・事故対応・廃棄まで整理します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- certmgr.msc と certlm.msc は何が違うのですか?
- 対象のストアが違います。certmgr.msc はログオン中のユーザーの証明書ストア(現在のユーザー、CurrentUser)を、certlm.msc はコンピューターの証明書ストア(ローカルコンピューター、LocalMachine)を開きます。コンピューターストアはPC上の全ユーザーとサービスに共通で、管理には管理者権限が必要です。管理者でないユーザーが管理できるのは自分のユーザーストアだけです。どちらも中身は「個人」「信頼されたルート証明機関」などの論理ストアに分かれており、PowerShellからは Cert:\CurrentUser と Cert:\LocalMachine として同じ構造が見えます。
- クライアント証明書はユーザーとコンピューターのどちらのストアに入れるべきですか?
- その証明書を使うプログラムが「誰として」動くかで決めます。対話ユーザーが起動するデスクトップアプリなら、使う本人のユーザーストア(Cert:\CurrentUser\My)が基本です。Windowsサービス・IISアプリケーションプール・タスクスケジューラで無人実行するプログラムなら、コンピューターストア(Cert:\LocalMachine\My)に入れ、実行アカウントに秘密キーの読み取り権限を付与します。ユーザーストアはアカウントごとに別物なので、開発者が自分のユーザーストアに入れた証明書は、別アカウントで動くサービスからは見えません。これが「開発中は動いたのに本番で見つからない」事故の典型原因です。
- Windowsサービスから証明書が見つからない・使えないときは何を確認すればよいですか?
- 確認は2段階です。第一に「どのストアを見ているか」。コードが StoreLocation.CurrentUser を開いていれば、それはサービス実行アカウントのユーザーストアであり、管理者が certmgr.msc で見ている自分のストアとは別物です。証明書をコンピューターストアに移し、コードも StoreLocation.LocalMachine に合わせます。第二に「秘密キーが読めるか」。証明書の一覧に見えることと秘密キーを使えることは別で、コンピューターストアの秘密キーには既定で管理者とSYSTEMしかアクセスできない構成が普通です。certlm.msc で対象の証明書から「秘密キーの管理」を開き、サービスの実行アカウント(NETWORK SERVICE等)に「読み取り」を付与してください。
- 証明書の期限切れをPowerShellで事前に見つけるには?
- Cert: ドライブに対する Get-ChildItem で棚卸しできます。たとえば Get-ChildItem Cert:\LocalMachine\My | Sort-Object NotAfter | Format-Table Thumbprint, Subject, NotAfter で、コンピューターストアの個人ストアを有効期限順に一覧できます。さらに -ExpiringInDays パラメーターを使うと「指定日数以内に期限が切れる証明書」だけを抽出でき、0を指定すれば期限切れ済みの証明書が出ます。これを月次で全サーバーに対して実行し、結果を証明書台帳と突き合わせる運用にしておくと、「期限切れで朝から接続できない」型の事故はほぼ防げます。