重新制作证书
Go1.15+废弃 Common Name 字段,推荐使用SAN证书,不然在连接gRPC服务实会出现错误
rpc error: code = Unavailable desc = connection error: desc = \
"transport: authentication handshake failed: \
x509: certificate relies on legacy Common Name field, \
use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0"
上一节已经熟悉签发流程:生成密钥对(.key) => 生成请求(.csr) => CA签发证书(.pem),这次简化配置文件并重新制作CA证书,客户端证书
CA证书
生成CA密钥对
openssl genrsa -out ca.key.pem 4096
生成CA证书
自签证书
openssl req -config openssl-ca.conf \
-key ca.key.pem \
-new -x509 -days 3650 \
-out ca.cert.pem
-----
Country Name (2 letter code) [CN]:
State or Province Name [Guangdong]:
Locality Name [Guangzhou]:
Organization Name [CA]:
Organizational Unit Name []:
Common Name []:
Email Address []:
CA配置文件openssl-ca.conf
base_dir = .
certificate = $base_dir/ca.cert.pem # The CA certificate
private_key = $base_dir/ca.key.pem # The CA private key
new_certs_dir = $base_dir # Location for new certs after signing
database = $base_dir/index.txt # Database index file
serial = $base_dir/serial # The current serial number
unique_subject = no # Set to 'no' to allow creation of
# several certificates with same subject.
HOME = .
RANDFILE = $ENV::HOME/.rnd
####################################################################
[ ca ]
# `man ca`
default_ca = CA_default # The default ca section
[ CA_default ]
crl_extensions = crl_ext
default_crl_days = 30 # How long before next CRL
default_days = 3650 # How long to certify for
default_md = sha256 # Use public key default MD
preserve = no # Keep passed DN ordering
x509_extensions = ca_ext # Extension to add when the -x509 option is used.
policy = policy_anything
email_in_dn = no # Don't concat the email in the DN
copy_extensions = copy # Required to copy SANs from CSR to cert
####################################################################
[ req ]
default_bits = 2048
default_keyfile = ca_key.pem
distinguished_name = ca_distinguished_name
x509_extensions = ca_extensions
string_mask = utf8only
[ ca_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name
localityName = Locality Name
organizationName = Organization Name
organizationalUnitName = Organizational Unit Name
commonName = Common Name
emailAddress = Email Address
# Optionally, specify some defaults.
countryName_default = CN
stateOrProvinceName_default = Guangdong
localityName_default = Guangzhou
organizationName_default = CA
organizationalUnitName_default =
emailAddress_default =
####################################################################
[ policy_anything ]
# See the POLICY FORMAT section of the `ca` man page.
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
####################################################################
[ ca_ext ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
basicConstraints = critical, CA:true
keyUsage = keyCertSign, cRLSign
[ server_ext ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
[ client_ext ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection
[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
authorityKeyIdentifier = keyid:always
服务端证书
生成服务端密钥对
openssl genrsa -out server.key.pem 2048
配置文件openssl.conf
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name
localityName = Locality Name
organizationName = Organization Name
organizationalUnitName = Organizational Unit Name
commonName = Common Name
emailAddress = Email Address
# Optionally, specify some defaults.
countryName_default = CN
stateOrProvinceName_default = Guangdong
localityName_default = Guangzhou
organizationName_default = CA
organizationalUnitName_default =
commonName_default = localhost
emailAddress_default =
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
IP.1 = 0.0.0.0
IP.2 = 127.0.0.1
生成CSR
openssl req -config openssl.conf \
-key server.key.pem \
-new -out server.csr.pem
CA签发服务端证书
openssl ca -config openssl-ca.conf \
-extensions server_ext -days 2920 \
-in server.csr.pem \
-out server.cert.pem
客户端证书
生成客户端密钥对
openssl genrsa -out client.key.pem 2048
生成CSR
openssl req -config openssl.conf \
-key client.key.pem \
-new -out client.csr.pem
CA签发客户端证书
openssl ca -config openssl-ca.conf \
-extensions client_ext -days 2920 \
-in client.csr.pem \
-out client.cert.pem
为gRPC开启安全连接
开启安全验证分为
-
insecure:不使用安全验证
-
server-side:客户端认证服务端
-
mutual:不仅是客户端认证服务端,服务端也认证客户端
上面生成的证书文件目录如下
.
├── 01.pem
├── 02.pem
├── ca.cert.pem # CA自签证书
├── ca.key.pem
├── client.cert.pem # 客户端证书
├── client.csr.pem
├── client.key.pem # 客户端密钥对(需自行保密)
├── index.txt
├── index.txt.attr
├── index.txt.attr.old
├── index.txt.old
├── openssl-ca.conf
├── openssl.conf
├── serial
├── serial.old
├── server.cert.pem # 服务端证书
├── server.csr.pem
└── server.key.pem # 服务端密钥对(需自行保密)
将用到的证书和密钥对整理一下并复制到工程目录
......
├── configs
│ └── certs
│ ├── ca.cert.pem
│ ├── client.cert.pem
│ ├── client.key.pem
│ ├── server.cert.pem
│ └── server.key.pem
......
测试一下
在 pkg 目录下新建 tls.go
└── pkg
└── secure
└── tls.go
tls.go :封装密钥对及相关证书的加载过程
package secure
import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func TLSServerOption(certFile, keyFile string) (grpc.ServerOption, error) {
creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
if err != nil {
return nil, fmt.Errorf("credentials.NewServerTLSFromFile err: %w", err)
}
return grpc.Creds(creds), nil
}
func TLSDialOption(certFile, serverName string) (grpc.DialOption, error) {
creds, err := credentials.NewClientTLSFromFile(certFile, serverName)
if err != nil {
return nil, fmt.Errorf("credentials.NewClientTLSFromFile err: %w", err)
}
return grpc.WithTransportCredentials(creds), nil
}
func CAServerOption(certFile, keyFile, caFile string) (grpc.ServerOption, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, fmt.Errorf("tls.LoadX509KeyPair err: %w", err)
}
certPool := x509.NewCertPool()
ca, err := os.ReadFile(caFile)
if err != nil {
return nil, fmt.Errorf("ioutil.ReadFile err: %w", err)
}
if ok := certPool.AppendCertsFromPEM(ca); !ok {
return nil, fmt.Errorf("certPool.AppendCertsFromPEM err")
}
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
})
return grpc.Creds(creds), nil
}
func CADialOption(certFile, keyFile, caFile, serverName string) (grpc.DialOption, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, fmt.Errorf("tls.LoadX509KeyPair err: %w", err)
}
certPool := x509.NewCertPool()
ca, err := os.ReadFile(caFile)
if err != nil {
return nil, fmt.Errorf("ioutil.ReadFile err: %w", err)
}
if ok := certPool.AppendCertsFromPEM(ca); !ok {
return nil, fmt.Errorf("certPool.AppendCertsFromPEM err")
}
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
ServerName: serverName,
RootCAs: certPool,
})
return grpc.WithTransportCredentials(creds), nil
}
最后修改 gRPC 服务的启动代码
package server
import (
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
pb "go-web/api/gen/v1"
"go-web/configs"
"go-web/internal/app/health/service"
"go-web/pkg/secure"
)
const (
GRPC_ADDR = ":58081"
PATH_SVR = "certs/server.cert.pem"
PATH_KEY = "certs/server.key.pem"
)
func NewGRPC(srv *service.EchoService) (grpcSvr *grpc.Server, err error) {
credsServerOption, err := secure.TLSServerOption(configs.Path(PATH_SVR), configs.Path(PATH_KEY))
if err != nil {
return nil, err
}
opts := []grpc.ServerOption{
// Enable TLS for all incoming connections.
credsServerOption,
}
grpcSvr = grpc.NewServer(opts...)
pb.RegisterEchoServiceServer(grpcSvr, srv)
reflection.Register(grpcSvr)
go func() {
listener, err := net.Listen("tcp", GRPC_ADDR)
if err != nil {
panic(err)
}
if err = grpcSvr.Serve(listener); err != nil {
panic(err)
}
}()
return
}
使用 grpcurl 测试,未加载CA证书验证服务端证书
grpcurl -d '{"message": "Hello, World!"}' localhost:58081 api.v1.EchoService.Echo
Failed to dial target host "localhost:58081": \
tls: failed to verify certificate: x509: certificate signed by unknown authority
加载CA证书验证服务端证书
grpcurl -cacert ca.cert.pem -d '{"message": "Hello, World!"}' \
localhost:58081 api.v1.EchoService.Echo
{
"message": "Hello, World!"
}