TLS : Handshake Failure Using GoLang tls client - go

I'm trying to connect to a server over SSL/TLS using golang http/tsl client which is resulting in 'Handshake Faliure(40)' error, but for some reason, this same endpoint works with CURL command. After some debugging, I have collected the following data.
func PrepCerts(certMap map[string]string) (*http.Transport, bool) {
ok := false
tlsConfig := &tls.Config{}
if len(certMap["ca"]) > 0 {
caCert, err := ioutil.ReadFile(certMap["ca"])
fmt.Println("caCert : ", caCert)
if err != nil {
log.Fatal(err)
} else {
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
(*tlsConfig).RootCAs = caCertPool
ok = true
}
}
if len(certMap["cert"]) > 0 && len(certMap["key"]) > 0 {
cert, err := tls.LoadX509KeyPair(certMap["cert"], certMap["key"])
fmt.Println("cert : ", cert)
if err != nil {
log.Fatal(err)
} else {
(*tlsConfig).Certificates = []tls.Certificate{cert}
ok = true
}
}
tlsConfig.BuildNameToCertificate()
return &http.Transport{TLSClientConfig: tlsConfig}, ok
}
Code that uses above function
client := &http.Client{
Timeout: timeout,
}
//certMap = map[string]string{
// ca : "filelocation",
// cert : "filelocation",
// key " "filelocation",
//}
if transport, ok := PrepCerts(certMap); ok {
(*client).Transport = transport
}
resp, err := client.Do(req)

From the captured packets it can be seen that the server is requesting a certificate from the client (Certificate Request). From the the detailed images included in the question it can also be seen that no certificates are sent by the client (Certificate record with Certificate Length 0).
What can also be seen is that the server complains with an Alert after the client has send the (possible empty) certificate so it likely does not like what the client has sent. So it is for sure not a problem of agreeing to a cipher (server agreed on one already) and not a problem that the client does not like the servers certificate (alert is send by server not client).
Based on your code you are trying to do something with client certificates but based on the pcap it looks like you don't succeed in using one. So somewhere there is the problem.

As stated by FiloSottile on Github.
What I think is happening here is that the Certificate Request applies constraints (RSA vs ECDSA, or a specific issuer) which are not satisfied by your certificate.
Using his suggestions you can override the client transports tls.Config.GetClientCertificate()) method.
After following this advice I came to the conclusion that Go will not present tls.Config.RootCAs along with tls.Config.Certificates in response to a certificate request packet.
To solve this issue combine the client certificate and its CA bundle into a single file before calling x509.LoadX509KeyPair(). Taking note that the order of certificates in the file matters. If the client certificate isn't the first one in the bundle you will get a tls: private key does not match public key error.
Once you have combined the client certificate with its CA bundle you can load them into your client like so.
package main
import (
"crypto/tls"
"io/ioutil"
"net/http"
"time"
log "github.com/sirupsen/logrus"
)
const (
certFile = "/location/of/client_cert.bundle.pem"
keyFile = "/location/of/client_cert.key.pem"
testURL = "https://mtls-site"
)
func main() {
clientCert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
panic(err)
}
client := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
Certificates: []tls.Certificate{clientCert},
},
},
}
resp, err := client.Get(testURL)
if err != nil {
panic(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
log.Infof("%s %s", resp.Status, body)
}

Related

Golang - Mutual TLS with Identity from Keychain cannot use signer (type crypto.Signer) as type []byte in argument to tls.X509KeyPair

I am trying to find a way to keychain https client certificate based authentication from macOS system. When I export the certificate and the key I can already successfully connect to my server, but that is not my goal. From the keychain it looks more difficult. I can't get the private key into the TLS Certificate Config. Maybe someone has an idea or a better way. Thanks a lot
package main
import (
"crypto/tls"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/github/certstore"
"crypto/x509"
)
func HttpClient() (client *http.Client) {
// Open Keychain Certificate System Store
store, err := certstore.Open()
if err != nil {
log.Println(err)
}
defer store.Close()
// Get all in Identities
idents, err := store.Identities()
if err != nil {
log.Println(err)
}
// Iterate through the identities, looking for the one we want.
var me certstore.Identity
for _, ident := range idents {
defer ident.Close()
crt, errr := ident.Certificate()
if errr != nil {
log.Println(errr)
}
if crt.Subject.CommonName == "SCEP Identity UUID12345" {
me = ident
log.Println(crt.Subject.CommonName)
log.Println("Org: ",crt.Subject.Organization)
log.Println("Signer:", ident.Signer)
// Everything works fine when i load the cert and key from disk.
// x509cert, err := tls.LoadX509KeyPair("client.pem", "client.key")
chain, _ := ident.CertificateChain()
signer, er := me.Signer()
if er != nil {
log.Println("PrivateKey Error:",er)
}
certsnew := tls.Certificate{
Leaf: chain[0],
Certificate: serializeChain(chain),
PrivateKey: signer,
}
// Test to create KeyPair but does not work
// Error: cannot use signer (type crypto.Signer) as type []byte in argument to tls.X509KeyPair
tlsKeyPair, err := tls.X509KeyPair(chain[0].Raw, signer)
certs := []tls.Certificate{certsnew}
// if len(certs) == 0 {
// client = &http.Client{}
// return
// }
tr := &http.Transport{
TLSClientConfig: &tls.Config{Certificates: certs,
InsecureSkipVerify: true,
Renegotiation: tls.RenegotiateOnceAsClient},
}
client = &http.Client{Transport: tr}
}
}
return
}

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 get x509 Certificate from http Client in Go

I've been over the docs at https://golang.org/pkg/ but can't make this connection.
I am creating a client and request like so (error handling removed):
client := http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
req, reqErr := http.NewRequest(requestMethod, requestUrl, nil)
resp, clientErr := client.Do(req)
I need to get a x509.Certificate to read details of the cert returned from the server, but still need the http.Repsonse as well.
How can I get a x509.Certificate instance and an http.Response while only making a single request?
The response has a TLS *tls.ConnectionState field, which in turn has:
type ConnectionState struct {
// other fields
PeerCertificates []*x509.Certificate // certificate chain presented by remote peer
}
so you can just do:
resp, clientErr := client.Do(req)
if clientErr != nil {
panic(clientErr)
}
if resp.TLS != nil {
certificates := resp.TLS.PeerCertificates
if len(certificates) > 0 {
// you probably want certificates[0]
cert := certificates[0]
}
}

How to force client to use http/2? (instead of falling back to http 1.1)

How can I force a simple Go client to use HTTP/2 and prevent it from falling back to HTTP 1.1 ?
I have a simple HTTP/2 server running on "localhost" and it returns details of the request in its reply. Here is the output using Google Chrome for this URL: https://localhost:40443/bananas
I like bananas!
Method = GET
URL = /bananas
Proto = HTTP/2.0
Host = localhost:40443
RequestURI = /bananas
But here is what I get for my Go client code. You can see it falls back to HTTP 1.1
I like monkeys!
Method = GET
URL = /monkeys
Proto = HTTP/1.1
Host = localhost:40443
RequestURI = /monkeys
Below is the source code of my best attempt to contact the same server using HTTP/2, but it always falls back to HTTP 1.1
// simple http/2 client
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net/http"
)
const (
certFile = "client-cert.pem"
keyFile = "client-key.pem"
caFile = "server-cert.pem"
)
func main() {
// Load client certificate
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Fatal(err)
}
// Load CA cert
caCert, err := ioutil.ReadFile(caFile)
if err != nil {
log.Fatal(err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// Setup HTTPS client
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
}
tlsConfig.BuildNameToCertificate()
transport := &http.Transport{TLSClientConfig: tlsConfig}
client := &http.Client{Transport: transport}
response, err := client.Get("https://localhost:40443/monkeys")
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
// dump response
text, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Body:\n%s", text)
}
Any suggestions would be appreciated, including pointers to other working examples that illustrate how to make HTTP/2 client requests in Go.
First import "golang.org/x/net/http2" package. And then change
transport := &http.Transport{TLSClientConfig: tlsConfig}
to
transport := &http2.Transport{TLSClientConfig: tlsConfig}

Transport options to ensure net/http client connect via TLS 1.2

I have a go service that makes REST requests to an HTTP server that I don't control. A customer asked my to "confirm" that my service is connecting via TLS 1.2. Is that something that I can do in code?
Current code looks something like this:
request, _ := http.NewRequest("PUT",
"https://example.com/path/to/endpoint",
bytes.NewReader(json))
client := &http.Client{}
response, _ := client.Do(request)
defer response.Body.Close()
str, err := ioutil.ReadAll(response.Body)
Based on a quick read of the docs I believe I need to use a Transport and build my client using that transport. Something like this:
tr := &http.Transport{
... some options here ...
}
client := &http.Client{Transport: tr}
But I'm not sure what options I should set.
At the time of writing, Go will speak TLS 1.2 automatically if the server supports it.
tls.ConnectionState reports various negotiated TLS parameters of a connection, including the protocol version.
To get the underlying TLS connection for an HTTP client it is easiest to set the DialTLS field of the Transport to a function that establishes and remembers the connection. Once the response arrived (but before you close the response body!), call tls.Conn.ConnectionState:
package main
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
)
func main() {
var (
conn *tls.Conn
err error
)
tlsConfig := http.DefaultTransport.(*http.Transport).TLSClientConfig
c := &http.Client{
Transport: &http.Transport{
DialTLS: func(network, addr string) (net.Conn, error) {
conn, err = tls.Dial(network, addr, tlsConfig)
return conn, err
},
},
}
res, err := c.Get("https://example.com")
if err != nil {
log.Fatal(err)
}
versions := map[uint16]string{
tls.VersionSSL30: "SSL",
tls.VersionTLS10: "TLS 1.0",
tls.VersionTLS11: "TLS 1.1",
tls.VersionTLS12: "TLS 1.2",
}
fmt.Println(res.Request.URL)
fmt.Println(res.Status)
v := conn.ConnectionState().Version
fmt.Println(versions[v])
res.Body.Close()
}
// Output:
// https://example.com
// 200 OK
// TLS 1.2
From the docs
Package tls partially implements TLS 1.2, as specified in RFC 5246.
That beeing said I keep this function as a snippet to create the necessary configuration:
func NewTLSConfig(clientCertFile, clientKeyFile, caCertFile string) (*tls.Config, error) {
tlsConfig := tls.Config{}
// Load client cert
cert, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile)
if err != nil {
return &tlsConfig, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
// Load CA cert
caCert, err := ioutil.ReadFile(caCertFile)
if err != nil {
return &tlsConfig, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig.RootCAs = caCertPool
tlsConfig.BuildNameToCertificate()
return &tlsConfig, err
}
After that you just need to initialize the transport:
transport := &http.Transport{TLSClientConfig: tlsConfig}
client := &http.Client{Transport: transport}

Resources