在下载时将用户特定数据嵌入到authenticode签名安装程序中

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

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

我正在考虑几个选项:

  1. 将用户特定的UUID嵌入到setup.exe中,并在Web服务器上按需执行代码签名
    缺点:不知道怎么做?
  2. 将用户特定的UUID嵌入到安装程序文件的名称中(例如setup_08adfb12_2712_4f1e_8630_e202da352657.exe)
    缺点:这不是很漂亮,如果重命名安装程序会失败
  3. 将安装程序和包含UUID的设置文件包装到自解压缩zip中

如何将用户特定数据嵌入到Web服务器上的已签名可执行文件中?

整个PE没有签名。 您可以通过将数据添加到签名表中将数据嵌入到已签名的PE中。 Webex和其他工具使用此方法来提供一键式会议实用程序。

从技术上讲,PKCS#7签名有一个属性列表,这些属性被专门指定为可以使用的未经身份validation的属性,但我知道没有完整的PE解析器就没有简单的方法可以写入这些字段。 幸运的是,我们已经有了signtool ,并且对已经签名的文件添加了一个额外的签名是一种使用未经身份validation的字段的非破坏性操作。

我整理了一个演示 ,它使用这种技术将数据从MVC网站传递到可下载的Windows窗体可执行文件。

程序是:

  1. 从标准进程生成的authenticode signed和timestamped exe开始
    (必须能够无依赖地运行 – ILMerge或类似的)
  2. 将未加阻的exe复制到临时文件
  3. 创建一个短暂的代码签名证书,其中包括辅助数据作为X509扩展
  4. 使用signtool将辅助签名添加到临时文件
  5. 将临时文件返回给客户端,下载完成后将其删除

在客户端,应用程序:

  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"); } } }