Hello everyone 👋, Welcome back! In our previous blog, we have seen how to create an HTTPS Server in GoLang, we have also seen fetching the data using API on browser using HTTPS. But, how can a programmed client can fetch data from HTTPS server. Let's check that out in this blog.

Normal HTTP Client

Let's write a Go client to our server, and see what happens when we make a HTTP request to an HTTPS server.

Create a directory called client in the root directory of our code base. Paste the following code in the main.go file under client directory.

package main  import (   "fmt"   "net/http"   "io/ioutil" )  func main() {    url := "http://localhost:9090/products"   method := "GET"    client := &http.Client {   }   req, err := http.NewRequest(method, url, nil)    if err != nil {     fmt.Println(err)     return   }   req.Header.Add("Authorization", "Basic YWRtaW46YWRtaW5AcGFzc3dvcmQ=")    res, err := client.Do(req)   if err != nil {     fmt.Println(err)     return   }   defer res.Body.Close()    body, err := ioutil.ReadAll(res.Body)   if err != nil {     fmt.Println(err)     return   }   fmt.Println(string(body)) }

Before running the client, let's start our server.

go run .\main.go

Run the above code using the following command from the root directory. Note that we have added authorization header, because our server has basic auth enabled. Refer to this blog for more details.

go run .\client\main.go
That Didn T Work GIFs | Tenor
didn't work gif

As you must have seen, the client request fails due to below error.

Client sent an HTTP request to HTTPS server error.
What Should I Do GIFs | Tenor

What should we do?

Lets change the protocol in the URL from http to https in line 13, and re-run the same code. We should now see this.

Certificate signed by unknown authority error.

The error rightly implies, that the incoming certificate from the server, as part of TLS handshake is not signed by any of the trusted authorities existing in your machine.

Way out!

So, there are two options now. Trust the authority which signed the server certificate in our machine, or just make client trust it programmatically. Let's get the second option working now, to make sure our code works on any machine.

Adding Transport Configuration to HTTP client

We will now add the transport configuration to our basic http client inside our client. Modify the main.go under client directory as below.

package main  import ( 	"crypto/tls" 	"crypto/x509" 	"fmt" 	"io/ioutil" 	"net/http" )  func main() {  	url := "https://localhost:9090/products" 	method := "GET" 	caCert, err := ioutil.ReadFile("server.crt") 	if err != nil { 		fmt.Println("Failed to read certificate, " + err.Error()) 		return 	} 	caCertPool := x509.NewCertPool() 	caCertPool.AppendCertsFromPEM(caCert) 	client := &http.Client{ 		Transport: &http.Transport{ 			TLSClientConfig: &tls.Config{ 				RootCAs: caCertPool, 			}, 		}, 	} 	req, err := http.NewRequest(method, url, nil) 	if err != nil { 		fmt.Println(err) 		return 	} 	req.Header.Add("Authorization", "Basic YWRtaW46YWRtaW5AcGFzc3dvcmQ=")  	res, err := client.Do(req) 	if err != nil { 		fmt.Println(err) 		return 	} 	defer res.Body.Close()  	body, err := ioutil.ReadAll(res.Body) 	if err != nil { 		fmt.Println(err) 		return 	} 	fmt.Println(string(body)) } 

Re-run the client using the same command.

go run .\client\main.go
Missing SAN's, certificate relies on Common Name.

Aah, one more error to solve. But don't worry It's an opportunity to learn. The latest TLS protocols rely on SAN's for hostname matching, which we haven't configured while creating our certificates. So, let's now do this.

Adding SAN's to certificate

Create a text file named san.txt, and let's add SAN's to it.

subjectAltName = @alt_names [alt_names] IP.1 = 127.0.0.1 DNS.1 = localhost

Run the following commands to re-generate certificates which will be signed using our self-generated CA.

openssl genrsa -out ca.key 4096 openssl req -x509 -new -nodes -sha256 -key ca.key -days 3650 -out ca.crt openssl req -newkey rsa:4096 -nodes -keyout server.key -out server.csr openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -extfile san.txt 

Fill in these details when prompted.

Certificate information

This should generate a set of certificates in our folder. Make sure everything is generated.

contents of project directory

Restart server with new certificates

Before restart, make a small change to GET handlers in our handlers.go file.

Important: Modify the GET Handlers in our handlers.go to return 200 OK instead of 302 StatusFound, as it's used for redirection.

Restart the server with the new generated certificates, using the following command.

go run .\main.go
server running

Re-run the client

As now we have self generated CA, lets use it to trust incoming server certificates. For the one-last time modify the main.go file under client directory as below. Change the filename from server.crt to ca.crt in line 14.

Change to be made.

Now run the HTTPS client using the following command.

go run .\client\main.go

Hurray!! We now successfully connect to a HTTPS enabled server programmatically. It becomes more easy if you trust the CA certificate on your system, which also enables secure flag on your browser(if added to browser trusted CA). But, as mentioned our aim is to make our code work anywhere!

As always, here's the link to our GitHub repo.

Output

Hey! There's other way to do so(without trust, without adding certs to client code), by containerizing our client application. Let's discuss that in our upcoming blogs. Until then, stay safe. Cheers ✌✌


This free site is ad-supported. Learn more