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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章