Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get client certificates in Go HTTPS server

I'm trying to understand how to get client's certificates in Go web server. Here is a server code:

package main

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

func defaultHandler(w http.ResponseWriter, r *http.Request) {
    dump, err := httputil.DumpRequest(r, true)
    log.Println("HTTP request", r, string(dump), err)
    log.Println("HTTP TLS", r.TLS, string(r.TLS.TLSUnique))
    certs := r.TLS.PeerCertificates
    log.Println("HTTP CERTS", certs)
    w.WriteHeader(http.StatusMethodNotAllowed)
    w.Write([]byte("Hello"))
}

func main() {
    http.HandleFunc("/", defaultHandler)
    http.ListenAndServeTLS(":8080", "server.crt", "server.key", nil)
}

and here is client code

package main

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

func HttpClient() (client *http.Client) {
    uckey := os.Getenv("X509_USER_KEY")
    ucert := os.Getenv("X509_USER_CERT")
    x509cert, err := tls.LoadX509KeyPair(ucert, uckey)
    if err != nil {
        panic(err.Error())
    }
    certs := []tls.Certificate{x509cert}
    if len(certs) == 0 {
       client = &http.Client{}
       return
    }
    tr := &http.Transport{
        TLSClientConfig: &tls.Config{Certificates: certs,
        InsecureSkipVerify: true},
    }
    client = &http.Client{Transport: tr}
    return
}

func main() {
    rurl := "https://localhost:8080"
    client := HttpClient()
    req, err := http.NewRequest("GET", rurl, nil)
    if err != nil {
       log.Println("Unable to make GET request", err)
       os.Exit(1)
    }
    req.Header.Add("Accept", "*/*")
    resp, err := client.Do(req)
    if err != nil {
        log.Println(err)
        os.Exit(1)
    }
    defer resp.Body.Close()
    data, err := ioutil.ReadAll(resp.Body)
    log.Println(string(data))
}

If I run both server and a client I see the following on a server side:

2017/02/08 15:46:49 HTTP request &{GET / HTTP/1.1 1 1 map[User-Agent:[Go-http-client/1.1] Accept:[*/*] Accept-Encoding:[gzip]] {} 0 [] false localhost:8080 map[] map[] <nil> map[] 127.0.0.1:58941 / 0xc4204ef080 <nil> <nil> 0xc420014d40} GET / HTTP/1.1
Host: localhost:8080
Accept: */*
Accept-Encoding: gzip
User-Agent: Go-http-client/1.1

 <nil>
2017/02/08 15:46:49 HTTP TLS &{771 true false 49195  true localhost [] [] []   [] [203 144 196 105 155 216 89 105 83 90 93 4]} ːiSZ]
2017/02/08 15:46:49 HTTP CERTS []

As you can see the client's certificates are empty.

While if I invoke curl call to a server providing my certificates, then I can see server certificates:

curl -L -k --key mykey.key --cert mycert.pem -vvv https://localhost:8080
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /opt/local/share/curl/curl-ca-bundle.crt
    CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-ECDSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=US; ST=NY; L=Town; O=Bla-Bla
*  start date: Feb  8 14:12:06 2017 GMT
*  expire date: Feb  6 14:12:06 2027 GMT
*  issuer: C=US; ST=NY; L=Ithaca; O=Cornell
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.52.1
> Accept: */*

As you can see SSL negotiation is in place and curl client successfully reports server certificate. What I need is to access client's certificate on a server side to do proper authentication. But so far I can't see any client's certificate.

Any help is really welcome. Thanks, Valentin.

like image 828
Valentin Avatar asked Feb 08 '17 21:02

Valentin


People also ask

How can I get certificate from https server?

To obtain an HTTPS certificate, perform the following steps: Create a private and public key pair, and prepare a Certificate Signing Request (CSR), including information about the organization and the public key. Contact a certification authority and request an HTTPS certificate, based on the CSR.

How do I get a client certificate for my browser?

In Chrome, go to Settings. On the Settings page, below Default browser, click Show advanced settings. Under HTTPS/SSL, click Manage certificates. In the Certificates window, on the Personal tab, you should see your Client Certificate.


1 Answers

The client shouldn't send a certificate unless requested. Set ClientAuth in the tls.Config to an appropriate tls.ClientAuthType.

For example, to only request that a client send a certificate, you can use:

server := &http.Server{
    Addr: ":8080",
    TLSConfig: &tls.Config{
        ClientAuth: tls.RequestClientCert,
    },
}

server.ListenAndServeTLS("server.crt", "server.key")
like image 180
JimB Avatar answered Sep 18 '22 06:09

JimB