下载时将特定于用户的数据嵌入到经过验证码签名的安装程序中

Embed user-specific data into an authenticode signed installer on download

我使用我的用户从我的网站下载的 InnoSetup 安装了一个 Windows Forms 应用程序。他们将此软件安装到多台 PC 上。

应用程序与必须能够识别用户的 Web API 对话。我正在创建一个 Web 应用程序,用户可以在其中登录并下载该应用程序。我想在安装程序中嵌入一个普遍唯一的 ID,这样他们就不必在安装后再次登录。我希望他们下载 运行 setup.exe,让应用程序自行处理。

我正在考虑几个选项:

  1. 将特定于用户的 UUID 嵌入 setup.exe 并在 Web 服务器上按需执行代码签名
    缺点:不确定如何执行此操作?
  2. 将特定于用户的 UUID 嵌入到安装程序文件的名称中(例如 setup_08adfb12_2712_4f1e_8630_e202da352657.exe)
    缺点:这不太好,如果重命名安装程序将会失败
  3. 将安装程序和包含 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.

程序是:

  1. 从标准进程生成的经过验证码签名和时间戳的 exe 开始
    (必须能够运行 没有依赖项 - ILMerge 或类似的)
  2. 将未标记的 exe 复制到临时文件
  3. 创建临时代码签名证书,其中包含辅助数据作为 X509 extension
  4. 使用signtool将辅助签名添加到临时文件
  5. Return客户端临时文件,下载完成后删除

在客户端,应用程序:

  1. 从当前执行的 exe 中读取签名证书
  2. 查找主题名称已知的证书
  3. 找到具有已知 OID 的扩展
  4. 根据扩展中包含的数据改变其行为

这个过程有很多优点:

  • 不要乱用 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");
        }
    }
}