验证签名链 SWI-Prolog

Verifying a signature chain SWI-Prolog

此问题与

相关

下载并打开证书后,我该如何验证签名链? 我有:

:-use_module(library(http/http_client)).

url('https://s3.amazonaws.com/echo.api/echo-api-cert-4.pem').

url_data1(Url,Certs):-
 http_open(Url,Stream,[]),
 all_certs(Stream,Certs),
 forall(member(C,Certs),my_validate(C)),
 close(Stream).

all_certs(Stream,[C1|Certs]):-
 catch(load_certificate(Stream,C1),_,fail),
 all_certs(Stream,Certs),!.
 all_certs(_Stream,[]).

my_validate(C):-
 memberchk(to_be_signed(Signed),C),
 memberchk(key(Key),C),
 memberchk(signature(Signature),C),
 memberchk(signature_algorithm(A),C),
 algo_code(A,Code),
 rsa_verify(Key,Signed,Signature,[type(Code)]).

algo_code('RSA-SHA256',sha256).
algo_code('RSA-SHA1',sha1).

这目前失败了。

预赛

使用 Prolog 验证数字签名和整个证书链非常容易。

但是,您需要对证书的签名方式有基本的了解。 证书链是一系列证书 C0, C1, ..., CN。我正在使用 CN 来表示 root 证书。根据使用的约定,您可以mutatis mutandis当然可以颠倒顺序。

重要的是,证书Ck是使用私钥签署的,对应于public密钥 of Ck+1.

因此,您的代码存在的一个问题是您错误地使用了 C 的 public 密钥来验证 C 的签名,即使证书是使用对应于 不同 证书。

另一个问题源于对什么 正在签名的一些混淆。我们正在对证书的 待签名 部分的 hash 进行签名,而不是数据本身。因此,我们必须根据该哈希值验证签名。

具体例子

为了使这个答案独立,我 post 在这里使用了您的用例的相关数据,即在撰写本文时文件包含的证书的相关属性。

数据

第一张证书

从链中的第一个证书开始,我们需要 signatureto-be-signed 部分,它们是:

signature("7B86A6E7A86B192579380108B7EADA1C25E288AB46120117DC6A80635324C89713C70206EF3FE13CCA5EDFF43601972CB96658826ADCD68B0FB3AE7F5607D036D6AE8AF824CD96D7F1B4DB58E714343031F292B17E6EEF83872A0FD586CC0D1DA85A677E4AB4C6540E9132B5BC5644533E0388F830B1B6757B7DB88AB82846F08B3B6DCEC7F24319AB7EA56F86592DDDEC4522CAF331C8B81A4E543FBDBF4D661B534BAE546465DB88A525BC82A7B4127F0AFF4A55525927A66A09055743F109E30D90CA074D258166F0E472CB7CCDB0747ADE74F7040CFEEB9A78C3483864C5106D542556C874AF768005A6EC83ADEB2EE32F8E6F7182A362775C2BF40AFA20").
to_be_signed("30820466A00302010202103F25CAAEE5AA838FA91C051A75FC719D300D06092A864886F70D01010B0500307E310B3009060355040613025553311D301B060355040A131453796D616E74656320436F72706F726174696F6E311F301D060355040B131653796D616E746563205472757374204E6574776F726B312F302D0603550403132653796D616E74656320436C61737320332053656375726520536572766572204341202D204734301E170D3136313030373030303030305A170D3137313033303233353935395A306D310B30090603550406130255533113301106035504080C0A57617368696E67746F6E3110300E06035504070C0753656174746C6531193017060355040A0C10416D617A6F6E2E636F6D2C20496E632E311C301A06035504030C136563686F2D6170692E616D617A6F6E2E636F6D30820122300D06092A864886F70D01010105000382010F003082010A02820101009CAFB306BB910354E76E0406C44F9BE178934D9C83906C09EB4EBC006B1742682DF655610BC0934C2E30751E4D5E8B1BA15EBFEB7C28AD008DA38D7672C0558D4CB71F5FD512CFB92AFF80880394B8AA017C453CCC0BC709CEC698E29480D89D703034312A71DD94CC48619B91F68B8A44739DEEA7159EA334E9E4A93B460FA4AB0886202CD02B49C6283F321C5C4CA91C5AE8827CEB47811ED1871E7C66724BCD58A9EBFF9658B4D5D02046FA6702DCBAF2B6139B190D8735121BA2086C51C8C5724A0044C090688A25C819A5F1B6D4E9390E4DF21AB11263F203E4E9F1BCCC625D29D7C21B7C243E9B775E6E8B4B4F0DAF390748E964B968A9065EDBAA11A30203010001A382020730820203301E0603551D110417301582136563686F2D6170692E616D617A6F6E2E636F6D30090603551D1304023000300E0603551D0F0101FF0404030205A0301D0603551D250416301406082B0601050507030106082B0601050507030230610603551D20045A30583056060667810C010202304C302306082B06010505070201161768747470733A2F2F642E73796D63622E636F6D2F637073302506082B0601050507020230190C1768747470733A2F2F642E73796D63622E636F6D2F727061301F0603551D230418301680145F60CF619055DF8443148A602AB2F57AF44318EF302B0603551D1F042430223020A01EA01C861A687474703A2F2F73732E73796D63622E636F6D2F73732E63726C305706082B06010505070101044B3049301F06082B060105050730018613687474703A2F2F73732E73796D63642E636F6D302606082B06010505073002861A687474703A2F2F73732E73796D63622E636F6D2F73732E637274300F06032B654D0408300602010102010130818B060A2B06010401D679020402047D047B0079007700A7CE4A4E6207E0ADDEE5FDAA4B1F86768767B5D002A55D47310E7E670A95EAB2000001579FAA404A0000040300483046022100CA6B7C069C140B774E8D64ED95D4A59E92349E04AA3C71B304F32C74BC857F81022100FA152CF8558BA0A87B515AB7522D1BE1725740E128042309DD274FEEA5EB3B62").

第二个证书

从第二个证书开始,我们只需要 public 密钥 来验证为先前证书颁发的签名:

key(public_key(rsa("B2D805CA1C742DB5175639C54A520996E84BD80CF1689F9A422862C3A530537E5511825B037A0D2FE17904C9B496771981019459F9BCF77A9927822DB783DD5A277FB2037A9C5325E9481F464FC89D29F8BE7956F6F7FDD93A68DA8B4B82334112C3C83CCCD6967A84211A22040327178B1C6861930F0E5180331DB4B5CEEB7ED062ACEEB37B0174EF6935EBCAD53DA9EE9798CA8DAA440E25994A1596A4CE6D02541F2A6A26E2063A6348ACB44CD1759350FF132FD6DAE1C618F59FC9255DF3003ADE264DB42909CD0F3D236F164A8116FBF28310C3B8D6D855323DF1BD0FBD8C52954A16977A522163752F16F9C466BEF5B509D8FF2700CD447C6F4B3FB0F7", "010001", -, -, -, -, -, -))).

验证

计算哈希值

正如我所说,散列 是实际签名的内容。重要的是,我指的不是整个证书的散列,而是 to-be-signed 部分的散列。这个区别很重要,因为整个证书的散列也包含签名,当然在签名证书时还不可用。

在 SWI-Prolog 中,我们可以使用 library(crypto):

获得 to-be-signed 部分的散列
?- to_be_signed(TBS),
   hex_bytes(TBS, Bytes),
   crypto_data_hash(Bytes, Hash, [algorithm(sha256), encoding(octet)]).
TBS = "3082...EB3B62",
Bytes = [48, 130, 4, 102, 160, 3, 2, 1, 2|...],
Hash = '651bdcdd90251f71a47a5d1bbc6f28486c94d2dc3739dcd58ecb09b3f224ee05'.

我正在使用 sha256,因为第一个证书在其 signature_algorithm/1 字段中指示(RSA 和)SHA256

使用 CLP(FD) 约束验证签名

验证 RSA 签名的最简单方法之一是使用 CLP(FD) 约束。我们只需要计算 SigExp mod p。我们插入我们的具体数字,使用 (#=)/2 计算 整数的算术表达式:

?- X #= 0x7B86A6E7A86B192579380108B7EADA1C25E288AB46120117DC6A80635324C89713C70206EF3FE13CCA5EDFF43601972CB96658826ADCD68B0FB3AE7F5607D036D6AE8AF824CD96D7F1B4DB58E714343031F292B17E6EEF83872A0FD586CC0D1DA85A677E4AB4C6540E9132B5BC5644533E0388F830B1B6757B7DB88AB82846F08B3B6DCEC7F24319AB7EA56F86592DDDEC4522CAF331C8B81A4E543FBDBF4D661B534BAE546465DB88A525BC82A7B4127F0AFF4A55525927A66A09055743F109E30D90CA074D258166F0E472CB7CCDB0747ADE74F7040CFEEB9A78C3483864C5106D542556C874AF768005A6EC83ADEB2EE32F8E6F7182A362775C2BF40AFA20^0x010001
        mod 0xB2D805CA1C742DB5175639C54A520996E84BD80CF1689F9A422862C3A530537E5511825B037A0D2FE17904C9B496771981019459F9BCF77A9927822DB783DD5A277FB2037A9C5325E9481F464FC89D29F8BE7956F6F7FDD93A68DA8B4B82334112C3C83CCCD6967A84211A22040327178B1C6861930F0E5180331DB4B5CEEB7ED062ACEEB37B0174EF6935EBCAD53DA9EE9798CA8DAA440E25994A1596A4CE6D02541F2A6A26E2063A6348ACB44CD1759350FF132FD6DAE1C618F59FC9255DF3003ADE264DB42909CD0F3D236F164A8116FBF28310C3B8D6D855323DF1BD0FBD8C52954A16977A522163752F16F9C466BEF5B509D8FF2700CD447C6F4B3FB0F7.

它产生:

X = 986236757547332986472011617696226561292849812918563355472727826767720188564083584387121625107510786855734801053524719833194566624465665316622563244215340671405971599343902468620306327831715457360719532421388780770165778156818229863337344187575566725786793391480600129482653072861971002459947277805295727097226389568776499707662505334062639449916265137796823793276300221537201727072401742985542559596685092673521228140822200236743113743661549252453726123450722876929538747702356573783116197523966334991563351853851212597377279504828784716104866621888265058037501385433453379649364782998949981722124880992983641605.

游览:关于CLP(FD)的效率和使用。

你现在可以说:"Well, I don't really need (#=)/2, I can always use (is)/2 which I learned decades ago." 但是,如果你在这样的例子中使用 (is)/2,你很容易得到这样的代码 效率低了数千倍。作为一个简单的基准,考虑谓词:

signature_pow(Sig, Exp, P, Pow) :-
        Pow #= Sig^Exp mod P.

现在我们有,查询:

?- time(signature_pow(0x7B86A6E7A86B192579380108B7EADA1C25E288AB46120117DC6A80635324C89713C70206EF3FE13CCA5EDFF43601972CB96658826ADCD68B0FB3AE7F5607D036D6AE8AF824CD96D7F1B4DB58E714343031F292B17E6EEF83872A0FD586CC0D1DA85A677E4AB4C6540E9132B5BC5644533E0388F830B1B6757B7DB88AB82846F08B3B6DCEC7F24319AB7EA56F86592DDDEC4522CAF331C8B81A4E543FBDBF4D661B534BAE546465DB88A525BC82A7B4127F0AFF4A55525927A66A09055743F109E30D90CA074D258166F0E472CB7CCDB0747ADE74F7040CFEEB9A78C3483864C5106D542556C874AF768005A6EC83ADEB2EE32F8E6F7182A362775C2BF40AFA20, 0x010001, 0xB2D805CA1C742DB5175639C54A520996E84BD80CF1689F9A422862C3A530537E5511825B037A0D2FE17904C9B496771981019459F9BCF77A9927822DB783DD5A277FB2037A9C5325E9481F464FC89D29F8BE7956F6F7FDD93A68DA8B4B82334112C3C83CCCD6967A84211A22040327178B1C6861930F0E5180331DB4B5CEEB7ED062ACEEB37B0174EF6935EBCAD53DA9EE9798CA8DAA440E25994A1596A4CE6D02541F2A6A26E2063A6348ACB44CD1759350FF132FD6DAE1C618F59FC9255DF3003ADE264DB42909CD0F3D236F164A8116FBF28310C3B8D6D855323DF1BD0FBD8C52954A16977A522163752F16F9C466BEF5B509D8FF2700CD447C6F4B3FB0F7, Pow)).

时间:

% 16 inferences, 0.000 CPU in 0.000 seconds (99% CPU, 130624 Lips)

相比之下,如果我们在 Prolog 语言开发中回归并将 (#=)/2 替换为 (is)/2,我们得到:

% 3 inferences, 1.847 CPU in 1.852 seconds (100% CPU, 2 Lips)

原因:在 SWI-Prolog 中,某些涉及 (#=)/2 自动 的目标使用专门的算术谓词。您无需学习这些谓词即可使用它们。 CLP(FD) 为您代劳

建议:在 Prolog 中使用 CLP(FD) 约束对 整数 进行推理。它们通常使您的谓词更通用,有时 大大 更有效。


那么 X 呢?要查看它是什么,请考虑其 十六进制 编码:

?- format("~16r", [$X]).
 1fffffff...fff003031300d060960864801650304020105000420651bdcdd90251f71a47a5d1bbc6f28486c94d2dc3739dcd58ecb09b3f224ee05

这听起来很熟悉:最后,您会看到证书的 to-be-signed 部分的 hash 出现了.这意味着签名已签出!

验证签名 rsa_verify/4

或者,我们可以使用 library(crypto) 中的 rsa_verify/4 来验证签名。

这是完整的查询:

?- to_be_signed(TBS),
   hex_bytes(TBS, Bytes),
   crypto_data_hash(Bytes, Hash, [algorithm(sha256), encoding(octet)]),
   signature(Sig),
   key(Key),
   rsa_verify(Key, Hash, Sig, [type(sha256)]).

由于此成功,我们知道 私钥 对应于 Key 用于生成签名。

结束语

我有一个重要的评论:通常,这当然是全部完全没有必要

SWI-Prolog SSL 基础结构自动 验证证书链,因此每次您使用 http_open/3 和相关谓词通过 TLS 建立连接时,都会验证所有签名。但是自己进行这些计算很有趣。有时甚至是必要的,如果,如本例所示,您正在对存储在某处的证书进行推理。

补充一点:请在您的代码中使用 setup_call_cleanup/3。否则,如果在 close/1 之前出现任何问题,您就有可能泄露文件描述符,实际上在您的示例中也是如此。