.NET TIPS

ClickOnceのデジタル証明書を更新するには?[VS 2005のみ、2.0のみ、C#、VB]

デジタルアドバンテージ 一色 政彦
2007/06/28
2007/11/10、2007/12/20 更新

 ClickOnce機能で配布されるアプリケーション(以降、ClickOnceアプリ)は、「コードサイニング証明書(Authenticode対応Digital ID)」(以下、デジタル証明書)によって署名されなければならない。この署名作業はVisual Studio 2005(以降、VS 2005)が自動的に行ってくれるため、あまり意識していない開発者も少なくないだろう。

 このVS 2005による自動署名のほかにも、makecert.exeを使って手動で発行したデジタル証明書を使う方法や、第三者機関のVeriSignなどが発行したデジタル証明書を利用する方法などがある(詳しくは「連載:ClickOnceの真実 第7回」の「■セキュリティの設定方法1 ― デジタル証明書による署名方法」を参照されたい)。

 しかし、いずれの方法で署名したとしても、そこで使われるデジタル証明書の有効期間は基本的に12カ月である。そのため、1年が過ぎてアプリケーションをバージョン・アップしようとすると、この有効期限に引っかかり、ClickOnce発行する際にエラーとなってしまうのだ。

 次の画面は実際に有効期限が切れたデジタル証明書を使って発行しようとしたときの[エラー一覧]の表示である。

有効期限が切れたデジタル証明書で発行しようとしたときのエラー
「The signer's certificate is not valid for signing.」のメッセージが、署名に使おうとしているデジタル証明書が(有効期限が切れているなどの理由で)不正であることを通知している。

 このエラーを回避するには、デジタル証明書を更新して有効期限を延ばすしかない(VS 2005でデジタル証明書を新たに作り直すと、「デジタル証明書が一致しない」というエラーになり、クライアントPCからClickOnceアプリをアップデートできないので注意すること。なお、読者からの問い合わせによると、VeriSignなどが発行した証明書を更新しても同様のエラーが発生することがある。筆者はVeriSignの証明書で動作を検証したわけでない)。

 だが、VS 2005が自動生成したデジタル証明書(=「<プロジェクト名>_TemporaryKey.pfx」ファイル)の場合は、どうやって有効期限を延期すればよいのだろうか? これについて本稿で説明する。

VS 2005が自動生成したデジタル証明書を更新するには?

 実はVS 2005には証明書を作成する機能はあるが、それを更新する機能は備えていない(次期Visual Studio 2008(コード名:Orcas)では更新機能に対応するらしい)。そのため、VS 2005で自動署名している場合には、ClickOnceアプリをバージョン・アップ/更新できない問題には簡単に対応できない(有効期限は署名時にチェックされるので、有効期限内に署名したClickOnceアプリであれば、期限失効後もそのまま利用できる。ここで問題となるのは新たなバージョンを開発・提供する場合である)。これに対し米Microsoftは、以下のKB(ナレッジ・ベース)を提供している。

 これを読むと、1つの対応策は「すべてのクライアントでいったん既存のClickOnceアプリをアンインストールして、新しいデジタル証明書で署名したClickOnceアプリを再インストールすること」で、もう1つは「RenewCert.exeというコマンドライン・ツールを作り、それを使ってデジタル証明書を更新すること」と記述されている。前者の方法はクライアント数が増えてくると現実的な解決策ではなくなる。そこで本稿では、後者の方法を用いてデジタル証明書を更新する手順を紹介する。

 取りあえず最初にRenewCert.exeを作成する必要がある。このツールは、Visual C ++を使用し、Win32コンソール・アプリケーションとして作成する。

●RenewCert.exeの作成手順

 RenewCert.exeを作成するには、まずVS 2005(Visual C++ 2005 Express EditionでもOK)のIDEを立ち上げ、メニュー・バーから[ファイル]−[新規作成]−[プロジェクト]を選択して[新しいプロジェクト]ダイアログを表示し、そこで「Visual C++」による「Win32 コンソール アプリケーション」のプロジェクトを「RenewCert」という名前で作成する。

 次の画面は実際に[新しいプロジェクト]ダイアログで指定する内容である。

「RenewCert」プロジェクトの新規作成
[新しいプロジェクト]ダイアログで「RenewCert」プロジェクトを作成しているところ。[場所]は任意のディレクトリを入力してよい。
  「Visual C++」を選択する。
  「Win32 コンソール アプリケーション」を選択する。
  [プロジェクト名]に「RenewCert」を入力する。
  [OK]ボタンをクリックする。

 上記画面の手順を実行すると、次のような[Win32 アプリケーション ウィザード]ダイアログが表示されるので[完了]ボタンをクリックしてダイアログを閉じる。

[Win32 アプリケーション ウィザード]ダイアログにおけるプロジェクト設定
何も設定せずに[完了]ボタンをクリックしてウィザードを終了する。

 以上の作業により、「RenewCert」プロジェクトが新規に作成される。

 次に[ソリューション エクスプローラ]で(自動生成されている)「RenewCert.cpp」項目をダブルクリックしてRenewCert.cppファイルを開き、既存のコードを以下のコードに書き換える(単純にコピー&ペーストでOK)。

 なお下記のコードは、先ほどのMicorosoft サポートオンラインで示されているものであり、本稿でオリジナルに作成したものではない(ただし、日本語の説明を追加したり、不要なコード部分はカットしたりするなど若干の改変を行っている)。

#include "stdafx.h"

void ReadPFXFile(LPCWSTR fileName, CRYPT_DATA_BLOB *pPFX)
{
  HANDLE hCertFile = NULL;
  DWORD cbRead = 0;
  DWORD dwFileSize = 0, dwFileSizeHi = 0;

  hCertFile = CreateFile(fileName,
    GENERIC_READ, FILE_SHARE_READ,
    NULL, OPEN_EXISTING, 0, NULL);
  dwFileSize = GetFileSize(hCertFile, &dwFileSizeHi);
  pPFX->pbData =
    (BYTE *)CryptMemAlloc(dwFileSize*sizeof(BYTE));
  pPFX->cbData = dwFileSize;
  ReadFile(hCertFile,
    pPFX->pbData, pPFX->cbData, &cbRead, NULL);
  CloseHandle(hCertFile);
}

void GetPrivateKey(CRYPT_DATA_BLOB pPFX, LPCWSTR szPassword, HCRYPTPROV *hCPContext)
{

  HCERTSTORE hCertStore = NULL;
  PCCERT_CONTEXT hCertContext = NULL;
  DWORD dwKeySpec = AT_SIGNATURE;
  BOOL bFreeCertKey = TRUE;

  hCertStore =
    PFXImportCertStore(&pPFX, szPassword, CRYPT_EXPORTABLE);
  hCertContext = CertEnumCertificatesInStore(hCertStore, NULL);
  CryptAcquireCertificatePrivateKey(hCertContext,
    0, NULL, hCPContext, &dwKeySpec, &bFreeCertKey);
  CertCloseStore(hCertStore, CERT_CLOSE_STORE_FORCE_FLAG);
}

void MakeNewCert(HCRYPTPROV hCPContext, LPCWSTR szCertName, LPCWSTR szPassword, CRYPT_DATA_BLOB *pPFX)
{
  CERT_NAME_BLOB certNameBlob = {0,NULL};
  PCCERT_CONTEXT hCertContext = NULL;
  SYSTEMTIME certExpireDate;
  HCERTSTORE hTempStore = NULL;

  CertStrToName(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
    szCertName, CERT_OID_NAME_STR, NULL, NULL,
    &certNameBlob.cbData, NULL);
  certNameBlob.pbData =
    (BYTE *)CryptMemAlloc(sizeof(BYTE)*certNameBlob.cbData);
  CertStrToName(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
    szCertName, CERT_OID_NAME_STR, NULL,
    certNameBlob.pbData, &certNameBlob.cbData, NULL);

  GetSystemTime(&certExpireDate);
  certExpireDate.wYear += 5;

  hCertContext = CertCreateSelfSignCertificate(hCPContext,
    &certNameBlob, 0, NULL, NULL, NULL,
    &certExpireDate, NULL);
  hTempStore = CertOpenStore(CERT_STORE_PROV_MEMORY,
    0, NULL, CERT_STORE_CREATE_NEW_FLAG, 0);
  CertAddCertificateContextToStore(hTempStore,
    hCertContext, CERT_STORE_ADD_NEW, NULL);
  PFXExportCertStoreEx(hTempStore, pPFX, szPassword, NULL,
    EXPORT_PRIVATE_KEYS);
  pPFX->pbData =
    (BYTE *)CryptMemAlloc(sizeof(BYTE)*pPFX->cbData);
  PFXExportCertStoreEx(hTempStore, pPFX, szPassword, NULL,
    EXPORT_PRIVATE_KEYS);

  CryptMemFree(certNameBlob.pbData);
  CertCloseStore(hTempStore, CERT_CLOSE_STORE_FORCE_FLAG);
  CertFreeCertificateContext(hCertContext);
}

void WritePFX(CRYPT_DATA_BLOB pPFX, LPCWSTR szOutputFile)
{
  HANDLE hOutputFile = NULL;
  DWORD cbWritten = 0;

  hOutputFile = CreateFile(szOutputFile,
    GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
    FILE_FLAG_SEQUENTIAL_SCAN, NULL);
  WriteFile(hOutputFile, pPFX.pbData, pPFX.cbData,
    &cbWritten, NULL);
  CloseHandle(hOutputFile);
}

int _tmain(int argc, _TCHAR* argv[])
{
  LPCWSTR szCertFileName = NULL;
  CRYPT_DATA_BLOB pPFX;
  LPCWSTR szPassword = NULL;
  HCRYPTPROV hCPContext = NULL;
  LPCWSTR szCertName = L"CN=NewCert";
  CRYPT_DATA_BLOB pPfxOutputBlob = {0,NULL};
  LPCWSTR szOutFile = NULL;

  // 以下でコマンドラインを解析します。
  if (argc == 1)
  {
    printf("renewcert [optional]\n");
    printf("Example: renewcert oldcert.pfx newcert.pfx CN=MyNewCert\" MySuperSecretPassword");
    return 0;
  }

  if (argc >= 2)
    szCertFileName = argv[1];

  if (argc >= 3)
    szOutFile = argv[2];

  // 【注意】必ず"CN="という形式にしてください。
  if (argc >= 4)
    szCertName = argv[3];

  if (argc >= 5)
    szPassword = argv[4];

  // 以下でデジタル証明書(.pfxファイル)を更新します。
  ReadPFXFile(szCertFileName, &pPFX);

  GetPrivateKey(pPFX, szPassword, &hCPContext);

  MakeNewCert(hCPContext, szCertName, szPassword,
    &pPfxOutputBlob);

  WritePFX(pPfxOutputBlob, szOutFile);

  // メモリなどをクリーン・アップします。
  CryptReleaseContext(hCPContext, 0);
  CryptMemFree(pPfxOutputBlob.pbData);
  CryptMemFree(pPFX.pbData);

  return 0;
}
置き換えるコード内容(RenewCert.cppファイル)
コード内容の説明は本稿の趣旨ではないため割愛する。

 さらにstdafx.hファイルを開き、既存のコードを以下のコードに書き換える。

// stdafx.h : 標準のシステム インクルード ファイルの
// インクルード ファイル、または 参照回数が多く、かつ
// あまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。
//

#pragma once

#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <wincrypt.h>
置き換えるコード内容(stdafx.hファイル)
コード内容の説明は本稿の趣旨ではないため割愛する。

 以上でコーディングは終了だ。

 上記コードでは、(wincrypt.hヘッダ・ファイルをインクルードして)Crypt32.dllファイルの関数や構造体などを利用するので、次の画面のようにして、プロジェクトのプロパティで「Crypt32.lib」ライブラリをリンカの入力に指定する必要がある。

プロジェクトのプロパティにおけるリンカの入力の指定
プロジェクトのプロパティ(=[RenewCert プロパティ ページ]ダイアログ)を表示するには、[ソリューション エクスプローラ]でプロジェクト項目を右クリックして、表示されるコンテキスト・メニューで[プロパティ]をクリックすればよい。
  [RenewCert プロパティ ページ]ダイアログの[構成]から「すべての構成」を選ぶ。
  [構成プロパティ]−[リンカ]−[入力]を選択する。
  [追加の依存ファイル]を選択すると入力欄の右端に[...]ボタンが現れるので、これをクリック。これにより、[追加の依存ファイル]ダイアログが表示される。
  [追加の依存ファイル]ダイアログの入力欄に「Crypt32.lib」を入力する。すでにほかの内容が入力されている場合は、改行して新しい行に「Crypt32.lib」を入力すればよい。Visual C++ 2005 Express Editionの場合は、さらに「Advapi32.lib」も入力しなければならない(Express Edition以外のVS 2005ではデフォルトでAdvapi32.libがリンクされているはずだ)。「error LNK2019: 未解決の外部シンボル __imp__CryptReleaseContext@8 が関数 _wmain で参照されました。」というエラーが表示される場合は「Crypt32.lib」に加えて「Advapi32.lib」も入力すればよい。
  [追加の依存ファイル]ダイアログの[OK]ボタンをクリック。
  [RenewCert プロパティ ページ]ダイアログの[OK]ボタンをクリックすれば完了。

 あとはソリューションをビルドすればRenewCert.exeファイルが生成される。

●RenewCert.exeの利用

 RenewCert.exeの使い方は簡単だ。コマンドラインでの構文は以下のようになっている。

RenewCert.exe <元のデジタル証明書のファイル・パス名>.pfx <更新後のデジタル証明書のファイル・パス名>.pfx "CN=<発行者名>" <パスワード>

 VS 2005で自動生成したデジタル証明書における発行者名は、ClickOnceの発行を行ったユーザーの名前(例えば「D-ADVANTAGE\masa-i」)になっているはずだ。RenewCert.exeでもこの名前を指定すればよい。発行者の名前を変更したいのであれば、任意の名前を指定してもOKだ。

 例えば筆者の環境では、以下のコマンドラインでデジタル証明書を更新できた(パスワードは指定していない)。

renewcert C:\WindowsApplication1_TemporaryKey.pfx C:\WindowsApplication1_TemporaryKey_New.pfx "CN=D-ADVANTAGE\masa-i"

 これを実行すると、C:\にあるWindowsApplication1_TemporaryKey.pfxファイルが読み込まれ、新しくWindowsApplication1_TemporaryKey_New.pfxファイルが生成された(元のファイルは書き換えられない)。

 最後に実際にVS 2005を使って、この新しいデジタル証明書(.pfxファイル)を利用するようにすれば、冒頭で示したようなエラーが表示されずにClickOnceの発行が成功するはずである。VS 2005でデジタル証明書を指定する方法は、先ほども示した「連載:ClickOnceの真実 第7回」の「■セキュリティの設定方法1 ― デジタル証明書による署名方法」を参考にしてほしい。

 RenewCert.exeでデジタル証明書を更新すると、有効期限は5年延長されるので、当分の間はデジタル証明書の更新で作業が発生することはないだろう。End of Article

利用可能バージョン:.NET Framework 2.0のみ
カテゴリ:Windowsフォーム 処理対象:ClickOnce

この記事と関連性の高い別の.NET TIPS
プログラムでデジタル証明書をインストールするには?
SSL通信で信頼されない証明書を回避するには?
[Azure]Windows AzureアプリケーションをVisual Studioからデプロイするには?
ClickOnceアプリをコマンドラインから発行するには?
ClickOnceアプリケーションをデバッグするには?
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」


更新履歴
【2007/12/20】本記事の一部に以下の文を追記しました。お詫びして訂正させていただきます。

なお、読者からの問い合わせによると、VeriSignなどが発行した証明書を更新しても同様のエラーが発生することがある。筆者はVeriSignの証明書で動作を検証したわけでない

【2007/11/10】本記事の一部に以下のような誤りがありました。お詫びして訂正させていただきます。

このエラーを回避するには、デジタル証明書を更新して有効期限を延ばすしかない。その際、上記のmakecert.exeなどのツールや第三者機関のVeriSignなどによって作成されたデジタル証明書を利用している場合は、デジタル証明書を作成したときに使用した「秘密鍵ファイル」(.pvkファイル:PriVate Key Certificate)と「公開鍵ファイル」(.spcファイル:Software Publisher's Certificate)を用いて新しいデジタル証明書を発行し、有効期限を延ばす必要がある(このとき、最初に作成したときと同一の秘密鍵ファイルと公開鍵ファイルを使わないと、デジタル証明書が一致しないというエラーになり、クライアントPCからClickOnceアプリをアップデートできないので注意すること)。
このエラーを回避するには、デジタル証明書を更新して有効期限を延ばすしかない(VS 2005でデジタル証明書を新たに作り直すと、「デジタル証明書が一致しない」というエラーになり、クライアントPCからClickOnceアプリをアップデートできないので注意すること)。


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間