golang證書認證通信


1. Golang中證書相關包

  • crypto/tls實現tls1.2tls1.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提供HTTPclientserver實現。

客戶端定制: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.keyserver.crt

客戶端:客戶端生成一個隨機數random-client,傳到服務器端;

服務端:服務器端接收消息之后,生成一個隨機數random-server和包含公鑰的證書,一起回饋給客戶端;

客戶端:客戶端收到的東西原封不動,加上premaster secret(通過random-clientrandom-server 經過一定算法生成的數據),再一次送給服務器端,這次傳過去的東西是經過服務端的公鑰進行加密后數據;

服務端:服務端經過私鑰(server.key),進行解密,獲取 premaster secret(協商密鑰過程);

此時客戶端和服務器端都擁有了三個要素:random-clientrandom-serverpremaster 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端證書的時候,要多添加一個字段,golangserver端認證程序會對這個字段進行認證:

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.ServerHandler是一個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}

 

 

參考:

1.  Golang之雙向認證 簡書

2.  https原理以及golang基本實現

3.  Go語言的http/2服務器功能及客戶端使用

4. gRPC+gRPC Gateway 能不能不用證書?  net/http2必須使用TLS交互;

“h2c” 標識允許通過明文 TCP 運行 HTTP/2 的協議,此標識符用於 HTTP/1.1 升級標頭字段以及標識 HTTP/2 over TCP。

 5. 通過 GoLang 全面了解 HTTPS

6. Golang設置https訪問,以及http如何重定向到https


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM