- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我在对 UWP appxbundle 文件进行 Authenticode 签名方面遇到了一个相当有趣的问题。
一些背景:客户向我们提供了包含签名证书的 SafeNet USB token 。当然,私钥是不可导出的。我希望能够使用此证书进行自动发布版本来签署包。不幸的是, token 要求每个 session 输入一次 PIN,因此,例如,如果构建代理重新启动,构建将会失败。我们在 token 上启用了单点登录,因此每次 session 解锁一次就足够了。
当前状态:鉴于 token 已解锁,我们可以在 appxbundle 上使用signtool,没有任何问题。这工作得很好,但一旦机器重新启动或工作站被锁定,就会崩溃。
经过一番搜索,我设法找到 this一段代码。这将获取签名参数(包括 token PIN)并调用 Windows API 对目标文件进行签名。我成功地编译了它,并且它完美地用于签署安装包装程序(EXE 文件) - token 不要求 PIN,并且通过 API 调用自动解锁。
但是,当我在 appxbundle 文件上调用相同的代码时,对 CryptUIWizDigitalSign
的调用失败,错误代码为 0x80080209 APPX_E_INVALID_SIP_CLIENT_DATA
。这对我来说是一个谜,因为使用相同的参数/证书在同一个包上调用signtool可以正常工作,因此证书应该与包完全兼容。
有人有过类似的经历吗?有没有办法找出错误的根本原因(我的证书和 bundle 之间不兼容)?
编辑 1
回复评论:
我用来调用 API 的代码(直接取自上述 SO 问题)
#include <windows.h>
#include <cryptuiapi.h>
#include <iostream>
#include <string>
#pragma comment (lib, "cryptui.lib")
const std::wstring ETOKEN_BASE_CRYPT_PROV_NAME = L"eToken Base Cryptographic Provider";
std::string utf16_to_utf8(const std::wstring& str)
{
if (str.empty())
{
return "";
}
auto utf8len = ::WideCharToMultiByte(CP_UTF8, 0, str.data(), str.size(), NULL, 0, NULL, NULL);
if (utf8len == 0)
{
return "";
}
std::string utf8Str;
utf8Str.resize(utf8len);
::WideCharToMultiByte(CP_UTF8, 0, str.data(), str.size(), &utf8Str[0], utf8Str.size(), NULL, NULL);
return utf8Str;
}
struct CryptProvHandle
{
HCRYPTPROV Handle = NULL;
CryptProvHandle(HCRYPTPROV handle = NULL) : Handle(handle) {}
~CryptProvHandle() { if (Handle) ::CryptReleaseContext(Handle, 0); }
};
HCRYPTPROV token_logon(const std::wstring& containerName, const std::string& tokenPin)
{
CryptProvHandle cryptProv;
if (!::CryptAcquireContext(&cryptProv.Handle, containerName.c_str(), ETOKEN_BASE_CRYPT_PROV_NAME.c_str(), PROV_RSA_FULL, CRYPT_SILENT))
{
std::wcerr << L"CryptAcquireContext failed, error " << std::hex << std::showbase << ::GetLastError() << L"\n";
return NULL;
}
if (!::CryptSetProvParam(cryptProv.Handle, PP_SIGNATURE_PIN, reinterpret_cast<const BYTE*>(tokenPin.c_str()), 0))
{
std::wcerr << L"CryptSetProvParam failed, error " << std::hex << std::showbase << ::GetLastError() << L"\n";
return NULL;
}
auto result = cryptProv.Handle;
cryptProv.Handle = NULL;
return result;
}
int wmain(int argc, wchar_t** argv)
{
if (argc < 6)
{
std::wcerr << L"usage: etokensign.exe <certificate file path> <private key container name> <token PIN> <timestamp URL> <path to file to sign>\n";
return 1;
}
const std::wstring certFile = argv[1];
const std::wstring containerName = argv[2];
const std::wstring tokenPin = argv[3];
const std::wstring timestampUrl = argv[4];
const std::wstring fileToSign = argv[5];
CryptProvHandle cryptProv = token_logon(containerName, utf16_to_utf8(tokenPin));
if (!cryptProv.Handle)
{
return 1;
}
CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO extInfo = {};
extInfo.dwSize = sizeof(extInfo);
extInfo.pszHashAlg = szOID_NIST_sha256; // Use SHA256 instead of default SHA1
CRYPT_KEY_PROV_INFO keyProvInfo = {};
keyProvInfo.pwszContainerName = const_cast<wchar_t*>(containerName.c_str());
keyProvInfo.pwszProvName = const_cast<wchar_t*>(ETOKEN_BASE_CRYPT_PROV_NAME.c_str());
keyProvInfo.dwProvType = PROV_RSA_FULL;
CRYPTUI_WIZ_DIGITAL_SIGN_CERT_PVK_INFO pvkInfo = {};
pvkInfo.dwSize = sizeof(pvkInfo);
pvkInfo.pwszSigningCertFileName = const_cast<wchar_t*>(certFile.c_str());
pvkInfo.dwPvkChoice = CRYPTUI_WIZ_DIGITAL_SIGN_PVK_PROV;
pvkInfo.pPvkProvInfo = &keyProvInfo;
CRYPTUI_WIZ_DIGITAL_SIGN_INFO signInfo = {};
signInfo.dwSize = sizeof(signInfo);
signInfo.dwSubjectChoice = CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE;
signInfo.pwszFileName = fileToSign.c_str();
signInfo.dwSigningCertChoice = CRYPTUI_WIZ_DIGITAL_SIGN_PVK;
signInfo.pSigningCertPvkInfo = &pvkInfo;
signInfo.pwszTimestampURL = timestampUrl.c_str();
signInfo.pSignExtInfo = &extInfo;
if (!::CryptUIWizDigitalSign(CRYPTUI_WIZ_NO_UI, NULL, NULL, &signInfo, NULL))
{
std::wcerr << L"CryptUIWizDigitalSign failed, error " << std::hex << std::showbase << ::GetLastError() << L"\n";
return 1;
}
std::wcout << L"Successfully signed " << fileToSign << L"\n";
return 0;
}
证书是从 token 导出的 CER 文件(仅限公共(public)部分),容器名称取自 token 的信息。正如我提到的,这对于 EXE 文件是正确的。
signtool 命令
signtool sign/sha1“证书指纹”/fd SHA256/n“主题名称”/t“http://timestamp.verisign.com/scripts/timestamp.dll”/debug“$path”
当我手动调用它或在 token 解锁时从 CI 构建调用它时,这也有效。但上面的代码失败并出现上述错误。
编辑2
感谢大家,我现在已经有了一个有效的实现!按照 RbMm 的建议,我最终使用了 SignerSignEx2
API。这似乎对 appx 包和 PE 文件都适用(每个文件都有不同的参数)。使用 TFS 2017 构建代理在 Windows 10 上进行验证 - 解锁 token ,在证书存储中查找指定的证书,并对指定的文件进行签名+时间戳。
我在 GitHub 上发布了结果,如果有人感兴趣的话:https://github.com/mareklinka/SafeNetTokenSigner
最佳答案
首先我看看CryptUIWizDigitalSign
失败的地方:
名为 SignerSignEx
的 CryptUIWizDigitalSign
函数,其中 pSipData == 0
。对于符号 PE 文件(exe、dll、sys) - 这是可以的并且可以工作。但对于 appxbundle (zip 存档文件类型),此参数是必需的,并且必须指向 APPX_SIP_CLIENT_DATA
:对于appxbundle调用堆栈是
CryptUIWizDigitalSign
SignerSignEx
HRESULT Appx::Packaging::AppxSipClientData::Initialize(SIP_SUBJECTINFO* subjectInfo)
在Appx::Packaging::AppxSipClientData::Initialize
的一开始,我们可以查看下一个代码:
if (!subjectInfo->pClientData) return APPX_E_INVALID_SIP_CLIENT_DATA;
这正是您的代码失败的地方。
而不是CryptUIWizDigitalSign
需要直接调用 SignerSignEx2
在这种情况下,pSipData
是必需参数。
在 msdn 中存在完整的工作示例 - How to programmatically sign an app package (C++)
这里的关键点:
APPX_SIP_CLIENT_DATA sipClientData = {};
sipClientData.pSignerParams = &signerParams;
signerParams.pSipData = &sipClientData;
现代SignTool
调用SignerSignEx2
直接:
这里再次清晰可见:
if (!subjectInfo->pClientData) return APPX_E_INVALID_SIP_CLIENT_DATA;
在此之后调用
HRESULT Appx::Packaging::Packaging::SignFile(
PCWSTR FileName, APPX_SIP_CLIENT_DATA* sipClientData)
从这里开始下一个代码:
if (!sipClientData->pSignerParams) return APPX_E_INVALID_SIP_CLIENT_DATA;
这在 msdn 中有明确说明:
You must provide a pointer to an APPX_SIP_CLIENT_DATA structure as the pSipData parameter when you sign an app package. You must populate the pSignerParams member of APPX_SIP_CLIENT_DATA with the same parameters that you use to sign the app package. To do this, define your desired parameters on the SIGNER_SIGN_EX2_PARAMS structure, assign the address of this structure to pSignerParams, and then directly reference the structure's members as well when you call SignerSignEx2.
问题 - 为什么需要再次提供调用 SignerSignEx2
中使用的相同参数?因为appxbundle实际上是存档,其中包含多个文件。每个文件都需要签名。对于此 Appx::Packaging::Packaging::SignFile
再次递归调用 SignerSignEx2
:
对于此递归调用 pSignerParams
并使用 - 用于调用 SignerSignEx2
,其参数与顶部调用完全相同
关于winapi - 使用 CryptUIWizDigitalSign API 签署 appxbundle,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48804073/
有人可以告诉我签署我的 clickonce 应用程序的步骤吗? 如果从某人那里购买证书,我该如何安装它在我的开发盒以及我希望它运行的服务器上? 请概述步骤。 马尔科姆 最佳答案 以下是我相信您正在执行
我正在使用命令行 jarsigner.exe -keystore Keys.jks base.apk debug0 使用我自己的 key 退出 APK ,在删除 META-INF/CERT.RSA、C
我使用 iTextSharp 签署 PDF 文件。但是 Adobe Reader 无法验证我的签名。我使用证书颁发机构生成的 SHA-2 测试证书(我也尝试过 SHA-1)。我已经为该机构的测试证
我在尝试签署 F# 类库项目时遇到了很多麻烦。 首先我试过这个thread ,使用 AssemblyKeyFileAttribute 但没有成功。 我还尝试将标志“--keyfile:keyfile.
我正在尝试掌握 .NET dll/程序集的正式签名。 尤其 何时以及如何使用私钥 创建/控制私钥的最佳实践 需要签署什么样的模块/最佳实践 最佳答案 请参阅使用 Strong Name Signatu
我有一个由五个项目组成的解决方案,每个项目都编译成单独的程序集。现在我正在对它们进行代码签名,但我很确定我做错了。这里的最佳做法是什么? 用不同的 key 对每个签名;确保密码不同 用不同的 key
我有一个由五个项目组成的解决方案,每个项目都编译为单独的程序集。现在我正在对它们进行代码签名,但我很确定我做错了。这里的最佳实践是什么? 使用不同的 key 对每个签名进行签名;确保密码不同 使用不同
尝试在 Java 中签署 SOAP 消息时抛出异常: 14:47:39.896 [AWT-EventQueue-0] ERROR com.ui.FinestraPrincipal - WSHandle
我正在运行 java web start 应用程序。由于应用程序数字签名已过期。我已经从 CA 购买了签名,并使用 storetype 作为 pkcs12 对我的 jar 文件进行了签名。 对 JNL
我已经在jar中手动添加了一些类文件。并在服务器中替换。但服务器没有拿出这个新的 jar 说:java.lang.SecurityException:类“test.TestProcess2”的签名者信
我想使用 keytool 对我的插件 jar 进行签名以生成 key 。 我知道有 -validity 选项,但如何设置它以使插件始终有效且永不过期。 谢谢 最佳答案 查看 keytool 文档: V
我有一个需要互联网访问的 Java 应用程序,因为它通过 WebView 嵌入了 Web 浏览器。 JavaFX 组件。 如果应用程序未打包在 Jar 中,则执行不会出现问题。但是,当打包在 Jar
我以前做过,但我完全忘记了如何签署 activeX 控件? 最佳答案 Digital Signing for ActiveX Components (MSDN) 关于activex - 签署 Acti
不知不觉中我删除了通常命名如下的钥匙串(keychain)系统证书 Software Signing com.apple.systemdefault com.apple.kerberos.kdc Ap
我尝试了一些基于 iText v1 或 v2 的数字 PDF 签名实用程序,发现似乎整个 PDF 都加载到内存中(对于 60M PDF 进程可能需要多达 300-400MB 的内存)。 最近的 iTe
我正在尝试使用 Python 对 Amazon 的 SimpleDB 服务进行 API 调用。例如,我使用的是最简单的请求 ListDomains。然而,无论我如何尝试,响应始终是“我们计算的请求签名
我的同事给了我一个Java项目来支持和增强。我需要签署 .jar 文件。我只找到了下一个文件:genkey.bat、project.cer、signjar.bat 和 keystore-jar.jar
目前我正在升级我的旧应用程序,它是使用 itextsharp 5.0.0 完成的到 5.4.5(最新)...但是我在获取等效代码时遇到了问题 PdfSignatureAppearance.SetCry
如果我有一个作为 API 请求发送的复杂对象(例如下面的订单),我应该在生成签名时包括所有属性还是应该只使用一个子集? 我问是因为我不清楚,而且从查看其他 API 的请求参数来看,请求参数是扁平和简单
为什么 WP7 项目不支持强名称签名?项目属性中没有签名选项卡。 无论如何,我已经使用 AssemblyInfo.cs 中的 AssemblyKeyFile 和 AssemblyDelaySign 属
我是一名优秀的程序员,十分优秀!