Como fazer uma solicitação https com certificado inválido?

128

Digamos que eu queira ser https://golang.orgprogramaticamente. Atualmente, o golang.org (ssl) possui um certificado inválido emitido para o *.appspot.comSo quando executo isso:

package main

import (
    "log"
    "net/http"
)

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

Eu recebo (como eu esperava)

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

Agora, eu mesmo quero confiar neste certificado (imagine um certificado auto-emitido onde eu possa validar impressões digitais etc.): como posso fazer uma solicitação e validar / confiar no certificado?

Eu provavelmente preciso usar o openssl para baixar o certificado, carregá-lo no meu arquivo e preencher tls.Configstruct!?

topskip
fonte
5
este não é um "certificado inválido", é um certificado com uma CN diferente. InsecureSkipVerify não é um uso legítimo aqui. Você deve definir o ServerName no tls.Config para corresponder ao que você está tentando conectar. Esta postagem do StackOverflow está fazendo com que essa grande falha de segurança no código Go se espalhe por toda parte. O InsecureSkipVerify não verifica o certificado. O que você deseja é verificar se o certificado foi legitimamente assinado por uma entidade confiável, mesmo se o CN não corresponder ao nome do host. Túneis e NATS podem legitimamente causar incompatibilidade.
Rob

Respostas:

283

Nota de segurança: Desabilitar as verificações de segurança é perigoso e deve ser evitado

Você pode desativar as verificações de segurança globalmente para todas as solicitações do cliente padrão:

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)
    }
}

Você pode desativar a verificação de segurança para um cliente:

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)
    }
}
cyberdelia
fonte
17
Gostaria de saber onde colocar um certificado confiável para que a conexão possa ser usada sem InsecureSkipVerify: true. Isso é possível?
topskip
7
NameToCertificatepode ajudar, ver a tls.Configdocumentação: golang.org/pkg/crypto/tls/#Config
Cyberdelia
1
Aqui está um exemplo para adicionar pools de certificados CA personalizados: golang.org/pkg/crypto/tls/#example_Dial Você também pode usar isso em um cliente HTTP.
Bitbored
7
Muitas pessoas acabam aqui tentando desativar as verificações de nome de host (não desativando totalmente as verificações de certificado). Esta é a resposta errada. Go exige que você defina o ServerName na configuração tls para corresponder ao CN do host ao qual você está se conectando, se não for o nome do DNS ao qual você se conectou. O InsecureSkipVerify não é mais seguro do que um telnet antigo simples para a porta. Não há autenticação com essa configuração. Usuário ServerName!
Rob
8
Cuidado: um transporte criado como esse usa valores zero para a maioria de seus campos e, portanto, perde todos os padrões . Como a resposta abaixo sugere, você pode copiá-los. Nós nos divertimos muito descobrindo por que estamos ficando sem descritores de arquivo , porque perdemos o arquivoDialer.Timeout .
Mknecht # 30/17
26

Aqui está uma maneira de fazê-lo sem perder as configurações padrão do DefaultTransporte sem precisar da solicitação falsa conforme o comentário do usuário.

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},
}

ATUALIZAR

Maneira mais curta:

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

Maneira correta (a partir do Go 1.13) (fornecida pela resposta abaixo ):

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

Aviso : Apenas para fins de teste / desenvolvimento. Qualquer outra coisa, proceda por sua conta e risco !!!

Jonathan Lin
fonte
não seria mais fácil simplesmente copiar as configurações de transporte padrão usando mytransportsettings := &(*http.DefaultTransport.(*http.Transport))e apenas modificar a configuração do cliente TLS mytransportsettings.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}?
TheDiveO 28/01
Vale a pena tentar, acho que estava tentando ter certeza de não modificar o verdadeiro DefaultTransport
Jonathan Lin
1
isso garante uma cópia superficial, como você fez, o que é suficiente para o caso de uso discutido. Na verdade, estou implantando isso no código de trabalho.
TheDiveO 29/01
19

Todas essas respostas estão erradas! Não use InsecureSkipVerifypara lidar com uma CN que não corresponda ao nome do host. Os desenvolvedores do Go imprudentemente foram inflexíveis quanto a não desativar as verificações de nome de host (que têm usos legítimos - túneis, nats, certificados de cluster compartilhado etc.), além de terem algo parecido, mas na verdade ignoram completamente a verificação de certificado. Você precisa saber que o certificado é válido e assinado por um certificado confiável. Mas em cenários comuns, você sabe que o CN não corresponderá ao nome do host ao qual você se conectou. Para aqueles, definido ServerNameno tls.Config. Se tls.Config.ServerName== remoteServerCN, a verificação do certificado será bem-sucedida. Isso é o que você quer. InsecureSkipVerifysignifica que não há autenticação; e está maduro para um homem-no-meio; derrotando o propósito de usar TLS.

Há um uso legítimo para InsecureSkipVerify: use-o para conectar-se a um host e obter seu certificado e, em seguida, desconecte-o imediatamente. Se você configurou seu código para uso InsecureSkipVerify, geralmente é porque você não o definiu ServerNamecorretamente (ele precisará vir de um env var ou algo assim - não fique com dor de barriga com esse requisito ... faça-o corretamente).

Em particular, se você usa certificados de clientes e confia neles para autenticação, basicamente possui um login falso que na verdade não entra mais. Recuse o código que sim InsecureSkipVerify, ou você aprenderá o que há de errado com ele da maneira mais difícil!

Roubar
fonte
1
Você conhece um site onde eu posso testar isso? golang org agora não lança mais um erro.
topskip
3
Esta resposta é terrivelmente enganosa. Se você aceitar qualquer certificado assinado validamente, independentemente do nome do host, ainda não estará obtendo segurança real. Posso facilmente obter um certificado válido para qualquer domínio que eu controle; se você apenas especificar meu nome de host para a verificação TLS, perderá a validação de que eu realmente sou quem digo que sou. Nesse ponto, o fato de o certificado ser "legítimo" não importa; ainda está errado e você ainda está vulnerável ao homem no meio.
Daniel Farrell
5
Em uma empresa em que você realmente não confia em nenhuma das autoridades de certificação comerciais (por exemplo, se elas estiverem fora dos EUA, por exemplo) e substitua por uma autoridade de certificação da sua empresa, você só precisa validar que esse é um dos seus certificados. A verificação do nome do host é para situações em que os usuários esperam que o nome do host corresponda, mas o DNS não é seguro. Na empresa, você geralmente se conecta a um cluster de máquinas onde não é possível corresponder o nome do host / IP, porque são os clones exatos de uma máquina gerada sob novos IPs. O nome do DNS encontra o cert, e cert é o ID, não o nome do DNS.
Rob
3
a ideia é que o código do cliente confie apenas na autoridade de certificação corporativa. na verdade, é muito mais seguro que o sistema CA atualmente em uso. Nesse caso, nada (Thawte, Versign, etc) ... é confiável. Apenas a CA que executamos. Navegadores da Web têm enormes listas de confiança. Os serviços que conversam entre si têm apenas uma CA em seu arquivo de confiança.
Rob Rob
1
obrigado @Rob Eu tenho uma configuração idêntica em que nossos serviços confiam apenas na mesma CA única e em nada mais. Esta é uma informação vital e muita ajuda.
user99999991
8

A maneira correta de fazer isso se você deseja manter as configurações de transporte padrão é agora (a partir do Go 1.13):

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

Transport.Clone faz uma cópia profunda do transporte. Dessa forma, você não precisa se preocupar em perder nenhum novo campo que seja adicionado à Transportestrutura ao longo do tempo.

Bogdan Popa
fonte
7

Se você deseja usar as configurações padrão do pacote http, para não precisar criar um novo objeto de Transporte e Cliente, pode alterar para ignorar a verificação do certificado da seguinte maneira:

tr := http.DefaultTransport.(*http.Transport)
tr.TLSClientConfig.InsecureSkipVerify = true
Cornel Damian
fonte
7
Fazer isso resultaria empanic: runtime error: invalid memory address or nil pointer dereference
OscarRyz 15/03
6
Se você não usar o padrão http solicitante antes disso, você deve forçar um pedido falso de modo que o transporte padrão é inicializado
Cornel Damian
1
isso não está desativando as verificações de nome de host. desativa todas as verificações de certificado. Ir força você a definir o ServerName igual ao CN no certificado do qual você se conecta. O InsecureSkipVerify se conectará a um servidor MITM não autorizado que finge encaminhar para os serviços reais. O ÚNICO uso legítimo para um InsecureSkipVerify é obter o certificado da extremidade remota e desconectar imediatamente.
Rob
Isso só está ok se você TAMBÉM especificar um VerifyPeerCertificate. Se você acabou de definir o InsecureSkipVerify, não será verificado. Mas um VerifyPeerCertificate foi adicionado para que você possa reescrever a verificação para fazer o que precisa. Você pode querer ignorar o nome do host ou possivelmente até a data de validade. Google para várias implementações do VerifyPeerCertificate que fazem isso.
Rob
0

Geralmente, o domínio DNS da URL DEVE corresponder ao Assunto do certificado do certificado.

Antigamente, isso poderia ser definido pelo domínio como cn do certificado ou tendo o domínio definido como um Nome alternativo do assunto.

O suporte ao cn foi descontinuado por um longo tempo (desde 2000, na RFC 2818 ) e o navegador Chrome nem olha mais para o cn, então hoje você precisa ter o domínio DNS do URL como um nome alternativo do assunto.

RFC 6125, que proíbe a verificação do cn se o SAN for DNS Domain estiver presente, mas não se o SAN for IP Address estiver presente. O RFC 6125 também repete que o cn está obsoleto, o que já foi mencionado na RFC 2818. E o Fórum do Navegador da Autoridade de Certificação, que, combinado com o RFC 6125, significa essencialmente que o cn nunca será verificado quanto ao nome de domínio DNS.

jwilleke
fonte