gRPC双向证书认证客户端出现无效证书错误求助
问题描述
我的代码在非安全模式和服务端认证模式下均可正常运行,查遍资料确认配置无误,但双向认证模式始终无法正常工作,恳请协助解决。
错误信息
alert bad certificate:ssl/record/rec_layer_s3.c:908:SSL alert number 42
证书相关说明
证书、密钥和根CA均在Mac上通过mkcert生成,已存入文件并通过配置文件引用。
证书验证测试结果
openssl verify -CAfile rootCA.crt localhost.crt localhost.crt: OK openssl x509 -noout -modulus -in localhost.crt | openssl md5 MD5(stdin)= 2458874611c7bad14f86a76c4b28a332 openssl rsa -noout -modulus -in ../localhost-private.key | openssl md5 MD5(stdin)= 2458874611c7bad14f86a76c4b28a332
注:loadConfig代码未附上,该功能运行正常。
客户端配置
grpc_host: "localhost" grpc_port: 50051 grpc_secure: mutual: true grpc_tls_info: tls_certificate_fqn: "/Volumes/development-share/.keys/mac-localhost/tls/certs/client-localhost.crt" tls_private_key_fqn: "/Volumes/development-share/.keys/mac-localhost/tls/client-localhost.key" tls_ca_bundle_fqn: "/Volumes/development-share/.keys/mac-localhost/tls/certs/rootCA.crt"
服务端配置
grpc_host: "localhost" grpc_port: 50051 grpc_secure: mutual: true grpc_tls_info: tls_certificate_fqn: "/Volumes/development-share/.keys/mac-localhost/tls/certs/localhost.crt" tls_private_key_fqn: "/Volumes/development-share/.keys/mac-localhost/tls/localhost-private.key" tls_ca_bundle_fqn: "/Volumes/development-share/.keys/mac-localhost/tls/certs/rootCA.crt"
服务端GRPC核心代码
func NewGRPCService( extensionName string, config GRPCConfiguration, ) (gRPCServicePtr *GRPCService, errorInfo errs.ErrorInfo) { var ( tCertificate tls.Certificate tCACertPool *x509.CertPool tGRPCListener net.Listener tTLSConfig *tls.Config tTransportCredentials credentials.TransportCredentials ) gRPCServicePtr = &GRPCService{ host: config.GRPCHost, } if tGRPCListener, errorInfo.Error = net.Listen(ctv.VAL_TCP, fmt.Sprintf("%s:%d", ctv.VAL_LOCAL_HOST, config.GRPCPort)); errorInfo.Error != nil { errorInfo = errs.NewErrorInfo(errorInfo.Error, errs.BuildLabelValue(ctv.LBL_GRPC_LISTENER, ctv.TXT_FAILED)) return } gRPCServicePtr.GRPCListenerPtr = &tGRPCListener tTLSConfig = &tls.Config{} switch { case config.GRPCSecure.ServerSide: if tCertificate, errorInfo = LoadTLSCredentialsWithKey(config.GRPCTLSInfo); errorInfo.Error != nil { return } tTLSConfig.Certificates = []tls.Certificate{tCertificate} tTLSConfig.ClientAuth = tls.NoClientCert tTransportCredentials = credentials.NewTLS(tTLSConfig) gRPCServicePtr.GRPCServerPtr = grpc.NewServer(grpc.Creds(tTransportCredentials)) if hlps.CheckPointerNotNil(gRPCServicePtr.GRPCServerPtr, errs.ErrPointerMissing, ctv.FN_GRPC_SERVER_POINTER); errorInfo.Error != nil { return } case config.GRPCSecure.Mutual: if tCertificate, errorInfo = LoadTLSCredentialsWithKey(config.GRPCTLSInfo); errorInfo.Error != nil { return } if tCACertPool, errorInfo = LoadTLSCACertificate(config.GRPCTLSInfo); errorInfo.Error != nil { return } tTLSConfig.Certificates = []tls.Certificate{tCertificate} tTLSConfig.RootCAs = tCACertPool tTLSConfig.ClientCAs = tCACertPool tTLSConfig.ClientAuth = tls.RequireAndVerifyClientCert tTransportCredentials = credentials.NewTLS(tTLSConfig) gRPCServicePtr.GRPCServerPtr = grpc.NewServer(grpc.Creds(tTransportCredentials)) default: // 若server side和mutual都设为false,默认使用该安全模式 tTLSConfig.ClientAuth = tls.NoClientCert gRPCServicePtr.GRPCServerPtr = grpc.NewServer() } return } func LoadTLSCACertificate(tlsConfig jwts.TLSInfo) (caCertPoolPtr *x509.CertPool, errorInfo errs.ErrorInfo) { var ( tCACertificateFile []byte ) if tCACertificateFile, errorInfo.Error = os.ReadFile(tlsConfig.TLSCABundleFQN); errorInfo.Error != nil { errorInfo = errs.NewErrorInfo(errorInfo.Error, errs.BuildLabelValue(ctv.LBL_TLS_CA_BUNDLE_FILENAME, tlsConfig.TLSCABundleFQN)) return } caCertPoolPtr = x509.NewCertPool() if ok := caCertPoolPtr.AppendCertsFromPEM(tCACertificateFile); !ok { errorInfo = errs.NewErrorInfo(errorInfo.Error, errs.BuildLabelValue(ctv.LBL_TLS_CA_CERT_POOL, ctv.TXT_FAILED)) } return } func LoadTLSCredentialsWithKey(tlsConfig jwts.TLSInfo) (certificate tls.Certificate, errorInfo errs.ErrorInfo) { if certificate, errorInfo.Error = tls.LoadX509KeyPair(tlsConfig.TLSCertFQN, tlsConfig.TLSPrivateKeyFQN); errorInfo.Error != nil { errorInfo = errs.NewErrorInfo( errorInfo.Error, fmt.Sprintf( "%s, %s", errs.BuildLabelValue(ctv.LBL_TLS_CERTIFICATE_FILENAME, tlsConfig.TLSCertFQN), errs.BuildLabelValue( ctv.LBL_TLS_PRIVATE_KEY_FILENAME, tlsConfig.TLSPrivateKey, ), ), ) } return }
服务端Main.go
func main() { var ( errorInfo errs.ErrorInfo tConfig grpcs.GRPCConfiguration tGRPCServicePtr *grpcs.GRPCService tSecurityLevel string ) if tConfig, errorInfo = loadConfig(configFilename); errorInfo.Error != nil { log.Fatalf("服务启动失败: %v", errorInfo.Error) } tGRPCServicePtr, errorInfo = grpcs.NewGRPCService(utilityName, tConfig) pb.RegisterGRPCTestServicesServer(tGRPCServicePtr.GRPCServerPtr, &Server{}) switch { case tConfig.GRPCSecure.Mutual: tSecurityLevel = "双向认证" case tConfig.GRPCSecure.ServerSide: tSecurityLevel = "服务端认证" default: tSecurityLevel = "非安全模式" } log.Printf("gRPC服务启动于端口 %v / 安全等级: %s", tConfig.GRPCPort, tSecurityLevel) if err := tGRPCServicePtr.GRPCServerPtr.Serve(*tGRPCServicePtr.GRPCListenerPtr); err != nil { log.Fatalf("服务启动失败: %v", err) } fmt.Println(tGRPCServicePtr, errorInfo) }
客户端Main.go
func main() { var ( errorInfo errs.ErrorInfo tCACertPoolPtr *x509.CertPool tClientCertificate tls.Certificate tConfig grpcs.GRPCConfiguration tConn *grpc.ClientConn tPongReply *pb.Pong tTLSConfig *tls.Config tTransportCredentials credentials.TransportCredentials ) if tConfig, errorInfo = loadConfig(configFilename); errorInfo.Error != nil { log.Fatalf("配置文件加载失败: %v", errorInfo.Error) } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if tClientCertificate, errorInfo = grpcs.LoadTLSCredentialsWithKey(tConfig.GRPCTLSInfo); errorInfo.Error != nil { log.Fatalf("TLS凭证加载失败: %v", errorInfo.Error) } if tCACertPoolPtr, errorInfo = grpcs.LoadTLSCACertificate(tConfig.GRPCTLSInfo); errorInfo.Error != nil { log.Fatalf("TLS根CA证书加载失败: %v", errorInfo.Error) } tTLSConfig = &tls.Config{ RootCAs: tCACertPoolPtr, ClientCAs: tCACertPoolPtr, Certificates: []tls.Certificate{tClientCertificate}, } tTLSConfig.ServerName = ctv.LOCAL_HOST tTLSConfig.InsecureSkipVerify = false tTransportCredentials = credentials.NewTLS(tTLSConfig) tConn, errorInfo.Error = grpc.NewClient(fmt.Sprintf("%s:%v", tConfig.GRPCHost, tConfig.GRPCPort), grpc.WithTransportCredentials(tTransportCredentials)) if errorInfo.Error != nil { log.Fatalf("连接gRPC服务器失败 %s: %v", fmt.Sprintf("%s:%v", tConfig.GRPCHost, tConfig.GRPCPort), errorInfo.Error) } defer func(conn *grpc.ClientConn) { err := conn.Close() if err != nil { } }(tConn) c := pb.NewGRPCTestServicesClient(tConn) if tPongReply, errorInfo.Error = c.DKPing( ctx, &pb.Ping{ Email: "scott@yackofamily.com", }, ); errorInfo.Error != nil { errorInfo = errs.NewErrorInfo(errorInfo.Error, ctv.VAL_EMPTY) } log.Printf("Ping响应: %v", tPongReply) if errorInfo.Error != nil { log.Printf("错误: %v", errorInfo.Error.Error()) } }
问题排查与解决方案
SSL alert number 42对应bad certificate,说明服务端验证客户端证书时不认可,可从以下方向排查:
验证客户端证书有效性
执行命令确认客户端证书由指定根CA签发:openssl verify -CAfile rootCA.crt client-localhost.crt若返回
OK则证书本身有效,否则重新生成客户端证书。检查客户端证书SAN字段
执行命令查看客户端证书的主题备用名称:openssl x509 -in client-localhost.crt -text -noout确认
X509v3 Subject Alternative Name包含DNS:localhost(或客户端连接使用的IP地址),若缺失则重新生成证书:mkcert client localhost 127.0.0.1 ::1修正服务端监听器绑定地址
当前服务端硬编码绑定ctv.VAL_LOCAL_HOST,改为绑定配置中的config.GRPCHost,避免地址不匹配:if tGRPCListener, errorInfo.Error = net.Listen(ctv.VAL_TCP, fmt.Sprintf("%s:%d", config.GRPCHost, config.GRPCPort)); errorInfo.Error != nil {优化客户端TLS配置
客户端无需设置ClientCAs(该字段用于验证其他客户端证书),仅保留RootCAs验证服务端证书:tTLSConfig = &tls.Config{ RootCAs: tCACertPoolPtr, Certificates: []tls.Certificate{tClientCertificate}, }开启GRPC调试日志
启动服务端和客户端时设置环境变量,获取更详细的TLS握手日志:export GRPC_GO_LOG_SEVERITY_LEVEL=info export GRPC_GO_LOG_VERBOSITY_LEVEL=99
内容的提问来源于stack exchange,提问作者S. Yacko
相关产品推荐
相关产品推荐

