I have a file that tries to run a REST and a gRPC server together that should successfully handle both http and https requests.
However, when attempting to test the server with HTTPS requests, I encounter the below error:
REST Server started
Listening for requests at: https://localhost:8080/
E0221 10:21:11.667654416 53391 ssl_transport_security.cc:2258] Could not create ssl context.
E0221 10:21:11.667749341 53391 ssl_security_connector.cc:270] Handshaker factory creation failed with TSI_OUT_OF_RESOURCES.
E0221 10:21:11.667887612 53391 chttp2_server.cc:1045] UNKNOWN:Unable to create secure server with credentials of type Ssl {file:"/opt/vcpkg/buildtrees/grpc/src/v1.51.1-1066d25324.clean/src/core/ext/transport/chttp2/server/chttp2_server.cc", file_line:1032, created_time:"2024-02-21T10:21:11.667802229+00:00"}
Failed to start gRPC server
Replicating the above Error
Libraries being used:
- Openssl 3.2.1
- grpc 1.41.1
- protobuf 3.21.12
- cryptopp 8.9.0
- boost 1.71.0
- cpprestsdk 2.10.15-1
Proto file for dummy gRPC server:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
gRPC and REST server code:
#include <fstream>
#include <sstream>
#include <iostream>
#include <memory>
#include <string>
#include <cpprest/http_listener.h>
#include <cpprest/uri.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <cryptopp/cryptlib.h>
#include <cryptopp/config_int.h>
#include <cryptopp/secblock.h>
#include <cryptopp/smartptr.h>
#include <cryptopp/osrng.h>
#include <cryptopp/aes.h>
#include <cryptopp/sha.h>
#include <grpcpp/grpcpp.h>
#include "test.grpc.pb.h"
#include "test.pb.h"
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "shares.grpc.pb.h"
#include "shares.pb.h"
#include <thread>
using web::http::http_request;
using web::http::http_response;
using web::http::methods;
using web::http::status_codes;
using web::http::experimental::listener::http_listener;
using web::http::experimental::listener::http_listener_config;
using web::uri_builder;
using grpc::Channel;
using grpc::ClientContext;
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using helloworld::Greeter;
using helloworld::HelloRequest;
using helloworld::HelloResponse;
class GreeterServiceImpl final : public Greeter::Service {
Status SayHello(ServerContext* context, const HelloRequest* request,
HelloResponse* reply) override {
std::string prefix("Hello ");
reply->set_message(prefix + request->name());
return Status::OK;
}
};
std::string read_keycert(const std::string& filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "Error opening file: " << filepath << std::endl;
return "";
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
void RunGrpcServer() {
std::string server_address("localhost:50052");
GreeterServiceImpl service;
ServerBuilder builder;
grpc::SslServerCredentialsOptions::PemKeyCertPair pkcp;
pkcp.private_key = read_keycert("test-private-key.pem");
pkcp.cert_chain = read_keycert("test-certificate.pem");
grpc::SslServerCredentialsOptions ssl_opts;
ssl_opts.pem_root_certs="";
ssl_opts.pem_key_cert_pairs.push_back(pkcp);
auto channel_creds = grpc::SslServerCredentials(ssl_opts);
builder.AddListeningPort(server_address, channel_creds);
builder.RegisterService(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());
if (server) {
std::cout << "gRPC Server listening on " << server_address << std::endl;
server->Wait(); // Wait for the server to shut down
} else {
std::cerr << "Failed to start gRPC server" << std::endl;
}
}
void RunRestServer() {
utility::string_t address = U("https://localhost:8080");
uri_builder uri(address);
auto addr = uri.to_uri().to_string();
http_listener_config config;
config.set_ssl_context_callback([](boost::asio::ssl::context& ctx){
ctx.set_options(boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::no_sslv3
| boost::asio::ssl::context::tlsv12);
ctx.use_certificate_chain_file("test-certificate.crt");
ctx.use_private_key_file("test-private-key.key", boost::asio::ssl::context::pem);
});
http_listener listener(addr, config);
listener.support(methods::GET, [](http_request request) {
request.reply(status_codes::OK, "Hello from secure REST server!");
});
try {
listener.open().then([]() {
std::cout << "REST Server started" << std::endl;
}).wait();
std::cout << "Listening for requests at: " << addr << std::endl;
std::thread([=]() {
while (true);
}).detach();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
int main() {
std::thread grpc_server_thread(RunGrpcServer);
std::thread rest_server_thread(RunRestServer);
grpc_server_thread.join();
rest_server_thread.join();
return 0;
}
Building the above code using CMake
CMakeList also includes code to generate certificates:
find_package(cpprestsdk REQUIRED)
find_package(Boost REQUIRED program_options)
find_package(Catch2 CONFIG REQUIRED)
find_package(cryptopp CONFIG REQUIRED)
find_package(Protobuf CONFIG REQUIRED)
find_package(gRPC CONFIG REQUIRED)
find_package(OpenSSL REQUIRED)
# generate keys and certificate for grpc with https unit tests
add_custom_target(
keys ALL
COMMAND openssl ecparam -genkey -name prime256v1 -noout -out test-private-key.pem
COMMAND openssl ec -in test-private-key.pem -pubout -out test-public-key.pem
COMMAND openssl req -new -x509 -sha256 -key test-private-key.pem -subj /CN=localhost -out test-certificate.pem
COMMENT "Generating keys and certificate for https unit tests"
BYPRODUCTS test-private-key.pem test-public-key.pem test-certificate.pem
)
# Proto file
get_filename_component(test_proto "protos/test.proto" ABSOLUTE)
get_filename_component(test_proto_path "${test_proto}" PATH)
set(VCPKG_TOOL_PATH "/opt/vcpkg/installed/x64-linux/tools")
# Generated files will be in the corresponding build directory
set(proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/test.pb.cc")
set(proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/test.pb.h")
set(grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/test.grpc.pb.cc")
set(grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/test.grpc.pb.h")
# Generating protobuf files for hello-world testing
add_custom_command(
OUTPUT "${proto_srcs}" "${proto_hdrs}"
COMMAND ${VCPKG_TOOL_PATH}/protobuf/protoc
ARGS --cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
-I "${test_proto_path}"
"${test_proto}"
DEPENDS "${test_proto}"
)
# Generating gRPC files
add_custom_command(
OUTPUT "${grpc_srcs}" "${grpc_hdrs}"
COMMAND ${VCPKG_TOOL_PATH}/protobuf/protoc
ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
--plugin=protoc-gen-grpc="${VCPKG_TOOL_PATH}/grpc/grpc_cpp_plugin"
-I "${test_proto_path}"
"${test_proto}"
DEPENDS "${proto_srcs}"
)
include_directories(${CMAKE_CURRENT_BINARY_DIR}) # Add the directory containing generated files
include_directories(/opt/vcpkg/installed/x64-linux/include/grpc++
/opt/vcpkg/installed/x64-linux/include/grpcpp
/opt/vcpkg/installed/x64-linux/include/grpc )
add_executable(grpc_test test/grpc.cpp ${proto_hdrs} ${proto_srcs} ${grpc_srcs} ${grpc_hdrs})
target_link_libraries(grpc_test PRIVATE protobuf::libprotobuf gRPC::grpc++ gRPC::grpc++_reflection
ssl ${Boost_LIBRARIES} crypto cryptopp::cryptopp yaml-cpp stdc++fs pthread gmp cpprest)
Now inside your project build directory you can run:
./grpc_test
This should replicate the above given error!
PS: All of these certificates are available in the correct location and should have been found by the servers!
How can I troubleshoot and resolve this problem to enable my servers to handle HTTPS requests effectively?
NOTE: I have tried running the above files with OpenSSL 1.1.1n and it doesn't give me any error and everything works. I fail to understand this behavior.
Any insights or suggestions would be greatly appreciated. Thank you!