go语言实现http双向认证
1、准备客户端、服务端、根证书
├── client
│ ├── client.crt
│ ├── client.csr
│ ├── client.key
│ └── tls.cnf
├── root
│ ├── root.crt
│ ├── root.key
│ ├── root.srl
│ └── tls.cnf
└── server
├── server.crt
├── server.csr
├── server.key
└── tls.cnf
# root配置
>> cat tls.cnf
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = v3_ext
distinguished_name = dn
[ dn ]
C = CN
ST = Jiangsu
L = NJ
O = ROOT
CN =localhost # 通用名(Common Name):证书的主域名
[ req_ext ] # 备用名称(SAN),现代TLS强制要求,定义证书有效的所有域名
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = localhost
[ v3_ext ]
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
subjectAltName=@alt_names
# server配置
>> cat tls.cnf
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = v3_ext
distinguished_name = dn
[ dn ]
C = CN
ST = Jiangsu
L = NJ
O = SERVER
CN = com.test.server # 通用名(Common Name):证书的主域名
[ req_ext ] # 备用名称(SAN),现代TLS强制要求,定义证书有效的所有域名
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = localhost
DNS.2 = com.test.server
[ v3_ext ]
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
subjectAltName=@alt_names
# client配置
>> cat tls.cnf
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = v3_ext
distinguished_name = dn
[ dn ]
C = CN
ST = Jiangsu
L = NJ
O = CLIENT
CN =com.test.client # 通用名(Common Name):证书的主域名
[ req_ext ] # 备用名称(SAN),现代TLS强制要求,定义证书有效的所有域名
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = localhost
DNS.2 = com.test.client
[ v3_ext ]
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
subjectAltName=@alt_names
1、生成根证书
openssl genrsa -out root.key 2048
openssl req -x509 -new -nodes -key root.key -subj "/CN=ROOT CA" -days 7300 -out root.crt
2、生成服务端证书
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -config tls.cnf
openssl x509 -req -in server.csr -CA root.crt -CAkey root.key \-CAcreateserial -out server.crt -days 3650 \-extensions v3_ext -extfile tls.cnf
3、生成客户端证书
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -config tls.cnf
openssl x509 -req -in client.csr -CA root.crt -CAkey root.key \-CAcreateserial -out client.crt -days 3650 \-extensions v3_ext -extfile tls.cnf
4、编写服务端代码
package main
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
)
func main() {
var (
rootPath = "/root/app/root.crt"
certPath = "/root/app/server.crt"
keyPath = "/root/app/server.key"
)
certPool := x509.NewCertPool()
rootCert, err := ioutil.ReadFile(rootPath)
if err != nil {
log.Fatalf("Failed to read CA certificate: %v", err)
}
if ok := certPool.AppendCertsFromPEM(rootCert); !ok {
log.Fatal("Failed to append CA certificate")
}
cfg := &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
InsecureSkipVerify: false,
ClientCAs: certPool,
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
},
}
cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 {
return fmt.Errorf("no certificate chain verified")
}
clientCert := verifiedChains[0][0]
allowedDomains := []string{"com.test.client"}
for _, dns := range clientCert.DNSNames {
for _, allowed := range allowedDomains {
if dns == allowed {
return nil
}
}
}
return fmt.Errorf("certificate SAN not in allowed list")
}
r := mux.NewRouter()
r.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
var resp = make(map[string]string)
resp["status"] = "ok"
resp["msg"] = "success"
_ = json.NewEncoder(w).Encode(resp)
}).Methods("GET")
server := &http.Server{
Addr: ":" + "15000",
Handler: r,
TLSConfig: cfg,
ReadHeaderTimeout: 30 * time.Second,
}
log.Info("listening on port " + "15000")
err = server.ListenAndServeTLS(certPath, keyPath)
if err != nil {
log.Errorf("ListenAndServeTLS failed:%s", err.Error())
}
}
5、编写客户端代码
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
_ "io"
"io/ioutil"
"net/http"
log "github.com/sirupsen/logrus"
)
var (
rootPath = "/root/app/root.crt"
clientCertPath = "/root/app/client.crt"
clientKeyPath = "/root/app/client.key"
)
func createHttpClient() (*http.Client, error) {
rootCert, err := ioutil.ReadFile(rootPath)
if err != nil {
log.Fatalf("Failed to read CA certificate: %v", err)
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(rootCert); !ok {
log.Fatal("Failed to append CA certificate")
}
crtText, _ := ioutil.ReadFile(clientCertPath)
keyText, _ := ioutil.ReadFile(clientKeyPath)
certPair, _ := tls.X509KeyPair(crtText, keyText)
transport := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
InsecureSkipVerify: false,
Certificates: []tls.Certificate{certPair},
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
},
},
}
client := &http.Client{Transport: transport}
return client, nil
}
func main() {
client, err := createHttpClient()
if err != nil {
log.Fatalln(err)
}
ctx := context.TODO()
url := fmt.Sprintf("https://com.test.server:15000/test")
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
log.Fatalln(err)
}
req.Header.Set("Content-Type", "application/json")
response, err := client.Do(req)
if err != nil {
log.Fatalln(err)
}
var v []byte
if v, err = io.ReadAll(response.Body); err != nil {
log.Fatalln(err)
} else {
fmt.Println(string(v))
}
}
使用:
生产环境上需要在服务端和客户端服务器上的/etc/hosts文件中配置对方的的ip到域名的映射。
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
_ "io"
"io/ioutil"
"net/http"
log "github.com/sirupsen/logrus"
)
var (
rootPath = "/root/app/root.crt"
clientCertPath = "/root/app/client.crt"
clientKeyPath = "/root/app/client.key"
)
func createHttpClient() (*http.Client, error) {
rootCert, err := ioutil.ReadFile(rootPath)
if err != nil {
log.Fatalf("Failed to read CA certificate: %v", err)
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(rootCert); !ok {
log.Fatal("Failed to append CA certificate")
}
crtText, _ := ioutil.ReadFile(clientCertPath)
keyText, _ := ioutil.ReadFile(clientKeyPath)
certPair, _ := tls.X509KeyPair(crtText, keyText)
transport := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
InsecureSkipVerify: false,
Certificates: []tls.Certificate{certPair},
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
},
},
}
client := &http.Client{Transport: transport}
return client, nil
}
func main() {
client, err := createHttpClient()
if err != nil {
log.Fatalln(err)
}
ctx := context.TODO()
url := fmt.Sprintf("https://com.test.server:15000/test")
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
log.Fatalln(err)
}
req.Header.Set("Content-Type", "application/json")
response, err := client.Do(req)
if err != nil {
log.Fatalln(err)
}
var v []byte
if v, err = io.ReadAll(response.Body); err != nil {
log.Fatalln(err)
} else {
fmt.Println(string(v))
}
}
浙公网安备 33010602011771号