下载时将特定于用户的数据嵌入到经过验证码签名的安装程序中
Embed user-specific data into an authenticode signed installer on download
我使用我的用户从我的网站下载的 InnoSetup 安装了一个 Windows Forms 应用程序。他们将此软件安装到多台 PC 上。
应用程序与必须能够识别用户的 Web API 对话。我正在创建一个 Web 应用程序,用户可以在其中登录并下载该应用程序。我想在安装程序中嵌入一个普遍唯一的 ID,这样他们就不必在安装后再次登录。我希望他们下载 运行 setup.exe,让应用程序自行处理。
我正在考虑几个选项:
- 将特定于用户的 UUID 嵌入 setup.exe 并在 Web 服务器上按需执行代码签名
缺点:不确定如何执行此操作?
- 将特定于用户的 UUID 嵌入到安装程序文件的名称中(例如 setup_08adfb12_2712_4f1e_8630_e202da352657.exe)
缺点:这不太好,如果重命名安装程序将会失败
- 将安装程序和包含 UUID 的设置文件打包成自解压 zip
如何将用户特定的数据嵌入到 Web 服务器上已签名的可执行文件中?
整个PE没有签名。您可以通过将数据添加到签名 table 来将数据嵌入到签名的 PE 中。此方法 used by Webex and other tools 提供 one-click 会议实用程序。
从技术上讲,PKCS#7 签名有一个属性列表,这些属性被专门指定为未验证的,可以使用,但我知道没有完整的 PE 解析器就没有简单的方法来写入这些字段。幸运的是,我们已经有了 signtool
,并且向已签名的文件添加额外的签名是一个 non-destructive 使用未验证字段的操作。
I put together a demo 使用此技术将数据从 MVC 网站传递到可下载的 windows 表单 executable.
程序是:
- 从标准进程生成的经过验证码签名和时间戳的 exe 开始
(必须能够运行 没有依赖项 - ILMerge 或类似的)
- 将未标记的 exe 复制到临时文件
- 创建临时代码签名证书,其中包含辅助数据作为 X509 extension
- 使用
signtool
将辅助签名添加到临时文件
- Return客户端临时文件,下载完成后删除
在客户端,应用程序:
- 从当前执行的 exe 中读取签名证书
- 查找主题名称已知的证书
- 找到具有已知 OID 的扩展
- 根据扩展中包含的数据改变其行为
这个过程有很多优点:
- 不要乱用 PE 布局
- 公共信任的代码签名证书可以保持离线状态(甚至在 HSM 中),仅在 Web 服务器上使用临时证书
- Web 服务器未生成任何出站流量(如果执行时间戳,则需要这样做)
- 快速(对于 1MB 的 exe,<50ms)
- 可以在 IIS 中 运行
用法
客户端检索数据(Demo Application\MainForm.cs)
try
{
var thisPath = Assembly.GetExecutingAssembly().Location;
var stampData = StampReader.ReadStampFromFile(thisPath, StampConstants.StampSubject, StampConstants.StampOid);
var stampText = Encoding.UTF8.GetString(stampData);
lbStamped.Text = stampText;
}
catch (StampNotFoundException ex)
{
MessageBox.Show(this, $"Could not locate stamp\r\n\r\n{ex.Message}", Text);
}
服务器端戳记(Demo Website\Controllers\HomeController.cs)
var stampText = $"Server time is currently {DateTime.Now} at time of stamping";
var stampData = Encoding.UTF8.GetBytes(stampText);
var sourceFile = Server.MapPath("~/Content/Demo Application.exe");
var signToolPath = Server.MapPath("~/App_Data/signtool.exe");
var tempFile = Path.GetTempFileName();
bool deleteStreamOpened = false;
try
{
IOFile.Copy(sourceFile, tempFile, true);
StampWriter.StampFile(tempFile, signToolPath, StampConstants.StampSubject, StampConstants.StampOid, stampData);
var deleteOnClose = new FileStream(tempFile, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, 4096, FileOptions.DeleteOnClose);
deleteStreamOpened = true;
return File(deleteOnClose, "application/octet-stream", "Demo Application.exe");
}
finally
{
if (!deleteStreamOpened)
{
try
{
IOFile.Delete(tempFile);
}
catch
{
// no-op, opportunistic cleanup
Debug.WriteLine("Failed to cleanup file");
}
}
}
我使用我的用户从我的网站下载的 InnoSetup 安装了一个 Windows Forms 应用程序。他们将此软件安装到多台 PC 上。
应用程序与必须能够识别用户的 Web API 对话。我正在创建一个 Web 应用程序,用户可以在其中登录并下载该应用程序。我想在安装程序中嵌入一个普遍唯一的 ID,这样他们就不必在安装后再次登录。我希望他们下载 运行 setup.exe,让应用程序自行处理。
我正在考虑几个选项:
- 将特定于用户的 UUID 嵌入 setup.exe 并在 Web 服务器上按需执行代码签名
缺点:不确定如何执行此操作? - 将特定于用户的 UUID 嵌入到安装程序文件的名称中(例如 setup_08adfb12_2712_4f1e_8630_e202da352657.exe)
缺点:这不太好,如果重命名安装程序将会失败 - 将安装程序和包含 UUID 的设置文件打包成自解压 zip
如何将用户特定的数据嵌入到 Web 服务器上已签名的可执行文件中?
整个PE没有签名。您可以通过将数据添加到签名 table 来将数据嵌入到签名的 PE 中。此方法 used by Webex and other tools 提供 one-click 会议实用程序。
从技术上讲,PKCS#7 签名有一个属性列表,这些属性被专门指定为未验证的,可以使用,但我知道没有完整的 PE 解析器就没有简单的方法来写入这些字段。幸运的是,我们已经有了 signtool
,并且向已签名的文件添加额外的签名是一个 non-destructive 使用未验证字段的操作。
I put together a demo 使用此技术将数据从 MVC 网站传递到可下载的 windows 表单 executable.
程序是:
- 从标准进程生成的经过验证码签名和时间戳的 exe 开始
(必须能够运行 没有依赖项 - ILMerge 或类似的) - 将未标记的 exe 复制到临时文件
- 创建临时代码签名证书,其中包含辅助数据作为 X509 extension
- 使用
signtool
将辅助签名添加到临时文件 - Return客户端临时文件,下载完成后删除
在客户端,应用程序:
- 从当前执行的 exe 中读取签名证书
- 查找主题名称已知的证书
- 找到具有已知 OID 的扩展
- 根据扩展中包含的数据改变其行为
这个过程有很多优点:
- 不要乱用 PE 布局
- 公共信任的代码签名证书可以保持离线状态(甚至在 HSM 中),仅在 Web 服务器上使用临时证书
- Web 服务器未生成任何出站流量(如果执行时间戳,则需要这样做)
- 快速(对于 1MB 的 exe,<50ms)
- 可以在 IIS 中 运行
用法
客户端检索数据(Demo Application\MainForm.cs)
try
{
var thisPath = Assembly.GetExecutingAssembly().Location;
var stampData = StampReader.ReadStampFromFile(thisPath, StampConstants.StampSubject, StampConstants.StampOid);
var stampText = Encoding.UTF8.GetString(stampData);
lbStamped.Text = stampText;
}
catch (StampNotFoundException ex)
{
MessageBox.Show(this, $"Could not locate stamp\r\n\r\n{ex.Message}", Text);
}
服务器端戳记(Demo Website\Controllers\HomeController.cs)
var stampText = $"Server time is currently {DateTime.Now} at time of stamping";
var stampData = Encoding.UTF8.GetBytes(stampText);
var sourceFile = Server.MapPath("~/Content/Demo Application.exe");
var signToolPath = Server.MapPath("~/App_Data/signtool.exe");
var tempFile = Path.GetTempFileName();
bool deleteStreamOpened = false;
try
{
IOFile.Copy(sourceFile, tempFile, true);
StampWriter.StampFile(tempFile, signToolPath, StampConstants.StampSubject, StampConstants.StampOid, stampData);
var deleteOnClose = new FileStream(tempFile, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, 4096, FileOptions.DeleteOnClose);
deleteStreamOpened = true;
return File(deleteOnClose, "application/octet-stream", "Demo Application.exe");
}
finally
{
if (!deleteStreamOpened)
{
try
{
IOFile.Delete(tempFile);
}
catch
{
// no-op, opportunistic cleanup
Debug.WriteLine("Failed to cleanup file");
}
}
}