如何使用错误的证书执行https请求?


128

说我想以https://golang.org编程方式获取。当前,golang.org(ssl)的证书不正确,*.appspot.com因此我将其签发给So:

package main

import (
    "log"
    "net/http"
)

func main() {
    _, err := http.Get("https://golang.org/")
    if err != nil {
        log.Fatal(err)
    }
}

我得到了(如我所料)

Get https://golang.org/: certificate is valid for *.appspot.com, *.*.appspot.com, appspot.com, not golang.org

现在,我想自己信任该证书(想象一个可以颁发指纹的自我发行的证书,等等):如何提出请求并验证/信任该证书?

我可能需要使用openssl下载证书,将其加载到我的文件中并填写tls.Configstruct!?。


5
这不是“不良证书”,而是具有不同CN的证书。InsecureSkipVerify在这里不是合法用途。您必须在tls.Config中设置ServerName以匹配您尝试连接的内容。这个StackOverflow帖子导致Go代码中的这个大安全漏洞传播到各处。InsecureSkipVerify根本不检查证书。您要验证的是证书是否由受信任实体合法签名,即使CN与主机名不匹配也是如此。隧道和NATS可以合理地导致此不匹配。
罗布(Rob)

Answers:


283

安全说明:禁用安全检查是危险的,应避免

您可以全局禁用默认客户端的所有请求的安全检查:

package main

import (
    "fmt"
    "net/http"
    "crypto/tls"
)

func main() {
    http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
    _, err := http.Get("https://golang.org/")
    if err != nil {
        fmt.Println(err)
    }
}

您可以禁用客户端的安全检查:

package main

import (
    "fmt"
    "net/http"
    "crypto/tls"
)

func main() {
    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{Transport: tr}
    _, err := client.Get("https://golang.org/")
    if err != nil {
        fmt.Println(err)
    }
}

17
我不知道在哪里放置受信任的证书,以便可以不使用连接InsecureSkipVerify: true。那可能吗?
topskip 2012年

7
NameToCertificate可能有帮助,请参阅tls.Config文档:golang.org/pkg/crypto/tls/#Config
cyberdelia

1
以下是添加自定义CA证书池的示例:golang.org/pkg/crypto/tls/#example_Dial 您也可以在HTTP客户端中使用它。
Bitbored 2014年

7
许多人最终试图禁用主机名检查(而不是完全禁用证书检查)。这是错误的答案。Go要求您在tls配置中设置ServerName以匹配您要连接的主机的CN,如果它不是您所连接的dns名称。InsecureSkipVerify的安全性不比端口普通的telnet安全。此设置没有身份验证。用户ServerName代替!
罗布

8
当心:这样创建的传输对其大部分字段使用零值,因此会丢失所有默认值。如下面的答案所示,您可能需要将它们复制过来。弄清楚为什么我们的文件描述符用尽了,我们感到很有趣,因为我们丢失了Dialer.Timeout
mknecht

26

这是一种实现方法,而不会丢失的默认设置DefaultTransport,并且不需要根据用户评论提供虚假请求。

defaultTransport := http.DefaultTransport.(*http.Transport)

// Create new Transport that ignores self-signed SSL
customTransport := &http.Transport{
  Proxy:                 defaultTransport.Proxy,
  DialContext:           defaultTransport.DialContext,
  MaxIdleConns:          defaultTransport.MaxIdleConns,
  IdleConnTimeout:       defaultTransport.IdleConnTimeout,
  ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
  TLSHandshakeTimeout:   defaultTransport.TLSHandshakeTimeout,
  TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
}

更新

较短的方法:

customTransport := &(*http.DefaultTransport.(*http.Transport)) // make shallow copy
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}

正确的方式(从Go 1.13开始)(由下面答案提供):

customTransport := http.DefaultTransport.(*http.Transport).Clone()
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}

警告:仅用于测试/开发目的。否则,后果自负!!!


仅使用复制默认传输设置mytransportsettings := &(*http.DefaultTransport.(*http.Transport)),然后修改TLS客户端配置,会不会更容易mytransportsettings.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
TheDiveO

值得一试,我想我想确保自己不修改真正的DefaultTransport
Jonathan Lin

1
这样可以确保像您一样制作浅表副本,对于所讨论的用例而言,这就足够了。我实际上是在工作代码中部署它。
TheDiveO

19

所有这些答案都是错误的!请勿InsecureSkipVerify用于处理与主机名不匹配的CN。Go开发人员明智地坚持不禁用主机名检查(它具有合法用途-隧道,nat,共享群集证书等),同时具有看起来相似但实际上完全忽略证书检查的功能。您需要知道证书是有效的,并且由您信任的证书签名。但是在常见情况下,您知道CN与您连接的主机名不匹配。对于这些,请设置ServerNametls.Config。如果tls.Config.ServerName== remoteServerCN,则证书检查将成功。这就是你想要的。 InsecureSkipVerify表示没有身份验证;中间人已经成熟了;达不到使用TLS的目的。

有一个合法用途InsecureSkipVerify:使用它连接到主机并获取其证书,然后立即断开连接。如果您设置要使用的代码InsecureSkipVerify,通常是因为设置不ServerName正确(它需要来自env var之类的东西-不要为这个要求而烦恼……正确地做它)。

特别是,如果您使用客户端证书并依靠它们进行身份验证,则您基本上是伪造的登录名,实际上不再登录。拒绝这样做的代码InsecureSkipVerify,否则您将很难通过它学习出问题所在!


1
您是否知道我可以测试该网站?golang org现在不再抛出错误。
topskip

3
这个答案极具误导性。如果您接受任何有效签名的证书(无论主机名如何),那么您仍将无法获得真正的安全性。我可以轻松获得我控制的任何域的有效证书;如果仅打算为TLS检查指定我的主机名,那么您将失去对我确实是我所说的人的验证。此时,证书为“合法”这一事实无关紧要;这仍然是错误的,并且您仍然容易受到中间人的攻击。
Daniel Farrell

5
在您实际上不信任任何商业CA(即,例如,如果它们在美国以外)并为您的企业替换为一个CA的企业中,您只需要验证这是您的证书之一即可。主机名检查用于用户期望主机名匹配但DNS不安全的情况。在企业中,通常会连接到主机名/ IP不可能匹配的机器集群,因为这是在新IP下生成的机器的精确克隆。dns名称找到证书,cert是id,而不是dns名称。
罗布

3
想法是客户端代码仅信任企业CA。它实际上比当前使用的CA系统更加安全。在这种情况下,任何东西(Thawte,Versign等)都不受信任。只是我们运行的CA。Web浏览器具有巨大的信任列表。彼此通话的服务在其信任文件中只有一个CA。
罗布

1
谢谢@Rob我有一个相同的设置,其中我们的服务仅信任相同的单个CA,而没有其他信任。这是至关重要的信息,也有很大帮助。
user99999991

8

现在,如果要保留默认的传输设置,则执行此操作的正确方法是(从Go 1.13开始):

customTransport := http.DefaultTransport.(*http.Transport).Clone()
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
client = &http.Client{Transport: customTransport}

Transport.Clone复制该传输的深层副本。这样,您不必担心Transport随着时间的推移会丢失添加到结构中的任何新字段。


7

如果要使用http包中的默认设置,则不需要创建新的Transport and Client对象,可以更改为忽略证书验证,如下所示:

tr := http.DefaultTransport.(*http.Transport)
tr.TLSClientConfig.InsecureSkipVerify = true

7
这样做会导致panic: runtime error: invalid memory address or nil pointer dereference
OscarRyz

6
如果您在此之前未使用默认的http请求程序,则必须强制执行伪造的请求,以便对默认传输进行初始化
山茱萸达米安

1
这不会禁用主机名检查。它禁用所有证书检查。Go强制您在连接证书中将ServerName设置为与CN相等。InsecureSkipVerify将连接到假冒的MITM服务器,该服务器假装转发到真实服务。InsecureSkipVerify的唯一合法用途是获取远程端的证书并立即断开连接。
罗布(Rob)

仅当您还指定一个VerifyPeerCertificate时,这才可以。如果仅设置InsecureSkipVerify,则根本不会进行检查。但是添加了一个VerifyPeerCertificate,以便您可以重写检查以执行所需的操作。您可能要忽略主机名,甚至可能忽略到期日期。Google提供了执行此操作的VerifyPeerCertificate的各种实现。
抢夺

0

通常,URL的DNS域必须与证书的证书主题匹配。

在过去,这可以通过将域设置为证书的cn或通过将域设置为“使用者备用名称”来实现。

很久以来(在RFC 2818中,自2000年开始)就不再支持cn ,Chrome浏览器甚至不再查看cn,因此今天您需要将URLDNS域用作使用者替代名称。

RFC 6125禁止检查cn(如果存在用于DNS域的SAN),但不检查cn(如果存在用于IP地址的SAN)。RFC 6125还重复了cn 28,这已经在RFC 2818中说过了。不存在证书颁发机构浏览器论坛,该论坛与RFC 6125结合意味着从不检查cn的DNS域名。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.