使用Go和gRPC建立安全TLS连接的方法有很多种。与流行的看法相反,您无需手动向gRPC客户端提供服务器证书以加密连接。这篇文章将提供不同场景的代码示例列表。如果您只想查看代码,请转到源代码存储库。您需要克隆此存储库(Go1.11 +)。
“Web浏览器不持有TLS的公共证书,为什么我的应用程序?”[ 不需要:Go中的gRPC客户端证书 ]
这是一系列三篇文章的第1部分。在第2部分中,我们将使用Let的加密和自动证书管理环境(ACME)来涵盖公共证书,最后在第3部分中介绍相互身份验证。
介绍
RFC 5246,传输层安全性(TLS)协议的主要目的是提供两个通信应用程序之间的隐私和数据完整性。TLS是gRPC内置的身份验证机制之一。它具有TLS集成并促进使用TLS对服务器进行身份验证,并加密客户端和服务器之间交换的所有数据 “[ gRPC身份验证 ]。
为了建立TLS连接,客户端必须向Client Hello
服务器发送消息以启动TLS握手。TLS握手协议允许服务器和客户端相互认证,并在应用协议发送或接收其第一个数据字节之前协商加密算法和加密密钥 [ RFC 5246 ]。
一个Client Hello
消息包含一个选项列表客户支持建立安全连接; TLS版本,随机数,会话ID,密码套件,压缩方法和扩展,如下面的数据包捕获所示。
服务器应答背面与Server Hello
包括其优选的TLS版本,随机数,会话ID和密码套件和压缩方法中选择(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
和null
在下面的图片)。服务器还将包含签名的TLS证书。客户端 - 根据其配置 - 将使用证书颁发机构(CA)验证此证书,以证明服务器的身份。CA是颁发数字证书的可信方。
证书也可以单独发送消息,如下面的捕获。
在此协商之后,他们通过加密通道(对称与非对称加密)启动客户端密钥交换。接下来,他们开始发送加密的应用数据。我稍微过度简化了这一部分,但我认为我们已经有足够的上下文来评估要遵循的代码片段。
证书
在我们进入代码之前,让我们谈谈证书。RFC 5280中详细描述了X.509 v3证书格式。它除了其他功能外,还对服务器的公钥和数字签名进行编码(以验证证书的真实性)。
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
在你提出要求之前,TBS意味着要签名。
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
...
}
X.509证书的一些最相关的字段是:
-
subject
:颁发证书的主题的名称。 -
subjectPublicKey
:使用密钥的公钥和算法(例如,RSA,DSA或Diffie-Hellman)。见下文。 -
issuer
:已签署并颁发证书的CA的名称 -
signature
:CA用于签署证书的算法的算法标识符(与signatureAlgorithm
)相同。
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
您可以在x.509库中将其视为Go代码。
ype Certificate struct {
...
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
...
}
创建自签名证书
虽然SSL证书在由受信任的证书颁发机构(CA)颁发时最可靠,但我们将使用自签名证书用于此帖子,这意味着我们自己签署(我们是CA)。在第2部分中,我们将使用证书加密
创建这些的步骤如下所示,我们依赖openssl
和配置文件(certificate.conf
)来优先使用主题备用名称(subjectAltName
)而不是已弃用的公用名(CN
)。
为了一次性重现所有这些,您可以make cert
在克隆存储库后运行(这是所有gRPC示例要遵循的先决条件)。一步一步如下。
创建根签名密钥
openssl genrsa -out ca.key 4096
生成自签名根证书
您可以修改/C=US/ST=NJ/O=CA, Inc.`以适合您的位置和虚构的CA名称。
openssl req -new -x509 -key ca.key -sha256 -subj "/C=US/ST=NJ/O=CA, Inc." -days 365 -out ca.cert
这将导致我们的CA获得以下证书。
CA证书
为服务器创建密钥证书
openssl genrsa -out service.key 4096
创建签名CSR
openssl req -new -key service.key -out service.csr -config certificate.conf
为服务器生成证书
openssl x509 -req -in service.csr -CA ca.cert -CAkey ca.key -CAcreateserial -out service.pem -days 365 -sha256 -extfile certificate.conf -extensions req_ext
这将产生以下证书。
服务器证书
校验
您也可以查看证书openssl
。
$ openssl x509 -in service.pem -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 12273773735572067708 (0xaa55342eea4ad57c)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=NJ, O=CA, Inc.
Validity
Not Before: Jun 28 13:56:36 2019 GMT
Not After : Jun 27 13:56:36 2020 GMT
Subject: C=US, ST=NJ, O=Test, Inc., CN=localhost
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
Modulus:
00:c4:02:ab:d6:21:ac:38:58:98:cc:dc:65:b6:9b:
df:96:8f:4a:f9:9a:e2:ce:3a:65:78:07:6a:8b:d0:
...
gRPC服务
现在,让我们看看我们如何使用Go和gRPC以及非常简单的gRPC服务来应用和利用所有这些,以通过其ID检索用户名。我们将查询ID = 1
,它应返回用户Nicolas
。protobuf定义如下。
syntax = "proto3";
package test;
service gUMI {
rpc GetByID (GetByIDRequest) returns (User);
}
message GetByIDRequest {
uint32 id = 1;
}
message User {
string name = 1;
string email = 2;
uint32 id = 3;
}
已编译的代码已在存储库中生成。你可以再次编译make proto
。
不安全的连接
我们来看几个非推荐的做法。
没有加密的连接
如果你不希望加密连接,转到grpc
包提供DialOption
WithInsecure()
的客户端。这个加上没有任何服务器的服务器ServerOption
将导致未加密的连接。
// Client
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// Server
s := grpc.NewServer()
// ... register gRPC services ...
if err = s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
为了重现这一点,请make run-server-insecure
在一个选项卡和run-client-insecure
另一个选项卡中运行。
$ make run-server-insecure
2019/07/05 18:08:03 Creating listener on port: 50051
2019/07/05 18:08:03 Starting gRPC services
2019/07/05 18:08:03 Listening for incoming connections
在另一个标签中
$ make run-client-insecure
User found: Nicolas
客户端不验证服务器
在这种情况下,我们使用服务器的公钥对连接进行加密,但是客户端不会验证服务器证书的完整性,因此您无法确保实际上是在与服务器通信而不是与服务器中的人员通信。中间(man-in-the-middle
攻击)。
为此,我们在之前创建的服务器端提供公钥和私钥对。客户端需要InsecureSkipVerify
将tls
包中的配置标志设置为true
。
// Client
config := &tls.Config{
InsecureSkipVerify: true,
}
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(credentials.NewTLS(config)))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// Server
creds, err := credentials.NewServerTLSFromFile("service.pem", "service.key")
if err != nil {
log.Fatalf("Failed to setup TLS: %v", err)
}
s := grpc.NewServer(grpc.Creds(creds))
// ... register gRPC services ...
if err = s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
为了重现这一点,请make run-server
在一个选项卡和run-client
另一个选项卡中运行。
安全连接
让我们来看看我们如何加密沟通渠道并验证我们正在与我们认为自己的人交谈。
自动下载服务器证书并进行验证
为了验证服务器的身份(对其进行身份验证),客户端使用证书颁发机构(CA)证书对服务器证书上的CA签名进行身份验证。您可以向客户端提供CA证书,也可以依赖操作系统中包含的一组可信CA证书(可信密钥库)。
没有CA证书文件
在前面的示例中,我们并没有在客户端做任何特殊的事情来加密连接,而不是将InsecureSkipVerify
标志设置为true
。在这种情况下,我们将切换标志false
以查看会发生什么。将不会建立连接,客户端将记录x509: certificate signed by unknown authority
。
// Client
config := &tls.Config{
InsecureSkipVerify: false,
}
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(credentials.NewTLS(config)))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// Server
creds, err := credentials.NewServerTLSFromFile("service.pem", "service.key")
if err != nil {
log.Fatalf("Failed to setup TLS: %v", err)
}
s := grpc.NewServer(grpc.Creds(creds))
// ... register gRPC services ...
if err = s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
为了重现这一点,请make run-server
在一个选项卡和run-client-noca
另一个选项卡中运行。
使用证书颁发机构(CA)证书文件
让我们手动提供CA证书文件(ca.cert
)并将InsecureSkipVerify
选项保留为false
。
// Client
b, _ := ioutil.ReadFile("ca.cert")
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(b) {
return nil, errors.New("credentials: failed to append certificates")
}
config := &tls.Config{
InsecureSkipVerify: false,
RootCAs: cp,
}
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(credentials.NewTLS(config)))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// Server
creds, err := credentials.NewServerTLSFromFile("service.pem", "service.key")
if err != nil {
log.Fatalf("Failed to setup TLS: %v", err)
}
s := grpc.NewServer(grpc.Creds(creds))
// ... register gRPC services ...
if err = s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
为了重现这一点,请make run-server
在一个选项卡和run-client-ca
另一个选项卡中运行。
使用系统中包含的CA证书(OS /浏览器)
空的tls
config(tls.Config{}
)将负责加载您的系统CA证书。我们将在本系列文章的第2部分中验证此场景(使用来自公共域的Let's Encrypt的证书)。
您也可以手动从系统加载CA证书SystemCertPool()
。
certPool, err := x509.SystemCertPool()
如果您拥有服务器证书并且您信任它
这是Internet教程中最常见的场景。如果您拥有服务器和客户端,则可以与客户端预先共享服务器的certificate(service.pem
),并直接使用它来加密通道。
// Client
creds, err := credentials.NewClientTLSFromFile("service.pem", "")
if err != nil {
log.Fatalf("could not process the credentials: %v", err)
}
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// Server
creds, err := credentials.NewServerTLSFromFile("service.pem", "service.key")
if err != nil {
log.Fatalf("Failed to setup TLS: %v", err)
}
s := grpc.NewServer(grpc.Creds(creds))
// ... register gRPC services ...
if err = s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
为了重现这一点,请make run-server
在一个选项卡和run-client-file
另一个选项卡中运行。
结论
有不同的方法可以为gRPC设置TLS。提供完整性和隐私并不需要花费太多精力,所以强烈建议您远离类似WithInsecure()
或设置InsecureSkipVerify
标记的方法true
。
转:https://itnext.io/practical-guide-to-securing-grpc-connections-with-go-and-tls-part-1-f63058e9d6d1