【问题标题】:Verifying a signature chain SWI-Prolog验证签名链 SWI-Prolog
【发布时间】:2017-06-12 19:21:43
【问题描述】:

此问题与Opening and checking a Pem file in 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).

目前失败。

【问题讨论】:

  • 这就是你所拥有的,但它做什么或不做什么是问题所在?
  • 证书“链”只包含一个证书!这应该是自签名的吗?否则,发行者的公钥是什么?
  • 我尝试使用演示 pem 文件。我找不到包含证书链的。我已将问题更新为我认为当前呈现链的 URL,但据我了解,该 URL 可能会在未来某个时间点移动或不呈现链!

标签: ssl prolog swi-prolog pem


【解决方案1】:

预赛

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

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

重要的是,证书 Ck 是使用与 Ck+ 的公钥对应的私钥签名的1.

因此,您的代码的问题之一是您错误地使用 C 的公钥来验证 C 的签名,即使证书是使用对应于 不同的私钥签名的证书。

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

具体例子

为了使这个答案自成一体,我在此处发布了您的用例中的相关数据,即在撰写本文时文件包含的证书的相关属性。

数据

第一个证书

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

签名( “7B86A6E7A86B192579380108B7EADA1C25E288AB46120117DC6A80635324C89713C70206EF3FE13CCA5EDFF43601972CB96658826ADCD68B0FB3AE7F5607D036D6AE8AF824CD96D7F1B4DB58E714343031F292B17E6EEF83872A0FD586CC0D1DA85A677E4AB4C6540E9132B5BC5644533E0388F830B1B6757B7DB88AB82846F08B3B6DCEC7F24319AB7EA56F86592DDDEC4522CAF331C8B81A4E543FBDBF4D661B534BAE546465DB88A525BC82A7B4127F0AFF4A55525927A66A09055743F109E30D90CA074D258166F0E472CB7CCDB0747ADE74F7040CFEEB9A78C3483864C5106D542556C874AF768005A6EC83ADEB2EE32F8E6F7182A362775C2BF40AFA20”)。 to_be_signed(“30820466A00302010202103F25CAAEE5AA838FA91C051A75FC719D300D06092A864886F70D01010B0500307E310B3009060355040613025553311D301B060355040A131453796D616E74656320436F72706F726174696F6E311F301D060355040B131653796D616E746563205472757374204E6574776F726B312F302D0603550403132653796D616E74656320436C61737320332053656375726520536572766572204341202D204734301E170D3136313030373030303030305A170D3137313033303233353935395A306D310B30090603550406130255533113301106035504080C0A57617368696E67746F6E3110300E06035504070C0753656174746C6531193017060355040A0C10416D617A6F6E2E636F6D2C20496E632E311C301A06035504030C136563686F2D6170692E616D617A6F6E2E636F6D30820122300D06092A864886F70D01010105000382010F003082010A02820101009CAFB306BB910354E76E0406C44F9BE178934D9C83906C09EB4EBC006B1742682DF655610BC0934C2E30751E4D5E8B1BA15EBFEB7C28AD008DA38D7672C0558D4CB71F5FD512CFB92AFF80880394B8AA017C453CCC0BC709CEC698E29480D89D703034312A71DD94CC48619B91F68B8A44739DEEA7159EA334E9E4A93B460FA4AB0886202CD02B49C6283F321C5C4CA91C5AE8 827CEB47811ED1871E7C66724BCD58A9EBFF9658B4D5D02046FA6702DCBAF2B6139B190D8735121BA2086C51C8C5724A0044C090688A25C819A5F1B6D4E9390E4DF21AB11263F203E4E9F1BCCC625D29D7C21B7C243E9B775E6E8B4B4F0DAF390748E964B968A9065EDBAA11A30203010001A382020730820203301E0603551D110417301582136563686F2D6170692E616D617A6F6E2E636F6D30090603551D1304023000300E0603551D0F0101FF0404030205A0301D0603551D250416301406082B0601050507030106082B0601050507030230610603551D20045A30583056060667810C010202304C302306082B06010505070201161768747470733A2F2F642E73796D63622E636F6D2F637073302506082B0601050507020230190C1768747470733A2F2F642E73796D63622E636F6D2F727061301F0603551D230418301680145F60CF619055DF8443148A602AB2F57AF44318EF302B0603551D1F042430223020A01EA01C861A687474703A2F2F73732E73796D63622E636F6D2F73732E63726C305706082B06010505070101044B3049301F06082B060105050730018613687474703A2F2F73732E73796D63642E636F6D302606082B06010505073002861A687474703A2F2F73732E73796D63622E636F6D2F73732E637274300F06032B654D0408300602010102010130818B060A 2B06010401D679020402047D047B0079007700A7CE4A4E6207E0ADDEE5FDAA4B1F86768767B5D002A55D47310E7E670A95EAB2000001579FAA404A0000040300483046022100CA6B7C069C140B774E8D64ED95D4A59E92349E04AA3C71B304F32C74BC857F81022100FA152CF8558BA0A87B515AB7522D1BE1725740E128042309DD274FEEA5EB3B62" )。

第二个证书

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

键(PUBLIC_KEY(RSA( “B2D805CA1C742DB5175639C54A520996E84BD80CF1689F9A422862C3A530537E5511825B037A0D2FE17904C9B496771981019459F9BCF77A9927822DB783DD5A277FB2037A9C5325E9481F464FC89D29F8BE7956F6F7FDD93A68DA8B4B82334112C3C83CCCD6967A84211A22040327178B1C6861930F0E5180331DB4B5CEEB7ED062ACEEB37B0174EF6935EBCAD53DA9EE9798CA8DAA440E25994A1596A4CE6D02541F2A6A26E2063A6348ACB44CD1759350FF132FD6DAE1C618F59FC9255DF3003ADE264DB42909CD0F3D236F164A8116FBF28310C3B8D6D855323DF1BD0FBD8C52954A16977A522163752F16F9C466BEF5B509D8FF2700CD447C6F4B3FB0F7”, “010001”, - , - , - , - , - , - )))。

验证

计算哈希

正如我所说,hash 是实际签名的内容。重要的是,我不是指整个证书的哈希值,而是 to-be-signed 部分的哈希值。这种差异很重要,因为整个证书的哈希也包含签名,而签名在证书签署时当然还不可用。

在 SWI-Prolog 中,我们可以使用 library(crypto) 获取 to-be-signed 部分的哈希:

?- to_be_signed(TBS), hex_bytes(TBS,字节), crypto_data_hash(字节,哈希,[算法(sha256),编码(八位字节)])。 TBS = "3082...EB3B62", 字节 = [48, 130, 4, 102, 160, 3, 2, 1, 2|...], 哈希 = '651bdcdd90251f71a47a5d1bbc6f28486c94d2dc3739dcd58ecb09b3f224ee05'。

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

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

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

? - X#= 0x7B86A6E7A86B192579380108B7EADA1C25E288AB46120117DC6A80635324C89713C70206EF3FE13CCA5EDFF43601972CB96658826ADCD68B0FB3AE7F5607D036D6AE8AF824CD96D7F1B4DB58E714343031F292B17E6EEF83872A0FD586CC0D1DA85A677E4AB4C6540E9132B5BC5644533E0388F830B1B6757B7DB88AB82846F08B3B6DCEC7F24319AB7EA56F86592DDDEC4522CAF331C8B81A4E543FBDBF4D661B534BAE546465DB88A525BC82A7B4127F0AFF4A55525927A66A09055743F109E30D90CA074D258166F0E472CB7CCDB0747ADE74F7040CFEEB9A78C3483864C5106D542556C874AF768005A6EC83ADEB2EE32F8E6F7182A362775C2BF40AFA20 ^ 0x010001 国防部0xB2D805CA1C742DB5175639C54A520996E84BD80CF1689F9A422862C3A530537E5511825B037A0D2FE17904C9B496771981019459F9BCF77A9927822DB783DD5A277FB2037A9C5325E9481F464FC89D29F8BE7956F6F7FDD93A68DA8B4B82334112C3C83CCCD6967A84211A22040327178B1C6861930F0E5180331DB4B5CEEB7ED062ACEEB37B0174EF6935EBCAD53DA9EE9798CA8DAA440E25994A1596A4CE6D02541F2A6A26E2063A6348ACB44CD1759350FF132FD6DAE1C618F59FC9255DF3003ADE264DB42909CD0F3D236F164A8116FBF28310C3B8D6D855323DF1BD0FBD8C52954A16977A522163752F16F9C466BEF5B509D8FF2700CD447C6F4B3FB0F7。

它产生:

X = 986236757547332986472011617696226561292849812918563355472727826767720188564083584387121625107510786855734801053524719833194566624465665316622563244215340671405971599343902468620306327831715457360719532421388780770165778156818229863337344187575566725786793391480600129482653072861971002459947277805295727097226389568776499707662505334062639449916265137796823793276300221537201727072401742985542559596685092673521228140822200236743113743661549252453726123450722876929538747702356573783116197523966334991563351853851212597377279504828784716104866621888265058037501385433453379649364782998949981722124880992983641605。

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

您现在可能会说:“好吧,我真的不需要(#=)/2,我总是可以使用几十年前学到的(is)/2。” 但是,如果您在此类示例中使用(is)/2,您很容易得到效率降低数千倍的代码。作为一个简单的基准,考虑谓词:

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

现在我们有了查询:

? - 时间(signature_pow(0x7B86A6E7A86B192579380108B7EADA1C25E288AB46120117DC6A80635324C89713C70206EF3FE13CCA5EDFF43601972CB96658826ADCD68B0FB3AE7F5607D036D6AE8AF824CD96D7F1B4DB58E714343031F292B17E6EEF83872A0FD586CC0D1DA85A677E4AB4C6540E9132B5BC5644533E0388F830B1B6757B7DB88AB82846F08B3B6DCEC7F24319AB7EA56F86592DDDEC4522CAF331C8B81A4E543FBDBF4D661B534BAE546465DB88A525BC82A7B4127F0AFF4A55525927A66A09055743F109E30D90CA074D258166F0E472CB7CCDB0747ADE74F7040CFEEB9A78C3483864C5106D542556C874AF768005A6EC83ADEB2EE32F8E6F7182A362775C2BF40AFA20,0x010001,0xB2D805CA1C742DB5175639C54A520996E84BD80CF1689F9A422862C3A530537E5511825B037A0D2FE17904C9B496771981019459F9BCF77A9927822DB783DD5A277FB2037A9C5325E9481F464FC89D29F8BE7956F6F7FDD93A68DA8B4B82334112C3C83CCCD6967A84211A22040327178B1C6861930F0E5180331DB4B5CEEB7ED062ACEEB37B0174EF6935EBCAD53DA9EE9798CA8DAA440E25994A1596A4CE6D02541F2A6A26E2063A6348ACB44CD1759350FF132FD6DAE1C618F59FC9255DF3003ADE264DB42909CD0F3D236F164A8116FBF28310C3B8D6D855323DF1BD0FBD8C 52954A16977A522163752F16F9C466BEF5B509D8FF2700CD447C6F4B3FB0F7,战俘))。

时间安排:

% 16 次推理,0.000 CPU 在 0.000 秒内(99% CPU,130624 唇)

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

% 3 次推理,1.847 CPU 在 1.852 秒内(100% CPU,2 唇)

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

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


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

?- 格式(“~16r”,[$X])。 1fffffff...fff003031300d060960864801650304020105000420651bdcdd90251f71a47a5d1bbc6f28486c94d2dc3739dcd58ecb09b3f224ee05

这听起来很熟悉:最后,您会看到证书待签名部分的散列。这意味着签名签出!

rsa_verify/4验证签名

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

这是完整的查询:

?- to_be_signed(TBS), hex_bytes(TBS,字节), crypto_data_hash(字节,哈希,[算法(sha256),编码(八位字节)]), 签名(Sig), 键(键), rsa_verify(密钥,哈希,信号,[类型(sha256)])。

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

结束语

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

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

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

【讨论】:

  • 这些"B2D805CA1..." 术语是什么?
  • 这些是十六进制编码的整数。请删除引号并添加 0x 以直接在 Prolog 查询和程序中使用它们。例如:?- X = 0xB2D805CA1.,以 10 为底产生:X = 48008027297
  • 感谢您的详细解答。现在开始有所收获!
猜你喜欢
  • 1970-01-01
  • 2012-03-10
  • 2016-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多