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}
參考:
1. Golang之雙向認證 簡書
4. gRPC+gRPC Gateway 能不能不用證書? net/http2必須使用TLS交互;
“h2c” 標識允許通過明文 TCP 運行 HTTP/2 的協議,此標識符用於 HTTP/1.1 升級標頭字段以及標識 HTTP/2 over TCP。