¿Cómo hacer una solicitud https con certificado incorrecto?

128

Digamos que quiero llegar https://golang.orgprogramáticamente. Actualmente golang.org (ssl) tiene un certificado incorrecto que se emite a *.appspot.comSo cuando ejecuto esto:

package main

import (
    "log"
    "net/http"
)

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

Me sale (como esperaba)

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

Ahora, quiero confiar en este certificado yo mismo (imagine un certificado autoemitido donde pueda validar huella digital, etc.): ¿cómo puedo hacer una solicitud y validar / confiar en el certificado?

¡Probablemente necesito usar openssl para descargar el certificado, cargarlo en mi archivo y completar tls.Configstruct!?

topskip
fuente
55
Este no es un "certificado incorrecto", es un certificado con un CN diferente. InsecureSkipVerify no es un uso legítimo aquí. Debe establecer ServerName en tls.Config para que coincida con lo que está intentando conectarse. Esta publicación de StackOverflow está causando que este gran agujero de seguridad en el código Go se extienda por todas partes. InsecureSkipVerify no verifica el certificado en absoluto. Lo que desea es verificar que el certificado haya sido firmado legítimamente por una entidad confiable, incluso si el CN ​​no coincide con el nombre de host. Los túneles y NATS pueden hacer que esto no coincida legítimamente.
Rob

Respuestas:

283

Nota de seguridad: deshabilitar los controles de seguridad es peligroso y debe evitarse

Puede deshabilitar las comprobaciones de seguridad de forma global para todas las solicitudes del cliente predeterminado:

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

Puede deshabilitar la comprobación de seguridad para un 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
fuente
17
Me pregunto dónde colocar un certificado de confianza para que la conexión se pueda usar sin ella InsecureSkipVerify: true. ¿Es eso posible?
topskip
77
NameToCertificatepodría ayudar, consulte la tls.Configdocumentación: golang.org/pkg/crypto/tls/#Config
cyberdelia
1
Aquí hay un ejemplo para agregar grupos de certificados de CA personalizados: golang.org/pkg/crypto/tls/#example_Dial También puede usar esto en un cliente HTTP.
Bitbored
77
Muchas personas terminan aquí tratando de deshabilitar las comprobaciones de nombres de host (no deshabilitando completamente las comprobaciones de certificados). Esta es la respuesta incorrecta. Go requiere que establezca el ServerName en la configuración de tls para que coincida con el CN ​​del host al que se está conectando, si no es el nombre DNS con el que se conectó. InsecureSkipVerify no es más seguro que un simple telnet antiguo para el puerto. NO hay autenticación con esta configuración. Usuario ServerName en su lugar!
Rob
8
Cuidado: un transporte así creado utiliza valores cero para la mayoría de sus campos y, por lo tanto, pierde todos los valores predeterminados . Como sugiere la siguiente respuesta , es posible que desee copiarlos. Nos divertimos mucho descubriendo por qué nos estamos quedando sin descriptores de archivo , porque perdimos el Dialer.Timeout.
mknecht
26

Aquí hay una manera de hacerlo sin perder la configuración predeterminada del DefaultTransport, y sin necesidad de la solicitud falsa según el comentario del usuario.

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

ACTUALIZAR

Camino más corto:

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

Forma correcta (a partir de Go 1.13) (proporcionada por la respuesta a continuación ):

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

Advertencia : solo con fines de prueba / desarrollo. ¡Cualquier otra cosa, proceda bajo su propio riesgo!

Jonathan Lin
fuente
¿no sería más fácil simplemente copiar la configuración de transporte predeterminada usando mytransportsettings := &(*http.DefaultTransport.(*http.Transport))y luego simplemente modificando la configuración del cliente TLS mytransportsettings.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}?
TheDiveO
Vale la pena intentarlo, creo que estaba tratando de asegurarme de no modificar el DefaultTransport real
Jonathan Lin
1
esto asegura hacer una copia superficial, como lo hizo, que es suficiente para el caso de uso discutido. De hecho, estoy implementando esto en código de trabajo.
TheDiveO
19

¡Todas estas respuestas están equivocadas! No lo use InsecureSkipVerifypara tratar con un CN que no coincide con el nombre de host. Los desarrolladores de Go imprudentemente se mostraron inflexibles al no deshabilitar las comprobaciones de nombres de host (que tiene usos legítimos: túneles, nats, certificados de clúster compartidos, etc.), al tiempo que también tenían algo que se parecía, pero en realidad ignora por completo la comprobación de certificados. Debe saber que el certificado es válido y está firmado por un certificado en el que confía. Pero en escenarios comunes, sabe que el CN ​​no coincidirá con el nombre de host con el que se conectó. Para aquellos, establecido ServerNameen tls.Config. Si tls.Config.ServerName== remoteServerCN, entonces la verificación del certificado tendrá éxito. Esto es lo que quieres. InsecureSkipVerifysignifica que NO hay autenticación; y está maduro para un hombre en el medio; derrotando el propósito de usar TLS.

Hay un uso legítimo para InsecureSkipVerify: usarlo para conectarse a un host y tomar su certificado, luego desconectarlo inmediatamente. Si configura su código para usarlo InsecureSkipVerify, generalmente es porque no lo configuró ServerNamecorrectamente (tendrá que provenir de una variable ambiental o algo así; no se preocupe por este requisito ... hágalo correctamente).

En particular, si usa certificados de cliente y confía en ellos para la autenticación, básicamente tiene un inicio de sesión falso que en realidad ya no inicia sesión. ¡Rechace el código que sí lo hace InsecureSkipVerify, o aprenderá lo que está mal con él de la manera difícil!

Robar
fuente
1
¿Conoces un sitio donde puedo probar esto? golang org ahora ya no arroja un error.
topskip
3
Esta respuesta es terriblemente engañosa. Si acepta cualquier certificado firmado de forma válida independientemente del nombre de host, aún no obtiene seguridad real. Puedo obtener fácilmente un certificado válido para cualquier dominio que controlo; si solo va a especificar mi nombre de host para la comprobación de TLS, perderá la validación de que realmente soy quien digo que soy. En ese punto, el hecho de que el certificado sea "legítimo" no importa; es todavía mal y que estás siendo vulnerables a hombre en el medio.
Daniel Farrell el
55
En una empresa en la que realmente no confía en ninguna de las CA comerciales (es decir, si están fuera de los EE. UU., Por ejemplo) y las reemplaza con una CA para su empresa, solo tiene que validar que este es uno de sus certificados. La verificación del nombre de host es para situaciones en las que los usuarios esperan que el nombre de host coincida, pero DNS no es seguro. En la empresa, generalmente se conecta a un grupo de máquinas donde no es posible que el nombre de host / IP coincida, porque son clones exactos de una máquina generada bajo nuevas IP. El nombre dns encuentra el cert, y cert es el id, no el nombre dns.
Rob
3
la idea es que el código del cliente solo confíe en la CA de la empresa. en realidad es mucho más seguro que el sistema de CA actualmente en uso. En ese caso, nada (Thawte, Versign, etc.) ... es confiable. Solo la CA que corremos. Los navegadores web tienen enormes listas de confianza. Los servicios que se comunican entre sí solo tienen una CA en su archivo de confianza.
Rob
1
gracias @Rob Tengo una configuración idéntica en la que nuestros servicios solo confían en la misma CA única y nada más. Esta es información vital y mucha ayuda.
user99999991
8

La forma correcta de hacer esto si desea mantener la configuración de transporte predeterminada es ahora (a partir de Go 1.13):

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

Transport.Clone hace una copia profunda del transporte. De esta manera, no tiene que preocuparse por perder ningún campo nuevo que se agregue a la Transportestructura con el tiempo.

Bogdan Popa
fuente
7

Si desea utilizar la configuración predeterminada del paquete http, por lo que no necesita crear un nuevo objeto de transporte y cliente, puede cambiar para ignorar la verificación del certificado de esta manera:

tr := http.DefaultTransport.(*http.Transport)
tr.TLSClientConfig.InsecureSkipVerify = true
Cornel Damian
fuente
77
Hacer esto daría como resultadopanic: runtime error: invalid memory address or nil pointer dereference
OscarRyz
66
Si no utiliza el solicitante http predeterminado antes de esto, debe forzar una solicitud falsa para que se inicialice el transporte predeterminado
Cornel Damian
1
Esto no deshabilita las comprobaciones de nombre de host. deshabilita todas las verificaciones de certificados. Ir te obliga a establecer el ServerName igual al CN en el certificado de lo que te conectas. InsecureSkipVerify se conectará a un servidor MITM falso que pretende reenviar a los servicios reales. El ÚNICO uso legítimo de un InsecureSkipVerify es tomar el certificado del extremo remoto y desconectarlo inmediatamente.
Rob
Esto solo está bien si TAMBIÉN especificas un VerifyPeerCertificate. Si solo configura InsecureSkipVerify, no se verifica en absoluto. Pero se ha agregado un VerifyPeerCertificate para que pueda volver a escribir el cheque para hacer lo que necesita. Es posible que desee ignorar el nombre de host, o posiblemente incluso la fecha de vencimiento. Google para diversas implementaciones de VerifyPeerCertificate que hacen esto.
Rob
0

En general, el dominio DNS de la URL DEBE coincidir con el sujeto del certificado del certificado.

En el pasado, esto podría ser configurando el dominio como cn del certificado o estableciendo el dominio como un Nombre alternativo del sujeto.

El soporte para cn fue obsoleto durante mucho tiempo (desde 2000 en RFC 2818 ) y el navegador Chrome ya ni siquiera mirará el cn, por lo que hoy debe tener el dominio DNS de la URL como un nombre alternativo del sujeto.

RFC 6125 que prohíbe verificar el cn si existe SAN para Dominio DNS, pero no si SAN para Dirección IP está presente. El RFC 6125 también repite que cn está en desuso, lo que ya se dijo en el RFC 2818. Y el Foro del Navegador de la Autoridad de Certificación estará presente, lo que en combinación con el RFC 6125 esencialmente significa que cn nunca se comprobará el nombre de Dominio DNS.

jwilleke
fuente