213

Say I want to get https://golang.org programatically. Currently golang.org (ssl) has a bad certificate which is issued to *.appspot.com So when I run this:

package main

import (
    "log"
    "net/http"
)

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

I get (as I expected)

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

Now, I want to trust this certificate myself (imagine a self-issued certificate where I can validate fingerprint etc.): how can I make a request and validate/trust the certificate?

I probably need to use openssl to download the certificate, load it into my file and fill tls.Config struct !?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
topskip
  • 16,207
  • 15
  • 67
  • 99
  • 6
    this is not a "bad certificate" it's a certificate with a different CN. InsecureSkipVerify is not a legitimate use here. You must set ServerName in the tls.Config to match what you are trying to connect to. This StackOverflow post is causing this big security hole in Go code to spread everywhere. InsecureSkipVerify doesn't check the certificate AT ALL. What you want is to verify that the certificate was legitimately signed by a trusted entity, even if the CN doesn't match the hostname. Tunnels and NATS can legitimately cause this to mismatch. – Rob Nov 08 '17 at 01:05
  • okay, so we got workarounds for test development but would anyone like to suggest the ideal way to solve this issue? I have used http.Get before but its only causing me problem for Post requests. How to get the certificate verified? – Aniket Kariya Mar 16 '22 at 14:38

6 Answers6

418

Security note: Disabling security checks is dangerous and should be avoided

You can disable security checks globally for all requests of the default client:

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

You can disable security check for a client:

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)
    }
}
Matthias
  • 143
  • 1
  • 8
cyberdelia
  • 5,343
  • 1
  • 19
  • 16
  • 24
    I wonder where to put a trusted certificate so that the connection can be used without `InsecureSkipVerify: true`. Is that possible? – topskip Aug 25 '12 at 14:19
  • 7
    `NameToCertificate` might help, see the `tls.Config` documentation : http://golang.org/pkg/crypto/tls/#Config – cyberdelia Aug 25 '12 at 14:24
  • When using this way to disable the check, it worked for cases where the certificate was expired, but not for the cases of self signed certificates. Does InsecureSkipVerify really skip the security check? – Alexander Aug 18 '14 at 11:43
  • 1
    Here is an example for adding custom CA certificate pools: http://golang.org/pkg/crypto/tls/#example_Dial You can use this in a HTTP client as well. – Bitbored Dec 11 '14 at 12:41
  • Works for self-signed certificates as well for me – Lupus Sep 30 '15 at 05:33
  • 7
    Lots of people end up here trying to disable hostname checks (not fully disabling certificate checks). This is the wrong answer. Go requires you to set the ServerName in the tls config to match the CN of the host you are connecting to, if it is not the dns name you connected with. InsecureSkipVerify is no more secure than a plain-old-telnet to the port. There is NO authentication with this setting. User ServerName instead! – Rob Nov 08 '17 at 01:00
  • 1
    @topskip note that there is RootCAs and ClientCAs. If you have a client cert, then ClientCAs specifies what signed the client cert. RootCAs specifies what signed the server cert. ServerName is the CN expected when you connect to another machine. By default it is expected to be the hostname you connect to it with, like: www.foobank.com:8443 expects CN www.foobank.com. – Rob Nov 08 '17 at 01:21
  • 1
    @topskip But if it's a perfectly legitimate cert where the CN is shared among machines, like "authorizationService", then set ServerName to "authorizationService" to match the CN rather than the hostname or IP you use to connect to it. This happens a lot with clusters that have dynamic IPs and Hostnames. InsecureSkipVerify can be a disaster when you get hostnames from things like world-writable zookeeper servers. – Rob Nov 08 '17 at 01:21
  • 15
    Beware: A transport created like that uses zero-values for most of its fields and therefore **looses all defaults**. As [the answer below](https://stackoverflow.com/a/46011355/3146034) suggests, you may want to copy them over. We had a good deal of fun figuring out [why we're running out of file descriptors](http://muratknecht.de/tech/pitfalls-disabling-ssl-verification-with-insecureskipverify-golang/), because we lost the `Dialer.Timeout`. – mknecht Dec 30 '17 at 08:36
  • I used https://stackoverflow.com/questions/31535569/golang-how-to-read-response-body-of-reverseproxy to modify response now http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} has stopped working. Any idea why? – vishu9219 Jun 06 '18 at 13:14
  • i wonder what are the risks of disabling certificate security check if your app is about to perform an https request to a given and fixed domain? if there any risk in that case too? – Victor Mar 18 '20 at 19:57
49

Proper way (as of Go 1.13) (provided by answer below):

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

Original Answer:

Here's a way to do it without losing the default settings of the DefaultTransport, and without needing the fake request as per user comment.

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},
}
client := &http.Client{Transport: customTransport}

Shorter way:

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

Warning: For testing/development purposes only. Anything else, proceed at your own risk!!!

Jonathan Lin
  • 19,922
  • 7
  • 69
  • 65
  • wouldn't it be easier to simply copy the default transport settings using `mytransportsettings := &(*http.DefaultTransport.(*http.Transport))` and then just modifying the TLS client config `mytransportsettings.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}`? – TheDiveO Jan 28 '20 at 21:28
  • Worth a try, I think I was trying to make sure I don't modify the real DefaultTransport – Jonathan Lin Jan 29 '20 at 02:37
  • 1
    this *ensures* to make a shallow copy, like you did, which is enough for the use case discussed. I'm actually deploying this in working code. – TheDiveO Jan 29 '20 at 13:11
30

All of these answers are wrong! Do not use InsecureSkipVerify to deal with a CN that doesn't match the hostname. The Go developers unwisely were adamant about not disabling hostname checks (which has legitimate uses - tunnels, nats, shared cluster certs, etc), while also having something that looks similar but actually completely ignores the certificate check. You need to know that the certificate is valid and signed by a cert that you trust. But in common scenarios, you know that the CN won't match the hostname you connected with. For those, set ServerName on tls.Config. If tls.Config.ServerName == remoteServerCN, then the certificate check will succeed. This is what you want. InsecureSkipVerify means that there is NO authentication; and it's ripe for a Man-In-The-Middle; defeating the purpose of using TLS.

There is one legitimate use for InsecureSkipVerify: use it to connect to a host and grab its certificate, then immediately disconnect. If you setup your code to use InsecureSkipVerify, it's generally because you didn't set ServerName properly (it will need to come from an env var or something - don't belly-ache about this requirement... do it correctly).

In particular, if you use client certs and rely on them for authentication, you basically have a fake login that doesn't actually login any more. Refuse code that does InsecureSkipVerify, or you will learn what is wrong with it the hard way!

Jondlm
  • 8,764
  • 2
  • 24
  • 30
Rob
  • 1,387
  • 1
  • 13
  • 18
  • 1
    Do you happen to know a site where I can test this? golang org now doesn't throw an error anymore. – topskip Nov 08 '17 at 07:57
  • use openssl tools to make certificates. you can write unit tests to thoroughly test this. – Rob Nov 08 '17 at 13:25
  • 3
    This answer is terribly misleading. If you accept any validly signed certificate regardless of hostname, you're still not getting real security. I can easily obtain a valid certificate for any domains I control; if you're just going to specify my hostname for the TLS check, you lose the validation that I really am who I say I am. At that point, the fact that the certificate is "legit" doesn't matter; it's _still_ wrong and you're _still_ vulnerable to man in the middle. – erik258 Jan 23 '18 at 20:31
  • 6
    In an Enterprise where you actually don't trust _any_ of the commercial CAs (ie: if they are outside the US for instance), and replace with one CA for your Enterprise, you just need to validate that this is one of your certs. The hostname check is for situations where users are expecting the hostname to match, but DNS isn't secure. In the enterprise, you generally connect to a _cluster_ of machines where it's not possible for the hostname/IP to match, because it's exact clones of a machine spawned under new IPs. The dns name finds the cert, and cert is the id, not dns name. – Rob Jan 23 '18 at 21:25
  • 5
    the idea is that client code only trusts the enterprise CA. it's actually far more secure than the CA system currently in use. In that case, nothing (Thawte, Versign, etc)... is trusted. Just the CA that we run. Web browsers have huge trust lists. Services talking to each other only have the one CA in its trust file. – Rob Jan 23 '18 at 21:27
  • 1
    thank you @Rob I have an identical setup where our services only trust the same, single CA and nothing else. This is vital information and much help. – user99999991 Mar 20 '19 at 05:05
  • *it's ripe for a Man-In-The-Middle; defeating the purpose of using TLS.* This is not correct - yes, you may have a MITM attack but TLS also has other uses (encryption, browser mixed content, ...). – WoJ Mar 20 '23 at 12:54
15

The correct way to do this if you want to maintain the default transport settings is now (as of Go 1.13):

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

Transport.Clone makes a deep copy of the transport. This way you don't have to worry about missing any new fields that get added to the Transport struct over time.

Bogdan Popa
  • 1,081
  • 7
  • 6
6

If you want to use the default settings from http package, so you don't need to create a new Transport and Client object, you can change to ignore the certificate verification like this:

tr := http.DefaultTransport.(*http.Transport)
tr.TLSClientConfig.InsecureSkipVerify = true
Cornel Damian
  • 733
  • 8
  • 11
  • 7
    Doing this would result in `panic: runtime error: invalid memory address or nil pointer dereference` – OscarRyz Mar 15 '17 at 21:37
  • 6
    If you don't use the default http requester before this, you must force a fake request so the default transport it's initialized – Cornel Damian Mar 16 '17 at 06:13
  • 1
    this is not disabling hostname checks. it disables all of certificate checks. Go forces you to set the ServerName equal to the CN in the cert of what you connect to. InsecureSkipVerify will connect to a rogue MITM server that pretends to forward to the real services. The ONLY legitimate use for an InsecureSkipVerify is to grab the cert of the remote end and immediately disconnect. – Rob Nov 08 '17 at 01:02
  • This is only ok if you ALSO specify a VerifyPeerCertificate. If you just set InsecureSkipVerify it doesn't check at all. But a VerifyPeerCertificate has been added so that you can rewrite the check to do what you need. You may want to ignore hostname, or possibly even expiration date. Google for various implementations of VerifyPeerCertificate that do this. – Rob Apr 02 '20 at 03:59
1

Generally, The DNS Domain of the URL MUST match the Certificate Subject of the certificate.

In former times this could be either by setting the domain as cn of the certificate or by having the domain set as a Subject Alternative Name.

Support for cn was deprecated for a long time (since 2000 in RFC 2818) and Chrome browser will not even look at the cn anymore so today you need to have the DNS Domain of the URL as a Subject Alternative Name.

RFC 6125 which forbids checking the cn if SAN for DNS Domain is present, but not if SAN for IP Address is present. RFC 6125 also repeats that cn is deprecated which was already said in RFC 2818. And the Certification Authority Browser Forum to be present which in combination with RFC 6125 essentially means that cn will never be checked for DNS Domain name.

Community
  • 1
  • 1
jwilleke
  • 10,467
  • 1
  • 30
  • 51