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

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

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

type CertPool struct {
    // contains filtered or unexported fields
}

CertPool is a set of certificates.

func NewCertPool() *CertPool

NewCertPool返回一个空的CertPool。

func SystemCertPool() (*CertPool, error)

SystemCertPool returns a copy of the system cert pool.

func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool)

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
}

示例:

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

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

client := &http.Client{
    CheckRedirect: redirectPolicyFunc,
    Transport: tr,
}

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

服务端定制:

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

HTTPS监听

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

func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error

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

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

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

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

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

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

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

如果/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 

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

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

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

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

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

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

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

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!

Client测试

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

200 OK 

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

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

 

5. 跳过证书验证

POSTMAN或curl中都可以跳过单向证书验证,golang也可以滴。

 

    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{Transport:tr}

 

 

参考:

简书

“h2c” 标识允许通过明文 TCP 运行 HTTP/2 的协议,此标识符用于 HTTP/1.1 升级标头字段以及标识 HTTP/2 over TCP。