Thunderbird,RNP和一个良好API的重要性

2021-05-06 23:43:03

我最近与雷鸟开发商关于API设计交谈。在该谈话的过程中,我对RNP表示担忧,新的OpenPGP实现最近在雷霆代替Gnupg。那个人对我的Assertionthat RNP的API需要改进,要求“它不是主观的Adver API是什么?”我同意我们还没有良好的指标,以向API介绍。但是,我不同意我们根本无法判断API。 Infact,我怀疑,最经验丰富的程序员知道一个坏API Whenthey看到它。此外,我认为我们可以提出一些优秀学习,我会根据我的经验和努力工作,查询,红杉,红杉和rnp做好努力。然后,我会看一下RNP的API.unantunctince,RNP的API不仅易于误用,而且它是不应该以安全-CressionContext使用的。然而,雷伯德被弱势群体依赖于脆弱的人,即活动家,律师,他们的通信合作伙伴那么多的这种保护。对我来说,这意味着Thunderbird应该向他们使用RNP的决定。

注意:另请参阅此相关邮件,让我们使用GPL Librariesin Thunderbird!,我发送到Thunderbird的邮件列表。

在开始与贾斯特和凯的SemoIa项目开始之前,我们三个美国在Gnupg一起工作。除了在GPG上攻击GPG之外,Wealso还与许多GPG的下游用户进行了交谈并合作。人们对GNUPG有很多好事。

单击缩略图将从YouTube加载内容。对我们的两次批评GPG的API突出了。第一个批评被蒸馏至:GPG的API是一种太为意思。福林,GPG具有钥匙圈的方法。这意味着如果它已被称为键入键入,则可以使用或检查OpenPGP证书。但一些开发人员只想在审查它后进口acercificate。例如,当通过指纹查找关键服务器上的USP证书时,可以检查返回的证书是正确的,因为URL ISSelf验证。可以用GPG执行此操作,但围绕GPG的编程模型进行了速度。基本思想是下面的:创建一个临时目录,添加一个配置文件,告诉GPG使用备用目录,导入CercoritateThere,检查证书,并清理临时目录。这是根据我们的委任添加的官方建议使用GPG的下游用户。是的,它有效。但是,TheapProach需要操作系统特定的代码,慢慢和错误。

另一个批评我反复听到的是,使用GPGRequires很多术语知识,以避免滥用它。或者,换过来,在使用GPG的Apito不无意中引入漏洞时,人们必须非常小心。

为了更好地理解第二个问题,请考虑Efailvulnersabilities。基本问题是GPG的解密API:在解密消息时,即使inputhas已损坏,GPG也会发出明文。 GPG在这种情况下返回错误,但是某些应用程序无论如何都显示损坏的明文。因为,为什么不显示消息的一部分比没有什么,对吧?好吧,eFail漏洞展示了攻击者如何将Web错误插入加密的消息,并且当监视器观看消息时,Web错误exfiltrates消息。哎哟。

那么,谁对这个错误负责? Gnupg开发人员认为,应用程序使用GPG错误:

建议Muas考虑解密_failedStatus代码,而不是显示数据或至少使用正确的方式来播放可能的损坏邮件,而无需创建邮件是疑似的用户和致其致力于用户。

GPG发出错误;申请不粘附在Apicontract上。我必须同意Gnupg开发人员,并添加:GPG'Sinterface是(并且仍然存在)灾难等待发生的灾难,因为它不是指导用户做正确的事情。相反,自然,看似有助于的事情是错误的事情。并且,这种类型的API在GNUPG中常见。

这两个实现 - 即GPG的API是过于称性的,而且对我来说是使用右的右转。当我们开始这些项目时,我们同意我们想避免制作司法额。基于这些观察,我们采用了两次试验,即Wecontinue用于指导SemoIa的API的发展。首先,除了任何高级API之外,应该有一个低级API,这在意义上未致电,即它不会阻止userfrom在做任何合法的情况下。同时,API应该指导用户通过使正确的事情变得容易,明显的事情来做出右(自夸)的事情。

为了实现这两种,略微相互冲突的终止,但防止错误,我们靠在两种工具上,以外:类型和示例。通过在Compiletime的API合同中正式化,甚至强迫特定转换,难以使用Objectin的难以使用Objectin。而且,示例 - 代码片段 - 将被复制。所以,良好的例子将仅教导用户如何正确使用功能,但是它们如何使用它。

我想展示我们如何在SequoIa中使用类型来帮助usmake一个好的API。要了解该示例,关于OpenPGP的一点背景知识是有用的。

OpenPGP中有几种基本数据类型。三是:证书,键和用户ID等组件,以及绑定。证书的根是主键,它完全确定了证书的指纹(指纹=散列(主键))。证书通常包括子项和用户ID等组件。 OpenPGP使用ASO绑定签名将组件绑定到证书。使指纹只是主键的散列,并使用签名将组件绑定到临时键意味着可以添加其他ComponentSlater。绑定签名还包括属性。这使得更改组件,例如,延长子项的到期。这是一个与给定组件有多个有效的签名。绑定签名不仅是常规的,而且是OpenPGP安全性的一个组成部分。

因为可以有多个有效的绑定签名,所以我们需要一种选择正确的绑定。作为第一个近似,Rovernignature是最新,非过期,非撤销的有效签名,其未来未创建。但是什么是有效的签名?在SemoIa中,签名不仅需要经常检查,它需要与政策一致。福林,由于其受损的碰撞抵抗,我们只允许SHA-1在一个非常有限的情况下。 (PGPAINLESS工作的Paul Schaub最近写了关于这些复杂性的细节。)强迫API的用户来保持所有这些问题邀请漏洞。在SemoIa中,获得折放时间的简单方法是安全的方式。考虑这段代码,这是它的东西:

让P =& standardpolicy :: new();让cert = cert :: from_str(cert)? ;用于证书。用_policy(p,none)? 。键()。 subkeys(){println! (" key {}:expiry:{}",k。如果让某些(t)= k。key_expiration_time(){datetime ::> :: from(t )。to_rfc3339()} else {"从不"。进入()}); }

证书是证书。我们首先向它应用策略。(策略是用户可定义的,但通常是StandardPolicy Isnot只有足够的,但最合适。)这有效地创建了证书的视图,其中仅可见具有有效绑定属性的组件。重要的是,它还修改并揭示了新方法的数量。例如,键方法已经过编码以返回有效的相响应而不是密钥结果。 (这是一个合并,因为它包括一个关键,还包括任何相关的签名;一些Peopletorect Katamari将是一个更好的名字。¯_(ツ)_ /¯)有效的怀喇叭根据Theabove标准具有有效的绑定签名。而且,它公开了像key_expiration_time这样的方法,只在有效键上才有意义!还注意:key_expiration_time的返回类型是符合人体工程学的。 key_expiration_time返回SystemTime,而不是return return,而是安全且易于使用。

符合我们第一个启用一切的原则,adeveloper仍然可以访问各个签名和考试子包,以从outsingbinding签名中获取密钥的到期时间。但是,与正确的方法相比,使用Sequoia的API获得钥匙的xpiry时间,他们必须超出方式不同的方式。在我们看来,这是一个很好的API。

我们在2020年12月发布了Semenoia图书馆的v1.0.在此之前,我们的功能完整并准备发布。但是,我们等待。我们花了以下九个月为公共API添加了编织和例子。看看证书数据结构的题目,以查看结果的示例。如博客帖子中所述,我们没有完全管理全部函数的一个例子,但我们确实得到了很远。作为写作示例的副作用,我们确定了我们抛光的斑点。

自发布以来,我们已与许多开发人员联系,这些开发人员将集成的SemoIa集成到其代码中。常见的reldainfulfulfulful文件是文档和示例。而且,我们可以确认:事件是我们自己的代码,我们几乎每天都引用文档,并复制自己的示例。这更容易。而且,由于示意图显示了如何正确使用该函数,因此为什么重做工作刮擦?

RNP是一种主要由核糖开发的OpenPGP实施。大约两年前,雷鸟决定将enigmail集成到雷鸟中,同时替换Gnupg withrnp。雷鸟已经选择了RNP不仅是RNP的认可,而且意味着RNP也可能成为邮件加密的最常用的OpenPGPimplementation。

批评可以很容易地解释为消极。我想明确的是,我认为罗丝正在做的工作是Goodand的重要性,我很感谢他们正在投资时间和资源进入一个新的OpenPGP实施。 OpenPGP EcosystemDesperally需要更多的多样性。但是,这不是useah在安全关键背景下的未成熟产品的借口。

不幸的是,RNP尚未在我认为它不能部署的地方。 eNigmail不仅被人们担心隐私,而且由记者,活动家和律师们担任安全和他们的沟通保镖的安全。在2017年,在没有边境的记者的Theasia-Pacific Officific Office of Theasia-Pacific Office的采访中,他说:

我们主要使用GPG与我们的来源自由沟通。他们向我们提供关于人权和违规行为的信息,而且违规是敏感的信息,并且有必要保护他们的谈话。

因此,即使在这个过渡期间,雷伯德继续向Specest体验提供最安全的体验。

在谈论我们如何在SemoIa中使用类型来使其更加难以使API造成的API,我展示了如何在几个代码中获得密钥的到期时间。我想首先展示一个不是AnopenPGP或RNP专家的人如何实现使用RNP的相同功能。以下代码迭代证书(键)子标记打印每个子键的到期时间。召回:存储在子项的绑定签名上的到期时,值为0意味着键不会过期。

INT I; for(i = 0; i< sk_count; i ++){rnp_key_handle_t sk; err = rnp_key_get_subkey_at(key,i,& sk); if(err){printf(" rnp_key_get_subkey_at(%d):%x \ n",我,错误);返回1; uint32_t expiration_time; err = rnp_key_get_expiration(sk,& expiration_time); if(err){printf("#%d(%s)。rnp_key_get_expiration:%x \ n",i + 1,desc [i],错误); } else {printf("#%d(%d i],expiration_time); }}

我对具有五个子键的证书测试了此代码。 FirstSubkey具有有效的绑定签名,而且不会过期;第二章是有效的绑定签名,并在将来到期;第三章是有效的绑定签名,已经过期;第四个哈桑无效的绑定签名,这表明子键将未来过期;而且,第五个根本没有绑定签名。该输出:

#1(未到期)到期键和#39;■创建时间后0秒到期。#2(过期)将键和#39;创建时间后94670781秒过期。#3(已过期)key&#后86400秒到期39; s创建时间。#4(无效SIG)键后到期0秒钟' s创建时间后0秒。#5(No SIG)键后0秒左右' s创建时间。

首先要注意到对RNP_KEY_GET_EXPIASSCECESCRIONSCESCHIONS的呼叫是否具有有效的绑定签名,具有一个不存在的绑定签名,甚至没有绑定签名atall!阅读文档,这种行为有点令人惊讶。说:

由于关键的到期时间存储在绑定签名上,我是一个OpenPGP专家,理解这是表示对RNP_KEY_GET_EXPIATION的调用只会在子项具有validBinding签名时成功。相反,如果没有validBinding签名,则该功能默认为0,哪个Giventhe注意,API的用户可以合理地解释为键未到期的意义。

为了改进此代码,有必要先检查键盘是否是有效的绑定签名。有些函数要执行此操作,以解决RNP以解决CVE-2021-23991。特别是,ThernP开发人员添加了函数RNP_KEY_IS_VALID来返回密钥有效。此添加是一个改进,但它将开发人员加入到这些安全关键检查,注意事项,因为如果他们使用SemoIa,他们会因为它们而选择。由于安全检查是非功能,因此它们很容易忘记:即使忘记了安全检查,代码也可以工作。自从知道考虑到以来需要专家知识,他们将被遗忘。

以下代码包括安全检查并跳过RNP_KEY_IS_VALID认为无效的键:

INT I; for(i = 0; i< sk_count; i ++){rnp_key_handle_t sk; err = rnp_key_get_subkey_at(key,i,& sk); if(err){printf(" rnp_key_get_subkey_at(%d):%x \ n",我,错误);返回1; bool is_valid = false; err = rnp_key_is_valid(sk,& is_valid); if(err){printf(" rnp_key_is_valid:%x \ n",错误);返回1; }如果(!is_valid){printf("#%d(%d(%s)无效,跳过。\ n",i + 1,desc [i]);继续 ; uint32_t expiration_time; err = rnp_key_get_expiration(sk,& expiration_time); if(err){printf("#%d(%s)。rnp_key_get_expiration:%x \ n",i + 1,desc [i],错误); } else {printf("#%d(%d i],expiration_time); }}

#1(未到期)将键和#39;创建时间后0秒过期。#2(过期)在键和#39;创建时间后到期94670781秒。#3(已过期)无效,跳跃。# 4(无效的SIG)无效,跳过。#5(没有SIG)无效,跳过。

代码正确地跳过了没有有效绑定的两个键,但它也会跳过过期的密钥,尽管文档确实警告我们这一功能“检查......到期时间”,但这可能是不想要的。

虽然有些情况下我们不想使用一个钥匙函数如果它过期,但有时我们会这样做。例如,如果Auser忘记扩展子键的到期时间,则它们应该是ableto看到子项在检查证书时已过期,并且能够扩展到期。虽然GPG - List-keys不太通知键,但在编辑证书时,它确实显示了SubKeySthat已过期,以便用户可以扩展其到期:

$ gpg --edit-key 93d3a2b8df67ce4b674999b807a5d85899b807a5d8589f2492f9secret键可用。42589f2492f9创建了:2021-04-26到期:2024-04-26使用率:c trust:未知有效性:未知有效性:未知有效性:未知有效性:未知有效性:创建:2021-04-27 Expliedssssssssssssssb ED25519 / 1E2F512A0FE99515 :从不使用:SSB CV25519 / 8CDDC2BC5EEB61A3创建:2021-04-26过期:2024-04-26使用:E SSB ED25519 / 142D550E6E6DF02E已创建:2021-04-26已过期:2021-04-27使用:S [未知] (1)。 Alice< [email protected]& gt;

还有其他情况,未经临时的密钥不对无效。例如,让我们说Alice发送Bob A SignedMessage:“我将在一年内支付100欧元,”和六个月签名键盘。当年结束时,爱丽丝是否在签名的基础上鲍勃·鲍勃主酮?我会说是的。在制作时签名是无效的。钥匙已过期的事实是无关紧要的。当然,一旦密钥过期,展开后的签名应该被视为无效。同样,不应该用过期的密钥加密消息。

简而言之,是否应该被视为有效的关键是高度依赖的上下文。 RNP_KEY_IS_VALID总比没有好,但是,尽管它名称,但通常对一般确定密钥有效而不是充分的分别。

同样的提交引入了第二个函数,rnp_key_valid_till.this函数返回“时间戳,直到键可以像有效的那样有效......如果密钥从未有效,则零值窗口[返回]。”我们可以使用此功能来确定AKEY是否曾经有效地通过检查此功能是否返回ANON-ZERE值:

INT I; for(i = 0; i< sk_count; i ++){rnp_key_handle_t sk; err = rnp_key_get_subkey_at(key,i,& sk); if(err){printf(" rnp_key_get_subkey_at(%d):%x \ n",我,错误);返回1; uint32_t有效; err = rnp_key_valid_till(sk,& valid_till); if(err){printf(" rnp_key_valid_till:%x \ n",错误);返回1; printf("#%d(%d(%d if(valid_till == 0){printf("无效,跳过。\ n");继续 ; uint32_t expiration_time; err = rnp_key_get_expiration(sk,& expiration_time); if(err){printf(" rnp_key_get_expiration:%x \ n",错误); } else {printf(" expires%" priu32" key之后的秒数' s创作时间。\ n",expiration_time); }}

#1(未到期)在时代后有效期至1714111110秒;在键和#39;创建时间后到期0秒。#2(到期)在时代后有效期到1714111110秒;在Key' s创建时间后到期94670781秒。#3(已过期)epoch后直到1619527593秒;在键和#39的创建时间后到期86400秒。#4(无效SIG)在epoch之后有效期为0秒;无效,跳过。#5(NO SIG)在时代后触及0秒;无效,跳过。

现在我们得到了我们想要的结果! 我们正确地打印了前三个子键的折叠时间,并指示Thelast两个子键无效。 但是,让我们仔细看看RNP_KEY_VALID_TILL。 首先,INOPENPGP,密钥的到期时间被存储为从密钥的无符号32位创建时间存储为无符号32位OFFET。 因此,函数应该有 ......