Golang SSL TCP socket certificate configuration - ssl

I'm creating a Go TCP server (NOT http/s) and I'm trying to configure it to use SSL. I have a StartCom free SSL certificate which I am trying to use to accomplish this. My server code looks like this:
cert, err := tls.LoadX509KeyPair("example.com.pem", "example.com.key")
if err != nil {
fmt.Println("Error loading certificate. ",err)
}
trustCert, err := ioutil.ReadFile("sub.class1.server.ca.pem")
if err != nil {
fmt.Println("Error loading trust certificate. ",err)
}
validationCert, err := ioutil.ReadFile("ca.pem")
if err != nil {
fmt.Println("Error loading validation certificate. ",err)
}
certs := x509.NewCertPool()
if !certs.AppendCertsFromPEM(validationCert) {
fmt.Println("Error installing validation certificate.")
}
if !certs.AppendCertsFromPEM(trustCert) {
fmt.Println("Error installing trust certificate.")
}
sslConfig := tls.Config{RootCAs: certs,Certificates: []tls.Certificate{cert}}
service := ":5555"
tcpAddr, error := net.ResolveTCPAddr("tcp", service)
if error != nil {
fmt.Println("Error: Could not resolve address")
} else {
netListen, error := tls.Listen(tcpAddr.Network(), tcpAddr.String(), &sslConfig)
if error != nil {
fmt.Println(error)
} else {
defer netListen.Close()
for {
fmt.Println("Waiting for clients")
connection, error := netListen.Accept()
I've tried switching around the order of the certs, not including some certs, etc. but the output from openssl s_client -CApath /etc/ssl/certs/ -connect localhost:5555 remains essentially the same, verify error:num=20:unable to get local issuer certificate. See here for full output. I seem to be doing something wrong with the intermediate certificates, but I have no idea what. I have been working on this for a few days, lots of googling and SO'ing, but nothing seemed to quite fit my situation. I have set up many certificates in Apache and HAProxy, but this really has me stumped.

The RootCAs field is for clients verifying server certificates. I assume you only want to present a cert for verification, so anything you need should be loaded into the Certificates slice.
Here is a minimal example:
cert, err := tls.LoadX509KeyPair("example.com.pem", "example.com.key")
if err != nil {
log.Fatal("Error loading certificate. ", err)
}
tlsCfg := &tls.Config{Certificates: []tls.Certificate{cert}}
listener, err := tls.Listen("tcp4", "127.0.0.1:5555", tlsCfg)
if err != nil {
log.Fatal(err)
}
defer listener.Close()
for {
log.Println("Waiting for clients")
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
go handle(conn)
}
Even though you're not using HTTPS, it may still be useful to walk through the server setup starting at http.ListenAndServeTLS.

Related

ListenAndServerTLS keeps failing with error: failed to find any PEM data in certificate input

I bought an SSL certificate from Godaddy for a web site.
I added the files in the server and run the service and it just returns an error:
failed to find any PEM data in certificate input
I used cat to generate a server.pem file with all the files, even added a godaddy pem intermediate pem file they provide for a G2 Certificate Chain and nothing.
cat generated-private-key.txt > server.pem
cat 678f65b8a7391017.crt >> server.pem
cat gd_bundle-g2-g1.crt >> server.pem
cat gdig2.crt.pem >> server.pem
Using self signed certificate works but off course it's not usable in real world.
Code attempt 1:
log.Fatal(http.ListenAndServeTLS(fmt.Sprintf("%s:%d", configuration.HttpServer.Address, configuration.HttpServer.Port), "server.pem", "generated-private-key.txt", router))
Code attempt 2:
cert, err := tls.LoadX509KeyPair("server.pem","generated-private-key.txt")
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
pem, err := ioutil.ReadFile("gd_bundle-g2-g1.crt")
if err != nil {
log.Fatalf("Failed to read client certificate authority: %v", err)
}
certpool := x509.NewCertPool()
if !certpool.AppendCertsFromPEM(pem) {
log.Fatalf("Can't parse client certificate authority")
}
tlsConfig := &tls.Config{
ClientCAs: certpool,
Certificates: []tls.Certificate{cert},
}
srv := &http.Server{
Addr: fmt.Sprintf("%s:%d", configuration.HttpServer.Address, configuration.HttpServer.Port),
Handler: router,
ReadTimeout: time.Duration(5) * time.Second,
WriteTimeout: time.Duration(5) * time.Second,
TLSConfig: tlsConfig,
}
log.Fatal(srv.ListenAndServeTLS("678f65b8a7391017.crt","generated-private-key.txt"))
Both give the same error.
I need to have this up and running as I already have the back-end done but now I just want to enable HTTPS for productive environment.
Finally solved it, I had to combine both generated-private-key.txt and generated-csr.txt that Godaddy provided me into a single "server.key" file.
Incredible what lack of knowledge of certain things end up wasting so much time.
But I guess that's why were here, for the thrill of exploring.
Thank you everyone for the help!
I've struggled with this myself and I think your issue here is that you need to process the keys before presenting for the http.Server, and you'll need to include the RootCA. I've downloaded an SSL from GoDaddy (using the Other option) and grabbed their gd_bundle-g2.crt RootCA from here. Once you've grabbed that, create a function like below (added a gist here):
func genTLS() (*tls.Config, error) {
caCert, err := ioutil.ReadFile("/home/sborza/gd_bundle-g2.crt")
if err != nil {
return nil, fmt.Errorf("read root cert: %s", err.Error())
}
// **** START PRIV KEY PROCESSING ****
clientBytes, err := ioutil.ReadFile("/home/sborza/sborza_dev.key")
if err != nil {
return nil, fmt.Errorf("read client priv key: %s", err.Error())
}
cb, _ := pem.Decode(clientBytes)
k, err := x509.ParsePKCS8PrivateKey(cb.Bytes)
if err != nil {
return nil, fmt.Errorf("parse client privkey: %s", err.Error())
}
clientKey, _ := x509.MarshalPKCS8PrivateKey(k)
clientKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: clientKey,
})
// **** END PRIV KEY PROCESSING ****
// **** START CERT PROCESSING ****
certBytes, err := ioutil.ReadFile("/home/sborza/sborza_dev.pem")
if err != nil {
return nil, fmt.Errorf("read client cert: %s", err.Error())
}
cbk, _ := pem.Decode(certBytes)
certs, err := x509.ParseCertificates(cbk.Bytes)
if err != nil {
return nil, fmt.Errorf("parse client cert: %s", err.Error())
}
clientCertPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certs[0].Raw,
})
// **** END CERT PROCESSING ****
// **** START TLS CONFIG ****
cert, err := tls.X509KeyPair(clientCertPEM, clientKeyPEM)
if err != nil {
return nil, fmt.Errorf("tls key pair: %s", err.Error())
}
caCertPool := x509.NewCertPool()
if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
return nil, fmt.Errorf("append cert: %s", err.Error())
}
return &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{cert},
}, nil
// **** END TLS CONFIG ****
}
As per one of the other comments, for the first param to ListenAndServeTLS, the GoDaddy cert and chain files have to be concatenated. Here is an example
var srv http.Server
srv.Handler = yourHandler
srv.Addr = ":443"
// Load cert files
chainBytes, err := ioutil.ReadFile("gd_bundle-g2-g1.crt") // GoDaddy chain
if err != nil {
log.Fatal(err)
}
certBytes, err := ioutil.ReadFile("server.crt") // Your server cert
if err != nil {
log.Fatal(err)
}
keyBytes, err := ioutil.ReadFile("server.key") // Your server key
if err != nil {
log.Fatal(err)
}
// Concatenate chain and cert
b := bytes.NewBuffer(certBytes)
b.Write([]byte("\n"))
b.Write(chainBytes)
// Setup TLS
cert, err := tls.X509KeyPair(b.Bytes(), keyBytes)
if err != nil {
log.Fatal(err)
}
cfg := &tls.Config{
Certificates: []tls.Certificate{cert},
}
srv.TLSConfig = cfg
// Start server
err = srv.ListenAndServeTLS("", "")
if err != nil {
log.Fatal(err)
}
My experience was that just using the cert file is fine in latest versions of browsers like Google Chrome, but I would get TLS errors with clients like HTTPie or CURL
certificate verify failed
And various errors on the server
tls: bad record MAC
tls: unknown certificate authority
Concatenating the cert and chain files solved above errors.
The issue was with the key file. This was the same key I used given by GoDaddy without any modification. The beginning of the file had some issue (like UTF-8 BOM at the start of the file or similar) as #SteffenUllrich mentioned. To fix this, I added an empty line just above the key file and it worked.
Finally, the key looks like:
<Empty line>
-----BEGIN RSA PRIVATE KEY-----
wlWPpSnGEdNjRapfW/6+xzjDVAaKC41c5b07OAviFchwqGI+88
aZGwBJnTgkbsLddddddd=
-----END RSA PRIVATE KEY-----

Getting error "Certificate is valid for ServerCommonName, not ClientCommonName"

I am trying to do a TLS authentication of a remote server. This server is configured with two certificates (one root and one it's own). Locally, I have the same root certificate. I am doing a TLS handshake (to validate that server can be trusted) by creating a client locally. However, on doing that, I am getting error: x509: Certificate is valid for ServerCommonName, not ClientCommonName. When I am trying to validate certificates present by server, ideally, the certificate chain of server should have a root cert that I trust and this is fine. Not able to understand why getting this particular error.
Can someone help? Below is the code...
func CheckTLSendpoint() error {
getDecoded()
var tlsConfig tls.Config
cer, _ := tls.X509KeyPair(ClientCertPem, ClientKeyPem)
// Checking verification of server certificate by the client is required or not
rootCA := x509.NewCertPool()
rootCA.AppendCertsFromPEM(RootCaPEM)
tlsConfig = tls.Config{
RootCAs: rootCA,
Certificates: []tls.Certificate{cer},
ServerName: "ClientCommonName", //this is common name of my client certificate
}
tlsConfig.BuildNameToCertificate()
rAddr := "10.20.30.40:3325"
conn, err := net.DialTimeout("tcp", rAddr, 10*time.Second)
defer conn.Close()
if err != nil {
return fmt.Errorf("TCP connection error : %s", err.Error())
}
c := tls.Client(conn, &tlsConfig)
defer c.Close()
err = c.Handshake()
if err != nil {
return fmt.Errorf("TLS connection error : %s", err.Error())
}
return nil
}

HTTP SSL with GoDaddy's certificate - This server's certificate chain is incomplete

in general I got 3 files from GoDaddy:
main Certificate file
Server Private Key
Bundle file
in configured all these files in my Go server in the following way:
cert, err := tls.LoadX509KeyPair("myalcoholist.pem","myalcoholist.key")
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
pem, err := ioutil.ReadFile("cert/sf_bundle-g2-g1.crt")
if err != nil {
log.Fatalf("Failed to read client certificate authority: %v", err)
}
if !certpool.AppendCertsFromPEM(pem) {
log.Fatalf("Can't parse client certificate authority")
}
tlsConfig := &tls.Config{
ClientCAs: certpool,
Certificates: []tls.Certificate{cert},
}
srv := &http.Server{
Addr: "myalcoholist.com:443",
Handler: n,
ReadTimeout: time.Duration(5) * time.Second,
WriteTimeout: time.Duration(5) * time.Second,
TLSConfig: tlsConfig,
}
err := srv.ListenAndServeTLS("cert/myalcoholist.pem","cert/myalcoholist.key")
The web server runs properly, it's currently published at https://myalcoholist.com:443.
I validated my SSL using https://www.ssllabs.com/ssltest/analyze.html?d=myalcoholist.com and it's response is This server's certificate chain is incomplete. Grade capped to B.
you can go to this link to see the all detailed result.
what am I missing?
Following that thread, and from the net/http/#ListenAndServeTLS() doc:
If the certificate is signed by a certificate authority, the certFile should be the concatenation of the server's certificate, any intermediates, and the CA's certificate.
Try and make sure your cert/myalcoholist.pem includes the CA certificates as well.
That thread used:
myTLSConfig := &tls.Config{
CipherSuites: []uint16{
tls.TLS_RSA_WITH_RC4_128_SHA,
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},}
myTLSConfig.PreferServerCipherSuites = true
const myWebServerListenAddress = "0.0.0.0:5555"
myTLSWebServer := &http.Server{Addr: myWebServerListenAddress, TLSConfig: myTLSConfig, Handler: router}
if err = myTLSWebServer.ListenAndServeTLS("/home/loongson/webServerKeysV2/golangCertFile2", "/home/loongson/webServerKeysV2/adequatech.ca-comodoinstantssl-exported-privatekey-rsa-ForApache.key"); err != nil {
panic(err)
}
Compared to my previous answer, adding a cipher suite is a good idea, but again, try and see if the certificate file passed to ListenAndServeTLS works better if it includes the CAs.
Sure enough, https://www.ssllabs.com/ssltest/analyze.html?d=myalcoholist.com reports grade A, with the warning: “Chain issues: Contains anchor”.
See "SSL/TLS: How to fix “Chain issues: Contains anchor”" to remove that warning, but this is not an error though:
RFC 2119: the server is allowed to include the root certificate (aka "trust anchor") in the chain, or omit it. Some servers include it

Get remote ssl certificate in golang

I want to receive a TCP connection over TLS. I want to validate client certificate and use it to authenticate the client to my application.
Go has the standard crypto/tls package. It can validate client/server certificates. But I can't find way to get details of the remote (client) certificate, like the common name.
Have to call crypto/tls/Conn.Handshake.
Then you can read peer certificate:
tlsconn.ConnectionState().PeerCertificates[0].Subject.CommonName
Following code may help you get your answer
package main
import (
"crypto/tls"
"fmt"
"log"
)
func main() {
conf := &tls.Config{
InsecureSkipVerify: true,
}
conn, err := tls.Dial("tcp", "www.google.com:443", conf)
if err != nil {
log.Println("Error in Dial", err)
return
}
defer conn.Close()
certs := conn.ConnectionState().PeerCertificates
for _, cert := range certs {
fmt.Printf("Issuer Name: %s\n", cert.Issuer)
fmt.Printf("Expiry: %s \n", cert.NotAfter.Format("2006-January-02"))
fmt.Printf("Common Name: %s \n", cert.Issuer.CommonName)
}
}
When working with crypto/tls you can query any Conn object for ConnectionState:
func (c *Conn) ConnectionState() ConnectionState
The ConnectionState struct contains information about the client certificate:
type ConnectionState struct {
PeerCertificates []*x509.Certificate // certificate chain presented by remote peer
}
The x509.Certificate should be pretty straightforward to work with.
Before the server requests for client authentication, you have to configure the connection with the server certificate, client CA (otherwise you will have to verify the trust chain manually, you really don't want that), and tls.RequireAndVerifyClientCert. For example:
// Load my SSL key and certificate
cert, err := tls.LoadX509KeyPair(settings.MyCertificateFile, settings.MyKeyFile)
checkError(err, "LoadX509KeyPair")
// Load the CA certificate for client certificate validation
capool := x509.NewCertPool()
cacert, err := ioutil.ReadFile(settings.CAKeyFile)
checkError(err, "loadCACert")
capool.AppendCertsFromPEM(cacert)
// Prepare server configuration
config := tls.Config{Certificates: []tls.Certificate{cert}, ClientCAs: capool, ClientAuth: tls.RequireAndVerifyClientCert}
config.NextProtos = []string{"http/1.1"}
config.Rand = rand.Reader
There is an easier way to do that:
func renewCert(w http.ResponseWriter, r *http.Request) {
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
cn := strings.ToLower(r.TLS.PeerCertificates[0].Subject.CommonName)
fmt.Println("CN: %s", cn)
}
}

Issues with TLS connection in Golang

I have the following certificate hierarchy:
Root-->CA-->3 leaf certificates
The entire chain has both serverAuth and clientAuth as extended key usages explicitly defined.
In my go code, I create a tls.Config object like so:
func parseCert(certFile, keyFile string) (cert tls.Certificate, err error) {
certPEMBlock , err := ioutil.ReadFile(certFile)
if err != nil {
return
}
var certDERBlock *pem.Block
for {
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
if certDERBlock == nil {
break
}
if certDERBlock.Type == "CERTIFICATE" {
cert.Certificate = append(cert.Certificate, certDERBlock.Bytes)
}
}
// Need to flip the array because openssl gives it to us in the opposite format than golang tls expects.
cpy := make([][]byte, len(cert.Certificate))
copy(cpy, cert.Certificate)
var j = 0
for i := len(cpy)-1; i >=0; i-- {
cert.Certificate[j] = cert.Certificate[i]
j++
}
keyData, err := ioutil.ReadFile(keyFile)
if err != nil {
return
}
block, _ := pem.Decode(keyData)
if err != nil {
return
}
ecdsaKey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return
}
cert.PrivateKey = ecdsaKey
return
}
// configure and create a tls.Config instance using the provided cert, key, and ca cert files.
func configureTLS(certFile, keyFile, caCertFile string) (tlsConfig *tls.Config, err error) {
c, err := parseCert(certFile, keyFile)
if err != nil {
return
}
ciphers := []uint16 {
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
}
certPool := x509.NewCertPool()
buf, err := ioutil.ReadFile(caCertFile)
if nil != err {
log.Println("failed to load ca cert")
log.Fatal(seelog.Errorf("failed to load ca cert.\n%s", err))
}
if !certPool.AppendCertsFromPEM(buf) {
log.Fatalln("Failed to parse truststore")
}
tlsConfig = &tls.Config {
CipherSuites: ciphers,
ClientAuth: tls.RequireAndVerifyClientCert,
PreferServerCipherSuites: true,
RootCAs: certPool,
ClientCAs: certPool,
Certificates: []tls.Certificate{c},
}
return
}
certFile is the certificate chain file and keyFile is the private key file. caCertFile is the truststore and consists of just the root certificate
So basically, here is what I expect to have inside of my tls.Config object that comes out of this function:
RootCAs: Just the root certificate from caCertFile
ClientCAs: Again, just the root certificate from caCertFile, same as RootCAs
Certificates: A single certificate chain, containing all of the certificates in certFile, ordered to be leaf first.
Now, I have 3 pieces here. A server, a relay, and a client. The client connects directly to the relay, which in turn forwards the request to the server. All three pieces use the same configuration code, of course using different certs/keys. The caCertFile is the same between all 3 pieces.
Now, if I stand up the server and the relay and connect to the relay from my browser, all goes well, so I can assume that the connection between relay and server is fine. The issue comes about when I try to connect my client to the relay. When I do so, the TLS handshake fails and the following error is returned:
x509: certificate signed by unknown authority
On the relay side of things, I get the following error:
http: TLS handshake error from : remote error: bad certificate
I am really at a loss here. I obviously have something setup incorrectly, but I am not sure what. It's really weird that it works from the browser (meaning that the config is correct from relay to server), but it doesn't work with the same config from my client.
Update:
So if I add InsecureSkipVerify: true to my tls.Config object on both the relay and the client, the errors change to:
on the client: remote error: bad certificate
and on the relay: http: TLS handshake error from : tls: client didn't provide a certificate
So it looks like the client is rejecting the certificate on from the server (the relay) due to it being invalid for some reason and thus never sending its certificate to the server (the relay).
I really wish go had better logging. I can't even hook into this process to see what, exactly, is going on.
When you say
Need to flip the array because openssl gives it to us in the opposite format than golang tls expects.
I have used certificates generated by openssl and had no problem opening them with:
tls.LoadX509KeyPair(cert, key)
Anyway, the error message bad certificate is due to the server not managing to match the client-provided certificate against its RootCAs. I have also had this problem in Go using self-signed certificats and the only work-around I've found is to install the caCertFile into the machines system certs, and use x509.SystemCertPool() instead of x509.NewCertPool().
Maybe someone else will have another solution?
Beside what beldin0 suggested.
I have tried another way to do this.
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(crt)
client := &http.Client{
//some config
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
},
},
}
Here, the variable "crt" is the content in your certificate.
Basically, you just add it into your code(or read as a config file).
Then everything would be fine.