如何在 SQL 中验证代码签名程序集

How to verify code signed assembly in SQL

我在 SQL Server (2012) 中阅读了大量关于非对称密钥和证书的内容,但它们似乎专门用于签署数据。我拥有的是一个 SQL CLR 程序集,我已经用数字证书对其进行了签名,并且已成功加载为 UNSAFE 程序集并成功执行了 SqlFunction。但是,我想向 SqlFunction 添加逻辑以验证程序集是否由我的证书签名。对于所有其他程序集,我们提取 public 密钥并将其与许可证文件中的密钥进行比较。但是当程序集在 SQL 内部执行时,似乎没有办法提取 public 键。是否有我不了解的 SQL CLR 或 CERT/ASK 体系结构的一部分才能访问它? TIA!

需求说明:

我们出售以二进制格式将数据生成到我们的数据库架构中的软件,因此客户无法使用 T-SQL 直接查询数据。这样做既是为了提高性能,也是为了黑盒我们输出数据的数据分析算法。目前,客户必须使用我们的软件通过 "BusnLogicCore.dll" "unpack" 此 blob 数据。仅当调用程序集已由我们事先批准的证书签名时,此 dll 才会执行。对于购买我们 SDK 的客户,客户将他们自己的证书的 public 密钥发送给我们。并且我们将 pk 添加到我们颁发给他们的许可证文件中,以便程序集可以确认他们被允许调用 BusnLogicCore.dll。我们现在正在创建 SQL CLR 函数,该函数允许客户使用 T-SQL 调用 BusnLogicCore.dll 并将 blob 数据解包作为其查询的一部分。但是,我们无法确认 SQL CLR 函数已由我们批准调用 BusnLogicCore.dll 的证书签名。如果我们在这个逻辑上打个洞,让SQL CLR函数不经过这个验证,那么没有购买SDK的客户可以自己写SQL CLR调用 BusnLogicCore.dll 的函数。它比这稍微复杂一点,我们可以对许可证进行额外的检查,但最好不要绕过这个验证。这是一个边缘案例吗?可能吧,但我们想尽我们所能保护我们的 IP,没有这个选项是令人沮丧的。

更新: 我检查了 sys.certificates table,但 cert_serial_number 和指纹列都不匹配证书的 public 密钥。

假设您首先将证书加载到 master 数据库中,从该证书创建一个登录名,并授予该登录名 UNSAFE ASSEMBLY 权限,这样您就可以使用 PERMISSION_SET = UNASFE,那么我不知道你为什么想要/需要做你要求的事情,因为那个问题在那个时候已经得到了回答。

意思是,如果您能够将程序集创建为 UNSAFE(假设数据库已将 TRUSTWORTHY 设置为 OFF,因此使用证书——这样更好无论如何都要走),那么只有在程序集使用首先加载到 master 等的证书(或强名称密钥/非对称密钥)签名时才会发生这种情况(如本答案开头所述) ).如果程序集未签名或由不同的证书(或强名称密钥)签名,则在尝试将其创建为 UNSAFEEXTERNAL_ACCESS(甚至 SAFE 从 SQL Server 2017 开始。

不过,sys.certificates 系统目录视图中仍有一些信息可用于匹配数据库之间的证书。如果您在 master 中创建证书之前加载了程序集(通过将数据库设置为 TRUSTWORTHY ON —— 坏主意,或者加载为 SAFE 以更改为 UNSAFEmaster 中设置证书后——从 SQL Server 2017 开始将不起作用),然后您可以使用 CREATE CERTIFICATE [bob] FROM ASSEMBLY [YourAssembly]; 从程序集中提取证书,然后从 sys.certificates.

中获取该证书的 cert_serial_numberthumbprint

再澄清一点:

  1. 您不能直接在 SQL 服务器中直接获取实际的 public 密钥(有时称为 "certificate" 因为这不会混淆,对吧?) built-in机制。不幸的是,您可以通过 sys.asymmetric_keys 获取非对称密钥/强名称密钥的 public 密钥,但不适用于证书。

  2. sys.certificatescert_serial_number 字段中找到的序列号正是:序列号,而不是 public 键。它匹配在命令提示符下执行 certutil -dump certificate.cer 时返回的 "Serial Number"。

  3. sys.certificatesthumbprint 字段中找到的指纹是 public 密钥的 SHA1 散列(有时称为 "certificate"), 而不是 public 键。它匹配在命令提示符下执行 certutil -dump certificate.cer 时返回的 "Cert Hash(sha1)"。

因此,如果您需要分析 SQL 服务器中的程序集,您可能需要使用 .NET 方法来执行此操作,但我不知道该库是否在 "supported" 列表,如果没有,则需要添加库并将程序集设置为 UNSAFE.

否则,也许您可​​以选择几个选项来检查什么:public 密钥(如果可用),否则指纹 and/or 序列号。

或者,因为您有 public 密钥,您可以通过 SELECT CERTENCODED(CERT_ID(N'certificate_name')) 获得完整的证书。这将为您提供证书的完整系列字节,其中包括 public 密钥。您可以搜索 public 密钥作为完整证书字符串形式的子字符串。

或者,按照相同的思路,您可以扫描 sys.assembly_files 的 [content] 字段以查找是否存在 public 关键字节。这比扫描证书更直接,但它只允许特定系列的字节在程序集的某处进行验证,并不意味着程序集以任何方式用这些字节作为 public 密钥签名.

所以也许更有保证的方法是从程序集创建证书(即CREATE CERTIFICATE [tmp] FROM ASSEMBLY...),使用CERTENCODED()获取完整证书,然后扫描public关键子串。每次通过静态 class 构造函数加载 class 时执行一次。当然,这意味着您需要与数据库建立常规/外部连接,这意味着程序集至少需要 EXTERNAL_ACCESS。并且您需要确保该过程具有创建证书的权限。并且,您应该将 CREATE CERTIFICATE 包装在 TRY...CATCH 构造中,因为它可能已经存在(第一次之后,它要么存在,要么您每次都需要删除证书,但这可能只会使初始验证过程需要更长的时间,所以我想您可以将其留在数据库中)。

不确定完整的程序集字节是否像版本和其他一些东西一样可以通过反射获得。可能值得研究。

此外,请考虑支持我刚刚提交的将 public_key 字段添加到 sys.certificates 的建议:

Expose public_key of Certificate in sys.certificates, just like sys.asymmetric_keys is doing