证书和公钥固定

证书和公钥固定是实现证书和公钥固定的技术指南,如弗吉尼亚章节的介绍在移动空间中保护无线信道所述。本指南的重点是提供清晰,简单,可操作的指导,以便在恶劣的环境中保护渠道,在这种环境中,参与者可能是恶意的,信任会议是一种责任。

Pinning Cheat Sheet提供备忘

介绍

安全渠道是远程和移动工作的用户和员工的基石。用户和开发人员在发送和接收数据时需要端到端的安全性 - 特别是受VPN,SSL或TLS保护的信道上的敏感数据。虽然控制DNS和CA的组织可能会将风险降低到大多数威胁模型下的微不足道的水平,但用户和开发人员屈服于其他DNS和公共CA层次结构会面临非常微不足道的风险。事实上,历史已经表明那些依赖外部服务的人在其安全渠道中遭受了长期违规。

大流行滥用信任导致用户,开发人员和应用程序就不受信任的输入做出安全相关的决策。这种情况有点悖论:DNS和CA等实体是受信任的,应该提供可靠的输入; 但他们的输入不可信任。依赖不安全的输入来进行与安全相关的决策不仅是不良业力,而且还违反了许多安全编码原则(例如,参见OWASP的注入理论数据验证)。

固定有效地消除了“信任会议”。在做出与对等方身份相关的安全决策时,固定证书或公钥的应用程序不再需要依赖其他人(例如DNS或CA)。对于熟悉SSH的人,您应该意识到公钥锁定几乎与SSH的StrictHostKeyChecking选项完全相同。SSH一直都是正确的,世界其他地方开始意识到通过公钥直接识别主机或服务的优点。

积极参与销售活动的其他人包括Google及其浏览器Chrome。Chrome成功地发现了DigiNotar的妥协,发现了伊朗政府对其公民的可疑拦截。最初的妥协报告可以在这个MITM攻击Gmail的SSL中找到?; 谷歌安全公司立即回应了关于中间人攻击的最新消息

有什么问题?

用户,开发人员和应用程序期望在其安全通道上实现端到端安全性,但某些安全通道无法满足预期。具体而言,使用众所周知的协议(如VPN,SSL和TLS)构建的通道可能容易受到大量攻击。

本文的讨论选项卡中列出了过去失败的示例。这个备忘单不会试图对行业中的故障进行编目,调查脚手架中的设计缺陷,证明缺乏对提供商的责任或责任,解释服务中的底层竞争,或揭开例如之间的勾结的神秘面纱。 ,浏览器和CA. 如需更多阅读,请访问PKI已破损互联网已损坏

病人0

最初的问题是密钥分配问题。可以通过加密将不安全通信转换为安全通信问题。加密通信可以通过签名转换为身份问题。身份问题终止于密钥分发问题。他们是同样的问题。

治愈

密钥分发问题有三种方法。首先是掌握您的合作伙伴或同伴(即同伴,服务器或服务)的第一手资料。这可以通过SneakerNet解决。遗憾的是,SneakerNet无法扩展,也无法用于解决密钥分发问题。

第二种是依赖其他人,它有两种变体:(1)信任网,(2)信任等级。Web of Trust和Trust of Trust在无菌环境中解决密钥分发问题。但是,信任网和信任层次都要求我们依赖他人 - 或者给予信任。在实践中,信任他人显示出有问题。

什么是固定?

固定是将主机与其预期的 X509证书或公钥相关联的过程。一旦知道或看到主机的证书或公钥,证书或公钥就会与主机关联或“固定”。如果可以接受多个证书或公钥,则该程序将保留一个pinset(取自Jon Larimer和Kenny Root Google I / O对话)。在这种情况下,广告标识必须与pinset中的一个元素匹配。

主机或服务的证书或公钥可以在开发时添加到应用程序,也可以在首次遇到证书或公钥时添加。前者 - 在开发时添加 - 是首选,因为在带外预加载证书或公钥通常意味着攻击者无法污染引脚。如果在首次遇到时添加了证书或公钥,您将使用密钥连续性。如果攻击者在第一次遭遇期间具有特权位置,则密钥连续性可能会失败。

固定利用对用户与组织或服务之间预先存在的关系的了解,以帮助做出更好的安全相关决策。因为您已经拥有服务器或服务的信息,所以您不需要依赖用于解决密钥分发问题的通用机制。也就是说,您无需为名称/地址映射或CA提供绑定和状态的DNS。一个例外是撤销,下面将在Pinning Gaps中进行讨论。

值得一提的是,Pinning不是Stapling。Stapling在同一请求中发送证书和OCSP响应者信息,以避免客户端在路径验证期间应执行的额外提取。

什么时候你别?

您应该在任何时候想要相对确定远程主机的身份或在恶劣环境中操作时固定。由于其中一个或两个几乎总是正确的,所以你应该总是固定。

一个完美的例子:在准备演示和备忘单两周左右的时间里,我们观察到三个相关和相关的失败。首先是诺基亚/ Opera故意打破安全通道 ; 第二个是DigiCert发布​​恶意软件代码签名证书 ; 第三是Bit9丢失其根签名密钥。环境不仅充满敌意,而且有毒。

什么时候你白名单?

如果您正在为一个实施“出口过滤”的组织工作,作为数据丢失防护(DLP)策略的一部分,您可能会遇到拦截代理。我喜欢把这些东西称为“好”坏人(而不是“坏”坏人),因为它们都破坏了端到端的安全性,我们无法区分它们。在这种情况下,不要将拦截代理列入白名单,因为它会破坏您的安全目标。在风险接受人员的指示下,将拦截代理的公钥添加到您的pinset 。

注意:如果将其他主机的证书或公钥列入白名单(例如,为了容纳拦截代理),则不再固定主机的预期证书和密钥。渠道的安全性和完整性可能会受到影响,并且肯定会打破用户和组织的端到端安全期望。

有关拦截代理的更多阅读,它们赋予的额外风险以及它们如何失败,请参阅Matthew Green博士的拦截代理如何失败?和Jeff Jarmoc的BlackHat谈论SSL / TLS拦截代理和传递信任

怎么针?

我们的想法是重用现有的协议和基础设施,但要以强化的方式使用它们。为了重复使用,程序将继续执行建立安全连接时所做的事情。

为了强化通道,程序将利用库,框架或平台提供的OnConnect回调。在回调中,程序将通过验证其证书或公钥来验证远程主机的身份。虽然固定不必在OnConnect回调中发生,但它通常最方便,因为底层连接信息随时可用。

什么应该固定?

首先要决定的是应该固定什么。对于这个选择,您有两种选择:您可以(1)固定证书; 或(2)固定公钥。如果选择公钥,则还有两个选择:(a)固定subjectPublicKeyInfo ; 或(b)固定其中一种具体类型,例如RSAPublicKeyDSAPublicKey

下面将更详细地解释这三种选择。我鼓励你固定subjectPublicKeyInfo,因为它有公共参数(例如RSA公钥的{e,n}上下文信息,如算法和OID。上下文将帮助您有时保持轴承,下面的图1显示了可用的附加信息。

编码/格式

出于本文的目的,对象采用X509兼容的表示格式(PKCS#1遵循X509,两者都使用ASN.1)。如果您有PEM编码对象(例如,----- BEGIN CERTIFICATE ---------- END CERTIFICATE -----),则将对象转换为DER编码。下面以格式转换提供了使用OpenSSL的转换

证书是通过签名将实体(例如个人或组织)绑定到公钥的对象。证书是DER编码的,并且具有关联的数据或属性,例如主题(被识别或绑定),发行者(签名者),有效性NotBeforeNotAfter)以及公钥

证书具有subjectPublicKeyInfo。subjectPublicKeyInfo是具有附加信息的密钥。ASN.1类型包括算法ID版本和用于保存具体公钥的可扩展格式。下面的图1和图2示出了相同RSA密钥的不同视图,其是subjectPublicKeyInfo。关键是网站random.org,它用于下面的示例程序和列表中。

图1:使用dumpans1转储的subjectPublicKeyInfo

图2:十六进制编辑器下的subjectPublicKeyInfo

具体公钥是编码的公钥。密钥格式通常在其他地方指定 - 例如,在RSA公钥的情况下,PKCS#1。在RSA公钥的情况下,类型是RSAPublicKey,参数{e,n}将是ASN.1编码的。上面的图1和2清楚地显示了模量(线28处的n)和指数(线289处的e)。对于DSA,具体类型是DSAPublicKey,ASN.1编码的参数是{p,q,g,y}

最后的要点:(1)证书将实体与公钥绑定; (2)证书有subjectPublicKeyInfo; (3)subjectPublicKeyInfo具有具体的公钥。对于那些想要了解更多信息的人,可以在Code Project的文章Cryptographic Interoperability:Keys中找到程序员角度的更深入的讨论。

证书

证书

证书最容易固定。您可以从网站上获取带外证书,让IT人员通过电子邮件向您发送公司证书,使用openssl s_client检索证书等。证书过期后,您将更新您的申请。假设您的应用程序没有错误或安全缺陷,应用程序将每年或每两年更新一次。在运行时,您将在回调中检索网站或服务器的证书。在回调中,您将检索到的证书与程序中嵌入的证书进行比较。如果比较失败,则方法或功能失败。

钉住证书有一个缺点。如果站点定期轮换其证书,则需要定期更新您的应用程序。例如,Google会轮换其证书,因此您需要每月更新一次应用程序(如果它依赖于Google服务)。即使Google轮换其证书,底层公钥(在证书中)仍然是静态的。

公钥

公钥

由于从证书中提取公钥所需的额外步骤,公钥锁定更灵活,但有点棘手。与证书一样,程序使用其公钥的嵌入副本检查提取的公钥。两个公钥固定有两个缺点。首先,由于您通常必须从证书中提取密钥,因此更难使用密钥(而不是证书)。在Java和.Net中,提取是一个小小的不便,但它在Cocoa / CocoaTouch和OpenSSL中很不舒服。其次,密钥是静态的,可能违反密钥轮换策略。

哈希

虽然上面的三个选择使用DER编码,但也可以使用信息的散列(或其他变换)。事实上,原始的示例程序是使用消化的证书和公钥编写的。更改样本以允许程序员使用dumpasn1和其他ASN.1解码器等工具检查对象。

哈希还提供了三个额外的好处。首先,哈希允许您匿名化证书或公钥。如果您的应用程序担心在反编译和重新设计期间泄漏信息,这可能很重要。

其次,消化的证书指纹通常可用作许多库的本机API,因此使用起来非常方便。

最后,组织可能希望在主要身份被泄露的情况下提供保留(或备份)身份。散列可确保您的对手在使用之前不会看到保留的证书或公钥。实际上,谷歌的IETF草案websec-key-pinning使用了这种技术。

钉扎的例子

本节演示Android Java,iOS,.Net和OpenSSL中的证书和公钥固定。

HTTP固定

RFC 7469引入了一个新的HTTP标头,允许SSL服务器使用不应更改这些证书的时间范围声明其证书的哈希值。例如:

Public-Key-Pins:max-age = 2592000;
销-SHA256 = “E9CZ9INDbd + 2eRQozYqqbQ2yXLVKB9 + xcprMF + 44U1g =”;
销-SHA256 = “LPJNul + wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ =”;
报告-URI = “http://example.com/pkp-report”

请注意,RFC 7469存在争议,因为它允许覆盖本地安装的权限。也就是说,它允许成功捕获用户的对手或其他方使用非真实或欺诈性信息覆盖已知良好的pinset。其次,报告机制被抑制了破坏的pinset,因此一个合规的用户代理将在事后掩盖。也就是说,断开的pinset的报告被称为必须不报告[1 ]。

Android的

自Android N以来,实现固定的首选方法是利用Android的网络安全配置功能,该功能允许应用程序在安全的声明性配置文件中自定义其网络安全设置,而无需修改应用程序代码。

要启用固定,可以使用`<pin-set>`配置设置

如果需要支持运行早于N的Android版本的设备,则可以通过https://github.com/datatheorem/TrustKit-Android上的TrustKit Android库获得网络安全配置固定功能的后退端口。

最后,Android文档提供了一个示例,说明如何在未知CA实施文档中的应用程序代码中自定义SSL验证(以实现固定)。但是,应该避免从头开始实施固定验证,因为实施错误极有可能并且通常会导致严重的漏洞。

iOS版

TrustKit是一个用于iOS和macOS的开源SSL固定库,可从https://github.com/datatheorem/TrustKit获得。它提供了一个易于使用的API来实现固定,并已部署在许多应用程序中。

否则,有关如何在iOS上自定义SSL验证(以实现固定)的更多详细信息,请参阅https://developer.apple.com/library/content/technotes/tn2232上的“HTTPS服务器信任评估”技术说明。/_index.html。但是,应该避免从头开始实施固定验证,因为实施错误极有可能并且通常会导致严重的漏洞。

。净

.Net固定可以通过使用ServicePointManager来实现,如下所示。

下载:.Net示例程序

//编码RSAPublicKey
private static String PUB_KEY =“30818902818100C4A06B7B52F8D17DC1CCB47362”+
    “C64AB799AAE19E245A7559E9CEEC7D8AA4DF07CB0B21FDFD763C63A313A668FE9D764E”+
    “D913C51A676788DB62AF624F422C2F112C1316922AA5D37823CD9F43D1FC54513D14B2”+
    “9E36991F08A042C42EAAEEE5FE8E2CB10167174A359CEBF6FACC2C9CA933AD403137EE”+
    “2C3F4CBED9460129C72B0203010001”;

public static void Main(string [] args)
{
  ServicePointManager.ServerCertificateValidationCallback = PinPublicKey;
  WebRequest wr = WebRequest.Create(“https://encrypted.google.com/”);
  wr.GetResponse();
}

public static bool PinPublicKey(对象发送者,X509Certificate证书,X509Chain链,
                                SslPolicyErrors sslPolicyErrors)
{
  if(null == certificate)
    返回false;

  String pk = certificate.GetPublicKeyString();
  if(pk.Equals(PUB_KEY))
    返回true;

  // 坏狗
  返回false;
}

OpenSSL的

使用OpenSSL可以在两个地方之一进行固定。首先是用户提供的verify_callback。第二个是通过SSL_get_peer_certificate建立连接之后。这两种方法都允许您访问对等方的证书。

虽然OpenSSL执行X509检查,但您必须使连接失败并在出错时拆除套接字。根据设计,不提供证书的服务器将导致X509_V_OK具有NULL证书。要检查习惯验证的结果:(1)您必须调用SSL_get_verify_result并验证返回码是X509_V_OK; (2)您必须调用SSL_get_peer_certificate并验证证书是否为非NULL

下载:OpenSSL示例程序

int pkp_pin_peer_pubkey(SSL * ssl)
{
    if(NULL == ssl)返回FALSE;
    
    X509 * cert = NULL;
    FILE * fp = NULL;
    
    /* 刮 */
    int len1 = 0,len2 = 0;
    unsigned char * buff1 = NULL,* buff2 = NULL;
    
    / *结果返回给调用者* /
    int ret = 0,result = FALSE;
    
    {
        / * http://www.openssl.org/docs/ssl/SSL_get_peer_certificate.html * /
        cert = SSL_get_peer_certificate(ssl);
        if(!(cert!= NULL))
            打破; / *失败* /
        
        / *开始回放以获得subjectPublicKeyInfo * /
        / *感谢Viktor Dukhovni在OpenSSL邮件列表上* /
        
        / * http://groups.google.com/group/mailing.openssl.users/browse_thread/thread/d61858dae102c6c7 * /
        len1 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert),NULL);
        if(!(len1> 0))
            打破; / *失败* /
        
        /* 刮 */
        unsigned char * temp = NULL;
        
        / * http://www.openssl.org/docs/crypto/buffer.html * /
        buff1 = temp = OPENSSL_malloc(len1);
        if(!(buff1!= NULL))
            打破; / *失败* /
        
        / * http://www.openssl.org/docs/crypto/d2i_X509.html * /
        len2 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert),&temp);

        / *这些检查验证我们恢复了与调整缓冲区大小时相同的值。* /
        / *它非常弱,因为它们应该始终相同。但它给了我们一些测试。* /
        if(!((len1 == len2)&&(temp!= NULL)&&((temp  -  buff1)== len1)))
            打破; / *失败* /
        
        / *结束回转* /
        
        / *见上面的警告!!! * /
        / * http://pubs.opengroup.org/onlinepubs/009696699/functions/fopen.html * /
        fp = fopen(“random-org.der”,“rx”);
        if(NULL == fp){
            fp = fopen(“random-org.der”,“r”);
        
        if(!(NULL!= fp))
            打破; / *失败* /
        
        / *寻求eof来确定文件的大小* /
        / * http://pubs.opengroup.org/onlinepubs/009696699/functions/fseek.html * /
        ret = fseek(fp,0,SEEK_END);
        if(!(0 == ret))
            打破; / *失败* /
        
        / *获取文件的大小* /
        / * http://pubs.opengroup.org/onlinepubs/009696699/functions/ftell.html * /
        long size = ftell(fp);

        / *任意大小,但应相对较小(小于1K或2K)* /
        if(!(size!= -1 && size> 0 && size <2048))
            打破; / *失败* /
        
        / *快退到开始执行读取* /
        / * http://pubs.opengroup.org/onlinepubs/009696699/functions/fseek.html * /
        ret = fseek(fp,0,SEEK_SET);
        if(!(0 == ret))
            打破; / *失败* /
        
        / *重用buff2和len2 * /
        buff2 = NULL; len2 =(int)size;
        
        / * http://www.openssl.org/docs/crypto/buffer.html * /
        buff2 = OPENSSL_malloc(len2);
        if(!(buff2!= NULL))
            打破; / *失败* /
        
        / * http://pubs.opengroup.org/onlinepubs/009696699/functions/fread.html * /
        / *返回读取的元素数,应为1 * /
        ret =(int)fread(buff2,(size_t)len2,1,fp);
        if(!(ret == 1))
            打破; / *失败* /
        
        / *重复使用大小。MIN和MAX宏低于...... * /
        size = len1 <len2?len1:len2;
        
        / ******** /
        /***** 财源 *****/
        / ******** /
        if(len1!=(int)size || len2!=(int)size || 0!= memcmp(buff1,buff2,(size_t)size))
            打破; / *失败* /
        
        / *一个好的退出点* /
        result = TRUE;
        
    } while(0);
    
    if(fp!= NULL)
        FCLOSE(FP);
    
    / * http://www.openssl.org/docs/crypto/buffer.html * /
    if(NULL!= buff2)
        OPENSSL_free(buff2);
    
    / * http://www.openssl.org/docs/crypto/buffer.html * /
    if(NULL!= buff1)
        OPENSSL_free(BUFF1);
    
    / * http://www.openssl.org/docs/crypto/X509_new.html * /
    if(NULL!= cert)
        X509_free(CERT);
    
    返回结果;
}

本节介绍了与固定相关的administrivia和其他项目。

短暂的钥匙

临时密钥是用于协议执行的一个实例的临时密钥,然后被丢弃。短暂密钥具有提供前向保密的好处,这意味着站点或服务的长期(静态)签名密钥的折衷不利于解密过去的消息,因为密钥是临时的并且被丢弃(一旦会话终止)。

临时密钥不会影响固定,因为Ephemeral密钥是在单独的ServerKeyExchange消息中传递的。此外,临时密钥是密钥而不是证书,因此它不会更改证书链的构造。也就是说,感兴趣的证书仍然位于证书[0]

固定差距

由于重用现有的基础设施和协议,在固定时存在两个空白。首先,程序不会根据服务器的公共信息向对等服务器发送明确的质询。因此程序永远不知道对等体是否可以实际解密消息。然而,由于对手将收到无法解密的消息,因此缺点通常在实践中是学术性的。

第二是撤销。客户端通常不会进行吊销检查,因此可以在pinset中使用已知的错误证书或密钥。即使撤销处于活动状态,证书撤销列表(CRL)和在线证书状态协议(OCSP)也可以在恶意环境中失败。应用程序可以采取措施进行修复,主要方法是新鲜度。也就是说,应用程序应在关键安全性参数更改时立即更新和分发。

没关系^ @ $!

如果您没有预先存在的关系,那么一切都不会丢失。首先,您可以在第一次遇到主机或服务器的证书或公钥时固定它们。如果遇到证书或公钥时坏人没有活动,他或她将无法成功应对未来有趣的业务。

其次,由于ChromiumCertificate Patrol等项目以及EFF的SSL天文台等计划,在现场更快地发现了不良证书。

第三,帮助正在进行中,有许多未来将有助于这些努力:

虽然Sovereign Keys和Convergence仍然要求我们向外界提供信托,但有关各方不会为股东提供服务或贪图收入流。他们的兴趣是行业透明度和用户安全性。

更多信息?

钉扎是一种旧的新东西,已被震动,搅拌和重新包装。虽然“pinning”和“pinsets”对于旧事物来说是相对较新的术语,但Jon Larimer和Kenny Root在Google I / O 2012上花了很多时间来讨论Android应用中的安全和隐私问题

格式转换

为方便读者,以下使用OpenSSL在PEM和DER格式之间进行转换。

#公钥,X509
$ openssl genrsa -out rsa-openssl.pem 3072
$ openssl rsa -in rsa-openssl.pem -pubout -outform DER -out rsa-openssl.der
#私钥,PKCS#8
$ openssl genrsa -out rsa-openssl.pem 3072
$ openssl pkcs8 -nocrypt -in rsa-openssl.pem -inform PEM -topk8 -outform DER -out rsa-openssl.der

参考

作者和主要编辑

  • Jeffrey Walton - jeffrey,owasp.org
  • 约翰史蒂文 - 约翰,owasp.org
  • Jim Manico - jim,owasp.org
  • 凯文沃尔 - 凯文,owasp.org
  • Ricardo Iramar - ricardo.iramar,owasp.org