DEV Community

Cover image for πŸ” Building a self signed server in golang
Luiz Lelis
Luiz Lelis

Posted on • Updated on • Originally published at luizlelis.com

πŸ” Building a self signed server in golang

This article will be useful to you if you want to create a self signed server in golang. There are many ways to use certificates to build and run a https server, this article will approach one of them: self-signed using openssl tool. You can see all the source code used in the current article in the public github repository.

Why https?

Firstly let's remember some concepts. The Hypertext Transfer Protocol (Http) specifies a standard track protocol for the internet community and has been in use since 1990. The problem with the use of only http is that the exchanged messages between server and client will not be encrypted, so everyone who intercepts those messages will know exactly what that messages means and also can modify the data to masquerade as one of the peers involved. To avoid attacks like man-in-the-middle and to provide private communication, the Secure Sockets Layer (SSL) was first introduced by Netscape in 1994 being the pioneer in secure communications protocols, but it was succeeded later by the Transport Layer Security (TLS). TLS is the channel-oriented security protocol currently in use on the internet and it's composed basically by two protocols: the handshake and the record protocol.

The TLS Handshake protocol is responsible to authenticate two end-points, besides that, it also negotiates cryptographic parameters and generates keying material. The record protocol uses the parameters established by the handshake protocol to protect traffic between the end-points.

Why self signed?

By default your operation system trusts in a set of certification authorities (CA) like GlobalSign, Let's Encrypt, Digicert, GoDaddy, etc. The self-signed certificates are those that aren't signed by any CA, in this case, the certificate is signed with its own private key, instead of requesting it from a CA. So in that case, the client should trust in the certificate issued by the server.

The first thing you need to ask yourself is: why do I need a self signed certificate? There are few reasons for that and I've never faced a reason to use it in a production environment. So maybe you're thinking: so on, what is this article for? There are some scenarios which demands you to provide a https endpoint (to run your application locally for example). In my case, I needed it to run an application locally to integrate it with a cloud service that requires a https endpoint. There are some frameworks, SDKs or tool kits written in other languages that provides to you an https endpoint natively with self-signed certificates (it's the case of JDK and .NET Core), but I didn't find anything like that in golang.

NOTE: if you're gonna use https in production, I strongly recommend you to use a certificate signed by a CA (try to use a cloud solution like AWS Certificate Manager, or an open source tool like certbot). There are some security risks that you should be aware of when using self-signed, you can see more about it here.

Running the project

"Talk is cheap, show me the code" - Linus Torvalds

To proceed with the next steps, you're gonna need to clone this github repo. The current example is composed by a server and a client called https-server and https-client respectively. Each one runs in its specific container, the server provides a REST API written in golang and is responsible to create the self signed certificate. That certificate protects two hostnames: localhost and https-server, that multi-domain approach is possible thanks to the Subject Alternative Names (SANs). Take a look at the diagram below that represents the current example:

certificate-diagram

As you can see above, the server generates the certificate and the clients trust that certificate (client container or a client running in the host). So, to up the client and server containers, run the command below:

docker-compose up
Enter fullscreen mode Exit fullscreen mode

Server

The command above will firstly up the server container and run some commands from a file called generate-certificate.sh. That bash file contains some openssl commands to create the self signed certificate. First, it generates a servercert.key and servercert.csr which are respectively: the private key and the certificate signing request (CSR) that contains the public key. The CN field in -subj is very important because some browsers like chrome require that information (CN means Common Name, that's the domain name you would like to have SSL secured). Then, the certificate file will be generated also, this file, named servercert.crt, is generated by the last command in the bash file. That's the self-signed certificate signed by your own servercert.key private key. The x509 flag states the standard format of an SSL/TLS certificate, the X.509 format. Finally, the https server are gonna get up by the go run main.go command. Take a look at the bash commands bellow:

apk update && apk add openssl && rm -rf /var/cache/apk/*
openssl req -new -subj "/C=US/ST=California/CN=localhost" \
    -newkey rsa:2048 -nodes -keyout "$FILE_CERT_NAME.key" -out "$FILE_CERT_NAME.csr"
openssl x509 -req -days 365 -in "$FILE_CERT_NAME.csr" -signkey "$FILE_CERT_NAME.key" -out "certificates/$FILE_CERT_NAME.crt" -extfile "self-signed-cert.ext"
Enter fullscreen mode Exit fullscreen mode

the ext file has all tha SANs protected by the certificate:

subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = https-server
Enter fullscreen mode Exit fullscreen mode

Now that you already have the certificate, you need to serve your https server. Inside the main.go file, the ListenAndServeTLS method is responsible for use the cert and key to serve the https self signed server:

func handleRequests() {

  tlsCert := os.Getenv("tls-certificate")
  tlsKey := os.Getenv("tls-key")
  serverPort := os.Getenv("server-port")

  router := mux.NewRouter().StrictSlash(true)
  controllers.HandleHomeRoutes(router, "https")

  log.Fatal(http.ListenAndServeTLS(serverPort, tlsCert, tlsKey, router))
}
Enter fullscreen mode Exit fullscreen mode

Along with that, as the cert and key was gotten from the .env file, you should declare both paths:

tls-certificate="certificates/servercert.crt"
tls-key="servercert.key"
Enter fullscreen mode Exit fullscreen mode

Client

The client container has a volume with the path where the server certificate was generated: ./server/certificates:/certificates. That's because the client needs to trust that certificate to make https calls to the server. The command update-ca-certificates is responsible to add that certificate to the system's trust store, it was executed in trust-server-certificate.sh bash file. After that, the client will be able to call the server with https (the handshake will happen normally). The https-client container calls the /home endpoint from the server with https two times after trusting its certificate, take a look at the curl calls in get-server-home.sh file:

#!/bin/ash
echo "Installing curl package"
apk update && apk add curl && rm -rf /var/cache/apk/*
echo "Two requests below to get https server home"
sleep 10
curl https://https-server:8081/home
sleep 20
curl https://https-server:8081/home
Enter fullscreen mode Exit fullscreen mode

Call the server with a client running locally (localhost)

As mentioned before, you need to trust the server certificate in your local trust store if you want to use https. If you're using a linux based OS, you should run the commands shown in trust-server-certificate.sh file. Otherwise, follow one of the steps below:

If you call a server endpoint before trusting the server certificate, you'll get an error like the following in your browser:

before-trust

after trusting the certificate locally, you'll get the response with a 200 Ok status code:

after-trust

if you expand the certificate, you will see all the domains secured by the self-signed certificate:

certificate-sans

that behavior is also shown in the server stdout, before trusting the certificate there is a handshake error, but after trusting it, the handshake is successful:

https-server | 2022/02/07 00:59:53 http: TLS handshake error from 172.19.0.1:55672: remote error: tls: unknown certificate
https-server | Home page endepoint hit
Enter fullscreen mode Exit fullscreen mode

References

Digicert; Multi-Domain (SAN) Certificates - Using Subject Alternative Names

Globalsign; The Dangers of Self-Signed SSL Certificates

Keyfactor; What is a Self-Signed Certificate? Advantages, Risks & Alternatives

OpenSSL; Cryptography and SSL/TLS Toolkit

RFC 2616; Hypertext Transfer Protocol -- HTTP/1.1

RFC 4949; Internet Security Glossary, Version 2

RFC 6101; The Secure Sockets Layer (SSL) Protocol Version 3.0

RFC 8446; The Transport Layer Security (TLS) Protocol Version 1.3

Top comments (0)