Merge pull request #14483 from euroelessar/ticket-client

[grpc] Add SSL session client cache support
pull/14774/head
Jiangtao Li 7 years ago committed by GitHub
commit 356d491245
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      BUILD
  2. 44
      CMakeLists.txt
  3. 57
      Makefile
  4. 21
      build.yaml
  5. 4
      config.m4
  6. 5
      config.w32
  7. 2
      gRPC-C++.podspec
  8. 7
      gRPC-Core.podspec
  9. 3
      grpc.def
  10. 5
      grpc.gemspec
  11. 3
      grpc.gyp
  12. 19
      include/grpc/grpc_security.h
  13. 1
      include/grpc/grpc_security_constants.h
  14. 4
      include/grpc/impl/codegen/grpc_types.h
  15. 5
      package.xml
  16. 7
      src/core/lib/http/httpcli_security_connector.cc
  17. 11
      src/core/lib/security/credentials/ssl/ssl_credentials.cc
  18. 76
      src/core/lib/security/security_connector/security_connector.cc
  19. 4
      src/core/lib/security/security_connector/security_connector.h
  20. 73
      src/core/tsi/ssl/session_cache/ssl_session.h
  21. 58
      src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc
  22. 211
      src/core/tsi/ssl/session_cache/ssl_session_cache.cc
  23. 93
      src/core/tsi/ssl/session_cache/ssl_session_cache.h
  24. 76
      src/core/tsi/ssl/session_cache/ssl_session_openssl.cc
  25. 240
      src/core/tsi/ssl_transport_security.cc
  26. 98
      src/core/tsi/ssl_transport_security.h
  27. 3
      src/python/grpcio/grpc_core_dependencies.py
  28. 3
      src/python/grpcio_tests/tests/unit/_auth_context_test.py
  29. 6
      src/ruby/ext/grpc/rb_grpc_imports.generated.c
  30. 9
      src/ruby/ext/grpc/rb_grpc_imports.generated.h
  31. 17
      test/core/end2end/BUILD
  32. 280
      test/core/end2end/h2_ssl_session_reuse_test.cc
  33. 3
      test/core/surface/public_headers_must_be_c89.c
  34. 14
      test/core/tsi/BUILD
  35. 154
      test/core/tsi/ssl_session_cache_test.cc
  36. 159
      test/core/tsi/ssl_transport_security_test.cc
  37. 5
      tools/doxygen/Doxyfile.core.internal
  38. 29
      tools/run_tests/generated/sources_and_headers.json
  39. 24
      tools/run_tests/generated/tests.json

@ -1704,6 +1704,9 @@ grpc_cc_library(
"src/core/tsi/alts/handshaker/alts_tsi_handshaker.cc", "src/core/tsi/alts/handshaker/alts_tsi_handshaker.cc",
"src/core/tsi/alts/handshaker/alts_tsi_utils.cc", "src/core/tsi/alts/handshaker/alts_tsi_utils.cc",
"src/core/tsi/fake_transport_security.cc", "src/core/tsi/fake_transport_security.cc",
"src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc",
"src/core/tsi/ssl/session_cache/ssl_session_cache.cc",
"src/core/tsi/ssl/session_cache/ssl_session_openssl.cc",
"src/core/tsi/ssl_transport_security.cc", "src/core/tsi/ssl_transport_security.cc",
"src/core/tsi/transport_security_grpc.cc", "src/core/tsi/transport_security_grpc.cc",
], ],
@ -1715,6 +1718,8 @@ grpc_cc_library(
"src/core/tsi/alts/handshaker/alts_tsi_handshaker_private.h", "src/core/tsi/alts/handshaker/alts_tsi_handshaker_private.h",
"src/core/tsi/alts/handshaker/alts_tsi_utils.h", "src/core/tsi/alts/handshaker/alts_tsi_utils.h",
"src/core/tsi/fake_transport_security.h", "src/core/tsi/fake_transport_security.h",
"src/core/tsi/ssl/session_cache/ssl_session.h",
"src/core/tsi/ssl/session_cache/ssl_session_cache.h",
"src/core/tsi/ssl_transport_security.h", "src/core/tsi/ssl_transport_security.h",
"src/core/tsi/ssl_types.h", "src/core/tsi/ssl_types.h",
"src/core/tsi/transport_security_grpc.h", "src/core/tsi/transport_security_grpc.h",

@ -561,6 +561,7 @@ add_dependencies(buildtests_cxx grpc_tool_test)
add_dependencies(buildtests_cxx grpclb_api_test) add_dependencies(buildtests_cxx grpclb_api_test)
add_dependencies(buildtests_cxx grpclb_end2end_test) add_dependencies(buildtests_cxx grpclb_end2end_test)
add_dependencies(buildtests_cxx h2_ssl_cert_test) add_dependencies(buildtests_cxx h2_ssl_cert_test)
add_dependencies(buildtests_cxx h2_ssl_session_reuse_test)
add_dependencies(buildtests_cxx health_service_end2end_test) add_dependencies(buildtests_cxx health_service_end2end_test)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_cxx http2_client) add_dependencies(buildtests_cxx http2_client)
@ -1114,6 +1115,9 @@ add_library(grpc
src/core/ext/filters/deadline/deadline_filter.cc src/core/ext/filters/deadline/deadline_filter.cc
src/core/tsi/alts_transport_security.cc src/core/tsi/alts_transport_security.cc
src/core/tsi/fake_transport_security.cc src/core/tsi/fake_transport_security.cc
src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc
src/core/tsi/ssl/session_cache/ssl_session_cache.cc
src/core/tsi/ssl/session_cache/ssl_session_openssl.cc
src/core/tsi/ssl_transport_security.cc src/core/tsi/ssl_transport_security.cc
src/core/tsi/transport_security_grpc.cc src/core/tsi/transport_security_grpc.cc
src/core/ext/transport/chttp2/server/chttp2_server.cc src/core/ext/transport/chttp2/server/chttp2_server.cc
@ -1503,6 +1507,9 @@ add_library(grpc_cronet
src/core/ext/transport/chttp2/client/chttp2_connector.cc src/core/ext/transport/chttp2/client/chttp2_connector.cc
src/core/tsi/alts_transport_security.cc src/core/tsi/alts_transport_security.cc
src/core/tsi/fake_transport_security.cc src/core/tsi/fake_transport_security.cc
src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc
src/core/tsi/ssl/session_cache/ssl_session_cache.cc
src/core/tsi/ssl/session_cache/ssl_session_openssl.cc
src/core/tsi/ssl_transport_security.cc src/core/tsi/ssl_transport_security.cc
src/core/tsi/transport_security_grpc.cc src/core/tsi/transport_security_grpc.cc
src/core/ext/filters/load_reporting/server_load_reporting_filter.cc src/core/ext/filters/load_reporting/server_load_reporting_filter.cc
@ -11493,6 +11500,43 @@ target_link_libraries(h2_ssl_cert_test
endif (gRPC_BUILD_TESTS) endif (gRPC_BUILD_TESTS)
if (gRPC_BUILD_TESTS) if (gRPC_BUILD_TESTS)
add_executable(h2_ssl_session_reuse_test
test/core/end2end/h2_ssl_session_reuse_test.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(h2_ssl_session_reuse_test
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
PRIVATE third_party/googletest/googletest/include
PRIVATE third_party/googletest/googletest
PRIVATE third_party/googletest/googlemock/include
PRIVATE third_party/googletest/googlemock
PRIVATE ${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(h2_ssl_session_reuse_test
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
grpc_test_util
grpc++
grpc
gpr_test_util
gpr
${_gRPC_GFLAGS_LIBRARIES}
)
endif (gRPC_BUILD_TESTS)
if (gRPC_BUILD_TESTS)
add_executable(health_service_end2end_test add_executable(health_service_end2end_test
test/cpp/end2end/health_service_end2end_test.cc test/cpp/end2end/health_service_end2end_test.cc
third_party/googletest/googletest/src/gtest-all.cc third_party/googletest/googletest/src/gtest-all.cc

@ -1163,6 +1163,7 @@ grpc_tool_test: $(BINDIR)/$(CONFIG)/grpc_tool_test
grpclb_api_test: $(BINDIR)/$(CONFIG)/grpclb_api_test grpclb_api_test: $(BINDIR)/$(CONFIG)/grpclb_api_test
grpclb_end2end_test: $(BINDIR)/$(CONFIG)/grpclb_end2end_test grpclb_end2end_test: $(BINDIR)/$(CONFIG)/grpclb_end2end_test
h2_ssl_cert_test: $(BINDIR)/$(CONFIG)/h2_ssl_cert_test h2_ssl_cert_test: $(BINDIR)/$(CONFIG)/h2_ssl_cert_test
h2_ssl_session_reuse_test: $(BINDIR)/$(CONFIG)/h2_ssl_session_reuse_test
health_service_end2end_test: $(BINDIR)/$(CONFIG)/health_service_end2end_test health_service_end2end_test: $(BINDIR)/$(CONFIG)/health_service_end2end_test
http2_client: $(BINDIR)/$(CONFIG)/http2_client http2_client: $(BINDIR)/$(CONFIG)/http2_client
hybrid_end2end_test: $(BINDIR)/$(CONFIG)/hybrid_end2end_test hybrid_end2end_test: $(BINDIR)/$(CONFIG)/hybrid_end2end_test
@ -1641,6 +1642,7 @@ buildtests_cxx: privatelibs_cxx \
$(BINDIR)/$(CONFIG)/grpclb_api_test \ $(BINDIR)/$(CONFIG)/grpclb_api_test \
$(BINDIR)/$(CONFIG)/grpclb_end2end_test \ $(BINDIR)/$(CONFIG)/grpclb_end2end_test \
$(BINDIR)/$(CONFIG)/h2_ssl_cert_test \ $(BINDIR)/$(CONFIG)/h2_ssl_cert_test \
$(BINDIR)/$(CONFIG)/h2_ssl_session_reuse_test \
$(BINDIR)/$(CONFIG)/health_service_end2end_test \ $(BINDIR)/$(CONFIG)/health_service_end2end_test \
$(BINDIR)/$(CONFIG)/http2_client \ $(BINDIR)/$(CONFIG)/http2_client \
$(BINDIR)/$(CONFIG)/hybrid_end2end_test \ $(BINDIR)/$(CONFIG)/hybrid_end2end_test \
@ -1807,6 +1809,7 @@ buildtests_cxx: privatelibs_cxx \
$(BINDIR)/$(CONFIG)/grpclb_api_test \ $(BINDIR)/$(CONFIG)/grpclb_api_test \
$(BINDIR)/$(CONFIG)/grpclb_end2end_test \ $(BINDIR)/$(CONFIG)/grpclb_end2end_test \
$(BINDIR)/$(CONFIG)/h2_ssl_cert_test \ $(BINDIR)/$(CONFIG)/h2_ssl_cert_test \
$(BINDIR)/$(CONFIG)/h2_ssl_session_reuse_test \
$(BINDIR)/$(CONFIG)/health_service_end2end_test \ $(BINDIR)/$(CONFIG)/health_service_end2end_test \
$(BINDIR)/$(CONFIG)/http2_client \ $(BINDIR)/$(CONFIG)/http2_client \
$(BINDIR)/$(CONFIG)/hybrid_end2end_test \ $(BINDIR)/$(CONFIG)/hybrid_end2end_test \
@ -2255,6 +2258,8 @@ test_cxx: buildtests_cxx
$(Q) $(BINDIR)/$(CONFIG)/grpclb_end2end_test || ( echo test grpclb_end2end_test failed ; exit 1 ) $(Q) $(BINDIR)/$(CONFIG)/grpclb_end2end_test || ( echo test grpclb_end2end_test failed ; exit 1 )
$(E) "[RUN] Testing h2_ssl_cert_test" $(E) "[RUN] Testing h2_ssl_cert_test"
$(Q) $(BINDIR)/$(CONFIG)/h2_ssl_cert_test || ( echo test h2_ssl_cert_test failed ; exit 1 ) $(Q) $(BINDIR)/$(CONFIG)/h2_ssl_cert_test || ( echo test h2_ssl_cert_test failed ; exit 1 )
$(E) "[RUN] Testing h2_ssl_session_reuse_test"
$(Q) $(BINDIR)/$(CONFIG)/h2_ssl_session_reuse_test || ( echo test h2_ssl_session_reuse_test failed ; exit 1 )
$(E) "[RUN] Testing health_service_end2end_test" $(E) "[RUN] Testing health_service_end2end_test"
$(Q) $(BINDIR)/$(CONFIG)/health_service_end2end_test || ( echo test health_service_end2end_test failed ; exit 1 ) $(Q) $(BINDIR)/$(CONFIG)/health_service_end2end_test || ( echo test health_service_end2end_test failed ; exit 1 )
$(E) "[RUN] Testing inlined_vector_test" $(E) "[RUN] Testing inlined_vector_test"
@ -3419,6 +3424,9 @@ LIBGRPC_SRC = \
src/core/ext/filters/deadline/deadline_filter.cc \ src/core/ext/filters/deadline/deadline_filter.cc \
src/core/tsi/alts_transport_security.cc \ src/core/tsi/alts_transport_security.cc \
src/core/tsi/fake_transport_security.cc \ src/core/tsi/fake_transport_security.cc \
src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc \
src/core/tsi/ssl/session_cache/ssl_session_cache.cc \
src/core/tsi/ssl/session_cache/ssl_session_openssl.cc \
src/core/tsi/ssl_transport_security.cc \ src/core/tsi/ssl_transport_security.cc \
src/core/tsi/transport_security_grpc.cc \ src/core/tsi/transport_security_grpc.cc \
src/core/ext/transport/chttp2/server/chttp2_server.cc \ src/core/ext/transport/chttp2/server/chttp2_server.cc \
@ -3810,6 +3818,9 @@ LIBGRPC_CRONET_SRC = \
src/core/ext/transport/chttp2/client/chttp2_connector.cc \ src/core/ext/transport/chttp2/client/chttp2_connector.cc \
src/core/tsi/alts_transport_security.cc \ src/core/tsi/alts_transport_security.cc \
src/core/tsi/fake_transport_security.cc \ src/core/tsi/fake_transport_security.cc \
src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc \
src/core/tsi/ssl/session_cache/ssl_session_cache.cc \
src/core/tsi/ssl/session_cache/ssl_session_openssl.cc \
src/core/tsi/ssl_transport_security.cc \ src/core/tsi/ssl_transport_security.cc \
src/core/tsi/transport_security_grpc.cc \ src/core/tsi/transport_security_grpc.cc \
src/core/ext/filters/load_reporting/server_load_reporting_filter.cc \ src/core/ext/filters/load_reporting/server_load_reporting_filter.cc \
@ -17430,6 +17441,49 @@ endif
endif endif
H2_SSL_SESSION_REUSE_TEST_SRC = \
test/core/end2end/h2_ssl_session_reuse_test.cc \
H2_SSL_SESSION_REUSE_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(H2_SSL_SESSION_REUSE_TEST_SRC))))
ifeq ($(NO_SECURE),true)
# You can't build secure targets if you don't have OpenSSL.
$(BINDIR)/$(CONFIG)/h2_ssl_session_reuse_test: openssl_dep_error
else
ifeq ($(NO_PROTOBUF),true)
# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.0.0+.
$(BINDIR)/$(CONFIG)/h2_ssl_session_reuse_test: protobuf_dep_error
else
$(BINDIR)/$(CONFIG)/h2_ssl_session_reuse_test: $(PROTOBUF_DEP) $(H2_SSL_SESSION_REUSE_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
$(E) "[LD] Linking $@"
$(Q) mkdir -p `dirname $@`
$(Q) $(LDXX) $(LDFLAGS) $(H2_SSL_SESSION_REUSE_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/h2_ssl_session_reuse_test
endif
endif
$(OBJDIR)/$(CONFIG)/test/core/end2end/h2_ssl_session_reuse_test.o: $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
deps_h2_ssl_session_reuse_test: $(H2_SSL_SESSION_REUSE_TEST_OBJS:.o=.dep)
ifneq ($(NO_SECURE),true)
ifneq ($(NO_DEPS),true)
-include $(H2_SSL_SESSION_REUSE_TEST_OBJS:.o=.dep)
endif
endif
HEALTH_SERVICE_END2END_TEST_SRC = \ HEALTH_SERVICE_END2END_TEST_SRC = \
test/cpp/end2end/health_service_end2end_test.cc \ test/cpp/end2end/health_service_end2end_test.cc \
@ -23505,6 +23559,9 @@ src/core/tsi/alts/zero_copy_frame_protector/alts_iovec_record_protocol.cc: $(OPE
src/core/tsi/alts/zero_copy_frame_protector/alts_zero_copy_grpc_protector.cc: $(OPENSSL_DEP) src/core/tsi/alts/zero_copy_frame_protector/alts_zero_copy_grpc_protector.cc: $(OPENSSL_DEP)
src/core/tsi/alts_transport_security.cc: $(OPENSSL_DEP) src/core/tsi/alts_transport_security.cc: $(OPENSSL_DEP)
src/core/tsi/fake_transport_security.cc: $(OPENSSL_DEP) src/core/tsi/fake_transport_security.cc: $(OPENSSL_DEP)
src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc: $(OPENSSL_DEP)
src/core/tsi/ssl/session_cache/ssl_session_cache.cc: $(OPENSSL_DEP)
src/core/tsi/ssl/session_cache/ssl_session_openssl.cc: $(OPENSSL_DEP)
src/core/tsi/ssl_transport_security.cc: $(OPENSSL_DEP) src/core/tsi/ssl_transport_security.cc: $(OPENSSL_DEP)
src/core/tsi/transport_security.cc: $(OPENSSL_DEP) src/core/tsi/transport_security.cc: $(OPENSSL_DEP)
src/core/tsi/transport_security_adapter.cc: $(OPENSSL_DEP) src/core/tsi/transport_security_adapter.cc: $(OPENSSL_DEP)

@ -1037,12 +1037,17 @@ filegroups:
headers: headers:
- src/core/tsi/alts_transport_security.h - src/core/tsi/alts_transport_security.h
- src/core/tsi/fake_transport_security.h - src/core/tsi/fake_transport_security.h
- src/core/tsi/ssl/session_cache/ssl_session.h
- src/core/tsi/ssl/session_cache/ssl_session_cache.h
- src/core/tsi/ssl_transport_security.h - src/core/tsi/ssl_transport_security.h
- src/core/tsi/ssl_types.h - src/core/tsi/ssl_types.h
- src/core/tsi/transport_security_grpc.h - src/core/tsi/transport_security_grpc.h
src: src:
- src/core/tsi/alts_transport_security.cc - src/core/tsi/alts_transport_security.cc
- src/core/tsi/fake_transport_security.cc - src/core/tsi/fake_transport_security.cc
- src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc
- src/core/tsi/ssl/session_cache/ssl_session_cache.cc
- src/core/tsi/ssl/session_cache/ssl_session_openssl.cc
- src/core/tsi/ssl_transport_security.cc - src/core/tsi/ssl_transport_security.cc
- src/core/tsi/transport_security_grpc.cc - src/core/tsi/transport_security_grpc.cc
deps: deps:
@ -4532,6 +4537,22 @@ targets:
- gpr - gpr
uses: uses:
- grpc++_test - grpc++_test
- name: h2_ssl_session_reuse_test
gtest: true
build: test
language: c++
headers:
- test/core/end2end/end2end_tests.h
src:
- test/core/end2end/h2_ssl_session_reuse_test.cc
deps:
- grpc_test_util
- grpc++
- grpc
- gpr_test_util
- gpr
uses:
- grpc++_test
- name: health_service_end2end_test - name: health_service_end2end_test
gtest: true gtest: true
build: test build: test

@ -343,6 +343,9 @@ if test "$PHP_GRPC" != "no"; then
src/core/ext/filters/deadline/deadline_filter.cc \ src/core/ext/filters/deadline/deadline_filter.cc \
src/core/tsi/alts_transport_security.cc \ src/core/tsi/alts_transport_security.cc \
src/core/tsi/fake_transport_security.cc \ src/core/tsi/fake_transport_security.cc \
src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc \
src/core/tsi/ssl/session_cache/ssl_session_cache.cc \
src/core/tsi/ssl/session_cache/ssl_session_openssl.cc \
src/core/tsi/ssl_transport_security.cc \ src/core/tsi/ssl_transport_security.cc \
src/core/tsi/transport_security_grpc.cc \ src/core/tsi/transport_security_grpc.cc \
src/core/ext/transport/chttp2/server/chttp2_server.cc \ src/core/ext/transport/chttp2/server/chttp2_server.cc \
@ -700,6 +703,7 @@ if test "$PHP_GRPC" != "no"; then
PHP_ADD_BUILD_DIR($ext_builddir/src/core/tsi/alts/frame_protector) PHP_ADD_BUILD_DIR($ext_builddir/src/core/tsi/alts/frame_protector)
PHP_ADD_BUILD_DIR($ext_builddir/src/core/tsi/alts/handshaker) PHP_ADD_BUILD_DIR($ext_builddir/src/core/tsi/alts/handshaker)
PHP_ADD_BUILD_DIR($ext_builddir/src/core/tsi/alts/zero_copy_frame_protector) PHP_ADD_BUILD_DIR($ext_builddir/src/core/tsi/alts/zero_copy_frame_protector)
PHP_ADD_BUILD_DIR($ext_builddir/src/core/tsi/ssl/session_cache)
PHP_ADD_BUILD_DIR($ext_builddir/third_party/boringssl/crypto) PHP_ADD_BUILD_DIR($ext_builddir/third_party/boringssl/crypto)
PHP_ADD_BUILD_DIR($ext_builddir/third_party/boringssl/crypto/asn1) PHP_ADD_BUILD_DIR($ext_builddir/third_party/boringssl/crypto/asn1)
PHP_ADD_BUILD_DIR($ext_builddir/third_party/boringssl/crypto/base64) PHP_ADD_BUILD_DIR($ext_builddir/third_party/boringssl/crypto/base64)

@ -320,6 +320,9 @@ if (PHP_GRPC != "no") {
"src\\core\\ext\\filters\\deadline\\deadline_filter.cc " + "src\\core\\ext\\filters\\deadline\\deadline_filter.cc " +
"src\\core\\tsi\\alts_transport_security.cc " + "src\\core\\tsi\\alts_transport_security.cc " +
"src\\core\\tsi\\fake_transport_security.cc " + "src\\core\\tsi\\fake_transport_security.cc " +
"src\\core\\tsi\\ssl\\session_cache\\ssl_session_boringssl.cc " +
"src\\core\\tsi\\ssl\\session_cache\\ssl_session_cache.cc " +
"src\\core\\tsi\\ssl\\session_cache\\ssl_session_openssl.cc " +
"src\\core\\tsi\\ssl_transport_security.cc " + "src\\core\\tsi\\ssl_transport_security.cc " +
"src\\core\\tsi\\transport_security_grpc.cc " + "src\\core\\tsi\\transport_security_grpc.cc " +
"src\\core\\ext\\transport\\chttp2\\server\\chttp2_server.cc " + "src\\core\\ext\\transport\\chttp2\\server\\chttp2_server.cc " +
@ -714,6 +717,8 @@ if (PHP_GRPC != "no") {
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\tsi\\alts\\frame_protector"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\tsi\\alts\\frame_protector");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\tsi\\alts\\handshaker"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\tsi\\alts\\handshaker");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\tsi\\alts\\zero_copy_frame_protector"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\tsi\\alts\\zero_copy_frame_protector");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\tsi\\ssl");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\tsi\\ssl\\session_cache");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\php"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\php");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\php\\ext"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\php\\ext");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\php\\ext\\grpc"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\php\\ext\\grpc");

@ -333,6 +333,8 @@ Pod::Spec.new do |s|
'src/core/ext/filters/deadline/deadline_filter.h', 'src/core/ext/filters/deadline/deadline_filter.h',
'src/core/tsi/alts_transport_security.h', 'src/core/tsi/alts_transport_security.h',
'src/core/tsi/fake_transport_security.h', 'src/core/tsi/fake_transport_security.h',
'src/core/tsi/ssl/session_cache/ssl_session.h',
'src/core/tsi/ssl/session_cache/ssl_session_cache.h',
'src/core/tsi/ssl_transport_security.h', 'src/core/tsi/ssl_transport_security.h',
'src/core/tsi/ssl_types.h', 'src/core/tsi/ssl_types.h',
'src/core/tsi/transport_security_grpc.h', 'src/core/tsi/transport_security_grpc.h',

@ -344,6 +344,8 @@ Pod::Spec.new do |s|
'src/core/ext/filters/deadline/deadline_filter.h', 'src/core/ext/filters/deadline/deadline_filter.h',
'src/core/tsi/alts_transport_security.h', 'src/core/tsi/alts_transport_security.h',
'src/core/tsi/fake_transport_security.h', 'src/core/tsi/fake_transport_security.h',
'src/core/tsi/ssl/session_cache/ssl_session.h',
'src/core/tsi/ssl/session_cache/ssl_session_cache.h',
'src/core/tsi/ssl_transport_security.h', 'src/core/tsi/ssl_transport_security.h',
'src/core/tsi/ssl_types.h', 'src/core/tsi/ssl_types.h',
'src/core/tsi/transport_security_grpc.h', 'src/core/tsi/transport_security_grpc.h',
@ -756,6 +758,9 @@ Pod::Spec.new do |s|
'src/core/ext/filters/deadline/deadline_filter.cc', 'src/core/ext/filters/deadline/deadline_filter.cc',
'src/core/tsi/alts_transport_security.cc', 'src/core/tsi/alts_transport_security.cc',
'src/core/tsi/fake_transport_security.cc', 'src/core/tsi/fake_transport_security.cc',
'src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc',
'src/core/tsi/ssl/session_cache/ssl_session_cache.cc',
'src/core/tsi/ssl/session_cache/ssl_session_openssl.cc',
'src/core/tsi/ssl_transport_security.cc', 'src/core/tsi/ssl_transport_security.cc',
'src/core/tsi/transport_security_grpc.cc', 'src/core/tsi/transport_security_grpc.cc',
'src/core/ext/transport/chttp2/server/chttp2_server.cc', 'src/core/ext/transport/chttp2/server/chttp2_server.cc',
@ -911,6 +916,8 @@ Pod::Spec.new do |s|
'src/core/ext/filters/deadline/deadline_filter.h', 'src/core/ext/filters/deadline/deadline_filter.h',
'src/core/tsi/alts_transport_security.h', 'src/core/tsi/alts_transport_security.h',
'src/core/tsi/fake_transport_security.h', 'src/core/tsi/fake_transport_security.h',
'src/core/tsi/ssl/session_cache/ssl_session.h',
'src/core/tsi/ssl/session_cache/ssl_session_cache.h',
'src/core/tsi/ssl_transport_security.h', 'src/core/tsi/ssl_transport_security.h',
'src/core/tsi/ssl_types.h', 'src/core/tsi/ssl_types.h',
'src/core/tsi/transport_security_grpc.h', 'src/core/tsi/transport_security_grpc.h',

@ -83,6 +83,9 @@ EXPORTS
grpc_auth_context_add_property grpc_auth_context_add_property
grpc_auth_context_add_cstring_property grpc_auth_context_add_cstring_property
grpc_auth_context_set_peer_identity_property_name grpc_auth_context_set_peer_identity_property_name
grpc_ssl_session_cache_create_lru
grpc_ssl_session_cache_destroy
grpc_ssl_session_cache_create_channel_arg
grpc_channel_credentials_release grpc_channel_credentials_release
grpc_google_default_credentials_create grpc_google_default_credentials_create
grpc_set_ssl_roots_override_callback grpc_set_ssl_roots_override_callback

@ -274,6 +274,8 @@ Gem::Specification.new do |s|
s.files += %w( src/core/ext/filters/deadline/deadline_filter.h ) s.files += %w( src/core/ext/filters/deadline/deadline_filter.h )
s.files += %w( src/core/tsi/alts_transport_security.h ) s.files += %w( src/core/tsi/alts_transport_security.h )
s.files += %w( src/core/tsi/fake_transport_security.h ) s.files += %w( src/core/tsi/fake_transport_security.h )
s.files += %w( src/core/tsi/ssl/session_cache/ssl_session.h )
s.files += %w( src/core/tsi/ssl/session_cache/ssl_session_cache.h )
s.files += %w( src/core/tsi/ssl_transport_security.h ) s.files += %w( src/core/tsi/ssl_transport_security.h )
s.files += %w( src/core/tsi/ssl_types.h ) s.files += %w( src/core/tsi/ssl_types.h )
s.files += %w( src/core/tsi/transport_security_grpc.h ) s.files += %w( src/core/tsi/transport_security_grpc.h )
@ -689,6 +691,9 @@ Gem::Specification.new do |s|
s.files += %w( src/core/ext/filters/deadline/deadline_filter.cc ) s.files += %w( src/core/ext/filters/deadline/deadline_filter.cc )
s.files += %w( src/core/tsi/alts_transport_security.cc ) s.files += %w( src/core/tsi/alts_transport_security.cc )
s.files += %w( src/core/tsi/fake_transport_security.cc ) s.files += %w( src/core/tsi/fake_transport_security.cc )
s.files += %w( src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc )
s.files += %w( src/core/tsi/ssl/session_cache/ssl_session_cache.cc )
s.files += %w( src/core/tsi/ssl/session_cache/ssl_session_openssl.cc )
s.files += %w( src/core/tsi/ssl_transport_security.cc ) s.files += %w( src/core/tsi/ssl_transport_security.cc )
s.files += %w( src/core/tsi/transport_security_grpc.cc ) s.files += %w( src/core/tsi/transport_security_grpc.cc )
s.files += %w( src/core/ext/transport/chttp2/server/chttp2_server.cc ) s.files += %w( src/core/ext/transport/chttp2/server/chttp2_server.cc )

@ -496,6 +496,9 @@
'src/core/ext/filters/deadline/deadline_filter.cc', 'src/core/ext/filters/deadline/deadline_filter.cc',
'src/core/tsi/alts_transport_security.cc', 'src/core/tsi/alts_transport_security.cc',
'src/core/tsi/fake_transport_security.cc', 'src/core/tsi/fake_transport_security.cc',
'src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc',
'src/core/tsi/ssl/session_cache/ssl_session_cache.cc',
'src/core/tsi/ssl/session_cache/ssl_session_openssl.cc',
'src/core/tsi/ssl_transport_security.cc', 'src/core/tsi/ssl_transport_security.cc',
'src/core/tsi/transport_security_grpc.cc', 'src/core/tsi/transport_security_grpc.cc',
'src/core/ext/transport/chttp2/server/chttp2_server.cc', 'src/core/ext/transport/chttp2/server/chttp2_server.cc',

@ -100,6 +100,25 @@ GRPCAPI void grpc_auth_context_add_cstring_property(grpc_auth_context* ctx,
GRPCAPI int grpc_auth_context_set_peer_identity_property_name( GRPCAPI int grpc_auth_context_set_peer_identity_property_name(
grpc_auth_context* ctx, const char* name); grpc_auth_context* ctx, const char* name);
/** --- SSL Session Cache. ---
A SSL session cache object represents a way to cache client sessions
between connections. Only ticket-based resumption is supported. */
typedef struct grpc_ssl_session_cache grpc_ssl_session_cache;
/** Create LRU cache for client-side SSL sessions with the given capacity.
If capacity is < 1, a default capacity is used instead. */
GRPCAPI grpc_ssl_session_cache* grpc_ssl_session_cache_create_lru(
size_t capacity);
/** Destroy SSL session cache. */
GRPCAPI void grpc_ssl_session_cache_destroy(grpc_ssl_session_cache* cache);
/** Create a channel arg with the given cache object. */
GRPCAPI grpc_arg
grpc_ssl_session_cache_create_channel_arg(grpc_ssl_session_cache* cache);
/** --- grpc_channel_credentials object. --- /** --- grpc_channel_credentials object. ---
A channel credentials object represents a way to authenticate a client on a A channel credentials object represents a way to authenticate a client on a

@ -29,6 +29,7 @@ extern "C" {
#define GRPC_X509_CN_PROPERTY_NAME "x509_common_name" #define GRPC_X509_CN_PROPERTY_NAME "x509_common_name"
#define GRPC_X509_SAN_PROPERTY_NAME "x509_subject_alternative_name" #define GRPC_X509_SAN_PROPERTY_NAME "x509_subject_alternative_name"
#define GRPC_X509_PEM_CERT_PROPERTY_NAME "x509_pem_cert" #define GRPC_X509_PEM_CERT_PROPERTY_NAME "x509_pem_cert"
#define GRPC_SSL_SESSION_REUSED_PROPERTY "ssl_session_reused"
/** Environment variable that points to the default SSL roots file. This file /** Environment variable that points to the default SSL roots file. This file
must be a PEM encoded file with all the roots such as the one that can be must be a PEM encoded file with all the roots such as the one that can be

@ -258,6 +258,10 @@ typedef struct {
secure channel is an SSL channel). If this parameter is specified and the secure channel is an SSL channel). If this parameter is specified and the
underlying is not an SSL channel, it will just be ignored. */ underlying is not an SSL channel, it will just be ignored. */
#define GRPC_SSL_TARGET_NAME_OVERRIDE_ARG "grpc.ssl_target_name_override" #define GRPC_SSL_TARGET_NAME_OVERRIDE_ARG "grpc.ssl_target_name_override"
/** If non-zero, a pointer to a session cache (a pointer of type
grpc_ssl_session_cache*). (use grpc_ssl_session_cache_arg_vtable() to fetch
an appropriate pointer arg vtable) */
#define GRPC_SSL_SESSION_CACHE_ARG "grpc.ssl_session_cache"
/** Maximum metadata size, in bytes. Note this limit applies to the max sum of /** Maximum metadata size, in bytes. Note this limit applies to the max sum of
all metadata key-value entries in a batch of headers. */ all metadata key-value entries in a batch of headers. */
#define GRPC_ARG_MAX_METADATA_SIZE "grpc.max_metadata_size" #define GRPC_ARG_MAX_METADATA_SIZE "grpc.max_metadata_size"

@ -281,6 +281,8 @@
<file baseinstalldir="/" name="src/core/ext/filters/deadline/deadline_filter.h" role="src" /> <file baseinstalldir="/" name="src/core/ext/filters/deadline/deadline_filter.h" role="src" />
<file baseinstalldir="/" name="src/core/tsi/alts_transport_security.h" role="src" /> <file baseinstalldir="/" name="src/core/tsi/alts_transport_security.h" role="src" />
<file baseinstalldir="/" name="src/core/tsi/fake_transport_security.h" role="src" /> <file baseinstalldir="/" name="src/core/tsi/fake_transport_security.h" role="src" />
<file baseinstalldir="/" name="src/core/tsi/ssl/session_cache/ssl_session.h" role="src" />
<file baseinstalldir="/" name="src/core/tsi/ssl/session_cache/ssl_session_cache.h" role="src" />
<file baseinstalldir="/" name="src/core/tsi/ssl_transport_security.h" role="src" /> <file baseinstalldir="/" name="src/core/tsi/ssl_transport_security.h" role="src" />
<file baseinstalldir="/" name="src/core/tsi/ssl_types.h" role="src" /> <file baseinstalldir="/" name="src/core/tsi/ssl_types.h" role="src" />
<file baseinstalldir="/" name="src/core/tsi/transport_security_grpc.h" role="src" /> <file baseinstalldir="/" name="src/core/tsi/transport_security_grpc.h" role="src" />
@ -696,6 +698,9 @@
<file baseinstalldir="/" name="src/core/ext/filters/deadline/deadline_filter.cc" role="src" /> <file baseinstalldir="/" name="src/core/ext/filters/deadline/deadline_filter.cc" role="src" />
<file baseinstalldir="/" name="src/core/tsi/alts_transport_security.cc" role="src" /> <file baseinstalldir="/" name="src/core/tsi/alts_transport_security.cc" role="src" />
<file baseinstalldir="/" name="src/core/tsi/fake_transport_security.cc" role="src" /> <file baseinstalldir="/" name="src/core/tsi/fake_transport_security.cc" role="src" />
<file baseinstalldir="/" name="src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc" role="src" />
<file baseinstalldir="/" name="src/core/tsi/ssl/session_cache/ssl_session_cache.cc" role="src" />
<file baseinstalldir="/" name="src/core/tsi/ssl/session_cache/ssl_session_openssl.cc" role="src" />
<file baseinstalldir="/" name="src/core/tsi/ssl_transport_security.cc" role="src" /> <file baseinstalldir="/" name="src/core/tsi/ssl_transport_security.cc" role="src" />
<file baseinstalldir="/" name="src/core/tsi/transport_security_grpc.cc" role="src" /> <file baseinstalldir="/" name="src/core/tsi/transport_security_grpc.cc" role="src" />
<file baseinstalldir="/" name="src/core/ext/transport/chttp2/server/chttp2_server.cc" role="src" /> <file baseinstalldir="/" name="src/core/ext/transport/chttp2/server/chttp2_server.cc" role="src" />

@ -121,8 +121,11 @@ static grpc_security_status httpcli_ssl_channel_security_connector_create(
if (secure_peer_name != nullptr) { if (secure_peer_name != nullptr) {
c->secure_peer_name = gpr_strdup(secure_peer_name); c->secure_peer_name = gpr_strdup(secure_peer_name);
} }
result = tsi_create_ssl_client_handshaker_factory( tsi_ssl_client_handshaker_options options;
nullptr, pem_root_certs, nullptr, nullptr, 0, &c->handshaker_factory); memset(&options, 0, sizeof(options));
options.pem_root_certs = pem_root_certs;
result = tsi_create_ssl_client_handshaker_factory_with_options(
&options, &c->handshaker_factory);
if (result != TSI_OK) { if (result != TSI_OK) {
gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.", gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.",
tsi_result_to_string(result)); tsi_result_to_string(result));

@ -24,6 +24,7 @@
#include "src/core/lib/channel/channel_args.h" #include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/surface/api_trace.h" #include "src/core/lib/surface/api_trace.h"
#include "src/core/tsi/ssl_transport_security.h"
#include <grpc/support/alloc.h> #include <grpc/support/alloc.h>
#include <grpc/support/log.h> #include <grpc/support/log.h>
@ -56,16 +57,22 @@ static grpc_security_status ssl_create_security_connector(
grpc_ssl_credentials* c = reinterpret_cast<grpc_ssl_credentials*>(creds); grpc_ssl_credentials* c = reinterpret_cast<grpc_ssl_credentials*>(creds);
grpc_security_status status = GRPC_SECURITY_OK; grpc_security_status status = GRPC_SECURITY_OK;
const char* overridden_target_name = nullptr; const char* overridden_target_name = nullptr;
tsi_ssl_session_cache* ssl_session_cache = nullptr;
for (size_t i = 0; args && i < args->num_args; i++) { for (size_t i = 0; args && i < args->num_args; i++) {
grpc_arg* arg = &args->args[i]; grpc_arg* arg = &args->args[i];
if (strcmp(arg->key, GRPC_SSL_TARGET_NAME_OVERRIDE_ARG) == 0 && if (strcmp(arg->key, GRPC_SSL_TARGET_NAME_OVERRIDE_ARG) == 0 &&
arg->type == GRPC_ARG_STRING) { arg->type == GRPC_ARG_STRING) {
overridden_target_name = arg->value.string; overridden_target_name = arg->value.string;
break; }
if (strcmp(arg->key, GRPC_SSL_SESSION_CACHE_ARG) == 0 &&
arg->type == GRPC_ARG_POINTER) {
ssl_session_cache =
static_cast<tsi_ssl_session_cache*>(arg->value.pointer.p);
} }
} }
status = grpc_ssl_channel_security_connector_create( status = grpc_ssl_channel_security_connector_create(
creds, call_creds, &c->config, target, overridden_target_name, sc); creds, call_creds, &c->config, target, overridden_target_name,
ssl_session_cache, sc);
if (status != GRPC_SECURITY_OK) { if (status != GRPC_SECURITY_OK) {
return status; return status;
} }

@ -542,6 +542,46 @@ grpc_server_security_connector* grpc_fake_server_security_connector_create(
/* --- Ssl implementation. --- */ /* --- Ssl implementation. --- */
grpc_ssl_session_cache* grpc_ssl_session_cache_create_lru(size_t capacity) {
tsi_ssl_session_cache* cache = tsi_ssl_session_cache_create_lru(capacity);
return reinterpret_cast<grpc_ssl_session_cache*>(cache);
}
void grpc_ssl_session_cache_destroy(grpc_ssl_session_cache* cache) {
tsi_ssl_session_cache* tsi_cache =
reinterpret_cast<tsi_ssl_session_cache*>(cache);
tsi_ssl_session_cache_unref(tsi_cache);
}
static void* grpc_ssl_session_cache_arg_copy(void* p) {
tsi_ssl_session_cache* tsi_cache =
reinterpret_cast<tsi_ssl_session_cache*>(p);
// destroy call below will unref the pointer.
tsi_ssl_session_cache_ref(tsi_cache);
return p;
}
static void grpc_ssl_session_cache_arg_destroy(void* p) {
tsi_ssl_session_cache* tsi_cache =
reinterpret_cast<tsi_ssl_session_cache*>(p);
tsi_ssl_session_cache_unref(tsi_cache);
}
static int grpc_ssl_session_cache_arg_cmp(void* p, void* q) {
return GPR_ICMP(p, q);
}
grpc_arg grpc_ssl_session_cache_create_channel_arg(
grpc_ssl_session_cache* cache) {
static const grpc_arg_pointer_vtable vtable = {
grpc_ssl_session_cache_arg_copy,
grpc_ssl_session_cache_arg_destroy,
grpc_ssl_session_cache_arg_cmp,
};
return grpc_channel_arg_pointer_create(
const_cast<char*>(GRPC_SSL_SESSION_CACHE_ARG), cache, &vtable);
}
typedef struct { typedef struct {
grpc_channel_security_connector base; grpc_channel_security_connector base;
tsi_ssl_client_handshaker_factory* client_handshaker_factory; tsi_ssl_client_handshaker_factory* client_handshaker_factory;
@ -760,6 +800,9 @@ grpc_auth_context* tsi_ssl_peer_to_auth_context(const tsi_peer* peer) {
} else if (strcmp(prop->name, TSI_X509_PEM_CERT_PROPERTY) == 0) { } else if (strcmp(prop->name, TSI_X509_PEM_CERT_PROPERTY) == 0) {
grpc_auth_context_add_property(ctx, GRPC_X509_PEM_CERT_PROPERTY_NAME, grpc_auth_context_add_property(ctx, GRPC_X509_PEM_CERT_PROPERTY_NAME,
prop->value.data, prop->value.length); prop->value.data, prop->value.length);
} else if (strcmp(prop->name, TSI_SSL_SESSION_REUSED_PEER_PROPERTY) == 0) {
grpc_auth_context_add_property(ctx, GRPC_SSL_SESSION_REUSED_PROPERTY,
prop->value.data, prop->value.length);
} }
} }
if (peer_identity_property_name != nullptr) { if (peer_identity_property_name != nullptr) {
@ -983,28 +1026,30 @@ grpc_security_status grpc_ssl_channel_security_connector_create(
grpc_channel_credentials* channel_creds, grpc_channel_credentials* channel_creds,
grpc_call_credentials* request_metadata_creds, grpc_call_credentials* request_metadata_creds,
const grpc_ssl_config* config, const char* target_name, const grpc_ssl_config* config, const char* target_name,
const char* overridden_target_name, grpc_channel_security_connector** sc) { const char* overridden_target_name,
size_t num_alpn_protocols = 0; tsi_ssl_session_cache* ssl_session_cache,
const char** alpn_protocol_strings = grpc_channel_security_connector** sc) {
fill_alpn_protocol_strings(&num_alpn_protocols);
tsi_result result = TSI_OK; tsi_result result = TSI_OK;
grpc_ssl_channel_security_connector* c; grpc_ssl_channel_security_connector* c;
const char* pem_root_certs;
char* port; char* port;
bool has_key_cert_pair; bool has_key_cert_pair;
tsi_ssl_client_handshaker_options options;
memset(&options, 0, sizeof(options));
options.alpn_protocols =
fill_alpn_protocol_strings(&options.num_alpn_protocols);
if (config == nullptr || target_name == nullptr) { if (config == nullptr || target_name == nullptr) {
gpr_log(GPR_ERROR, "An ssl channel needs a config and a target name."); gpr_log(GPR_ERROR, "An ssl channel needs a config and a target name.");
goto error; goto error;
} }
if (config->pem_root_certs == nullptr) { if (config->pem_root_certs == nullptr) {
pem_root_certs = grpc_get_default_ssl_roots(); options.pem_root_certs = grpc_get_default_ssl_roots();
if (pem_root_certs == nullptr) { if (options.pem_root_certs == nullptr) {
gpr_log(GPR_ERROR, "Could not get default pem root certs."); gpr_log(GPR_ERROR, "Could not get default pem root certs.");
goto error; goto error;
} }
} else { } else {
pem_root_certs = config->pem_root_certs; options.pem_root_certs = config->pem_root_certs;
} }
c = static_cast<grpc_ssl_channel_security_connector*>( c = static_cast<grpc_ssl_channel_security_connector*>(
@ -1028,10 +1073,13 @@ grpc_security_status grpc_ssl_channel_security_connector_create(
has_key_cert_pair = config->pem_key_cert_pair != nullptr && has_key_cert_pair = config->pem_key_cert_pair != nullptr &&
config->pem_key_cert_pair->private_key != nullptr && config->pem_key_cert_pair->private_key != nullptr &&
config->pem_key_cert_pair->cert_chain != nullptr; config->pem_key_cert_pair->cert_chain != nullptr;
result = tsi_create_ssl_client_handshaker_factory( if (has_key_cert_pair) {
has_key_cert_pair ? config->pem_key_cert_pair : nullptr, pem_root_certs, options.pem_key_cert_pair = config->pem_key_cert_pair;
ssl_cipher_suites(), alpn_protocol_strings, }
static_cast<uint16_t>(num_alpn_protocols), &c->client_handshaker_factory); options.cipher_suites = ssl_cipher_suites();
options.session_cache = ssl_session_cache;
result = tsi_create_ssl_client_handshaker_factory_with_options(
&options, &c->client_handshaker_factory);
if (result != TSI_OK) { if (result != TSI_OK) {
gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.", gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.",
tsi_result_to_string(result)); tsi_result_to_string(result));
@ -1040,11 +1088,11 @@ grpc_security_status grpc_ssl_channel_security_connector_create(
goto error; goto error;
} }
*sc = &c->base; *sc = &c->base;
gpr_free((void*)alpn_protocol_strings); gpr_free((void*)options.alpn_protocols);
return GRPC_SECURITY_OK; return GRPC_SECURITY_OK;
error: error:
gpr_free((void*)alpn_protocol_strings); gpr_free((void*)options.alpn_protocols);
return GRPC_SECURITY_ERROR; return GRPC_SECURITY_ERROR;
} }

@ -212,7 +212,9 @@ grpc_security_status grpc_ssl_channel_security_connector_create(
grpc_channel_credentials* channel_creds, grpc_channel_credentials* channel_creds,
grpc_call_credentials* request_metadata_creds, grpc_call_credentials* request_metadata_creds,
const grpc_ssl_config* config, const char* target_name, const grpc_ssl_config* config, const char* target_name,
const char* overridden_target_name, grpc_channel_security_connector** sc); const char* overridden_target_name,
tsi_ssl_session_cache* ssl_session_cache,
grpc_channel_security_connector** sc);
/* Gets the default ssl roots. Returns NULL if not found. */ /* Gets the default ssl roots. Returns NULL if not found. */
const char* grpc_get_default_ssl_roots(void); const char* grpc_get_default_ssl_roots(void);

@ -0,0 +1,73 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_H
#define GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_H
#include <grpc/support/port_platform.h>
#include <grpc/slice.h>
extern "C" {
#include <openssl/ssl.h>
}
#include "src/core/lib/gprpp/ref_counted.h"
// The main purpose of code here is to provide means to cache SSL sessions
// in a way that they can be shared between connections.
//
// SSL_SESSION stands for single instance of session and is not generally safe
// to share between SSL contexts with different lifetimes. It happens because
// not all SSL implementations guarantee immutability of SSL_SESSION object.
// See SSL_SESSION documentation in BoringSSL and OpenSSL for more details.
namespace tsi {
struct SslSessionDeleter {
void operator()(SSL_SESSION* session) { SSL_SESSION_free(session); }
};
typedef std::unique_ptr<SSL_SESSION, SslSessionDeleter> SslSessionPtr;
/// SslCachedSession is an immutable thread-safe storage for single session
/// representation. It provides means to share SSL session data (e.g. TLS
/// ticket) between encrypted connections regardless of SSL context lifetime.
class SslCachedSession {
public:
// Not copyable nor movable.
SslCachedSession(const SslCachedSession&) = delete;
SslCachedSession& operator=(const SslCachedSession&) = delete;
/// Create single cached instance of \a session.
static grpc_core::UniquePtr<SslCachedSession> Create(SslSessionPtr session);
virtual ~SslCachedSession() = default;
/// Returns a copy of previously cached session.
virtual SslSessionPtr CopySession() const GRPC_ABSTRACT;
GRPC_ABSTRACT_BASE_CLASS
protected:
SslCachedSession() = default;
};
} // namespace tsi
#endif /* GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_H */

@ -0,0 +1,58 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <grpc/support/port_platform.h>
#include "src/core/tsi/ssl/session_cache/ssl_session.h"
#ifdef OPENSSL_IS_BORINGSSL
// BoringSSL allows SSL_SESSION to outlive SSL and SSL_CTX objects which are
// re-created by gRPC on every certificate rotation or subchannel creation.
// BoringSSL guarantees that SSL_SESSION is immutable so it's safe to share
// the same original session object between different threads and connections.
namespace tsi {
namespace {
class BoringSslCachedSession : public SslCachedSession {
public:
BoringSslCachedSession(SslSessionPtr session)
: session_(std::move(session)) {}
SslSessionPtr CopySession() const override {
// SslSessionPtr will dereference on destruction.
SSL_SESSION_up_ref(session_.get());
return SslSessionPtr(session_.get());
}
private:
SslSessionPtr session_;
};
} // namespace
grpc_core::UniquePtr<SslCachedSession> SslCachedSession::Create(
SslSessionPtr session) {
return grpc_core::UniquePtr<SslCachedSession>(
grpc_core::New<BoringSslCachedSession>(std::move(session)));
}
} // namespace tsi
#endif /* OPENSSL_IS_BORINGSSL */

@ -0,0 +1,211 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <grpc/support/port_platform.h>
#include "src/core/tsi/ssl/session_cache/ssl_session_cache.h"
#include "src/core/tsi/ssl/session_cache/ssl_session.h"
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
namespace tsi {
static void cache_key_avl_destroy(void* key, void* unused) {}
static void* cache_key_avl_copy(void* key, void* unused) { return key; }
static long cache_key_avl_compare(void* key1, void* key2, void* unused) {
return grpc_slice_cmp(*static_cast<grpc_slice*>(key1),
*static_cast<grpc_slice*>(key2));
}
static void cache_value_avl_destroy(void* value, void* unused) {}
static void* cache_value_avl_copy(void* value, void* unused) { return value; }
// AVL only stores pointers, ownership belonges to the linked list.
static const grpc_avl_vtable cache_avl_vtable = {
cache_key_avl_destroy, cache_key_avl_copy, cache_key_avl_compare,
cache_value_avl_destroy, cache_value_avl_copy,
};
/// Node for single cached session.
class SslSessionLRUCache::Node {
public:
Node(const grpc_slice& key, SslSessionPtr session) : key_(key) {
SetSession(std::move(session));
}
~Node() { grpc_slice_unref(key_); }
// Not copyable nor movable.
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
void* AvlKey() { return &key_; }
/// Returns a copy of the node's cache session.
SslSessionPtr CopySession() const { return session_->CopySession(); }
/// Set the \a session (which is moved) for the node.
void SetSession(SslSessionPtr session) {
session_ = SslCachedSession::Create(std::move(session));
}
private:
friend class SslSessionLRUCache;
grpc_slice key_;
grpc_core::UniquePtr<SslCachedSession> session_;
Node* next_ = nullptr;
Node* prev_ = nullptr;
};
SslSessionLRUCache::SslSessionLRUCache(size_t capacity) : capacity_(capacity) {
GPR_ASSERT(capacity > 0);
gpr_mu_init(&lock_);
entry_by_key_ = grpc_avl_create(&cache_avl_vtable);
}
SslSessionLRUCache::~SslSessionLRUCache() {
Node* node = use_order_list_head_;
while (node) {
Node* next = node->next_;
grpc_core::Delete(node);
node = next;
}
grpc_avl_unref(entry_by_key_, nullptr);
gpr_mu_destroy(&lock_);
}
size_t SslSessionLRUCache::Size() {
grpc_core::mu_guard guard(&lock_);
return use_order_list_size_;
}
SslSessionLRUCache::Node* SslSessionLRUCache::FindLocked(
const grpc_slice& key) {
void* value =
grpc_avl_get(entry_by_key_, const_cast<grpc_slice*>(&key), nullptr);
if (value == nullptr) {
return nullptr;
}
Node* node = static_cast<Node*>(value);
// Move to the beginning.
Remove(node);
PushFront(node);
AssertInvariants();
return node;
}
void SslSessionLRUCache::Put(const char* key, SslSessionPtr session) {
grpc_core::mu_guard guard(&lock_);
Node* node = FindLocked(grpc_slice_from_static_string(key));
if (node != nullptr) {
node->SetSession(std::move(session));
return;
}
grpc_slice key_slice = grpc_slice_from_copied_string(key);
node = grpc_core::New<Node>(key_slice, std::move(session));
PushFront(node);
entry_by_key_ = grpc_avl_add(entry_by_key_, node->AvlKey(), node, nullptr);
AssertInvariants();
if (use_order_list_size_ > capacity_) {
GPR_ASSERT(use_order_list_tail_);
node = use_order_list_tail_;
Remove(node);
// Order matters, key is destroyed after deleting node.
entry_by_key_ = grpc_avl_remove(entry_by_key_, node->AvlKey(), nullptr);
grpc_core::Delete(node);
AssertInvariants();
}
}
SslSessionPtr SslSessionLRUCache::Get(const char* key) {
grpc_core::mu_guard guard(&lock_);
// Key is only used for lookups.
grpc_slice key_slice = grpc_slice_from_static_string(key);
Node* node = FindLocked(key_slice);
if (node == nullptr) {
return nullptr;
}
return node->CopySession();
}
void SslSessionLRUCache::Remove(SslSessionLRUCache::Node* node) {
if (node->prev_ == nullptr) {
use_order_list_head_ = node->next_;
} else {
node->prev_->next_ = node->next_;
}
if (node->next_ == nullptr) {
use_order_list_tail_ = node->prev_;
} else {
node->next_->prev_ = node->prev_;
}
GPR_ASSERT(use_order_list_size_ >= 1);
use_order_list_size_--;
}
void SslSessionLRUCache::PushFront(SslSessionLRUCache::Node* node) {
if (use_order_list_head_ == nullptr) {
use_order_list_head_ = node;
use_order_list_tail_ = node;
node->next_ = nullptr;
node->prev_ = nullptr;
} else {
node->next_ = use_order_list_head_;
node->next_->prev_ = node;
use_order_list_head_ = node;
node->prev_ = nullptr;
}
use_order_list_size_++;
}
#ifndef NDEBUG
static size_t calculate_tree_size(grpc_avl_node* node) {
if (node == nullptr) {
return 0;
}
return 1 + calculate_tree_size(node->left) + calculate_tree_size(node->right);
}
void SslSessionLRUCache::AssertInvariants() {
size_t size = 0;
Node* prev = nullptr;
Node* current = use_order_list_head_;
while (current != nullptr) {
size++;
GPR_ASSERT(current->prev_ == prev);
void* node = grpc_avl_get(entry_by_key_, current->AvlKey(), nullptr);
GPR_ASSERT(node == current);
prev = current;
current = current->next_;
}
GPR_ASSERT(prev == use_order_list_tail_);
GPR_ASSERT(size == use_order_list_size_);
GPR_ASSERT(calculate_tree_size(entry_by_key_.root) == use_order_list_size_);
}
#else
void SslSessionLRUCache::AssertInvariants() {}
#endif
} // namespace tsi

@ -0,0 +1,93 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_CACHE_H
#define GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_CACHE_H
#include <grpc/support/port_platform.h>
#include <grpc/slice.h>
#include <grpc/support/sync.h>
extern "C" {
#include <openssl/ssl.h>
}
#include "src/core/lib/avl/avl.h"
#include "src/core/lib/gprpp/memory.h"
#include "src/core/lib/gprpp/ref_counted.h"
#include "src/core/tsi/ssl/session_cache/ssl_session.h"
/// Cache for SSL sessions for sessions resumption.
///
/// Older sessions may be evicted from the cache using LRU policy if capacity
/// limit is hit. All sessions are associated with some key, usually server
/// name. Note that servers are required to share session ticket encryption keys
/// in order for cache to be effective.
///
/// This class is thread safe.
namespace tsi {
class SslSessionLRUCache : public grpc_core::RefCounted<SslSessionLRUCache> {
public:
/// Create new LRU cache with the given capacity.
static grpc_core::RefCountedPtr<SslSessionLRUCache> Create(size_t capacity) {
return grpc_core::MakeRefCounted<SslSessionLRUCache>(capacity);
}
// Not copyable nor movable.
SslSessionLRUCache(const SslSessionLRUCache&) = delete;
SslSessionLRUCache& operator=(const SslSessionLRUCache&) = delete;
/// Returns current number of sessions in the cache.
size_t Size();
/// Add \a session in the cache using \a key. This operation may discard older
/// sessions.
void Put(const char* key, SslSessionPtr session);
/// Returns the session from the cache associated with \a key or null if not
/// found.
SslSessionPtr Get(const char* key);
private:
// So New() can call our private ctor.
template <typename T, typename... Args>
friend T* grpc_core::New(Args&&... args);
class Node;
explicit SslSessionLRUCache(size_t capacity);
~SslSessionLRUCache();
Node* FindLocked(const grpc_slice& key);
void Remove(Node* node);
void PushFront(Node* node);
void AssertInvariants();
gpr_mu lock_;
size_t capacity_;
Node* use_order_list_head_ = nullptr;
Node* use_order_list_tail_ = nullptr;
size_t use_order_list_size_ = 0;
grpc_avl entry_by_key_;
};
} // namespace tsi
#endif /* GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_CACHE_H */

@ -0,0 +1,76 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <grpc/support/port_platform.h>
#include "src/core/tsi/ssl/session_cache/ssl_session.h"
#include <grpc/support/log.h>
#ifndef OPENSSL_IS_BORINGSSL
// OpenSSL invalidates SSL_SESSION on SSL destruction making it pointless
// to cache sessions. The workaround is to serialize (relatively expensive)
// session into binary blob and re-create it from blob on every handshake.
// Note that it's safe to keep serialized session outside of SSL lifetime
// as openssl performs all necessary validation while attempting to use a
// session and creates a new one if something is wrong (e.g. server changed
// set of allowed codecs).
namespace tsi {
namespace {
class OpenSslCachedSession : public SslCachedSession {
public:
OpenSslCachedSession(SslSessionPtr session) {
int size = i2d_SSL_SESSION(session.get(), nullptr);
GPR_ASSERT(size > 0);
grpc_slice slice = grpc_slice_malloc(size_t(size));
unsigned char* start = GRPC_SLICE_START_PTR(slice);
int second_size = i2d_SSL_SESSION(session.get(), &start);
GPR_ASSERT(size == second_size);
serialized_session_ = slice;
}
virtual ~OpenSslCachedSession() { grpc_slice_unref(serialized_session_); }
SslSessionPtr CopySession() const override {
const unsigned char* data = GRPC_SLICE_START_PTR(serialized_session_);
size_t length = GRPC_SLICE_LENGTH(serialized_session_);
SSL_SESSION* session = d2i_SSL_SESSION(nullptr, &data, length);
if (session == nullptr) {
return SslSessionPtr();
}
return SslSessionPtr(session);
}
private:
grpc_slice serialized_session_;
};
} // namespace
grpc_core::UniquePtr<SslCachedSession> SslCachedSession::Create(
SslSessionPtr session) {
return grpc_core::UniquePtr<SslCachedSession>(
grpc_core::New<OpenSslCachedSession>(std::move(session)));
}
} // namespace tsi
#endif /* OPENSSL_IS_BORINGSSL */

@ -35,6 +35,7 @@
#include <grpc/support/alloc.h> #include <grpc/support/alloc.h>
#include <grpc/support/log.h> #include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <grpc/support/sync.h> #include <grpc/support/sync.h>
#include <grpc/support/thd_id.h> #include <grpc/support/thd_id.h>
@ -47,6 +48,8 @@ extern "C" {
#include <openssl/x509v3.h> #include <openssl/x509v3.h>
} }
#include "src/core/lib/gpr/useful.h"
#include "src/core/tsi/ssl/session_cache/ssl_session_cache.h"
#include "src/core/tsi/ssl_types.h" #include "src/core/tsi/ssl_types.h"
#include "src/core/tsi/transport_security.h" #include "src/core/tsi/transport_security.h"
@ -78,6 +81,7 @@ struct tsi_ssl_client_handshaker_factory {
SSL_CTX* ssl_context; SSL_CTX* ssl_context;
unsigned char* alpn_protocol_list; unsigned char* alpn_protocol_list;
size_t alpn_protocol_list_length; size_t alpn_protocol_list_length;
grpc_core::RefCountedPtr<tsi::SslSessionLRUCache> session_cache;
}; };
struct tsi_ssl_server_handshaker_factory { struct tsi_ssl_server_handshaker_factory {
@ -111,17 +115,19 @@ typedef struct {
/* --- Library Initialization. ---*/ /* --- Library Initialization. ---*/
static gpr_once init_openssl_once = GPR_ONCE_INIT; static gpr_once g_init_openssl_once = GPR_ONCE_INIT;
static gpr_mu* openssl_mutexes = nullptr; static gpr_mu* g_openssl_mutexes = nullptr;
static int g_ssl_ctx_ex_factory_index = -1;
static void openssl_locking_cb(int mode, int type, const char* file, static void openssl_locking_cb(int mode, int type, const char* file,
int line) GRPC_UNUSED; int line) GRPC_UNUSED;
static unsigned long openssl_thread_id_cb(void) GRPC_UNUSED; static unsigned long openssl_thread_id_cb(void) GRPC_UNUSED;
static const unsigned char kSslSessionIdContext[] = {'g', 'r', 'p', 'c'};
static void openssl_locking_cb(int mode, int type, const char* file, int line) { static void openssl_locking_cb(int mode, int type, const char* file, int line) {
if (mode & CRYPTO_LOCK) { if (mode & CRYPTO_LOCK) {
gpr_mu_lock(&openssl_mutexes[type]); gpr_mu_lock(&g_openssl_mutexes[type]);
} else { } else {
gpr_mu_unlock(&openssl_mutexes[type]); gpr_mu_unlock(&g_openssl_mutexes[type]);
} }
} }
@ -137,13 +143,16 @@ static void init_openssl(void) {
OpenSSL_add_all_algorithms(); OpenSSL_add_all_algorithms();
num_locks = CRYPTO_num_locks(); num_locks = CRYPTO_num_locks();
GPR_ASSERT(num_locks > 0); GPR_ASSERT(num_locks > 0);
openssl_mutexes = static_cast<gpr_mu*>( g_openssl_mutexes = static_cast<gpr_mu*>(
gpr_malloc(static_cast<size_t>(num_locks) * sizeof(gpr_mu))); gpr_malloc(static_cast<size_t>(num_locks) * sizeof(gpr_mu)));
for (i = 0; i < CRYPTO_num_locks(); i++) { for (i = 0; i < CRYPTO_num_locks(); i++) {
gpr_mu_init(&openssl_mutexes[i]); gpr_mu_init(&g_openssl_mutexes[i]);
} }
CRYPTO_set_locking_callback(openssl_locking_cb); CRYPTO_set_locking_callback(openssl_locking_cb);
CRYPTO_set_id_callback(openssl_thread_id_cb); CRYPTO_set_id_callback(openssl_thread_id_cb);
g_ssl_ctx_ex_factory_index =
SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
GPR_ASSERT(g_ssl_ctx_ex_factory_index != -1);
} }
/* --- Ssl utils. ---*/ /* --- Ssl utils. ---*/
@ -721,6 +730,23 @@ static int NullVerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) {
return 1; return 1;
} }
/* --- tsi_ssl_session_cache methods implementation. ---*/
tsi_ssl_session_cache* tsi_ssl_session_cache_create_lru(size_t capacity) {
/* Pointer will be dereferenced by unref call. */
return reinterpret_cast<tsi_ssl_session_cache*>(
tsi::SslSessionLRUCache::Create(capacity).release());
}
void tsi_ssl_session_cache_ref(tsi_ssl_session_cache* cache) {
/* Pointer will be dereferenced by unref call. */
reinterpret_cast<tsi::SslSessionLRUCache*>(cache)->Ref().release();
}
void tsi_ssl_session_cache_unref(tsi_ssl_session_cache* cache) {
reinterpret_cast<tsi::SslSessionLRUCache*>(cache)->Unref();
}
/* --- tsi_frame_protector methods implementation. ---*/ /* --- tsi_frame_protector methods implementation. ---*/
static tsi_result ssl_protector_protect(tsi_frame_protector* self, static tsi_result ssl_protector_protect(tsi_frame_protector* self,
@ -1015,25 +1041,34 @@ static tsi_result ssl_handshaker_extract_peer(tsi_handshaker* self,
SSL_get0_next_proto_negotiated(impl->ssl, &alpn_selected, SSL_get0_next_proto_negotiated(impl->ssl, &alpn_selected,
&alpn_selected_len); &alpn_selected_len);
} }
// 1 is for session reused property.
size_t new_property_count = peer->property_count + 1;
if (alpn_selected != nullptr) new_property_count++;
tsi_peer_property* new_properties = static_cast<tsi_peer_property*>(
gpr_zalloc(sizeof(*new_properties) * new_property_count));
for (size_t i = 0; i < peer->property_count; i++) {
new_properties[i] = peer->properties[i];
}
if (peer->properties != nullptr) gpr_free(peer->properties);
peer->properties = new_properties;
if (alpn_selected != nullptr) { if (alpn_selected != nullptr) {
size_t i;
tsi_peer_property* new_properties = static_cast<tsi_peer_property*>(
gpr_zalloc(sizeof(*new_properties) * (peer->property_count + 1)));
for (i = 0; i < peer->property_count; i++) {
new_properties[i] = peer->properties[i];
}
result = tsi_construct_string_peer_property( result = tsi_construct_string_peer_property(
TSI_SSL_ALPN_SELECTED_PROTOCOL, TSI_SSL_ALPN_SELECTED_PROTOCOL,
reinterpret_cast<const char*>(alpn_selected), alpn_selected_len, reinterpret_cast<const char*>(alpn_selected), alpn_selected_len,
&new_properties[peer->property_count]); &peer->properties[peer->property_count]);
if (result != TSI_OK) { if (result != TSI_OK) return result;
gpr_free(new_properties);
return result;
}
if (peer->properties != nullptr) gpr_free(peer->properties);
peer->property_count++; peer->property_count++;
peer->properties = new_properties;
} }
const char* session_reused = SSL_session_reused(impl->ssl) ? "true" : "false";
result = tsi_construct_string_peer_property(
TSI_SSL_SESSION_REUSED_PEER_PROPERTY, session_reused,
strlen(session_reused) + 1, &peer->properties[peer->property_count]);
if (result != TSI_OK) return result;
peer->property_count++;
return result; return result;
} }
@ -1103,6 +1138,19 @@ static const tsi_handshaker_vtable handshaker_vtable = {
/* --- tsi_ssl_handshaker_factory common methods. --- */ /* --- tsi_ssl_handshaker_factory common methods. --- */
static void tsi_ssl_handshaker_resume_session(
SSL* ssl, tsi::SslSessionLRUCache* session_cache) {
const char* server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (server_name == nullptr) {
return;
}
tsi::SslSessionPtr session = session_cache->Get(server_name);
if (session != nullptr) {
// SSL_set_session internally increments reference counter.
SSL_set_session(ssl, session.get());
}
}
static tsi_result create_tsi_ssl_handshaker(SSL_CTX* ctx, int is_client, static tsi_result create_tsi_ssl_handshaker(SSL_CTX* ctx, int is_client,
const char* server_name_indication, const char* server_name_indication,
tsi_ssl_handshaker_factory* factory, tsi_ssl_handshaker_factory* factory,
@ -1139,6 +1187,12 @@ static tsi_result create_tsi_ssl_handshaker(SSL_CTX* ctx, int is_client,
return TSI_INTERNAL_ERROR; return TSI_INTERNAL_ERROR;
} }
} }
tsi_ssl_client_handshaker_factory* client_factory =
reinterpret_cast<tsi_ssl_client_handshaker_factory*>(factory);
if (client_factory->session_cache != nullptr) {
tsi_ssl_handshaker_resume_session(ssl,
client_factory->session_cache.get());
}
ssl_result = SSL_do_handshake(ssl); ssl_result = SSL_do_handshake(ssl);
ssl_result = SSL_get_error(ssl, ssl_result); ssl_result = SSL_get_error(ssl, ssl_result);
if (ssl_result != SSL_ERROR_WANT_READ) { if (ssl_result != SSL_ERROR_WANT_READ) {
@ -1214,6 +1268,7 @@ static void tsi_ssl_client_handshaker_factory_destroy(
reinterpret_cast<tsi_ssl_client_handshaker_factory*>(factory); reinterpret_cast<tsi_ssl_client_handshaker_factory*>(factory);
if (self->ssl_context != nullptr) SSL_CTX_free(self->ssl_context); if (self->ssl_context != nullptr) SSL_CTX_free(self->ssl_context);
if (self->alpn_protocol_list != nullptr) gpr_free(self->alpn_protocol_list); if (self->alpn_protocol_list != nullptr) gpr_free(self->alpn_protocol_list);
self->session_cache.reset();
gpr_free(self); gpr_free(self);
} }
@ -1357,6 +1412,30 @@ static int server_handshaker_factory_npn_advertised_callback(
return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_OK;
} }
/// This callback is called when new \a session is established and ready to
/// be cached. This session can be reused for new connections to similar
/// servers at later point of time.
/// It's intended to be used with SSL_CTX_sess_set_new_cb function.
///
/// It returns 1 if callback takes ownership over \a session and 0 otherwise.
static int server_handshaker_factory_new_session_callback(
SSL* ssl, SSL_SESSION* session) {
SSL_CTX* ssl_context = SSL_get_SSL_CTX(ssl);
if (ssl_context == nullptr) {
return 0;
}
void* arg = SSL_CTX_get_ex_data(ssl_context, g_ssl_ctx_ex_factory_index);
tsi_ssl_client_handshaker_factory* factory =
static_cast<tsi_ssl_client_handshaker_factory*>(arg);
const char* server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (server_name == nullptr) {
return 0;
}
factory->session_cache->Put(server_name, tsi::SslSessionPtr(session));
// Return 1 to indicate transfered ownership over the given session.
return 1;
}
/* --- tsi_ssl_handshaker_factory constructors. --- */ /* --- tsi_ssl_handshaker_factory constructors. --- */
static tsi_ssl_handshaker_factory_vtable client_handshaker_factory_vtable = { static tsi_ssl_handshaker_factory_vtable client_handshaker_factory_vtable = {
@ -1367,15 +1446,29 @@ tsi_result tsi_create_ssl_client_handshaker_factory(
const char* pem_root_certs, const char* cipher_suites, const char* pem_root_certs, const char* cipher_suites,
const char** alpn_protocols, uint16_t num_alpn_protocols, const char** alpn_protocols, uint16_t num_alpn_protocols,
tsi_ssl_client_handshaker_factory** factory) { tsi_ssl_client_handshaker_factory** factory) {
tsi_ssl_client_handshaker_options options;
memset(&options, 0, sizeof(options));
options.pem_key_cert_pair = pem_key_cert_pair;
options.pem_root_certs = pem_root_certs;
options.cipher_suites = cipher_suites;
options.alpn_protocols = alpn_protocols;
options.num_alpn_protocols = num_alpn_protocols;
return tsi_create_ssl_client_handshaker_factory_with_options(&options,
factory);
}
tsi_result tsi_create_ssl_client_handshaker_factory_with_options(
const tsi_ssl_client_handshaker_options* options,
tsi_ssl_client_handshaker_factory** factory) {
SSL_CTX* ssl_context = nullptr; SSL_CTX* ssl_context = nullptr;
tsi_ssl_client_handshaker_factory* impl = nullptr; tsi_ssl_client_handshaker_factory* impl = nullptr;
tsi_result result = TSI_OK; tsi_result result = TSI_OK;
gpr_once_init(&init_openssl_once, init_openssl); gpr_once_init(&g_init_openssl_once, init_openssl);
if (factory == nullptr) return TSI_INVALID_ARGUMENT; if (factory == nullptr) return TSI_INVALID_ARGUMENT;
*factory = nullptr; *factory = nullptr;
if (pem_root_certs == nullptr) return TSI_INVALID_ARGUMENT; if (options->pem_root_certs == nullptr) return TSI_INVALID_ARGUMENT;
ssl_context = SSL_CTX_new(TLSv1_2_method()); ssl_context = SSL_CTX_new(TLSv1_2_method());
if (ssl_context == nullptr) { if (ssl_context == nullptr) {
@ -1390,21 +1483,33 @@ tsi_result tsi_create_ssl_client_handshaker_factory(
impl->ssl_context = ssl_context; impl->ssl_context = ssl_context;
if (options->session_cache != nullptr) {
// Unref is called manually on factory destruction.
impl->session_cache =
reinterpret_cast<tsi::SslSessionLRUCache*>(options->session_cache)
->Ref();
SSL_CTX_set_ex_data(ssl_context, g_ssl_ctx_ex_factory_index, impl);
SSL_CTX_sess_set_new_cb(ssl_context,
server_handshaker_factory_new_session_callback);
SSL_CTX_set_session_cache_mode(ssl_context, SSL_SESS_CACHE_CLIENT);
}
do { do {
result = result = populate_ssl_context(ssl_context, options->pem_key_cert_pair,
populate_ssl_context(ssl_context, pem_key_cert_pair, cipher_suites); options->cipher_suites);
if (result != TSI_OK) break; if (result != TSI_OK) break;
result = ssl_ctx_load_verification_certs(ssl_context, pem_root_certs, result = ssl_ctx_load_verification_certs(
strlen(pem_root_certs), nullptr); ssl_context, options->pem_root_certs, strlen(options->pem_root_certs),
nullptr);
if (result != TSI_OK) { if (result != TSI_OK) {
gpr_log(GPR_ERROR, "Cannot load server root certificates."); gpr_log(GPR_ERROR, "Cannot load server root certificates.");
break; break;
} }
if (num_alpn_protocols != 0) { if (options->num_alpn_protocols != 0) {
result = build_alpn_protocol_name_list(alpn_protocols, num_alpn_protocols, result = build_alpn_protocol_name_list(
&impl->alpn_protocol_list, options->alpn_protocols, options->num_alpn_protocols,
&impl->alpn_protocol_list_length); &impl->alpn_protocol_list, &impl->alpn_protocol_list_length);
if (result != TSI_OK) { if (result != TSI_OK) {
gpr_log(GPR_ERROR, "Building alpn list failed with error %s.", gpr_log(GPR_ERROR, "Building alpn list failed with error %s.",
tsi_result_to_string(result)); tsi_result_to_string(result));
@ -1457,15 +1562,32 @@ tsi_result tsi_create_ssl_server_handshaker_factory_ex(
tsi_client_certificate_request_type client_certificate_request, tsi_client_certificate_request_type client_certificate_request,
const char* cipher_suites, const char** alpn_protocols, const char* cipher_suites, const char** alpn_protocols,
uint16_t num_alpn_protocols, tsi_ssl_server_handshaker_factory** factory) { uint16_t num_alpn_protocols, tsi_ssl_server_handshaker_factory** factory) {
tsi_ssl_server_handshaker_options options;
memset(&options, 0, sizeof(options));
options.pem_key_cert_pairs = pem_key_cert_pairs;
options.num_key_cert_pairs = num_key_cert_pairs;
options.pem_client_root_certs = pem_client_root_certs;
options.client_certificate_request = client_certificate_request;
options.cipher_suites = cipher_suites;
options.alpn_protocols = alpn_protocols;
options.num_alpn_protocols = num_alpn_protocols;
return tsi_create_ssl_server_handshaker_factory_with_options(&options,
factory);
}
tsi_result tsi_create_ssl_server_handshaker_factory_with_options(
const tsi_ssl_server_handshaker_options* options,
tsi_ssl_server_handshaker_factory** factory) {
tsi_ssl_server_handshaker_factory* impl = nullptr; tsi_ssl_server_handshaker_factory* impl = nullptr;
tsi_result result = TSI_OK; tsi_result result = TSI_OK;
size_t i = 0; size_t i = 0;
gpr_once_init(&init_openssl_once, init_openssl); gpr_once_init(&g_init_openssl_once, init_openssl);
if (factory == nullptr) return TSI_INVALID_ARGUMENT; if (factory == nullptr) return TSI_INVALID_ARGUMENT;
*factory = nullptr; *factory = nullptr;
if (num_key_cert_pairs == 0 || pem_key_cert_pairs == nullptr) { if (options->num_key_cert_pairs == 0 ||
options->pem_key_cert_pairs == nullptr) {
return TSI_INVALID_ARGUMENT; return TSI_INVALID_ARGUMENT;
} }
@ -1474,28 +1596,28 @@ tsi_result tsi_create_ssl_server_handshaker_factory_ex(
tsi_ssl_handshaker_factory_init(&impl->base); tsi_ssl_handshaker_factory_init(&impl->base);
impl->base.vtable = &server_handshaker_factory_vtable; impl->base.vtable = &server_handshaker_factory_vtable;
impl->ssl_contexts = impl->ssl_contexts = static_cast<SSL_CTX**>(
static_cast<SSL_CTX**>(gpr_zalloc(num_key_cert_pairs * sizeof(SSL_CTX*))); gpr_zalloc(options->num_key_cert_pairs * sizeof(SSL_CTX*)));
impl->ssl_context_x509_subject_names = impl->ssl_context_x509_subject_names = static_cast<tsi_peer*>(
static_cast<tsi_peer*>(gpr_zalloc(num_key_cert_pairs * sizeof(tsi_peer))); gpr_zalloc(options->num_key_cert_pairs * sizeof(tsi_peer)));
if (impl->ssl_contexts == nullptr || if (impl->ssl_contexts == nullptr ||
impl->ssl_context_x509_subject_names == nullptr) { impl->ssl_context_x509_subject_names == nullptr) {
tsi_ssl_handshaker_factory_unref(&impl->base); tsi_ssl_handshaker_factory_unref(&impl->base);
return TSI_OUT_OF_RESOURCES; return TSI_OUT_OF_RESOURCES;
} }
impl->ssl_context_count = num_key_cert_pairs; impl->ssl_context_count = options->num_key_cert_pairs;
if (num_alpn_protocols > 0) { if (options->num_alpn_protocols > 0) {
result = build_alpn_protocol_name_list(alpn_protocols, num_alpn_protocols, result = build_alpn_protocol_name_list(
&impl->alpn_protocol_list, options->alpn_protocols, options->num_alpn_protocols,
&impl->alpn_protocol_list_length); &impl->alpn_protocol_list, &impl->alpn_protocol_list_length);
if (result != TSI_OK) { if (result != TSI_OK) {
tsi_ssl_handshaker_factory_unref(&impl->base); tsi_ssl_handshaker_factory_unref(&impl->base);
return result; return result;
} }
} }
for (i = 0; i < num_key_cert_pairs; i++) { for (i = 0; i < options->num_key_cert_pairs; i++) {
do { do {
impl->ssl_contexts[i] = SSL_CTX_new(TLSv1_2_method()); impl->ssl_contexts[i] = SSL_CTX_new(TLSv1_2_method());
if (impl->ssl_contexts[i] == nullptr) { if (impl->ssl_contexts[i] == nullptr) {
@ -1504,20 +1626,44 @@ tsi_result tsi_create_ssl_server_handshaker_factory_ex(
break; break;
} }
result = populate_ssl_context(impl->ssl_contexts[i], result = populate_ssl_context(impl->ssl_contexts[i],
&pem_key_cert_pairs[i], cipher_suites); &options->pem_key_cert_pairs[i],
options->cipher_suites);
if (result != TSI_OK) break; if (result != TSI_OK) break;
if (pem_client_root_certs != nullptr) { // TODO(elessar): Provide ability to disable session ticket keys.
// Allow client cache sessions (it's needed for OpenSSL only).
int set_sid_ctx_result = SSL_CTX_set_session_id_context(
impl->ssl_contexts[i], kSslSessionIdContext,
GPR_ARRAY_SIZE(kSslSessionIdContext));
if (set_sid_ctx_result == 0) {
gpr_log(GPR_ERROR, "Failed to set session id context.");
result = TSI_INTERNAL_ERROR;
break;
}
if (options->session_ticket_key != nullptr) {
if (SSL_CTX_set_tlsext_ticket_keys(
impl->ssl_contexts[i],
const_cast<char*>(options->session_ticket_key),
options->session_ticket_key_size) == 0) {
gpr_log(GPR_ERROR, "Invalid STEK size.");
result = TSI_INVALID_ARGUMENT;
break;
}
}
if (options->pem_client_root_certs != nullptr) {
STACK_OF(X509_NAME)* root_names = nullptr; STACK_OF(X509_NAME)* root_names = nullptr;
result = ssl_ctx_load_verification_certs( result = ssl_ctx_load_verification_certs(
impl->ssl_contexts[i], pem_client_root_certs, impl->ssl_contexts[i], options->pem_client_root_certs,
strlen(pem_client_root_certs), &root_names); strlen(options->pem_client_root_certs), &root_names);
if (result != TSI_OK) { if (result != TSI_OK) {
gpr_log(GPR_ERROR, "Invalid verification certs."); gpr_log(GPR_ERROR, "Invalid verification certs.");
break; break;
} }
SSL_CTX_set_client_CA_list(impl->ssl_contexts[i], root_names); SSL_CTX_set_client_CA_list(impl->ssl_contexts[i], root_names);
switch (client_certificate_request) { switch (options->client_certificate_request) {
case TSI_DONT_REQUEST_CLIENT_CERTIFICATE: case TSI_DONT_REQUEST_CLIENT_CERTIFICATE:
SSL_CTX_set_verify(impl->ssl_contexts[i], SSL_VERIFY_NONE, nullptr); SSL_CTX_set_verify(impl->ssl_contexts[i], SSL_VERIFY_NONE, nullptr);
break; break;
@ -1544,7 +1690,7 @@ tsi_result tsi_create_ssl_server_handshaker_factory_ex(
} }
result = extract_x509_subject_names_from_pem_cert( result = extract_x509_subject_names_from_pem_cert(
pem_key_cert_pairs[i].cert_chain, options->pem_key_cert_pairs[i].cert_chain,
&impl->ssl_context_x509_subject_names[i]); &impl->ssl_context_x509_subject_names[i]);
if (result != TSI_OK) break; if (result != TSI_OK) break;

@ -30,11 +30,27 @@
#define TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY "x509_subject_common_name" #define TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY "x509_subject_common_name"
#define TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY \ #define TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY \
"x509_subject_alternative_name" "x509_subject_alternative_name"
#define TSI_SSL_SESSION_REUSED_PEER_PROPERTY "ssl_session_reused"
#define TSI_X509_PEM_CERT_PROPERTY "x509_pem_cert" #define TSI_X509_PEM_CERT_PROPERTY "x509_pem_cert"
#define TSI_SSL_ALPN_SELECTED_PROTOCOL "ssl_alpn_selected_protocol" #define TSI_SSL_ALPN_SELECTED_PROTOCOL "ssl_alpn_selected_protocol"
/* --- tsi_ssl_session_cache object ---
Cache for SSL sessions for sessions resumption. */
typedef struct tsi_ssl_session_cache tsi_ssl_session_cache;
/* Create LRU cache for SSL sessions with \a capacity. */
tsi_ssl_session_cache* tsi_ssl_session_cache_create_lru(size_t capacity);
/* Increment reference counter of \a cache. */
void tsi_ssl_session_cache_ref(tsi_ssl_session_cache* cache);
/* Decrement reference counter of \a cache. */
void tsi_ssl_session_cache_unref(tsi_ssl_session_cache* cache);
/* --- tsi_ssl_client_handshaker_factory object --- /* --- tsi_ssl_client_handshaker_factory object ---
This object creates a client tsi_handshaker objects implemented in terms of This object creates a client tsi_handshaker objects implemented in terms of
@ -81,6 +97,43 @@ tsi_result tsi_create_ssl_client_handshaker_factory(
const char** alpn_protocols, uint16_t num_alpn_protocols, const char** alpn_protocols, uint16_t num_alpn_protocols,
tsi_ssl_client_handshaker_factory** factory); tsi_ssl_client_handshaker_factory** factory);
typedef struct {
/* pem_key_cert_pair is a pointer to 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. */
const tsi_ssl_pem_key_cert_pair* pem_key_cert_pair;
/* pem_roots_cert is the NULL-terminated string containing the PEM encoding of
the client root certificates. This parameter may be NULL if the server does
not want the client to be authenticated with SSL. */
const char* pem_root_certs;
/* cipher_suites contains an optional list of the ciphers that the client
supports. The format of this string is described in:
https://www.openssl.org/docs/apps/ciphers.html.
This parameter can be set to NULL to use the default set of ciphers.
TODO(jboeuf): Revisit the format of this parameter. */
const char* cipher_suites;
/* alpn_protocols is an array containing the NULL terminated protocol names
that the handshakers created with this factory support. This parameter can
be NULL. */
const char** alpn_protocols;
/* num_alpn_protocols is the number of alpn protocols and associated lengths
specified. If this parameter is 0, the other alpn parameters must be
NULL. */
size_t num_alpn_protocols;
/* ssl_session_cache is a cache for reusable client-side sessions. */
tsi_ssl_session_cache* session_cache;
} tsi_ssl_client_handshaker_options;
/* Creates a client handshaker factory.
- options is the options used to create a factory.
- factory is the address of the factory pointer to be created.
- This method returns TSI_OK on success or TSI_INVALID_PARAMETER in the case
where a parameter is invalid. */
tsi_result tsi_create_ssl_client_handshaker_factory_with_options(
const tsi_ssl_client_handshaker_options* options,
tsi_ssl_client_handshaker_factory** factory);
/* Creates a client handshaker. /* Creates a client handshaker.
- self is the factory from which the handshaker will be created. - self is the factory from which the handshaker will be created.
- server_name_indication indicates the name of the server the client is - server_name_indication indicates the name of the server the client is
@ -147,6 +200,51 @@ tsi_result tsi_create_ssl_server_handshaker_factory_ex(
const char* cipher_suites, const char** alpn_protocols, const char* cipher_suites, const char** alpn_protocols,
uint16_t num_alpn_protocols, tsi_ssl_server_handshaker_factory** factory); uint16_t num_alpn_protocols, tsi_ssl_server_handshaker_factory** factory);
typedef struct {
/* pem_key_cert_pairs is an array private key / certificate chains of the
server. */
const tsi_ssl_pem_key_cert_pair* pem_key_cert_pairs;
/* num_key_cert_pairs is the number of items in the pem_key_cert_pairs
array. */
size_t num_key_cert_pairs;
/* pem_root_certs is the NULL-terminated string containing the PEM encoding
of the server root certificates. */
const char* pem_client_root_certs;
/* client_certificate_request, if set to non-zero will force the client to
authenticate with an SSL cert. Note that this option is ignored if
pem_client_root_certs is NULL or pem_client_roots_certs_size is 0. */
tsi_client_certificate_request_type client_certificate_request;
/* cipher_suites contains an optional list of the ciphers that the server
supports. The format of this string is described in:
https://www.openssl.org/docs/apps/ciphers.html.
This parameter can be set to NULL to use the default set of ciphers.
TODO(jboeuf): Revisit the format of this parameter. */
const char* cipher_suites;
/* alpn_protocols is an array containing the NULL terminated protocol names
that the handshakers created with this factory support. This parameter can
be NULL. */
const char** alpn_protocols;
/* num_alpn_protocols is the number of alpn protocols and associated lengths
specified. If this parameter is 0, the other alpn parameters must be
NULL. */
uint16_t num_alpn_protocols;
/* session_ticket_key is optional key for encrypting session keys. If paramter
is not specified it must be NULL. */
const char* session_ticket_key;
/* session_ticket_key_size is a size of session ticket encryption key. */
size_t session_ticket_key_size;
} tsi_ssl_server_handshaker_options;
/* Creates a server handshaker factory.
- options is the options used to create a factory.
- factory is the address of the factory pointer to be created.
- This method returns TSI_OK on success or TSI_INVALID_PARAMETER in the case
where a parameter is invalid. */
tsi_result tsi_create_ssl_server_handshaker_factory_with_options(
const tsi_ssl_server_handshaker_options* options,
tsi_ssl_server_handshaker_factory** factory);
/* Creates a server handshaker. /* Creates a server handshaker.
- self is the factory from which the handshaker will be created. - self is the factory from which the handshaker will be created.
- handshaker is the address of the handshaker pointer to be created. - handshaker is the address of the handshaker pointer to be created.

@ -319,6 +319,9 @@ CORE_SOURCE_FILES = [
'src/core/ext/filters/deadline/deadline_filter.cc', 'src/core/ext/filters/deadline/deadline_filter.cc',
'src/core/tsi/alts_transport_security.cc', 'src/core/tsi/alts_transport_security.cc',
'src/core/tsi/fake_transport_security.cc', 'src/core/tsi/fake_transport_security.cc',
'src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc',
'src/core/tsi/ssl/session_cache/ssl_session_cache.cc',
'src/core/tsi/ssl/session_cache/ssl_session_openssl.cc',
'src/core/tsi/ssl_transport_security.cc', 'src/core/tsi/ssl_transport_security.cc',
'src/core/tsi/transport_security_grpc.cc', 'src/core/tsi/transport_security_grpc.cc',
'src/core/ext/transport/chttp2/server/chttp2_server.cc', 'src/core/ext/transport/chttp2/server/chttp2_server.cc',

@ -102,7 +102,8 @@ class AuthContextTest(unittest.TestCase):
self.assertIsNone(auth_data[_ID]) self.assertIsNone(auth_data[_ID])
self.assertIsNone(auth_data[_ID_KEY]) self.assertIsNone(auth_data[_ID_KEY])
self.assertDictEqual({ self.assertDictEqual({
'transport_security_type': [b'ssl'] 'transport_security_type': [b'ssl'],
'ssl_session_reused': [b'false'],
}, auth_data[_AUTH_CTX]) }, auth_data[_AUTH_CTX])
def testSecureClientCert(self): def testSecureClientCert(self):

@ -106,6 +106,9 @@ grpc_auth_context_release_type grpc_auth_context_release_import;
grpc_auth_context_add_property_type grpc_auth_context_add_property_import; grpc_auth_context_add_property_type grpc_auth_context_add_property_import;
grpc_auth_context_add_cstring_property_type grpc_auth_context_add_cstring_property_import; grpc_auth_context_add_cstring_property_type grpc_auth_context_add_cstring_property_import;
grpc_auth_context_set_peer_identity_property_name_type grpc_auth_context_set_peer_identity_property_name_import; grpc_auth_context_set_peer_identity_property_name_type grpc_auth_context_set_peer_identity_property_name_import;
grpc_ssl_session_cache_create_lru_type grpc_ssl_session_cache_create_lru_import;
grpc_ssl_session_cache_destroy_type grpc_ssl_session_cache_destroy_import;
grpc_ssl_session_cache_create_channel_arg_type grpc_ssl_session_cache_create_channel_arg_import;
grpc_channel_credentials_release_type grpc_channel_credentials_release_import; grpc_channel_credentials_release_type grpc_channel_credentials_release_import;
grpc_google_default_credentials_create_type grpc_google_default_credentials_create_import; grpc_google_default_credentials_create_type grpc_google_default_credentials_create_import;
grpc_set_ssl_roots_override_callback_type grpc_set_ssl_roots_override_callback_import; grpc_set_ssl_roots_override_callback_type grpc_set_ssl_roots_override_callback_import;
@ -342,6 +345,9 @@ void grpc_rb_load_imports(HMODULE library) {
grpc_auth_context_add_property_import = (grpc_auth_context_add_property_type) GetProcAddress(library, "grpc_auth_context_add_property"); grpc_auth_context_add_property_import = (grpc_auth_context_add_property_type) GetProcAddress(library, "grpc_auth_context_add_property");
grpc_auth_context_add_cstring_property_import = (grpc_auth_context_add_cstring_property_type) GetProcAddress(library, "grpc_auth_context_add_cstring_property"); grpc_auth_context_add_cstring_property_import = (grpc_auth_context_add_cstring_property_type) GetProcAddress(library, "grpc_auth_context_add_cstring_property");
grpc_auth_context_set_peer_identity_property_name_import = (grpc_auth_context_set_peer_identity_property_name_type) GetProcAddress(library, "grpc_auth_context_set_peer_identity_property_name"); grpc_auth_context_set_peer_identity_property_name_import = (grpc_auth_context_set_peer_identity_property_name_type) GetProcAddress(library, "grpc_auth_context_set_peer_identity_property_name");
grpc_ssl_session_cache_create_lru_import = (grpc_ssl_session_cache_create_lru_type) GetProcAddress(library, "grpc_ssl_session_cache_create_lru");
grpc_ssl_session_cache_destroy_import = (grpc_ssl_session_cache_destroy_type) GetProcAddress(library, "grpc_ssl_session_cache_destroy");
grpc_ssl_session_cache_create_channel_arg_import = (grpc_ssl_session_cache_create_channel_arg_type) GetProcAddress(library, "grpc_ssl_session_cache_create_channel_arg");
grpc_channel_credentials_release_import = (grpc_channel_credentials_release_type) GetProcAddress(library, "grpc_channel_credentials_release"); grpc_channel_credentials_release_import = (grpc_channel_credentials_release_type) GetProcAddress(library, "grpc_channel_credentials_release");
grpc_google_default_credentials_create_import = (grpc_google_default_credentials_create_type) GetProcAddress(library, "grpc_google_default_credentials_create"); grpc_google_default_credentials_create_import = (grpc_google_default_credentials_create_type) GetProcAddress(library, "grpc_google_default_credentials_create");
grpc_set_ssl_roots_override_callback_import = (grpc_set_ssl_roots_override_callback_type) GetProcAddress(library, "grpc_set_ssl_roots_override_callback"); grpc_set_ssl_roots_override_callback_import = (grpc_set_ssl_roots_override_callback_type) GetProcAddress(library, "grpc_set_ssl_roots_override_callback");

@ -293,6 +293,15 @@ extern grpc_auth_context_add_cstring_property_type grpc_auth_context_add_cstring
typedef int(*grpc_auth_context_set_peer_identity_property_name_type)(grpc_auth_context* ctx, const char* name); typedef int(*grpc_auth_context_set_peer_identity_property_name_type)(grpc_auth_context* ctx, const char* name);
extern grpc_auth_context_set_peer_identity_property_name_type grpc_auth_context_set_peer_identity_property_name_import; extern grpc_auth_context_set_peer_identity_property_name_type grpc_auth_context_set_peer_identity_property_name_import;
#define grpc_auth_context_set_peer_identity_property_name grpc_auth_context_set_peer_identity_property_name_import #define grpc_auth_context_set_peer_identity_property_name grpc_auth_context_set_peer_identity_property_name_import
typedef grpc_ssl_session_cache*(*grpc_ssl_session_cache_create_lru_type)(size_t capacity);
extern grpc_ssl_session_cache_create_lru_type grpc_ssl_session_cache_create_lru_import;
#define grpc_ssl_session_cache_create_lru grpc_ssl_session_cache_create_lru_import
typedef void(*grpc_ssl_session_cache_destroy_type)(grpc_ssl_session_cache* cache);
extern grpc_ssl_session_cache_destroy_type grpc_ssl_session_cache_destroy_import;
#define grpc_ssl_session_cache_destroy grpc_ssl_session_cache_destroy_import
typedef grpc_arg(*grpc_ssl_session_cache_create_channel_arg_type)(grpc_ssl_session_cache* cache);
extern grpc_ssl_session_cache_create_channel_arg_type grpc_ssl_session_cache_create_channel_arg_import;
#define grpc_ssl_session_cache_create_channel_arg grpc_ssl_session_cache_create_channel_arg_import
typedef void(*grpc_channel_credentials_release_type)(grpc_channel_credentials* creds); typedef void(*grpc_channel_credentials_release_type)(grpc_channel_credentials* creds);
extern grpc_channel_credentials_release_type grpc_channel_credentials_release_import; extern grpc_channel_credentials_release_type grpc_channel_credentials_release_import;
#define grpc_channel_credentials_release grpc_channel_credentials_release_import #define grpc_channel_credentials_release grpc_channel_credentials_release_import

@ -163,3 +163,20 @@ grpc_cc_test(
) )
grpc_end2end_tests() grpc_end2end_tests()
grpc_cc_test(
name = "h2_ssl_session_reuse_test",
srcs = ["h2_ssl_session_reuse_test.cc"],
external_deps = [
"gtest",
],
language = "C++",
deps = [
':end2end_tests',
'//:gpr',
'//:grpc',
'//:tsi',
'//test/core/util:gpr_test_util',
'//test/core/util:grpc_test_util',
],
)

@ -0,0 +1,280 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "test/core/end2end/end2end_tests.h"
#include <stdio.h>
#include <string.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/gpr/env.h"
#include "src/core/lib/gpr/host_port.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gpr/tmpfile.h"
#include "src/core/lib/security/credentials/credentials.h"
#include "test/core/end2end/cq_verifier.h"
#include "test/core/end2end/data/ssl_test_data.h"
#include "test/core/util/port.h"
#include "test/core/util/test_config.h"
#include <gtest/gtest.h>
namespace grpc {
namespace testing {
namespace {
void* tag(intptr_t t) { return (void*)t; }
gpr_timespec five_seconds_time() { return grpc_timeout_seconds_to_deadline(5); }
grpc_server* server_create(grpc_completion_queue* cq, char* server_addr) {
grpc_ssl_pem_key_cert_pair pem_cert_key_pair = {test_server1_key,
test_server1_cert};
grpc_server_credentials* server_creds = grpc_ssl_server_credentials_create_ex(
test_root_cert, &pem_cert_key_pair, 1,
GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY, nullptr);
grpc_server* server = grpc_server_create(nullptr, nullptr);
grpc_server_register_completion_queue(server, cq, nullptr);
GPR_ASSERT(
grpc_server_add_secure_http2_port(server, server_addr, server_creds));
grpc_server_credentials_release(server_creds);
grpc_server_start(server);
return server;
}
grpc_channel* client_create(char* server_addr, grpc_ssl_session_cache* cache) {
grpc_ssl_pem_key_cert_pair signed_client_key_cert_pair = {
test_signed_client_key, test_signed_client_cert};
grpc_channel_credentials* client_creds = grpc_ssl_credentials_create(
test_root_cert, &signed_client_key_cert_pair, nullptr);
grpc_arg args[] = {
grpc_channel_arg_string_create(
const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
const_cast<char*>("waterzooi.test.google.be")),
grpc_ssl_session_cache_create_channel_arg(cache),
};
grpc_channel_args* client_args =
grpc_channel_args_copy_and_add(nullptr, args, GPR_ARRAY_SIZE(args));
grpc_channel* client = grpc_secure_channel_create(client_creds, server_addr,
client_args, nullptr);
GPR_ASSERT(client != nullptr);
grpc_channel_credentials_release(client_creds);
{
grpc_core::ExecCtx exec_ctx;
grpc_channel_args_destroy(client_args);
}
return client;
}
void do_round_trip(grpc_completion_queue* cq, grpc_server* server,
char* server_addr, grpc_ssl_session_cache* cache,
bool expect_session_reuse) {
grpc_channel* client = client_create(server_addr, cache);
cq_verifier* cqv = cq_verifier_create(cq);
grpc_op ops[6];
grpc_op* op;
grpc_metadata_array initial_metadata_recv;
grpc_metadata_array trailing_metadata_recv;
grpc_metadata_array request_metadata_recv;
grpc_call_details call_details;
grpc_status_code status;
grpc_call_error error;
grpc_slice details;
int was_cancelled = 2;
gpr_timespec deadline = grpc_timeout_seconds_to_deadline(60);
grpc_call* c = grpc_channel_create_call(
client, nullptr, GRPC_PROPAGATE_DEFAULTS, cq,
grpc_slice_from_static_string("/foo"), nullptr, deadline, nullptr);
GPR_ASSERT(c);
grpc_metadata_array_init(&initial_metadata_recv);
grpc_metadata_array_init(&trailing_metadata_recv);
grpc_metadata_array_init(&request_metadata_recv);
grpc_call_details_init(&call_details);
memset(ops, 0, sizeof(ops));
op = ops;
op->op = GRPC_OP_SEND_INITIAL_METADATA;
op->data.send_initial_metadata.count = 0;
op->flags = 0;
op->reserved = nullptr;
op++;
op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
op->flags = 0;
op->reserved = nullptr;
op++;
op->op = GRPC_OP_RECV_INITIAL_METADATA;
op->data.recv_initial_metadata.recv_initial_metadata = &initial_metadata_recv;
op->flags = 0;
op->reserved = nullptr;
op++;
op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
op->data.recv_status_on_client.trailing_metadata = &trailing_metadata_recv;
op->data.recv_status_on_client.status = &status;
op->data.recv_status_on_client.status_details = &details;
op->flags = 0;
op->reserved = nullptr;
op++;
error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
nullptr);
GPR_ASSERT(GRPC_CALL_OK == error);
grpc_call* s;
error = grpc_server_request_call(server, &s, &call_details,
&request_metadata_recv, cq, cq, tag(101));
GPR_ASSERT(GRPC_CALL_OK == error);
CQ_EXPECT_COMPLETION(cqv, tag(101), 1);
cq_verify(cqv);
grpc_auth_context* auth = grpc_call_auth_context(s);
grpc_auth_property_iterator it = grpc_auth_context_find_properties_by_name(
auth, GRPC_SSL_SESSION_REUSED_PROPERTY);
const grpc_auth_property* property = grpc_auth_property_iterator_next(&it);
GPR_ASSERT(property != nullptr);
if (expect_session_reuse) {
GPR_ASSERT(strcmp(property->value, "true") == 0);
} else {
GPR_ASSERT(strcmp(property->value, "false") == 0);
}
grpc_auth_context_release(auth);
memset(ops, 0, sizeof(ops));
op = ops;
op->op = GRPC_OP_SEND_INITIAL_METADATA;
op->data.send_initial_metadata.count = 0;
op->flags = 0;
op->reserved = nullptr;
op++;
op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
op->data.recv_close_on_server.cancelled = &was_cancelled;
op->flags = 0;
op->reserved = nullptr;
op++;
op->op = GRPC_OP_SEND_STATUS_FROM_SERVER;
op->data.send_status_from_server.trailing_metadata_count = 0;
op->data.send_status_from_server.status = GRPC_STATUS_OK;
op->flags = 0;
op->reserved = nullptr;
op++;
error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103),
nullptr);
GPR_ASSERT(GRPC_CALL_OK == error);
CQ_EXPECT_COMPLETION(cqv, tag(103), 1);
CQ_EXPECT_COMPLETION(cqv, tag(1), 1);
cq_verify(cqv);
grpc_metadata_array_destroy(&initial_metadata_recv);
grpc_metadata_array_destroy(&trailing_metadata_recv);
grpc_metadata_array_destroy(&request_metadata_recv);
grpc_call_details_destroy(&call_details);
grpc_call_unref(c);
grpc_call_unref(s);
cq_verifier_destroy(cqv);
grpc_channel_destroy(client);
}
void drain_cq(grpc_completion_queue* cq) {
grpc_event ev;
do {
ev = grpc_completion_queue_next(cq, five_seconds_time(), nullptr);
} while (ev.type != GRPC_QUEUE_SHUTDOWN);
}
TEST(H2SessionReuseTest, SingleReuse) {
int port = grpc_pick_unused_port_or_die();
char* server_addr;
gpr_join_host_port(&server_addr, "localhost", port);
grpc_completion_queue* cq = grpc_completion_queue_create_for_next(nullptr);
grpc_ssl_session_cache* cache = grpc_ssl_session_cache_create_lru(16);
grpc_server* server = server_create(cq, server_addr);
do_round_trip(cq, server, server_addr, cache, false);
do_round_trip(cq, server, server_addr, cache, true);
do_round_trip(cq, server, server_addr, cache, true);
gpr_free(server_addr);
grpc_ssl_session_cache_destroy(cache);
GPR_ASSERT(grpc_completion_queue_next(
cq, grpc_timeout_milliseconds_to_deadline(100), nullptr)
.type == GRPC_QUEUE_TIMEOUT);
grpc_completion_queue* shutdown_cq =
grpc_completion_queue_create_for_pluck(nullptr);
grpc_server_shutdown_and_notify(server, shutdown_cq, tag(1000));
GPR_ASSERT(grpc_completion_queue_pluck(shutdown_cq, tag(1000),
grpc_timeout_seconds_to_deadline(5),
nullptr)
.type == GRPC_OP_COMPLETE);
grpc_server_destroy(server);
grpc_completion_queue_destroy(shutdown_cq);
grpc_completion_queue_shutdown(cq);
drain_cq(cq);
grpc_completion_queue_destroy(cq);
}
} // namespace
} // namespace testing
} // namespace grpc
int main(int argc, char** argv) {
FILE* roots_file;
size_t roots_size = strlen(test_root_cert);
char* roots_filename;
grpc_test_init(argc, argv);
/* Set the SSL roots env var. */
roots_file = gpr_tmpfile("chttp2_ssl_session_reuse_test", &roots_filename);
GPR_ASSERT(roots_filename != nullptr);
GPR_ASSERT(roots_file != nullptr);
GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
fclose(roots_file);
gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_filename);
grpc_init();
::testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
grpc_shutdown();
/* Cleanup. */
remove(roots_filename);
gpr_free(roots_filename);
return ret;
}

@ -141,6 +141,9 @@ int main(int argc, char **argv) {
printf("%lx", (unsigned long) grpc_auth_context_add_property); printf("%lx", (unsigned long) grpc_auth_context_add_property);
printf("%lx", (unsigned long) grpc_auth_context_add_cstring_property); printf("%lx", (unsigned long) grpc_auth_context_add_cstring_property);
printf("%lx", (unsigned long) grpc_auth_context_set_peer_identity_property_name); printf("%lx", (unsigned long) grpc_auth_context_set_peer_identity_property_name);
printf("%lx", (unsigned long) grpc_ssl_session_cache_create_lru);
printf("%lx", (unsigned long) grpc_ssl_session_cache_destroy);
printf("%lx", (unsigned long) grpc_ssl_session_cache_create_channel_arg);
printf("%lx", (unsigned long) grpc_channel_credentials_release); printf("%lx", (unsigned long) grpc_channel_credentials_release);
printf("%lx", (unsigned long) grpc_google_default_credentials_create); printf("%lx", (unsigned long) grpc_google_default_credentials_create);
printf("%lx", (unsigned long) grpc_set_ssl_roots_override_callback); printf("%lx", (unsigned long) grpc_set_ssl_roots_override_callback);

@ -41,6 +41,20 @@ grpc_cc_test(
], ],
) )
grpc_cc_test(
name = "ssl_session_cache_test",
srcs = ["ssl_session_cache_test.cc"],
language = "C++",
external_deps = [
"gtest",
],
deps = [
"//:grpc",
"//:gpr",
"//:tsi",
"//test/core/util:gpr_test_util",
],
)
grpc_cc_test( grpc_cc_test(
name = "ssl_transport_security_test", name = "ssl_transport_security_test",

@ -0,0 +1,154 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <string>
#include <unordered_set>
#include "src/core/tsi/ssl/session_cache/ssl_session_cache.h"
#include "test/core/util/test_config.h"
#include <grpc/grpc.h>
#include <grpc/support/log.h>
#include <gtest/gtest.h>
namespace grpc_core {
namespace {
class SessionTracker;
struct SessionExDataId {
SessionTracker* tracker;
long id;
};
class SessionTracker {
public:
SessionTracker() { ssl_context_ = SSL_CTX_new(TLSv1_2_method()); }
~SessionTracker() { SSL_CTX_free(ssl_context_); }
tsi::SslSessionPtr NewSession(long id) {
static int ex_data_id = SSL_SESSION_get_ex_new_index(
0, nullptr, nullptr, nullptr, DestroyExData);
GPR_ASSERT(ex_data_id != -1);
// OpenSSL and different version of BoringSSL don't agree on API
// so try both.
tsi::SslSessionPtr session = NewSessionInternal(SSL_SESSION_new);
SessionExDataId* data = new SessionExDataId{this, id};
int result = SSL_SESSION_set_ex_data(session.get(), ex_data_id, data);
EXPECT_EQ(result, 1);
alive_sessions_.insert(id);
return session;
}
bool IsAlive(long id) const {
return alive_sessions_.find(id) != alive_sessions_.end();
}
size_t AliveCount() const { return alive_sessions_.size(); }
private:
tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)()) {
return tsi::SslSessionPtr(cb());
}
tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)(const SSL_CTX*)) {
return tsi::SslSessionPtr(cb(ssl_context_));
}
static void DestroyExData(void* parent, void* ptr, CRYPTO_EX_DATA* ad,
int index, long argl, void* argp) {
SessionExDataId* data = static_cast<SessionExDataId*>(ptr);
data->tracker->alive_sessions_.erase(data->id);
delete data;
}
SSL_CTX* ssl_context_;
std::unordered_set<long> alive_sessions_;
};
TEST(SslSessionCacheTest, InitialState) {
SessionTracker tracker;
// Verify session initial state.
{
tsi::SslSessionPtr tmp_sess = tracker.NewSession(1);
EXPECT_EQ(tmp_sess->references, 1);
EXPECT_TRUE(tracker.IsAlive(1));
EXPECT_EQ(tracker.AliveCount(), 1);
}
EXPECT_FALSE(tracker.IsAlive(1));
EXPECT_EQ(tracker.AliveCount(), 0);
}
TEST(SslSessionCacheTest, LruCache) {
SessionTracker tracker;
{
RefCountedPtr<tsi::SslSessionLRUCache> cache =
tsi::SslSessionLRUCache::Create(3);
tsi::SslSessionPtr sess2 = tracker.NewSession(2);
SSL_SESSION* sess2_ptr = sess2.get();
cache->Put("first.dropbox.com", std::move(sess2));
EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess2_ptr);
EXPECT_TRUE(tracker.IsAlive(2));
EXPECT_EQ(tracker.AliveCount(), 1);
// Putting element with the same key destroys old session.
tsi::SslSessionPtr sess3 = tracker.NewSession(3);
SSL_SESSION* sess3_ptr = sess3.get();
cache->Put("first.dropbox.com", std::move(sess3));
EXPECT_FALSE(tracker.IsAlive(2));
EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess3_ptr);
EXPECT_TRUE(tracker.IsAlive(3));
EXPECT_EQ(tracker.AliveCount(), 1);
// Putting three more elements discards current one.
for (long id = 4; id < 7; id++) {
EXPECT_TRUE(tracker.IsAlive(3));
std::string domain = std::to_string(id) + ".random.domain";
cache->Put(domain.c_str(), tracker.NewSession(id));
}
EXPECT_EQ(cache->Size(), 3);
EXPECT_FALSE(tracker.IsAlive(3));
EXPECT_EQ(tracker.AliveCount(), 3);
// Accessing element moves it into front of the queue.
EXPECT_TRUE(cache->Get("4.random.domain"));
EXPECT_TRUE(tracker.IsAlive(4));
EXPECT_TRUE(tracker.IsAlive(5));
EXPECT_TRUE(tracker.IsAlive(6));
// One element has to be evicted from cache->
cache->Put("7.random.domain", tracker.NewSession(7));
EXPECT_TRUE(tracker.IsAlive(4));
EXPECT_FALSE(tracker.IsAlive(5));
EXPECT_TRUE(tracker.IsAlive(6));
EXPECT_TRUE(tracker.IsAlive(7));
EXPECT_EQ(tracker.AliveCount(), 3);
}
// Cache destructor destroys all sessions.
EXPECT_EQ(tracker.AliveCount(), 0);
}
} // namespace
} // namespace grpc_core
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
grpc_test_init(argc, argv);
grpc_init();
int ret = RUN_ALL_TESTS();
grpc_shutdown();
return ret;
}

@ -52,8 +52,8 @@ typedef enum AlpnMode {
typedef struct ssl_alpn_lib { typedef struct ssl_alpn_lib {
AlpnMode alpn_mode; AlpnMode alpn_mode;
char** server_alpn_protocols; const char** server_alpn_protocols;
char** client_alpn_protocols; const char** client_alpn_protocols;
uint16_t num_server_alpn_protocols; uint16_t num_server_alpn_protocols;
uint16_t num_client_alpn_protocols; uint16_t num_client_alpn_protocols;
} ssl_alpn_lib; } ssl_alpn_lib;
@ -76,6 +76,10 @@ typedef struct ssl_tsi_test_fixture {
ssl_alpn_lib* alpn_lib; ssl_alpn_lib* alpn_lib;
bool force_client_auth; bool force_client_auth;
char* server_name_indication; char* server_name_indication;
tsi_ssl_session_cache* session_cache;
bool session_reused;
const char* session_ticket_key;
size_t session_ticket_key_size;
tsi_ssl_server_handshaker_factory* server_handshaker_factory; tsi_ssl_server_handshaker_factory* server_handshaker_factory;
tsi_ssl_client_handshaker_factory* client_handshaker_factory; tsi_ssl_client_handshaker_factory* client_handshaker_factory;
} ssl_tsi_test_fixture; } ssl_tsi_test_fixture;
@ -89,47 +93,60 @@ static void ssl_test_setup_handshakers(tsi_test_fixture* fixture) {
ssl_key_cert_lib* key_cert_lib = ssl_fixture->key_cert_lib; ssl_key_cert_lib* key_cert_lib = ssl_fixture->key_cert_lib;
ssl_alpn_lib* alpn_lib = ssl_fixture->alpn_lib; ssl_alpn_lib* alpn_lib = ssl_fixture->alpn_lib;
/* Create client handshaker factory. */ /* Create client handshaker factory. */
tsi_ssl_pem_key_cert_pair* client_key_cert_pair = nullptr; tsi_ssl_client_handshaker_options client_options;
memset(&client_options, 0, sizeof(client_options));
client_options.pem_root_certs = key_cert_lib->root_cert;
if (ssl_fixture->force_client_auth) { if (ssl_fixture->force_client_auth) {
client_key_cert_pair = key_cert_lib->use_bad_client_cert client_options.pem_key_cert_pair =
? &key_cert_lib->bad_client_pem_key_cert_pair key_cert_lib->use_bad_client_cert
: &key_cert_lib->client_pem_key_cert_pair; ? &key_cert_lib->bad_client_pem_key_cert_pair
: &key_cert_lib->client_pem_key_cert_pair;
} }
char** client_alpn_protocols = nullptr;
uint16_t num_client_alpn_protocols = 0;
if (alpn_lib->alpn_mode == ALPN_CLIENT_NO_SERVER || if (alpn_lib->alpn_mode == ALPN_CLIENT_NO_SERVER ||
alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_OK || alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_OK ||
alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) { alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) {
client_alpn_protocols = alpn_lib->client_alpn_protocols; client_options.alpn_protocols = alpn_lib->client_alpn_protocols;
num_client_alpn_protocols = alpn_lib->num_client_alpn_protocols; client_options.num_alpn_protocols = alpn_lib->num_client_alpn_protocols;
} }
GPR_ASSERT(tsi_create_ssl_client_handshaker_factory( if (ssl_fixture->session_cache != nullptr) {
client_key_cert_pair, key_cert_lib->root_cert, nullptr, client_options.session_cache = ssl_fixture->session_cache;
(const char**)client_alpn_protocols, num_client_alpn_protocols, }
&ssl_fixture->client_handshaker_factory) == TSI_OK); GPR_ASSERT(tsi_create_ssl_client_handshaker_factory_with_options(
&client_options, &ssl_fixture->client_handshaker_factory) ==
TSI_OK);
/* Create server handshaker factory. */ /* Create server handshaker factory. */
char** server_alpn_protocols = nullptr; tsi_ssl_server_handshaker_options server_options;
uint16_t num_server_alpn_protocols = 0; memset(&server_options, 0, sizeof(server_options));
if (alpn_lib->alpn_mode == ALPN_SERVER_NO_CLIENT || if (alpn_lib->alpn_mode == ALPN_SERVER_NO_CLIENT ||
alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_OK || alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_OK ||
alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) { alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) {
server_alpn_protocols = alpn_lib->server_alpn_protocols; server_options.alpn_protocols = alpn_lib->server_alpn_protocols;
num_server_alpn_protocols = alpn_lib->num_server_alpn_protocols; server_options.num_alpn_protocols = alpn_lib->num_server_alpn_protocols;
if (alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) { if (alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_MISMATCH) {
num_server_alpn_protocols--; server_options.num_alpn_protocols--;
} }
} }
GPR_ASSERT(tsi_create_ssl_server_handshaker_factory( server_options.pem_key_cert_pairs =
key_cert_lib->use_bad_server_cert key_cert_lib->use_bad_server_cert
? key_cert_lib->bad_server_pem_key_cert_pairs ? key_cert_lib->bad_server_pem_key_cert_pairs
: key_cert_lib->server_pem_key_cert_pairs, : key_cert_lib->server_pem_key_cert_pairs;
key_cert_lib->use_bad_server_cert server_options.num_key_cert_pairs =
? key_cert_lib->bad_server_num_key_cert_pairs key_cert_lib->use_bad_server_cert
: key_cert_lib->server_num_key_cert_pairs, ? key_cert_lib->bad_server_num_key_cert_pairs
key_cert_lib->root_cert, ssl_fixture->force_client_auth, : key_cert_lib->server_num_key_cert_pairs;
nullptr, (const char**)server_alpn_protocols, server_options.pem_client_root_certs = key_cert_lib->root_cert;
num_server_alpn_protocols, if (ssl_fixture->force_client_auth) {
&ssl_fixture->server_handshaker_factory) == TSI_OK); server_options.client_certificate_request =
TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY;
} else {
server_options.client_certificate_request =
TSI_DONT_REQUEST_CLIENT_CERTIFICATE;
}
server_options.session_ticket_key = ssl_fixture->session_ticket_key;
server_options.session_ticket_key_size = ssl_fixture->session_ticket_key_size;
GPR_ASSERT(tsi_create_ssl_server_handshaker_factory_with_options(
&server_options, &ssl_fixture->server_handshaker_factory) ==
TSI_OK);
/* Create server and client handshakers. */ /* Create server and client handshakers. */
tsi_handshaker* client_handshaker = nullptr; tsi_handshaker* client_handshaker = nullptr;
GPR_ASSERT(tsi_ssl_client_handshaker_factory_create_handshaker( GPR_ASSERT(tsi_ssl_client_handshaker_factory_create_handshaker(
@ -176,6 +193,18 @@ check_basic_authenticated_peer_and_get_common_name(const tsi_peer* peer) {
return property; return property;
} }
static void check_session_reusage(ssl_tsi_test_fixture* ssl_fixture,
tsi_peer* peer) {
const tsi_peer_property* session_reused =
tsi_peer_get_property_by_name(peer, TSI_SSL_SESSION_REUSED_PEER_PROPERTY);
GPR_ASSERT(session_reused != nullptr);
if (ssl_fixture->session_reused) {
GPR_ASSERT(strcmp(session_reused->value.data, "true") == 0);
} else {
GPR_ASSERT(strcmp(session_reused->value.data, "false") == 0);
}
}
void check_server0_peer(tsi_peer* peer) { void check_server0_peer(tsi_peer* peer) {
const tsi_peer_property* property = const tsi_peer_property* property =
check_basic_authenticated_peer_and_get_common_name(peer); check_basic_authenticated_peer_and_get_common_name(peer);
@ -233,7 +262,7 @@ static void check_client_peer(ssl_tsi_test_fixture* ssl_fixture,
ssl_alpn_lib* alpn_lib = ssl_fixture->alpn_lib; ssl_alpn_lib* alpn_lib = ssl_fixture->alpn_lib;
if (!ssl_fixture->force_client_auth) { if (!ssl_fixture->force_client_auth) {
GPR_ASSERT(peer->property_count == GPR_ASSERT(peer->property_count ==
(alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_OK ? 1 : 0)); (alpn_lib->alpn_mode == ALPN_CLIENT_SERVER_OK ? 2 : 1));
} else { } else {
const tsi_peer_property* property = const tsi_peer_property* property =
check_basic_authenticated_peer_and_get_common_name(peer); check_basic_authenticated_peer_and_get_common_name(peer);
@ -257,8 +286,8 @@ static void ssl_test_check_handshaker_peers(tsi_test_fixture* fixture) {
if (expect_success) { if (expect_success) {
GPR_ASSERT(tsi_handshaker_result_extract_peer( GPR_ASSERT(tsi_handshaker_result_extract_peer(
ssl_fixture->base.client_result, &peer) == TSI_OK); ssl_fixture->base.client_result, &peer) == TSI_OK);
check_session_reusage(ssl_fixture, &peer);
check_alpn(ssl_fixture, &peer); check_alpn(ssl_fixture, &peer);
if (ssl_fixture->server_name_indication != nullptr) { if (ssl_fixture->server_name_indication != nullptr) {
check_server1_peer(&peer); check_server1_peer(&peer);
} else { } else {
@ -270,6 +299,7 @@ static void ssl_test_check_handshaker_peers(tsi_test_fixture* fixture) {
if (expect_success) { if (expect_success) {
GPR_ASSERT(tsi_handshaker_result_extract_peer( GPR_ASSERT(tsi_handshaker_result_extract_peer(
ssl_fixture->base.server_result, &peer) == TSI_OK); ssl_fixture->base.server_result, &peer) == TSI_OK);
check_session_reusage(ssl_fixture, &peer);
check_alpn(ssl_fixture, &peer); check_alpn(ssl_fixture, &peer);
check_client_peer(ssl_fixture, &peer); check_client_peer(ssl_fixture, &peer);
} else { } else {
@ -291,11 +321,11 @@ static void ssl_test_destruct(tsi_test_fixture* fixture) {
/* Destroy ssl_alpn_lib. */ /* Destroy ssl_alpn_lib. */
ssl_alpn_lib* alpn_lib = ssl_fixture->alpn_lib; ssl_alpn_lib* alpn_lib = ssl_fixture->alpn_lib;
for (size_t i = 0; i < alpn_lib->num_server_alpn_protocols; i++) { for (size_t i = 0; i < alpn_lib->num_server_alpn_protocols; i++) {
gpr_free(alpn_lib->server_alpn_protocols[i]); gpr_free(const_cast<char*>(alpn_lib->server_alpn_protocols[i]));
} }
gpr_free(alpn_lib->server_alpn_protocols); gpr_free(alpn_lib->server_alpn_protocols);
for (size_t i = 0; i < alpn_lib->num_client_alpn_protocols; i++) { for (size_t i = 0; i < alpn_lib->num_client_alpn_protocols; i++) {
gpr_free(alpn_lib->client_alpn_protocols[i]); gpr_free(const_cast<char*>(alpn_lib->client_alpn_protocols[i]));
} }
gpr_free(alpn_lib->client_alpn_protocols); gpr_free(alpn_lib->client_alpn_protocols);
gpr_free(alpn_lib); gpr_free(alpn_lib);
@ -316,6 +346,9 @@ static void ssl_test_destruct(tsi_test_fixture* fixture) {
key_cert_lib->bad_client_pem_key_cert_pair); key_cert_lib->bad_client_pem_key_cert_pair);
gpr_free(key_cert_lib->root_cert); gpr_free(key_cert_lib->root_cert);
gpr_free(key_cert_lib); gpr_free(key_cert_lib);
if (ssl_fixture->session_cache != nullptr) {
tsi_ssl_session_cache_unref(ssl_fixture->session_cache);
}
/* Unreference others. */ /* Unreference others. */
tsi_ssl_server_handshaker_factory_unref( tsi_ssl_server_handshaker_factory_unref(
ssl_fixture->server_handshaker_factory); ssl_fixture->server_handshaker_factory);
@ -388,10 +421,10 @@ static tsi_test_fixture* ssl_tsi_test_fixture_create() {
/* Create ssl_alpn_lib. */ /* Create ssl_alpn_lib. */
ssl_alpn_lib* alpn_lib = ssl_alpn_lib* alpn_lib =
static_cast<ssl_alpn_lib*>(gpr_zalloc(sizeof(*alpn_lib))); static_cast<ssl_alpn_lib*>(gpr_zalloc(sizeof(*alpn_lib)));
alpn_lib->server_alpn_protocols = alpn_lib->server_alpn_protocols = static_cast<const char**>(
static_cast<char**>(gpr_zalloc(sizeof(char*) * SSL_TSI_TEST_ALPN_NUM)); gpr_zalloc(sizeof(char*) * SSL_TSI_TEST_ALPN_NUM));
alpn_lib->client_alpn_protocols = alpn_lib->client_alpn_protocols = static_cast<const char**>(
static_cast<char**>(gpr_zalloc(sizeof(char*) * SSL_TSI_TEST_ALPN_NUM)); gpr_zalloc(sizeof(char*) * SSL_TSI_TEST_ALPN_NUM));
alpn_lib->server_alpn_protocols[0] = gpr_strdup(SSL_TSI_TEST_ALPN1); alpn_lib->server_alpn_protocols[0] = gpr_strdup(SSL_TSI_TEST_ALPN1);
alpn_lib->server_alpn_protocols[1] = gpr_strdup(SSL_TSI_TEST_ALPN3); alpn_lib->server_alpn_protocols[1] = gpr_strdup(SSL_TSI_TEST_ALPN3);
alpn_lib->client_alpn_protocols[0] = gpr_strdup(SSL_TSI_TEST_ALPN2); alpn_lib->client_alpn_protocols[0] = gpr_strdup(SSL_TSI_TEST_ALPN2);
@ -402,6 +435,9 @@ static tsi_test_fixture* ssl_tsi_test_fixture_create() {
ssl_fixture->alpn_lib = alpn_lib; ssl_fixture->alpn_lib = alpn_lib;
ssl_fixture->base.vtable = &vtable; ssl_fixture->base.vtable = &vtable;
ssl_fixture->server_name_indication = nullptr; ssl_fixture->server_name_indication = nullptr;
ssl_fixture->session_reused = false;
ssl_fixture->session_ticket_key = nullptr;
ssl_fixture->session_ticket_key_size = 0;
ssl_fixture->force_client_auth = false; ssl_fixture->force_client_auth = false;
return &ssl_fixture->base; return &ssl_fixture->base;
} }
@ -558,6 +594,38 @@ void ssl_tsi_test_do_round_trip_odd_buffer_size() {
} }
} }
void ssl_tsi_test_do_handshake_session_cache() {
tsi_ssl_session_cache* session_cache = tsi_ssl_session_cache_create_lru(16);
char session_ticket_key[48];
auto do_handshake = [&session_ticket_key,
&session_cache](bool session_reused) {
tsi_test_fixture* fixture = ssl_tsi_test_fixture_create();
ssl_tsi_test_fixture* ssl_fixture =
reinterpret_cast<ssl_tsi_test_fixture*>(fixture);
ssl_fixture->server_name_indication =
const_cast<char*>("waterzooi.test.google.be");
ssl_fixture->session_ticket_key = session_ticket_key;
ssl_fixture->session_ticket_key_size = 48;
tsi_ssl_session_cache_ref(session_cache);
ssl_fixture->session_cache = session_cache;
ssl_fixture->session_reused = session_reused;
tsi_test_do_round_trip(&ssl_fixture->base);
tsi_test_fixture_destroy(fixture);
};
memset(session_ticket_key, 'a', 48);
do_handshake(false);
do_handshake(true);
do_handshake(true);
// Changing session_ticket_key on server invalidates ticket.
memset(session_ticket_key, 'b', 48);
do_handshake(false);
do_handshake(true);
memset(session_ticket_key, 'c', 48);
do_handshake(false);
do_handshake(true);
tsi_ssl_session_cache_unref(session_cache);
}
static const tsi_ssl_handshaker_factory_vtable* original_vtable; static const tsi_ssl_handshaker_factory_vtable* original_vtable;
static bool handshaker_factory_destructor_called; static bool handshaker_factory_destructor_called;
@ -575,13 +643,14 @@ static tsi_ssl_handshaker_factory_vtable test_handshaker_factory_vtable = {
void test_tsi_ssl_client_handshaker_factory_refcounting() { void test_tsi_ssl_client_handshaker_factory_refcounting() {
int i; int i;
const char* cert_chain = char* cert_chain = load_file(SSL_TSI_TEST_CREDENTIALS_DIR, "client.pem");
load_file(SSL_TSI_TEST_CREDENTIALS_DIR, "client.pem");
tsi_ssl_client_handshaker_options options;
memset(&options, 0, sizeof(options));
options.pem_root_certs = cert_chain;
tsi_ssl_client_handshaker_factory* client_handshaker_factory; tsi_ssl_client_handshaker_factory* client_handshaker_factory;
GPR_ASSERT(tsi_create_ssl_client_handshaker_factory( GPR_ASSERT(tsi_create_ssl_client_handshaker_factory_with_options(
nullptr, cert_chain, nullptr, nullptr, 0, &options, &client_handshaker_factory) == TSI_OK);
&client_handshaker_factory) == TSI_OK);
handshaker_factory_destructor_called = false; handshaker_factory_destructor_called = false;
original_vtable = tsi_ssl_handshaker_factory_swap_vtable( original_vtable = tsi_ssl_handshaker_factory_swap_vtable(
@ -608,7 +677,7 @@ void test_tsi_ssl_client_handshaker_factory_refcounting() {
tsi_handshaker_destroy(handshaker[2]); tsi_handshaker_destroy(handshaker[2]);
GPR_ASSERT(handshaker_factory_destructor_called); GPR_ASSERT(handshaker_factory_destructor_called);
gpr_free((void*)cert_chain); gpr_free(cert_chain);
} }
void test_tsi_ssl_server_handshaker_factory_refcounting() { void test_tsi_ssl_server_handshaker_factory_refcounting() {
@ -673,6 +742,7 @@ void ssl_tsi_test_handshaker_factory_internals() {
int main(int argc, char** argv) { int main(int argc, char** argv) {
grpc_test_init(argc, argv); grpc_test_init(argc, argv);
grpc_init(); grpc_init();
ssl_tsi_test_do_handshake_tiny_handshake_buffer(); ssl_tsi_test_do_handshake_tiny_handshake_buffer();
ssl_tsi_test_do_handshake_small_handshake_buffer(); ssl_tsi_test_do_handshake_small_handshake_buffer();
ssl_tsi_test_do_handshake(); ssl_tsi_test_do_handshake();
@ -688,6 +758,7 @@ int main(int argc, char** argv) {
#endif #endif
ssl_tsi_test_do_handshake_alpn_server_no_client(); ssl_tsi_test_do_handshake_alpn_server_no_client();
ssl_tsi_test_do_handshake_alpn_client_server_ok(); ssl_tsi_test_do_handshake_alpn_client_server_ok();
ssl_tsi_test_do_handshake_session_cache();
ssl_tsi_test_do_round_trip_for_all_configs(); ssl_tsi_test_do_round_trip_for_all_configs();
ssl_tsi_test_do_round_trip_odd_buffer_size(); ssl_tsi_test_do_round_trip_odd_buffer_size();
ssl_tsi_test_handshaker_factory_internals(); ssl_tsi_test_handshaker_factory_internals();

@ -1478,6 +1478,11 @@ src/core/tsi/alts_transport_security.cc \
src/core/tsi/alts_transport_security.h \ src/core/tsi/alts_transport_security.h \
src/core/tsi/fake_transport_security.cc \ src/core/tsi/fake_transport_security.cc \
src/core/tsi/fake_transport_security.h \ src/core/tsi/fake_transport_security.h \
src/core/tsi/ssl/session_cache/ssl_session.h \
src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc \
src/core/tsi/ssl/session_cache/ssl_session_cache.cc \
src/core/tsi/ssl/session_cache/ssl_session_cache.h \
src/core/tsi/ssl/session_cache/ssl_session_openssl.cc \
src/core/tsi/ssl_transport_security.cc \ src/core/tsi/ssl_transport_security.cc \
src/core/tsi/ssl_transport_security.h \ src/core/tsi/ssl_transport_security.h \
src/core/tsi/ssl_types.h \ src/core/tsi/ssl_types.h \

@ -3677,6 +3677,28 @@
"third_party": false, "third_party": false,
"type": "target" "type": "target"
}, },
{
"deps": [
"gpr",
"gpr_test_util",
"grpc",
"grpc++",
"grpc++_test",
"grpc_test_util"
],
"headers": [
"test/core/end2end/end2end_tests.h"
],
"is_filegroup": false,
"language": "c++",
"name": "h2_ssl_session_reuse_test",
"src": [
"test/core/end2end/end2end_tests.h",
"test/core/end2end/h2_ssl_session_reuse_test.cc"
],
"third_party": false,
"type": "target"
},
{ {
"deps": [ "deps": [
"gpr", "gpr",
@ -10393,6 +10415,8 @@
"headers": [ "headers": [
"src/core/tsi/alts_transport_security.h", "src/core/tsi/alts_transport_security.h",
"src/core/tsi/fake_transport_security.h", "src/core/tsi/fake_transport_security.h",
"src/core/tsi/ssl/session_cache/ssl_session.h",
"src/core/tsi/ssl/session_cache/ssl_session_cache.h",
"src/core/tsi/ssl_transport_security.h", "src/core/tsi/ssl_transport_security.h",
"src/core/tsi/ssl_types.h", "src/core/tsi/ssl_types.h",
"src/core/tsi/transport_security_grpc.h" "src/core/tsi/transport_security_grpc.h"
@ -10405,6 +10429,11 @@
"src/core/tsi/alts_transport_security.h", "src/core/tsi/alts_transport_security.h",
"src/core/tsi/fake_transport_security.cc", "src/core/tsi/fake_transport_security.cc",
"src/core/tsi/fake_transport_security.h", "src/core/tsi/fake_transport_security.h",
"src/core/tsi/ssl/session_cache/ssl_session.h",
"src/core/tsi/ssl/session_cache/ssl_session_boringssl.cc",
"src/core/tsi/ssl/session_cache/ssl_session_cache.cc",
"src/core/tsi/ssl/session_cache/ssl_session_cache.h",
"src/core/tsi/ssl/session_cache/ssl_session_openssl.cc",
"src/core/tsi/ssl_transport_security.cc", "src/core/tsi/ssl_transport_security.cc",
"src/core/tsi/ssl_transport_security.h", "src/core/tsi/ssl_transport_security.h",
"src/core/tsi/ssl_types.h", "src/core/tsi/ssl_types.h",

@ -4249,6 +4249,30 @@
], ],
"uses_polling": true "uses_polling": true
}, },
{
"args": [],
"benchmark": false,
"ci_platforms": [
"linux",
"mac",
"posix",
"windows"
],
"cpu_cost": 1.0,
"exclude_configs": [],
"exclude_iomgrs": [],
"flaky": false,
"gtest": true,
"language": "c++",
"name": "h2_ssl_session_reuse_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": true
},
{ {
"args": [], "args": [],
"benchmark": false, "benchmark": false,

Loading…
Cancel
Save