Golang http tls handshake error from

What version of Go are you using (go version)? $ go version go version go1.14.2 darwin/amd64 Does this issue reproduce with the latest release? Yes. Description of the issue The current http server...

What version of Go are you using (go version)?

$ go version
go version go1.14.2 darwin/amd64

Does this issue reproduce with the latest release?

Yes.

Description of the issue

The current http server implementation makes it very cumbersome to access TLS handshake errors. I would like to be able to see them in order to add metrics and alerting to my service. (A recent misconfiguration caused all connections to fail, and we were unable to alert on these kinds of errors.)

Today, TLS handshake errors are reported by logging to the http.Server ErrorLog logger, if it is set. (Otherwise it goes to the standard logger.)

The line in question:

c.server.logf(«http: TLS handshake error from %s: %v», c.rwc.RemoteAddr(), err)
c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)

And the implementation of logf:

func (s *Server) logf(format string, args interface{}) {
if s.ErrorLog != nil {
s.ErrorLog.Printf(format, args)
} else {
log.Printf(format, args)
}
}
// logf prints to the ErrorLog of the *Server associated with request r
// via ServerContextKey. If there’s no associated server, or if ErrorLog
// is nil, logging is done via the log package’s standard logger.
func logf(r *Request, format string, args interface{}) {
s, _ := r.Context().Value(ServerContextKey).(*Server)
if s != nil && s.ErrorLog != nil {
s.ErrorLog.Printf(format, args)
} else {
log.Printf(format, args)
}
}
func (s *Server) logf(format string, args ...interface{}) {
	if s.ErrorLog != nil {
		s.ErrorLog.Printf(format, args...)
	} else {
		log.Printf(format, args...)
	}
}

ErrorLog is a *log.Logger, which is a struct and not an interface. It wraps an io.Writer. So in order to capture TLS handshake errors, one must write an io.Writer wrapper that searches for the string TLS handshake error. I think (but am not sure) that error messages are not covered by the Go 1 compatibility guarantee, so this method is both ugly and fragile.

It would be nice if there were another way to get notified of TLS handshake errors. This could be implemented as an optional callback function on the http.Server or tls.Config structs, or as a channel on http.Server. It may make sense to generalize it to more HTTP server errors, but we could start with handshake errors.

Reproducing the issue

An easy way to illustrate this in action is to start up a TLS HTTP server (http.ListenAndServeTLS) and then run nc localhost 1234 </dev/null. On the server you’ll get a log line like 2020/05/05 11:41:59 http: TLS handshake error from 127.0.0.1:63162: EOF

Содержание

  1. net/http: TLS handshake errors are not (reasonably) accessible for servers #38877
  2. Comments
  3. What version of Go are you using ( go version )?
  4. Does this issue reproduce with the latest release?
  5. Description of the issue
  6. Reproducing the issue
  7. From HTTP to HTTPS with Go
  8. Introduction⌗
  9. A Simple http server⌗
  10. PKI Setup⌗
  11. Generating keypair and certificates⌗
  12. Setting alias in /etc/hosts⌗
  13. Configuring TLS on the server⌗
  14. Configuring CA certificates on the client⌗
  15. Configuring mutual TLS⌗
  16. Server configuration⌗
  17. Client settings⌗
  18. Running mutual TLS authenticated client and server⌗
  19. Conslusion⌗
  20. References⌗
  21. From HTTP to HTTPS with Go
  22. Introduction⌗
  23. A Simple http server⌗
  24. PKI Setup⌗
  25. Generating keypair and certificates⌗
  26. Setting alias in /etc/hosts⌗
  27. Configuring TLS on the server⌗
  28. Configuring CA certificates on the client⌗
  29. Configuring mutual TLS⌗
  30. Server configuration⌗
  31. Client settings⌗
  32. Running mutual TLS authenticated client and server⌗
  33. Conslusion⌗
  34. References⌗

net/http: TLS handshake errors are not (reasonably) accessible for servers #38877

What version of Go are you using ( go version )?

Does this issue reproduce with the latest release?

Description of the issue

The current http server implementation makes it very cumbersome to access TLS handshake errors. I would like to be able to see them in order to add metrics and alerting to my service. (A recent misconfiguration caused all connections to fail, and we were unable to alert on these kinds of errors.)

Today, TLS handshake errors are reported by logging to the http.Server ErrorLog logger, if it is set. (Otherwise it goes to the standard logger.)

The line in question:

c . server . logf ( «http: TLS handshake error from %s: %v» , c . rwc . RemoteAddr (), err )

And the implementation of logf :

Lines 3062 to 3080 in 9b18968

func ( s * Server ) logf ( format string , args . interface <>) <
if s . ErrorLog != nil <
s . ErrorLog . Printf ( format , args . )
> else <
log . Printf ( format , args . )
>
>
// logf prints to the ErrorLog of the *Server associated with request r
// via ServerContextKey. If there’s no associated server, or if ErrorLog
// is nil, logging is done via the log package’s standard logger.
func logf ( r * Request , format string , args . interface <>) <
s , _ := r . Context (). Value ( ServerContextKey ).( * Server )
if s != nil && s . ErrorLog != nil <
s . ErrorLog . Printf ( format , args . )
> else <
log . Printf ( format , args . )
>
>

ErrorLog is a *log.Logger , which is a struct and not an interface. It wraps an io.Writer . So in order to capture TLS handshake errors, one must write an io.Writer wrapper that searches for the string TLS handshake error . I think (but am not sure) that error messages are not covered by the Go 1 compatibility guarantee, so this method is both ugly and fragile.

It would be nice if there were another way to get notified of TLS handshake errors. This could be implemented as an optional callback function on the http.Server or tls.Config structs, or as a channel on http.Server . It may make sense to generalize it to more HTTP server errors, but we could start with handshake errors.

Reproducing the issue

An easy way to illustrate this in action is to start up a TLS HTTP server ( http.ListenAndServeTLS ) and then run nc localhost 1234 . On the server you’ll get a log line like 2020/05/05 11:41:59 http: TLS handshake error from 127.0.0.1:63162: EOF

The text was updated successfully, but these errors were encountered:

Источник

From HTTP to HTTPS with Go

Introduction⌗

In this post, we will learn how to configure TLS encryption in Go. We will further explore how to set mutual-TLS encryption. The code presented in this blog post is available here. In this post, we just show the relevant snippets. The interested readers can clone the repository and follow along.

We will start by writing a simple Http server and a client in Go. We will then encrypt the traffic between them by configuring TLS on the server. Towards the end of this post, we will configure mutual TLS between the two parties.

A Simple http server⌗

Let’s start by creating an Http client-server implementation in Go. We expose an Http endpoint /server reachable on localhost:8080 . Then we call the endpoint using http.Client and print the result.

Full implementation is availabe here.

In the next sections, we will encrypt the traffic between the client and the server using TLS. Before we come to that stage, we should set up our public key infrastructure (PKI).

PKI Setup⌗

To set up our mini PKI infrastructure, we will use a Go utility called minica to produce root, server, and the client keypairs and certificates. In reality, a Certificate Authority (CA) or a Domain Administrator (within an organization) will provide you a keypair and a signed certificate. In our case, we will use minica to provision this for us.

Generating keypair and certificates⌗

Note: If generating these seems a hassle, you can reuse the certificates committed with the Github repository.

We will use the below steps to generate certificates.

  1. Install minica: go get github.com/jsha/minica
  2. Create server certificate by running minica —domains server-cert
  3. If you are running it for the first time, it will generate 4 files.
    1. minica.pem (root certificate)
    2. minica-key.pem (private key for root)
    3. server-cert/cert.pem (certificate for domain “server-cert”, signed by root’s public key)
    4. server-cert/key.pem (private key for domain “server-cert”)
  4. Create client certificate by running minica —domains client-cert . It will generate 2 new files
    1. client-cert/cert.pem (certificate for domain “client-cert”)
    2. client-cert/key.pem (private key for domain “client-cert”)

Alternatively, you can also use IP instead of domains with minica to generate your keypairs and certificates.

Setting alias in /etc/hosts⌗

The client and the server certificates generated above are valid for the domains server-cert and client-cert respectively. These domains do not exist, so we will create an alias for localhost (127.0.0.1). Once this is set up, we will be able to access our Http server using server-cert instead of localhost .

If you are on a platform other than Linux, you should Google on how to set it up for your OS. I use a Linux machine and setting the domain alias is pretty straightforward. Open /etc/hosts file and add below entries.

At this point, our infrastructure setup is complete. In the next sections, we will configure the server with these certificates, to encrypt the traffic between the client and the server.

Configuring TLS on the server⌗

Let use the key and certificate generated for the server-cert domain to configure TLS on the server. The client is the same as earlier. The only difference is that we will call the server on three different URLs to understand what is going on under the hood.

Full implementation is here

We start the server using http.ListenAndServeTLS() that takes four arguments, port, the path to the public certificate, the path to private key and Http-handler. Let us examine the response from the server. We send three different requests that will fail but will give us more insight into how Http encryption works.

Attepmt 1 to http://localhost:8080/server , the response is:

Client Error: Get http://localhost:8080/server: net/http: HTTP/1.x transport connection broken: malformed HTTP response “x15x03x01x00x02x02”

Server Error: http: TLS handshake error from 127.0.0.1:35694: tls: first record does not look like a TLS handshake

This is good news, which means the server is sending encrypted data. No one over Http will be able to make sense of it.

Attempt 2 to https://localhost:8080/server , the response is:

Client Error: Get https://localhost:8080/server: x509: certificate is valid for server-cert, not localhost

Server Error: http: TLS handshake error from 127.0.0.1:35698: remote error: tls: bad certificate

This is again good news, this means that a certificate issued to domain server-cert cannot be used by other domains (localhost).

Attempt 3 to https://server-cert:8080/server , the response is:

Client Error: Get https://server-cert:8080/server: x509: certificate signed by unknown authority

Server Error: http: TLS handshake error from 127.0.0.1:35700: remote error: tls: bad certificate

This error demonstrates that the client does not trust signed that certificate. Clients must be aware of the CA which has signed the certificate.

The whole idea behind this section was to demonstrate three guarantees that TLS ensures:

  • The message is always encrypted.
  • The server is actually what it says it is.
  • The client should not blindly believe the server certificate. They should be able to verify the server’s identity through a CA.

Configuring CA certificates on the client⌗

Let us configure the CA certificates on the client so that it can verify the server’s identity against root CA’s certificate. Since server-cert’s certificate was signed using root CA’s public key, the TLS handshake will validate and the communication will be encrypted.

Full implementation is available here.

This ensures all the three guarantees that we discussed earlier.

Configuring mutual TLS⌗

We have established a client’s trust on the server. But in a lot of use cases, the server needs to trust the client. For example, financial, healthcare or public service industry. For these scenarios, we can configure mutual TLS between the client and the server so that both parties can trust each other.

The TLS protocol has support for this from the beginning. The steps required to configure mutual TLS authentication are as follows:

  1. The server gets its certificate from a CA (CA-1). The client should have a public certificate of CA-1 that has signed the server’s certificate.
  2. The client gets its certificate from a CA (CA-2). The server should have the public certificate of CA-2 that has signed the client’s certificate. For simplicity, we will use the same CA (CA-1 == CA-2) to sign both client and server certificates.
  3. The server creates a CA certificate pool to validate all the clients. At this point, the server includes a public certificate of CA-2.
  4. Similarly, the client creates its own CA certificate pool and includes a public certificate for CA-1.
  5. Both parties validate the incoming requests against the CA certificate pool. If there are any validation errors on either side, the connection will be aborted.

Let us see it in action. Full implementation for this functionality is available here

Server configuration⌗

There are a few key things to note in this configuration:

  1. Instead of using http.ListenAndServeTLS() , we use server.ListenAndServerTLS() .
  2. We load the server certificate and key inside tls.Config.GetCertificate function.
  3. We create a pool of client CA certificates that the server should trust.
  4. We configure tls.Config.ClientAuth = tls.RequireAndVerifyClientCert , which will always verify the certificate of all the clients that try to connect. Only the validated clients will be able to continue the conversation.

Client settings⌗

The http.Client configuration changes a little for the client as well.

Notice some of the differences in the configuration as compared to server:

  1. In tls.Config, we use RootCAs to load certificate pool against ClientCAs setting on the server.
  2. We use tls.Config.GetClientCertificate to load client certificates against tls.Config.GetCertificate on the server.

The actual code in GitHub provides some callbacks, which could be used to see certificate information as well.

Running mutual TLS authenticated client and server⌗

Conslusion⌗

TLS configuration has always been more of a certificate management problem rather than an implementation affair. The typical confusions in the TLS configuration are often around using the correct certificates rather than its implementation. If you understand the TLS protocol and handshake correctly, Go offers everything else you need right out of the box.

You should also check an earlier post where we explored the TLS encryption and security from a theoretical standpoint.

References⌗

This post is hugely inspired by this wonderful talk by Liz Rice in Gophercon-2018, please check it out. Other useful references are mentioned below:

Источник

From HTTP to HTTPS with Go

Introduction⌗

In this post, we will learn how to configure TLS encryption in Go. We will further explore how to set mutual-TLS encryption. The code presented in this blog post is available here. In this post, we just show the relevant snippets. The interested readers can clone the repository and follow along.

We will start by writing a simple Http server and a client in Go. We will then encrypt the traffic between them by configuring TLS on the server. Towards the end of this post, we will configure mutual TLS between the two parties.

A Simple http server⌗

Let’s start by creating an Http client-server implementation in Go. We expose an Http endpoint /server reachable on localhost:8080 . Then we call the endpoint using http.Client and print the result.

Full implementation is availabe here.

In the next sections, we will encrypt the traffic between the client and the server using TLS. Before we come to that stage, we should set up our public key infrastructure (PKI).

PKI Setup⌗

To set up our mini PKI infrastructure, we will use a Go utility called minica to produce root, server, and the client keypairs and certificates. In reality, a Certificate Authority (CA) or a Domain Administrator (within an organization) will provide you a keypair and a signed certificate. In our case, we will use minica to provision this for us.

Generating keypair and certificates⌗

Note: If generating these seems a hassle, you can reuse the certificates committed with the Github repository.

We will use the below steps to generate certificates.

  1. Install minica: go get github.com/jsha/minica
  2. Create server certificate by running minica —domains server-cert
  3. If you are running it for the first time, it will generate 4 files.
    1. minica.pem (root certificate)
    2. minica-key.pem (private key for root)
    3. server-cert/cert.pem (certificate for domain “server-cert”, signed by root’s public key)
    4. server-cert/key.pem (private key for domain “server-cert”)
  4. Create client certificate by running minica —domains client-cert . It will generate 2 new files
    1. client-cert/cert.pem (certificate for domain “client-cert”)
    2. client-cert/key.pem (private key for domain “client-cert”)

Alternatively, you can also use IP instead of domains with minica to generate your keypairs and certificates.

Setting alias in /etc/hosts⌗

The client and the server certificates generated above are valid for the domains server-cert and client-cert respectively. These domains do not exist, so we will create an alias for localhost (127.0.0.1). Once this is set up, we will be able to access our Http server using server-cert instead of localhost .

If you are on a platform other than Linux, you should Google on how to set it up for your OS. I use a Linux machine and setting the domain alias is pretty straightforward. Open /etc/hosts file and add below entries.

At this point, our infrastructure setup is complete. In the next sections, we will configure the server with these certificates, to encrypt the traffic between the client and the server.

Configuring TLS on the server⌗

Let use the key and certificate generated for the server-cert domain to configure TLS on the server. The client is the same as earlier. The only difference is that we will call the server on three different URLs to understand what is going on under the hood.

Full implementation is here

We start the server using http.ListenAndServeTLS() that takes four arguments, port, the path to the public certificate, the path to private key and Http-handler. Let us examine the response from the server. We send three different requests that will fail but will give us more insight into how Http encryption works.

Attepmt 1 to http://localhost:8080/server , the response is:

Client Error: Get http://localhost:8080/server: net/http: HTTP/1.x transport connection broken: malformed HTTP response “x15x03x01x00x02x02”

Server Error: http: TLS handshake error from 127.0.0.1:35694: tls: first record does not look like a TLS handshake

This is good news, which means the server is sending encrypted data. No one over Http will be able to make sense of it.

Attempt 2 to https://localhost:8080/server , the response is:

Client Error: Get https://localhost:8080/server: x509: certificate is valid for server-cert, not localhost

Server Error: http: TLS handshake error from 127.0.0.1:35698: remote error: tls: bad certificate

This is again good news, this means that a certificate issued to domain server-cert cannot be used by other domains (localhost).

Attempt 3 to https://server-cert:8080/server , the response is:

Client Error: Get https://server-cert:8080/server: x509: certificate signed by unknown authority

Server Error: http: TLS handshake error from 127.0.0.1:35700: remote error: tls: bad certificate

This error demonstrates that the client does not trust signed that certificate. Clients must be aware of the CA which has signed the certificate.

The whole idea behind this section was to demonstrate three guarantees that TLS ensures:

  • The message is always encrypted.
  • The server is actually what it says it is.
  • The client should not blindly believe the server certificate. They should be able to verify the server’s identity through a CA.

Configuring CA certificates on the client⌗

Let us configure the CA certificates on the client so that it can verify the server’s identity against root CA’s certificate. Since server-cert’s certificate was signed using root CA’s public key, the TLS handshake will validate and the communication will be encrypted.

Full implementation is available here.

This ensures all the three guarantees that we discussed earlier.

Configuring mutual TLS⌗

We have established a client’s trust on the server. But in a lot of use cases, the server needs to trust the client. For example, financial, healthcare or public service industry. For these scenarios, we can configure mutual TLS between the client and the server so that both parties can trust each other.

The TLS protocol has support for this from the beginning. The steps required to configure mutual TLS authentication are as follows:

  1. The server gets its certificate from a CA (CA-1). The client should have a public certificate of CA-1 that has signed the server’s certificate.
  2. The client gets its certificate from a CA (CA-2). The server should have the public certificate of CA-2 that has signed the client’s certificate. For simplicity, we will use the same CA (CA-1 == CA-2) to sign both client and server certificates.
  3. The server creates a CA certificate pool to validate all the clients. At this point, the server includes a public certificate of CA-2.
  4. Similarly, the client creates its own CA certificate pool and includes a public certificate for CA-1.
  5. Both parties validate the incoming requests against the CA certificate pool. If there are any validation errors on either side, the connection will be aborted.

Let us see it in action. Full implementation for this functionality is available here

Server configuration⌗

There are a few key things to note in this configuration:

  1. Instead of using http.ListenAndServeTLS() , we use server.ListenAndServerTLS() .
  2. We load the server certificate and key inside tls.Config.GetCertificate function.
  3. We create a pool of client CA certificates that the server should trust.
  4. We configure tls.Config.ClientAuth = tls.RequireAndVerifyClientCert , which will always verify the certificate of all the clients that try to connect. Only the validated clients will be able to continue the conversation.

Client settings⌗

The http.Client configuration changes a little for the client as well.

Notice some of the differences in the configuration as compared to server:

  1. In tls.Config, we use RootCAs to load certificate pool against ClientCAs setting on the server.
  2. We use tls.Config.GetClientCertificate to load client certificates against tls.Config.GetCertificate on the server.

The actual code in GitHub provides some callbacks, which could be used to see certificate information as well.

Running mutual TLS authenticated client and server⌗

Conslusion⌗

TLS configuration has always been more of a certificate management problem rather than an implementation affair. The typical confusions in the TLS configuration are often around using the correct certificates rather than its implementation. If you understand the TLS protocol and handshake correctly, Go offers everything else you need right out of the box.

You should also check an earlier post where we explored the TLS encryption and security from a theoretical standpoint.

References⌗

This post is hugely inspired by this wonderful talk by Liz Rice in Gophercon-2018, please check it out. Other useful references are mentioned below:

Источник

NOTE: For an updated version of this post, see the GopherCon 2019 talk “PKI for Gopher”. (Video, Slides)

For a long time my knowledge of TLS was Googling “how to configure nginx as an HTTPS proxy.” Okay, the cert goes here and the key goes here, that’s my job done. But with more and more pushes for things HTTPS and HTTP/2 (which defaults to using TLS), it sometimes helps to understand this a little better.

Unfortunately a lot of the articles on this topic are either too high level or too specific and, when I need to learn the topic, I ended up just reading the Go documentation.

This is an article to explain how TLS (and HTTPS) works by creating and using certificates in running servers with Go. If you want, you can follow along by programming the examples in another window.

Public and Private Key Encryption

Public and private key cryptography is awesome and a good place to start. If you’ve ever used GitHub or had to log into an EC2 instance, you’ve seen these things before. A very common use case is to use them to prove your identity. You place your public key where anyone can see it, then use the private one to later confirm you are who you say you are.

Go has a fairly straight forward crypto/rsa package in the standard library. First we can just generate a new, random pair.

// create a public/private keypair
// NOTE: Use crypto/rand not math/rand
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
    log.Fatalf("generating random key: %v", err)
}

Here’s how this works. Things encrypt with a public key can only be decrypted by its paired private key. This involves some tricks with some very large prime numbers, but that’s for another article.

Let’s actually encrypt something using the public key.

plainText := []byte("The bourgeois human is a virus on the hard drive of the working robot!")

// use the public key to encrypt the message
cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, &privKey.PublicKey, plainText)
if err != nil {
    log.Fatalf("could not encrypt data: %v", err)
}
fmt.Printf("%sn", strconv.Quote(string(cipherText)))

As you can see, it’s even an effort to print the junk that’s results from this encryption.

"x83xccx11xe9x1b<x9axabxa3Hfqxfb]xb7x8axd3xfb3xadxfex01x1dx86dx1exf7xf0t.xc8x03fxd7Jxd6u0086\xb83xadx82xb0Ixe51:xe0x8cx94xfe}xb5x17xeb_x13Sx17xfahxbexcd=3axeexd0uxd0xf1$xc2bxf0`xb2xxbdx99xc0xf8xbc`xe7x8f黭gxe1xa1jx89x15xee,u061dxffxfexb7x84xbfx8b}t٫xa0x10Y)xaaxc4Mx18xac5xc9ٗD<xc1&fxebxf9S(x97sx01xc2sx1cuax82x1e1qxe83Č9x04x17x8cx1bxba`x9f,.xdc|%6xa5fxafxdbxd51xabJxf6#x11+S=pxxcc +87xe5x16x062xb6xdax0e~_>f,Sx80xb7xcax12wxf1xaax83xe3xde jxc2xfdx1exe6sx88|xf2?{x80x8cxfbx916xbfxb8xc7xeex81Ux9e1xc1sx86px01x80]rxa5vxdb|x84ץxce8xb7x0fxf6xd7x02Exc5u"

To decrypt this cipher text, we simply use the private key.

// decrypt with the private key
decryptedText, err := rsa.DecryptPKCS1v15(nil, privKey, cipherText)
if err != nil {
	log.Fatalf("error decrypting cipher text: %v", err)
}
fmt.Printf("%sn", decryptedText)
// The bourgeois human is a virus on the hard drive of the working robot!

Cool, but what good is this?

Let’s say I had your public key and, while talking on the Internet, I want to confirm that you are really you. What I could do is think of a random phrase, let’s say "Well, that's no ordinary rabbit." and encrypted it with your public key. You would have to have the private key to decrypt it, and if you where able to say that phrase back to me, I could confirm that I was really talking to you.

The cool part about this is you can prove you hold a private key without ever showing it to somebody.

Digital Signatures

A second trait of public private key-pairs is the ability to create a digital signature for a given message. These signatures can be used to ensure the validity of the document it signs.

To to this, the document is run through a hashing algorithm (we’ll use SHA256), then the private key computes a signature for the hashed results.

The public key can then confirm, again through math we’ll ignore, if its private key combined with a particular hash would have created that signature. Here’s what that looks like using crypto/rsa.

// compute the hash of our original plain text
hash := sha256.Sum256(plainText)
fmt.Printf("The hash of my message is: %#xn", hash)
// The hash of my message is: 0xe6a8502561b8e2328b856b4dbe6a9448d2bf76f02b7820e5d5d4907ed2e6db80

// generate a signature using the private key
signature, err := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256, hash[:])
if err != nil {
    log.Fatalf("error creating signature: %v", err)
}
// let's not print the signature, it's big and ugly

We can then attempt to verify the result with different combinations of messages and signatures.

// use a public key to verify the signature for a message was created by the private key
verify := func(pub *rsa.PublicKey, msg, signature []byte) error {
    hash := sha256.Sum256(msg)
    return rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash[:], signature)
}

fmt.Println(verify(&privKey.PublicKey, plainText, []byte("a bad signature")))
// crypto/rsa: verification error
fmt.Println(verify(&privKey.PublicKey, []byte("a different plain text"), signature))
// crypto/rsa: verification error
fmt.Println(verify(&privKey.PublicKey, plainText, signature))
// <nil>

What this signature is doing is confirming that a document has not been changed since the private key signed it. And because a public key is public and can be posted anywhere, anyone can run this same test.

This might be very helpful for say, a certificate authority, who wants to be able to distribute documents which can’t be altered without everyone detecting.

Go and x509

Go’s crypto/x509 package is what I’ll be using to actually generate and work with certificates. It’s a package with a lot of options and a somewhat intimidating interface. For instance, the ridiculous number of fields on the Certificate struct.

To create a new certificate, we first have to provide a template for one. Because we’ll be doing this a couple times, I’ve made a helper function to do some of the busy work.

// helper function to create a cert template with a serial number and other required fields
func CertTemplate() (*x509.Certificate, error) {
    // generate a random serial number (a real cert authority would have some logic behind this)
    serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
    serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
    if err != nil {
        return nil, errors.New("failed to generate serial number: " + err.Error())
    }

    tmpl := x509.Certificate{
        SerialNumber:          serialNumber,
        Subject:               pkix.Name{Organization: []string{"Yhat, Inc."}},
        SignatureAlgorithm:    x509.SHA256WithRSA,
        NotBefore:             time.Now(),
        NotAfter:              time.Now().Add(time.Hour), // valid for an hour
        BasicConstraintsValid: true,
    }
    return &tmpl, nil
}

Certificates are public keys with some attached information (like what domains they work for). In order to create a certificate, we need to both specify that information and provide a public key.

In this next block, we create a key-pair called rootKey and a certificate template called rootCertTmpl, then fill out some information about what we want to use it for.

// generate a new key-pair
rootKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
	log.Fatalf("generating random key: %v", err)
}

rootCertTmpl, err := CertTemplate()
if err != nil {
	log.Fatalf("creating cert template: %v", err)
}
// describe what the certificate will be used for
rootCertTmpl.IsCA = true
rootCertTmpl.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature
rootCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
rootCertTmpl.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}

Now the fun part.

Making a Self-Signed Certificate

Okay, it’s time to actually create a certificate.

Certificates must be signed by the private key of a parent certificate. Of course, there always has to be a certificate without a parent, and in these cases the certificate’s private key must be used in lieu of a parent’s.

x509.CreateCertificate takes 4 arguments (plus a source of randomness). The template of the certificate we want to create, the public key we want to wrap, the parent certificate, and the parent’s private key.

func CreateCert(template, parent *x509.Certificate, pub interface{}, parentPriv interface{}) (
    cert *x509.Certificate, certPEM []byte, err error) {

    certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentPriv)
    if err != nil {
        return
    }
    // parse the resulting certificate so we can use it again
    cert, err = x509.ParseCertificate(certDER)
    if err != nil {
        return
    }
    // PEM encode the certificate (this is a standard TLS encoding)
    b := pem.Block{Type: "CERTIFICATE", Bytes: certDER}
    certPEM = pem.EncodeToMemory(&b)
    return
}

To create our self-signed cert (named rootCert), we provide the arguments listed above. But instead of using a parent certificate, the root key’s information is used instead.

rootCert, rootCertPEM, err := CreateCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey)
if err != nil {
	log.Fatalf("error creating cert: %v", err)
}
fmt.Printf("%sn", rootCertPEM)
fmt.Printf("%#xn", rootCert.Signature) // more ugly binary

While printing out the signature isn’t incredibly useful, rootCertPEM should look very familiar for anyone who’s configured HTTPS or SSH’d into a server. Here’s what my code generated.

-----BEGIN CERTIFICATE-----
MIIC+jCCAeSgAwIBAgIRAK2uh2q3B+iVYia2l87Tch8wCwYJKoZIhvcNAQELMBUx
EzARBgNVBAoTClloYXQsIEluYy4wHhcNMTUwNjIwMjI1MDEyWhcNMTUwNjIwMjM1
MDEyWjAVMRMwEQYDVQQKEwpZaGF0LCBJbmMuMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAr3Y+KLFritC5CAsTCvYlZj/jczJrGmBNaLHtIUDSOQlrwEXy
DJqyl5kY8osu0YyZOFVsSbs/xNk5Hm9TmU/NSIxhGxJXkgd2QgeAzUP/zWWvvDiW
DL3KBu1FVbKnEdFd+7b3FHguHHh8/iHaeB09QgrX0cuf7ePC4PGKeIa9C8yQ8MNO
q6foQJ9H3p83oSUyl53obMP199Dseu8wVoTekzhesm/N6D2Rhb745T+RcQ8AguXd
xIob0x0D/orPprcvGDaabqiZnIS5zXVtdbgzKdpBc5Gwnb9b8cFICriOapVFWSLO
3Ta5uUDuUIDuwg/4Q66bJZqnNHlLoC/h1zvS6QIDAQABo0kwRzAOBgNVHQ8BAf8E
BAMCAIQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV
HREECDAGhwR/AAABMAsGCSqGSIb3DQEBCwOCAQEAJgNp51KA3v774tx8gFP4OMrL
wfpFZVhIOid35ol0dX0/oOXSUXs28AMIhpou/vWH5REkFadPxtZD1ErHzgB/h7Ce
Iln9L9ZIC/QMA93chNsDaj+M+Np9p4AckrO9BthqhWjqIbdwkRC4cb4gN1vei1MP
Pu1nhdvE3PKX4VG5pqc1DaMyKDotc1pc5jaOkz3NAGyTPn9PUyfQP88FqnYaf5/a
K5Vulo8NmzMOCcBjAJ9B0IXOLg9ba+dyiOK8pIayBiX28FRaxRUiU31iEPI8gbTN
/6W3f//C3eTDCCLwEmGOmOalpBnaF4wsA6CTxDmwDyTmj9+TRkaEEylEQTlXZA==
-----END CERTIFICATE-----

Right away we can use this PEM encoded block in a server. However, let’s remember that a certificate is just a public key. To prove ownership of a certificate you must have the private key as well. In the case of our server, we have to load rootKey if we want to use rootCert.

To keep this code cleaner, I’m going to use the httptest package, which will allow us to spin up and shut down servers on a whim.

// PEM encode the private key
rootKeyPEM := pem.EncodeToMemory(&pem.Block{
	Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rootKey),
})

// Create a TLS cert using the private key and certificate
rootTLSCert, err := tls.X509KeyPair(rootCertPEM, rootKeyPEM)
if err != nil {
	log.Fatalf("invalid key pair: %v", err)
}

ok := func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("HI!")) }
s := httptest.NewUnstartedServer(http.HandlerFunc(ok))

// Configure the server to present the certficate we created
s.TLS = &tls.Config{
	Certificates: []tls.Certificate{rootTLSCert},
}

We can now make a HTTP request to the server, where we get a very familiar error message.

// make a HTTPS request to the server
s.StartTLS()
_, err = http.Get(s.URL)
s.Close()

fmt.Println(err)
// http: TLS handshake error from 127.0.0.1:52944: remote error: bad certificate

The net/http package has rejected the certificate.

By default, net/http loads trusted certificates (public keys) from your computer. These are the same ones your browser uses when you surf the web. The issue is, the certificate we create, which the test server provided, has a digital signature. But, none of the public keys trusted by your browser validated that signature.

Getting the Client to Trust the Server

Rather than using a self-signed certificate, let’s create a setup that mimics a real situation where a certificate authority provides a organization with a cert for their website. To do this, we’ll pretend the rootCert we created before belongs to the certificate authority, and we’ll be attempting to create another certificate for our server.

First things first, we’ll create a new key-pair and template.

// create a key-pair for the server
servKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
	log.Fatalf("generating random key: %v", err)
}

// create a template for the server
servCertTmpl, err := CertTemplate()
if err != nil {
	log.Fatalf("creating cert template: %v", err)
}
servCertTmpl.KeyUsage = x509.KeyUsageDigitalSignature
servCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
servCertTmpl.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}

To create the server certificate, we’re going to use a real parent this time. And again, we provide a public key for the certificate, and the parents private key (rootKey) to do the signing.

// create a certificate which wraps the server's public key, sign it with the root private key
_, servCertPEM, err := CreateCert(servCertTmpl, rootCert, &servKey.PublicKey, rootKey)
if err != nil {
	log.Fatalf("error creating cert: %v", err)
}

We now have a PEM encoded certificate. To use this in a server, we have to have the private key to prove we own it.

// provide the private key and the cert
servKeyPEM := pem.EncodeToMemory(&pem.Block{
	Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(servKey),
})
servTLSCert, err := tls.X509KeyPair(servCertPEM, servKeyPEM)
if err != nil {
	log.Fatalf("invalid key pair: %v", err)
}
// create another test server and use the certificate
s = httptest.NewUnstartedServer(http.HandlerFunc(ok))
s.TLS = &tls.Config{
	Certificates: []tls.Certificate{servTLSCert},
}

If we made another request here, we’d still be in the same situation as before when our client reject the certificate.

To avoid this, we need to create a client which “trusts” servCert. Specifically, we have to trust a public key which validates servCert’s signature. Since we use the root key-pair to sign the certificate, if we trust rootCert (the public key), we’ll trust the server’s certificate.

// create a pool of trusted certs
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(rootCertPEM)

// configure a client to use trust those certificates
client := &http.Client{
	Transport: &http.Transport{
		TLSClientConfig: &tls.Config{RootCAs: certPool},
	},
}

When the server provides a certificate, the client will now validate the signature using all the certificates in certPool rather than the ones on my laptop. Let’s see if this worked.

s.StartTLS()
resp, err := client.Get(s.URL)
s.Close()
if err != nil {
	log.Fatalf("could not make GET request: %v", err)
}
dump, err := httputil.DumpResponse(resp, true)
if err != nil {
	log.Fatalf("could not dump response: %v", err)
}
fmt.Printf("%sn", dump)

And boom, we’re speaking HTTPS.

HTTP/1.1 200 OK
Content-Length: 3
Content-Type: text/plain; charset=utf-8
Date: Sat, 20 Jun 2015 22:50:14 GMT

HI!

Conclusion

Oddly enough, TLS is often more about managing certificates and private keys than worrying about how the actual over the wire encryption works. It’s also important to make sure that your servers and clients work with things like HTTPS. And it’s a bit of a hack to just turn verification off.

But as you learn more about TLS, you can find that it’s really powerful. Even if you aren’t serving HTTP traffic, being able to doing able to do this kind of verification and encryption is a lot easier than trying to set something else up yourself. And the next time a website dumps a bunch of .crt files on you, you’ll hopefully be able to understand exactly what to do with them.

Bonus: Getting the Server to Trust the Client

Most web servers don’t care who the client is who’s accessing them. Or at least the client authentication they do do isn’t at the TCP layer, it’s done with session tokens and HTTP middleware.

While websites don’t find this kind of auth particularly useful, databases and other architecture like a compute clusters, when a server wants to verify it’s client without a password, can use this to both restrict access and encrypt communications. For instance, the is what boot2docker does in its more recent releases, while Google’s Kubernetes platform has plans to use this for secure master to worker communication.

It’s easy to turn on client authentication for a Go server.

// create a new server which requires client authentication
s = httptest.NewUnstartedServer(http.HandlerFunc(ok))
s.TLS = &tls.Config{
	Certificates: []tls.Certificate{servTLSCert},
	ClientAuth:   tls.RequireAndVerifyClientCert,
}

s.StartTLS()
_, err = client.Get(s.URL)
s.Close()
fmt.Println(err)

After the request is made, we’ll actually see the server log something like this. It’s rejected the client.

http: TLS handshake error from 127.0.0.1:47038: tls: client didn't provide a certificate

In order for the client to provide a certificate, we have to create a template first.

// create a key-pair for the client
clientKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
	log.Fatalf("generating random key: %v", err)
}

// create a template for the client
clientCertTmpl, err := CertTemplate()
if err != nil {
	log.Fatalf("creating cert template: %v", err)
}
clientCertTmpl.KeyUsage = x509.KeyUsageDigitalSignature
clientCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}

When creating a new certificate we’ll again have the rootCert sign it. It doesn’t have to be the same parent as the server, but this makes the example easier.

// the root cert signs the cert by again providing its private key
_, clientCertPEM, err := CreateCert(clientCertTmpl, rootCert, &clientKey.PublicKey, rootKey)
if err != nil {
	log.Fatalf("error creating cert: %v", err)
}

// encode and load the cert and private key for the client
clientKeyPEM := pem.EncodeToMemory(&pem.Block{
	Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey),
})
clientTLSCert, err := tls.X509KeyPair(clientCertPEM, clientKeyPEM)
if err != nil {
	log.Fatalf("invalid key pair: %v", err)
}

The client now needs to trust the server’s cert by trusting the cert pool we made earlier. As a reminder this holds the rootCert. It’s also needs to present its own certificate.

authedClient := &http.Client{
	Transport: &http.Transport{
		TLSClientConfig: &tls.Config{
			RootCAs:      certPool,
			Certificates: []tls.Certificate{clientTLSCert},
		},
	},
}

Of course, the server still can’t verify the client. If we made a request now, we’d see something like this.

http: TLS handshake error from 127.0.0.1:59756: tls: failed to verify client's certificate: x509: certificate signed by unknown authority

To get around this, we have to configure a new test server to both present the server certificate, and trust the client’s (by trusting certPool which holds rootCert).

s = httptest.NewUnstartedServer(http.HandlerFunc(ok))
s.TLS = &tls.Config{
	Certificates: []tls.Certificate{servTLSCert},
	ClientAuth:   tls.RequireAndVerifyClientCert,
	ClientCAs:    certPool,
}
s.StartTLS()
_, err = authedClient.Get(s.URL)
s.Close()
fmt.Println(err)
// <nil>

And there you go, we’ve negotiated a secure conversation between a client and a server who both trust that each is properly authenticated.

In this example we are going to create a TLS based HTTP/2 server and let client communicate with it over HTTP/2 protocol instead of traditional HTTP/1.

Conventional HTTP/1 communications are made over their own connections so one request per one connection. This would be slow because connection negotiation, TLS handshake, TCP slow-start strategy so on. takes place for each request. The solution to this problem is to use HTTP/2 protocol. In the case of HTTP/2 communications, multiple requests can be made over a single connection. That means connection negotiation, TLS handshake, TCP slow-start strategy so on. takes place only once which improves the performance. This also depends on the HTTP keep-alive feature which is enabled by default but you might accidentally disable it while creating a custom transport. Unless you have a reason to disable it (I cannot imagine why you would anyway), I suggest you to use it.

Another reason you might accidentally disable connection reusing is not closing the response body as soon as using it. Pipelining allows multiple request and their responses to be communicated in parallel rather than in serial. For this to work you will have to use res.Body.Close() to close body as soon as possible without deferring it so that the following requests, if any, use existing connections to improve the overall performance.

Remember

The performance benefits of HTTP/2 come from simultaneous/parallel (pipelining) and batch requests. For example based on my tests, HTTP/2 was able to handle 500,000 requests (30sec~) but HTTP/1 barely managed 100 requests with many «broken pipe», «connection reset by peer» errors. The tests issued requests as in goroutines.

Timeouts

Server

Client

SSL

HTTP/2 protocol enforces TLS so we will have to first create a SSL key and a SSL certificate. Let’s move on to create self-signed private and public key pair.

Private key

This will be used only for the server.

$ openssl genrsa -out cert/private.key 4096

Generating RSA private key, 4096 bit long modulus
..........................++
..........................++

Public certificate

This will be used for both the server and the client.

$ openssl req -new -x509 -sha256 -days 1825 -key cert/private.key -out cert/public.crt

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.

Country Name (2 letter code) [AU]:UK
State or Province Name (full name) [Some-State]:London
Locality Name (eg, city) []:City of London
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Inanzzz Ltd
Organizational Unit Name (eg, section) []:Engineering
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:myemail@mydomain.com

The Common Name field above is very important. If what you enter doesn’t match what your client uses as part of the request host/URL or tls.Config.ServerName, you will get one of the errors below.

// Client side errors.
Get https://0.0.0.0:8443: x509: cannot validate certificate for 0.0.0.0 because it doesn't contain any IP SANs
Get https://127.0.0.1:8443: x509: cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs
Get https://localhost:8443: x509: certificate is not valid for any names, but wanted to match localhost
Get https://localhost:8443: x509: certificate is valid for localhost, not helloworld

// Server side errors.
http: TLS handshake error from 127.0.0.1:59981: remote error: tls: bad certificate
http: TLS handshake error from [::1]:59873: remote error: tls: bad certificate

Server

Structure

.
├── cert
│   ├── private.key
│   └── public.crt
└── cmd
   └── server
   └── main.go

main.go

package main

import (
"crypto/tls"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)

func main() {
server := &http.Server{
Addr: ":8443",
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
TLSConfig: tlsConfig(),
}

//// Having this does not change anything but just showing.
//// go get -u golang.org/x/net/http2
//if err := http2.ConfigureServer(server, nil); err != nil {
// log.Fatal(err)
//}

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fmt.Sprintf("Protocol: %s", r.Proto)))
})

if err := server.ListenAndServeTLS("", ""); err != nil {
log.Fatal(err)
}
}

func tlsConfig() *tls.Config {
crt, err := ioutil.ReadFile("./cert/public.crt")
if err != nil {
log.Fatal(err)
}

key, err := ioutil.ReadFile("./cert/private.key")
if err != nil {
log.Fatal(err)
}

cert, err := tls.X509KeyPair(crt, key)
if err != nil {
log.Fatal(err)
}

return &tls.Config{
Certificates: []tls.Certificate{cert},
ServerName: "localhost",
}
}

Run

This runs, waits for client requests and responds to them.

$ go run -race cmd/server/main.go 

Client

Structure

.
├── cert
│   └── public.crt
└── cmd
   └── client
   └── main.go

main.go

Run go get -u golang.org/x/net/http2 to install HTTP/2 package. As seen below I commented out transport1 function which depends on the default http package. It shows us how to create a HTTP/2 transport. Technically the transport1 and transport2 requests result in same response as shown below.

package main

import (
"crypto/tls"
"crypto/x509"
"fmt"
"golang.org/x/net/http2"
"io/ioutil"
"log"
"net/http"
)

func main() {
client := &http.Client{Transport: transport2()}

res, err := client.Get("https://localhost:8443")
if err != nil {
log.Fatal(err)
}

body, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Fatal(err)
}

res.Body.Close()

fmt.Printf("Code: %dn", res.StatusCode)
fmt.Printf("Body: %sn", body)
}

func transport2() *http2.Transport {
return &http2.Transport{
TLSClientConfig: tlsConfig(),
DisableCompression: true,
AllowHTTP: false,
}
}

//func transport1() *http.Transport {
// return &http.Transport{
// // Original configurations from `http.DefaultTransport` variable.
// DialContext: (&net.Dialer{
// Timeout: 30 * time.Second,
// KeepAlive: 30 * time.Second,
// }).DialContext,
// ForceAttemptHTTP2: true, // Set it to false to enforce HTTP/1
// MaxIdleConns: 100,
// IdleConnTimeout: 90 * time.Second,
// TLSHandshakeTimeout: 10 * time.Second,
// ExpectContinueTimeout: 1 * time.Second,
//
// // Our custom configurations.
// ResponseHeaderTimeout: 10 * time.Second,
// DisableCompression: true,
// // Set DisableKeepAlives to true when using HTTP/1 otherwise it will cause error: dial tcp [::1]:8090: socket: too many open files
// DisableKeepAlives: false,
// TLSClientConfig: tlsConfig(),
// }
//}

func tlsConfig() *tls.Config {
crt, err := ioutil.ReadFile("./cert/public.crt")
if err != nil {
log.Fatal(err)
}

rootCAs := x509.NewCertPool()
rootCAs.AppendCertsFromPEM(crt)

return &tls.Config{
RootCAs: rootCAs,
InsecureSkipVerify: false,
ServerName: "localhost",
}
}

Run

$ go run -race cmd/client/main.go
Code: 200
Body: Protocol: HTTP/2.0

References

  • Package http2
  • What is HTTP/2?
  • Demo Go + HTTP/2
  • The complete guide to Go net/http timeouts

Cover image for 🔐 Building a self signed server in golang

Luiz Lelis

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:

  • Mac Os

  • Windows

  • Linux

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

Ever wondered what mTLS (mutual TLS) looks like? Come, learn to implement mTLS
using Golang and OpenSSL.

Introduction

TLS (Transport Layer Security) provides the necessary encryption for applications
when communicating over a network. HTTPS (Hypertext Transfer Protocol Secure) is
an extension of HTTP that leverages TLS for security. The TLS technique requires
a CA (Certificate Authority) to issue a X.509 digital certificate to a service,
which is then handed over to the consumer of the service for it to validate it
with the CA itself. mTLS extends the same idea to applications, for example,
microservices wherein both the provider and the consumer require to produce
their own certificates to the other party. These certificates are validated by
both parties with their respective CAs. Once validated, the communication
between the server/client or provider/consumer happens securely.

The Implementation

Step 1 — Build a simple HTTP Server and Client

Let’s first create a simple HTTP Server in server.go which responds with
Hello, world! when requested for the /hello resource over port 8080.

package main

import (
	"io"
	"log"
	"net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
	// Write "Hello, world!" to the response body
	io.WriteString(w, "Hello, world!n")
}

func main() {
	// Set up a /hello resource handler
	http.HandleFunc("/hello", helloHandler)

	// Listen to port 8080 and wait
	log.Fatal(http.ListenAndServe(":8080", nil))
}

The Client simply requests for the /hello resource over port 8080 and prints
the response body to stdout. Here is what client.go looks like:

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

func main() {
	// Request /hello over port 8080 via the GET method
	r, err := http.Get("http://localhost:8080/hello")
	if err != nil {
		log.Fatal(err)
	}

	// Read the response body
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Fatal(err)
	}

	// Print the response body to stdout
	fmt.Printf("%sn", body)
}

Open an instance of the terminal and run the Server like so:

Open another instance of the terminal and run the Client:

You should see the following output from the Client.

Step 2 — Generate and use the Certificates with the Server

Use the following command to generate the certificates. The command creates a
2048 bit key certificate which is valid for 10 years. Additionally, the CN=localhost
asserts that the certificate is valid for the localhost domain.

openssl req -newkey rsa:2048 
  -new -nodes -x509 
  -days 3650 
  -out cert.pem 
  -keyout key.pem 
  -subj "/C=US/ST=California/L=Mountain View/O=Your Organization/OU=Your Unit/CN=localhost"

You should now have cert.pem and key.pem in your directory.

Let’s now enable TLS over HTTP i.e. HTTPS on the Server. Replace the
http.ListenAndServe(":8080", nil) call in server.go with
http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", nil) for the Server to
listen to HTTPS connections over port 8443 while supplying the certificates
generated earlier.

-	// Listen to port 8080 and wait
-	log.Fatal(http.ListenAndServe(":8080", nil))
+	// Listen to HTTPS connections on port 8443 and wait
+	log.Fatal(http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", nil))

You can verify the Server’s working by running it and browsing to https://localhost:8443/hello.

Secure Server

Let’s now update client.go to connect to the Server over HTTPS.

-	// Request /hello over port 8080 via the GET method
-	r, err := http.Get("http://localhost:8080/hello")
+	// Request /hello over HTTPS port 8443 via the GET method
+	r, err := http.Get("https://localhost:8443/hello")

Since our Client doesn’t yet know about the certificates, running it should
spit out the following error on the Server.

http: TLS handshake error from [::1]:59436: remote error: tls: bad certificate

On the Client, you should observe the following.

x509: certificate is not valid for any names, but wanted to match localhost

Step 3 — Supply the Certificates to the Client

Update the client.go code to read the previously generated certificates, like so:

-	// Request /hello over HTTPS port 8443 via the GET method
-	r, err := http.Get("https://localhost:8443/hello")

+	// Create a CA certificate pool and add cert.pem to it
+	caCert, err := ioutil.ReadFile("cert.pem")
+	if err != nil {
+		log.Fatal(err)
+	}
+	caCertPool := x509.NewCertPool()
+	caCertPool.AppendCertsFromPEM(caCert)
+
+	// Create a HTTPS client and supply the created CA pool
+	client := &http.Client{
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{
+				RootCAs: caCertPool,
+			},
+		},
+	}
+
+	// Request /hello via the created HTTPS client over port 8443 via GET
+	r, err := client.Get("https://localhost:8443/hello")

Here, we read the cert.pem file and supply it as the root CA when creating the
Client. Running the Client should now successfully display the following.

Final Step — Enable mTLS

On the Client, read and supply the key pair as the client certificate.

+	// Read the key pair to create certificate
+	cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
+	if err != nil {
+		log.Fatal(err)
+	}

...

-	// Create a HTTPS client and supply the created CA pool
+	// Create a HTTPS client and supply the created CA pool and certificate
	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				RootCAs: caCertPool,
+				Certificates: []tls.Certificate{cert},
			},
		},
	}

On the Server, we create a similar CA pool and supply it to the TLS config to
serve as the authority to validate Client certificates. We also use the same key
pair for the Server certificate.

-	// Listen to HTTPS connections on port 8443 and wait
-	log.Fatal(http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", nil))

+	// Create a CA certificate pool and add cert.pem to it
+	caCert, err := ioutil.ReadFile("cert.pem")
+	if err != nil {
+		log.Fatal(err)
+	}
+	caCertPool := x509.NewCertPool()
+	caCertPool.AppendCertsFromPEM(caCert)
+
+	// Create the TLS Config with the CA pool and enable Client certificate validation
+	tlsConfig := &tls.Config{
+		ClientCAs: caCertPool,
+		ClientAuth: tls.RequireAndVerifyClientCert,
+	}
+	tlsConfig.BuildNameToCertificate()
+
+	// Create a Server instance to listen on port 8443 with the TLS config
+	server := &http.Server{
+		Addr:      ":8443",
+		TLSConfig: tlsConfig,
+	}
+
+	// Listen to HTTPS connections with the server certificate and wait
+	log.Fatal(server.ListenAndServeTLS("cert.pem", "key.pem"))

Run server.go and then client.go and you should see a success message on the
Client, like so:

All Together

Finally, the server.go looks like the following.

package main

import (
	"crypto/tls"
	"crypto/x509"
	"io"
	"io/ioutil"
	"log"
	"net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
	// Write "Hello, world!" to the response body
	io.WriteString(w, "Hello, world!n")
}

func main() {
	// Set up a /hello resource handler
	http.HandleFunc("/hello", helloHandler)

	// Create a CA certificate pool and add cert.pem to it
	caCert, err := ioutil.ReadFile("cert.pem")
	if err != nil {
		log.Fatal(err)
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	// Create the TLS Config with the CA pool and enable Client certificate validation
	tlsConfig := &tls.Config{
		ClientCAs: caCertPool,
		ClientAuth: tls.RequireAndVerifyClientCert,
	}
	tlsConfig.BuildNameToCertificate()

	// Create a Server instance to listen on port 8443 with the TLS config
	server := &http.Server{
		Addr:      ":8443",
		TLSConfig: tlsConfig,
	}

	// Listen to HTTPS connections with the server certificate and wait
	log.Fatal(server.ListenAndServeTLS("cert.pem", "key.pem"))
}

The client.go file looks like so:

package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

func main() {
	// Read the key pair to create certificate
	cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
	if err != nil {
		log.Fatal(err)
	}

	// Create a CA certificate pool and add cert.pem to it
	caCert, err := ioutil.ReadFile("cert.pem")
	if err != nil {
		log.Fatal(err)
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	// Create a HTTPS client and supply the created CA pool and certificate
	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				RootCAs: caCertPool,
				Certificates: []tls.Certificate{cert},
			},
		},
	}

	// Request /hello via the created HTTPS client over port 8443 via GET
	r, err := client.Get("https://localhost:8443/hello")
	if err != nil {
		log.Fatal(err)
	}

	// Read the response body
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Fatal(err)
	}

	// Print the response body to stdout
	fmt.Printf("%sn", body)
}

Conclusion

Golang makes it really easy to implement mTLS, and this one’s just ~100 LOC.


I’d love to hear what you think.

Понравилась статья? Поделить с друзьями:
  • Golang go func error
  • Golang fmt error
  • Golang error with stack
  • Golang error warp
  • Golang error unwrap