Golang - TLS mutual authentication - Dump client certificates - go

I have TLS server with mutual authentication. I want to dump client certificates if handshake error. I use ErrorLog in http.Server struct, but this logger doesn't get the information about client certificates. I tried to use the VerifyConnection function in tls.Config struct, but it starts after the correct handshake. How can I dump the client certificates(wrong and corrects)?

You could dump the client certificates through tls Conn.ConnectionState after Conn.HandShake as long as the handshake of TLS is done.
Here are code snippets
config := tls.Config{
Certificates: []tls.Certificate{yourServerCert},
ClientAuth: tls.RequestClientCert,
InsecureSkipVerify: true,
}
listener, err := tls.Listen("tcp", "localhost:8080", &config)
if err != nil {
fmt.Println("server: listen err %+v \n", err)
return
}
conn, err := listener.Accept()
if err != nil {
fmt.Println("server: accept err %+v \n", err)
return
}
tlsConn, ok := conn.(*tls.Conn)
if !ok {
fmt.Println("server: invalid tls connection")
return
}
if err := tlsConn.Handshake(); err != nil {
fmt.Println("server: client handshake err %+v \n", err)
return
}
state := tlsConn.ConnectionState()
for _, v := range state.PeerCertificates {
fmt.Printf("server: remote client cert %+v \n", v)
}

Related

Golang x509: Certificate is valid for *.<DNS> not <DB_hostname>

I am trying to connect to MySQL DB using SSL via Golang and I am getting this error message
x509: certificate is valid for *., not <DB_hostname>
I have created certificates using this tutorial
https://www.devdungeon.com/content/creating-self-signed-ssl-certificates-openssl
Function to create TLSConf:
func createTLSConf() tls.Config {
rootCertPool := x509.NewCertPool()
pem, err := ioutil.ReadFile("certificate.pem")
if err != nil {
log.Fatal(err)
}
if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
log.Fatal("Failed to append PEM.")
}
clientCert := make([]tls.Certificate, 0, 1)
certs, err := tls.LoadX509KeyPair("certificate.pem", "privkey.pem")
if err != nil {
log.Fatal(err)
}
clientCert = append(clientCert, certs)
return tls.Config{
RootCAs: rootCertPool,
Certificates: clientCert,
InsecureSkipVerify: false,
}
}
Connection to DB
tlsConf := createTLSConf()
err := mysql.RegisterTLSConfig("custom", &tlsConf)
if err != nil {
log.Printf("Error %s when RegisterTLSConfig\n", err)
return
}
dsn := fmt.Sprintf("%s:%s#tcp(%s)/%s?tls=custom", "user", "pass", "db_host", "db_name")
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Printf("Error %s when opening DB\n", err)
log.Printf("%s", dsn)
return
}
Using connection
defer db.Close()
e := db.Ping()
fmt.Println(dsn, e)
db.Close()
Output:
<user>:<pass>#tcp(<db_host>)/<db_name>?tls=custom x509: certificate is valid for *.abc-xy-pxc, not <db_host>
abc-xy is a part of db_host and also abc_xy is the db_name, nut sure if it is important
When changing InsecureSkipVerify to true, the output looks like this:
[mysql] 2022/10/21 22:31:27 packets.go:37: remote error: tls: unknown certificate authority
Any idea how can I get this to work?
EDIT:
Sooo, I don't know why, but removing part of the code with Client certificates and changing InsecureSkipVerify to true solved the issue:
func createTLSConf() tls.Config {
rootCertPool := x509.NewCertPool()
pem, err := ioutil.ReadFile("certificate.pem")
if err != nil {
log.Fatal(err)
}
if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
log.Fatal("Failed to append PEM.")
}
return tls.Config{
RootCAs: rootCertPool,
InsecureSkipVerify: true, // needed for self signed certs
}
}
Hey I am also new to Go but you can give this a try. Consider my answer might not be the best but I am trying to be more active by helping and asking. You can generate your certificates in terminal and then use them.
run this in terminal at your project directory. (where your main.go located) and the /usr/local/go/src/crypto/tls/generate_cert.go should be the directory where Go standard library is installed.
go run /usr/local/go/src/crypto/tls/generate_cert.go --rsa-bits=2048 --host=localhost
open a DB connection return errors if any:
func openDB(dsn string) (*sql.DB, error){
db, err := sql.Open("mysql",dsn)
if err!= nil{
return nil,err
}
if err = db.Ping();err!=nil{
return nil,err
}
return db,nil
}
Then you have to configure your server settings. You will have a http.Server with your pem files.
declare a variable of crypto/tls.Config where only elliptic curves with assembly implementation are used.
tlsConfig := &tls.Config{
CurvePreferences:[]tls.CurveID{tls.X25519,tls.CurveP256}
}
then declare your server.
yourServer := &http.Server{
//Your configs
TLSConfig : tlsConfig,
//Your other configs if any
}
Now we have created our server with specific TLS configuration. All you have to do is running your server now. However, you have to update your server's method with ListenAndServeTLS(first arg = cert,second arg = key)
err = srv.ListenAndServeTLS(<Location of your cert.pem>, <Location of your key.pem>)

SupportsCertificate throws no mutually supported protocol versions for x509Certificate extracted from pfx

With the following code, I'm creating a TLS cert
func loadCert() tls.Certificate {
b, err := ioutil.ReadFile(fileName)
if err != nil {
log.Fatalln("failed to find / open cert file", err)
}
p, c, err1 := pkcs12.Decode(b, password)
if err1 != nil {
log.Fatalln("Failed to read cert content", err1)
}
tlsCert := tls.Certificate{
PrivateKey: p,
Leaf: c,
}
return tlsCert
}
And then with NewCertPool(), I am adding this to the pool
func makeRequest(){
// Loading cert
cert := loadCert()
caCertPool := x509.NewCertPool()
caCertPool.AddCert(cert.Leaf)
tConfig := &tls.Config{
RootCAs: caCertPool,
PreferServerCipherSuites: true,
InsecureSkipVerify: false,
Certificates: []tls.Certificate{cert},
}
tr := &http.Transport{
TLSClientConfig: tConfig,
}
client := http.Client{
Transport: tr,
}
request, reqError := http.NewRequest("POST", url, bytes.NewBuffer(dataToPost))
if reqError != nil {
log.Fatalln(" failed with error", reqError)
}
request.Header.Set("Accept", "*/*")
request.Header.Set("Connection", "keep-alive")
request.Header.Add("Content-Type", "text/xml;charset=UTF-8")
res, dErr := client.Do(request)
if dErr != nil {
log.Fatalln("Failed to make request", dErr)
} else {
log.Println(res.StatusCode)
}
}
SupportCertificate throws
no mutually supported protocol versions
Same certificate works just fine with tools like Postman. If I remove the check with this supportCertificate function and try to make POST call server reports that client certificate is not found, despite adding it in the transport above . And on the client side I get the error that connection has been reset and
x509: certificate signed by unknown authority
To remedy this I tried using SystemCertPool(), after which code fails to execute on windows with error SystemCertPool not found. On Docker or on VM it fails with
connection has been reset by peer
I'm really at loss here what I am missing, would appreciate some help on this.
I have already scoured through really helpful posts on how TLS can be configured and how CA certs could be added to the new pool. But I still seem to be missing something.
You can try to use your http.Client with configured tls.Config, where you re-defined Root CA. For example:
http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caPool,
},
},
where caPool can be obtained as
caPool, err := x509.SystemCertPool()
if err != nil {
return nil, fmt.Errorf("SystemCertPool() error: %v", err)
}
if caPool == nil {
caPool = x509.NewCertPool()
}
certs, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read CA from file '%q': %v", path, err)
}
if ok := caPool.AppendCertsFromPEM(certs); !ok {
return nil, fmt.Errorf("failed to append local CA to root cert pool")
}

How to test if https server my client certficate in Golang

Is there an easy way to determine if the server I communicate with from the http.Client accepted and validated my client certificate in mTLS?
In my code I want to know if the remote server accepted the client certificate I put in the transport for the client when I perform a http.Client.Get call.
The http.Response struct has a TLS field that contains the ConnectionState but this is also set when I do not provide a client cert in the transport. I do see that I get more elements in VerifiedChains when I perform a Get with client certificate than without the client certificate.
Thanks for all the suggestions and comments. I will comment on these and share the solution I came up with to get the information I sought.
I am in the process of testing the server and needed to see if the server asked for a client certificate and also which CAs it presents to the client for signing. I want a report similar to what this command produces:
openssl s_client -state -connect example.org:443
Thing is that our server only presents a client certificate for a specific path and not for the servername alone.
The code I came up with looks like this:
...
// Client wraps http.Client and has a callback for flagging a certificate request.
type client struct {
http.Client
clientAutenticated bool
}
// SetClientAuthenticated
func (c *client) SetClientAutenticated(auth bool) {
c.clientAutenticated = auth
}
...
// This prepares a tls config with customized GetClientCertificate
func prepareTLSConfig(setAuth func(bool)) *tls.Config {
certPool := x509.NewCertPool()
err := loadCertFiles(certPool, config.cacerts)
if err != nil {
log.Fatalf("%s", err)
}
cert, err := tls.LoadX509KeyPair(config.cert, config.key)
if err != nil {
log.Fatalf("error loading key pair: %s", err)
}
tlsConfig := &tls.Config{
// Certificates: []tls.Certificate{cert},
RootCAs: certPool,
MinVersion: config.tlsver,
Renegotiation: tls.RenegotiateOnceAsClient,
GetClientCertificate: buildGetClientCertificate([]tls.Certificate{cert}, setAuth),
}
return tlsConfig
}
// buildGetClientCertificate returns a closure that returns an verified client cert
// or an error
func buildGetClientCertificate(certs []tls.Certificate, setAuth func(bool))
func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
// return as closure
return func(requestInfo *tls.CertificateRequestInfo) (*tls.Certificate, error) {
logCertificates(requestInfo.AcceptableCAs)
setAuth(false)
log.Printf("Client cert requested by server")
var err error
for _, c := range certs {
if err = requestInfo.SupportsCertificate(&c); err == nil {
var cert *x509.Certificate
if c.Leaf != nil {
cert = c.Leaf
} else {
cert, _ = x509.ParseCertificate(c.Certificate[0])
}
subject := cert.Subject
issuer := cert.Issuer
log.Printf("Client cert accepted")
log.Printf(" s:/C=%v/ST=%v/L=%v/O=%v/OU=%v/CN=%s", subject.Country,
subject.Province, subject.Locality, subject.Organization,
subject.OrganizationalUnit, subject.CommonName)
log.Printf(" i:/C=%v/ST=%v/L=%v/O=%v/OU=%v/CN=%s", issuer.Country,
issuer.Province, issuer.Locality, issuer.Organization,
issuer.OrganizationalUnit, issuer.CommonName)
// Signal that a suitable CA has been found and therefore
// the client has been authenticated.
setAuth(true)
return &c, nil
}
err = fmt.Errorf("cert not supported: %w", err)
}
log.Print("Could not find suitable client cert for authentication")
return nil, err
}
}
...
// logCertificates logs the acceptableCAs for client certification
func logCertificates(acceptableCAs [][]byte) {
log.Printf("CA Names offered by server")
for _, ca := range acceptableCAs {
var name pkix.RDNSequence
if _, err := asn1.Unmarshal(ca, &name); err == nil {
log.Printf(" %s", name.String() )
}else {
log.Printf("error unmarshalling name: %s", err)
}
}
}

How to make send message to particular URI after successfull webscoket connection?

I have a secure websocket server running on localhost:443/server-demo ( jetty websocket server).
Now I am writing a go client that can communicate with the websocket server. I am able to connect to the websocket server using right certificates. Here is the sample code.
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"log"
)
func main() {
cert, err := tls.LoadX509KeyPair("nifi-1.10.0-bin/nifi-1.10.0/extras/gen-certs/certs/admin.pem", "nifi-1.10.0-bin/nifi-1.10.0/extras/gen-certs/certs/admin-key.pem")
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
conn, err := tls.Dial("tcp", "127.0.0.1:443", &config)
if err != nil {
log.Fatalf("client: dial: %s", err)
}
defer conn.Close()
log.Println("client: connected to: ", conn.RemoteAddr())
state := conn.ConnectionState()
for _, v := range state.PeerCertificates {
fmt.Println(x509.MarshalPKIXPublicKey(v.PublicKey))
fmt.Println(v.Subject)
}
log.Println("client: handshake: ", state.HandshakeComplete)
log.Println("client: mutual: ", state.NegotiatedProtocolIsMutual)
message := "Hello\n"
n, err := io.WriteString(conn, message)
if err != nil {
log.Fatalf("client: write: %s", err)
}
log.Printf("client: wrote %q (%d bytes)", message, n)
reply := make([]byte, 256)
n, err = conn.Read(reply)
log.Printf("client: read %q (%d bytes)", string(reply[:n]), n)
log.Print("client: exiting")
}
The above code throws this error:
"HTTP/1.1 400 No URI\r\nContent-Type: text/html;charset=iso-8859-1\r\nContent-Length: 49\r\nConnection: close\r\nServer: Jetty(9.4.19.v20190610)\r\n\r\n<h1>Bad Message 400</h1><pre>reason: No URI</pre>" (188 bytes)
My question is after making the connection how can I send message to particular URI? i.e I want to send a message to wss://localhost:443/server-demo.
The code in a question does not establish a WebSocket connection to the server.
To establish the WebSocket connection, the application must write a WebSocket handshake to conn and receive the handshake response. See the RFC for the details.
Most applications use a websocket package than handles all of these details. The gorilla/websocket package is a popular choice.
This code should get you started with gorilla:
cert, err := tls.LoadX509KeyPair("nifi-1.10.0-bin/nifi-1.10.0/extras/gen-certs/certs/admin.pem", "nifi-1.10.0-bin/nifi-1.10.0/extras/gen-certs/certs/admin-key.pem")
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
d := websocket.Dialer{
TLSClientConfig: &config,
}
c, _, err := d.Dial("wss://localhost:443/server-demo", nil)
if err != nil {
log.Fatal(err)
}
defer c.Close()
// Use `c` to send and receive messages

failed to complete security handshake on grpc?

I've already changed many times the port number on the server and client, but the server always get the incorrect port number.
when I execute the client the server will log this:
2017/05/07 15:06:07 grpc: Server.Serve failed to complete security handshake from "127.0.0.1:32763": remote error: tls: bad certificate
and on the client side, i got this:
2017/05/07 15:06:07 Failed to dial localhost:8070: connection error: desc = "transport: x509: certificate is not valid for any names, but wanted to match localhost:8070"; please retry.
rpc error: code = Internal desc = connection error: desc = "transport: x509: certificate is not valid for any names, but wanted to match localhost:8070"
I have this code for the server.go
func serve() {
addr := "localhost:8070"
crt, key := certificate.CreatePemKey()
certificate, err := tls.X509KeyPair(crt, key)
if err != nil {
fmt.Println(err)
}
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("F:/GIAG3.crt")
if err != nil {
fmt.Println(err)
}
if ok := certPool.AppendCertsFromPEM(ca); !ok {
fmt.Println("unable to append certificate")
}
lis, err := net.Listen("tcp", addr)
if err != nil {
fmt.Println("could not list on %s: %s", addr, err)
}
// Create the TLS credentials
creds := credentials.NewTLS(&tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{certificate},
ClientCAs: certPool,
})
srv := grpc.NewServer(grpc.Creds(creds))
pb.RegisterPingerServer(srv, &server{})
if err := srv.Serve(lis); err != nil {
fmt.Println("grpc serve error: %s", err)
}
}
and this is for the client.go
func testDial2() {
addr := "localhost:8070"
crt, key := certificate.CreatePemKey()
certificate, err := tls.X509KeyPair(crt, key)
if err != nil {
fmt.Println(err)
}
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("F:/GIAG3.crt")
if err != nil {
fmt.Println(err)
}
if ok := certPool.AppendCertsFromPEM(ca); !ok {
fmt.Println("unable to append certificate")
}
creds := credentials.NewTLS(&tls.Config{
ServerName: addr,
Certificates: []tls.Certificate{certificate},
RootCAs: certPool,
})
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds))
if err != nil {
fmt.Println(err)
}
defer conn.Close()
c := pb.NewPingerClient(conn)
r, err := c.Ping(context.Background(), &pb.Payload{Message: "Ping"})
if err != nil {
fmt.Println(err)
}
log.Printf("%s", r.Message)
}
this is for the CreatePemKey, it is based on this example https://golang.org/src/crypto/tls/generate_cert.go
func publicKey(priv interface{}) interface{} {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
default:
return nil
}
}
func pemBlockForKey(priv interface{}) *pem.Block {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err)
os.Exit(2)
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
return nil
}
}
func CreatePemKey() (certpem, keypem []byte) {
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
notBefore := time.Now()
notAfter := notBefore.AddDate(1, 0, 0)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Acme Co"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
// template.IPAddresses = append(template.IPAddresses, net.ParseIP("localhost"))
template.IsCA = true
derbytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
certpem = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derbytes})
keypem = pem.EncodeToMemory(pemBlockForKey(priv))
return certpem, keypem
}
BTW the GIAG3.crt is came from here https://pki.goog/
Please help me, thanks
If your server certificate doesn't have the domain definition and it's not signed by GIAG3 (like your example), you should add InsecureSkipVerify (this allow you skip the validation of the server name and the server certificate) in the client configuration, that will fix the issue with the invalid name.
creds := credentials.NewTLS(&tls.Config{
ServerName: addr,
Certificates: []tls.Certificate{certificate},
RootCAs: certPool,
InsecureSkipVerify: true,
})
But you'll have another problem, because the client is using a self-signed certificate, and the server required a certificate signed by GIAG3 for the authentication (tls.RequireAndVerifyClientCert), so you have some options with this,
you use a certificate signed by GIAG3 for the client.
reduce the authentication type to tls.RequireAnyClientCert, this allow you use any certificate at the moment of the auth (it could be or not signed by GIAG3), the client just need to use any certificate when it's connected.
remove the client authentication with certificate.
If you found this log in journalctl -u docker:
mai 29 10:33:04 ca275nt dockerd[1523]: time="2019-05-29T10:33:04.454362399-03:00" level=warning msg="grpc: Server.Serve failed to complete security handshake from \"192.168.0.45:58392\": remote error: tls: bad certificate" module=grpc
It can be related with docker swarm requests from another swarm cluster node host trying to connect to docker swarm master node with a invalid token.
You can discover the invalid cluster node hostname with:
nslookup 192.168.0.45
45.0.168.192.in-addr.arpa name = hostabc.domain.com.
45.0.168.192.in-addr.arpa name = hostabc.
Authoritative answers can be found from:
By running a docker swarm leave on the reported host, resolve the issue.
I know it's not related with the main question, but I was searching for:
docker "grpc: Server.Serve failed to complete security handshake from" and this was the first question. then I think it's useful to put the troubleshooting here to save some time to others.

Resources