原文地址:http://www.cnblogs.com/xuhaibiao/archive/2009/06/23/1509692.html
场景:用于生产环境的项目进行系统升级,在发布ClickOnce过程中发现签名已过期,现场工程师重新生成了一个签名,然后进行发布,发布完成后,所有客户端无法更新,提示签名错误。
问题:VS2005做年签名只有一年期限,所以很容易在维护期内就过期了,如果新做签名结果会造成客户端验证签名出错无法更新。
解决办法:
1、新做一个签名,使用新的签名做ClickOnce,所有客户端将原来程序删除,重新使用新的ClickOnce安装程序进行安装。
(此方法太可耻了,如果再有升级的话,可能还会有这问题,同时ClickOnce也得改名叫ClickOneYear了)。
2、利用原有签名,延长有效时间。
(中文)http://support.microsoft.com/kb/925521/zh-cn
(英文)http://support.microsoft.com/kb/925521/en-us
方法 1
更新客户端计算机 Microsoft 安装 ClickOnce 应用程序,.NET Framework 2.0 Service Pack 1 (SP1) 或更高版本。Windows Vista
.NET Framework 3.5 或.NET Framework 3.5 SP 1 应用。注意:.NET Framework 3.5 包含许多新功能,生成在.NET Framework 2.0 版时以增量方式和 3.0。.NET Framework 3.5 包括.NET Framework 2.0 SP1 和.NET Framework 3.0 SP1。
下列文件已可从 Microsoft 下载中心下载:
有关如何下载 Microsoft 支持文件的详细信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
Windows XP
.NET Framework 2.0 SP1 或.NET Framework 2.0 Service Pack 2 (SP2) 应用。下面是一些可从 Microsoft 下载中心下载的文件:
有关如何下载 Microsoft 支持文件的详细信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
方法 2
卸载您使用过期的证书注册的 ClickOnce 应用程序。方法 3
创建命令行程序集更新证书。Microsoft 提供的编程示例仅用于说明,没有任何明示或暗示的担保。这包括但不限于适销性或特定用途适用性的暗示担保。本文假定您熟悉所演示的编程语言和用于创建和调试过程的工具。Microsoft 的支持工程师可以帮助解释某个特定过程的功能。但是,他们不会修改这些示例以提供额外的功能或构建过程以满足您的特定要求。
- 在 Visual Studio 2005 中,在文件菜单上单击新建,然后单击项目。
- 单击Visual C++ Win32 控制台应用程序中的名称框中,键入RenewCert和,然后单击确定。
- 在Win32 应用程序向导对话框中,单击完成。
- 在 RenewCert.cpp 文件中,用下面的代码替换现有代码:
#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 PrintContainerName(HCRYPTPROV hCPContext) { DWORD containerNameLen = 0; CHAR *szContainerName = NULL; CryptGetProvParam(hCPContext, PP_CONTAINER, NULL, &containerNameLen, 0); szContainerName = (CHAR *)CryptMemAlloc(sizeof(BYTE)*containerNameLen); CryptGetProvParam(hCPContext, PP_CONTAINER, (BYTE *)szContainerName, &containerNameLen, 0); printf("This certificate's container name is: %s", szContainerName); } 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; // Parse the command line. if(argc == 1) { printf("renewcert <PFX File> <new cert filename> <new cert friendly name> [optional]<password> "); printf("Example: renewcert oldcert.pfx newcert.pfx "CN=MyNewCert" MySuperSecretPassword"); return 0; } if(argc >= 2) szCertFileName = argv[1]; if(argc >= 5) szPassword = argv[4]; // Uncomment this block to add <new cert filename> and <new cert friendly name> as parameters // NOTE: <new cert friendly name> must be of format "CN=<name>" if(argc >= 3) szOutFile = argv[2]; if(argc >= 4) szCertName = argv[3]; ReadPFXFile(szCertFileName, &pPFX); GetPrivateKey(pPFX, szPassword, &hCPContext); //PrintContainerName(hCPContext); // Uncomment this section to make a new PFX rather than just printing the container name. // Make sure you also uncomment the command line parameter section above. MakeNewCert(hCPContext, szCertName, szPassword, &pPfxOutputBlob); WritePFX(pPfxOutputBlob, szOutFile); // Clean up. CryptReleaseContext(hCPContext, 0); CryptMemFree(pPfxOutputBlob.pbData); CryptMemFree(pPFX.pbData); return 0; }
- 在 stdafx.h 文件中,用下面的代码替换现有代码:
// stdafx.h : include file for standard system include files, // or project specific include files that are used frequently, but // are changed infrequently. // #pragma once #define WIN32_LEAN_AND_MEAN // Exclude rarely used material from Windows headers. #include <stdio.h> #include <tchar.h> #include <windows.h> #include <wincrypt.h>
- 在项目菜单上,单击属性以打开此项目的属性页。
- 展开链接器节点,然后单击输入。
- 附加依赖项,右边的空白窗口中单击,然后单击省略号按钮 (...) 到附加依赖项对话框中打开。
- 在空白的窗口中,键入Crypt32.lib,然后单击确定。
- 单击应用,然后单击确定以关闭属性页。
- 在生成菜单中,单击生成解决方案。
- 生成解决方案后,执行以下命令,以更新证书,请:
renewcert <OldCertificate>.pfx <NewCertificate>.pfx "CN=<NewCertificateName>" <Password>
更多信息
要重现此问题的步骤
- 启动 Visual Studio 2005。
- 在文件菜单上单击新建,然后单击项目。
- 单击C#单击Windows 应用程序,请在名称框中,键入WindowsApplication1 ,然后单击确定。
- 在解决方案资源管理器中,用鼠标右键单击WindowsApplication1,然后单击属性。
- 单击签名,并将不久即会过期的证书。
- 在解决方案资源管理器中,用鼠标右键单击WindowsApplication1,然后单击发布。
- 在要在其中发布该应用程序?页上,键入一个有效的 URL,然后单击下一步。
http://ServerName/FolderName
- 在应用程序是否将可脱机吗?页面上,单击相应的选项。
备注:- 如果您想让用户从网络断开连接时运行该应用程序的用户,请单击是,此应用程序可联机或脱机。向导将在开始菜单上创建应用程序的快捷方式。
- 如果想要直接从发布位置运行该应用程序,则单击否,此应用程序只能联机使用。该向导不会在开始菜单上创建快捷方式。
- 单击下一步以继续。
- 单击完成以发布应用程序。
- 安装 ClickOnce 应用程序从 http://ServerName/ /publish.htmFolderName的 URL。
- 在证书过期后,请重复步骤 6 至 10 以重新发布应用程序。
- 尝试安装 ClickOnce 应用程序更新从 http://ServerName/ /publish.htmFolderName的 URL。
有此朋友发现过这样的问题,也知道解决办法,但如果机器上没装VC++可能操作起来也不方便,现编译完成一份,各位可以下载:
http://files.cnblogs.com/spymaster/RenewCert.rar
使用方法:到命令窗口,输入
renewcert <OldCertificate>.pfx <NewCertificate>.pfx "CN=<NewCertificateName>" <Password>
好了,新签名的有限期是5年。
3、项目之初就做一个长效的签名。
http://www.cnblogs.com/xuhaibiao/archive/2009/06/22/1508317.html
打开Microsoft .NET Framework 的SDK命令提示,按以下步骤操作:
1、创建一个自我签署的X.509证书(.cer)和一个.pvk私钥文件,用到makecert工具,命令如下:
makecert -r -n "CN= cncxz " -b 01/01/2005 -e 01/01/2018 -sv myselfName.pvk myselfName.cer
按提示设置私钥密码(也可以不使用密码)即可在当前目录生成相关文件
2、利用X.509证书(.cer)创建发行者证书 (.spc),用到cert2spc工具,命令如下:
cert2spc myselfName.cer myselfName.spc
3、从.pvk和.spc格式转换成.pfx格式,用到pvkimprt工具,命令如下:
pvkimprt -pfx myselfName.spc myselfName.pvk
按提示操作可导出.pfx证书,若第1步设置了私钥密码,此处需要输入验证
4、在vs项目的ClickOnce清单签名的证书设置处点击“从文件选择”浏览定位到第3步导出的.pfx证书,此处需要验证第3步中设置的证书私钥密码。
makecert工具和cert2spc工具.NET Framework自带,pvkimprt工具下载地址如下
http://download.microsoft.com/download/vba50/Utility/1.0/NT5/EN-US/pvkimprt.exe
如果以上下载链接有失效的,请到该地址下载:
http://download.csdn.net/detail/u012373717/8726827