How to get client certificates in Go HTTPS server

11,713

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")
Share:
11,713
Valentin
Author by

Valentin

Updated on June 04, 2022

Comments

  • Valentin
    Valentin almost 2 years

    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.

  • bithavoc
    bithavoc over 5 years
    FYI, this requests the client to present a certificate, that doesn't mean it's a valid certificate. you probably need RequireAndVerifyClientCert, here's a full example: gist.github.com/xjdrew/97be3811966c8300b724deabc10e38e2
  • Nick Roz
    Nick Roz over 3 years
    what about mtls?
  • JimB
    JimB over 3 years
    @NickRoz: what is the question about mTLS? There are multiple ClientAuthType values, and there is a ClientCAs field in the tls.Config.