gRPC SSL加密传输数据实例(C++版)

一、grpc SSL源码分析

官方文档说明

gRPC – Authentication
gRPC 官方文档中文版_V1.0

由于车载终端开发语言为C++,大致查阅了一下官方相关的文档,文档描述的内容比较简单。

服务端认证加密使用的 SSL/TLS
这是个最简单的认证场景:一个客户端仅仅想认证服务器并且加密所有数据。

1.客户端处理流程

// Create a default SSL ChannelCredentials object.
auto channel_creds = grpc::SslCredentials(grpc::SslCredentialsOptions());
// Create a channel using the credentials created in the previous step.
auto channel = grpc::CreateChannel(server_name, creds);
// Create a stub on the channel.
std::unique_ptr<Greeter::Stub> stub(Greeter::NewStub(channel));
// Make actual RPC calls on the stub.
grpc::Status s = stub->sayHello(&context, *request, response);

对于高级的用例比如改变根 CA 或使用客户端证书,可以在发送给工厂方法的 SslCredentialsOptions 参数里的相应选项进行设置。

查看了一下源码大致实现的过程。

(1)配置参数

SslCredentials方法的作用是根据SSL特定选项构建SSL证书。在grpc\include\grpcpp\security\credentials_impl.h中定义。

/// Builds SSL Credentials given SSL specific options
std::shared_ptr<ChannelCredentials> SslCredentials(
    const SslCredentialsOptions& options);

具体实现在grpc\src\cpp\client\secure_credentials.cc中实现。

// Builds SSL Credentials given SSL specific options
std::shared_ptr<ChannelCredentials> SslCredentials(
    const SslCredentialsOptions& options) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {
      options.pem_private_key.c_str(), options.pem_cert_chain.c_str()};

  grpc_channel_credentials* c_creds = grpc_ssl_credentials_create(
      options.pem_root_certs.empty() ? nullptr : options.pem_root_certs.c_str(),
      options.pem_private_key.empty() ? nullptr : &pem_key_cert_pair, nullptr,
      nullptr);
  return WrapChannelCredentials(c_creds);
}

传递进来的option参数

/// Options used to build SslCredentials.
struct SslCredentialsOptions {
  /// The buffer containing the PEM encoding of the server root certificates. If
  /// this parameter is empty, the default roots will be used.  The default
  /// roots can be overridden using the \a GRPC_DEFAULT_SSL_ROOTS_FILE_PATH
  /// environment variable pointing to a file on the file system containing the
  /// roots.
  grpc::string pem_root_certs;

  /// The buffer containing the PEM encoding of the client's private key. This
  /// parameter can be empty if the client does not have a private key.
  grpc::string pem_private_key;

  /// The buffer containing the PEM encoding of the client's certificate chain.
  /// This parameter can be empty if the client does not have a certificate
  /// chain.
  grpc::string pem_cert_chain;
};

pem_root_certs根证书,如果参数为空,它会使用默认的根证书。同时环境变量GRPC_DEFAULT_SSL_ROOTS_FILE_PATH,可以设置grpc搜索根证书的路径。

我实际测试如果该参数为空的话会出现握手失败的错误。

root@chenwr-pc:/home/workspace/project/grpc_project/grpc# GRPC_VERBOSITY=INFO ./greeter_client
I0423 15:23:48.876649338   10329 ev_epollex_linux.cc:1631]   Skipping epollex because it is not supported.
I0423 15:23:48.876739676   10329 ev_epoll1_linux.cc:116]     grpc epoll fd: 3
E0423 15:23:48.934196140   10329 ssl_transport_security.cc:1379] Handshake failed with fatal error SSL_ERROR_SSL: error:1000007d:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED.
I0423 15:23:48.934377487   10329 subchannel.cc:1003]         Connect failed: {"created":"@1587626628.934217529","description":"Handshake failed","file":"/home/workspace/project/grpc_project/grpc/src/core/lib/security/transport/security_handshaker.cc","file_line":307,"tsi_code":10,"tsi_error":"TSI_PROTOCOL_FAILURE"}
I0423 15:23:48.934517994   10329 subchannel.cc:942]          Subchannel 0x14dabd0: Retry in 997 milliseconds
14: failed to connect to all addresses
Greeter received: RPC failed

因此我在客户端上的pem_root_certs根证书设置为服务端的自签名证书,服务端上的pem_root_certs根证书设置为客户端的自签名证书。有个疑惑是SSL/TSL双向认证的时候会请求对方发生证书过来进行验证。grpc这个操作是否为了内部直接根据传递进来的证书自行验证从而省去请求的动作呢?

参数pem_cert_chain证书链,如果没有可以为空。但是我实际测试pkcp.cert_chain = “”;运行服务端会报错。我个人理解为pem_cert_chain充当公钥的作用,因此需要和pem_private_key成对出现。如果使用自签名证书的话,这个参数直接传递客户端自签名证书。

E0423 15:34:01.190980112   10763 ssl_transport_security.cc:777] Invalid cert chain file.
E0423 15:34:01.191044404   10763 ssl_security_connector.cc:263] Handshaker factory creation failed with TSI_INVALID_ARGUMENT.
/** Object that holds a private key / certificate chain pair in PEM format. */
typedef struct {
  /** private_key is the NULL-terminated string containing the PEM encoding of
     the client's private key. */
  const char* private_key;

  /** cert_chain is the NULL-terminated string containing the PEM encoding of
     the client's certificate chain. */
  const char* cert_chain;
} grpc_ssl_pem_key_cert_pair;

私钥证书对。
options.pem_private_key.c_str()为取字符串的首地址。
string.c_str是Borland封装的String类中的一个函数,它返回当前字符串的首字符地址。

(2)生成grpc ssl证书

将openssl生成的私钥证书通过接口生成grpc证书。

/** Deprecated in favor of grpc_ssl_server_credentials_create_ex. It will be
   removed after all of its call sites are migrated to
   grpc_ssl_server_credentials_create_ex. Creates an SSL credentials object.
   The security level of the resulting connection is GRPC_PRIVACY_AND_INTEGRITY.
   - pem_root_certs is the NULL-terminated string containing the PEM encoding
     of the server root certificates. If this parameter is NULL, the
     implementation will first try to dereference the file pointed by the
     GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment variable, and if that fails,
     try to get the roots set by grpc_override_ssl_default_roots. Eventually,
     if all these fail, it will try to get the roots from a well-known place on
     disk (in the grpc install directory).

     gRPC has implemented root cache if the underlying OpenSSL library supports
     it. The gRPC root certificates cache is only applicable on the default
     root certificates, which is used when this parameter is nullptr. If user
     provides their own pem_root_certs, when creating an SSL credential object,
     gRPC would not be able to cache it, and each subchannel will generate a
     copy of the root store. So it is recommended to avoid providing large room
     pem with pem_root_certs parameter to avoid excessive memory consumption,
     particularly on mobile platforms such as iOS.
   - pem_key_cert_pair is a pointer on the object containing client's private
     key and certificate chain. This parameter can be NULL if the client does
     not have such a key/cert pair.
   - verify_options is an optional verify_peer_options object which holds
     additional options controlling how peer certificates are verified. For
     example, you can supply a callback which receives the peer's certificate
     with which you can do additional verification. Can be NULL, in which
     case verification will retain default behavior. Any settings in
     verify_options are copied during this call, so the verify_options
     object can be released afterwards. */
GRPCAPI grpc_channel_credentials* grpc_ssl_credentials_create(
    const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
    const verify_peer_options* verify_options, void* reserved);

grpc_ssl_credentials_create参数说明:
pem_root_certs:如果为空,grpc会从环境变量GRPC_DEFAULT_SSL_ROOTS_FILE_PATH中搜索是否有根证书。如果失败,会尝试通过grpc_override_ssl_default_roots设置根。如果还是失败就会从grpc的安装目录中查找。这块比较容易理解,但是后面一段有个根缓存的东西,后面提到一点如果使用自己提供的根证书,提到一点避免large room pem 我个人的理解是密钥的长度不宜过大,但我觉得一般2048位也不大啊,不至于消耗过多的内存。这一点会影响到性能,还是注意一下,后续找找资料看看。

pem_key_cert_pair 是对象上的一个指针,该对象包含客户机的私钥和证书链。如果客户端没有这样的密钥/证书对,则此参数可以为空。这部分内容应该涉及SSL单向认证还是双向认证的问题。

verify_options是一个可选的verify_peer_options对象,它包含控制对等证书验证方式的其他选项。例如,您可以提供一个回调,它接收对等方的证书,您可以使用它进行额外的验证。可以为NULL,在这种情况下,验证将保留默认行为。在调用期间复制verify_options中的任何设置,因此可以在调用后释放verify_options对象。
这个参数暂时也不关心,实际都设置为NULL。

(3)创建Channel

使用域名和证书创建一个通讯的频道。
CreateChannel实现

grpc_channel* CreateChannel(const char* target, const grpc_channel_args* args) {
  if (target == nullptr) {
    gpr_log(GPR_ERROR, "cannot create channel with NULL target name");
    return nullptr;
  }
  // Add channel arg containing the server URI.
  grpc_core::UniquePtr<char> canonical_target =
      ResolverRegistry::AddDefaultPrefixIfNeeded(target);
  grpc_arg arg = grpc_channel_arg_string_create(
      const_cast<char*>(GRPC_ARG_SERVER_URI), canonical_target.get());
  const char* to_remove[] = {GRPC_ARG_SERVER_URI};
  grpc_channel_args* new_args =
      grpc_channel_args_copy_and_add_and_remove(args, to_remove, 1, &arg, 1);
  grpc_channel* channel =
      grpc_channel_create(target, new_args, GRPC_CLIENT_CHANNEL, nullptr);
  grpc_channel_args_destroy(new_args);
  return channel;
}

有点复杂,暂时不用这么深究。大致搞清楚两个参数的用法即可。target传入域名端口,args传入证书即可。

(4)创建存根并进行RPC远程调用
// Create a stub on the channel.
std::unique_ptr<Greeter::Stub> stub(Greeter::NewStub(channel));
// Make actual RPC calls on the stub.
grpc::Status s = stub->sayHello(&context, *request, response);
// Create a stub on the channel.
std::unique_ptr<Greeter::Stub> stub(Greeter::NewStub(channel));
// Make actual RPC calls on the stub.
grpc::Status s = stub->sayHello(&context, *request, response);

2.服务端处理流程

(1)创建证书

没找到服务端的处理流程,去github上找到一个大神写的demo。

empty root CA certificate causes C++ server to incorrectly accept clients · Issue #12146 · grpc/grpc

grpc::SslServerCredentialsOptions ssl_opts(GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY);
  ssl_opts.pem_root_certs = client_ca_pem;
  ssl_opts.pem_key_cert_pairs.push_back(pkcp);

  std::shared_ptr<grpc::ServerCredentials> creds = grpc::SslServerCredentials(ssl_opts);

  ServerBuilder builder;
  builder.AddListeningPort(server_address, creds);
  // Register "service" as the instance through which we'll communicate with
  // clients. In this case it corresponds to an *synchronous* service.
  builder.RegisterService(&service);
  // Finally assemble the server.
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;

SslServerCredentialsOptions配置服务端的SSL密钥证书这部分与客户端差不多。

/// Options to create ServerCredentials with SSL
struct SslServerCredentialsOptions {
  /// \warning Deprecated
  SslServerCredentialsOptions()
      : force_client_auth(false),
        client_certificate_request(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE) {}
  SslServerCredentialsOptions(
      grpc_ssl_client_certificate_request_type request_type)
      : force_client_auth(false), client_certificate_request(request_type) {}

  struct PemKeyCertPair {
    grpc::string private_key;
    grpc::string cert_chain;
  };
  grpc::string pem_root_certs;
  std::vector<PemKeyCertPair> pem_key_cert_pairs;
  /// \warning Deprecated
  bool force_client_auth;

  /// If both \a force_client_auth and \a client_certificate_request
  /// fields are set, \a force_client_auth takes effect, i.e.
  /// \a REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY
  /// will be enforced.
  grpc_ssl_client_certificate_request_type client_certificate_request;
};

grpc_ssl_client_certificate_request_type 枚举类型在include\grpc\grpc_security_constants.h中定义。

typedef enum {
  /** Server does not request client certificate.
     The certificate presented by the client is not checked by the server at
     all. (A client may present a self signed or signed certificate or not
     present a certificate at all and any of those option would be accepted) */
  GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE,
  /** Server requests client certificate but does not enforce that the client
     presents a certificate.

     If the client presents a certificate, the client authentication is left to
     the application (the necessary metadata will be available to the
     application via authentication context properties, see grpc_auth_context).

     The client's key certificate pair must be valid for the SSL connection to
     be established. */
  GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY,
  /** Server requests client certificate but does not enforce that the client
     presents a certificate.

     If the client presents a certificate, the client authentication is done by
     the gRPC framework. (For a successful connection the client needs to either
     present a certificate that can be verified against the root certificate
     configured by the server or not present a certificate at all)

     The client's key certificate pair must be valid for the SSL connection to
     be established. */
  GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY,
  /** Server requests client certificate and enforces that the client presents a
     certificate.

     If the client presents a certificate, the client authentication is left to
     the application (the necessary metadata will be available to the
     application via authentication context properties, see grpc_auth_context).

     The client's key certificate pair must be valid for the SSL connection to
     be established. */
  GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_BUT_DONT_VERIFY,
  /** Server requests client certificate and enforces that the client presents a
     certificate.

     The certificate presented by the client is verified by the gRPC framework.
     (For a successful connection the client needs to present a certificate that
     can be verified against the root certificate configured by the server)

     The client's key certificate pair must be valid for the SSL connection to
     be established. */
  GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY
} grpc_ssl_client_certificate_request_type;

因为需要双向认证,因此参数设置为GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY(GRPC SSL请求和要求客户端证书和验证)

SslServerCredentials创建证书
一直很纳闷为啥客户端的接口不声明成SslClientCredentials而是SslCredentials。不过影响不大,使用接口的时候注意一下就好。

具体实现grpc\src\cpp\server\secure_server_credentials.cc

std::shared_ptr<ServerCredentials> SslServerCredentials(
    const grpc::SslServerCredentialsOptions& options) {
  std::vector<grpc_ssl_pem_key_cert_pair> pem_key_cert_pairs;
  for (const auto& key_cert_pair : options.pem_key_cert_pairs) {
    grpc_ssl_pem_key_cert_pair p = {key_cert_pair.private_key.c_str(),
                                    key_cert_pair.cert_chain.c_str()};
    pem_key_cert_pairs.push_back(p);
  }
  grpc_server_credentials* c_creds = grpc_ssl_server_credentials_create_ex(
      options.pem_root_certs.empty() ? nullptr : options.pem_root_certs.c_str(),
      pem_key_cert_pairs.empty() ? nullptr : &pem_key_cert_pairs[0],
      pem_key_cert_pairs.size(),
      options.force_client_auth
          ? GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY
          : options.client_certificate_request,
      nullptr);
  return std::shared_ptr<ServerCredentials>(
      new SecureServerCredentials(c_creds));
}
(2)创建服务器流程
ServerBuilder builder;
  builder.AddListeningPort(server_address, creds);
  // Register "service" as the instance through which we'll communicate with
  // clients. In this case it corresponds to an *synchronous* service.
  builder.RegisterService(&service);
  // Finally assemble the server.
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;

与普通socket建立大同小异。
grpc\include\grpcpp中server_builder_impl.h 定义ServerBuilder类

 /// Return a running server which is ready for processing calls.
  /// Before calling, one typically needs to ensure that:
  ///  1. a service is registered - so that the server knows what to serve
  ///     (via RegisterService, or RegisterAsyncGenericService)
  ///  2. a listening port has been added - so the server knows where to receive
  ///     traffic (via AddListeningPort)
  ///  3. [for async api only] completion queues have been added via
  ///     AddCompletionQueue
  virtual std::unique_ptr<grpc::Server> BuildAndStart();

  /// Register a service. This call does not take ownership of the service.
  /// The service must exist for the lifetime of the \a Server instance returned
  /// by \a BuildAndStart().
  /// Matches requests with any :authority
  ServerBuilder& RegisterService(grpc::Service* service);

  /// Enlists an endpoint \a addr (port with an optional IP address) to
  /// bind the \a grpc::Server object to be created to.
  ///
  /// It can be invoked multiple times.
  ///
  /// \param addr_uri The address to try to bind to the server in URI form. If
  /// the scheme name is omitted, "dns:///" is assumed. To bind to any address,
  /// please use IPv6 any, i.e., [::]:<port>, which also accepts IPv4
  /// connections.  Valid values include dns:///localhost:1234, /
  /// 192.168.1.1:31416, dns:///[::1]:27182, etc.).
  /// \param creds The credentials associated with the server.
  /// \param selected_port[out] If not `nullptr`, gets populated with the port
  /// number bound to the \a grpc::Server for the corresponding endpoint after
  /// it is successfully bound by BuildAndStart(), 0 otherwise. AddListeningPort
  /// does not modify this pointer.
  ServerBuilder& AddListeningPort(
      const grpc::string& addr_uri,
      std::shared_ptr<grpc_impl::ServerCredentials> creds,
      int* selected_port = nullptr);

创建一个ServerBuilder对象,AddListeningPort这个方法将域名端口绑定服务器,证书与服务器进行关联。
RegisterService注册服务器
BuildAndStart接口创建并运行服务器。

简单流程

  • 创建服务对象
  • 创建构造服务器对象
  • 绑定服务器ip和端口
  • 注册服务器
  • 创建并运行服务器

二、简单实例

1.客户端代码

#include <iostream>
#include <memory>
#include <string>

#include <grpc++/grpc++.h>
#include <grpc++/security/credentials.h>
#include <fstream>

#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "helloworld.grpc.pb.h"
#endif

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using helloworld::HelloRequest;
using helloworld::HelloReply;
using helloworld::Greeter;

const char servercert_path[] = "./ssl_key/server_self_signed_crt.pem";
const char clientcert_path[] = "./ssl_key/client_self_signed_crt.pem";
const char clientkey_path[]  = "./ssl_key/client_privatekey.pem";

static std::string get_file_contents(const char *fpath)
{
  std::ifstream finstream(fpath);
  std::string contents;
  contents.assign((std::istreambuf_iterator<char>(finstream)),
                       std::istreambuf_iterator<char>());
  finstream.close();
  return contents;
}

class GreeterClient {
 public:
  GreeterClient(std::shared_ptr<Channel> channel)
      : stub_(Greeter::NewStub(channel)) {}

  // Assembles the client's payload, sends it and presents the response back
  // from the server.
  std::string SayHello(const std::string& user) {
    // Data we are sending to the server.
    HelloRequest request;
    request.set_name(user);

    // Container for the data we expect from the server.
    HelloReply reply;

    // Context for the client. It could be used to convey extra information to
    // the server and/or tweak certain RPC behaviors.
    ClientContext context;

    // The actual RPC.
    Status status = stub_->SayHello(&context, request, &reply);

    // Act upon its status.
    if (status.ok()) {
      return reply.message();
    } else {
      std::cout << status.error_code() << ": " << status.error_message()
                << std::endl;
      return "RPC failed";
    }
  }

 private:
  std::unique_ptr<Greeter::Stub> stub_;
};

int main(int argc, char** argv) {
  // Instantiate the client. It requires a channel, out of which the actual RPCs
  // are created. This channel models a connection to an endpoint (in this case,
  // localhost at port 50051). We indicate that the channel isn't authenticated
  // (use of InsecureChannelCredentials()).

  auto servercert = get_file_contents(servercert_path);
  auto clientkey  = get_file_contents(clientkey_path);
  auto clientcert = get_file_contents(clientcert_path);

  grpc::SslCredentialsOptions ssl_opts;
  ssl_opts.pem_root_certs  = servercert;
  ssl_opts.pem_private_key = clientkey;
  ssl_opts.pem_cert_chain  = clientcert;

  std::shared_ptr<grpc::ChannelCredentials> creds = grpc::SslCredentials(ssl_opts);

  GreeterClient greeter(grpc::CreateChannel(
      "localhost", creds));
  std::string user("world");
  std::string reply = greeter.SayHello(user);
  std::cout << "Greeter received: " << reply << std::endl;

  return 0;
}

2.服务端代码

#include <iostream>
#include <memory>
#include <string>

#include <grpc++/grpc++.h>
#include <grpc++/security/credentials.h>
#include <fstream>

#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "helloworld.grpc.pb.h"
#endif

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using helloworld::HelloRequest;
using helloworld::HelloReply;
using helloworld::Greeter;

const char clientcert_path[] = "./ssl_key/client_self_signed_crt.pem";
const char servercert_path[] = "./ssl_key/server_self_signed_crt.pem";
const char serverkey_path[]  = "./ssl_key/server_privatekey.pem";

static std::string get_file_contents(const char *fpath)
{
  std::ifstream finstream(fpath);
  std::string contents;
  contents.assign((std::istreambuf_iterator<char>(finstream)),
                       std::istreambuf_iterator<char>());
  finstream.close();
  return contents;
}

// Logic and data behind the server's behavior.
class GreeterServiceImpl final : public Greeter::Service {
  Status SayHello(ServerContext* context, const HelloRequest* request,
                  HelloReply* reply) override {
    std::string prefix("Hello ");
    reply->set_message(prefix + request->name());
    return Status::OK;
  }
};

void RunServer(char** argv) {
  std::string server_address("localhost:50051");
  GreeterServiceImpl service;

  auto clientcert = get_file_contents(clientcert_path); // for verifying clients
  auto servercert = get_file_contents(servercert_path);
  auto serverkey  = get_file_contents(serverkey_path);

  grpc::SslServerCredentialsOptions::PemKeyCertPair pkcp = {
    serverkey.c_str(), servercert.c_str()
  };

  grpc::SslServerCredentialsOptions ssl_opts(GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY);
  ssl_opts.pem_root_certs = clientcert;
  ssl_opts.pem_key_cert_pairs.push_back(pkcp);

  std::shared_ptr<grpc::ServerCredentials> creds;
  creds = grpc::SslServerCredentials(ssl_opts);

  ServerBuilder builder;
  builder.AddListeningPort(server_address, creds);
  builder.RegisterService(&service);
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;

  // Wait for the server to shutdown. Note that some other thread must be
  // responsible for shutting down the server for this call to ever return.
  server->Wait();
}

int main(int argc, char** argv) {
  RunServer(argv);

  return 0;
}

3.密钥生成脚本

#!/bin/sh

echo Generate CA key:
openssl genrsa -passout pass:1111 -des3 -out ca.key 2048

echo Generate CA certificate:
openssl req -passin pass:1111 -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/C=CN/ST=FuJian/L=XiaMen/O=YaXon/OU=gRPC/CN=localhost"
 
echo Generate server key:
openssl genrsa -passout pass:1111 -des3 -out server_privatekey.pem 2048
 
echo Generate server signing request:
openssl req -passin pass:1111 -new -key server_privatekey.pem -out server_csr.pem -subj "/C=CN/ST=FuJian/L=XiaMen/O=YaXon/OU=gRPC/CN=localhost"
 
echo Self-sign server certificate:
openssl x509 -req -passin pass:1111 -days 3650 -in server_csr.pem -CA ca.crt -CAkey ca.key -CAcreateserial -out server_self_signed_crt.pem
 
echo Remove passphrase from server key:
openssl rsa -passin pass:1111 -in server_privatekey.pem -out server_privatekey.pem
 
echo Generate client key
openssl genrsa -passout pass:1111 -des3 -out client_privatekey.pem 2048
 
echo Generate client signing request:
openssl req -passin pass:1111 -new -key client_privatekey.pem -out client_csr.pem -subj "/C=CN/ST=FuJian/L=XiaMen/O=YaXon/OU=gRPC/CN=localhost"
 
echo Self-sign client certificate:
openssl x509 -passin pass:1111 -req -days 3650 -in client_csr.pem -CA ca.crt -CAkey ca.key -CAcreateserial -out client_self_signed_crt.pem
 
echo Remove passphrase from client key:
openssl rsa -passin pass:1111 -in client_privatekey.pem -out client_privatekey.pem

4.实际测试运行结果

root@chenwr-pc:/home/workspace/project/grpc_project/grpc# GRPC_VERBOSITY=INFO ./greeter_server
I0423 14:38:07.332275206    8702 ev_epollex_linux.cc:1631]   Skipping epollex because it is not supported.
I0423 14:38:07.332379098    8702 ev_epoll1_linux.cc:116]     grpc epoll fd: 3
I0423 14:38:07.333027275    8702 server_builder.cc:332]      Synchronous server. Num CQs: 1, Min pollers: 1, Max Pollers: 2, CQ timeout (msec): 10000
Server listening on localhost:50051

root@chenwr-pc:/home/workspace/project/grpc_project/grpc# GRPC_VERBOSITY=INFO ./greeter_client
I0423 14:38:14.484686548    8709 ev_epollex_linux.cc:1631]   Skipping epollex because it is not supported.
I0423 14:38:14.484789046    8709 ev_epoll1_linux.cc:116]     grpc epoll fd: 3
I0423 14:38:14.504947090    8709 subchannel.cc:1055]         New connected subchannel at 0xd5d5f0 for subchannel 0xd5cd70
Greeter received: Hello world

三、注意事项

1.如果grpc SSL加密传输绑定为IP,并且证书不包含域名信息。
(生成密钥证书脚本中的-subj CN为设置域名)

GreeterClient greeter(grpc::CreateChannel(
      "127.0.0.1:50051", creds));

会出现如下错误

root@chenwr-pc:/home/workspace/project/grpc_project/grpc# GRPC_VERBOSITY=INFO ./greeter_server
I0423 14:33:47.782953499    8547 ev_epollex_linux.cc:1631]   Skipping epollex because it is not supported.
I0423 14:33:47.783058492    8547 ev_epoll1_linux.cc:116]     grpc epoll fd: 3
I0423 14:33:47.783750228    8547 server_builder.cc:332]      Synchronous server. Num CQs: 1, Min pollers: 1, Max Pollers: 2, CQ timeout (msec): 10000
I0423 14:33:47.788131754    8547 ssl_transport_security.cc:279] Could not get common name of subject from certificate.
Server listening on 127.0.0.1:50051
E0423 14:33:56.277264861    8551 ssl_transport_security.cc:1709] No match found for server name: 127.0.0.1.
I0423 14:33:56.283370850    8551 ssl_transport_security.cc:279] Could not get common name of subject from certificate.

root@chenwr-pc:/home/workspace/project/grpc_project/grpc# GRPC_VERBOSITY=INFO ./greeter_client
I0423 14:33:56.270917152    8553 ev_epollex_linux.cc:1631]   Skipping epollex because it is not supported.
I0423 14:33:56.271035505    8553 ev_epoll1_linux.cc:116]     grpc epoll fd: 3
I0423 14:33:56.283508687    8553 ssl_transport_security.cc:279] Could not get common name of subject from certificate.
I0423 14:33:56.283885220    8553 subchannel.cc:1003]         Connect failed: {"created":"@1587623636.283787741","description":"Peer name 127.0.0.1 is not in peer certificate","file":"/home/workspace/project/grpc_project/grpc/src/core/lib/security/security_connector/ssl/ssl_security_connector.cc","file_line":55}
I0423 14:33:56.284013256    8553 subchannel.cc:942]          Subchannel 0xe1f850: Retry in 988 milliseconds
14: failed to connect to all addresses
Greeter received: RPC failed

2.如果grpc SSL加密传输绑定为域名,并且证书不包含域名信息。
比如我实际测试未添加/CN=localhost。即使程序中使用

GreeterClient greeter(grpc::CreateChannel(
      "localhost:50051", creds));

运行依然会出错

E0423 14:36:02.849671123    8657 ssl_transport_security.cc:1709] No match found for server name: localhost.
I0423 14:36:02.866499334    8657 ssl_transport_security.cc:279] Could not get common name of subject from certifica

3.使用自定义域名
如果没有进行域名与ip绑定会出错

root@chenwr-pc:/home/workspace/project/grpc_project/grpc# GRPC_VERBOSITY=INFO ./greeter_server
I0423 14:51:20.496356740    9318 ev_epollex_linux.cc:1631]   Skipping epollex because it is not supported.
I0423 14:51:20.496452505    9318 ev_epoll1_linux.cc:116]     grpc epoll fd: 3
I0423 14:51:20.497141392    9318 server_builder.cc:332]      Synchronous server. Num CQs: 1, Min pollers: 1, Max Pollers: 2, CQ timeout (msec): 10000
E0423 14:51:26.347566527    9318 server_secure_chttp2.cc:81] {"created":"@1587624686.347327002","description":"Name or service not known","errno":-2,"file":"/home/workspace/project/grpc_project/grpc/src/core/lib/iomgr/resolve_address_posix.cc","file_line":108,"os_error":"Name or service not known","syscall":"getaddrinfo","target_address":"www.chenwr2018.com:50051"}
Server listening on www.chenwr2018.com:50051
Segmentation fault (core dumped)

IP绑定域名。
vi /etc/hosts 修改内容如图所示。

GreeterClient greeter(grpc::CreateChannel(
      "www.chenwr2018.com:50051", creds)); 端口可加也可不加。

运行结果:

root@chenwr-pc:/home/workspace/project/grpc_project/grpc# GRPC_VERBOSITY=INFO ./greeter_server
I0423 14:55:26.994279385    9562 ev_epollex_linux.cc:1631]   Skipping epollex because it is not supported.
I0423 14:55:26.994376646    9562 ev_epoll1_linux.cc:116]     grpc epoll fd: 3
I0423 14:55:26.995203388    9562 server_builder.cc:332]      Synchronous server. Num CQs: 1, Min pollers: 1, Max Pollers: 2, CQ timeout (msec): 10000
Server listening on www.chenwr2018.com:50051

root@chenwr-pc:/home/workspace/project/grpc_project/grpc# GRPC_VERBOSITY=INFO ./greeter_client
I0423 14:57:56.877750911    9610 ev_epollex_linux.cc:1631]   Skipping epollex because it is not supported.
I0423 14:57:56.877857866    9610 ev_epoll1_linux.cc:116]     grpc epoll fd: 3
I0423 14:57:56.897584852    9610 subchannel.cc:1055]         New connected subchannel at 0x148f5e0 for subchannel 0x147dd30
Greeter received: Hello world

4.如果使用不相关的证书
报错

root@chenwr-pc:/home/workspace/project/grpc_project/grpc# GRPC_VERBOSITY=INFO ./greeter_server
I0423 15:00:59.280097209    9847 ev_epollex_linux.cc:1631]   Skipping epollex because it is not supported.
I0423 15:00:59.280191481    9847 ev_epoll1_linux.cc:116]     grpc epoll fd: 3
I0423 15:00:59.280828605    9847 server_builder.cc:332]      Synchronous server. Num CQs: 1, Min pollers: 1, Max Pollers: 2, CQ timeout (msec): 10000
E0423 15:00:59.284965656    9847 ssl_transport_security.cc:785] Invalid private key.
E0423 15:00:59.285014702    9847 ssl_security_connector.cc:263] Handshaker factory creation failed with TSI_INVALID_ARGUMENT.
E0423 15:00:59.285068617    9847 server_secure_chttp2.cc:81] {"created":"@1587625259.285041668","description":"Unable to create secure server with credentials of type Ssl.","file":"/home/workspace/project/grpc_project/grpc/src/core/ext/transport/chttp2/server/secure/server_secure_chttp2.cc","file_line":63}
Server listening on www.chenwr2018.com:50051
Segmentation fault (core dumped)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章