golang证书认证通信

<style></style>

1. Golang中证书相关包

  • crypto/tls实现tls1.2和tls1.3。
type Config struct {    
     ......
    // Certificates contains one or more certificate chains to present to the
    // other side of the connection. The first certificate compatible with the
    // peer's requirements is selected automatically.
    //
// Server configurations must set one of Certificates, GetCertificate or    
// GetConfigForClient. Clients doing client-authentication may set either
    // Certificates or GetClientCertificate.
    //
    // Note: if there are multiple Certificates, and they don't have the
    // optional field Leaf set, certificate selection will incur a significant
    // per-handshake performance cost.
    Certificates []Certificate
    // RootCAs defines the set of root certificate authorities
    // that clients use when verifying server certificates.
    // If RootCAs is nil, TLS uses the host's root CA set.
    RootCAs *x509.CertPool
    // ServerName is used to verify the hostname on the returned
    // certificates unless InsecureSkipVerify is given. It is also included
    // in the client's handshake to support virtual hosting unless it is
    // an IP address.
    ServerName string
    // ClientAuth determines the server's policy for
    // TLS Client Authentication. The default is NoClientCert.
    ClientAuth ClientAuthType
    // ClientCAs defines the set of root certificate authorities
    // that servers use if required to verify a client certificate
    // by the policy in ClientAuth.
    ClientCAs *x509.CertPool
    // InsecureSkipVerify controls whether a client verifies the
    // server's certificate chain and host name.
    // If InsecureSkipVerify is true, TLS accepts any certificate
    // presented by the server and any host name in that certificate.
    // In this mode, TLS is susceptible to man-in-the-middle attacks.
    // This should be used only for testing.
InsecureSkipVerify bool
......
}
<style></style>

LoadX509KeyPair reads and parses a public/private key pair from a pair of files.

func LoadX509KeyPair(certFile, keyFile string) (Certificate, error)
<style></style>
  • crypot/x509 解析X.509格式的密钥和证书。

type CertPool struct {
    // contains filtered or unexported fields
}
<style></style>

CertPool is a set of certificates.

func NewCertPool() *CertPool
<style></style>

NewCertPool返回一个空的CertPool。

func SystemCertPool() (*CertPool, error)
<style></style>

SystemCertPool returns a copy of the system cert pool.

func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool)
<style></style>

AppendCertsFromPEM attempts to parse a series of PEM encoded certificates. It appends any certificates found to s and reports whether any certificates were successfully parsed.

type Certificate struct {    
Raw                     []byte // Complete ASN.1 DER content (certificate, signature algorithm and signature).    
RawTBSCertificate       []byte // Certificate part of raw ASN.1 DER content.    RawSubjectPublicKeyInfo []byte // DER encoded SubjectPublicKeyInfo.    
RawSubject              []byte // DER encoded Subject    
RawIssuer               []byte // DER encoded Issuer
Signature          []byte    
SignatureAlgorithm SignatureAlgorithm
PublicKeyAlgorithm PublicKeyAlgorithm    
PublicKey          interface{}
Version             int    
SerialNumber        *big.Int    
Issuer              pkix.Name    
Subject             pkix.Name    
NotBefore, NotAfter time.Time // Validity bounds.    
KeyUsage            KeyUsage
    // Extensions contains raw X.509 extensions. When parsing certificates,
    // this can be used to extract non-critical extensions that are not
    // parsed by this package. When marshaling certificates, the Extensions
    // field is ignored, see ExtraExtensions.
    Extensions []pkix.Extension // Go 1.2
    // ExtraExtensions contains extensions to be copied, raw, into any
    // marshaled certificates. Values override any extensions that would
    // otherwise be produced based on the other fields. The ExtraExtensions
    // field is not populated when parsing certificates, see Extensions.
    ExtraExtensions []pkix.Extension // Go 1.2
    // UnhandledCriticalExtensions contains a list of extension IDs that
    // were not (fully) processed when parsing. Verify will fail if this
    // slice is non-empty, unless verification is delegated to an OS
    // library which understands all the critical extensions.
    //
    // Users can access these extensions using Extensions and can remove
    // elements from this slice if they believe that they have been
    // handled.
    UnhandledCriticalExtensions []asn1.ObjectIdentifier // Go 1.5
ExtKeyUsage        []ExtKeyUsage           // Sequence of extended key usages.    
UnknownExtKeyUsage []asn1.ObjectIdentifier // Encountered extended key usages unknown to this package.
    // BasicConstraintsValid indicates whether IsCA, MaxPathLen,
    // and MaxPathLenZero are valid.
BasicConstraintsValid bool    
IsCA                  bool
    // MaxPathLen and MaxPathLenZero indicate the presence and
    // value of the BasicConstraints' "pathLenConstraint".
    //
    // When parsing a certificate, a positive non-zero MaxPathLen
    // means that the field was specified, -1 means it was unset,
    // and MaxPathLenZero being true mean that the field was
    // explicitly set to zero. The case of MaxPathLen==0 with MaxPathLenZero==false
    // should be treated equivalent to -1 (unset).
    //
    // When generating a certificate, an unset pathLenConstraint
    // can be requested with either MaxPathLen == -1 or using the
    // zero value for both MaxPathLen and MaxPathLenZero.
    MaxPathLen int    // MaxPathLenZero indicates that BasicConstraintsValid==true
    // and MaxPathLen==0 should be interpreted as an actual
    // maximum path length of zero. Otherwise, that combination is
    // interpreted as MaxPathLen not being set.
    MaxPathLenZero bool // Go 1.4
SubjectKeyId   []byte    
AuthorityKeyId []byte

// RFC 5280, 4.2.2.1 (Authority Information Access)    
OCSPServer            []string // Go 1.2    
IssuingCertificateURL []string // Go 1.2

    // Subject Alternate Name values. (Note that these values may not be valid
    // if invalid values were contained within a parsed certificate. For
// example, an element of DNSNames may not be a valid DNS domain name.)    DNSNames       []string    
EmailAddresses []string    
IPAddresses    []net.IP // Go 1.1    
URIs           []*url.URL // Go 1.10

// Name constraints    
PermittedDNSDomainsCritical bool // if true then the name constraints are marked critical.    
PermittedDNSDomains         []string    
ExcludedDNSDomains          []string // Go 1.9    
PermittedIPRanges           []*net.IPNet // Go 1.10    
ExcludedIPRanges            []*net.IPNet // Go 1.10    PermittedEmailAddresses     []string // Go 1.10    
ExcludedEmailAddresses      []string // Go 1.10    
PermittedURIDomains         []string // Go 1.10    
ExcludedURIDomains          []string // Go 1.10

// CRL Distribution Points    
CRLDistributionPoints []string // Go 1.2
    PolicyIdentifiers []asn1.ObjectIdentifier
}
<style></style>

示例:

    caCertPath := "cert/ca.crt"
    caCrt, err := ioutil.ReadFile(caCertPath)
    if err != nil {
        fmt.Println("ReadFile err:", err)
        return
    }
    pool.AppendCertsFromPEM(caCrt)
<style></style>
  • net/http提供HTTP的client和server实现。

客户端定制:For control over HTTP client headers, redirect policy等:

client := &http.Client{
    CheckRedirect: redirectPolicyFunc,
    Transport: tr,
}
<style></style>

Transport定制:For control over proxies, TLS configuration, keep-alives, compression等:

tr := &http.Transport{
    MaxIdleConns:       10,
    IdleConnTimeout:    30 * time.Second,
    DisableCompression: true,
    TLSClientConfig: &tls.Config{
        RootCAs: pool,
        Certificates: [] tls.Certificate{clicrt}
    }
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com")
<style></style>

服务端定制:

s := &http.Server{
    Addr:           ":8080",
    Handler:        myHandler,
    ReadTimeout:    10 * time.Second,
    WriteTimeout:   10 * time.Second,
    MaxHeaderBytes: 1 << 20,
    TLSConfig: &tls.Config{
        ClientCAs: pool,
         ClientAuth: tls.RequireAndVerifyClientCert,
    }
}
// log.Fatal(s.ListenAndServe())
log.Fatal(s.ListenAndServe("server.crt", "server.key"))
<style></style>

HTTPS监听

Files containing a certificate and matching private key for the server must be provided.

func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error
<style></style>

ListenAndServeTLS listens on the TCP network address srv.Addr and then calls ServeTLS to handle requests on incoming TLS connections. Accepted connections are configured to enable TCP keep-alives.

func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error
<style></style>

ListenAndServeTLS always returns a non-nil error. After Shutdown or Close, the returned error is ErrServerClosed. Server需要定制时使用。

<style></style>

2. 认证过程

单向认证过程:

客户点包含ca.crt,服务端包含server.key和server.crt。

客户端:客户端生成一个随机数random-client,传到服务器端;

服务端:服务器端接收消息之后,生成一个随机数random-server和包含公钥的证书,一起回馈给客户端;

客户端:客户端收到的东西原封不动,加上premaster secret(通过random-client、random-server 经过一定算法生成的数据),再一次送给服务器端,这次传过去的东西是经过服务端的公钥进行加密后数据;

服务端:服务端经过私钥(server.key),进行解密,获取 premaster secret(协商密钥过程);

此时客户端和服务器端都拥有了三个要素:random-client、random-server和premaster secret,安全通道已经建立,以后的交流都会校检上面的三个要素通过算法算出的session key。

双向认证过程相当于客户端和服务端反过来再执行认证、加解密、协商一遍。

3. 单向认证

单向认证只需要服务器端有证书即可。

CA证书

openssl  genrsa  -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -subj "/CN=wang.com" -days 365 -out ca.crt
<style></style>

Server证书

用CA证书签发server证书。

openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=server" -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt -days 365
<style></style>

Server.go

服务器监听在:https://server:8088,域名是server证书申请的CN。

单向认证时服务端只需要使用http.ListenAndServeTLS()或srv.ListenAndServeTLS()导入证书即可。一般情况下,不需要配置Server,直接采用默认的http.ListenAndServeTLS()。

package main

import (
    "fmt"
    "net/http"
    "os"
)

var Addr string = ":8088"

func handler(w http.ResponseWriter, r *http.Request){
    w.Write([]byte("Hello"))
}

func main(){
    http.HandleFunc("/", handler)
    _, err := os.Open("cert/server.crt")
    if err != nil {
        fmt.Println("Can't open server.crt")
        panic(err)
    }

    fmt.Printf("listen...[%s]\n", Addr)
    err = http.ListenAndServeTLS(Addr, "cert/server.crt", "cert/server.key", nil)
    if err != nil {
        fmt.Println(err)
    }
}
<style></style>

Client.go

需要提前将server添加到/etc/hosts中以便本地测试。

单向认证时client端需导入CA根证书,需要定制http.Transport。

Golang默认支持HTTP/2协议,只要使用TLS则默认启动HTTP/2特性,但对http Client做一些定制化配置后,会覆盖掉http库的默认行为,导致开启HTTP/1.1。

package main

import (
    "fmt"
    "crypto/tls"
    "crypto/x509"
    "flag"
    "io/ioutil"
    "log"
    "net/http"
    "golang.org/x/net/http2"
)

var addr = flag.String("addr", "https://server:8088?numa=4&numb=6", "connect to")
var httpVer = flag.Int("httpVer", 2, "HTTP version")

func main(){
    flag.Parse()

    client := &http.Client{}

    caCert, err := ioutil.ReadFile("cert/ca.crt")
    if err != nil {
        log.Fatalf("Reading server certificate: %s", err)
    }

    pool := x509.NewCertPool()
    pool.AppendCertsFromPEM(caCert)
    tlsConfig := &tls.Config{
        RootCAs: pool,
    }

    switch *httpVer {
        case 1:
            client.Transport = &http.Transport {
                TLSClientConfig: tlsConfig,
            }
        case 2:
            client.Transport = &http2.Transport {
                TLSClientConfig: tlsConfig,
            }
    }

    resp, err := client.Get(*addr)
    if err != nil {
        log.Fatalf("Failed get: %s", err)
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatalf("Failed reading response body: %s", err)
    }

    fmt.Printf("Response %d: %s\nbody: %s\n", resp.StatusCode, resp.Proto, string(body))
}
<style></style>

Curl的-k参数可忽略证书验证:

$ curl --cacert "cert/ca.crt" https://server:8088
 Hi, This is an example of https service in golang!
$ curl -k https://server:8088
 Hi, This is an example of https service in golang!
$ curl -v https://server:8088                                
* Rebuilt URL to: https://server:8088/
*   Trying 127.0.0.1...
* Connected to server (127.0.0.1) port 8088 (#0)
* found 133 certificates in /etc/ssl/certs/ca-certificates.crt
* found 403 certificates in /etc/ssl/certs
* ALPN, offering http/1.1
* SSL connection using TLS1.2 / ECDHE_RSA_AES_128_GCM_SHA256
* server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none
* Closing connection 0
<style></style>

如果/CN使用IP地址,就会报如下类似错误:

Get https://10.183.47.206:8081: x509: cannot validate certificate for 10.183.47.206 because it doesn't contain any IP SANs 
<style></style>

Client测试

$ go run client.go
Response 200: HTTP/2.0
body: Hello
$ go run client.go -httpVer=1
Response 200: HTTP/1.1
body: Hello
<style></style> <style></style>

HTTP/1.1非加密(10数据帧)

golang证书认证通信

HTTP/1.1单向认证(17数据帧)

golang证书认证通信

<style></style>

HTTP/2单向认证(22数据帧)

golang证书认证通信

4. 双向认证 

双向认证要求客户端和服务端都要有证书,且都用CA证书验证对端证书。

Client证书

用CA证书签发client证书,而非server证书签发。

注意生成client端证书的时候,要多添加一个字段,golang的server端认证程序会对这个字段进行认证:

openssl genrsa -out client.key 2048
openssl req -new -key client.key -subj "/CN=client" -out client.csr
echo extendedKeyUsage=clientAuth > extfile.cnf
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -set_serial 02  -extfile extfile.cnf -out client.crt -days 365
<style></style>

Server.go

双向认证时需定制http.Server,增加CA证书等。

定制的http.Server的Handler是一个interface,需要实现ServeHTTP()接口函数;

加载服务端的公钥和私钥用于解密客户端发送过来的随机字符;

加载CA证书是为了验证客户端的证书是否合格;

package main

import (
    "fmt"
    "net/http"
    "io/ioutil"
    "crypto/tls"
    "crypto/x509"
)

type myhandler struct{
}

func (h *myhandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, 
        " Hi, This is an example of https service in golang!\n")
}

func main(){
    pool := x509.NewCertPool()
    caCertPath := "cert/ca.crt"
    
    caCrt, err := ioutil.ReadFile(caCertPath)
    if err != nil {
        fmt.Println("ReadFile err: ", err)
        return
    }
    pool.AppendCertsFromPEM(caCrt)

    s := &http.Server{
        Addr: ":8088",
        Handler: &myhandler{},
        TLSConfig: &tls.Config{
            ClientCAs: pool,
            ClientAuth: tls.RequireAndVerifyClientCert,
        },
    }

    fmt.Println("listen...")
    err = s.ListenAndServeTLS("cert/server.crt", "cert/server.key")
    if err != nil {
        fmt.Println(err)
    }
}
<style></style>

Client.go

Client需要定制http.Transport以加载客户端证书和CA证书。

Client的证书需要tls.LoadX509KeyPair()导入。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "crypto/tls"
    "crypto/x509"
    "golang.org/x/net/http2"
)

func main(){
    // x509.Certificate
    pool := x509.NewCertPool()

    caCertPath := "cert/ca.crt"
    caCrt, err := ioutil.ReadFile(caCertPath)
    if err != nil {
        fmt.Println("ReadFile err:", err)
        return
    }
    pool.AppendCertsFromPEM(caCrt)

    cliCrt, err := tls.LoadX509KeyPair("cert/client.crt", "cert/client.key")
    if err != nil {
        fmt.Println("LoadX509keypair err: ", err)
        return
    }

//    tr := &http2.Transport{  // http2协议
    tr := &http.Transport{  // http1.1协议
        TLSClientConfig: &tls.Config{
            RootCAs: pool,
            Certificates: []tls.Certificate{cliCrt},
        },
    }
    client := &http.Client{Transport: tr}

    //resp, err := client.Get("https://localhost:8088")
    resp, err := client.Get("https://server:8088")
    if err != nil {
        fmt.Println("http get error: ", err)
        panic(err)
    }

    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
    fmt.Println(resp.Status)
}
<style></style>

Curl验证

$ curl --cacert cert/ca.crt --cert cert/client.crt --key cert/client.key https://server:8088
 Hi, This is an example of https service in golang!
<style></style>

Client测试

$ go run dul_client.go
 Hi, This is an example of https service in golang

200 OK 
<style></style>

HTTP/1.1双向认证(19数据帧)

golang证书认证通信

<style></style>

HTTP/2双向认证(24数据帧)

golang证书认证通信

 

<style></style>

参考:

1.  Golang之双向认证 简书

2.  https原理以及golang基本实现

3.  Go语言的http/2服务器功能及客户端使用

上一篇:shell脚本中判断一个字符串是否是空字符串


下一篇:linux 进程间消息队列通讯