如何在Go HTTPS服务器中获取客户端证书

huangapple go评论79阅读模式
英文:

How to get client certificates in Go HTTPS server

问题

我正在尝试理解如何在Go Web服务器中获取客户端证书。以下是服务器代码:

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)
}

以下是客户端代码:

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))
}

如果我同时运行服务器和客户端,我在服务器端看到以下内容:

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 []

如你所见,客户端的证书为空。

而如果我使用curl调用服务器并提供我的证书,我可以看到服务器证书:

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: */*

如你所见,SSL协商已经建立,curl客户端成功报告了服务器证书。我需要的是在服务器端访问客户端的证书以进行适当的身份验证。但到目前为止,我看不到任何客户端的证书。

非常感谢您的帮助。
Valentin.

英文:

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

package main

import (
    &quot;log&quot;
    &quot;net/http&quot;
    &quot;net/http/httputil&quot;
)

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

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

and here is client code

package main

import (
    &quot;crypto/tls&quot;
    &quot;io/ioutil&quot;
    &quot;log&quot;
    &quot;net/http&quot;
    &quot;os&quot;
)

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

func main() {
    rurl := &quot;https://localhost:8080&quot;
    client := HttpClient()
    req, err := http.NewRequest(&quot;GET&quot;, rurl, nil)
    if err != nil {
	   log.Println(&quot;Unable to make GET request&quot;, err)
	   os.Exit(1)
    }
    req.Header.Add(&quot;Accept&quot;, &quot;*/*&quot;)
    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 &amp;{GET / HTTP/1.1 1 1 map[User-Agent:[Go-http-client/1.1] Accept:[*/*] Accept-Encoding:[gzip]] {} 0 [] false localhost:8080 map[] map[] &lt;nil&gt; map[] 127.0.0.1:58941 / 0xc4204ef080 &lt;nil&gt; &lt;nil&gt; 0xc420014d40} GET / HTTP/1.1
Host: localhost:8080
Accept: */*
Accept-Encoding: gzip
User-Agent: Go-http-client/1.1

 &lt;nil&gt;
2017/02/08 15:46:49 HTTP TLS &amp;{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.
&gt; GET / HTTP/1.1
&gt; Host: localhost:8080
&gt; User-Agent: curl/7.52.1
&gt; 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.

答案1

得分: 11

客户端在未被请求时不应发送证书。在 tls.Config 中设置 ClientAuth 为适当的 tls.ClientAuthType

例如,要求客户端仅发送证书,可以使用以下代码:

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

server.ListenAndServeTLS("server.crt", "server.key")
英文:

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 := &amp;http.Server{
    Addr: &quot;:8080&quot;,
    TLSConfig: &amp;tls.Config{
        ClientAuth: tls.RequestClientCert,
    },
}

server.ListenAndServeTLS(&quot;server.crt&quot;, &quot;server.key&quot;)

huangapple
  • 本文由 发表于 2017年2月9日 05:02:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/42123532.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定