I'm trying to set up some logging/monitoring for a Golang application. I want to be alerted if an SSL handshake error occurs. In Java I looked for the string "javax.net.ssl.SSLHandshakeException". Is there an equivalent in Golang for when something goes wrong with an SSL handshake?
Golang has no exceptions, but funcs return errors. This code would set err if the handshake fails:
conf := &tls.Config{}
tlsCon, err := tls.Dial("tcp", "example.com:443", conf)
if err != nil { // err is set when handshake fails
fmt.Println(err.Error())
return
}
tlsCon.Close()
Related
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
}
I am trying to understand the mutual TLS working, I have the following example:
I have a client who wants to connect to server "svc1.example.com"
but the server has a
server certificate with a commonName as "svc1.example.cloud" and a SAN
as "svc.example.test.cloud".
Now when I make a GET request, I get the following:
x509: certificate is valid for svc.example.test.cloud, not svc1.example.com.
So, my question is should I make a the TLS clientConfig changes to include the servername? or should I add a custom verifyPeerCertificate function in the TLS client config, something like below?
Please, let me know, what should be the Servername and what should I check for in the verifyPeerCertificate function.
func customverify(customCName func(*x509.Certificate) bool) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if customCName == nil {
return nil
}
return func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
for _, certs := range verifiedChains {
leaf := certs[0]
if customCName(leaf) {
return nil
}
}
return fmt.Errorf("client identity verification failed")
}
}
func configureClient(certFile, keyFile string) (*http.Client, error) {
certpool, err := addRootCA()
if err != nil {
return nil, err
}
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
transport := ytls.NewClientTransport()
transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
transport.TLSClientConfig.RootCAs = certpool
//transport.TLSClientConfig.ServerName = expectedCName
transport.TLSClientConfig.VerifyPeerCertificate = customverify(func(cert *x509.Certificate) bool {
return cert.Subject.CommonName == "svc1.example.cloud"
})
httpClient := &http.Client{Transport: transport}
return httpClient, nil
}
Since x509: certificate is valid for svc.example.test.cloud, so transport.TLSClientConfig.ServerName = "svc.example.test.cloud"
From https://golang.org/pkg/crypto/tls/#Config
VerifyPeerCertificate, if not nil, is called after normal
certificate verification by either a TLS client or server. It
receives the raw ASN.1 certificates provided by the peer and also
any verified chains that normal processing found. If it returns a
non-nil error, the handshake is aborted and that error results.
If normal verification fails then the handshake will abort before
considering this callback. If normal verification is disabled by
setting InsecureSkipVerify, or (for a server) when ClientAuth is
RequestClientCert or RequireAnyClientCert, then this callback will
be considered but the verifiedChains argument will always be nil.
VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
So if normal verification fails, then VerifyPeerCertificate won't get called. Also if normal verification is passed, i don't think you need this extra check VerifyPeerCertificate.
What I'm trying to do:
Build a package (later usage) that provides a method to execute a get-request to any page through a given socks5 proxy.
My problem:
When ever I try to request a page with SSL (https) I get the following error:
Error executing request Get https://www.xxxxxxx.com: socks connect tcp 83.234.8.214:4145->www.xxxxxxx.com:443: EOF
However requesting http://www.google.com is working fine. So there must be a problem with the SSL connection. Can't imagine why this isn't working as I'm not very experienced with SSL-connections. End of file makes no sense to me.
My current code:
func main() {
// public socks5 - worked when I created this question
proxy_addr := "83.234.8.214:4145"
// With this address I get the error
web_addr := "https://www.whatismyip.com"
// Requesting google works fine
//web_addr := "http://www.google.com"
dialer, err := proxy.SOCKS5("tcp", proxy_addr, nil, proxy.Direct)
handleError(err, "error creating dialer")
httpTransport := &http.Transport{}
httpClient := &http.Client{Transport: httpTransport}
httpTransport.DialTLS = dialer.Dial
req, err := http.NewRequest("GET", web_addr, nil)
handleError(err, "error creating request")
httpClient.Timeout = 5 * time.Second
resp, err := httpClient.Do(req)
handleError(err, "error executing request")
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
handleError(err, "error reading body")
fmt.Println(string(b))
}
func handleError(err error, msg string) {
if err != nil {
log.Fatal(err)
}
}
So what am I missing in here to deal with ssl-connections?
Thank you very much.
Edit 1:
In case someone would think this is an issue with whatismyip.com I've done some more tests:
https://www.google.com
EOF error
https://stackoverflow.com
EOF error
https://www.youtube.com/
EOF error
Connection between your program and your socks5 proxy goes not through SSL/TLS
So you should change line
httpTransport.DialTLS = dialer.Dial
to
httpTransport.Dial = dialer.Dial
I checked https://www.whatismyip.com and https://www.google.com.
URLs are downloaded fine.
For test I set up 3proxy service on my server, test your code with fixed line and check 3proxy logs.
All made requests was in proxy server logs.
If you need more help - please let me know, I'll help
Things to notice:
Socks5 proxies need to support SSL connections.
The code from the question won't work with this answer as the proxy (used in the code) isn't supporting SSL connections.
I find myself needing to set up a WebSocket connection in a hostile environment in which a firewall sniffs SNI information from TLS which I'd rather it didn't. In my particular case, the WebSocket server does not use SNI for request handling, so as such, the SNI part of the handshake could be safely removed.
My question then becomes: In the golang.org WebSocket package, golang.org/x/net/websocket, what is the simplest way to strip SNI information while retaining validation of the provided chain?
The best I have been able to come up with is to simply replace the hostname of the URL to be dialled with its corresponding IP. This causes crypto/tls to never add the problematic SNI information, but, in the solution I was able to come up with, a custom validator ends up having to be provided to validate the chain:
func dial(url string, origin string) (*websocket.Conn, error) {
// Use system resolver to get IP of host
hostRegExp := regexp.MustCompile("//([^/]+)/")
host := hostRegExp.FindStringSubmatch(url)[1]
addrs, err := net.LookupHost(host)
if err != nil {
return fmt.Errorf("Could not resolve address of %s: %v", host, err)
}
ip := addrs[0]
// Replace the hostname in the given URL with its IP instead
newURL := strings.Replace(url, host, ip, 1)
config, _ := websocket.NewConfig(newURL, origin)
// As we have removed the hostname, the Go TLS package will not know what to
// validate the certificate DNS names against, so we have to provide a custom
// verifier based on the hostname we threw away.
config.TlsConfig = &tls.Config{
InsecureSkipVerify: true,
VerifyPeerCertificate: verifier(host),
}
return websocket.DialConfig(config)
}
func verifier(host string) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
// For simplicity, let us only consider the case in which the first certificate is the one
// to validate, and in which it is signed directly by a CA, with no parsing of
// intermediate certificates required.
opts := x509.VerifyOptions{
DNSName: host,
}
rawCert := rawCerts[0]
cert, err := x509.ParseCertificate(rawCert)
if err != nil {
return err
}
_, err = cert.Verify(opts)
return err
}
}
This totally works but seems rather clunky. Is there a simpler approach? (Ideally one that is not specific to WebSocket applications but works for TLS in general; the exact same idea as above could be applied to HTTPS.)
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.