diff --git a/.github/scripts/package-client-cpp-manylinux228.sh b/.github/scripts/package-client-cpp-manylinux228.sh index 6bfef0aa415b..be68fb1fc0a3 100644 --- a/.github/scripts/package-client-cpp-manylinux228.sh +++ b/.github/scripts/package-client-cpp-manylinux228.sh @@ -73,10 +73,10 @@ java -version # manylinux_2_28 is AlmaLinux 8, whose system OpenSSL is 1.1.1 (EOL and not # Apache-2.0 - must not be bundled/redistributed in an ASF convenience binary). -# Build OpenSSL 3.x from source instead (-Diotdb.openssl.from.source=ON), which -# keeps the glibc 2.28 baseline. OpenSSL 3.x's Configure needs perl plus a few -# modules (IPC::Cmd, Data::Dumper) that are not on the minimal image - install -# them even when perl itself is already present. +# A pinned Tongsuo 8.4-stable commit is always built from source (WITH_SSL=ON), which keeps the +# glibc 2.28 baseline. Tongsuo's Configure needs perl plus a +# few modules (IPC::Cmd, Data::Dumper) that are not on the minimal image - +# install them even when perl itself is already present. if command -v dnf >/dev/null 2>&1; then dnf install -y perl perl-IPC-Cmd perl-Data-Dumper else @@ -86,7 +86,6 @@ fi cd "${GITHUB_WORKSPACE:?GITHUB_WORKSPACE is not set}" ./mvnw clean package -P with-cpp -pl iotdb-client/client-cpp -am -DskipTests \ -Dspotless.skip=true \ - -Diotdb.openssl.from.source=ON \ -Dclient.cpp.package.classifier="${PACKAGE_CLASSIFIER}" SO="iotdb-client/client-cpp/target/install/lib/libiotdb_session.so" diff --git a/.github/workflows/client-cpp-package.yml b/.github/workflows/client-cpp-package.yml index 38eac3fbcbcc..9ad3fbb4a5ae 100644 --- a/.github/workflows/client-cpp-package.yml +++ b/.github/workflows/client-cpp-package.yml @@ -309,14 +309,12 @@ jobs: shell: bash run: | set -euxo pipefail - # Pin openssl@3 (Apache-2.0): the default 'openssl' formula will move to - # OpenSSL 4.0, which drops the legacy TLS-method APIs Thrift still uses. - brew install boost openssl@3 llvm@17 bison + # Build Tongsuo from source for SSL/TLS (国密 / TLCP support). + brew install boost llvm@17 bison perl ln -sf "$(brew --prefix llvm@17)/bin/clang-format" "$(brew --prefix)/bin/clang-format" echo "$(brew --prefix bison)/bin" >> "$GITHUB_PATH" echo "$(brew --prefix llvm@17)/bin" >> "$GITHUB_PATH" - # Homebrew OpenSSL is keg-only, so point find_package(OpenSSL) at it. - echo "OPENSSL_ROOT_DIR=$(brew --prefix openssl@3)" >> "$GITHUB_ENV" + echo "$(brew --prefix perl)/bin" >> "$GITHUB_PATH" clang-format --version bison --version - name: Cache Maven packages @@ -420,6 +418,7 @@ jobs: shell: pwsh run: | choco install winflexbison3 -y --no-progress + choco install strawberryperl -y --no-progress $boostArgs = @('install', '${{ matrix.boost_choco }}', '-y', '--no-progress') if ('${{ matrix.boost_choco_version }}' -ne '') { $boostArgs += @("--version=${{ matrix.boost_choco_version }}") @@ -433,18 +432,7 @@ jobs: throw "Boost not found under C:\local after installing ${{ matrix.boost_choco }}" } echo $boostDir.FullName >> $env:GITHUB_PATH - # Use a pinned OpenSSL 3.x (Apache-2.0). 'choco install openssl' now - # installs OpenSSL 4.0, which removed the legacy TLS-method APIs that - # Apache Thrift's TSSLSocket still calls. The FireDaemon zip is a clean - # prebuilt OpenSSL 3.5.x that keeps them. - $sslZip = "$env:RUNNER_TEMP\openssl-3.5.3.zip" - $sslDir = "$env:RUNNER_TEMP\openssl-3" - curl.exe -L --fail --retry 3 -o $sslZip 'https://download.firedaemon.com/FireDaemon-OpenSSL/openssl-3.5.3.zip' - Expand-Archive -Path $sslZip -DestinationPath $sslDir -Force - $sslPath = (Get-ChildItem $sslDir -Recurse -Directory -Filter 'x64' | Select-Object -First 1).FullName - if (-not $sslPath) { throw "OpenSSL x64 dir not found under $sslDir" } - echo "$sslPath\bin" >> $env:GITHUB_PATH - echo "OPENSSL_ROOT_DIR=$sslPath" >> $env:GITHUB_ENV + echo "C:\strawberry\perl\bin" >> $env:GITHUB_PATH - name: Cache Maven packages uses: actions/cache@v5 with: @@ -459,6 +447,12 @@ jobs: PACKAGE_CLASSIFIER: ${{ matrix.package_classifier }} run: | set -euxo pipefail + # Git Bash perl lacks modules for Tongsuo Configure; Strawberry is installed above. + if [ -d /c/Strawberry/perl/bin ]; then + export PATH="/c/Strawberry/perl/bin:${PATH}" + fi + which perl + perl -MLocale::Maketext::Simple -e1 MVN_ARGS=(./mvnw clean package -P with-cpp -pl iotdb-client/client-cpp -am -DskipTests \ -Dspotless.skip=true \ "-Dclient.cpp.package.classifier=${PACKAGE_CLASSIFIER}") diff --git a/.github/workflows/multi-language-client.yml b/.github/workflows/multi-language-client.yml index 5437a6549856..b86cdc307604 100644 --- a/.github/workflows/multi-language-client.yml +++ b/.github/workflows/multi-language-client.yml @@ -124,7 +124,7 @@ jobs: run: | set -euxo pipefail sudo apt-get update - sudo apt-get install -y libboost-all-dev openssl libssl-dev wget + sudo apt-get install -y libboost-all-dev perl wget # jammy (22.04): no clang-format-17 in default repos — use apt.llvm.org (same LLVM 17 as noble/choco/brew) . /etc/os-release if [[ "${VERSION_CODENAME}" == "jammy" ]]; then @@ -144,13 +144,12 @@ jobs: if: runner.os == 'macOS' shell: bash run: | - # Pin openssl@3 (Apache-2.0); the default formula will move to OpenSSL 4.0. - brew install boost openssl@3 llvm@17 bison + # Build Tongsuo from source for SSL/TLS (国密 / TLCP support). + brew install boost llvm@17 bison perl ln -sf "$(brew --prefix llvm@17)/bin/clang-format" "$(brew --prefix)/bin/clang-format" echo "$(brew --prefix bison)/bin" >> "$GITHUB_PATH" echo "$(brew --prefix llvm@17)/bin" >> "$GITHUB_PATH" - # Homebrew OpenSSL is keg-only, so point find_package(OpenSSL) at it. - echo "OPENSSL_ROOT_DIR=$(brew --prefix openssl@3)" >> "$GITHUB_ENV" + echo "$(brew --prefix perl)/bin" >> "$GITHUB_PATH" clang-format --version bison --version sudo rm -rf /Applications/Xcode_14.3.1.app @@ -163,19 +162,10 @@ jobs: run: | choco install winflexbison3 -y choco install boost-msvc-14.3 -y + choco install strawberryperl -y $boost_path = (Get-ChildItem -Path 'C:\local\' -Filter 'boost_*').FullName echo $boost_path >> $env:GITHUB_PATH - - # Pinned OpenSSL 3.x (Apache-2.0): 'choco install openssl' now installs - # OpenSSL 4.0, which removed the legacy TLS-method APIs Thrift uses. - $sslZip = "$env:RUNNER_TEMP\openssl-3.5.3.zip" - $sslDir = "$env:RUNNER_TEMP\openssl-3" - curl.exe -L --fail --retry 3 -o $sslZip 'https://download.firedaemon.com/FireDaemon-OpenSSL/openssl-3.5.3.zip' - Expand-Archive -Path $sslZip -DestinationPath $sslDir -Force - $sslPath = (Get-ChildItem $sslDir -Recurse -Directory -Filter 'x64' | Select-Object -First 1).FullName - if (-not $sslPath) { throw "OpenSSL x64 dir not found under $sslDir" } - echo "$sslPath\bin" >> $env:GITHUB_PATH - echo "OPENSSL_ROOT_DIR=$sslPath" >> $env:GITHUB_ENV + echo "C:\strawberry\perl\bin" >> $env:GITHUB_PATH choco install llvm --version=17.0.6 --force -y clang-format --version - name: Cache Maven packages @@ -197,8 +187,12 @@ jobs: # Explicitly using "install" instead of package in order to be sure we're using libs built on this machine # (was causing problems on windows, but could cause problem on linux, when updating the thrift module) run: | + if [ "${{ runner.os }}" = "Windows" ] && [ -d /c/Strawberry/perl/bin ]; then + export PATH="/c/Strawberry/perl/bin:${PATH}" + fi if [[ "${{ matrix.os }}" == "windows-2025-vs2026" ]]; then - ./mvnw clean verify -P with-cpp -pl iotdb-client/client-cpp -am -Dcmake.generator="Visual Studio 18 2026" + ./mvnw clean verify -P with-cpp -pl iotdb-client/client-cpp -am \ + -Dcmake.generator="Visual Studio 18 2026" else ./mvnw clean verify -P with-cpp -pl iotdb-client/client-cpp -am fi diff --git a/LICENSE-binary b/LICENSE-binary index 58c794bd8024..0430c04658b6 100644 --- a/LICENSE-binary +++ b/LICENSE-binary @@ -246,6 +246,7 @@ io.dropwizard.metrics:metrics-core:4.2.19 io.dropwizard.metrics:metrics-jvm:3.2.2 com.librato.metrics:metrics-librato:5.1.0 com.github.moquette-io.moquette:moquette-broker:0.18 +GmSSL:GmSSL:3.2.x (optional C++ NTLS provider) io.netty:netty-buffer:4.1.137.Final io.netty:netty-codec:4.1.137.Final io.netty:netty-codec-http:4.1.137.Final @@ -261,6 +262,7 @@ org.osgi:org.osgi.core:7.0.0 org.osgi:osgi.cmpn:7.0.0 org.ops4j.pax.jdbc:pax-jdbc-common:1.5.6 org.xerial.snappy:snappy-java:1.1.10.5 +Tongsuo:Tongsuo:8.4-stable commit 0aed892c (default C++ NTLS provider) io.airlift.airline:0.9 diff --git a/iotdb-client/client-cpp/CMakeLists.txt b/iotdb-client/client-cpp/CMakeLists.txt index 084396bc9b50..88af804d2edd 100644 --- a/iotdb-client/client-cpp/CMakeLists.txt +++ b/iotdb-client/client-cpp/CMakeLists.txt @@ -78,7 +78,7 @@ if(NOT MSVC) file(WRITE "${_iotdb_cxx11_abi_stamp}" "${_iotdb_cxx11_abi_stamp_value}") endif() -option(WITH_SSL "Build with OpenSSL support" ON) +option(WITH_SSL "Build with SSL/TLS support" ON) option(BUILD_TESTING "Build IT test executables" OFF) option(IOTDB_OFFLINE "Disable all network access during configure" OFF) set(IOTDB_SESSION_VERSION "0.0.0" @@ -99,6 +99,19 @@ set(BOOST_VERSION "${_iotdb_default_boost_version}" CACHE STRING "Boost version used when downloading / unpacking (Thrift build only)") set(THRIFT_VERSION "0.24.0" CACHE STRING "Apache Thrift version used when downloading / building") +set(IOTDB_NTLS_PROVIDER "TONGSUO" + CACHE STRING "NTLS provider: TONGSUO (default) or GMSSL") +set_property(CACHE IOTDB_NTLS_PROVIDER PROPERTY STRINGS TONGSUO GMSSL) +string(TOUPPER "${IOTDB_NTLS_PROVIDER}" IOTDB_NTLS_PROVIDER) +if(NOT IOTDB_NTLS_PROVIDER MATCHES "^(TONGSUO|GMSSL)$") + message(FATAL_ERROR "IOTDB_NTLS_PROVIDER must be TONGSUO or GMSSL") +endif() +set(TONGSUO_GIT_REF "0aed892c5f48c9a52d1f5667667ae45156b9cdf4" + CACHE STRING "Pinned Tongsuo git commit used when building SSL/TLS from source") +set(TONGSUO_SHA256 "4bb302df8ff73a89b3483873d10e7b6a4eeb041310b8eabd2be309004a88f8b6" + CACHE STRING "SHA-256 of the pinned Tongsuo source archive") +set(IOTDB_GMSSL_ROOT_DIR "" + CACHE PATH "Preinstalled GmSSL 3.2 root (required for the GMSSL provider)") if(WIN32) set(IOTDB_OS_DEPS_DIR "${IOTDB_DEPS_DIR}/windows") @@ -145,8 +158,8 @@ if(UNIX AND NOT APPLE) SOVERSION "${IOTDB_SESSION_SOVERSION}") endif() -# When SSL is on we bundle the OpenSSL shared libraries next to libiotdb_session -# in the package lib/ directory. Give the library an $ORIGIN-relative runtime +# When SSL is on we bundle the Tongsuo/OpenSSL-compatible shared libraries next to +# libiotdb_session in the package lib/ directory. Give the library an $ORIGIN-relative runtime # search path so the loader finds them without LD_LIBRARY_PATH / install_name # tweaks, keeping the SDK self-contained. if(WITH_SSL) @@ -179,6 +192,13 @@ target_include_directories(iotdb_session ${THRIFT_INCLUDE_DIR} $<$:${BOOST_INCLUDE_DIR}>) +if(WITH_SSL AND IOTDB_NTLS_PROVIDER STREQUAL "TONGSUO") + # Homebrew installs Boost and OpenSSL into the same broad include prefix. + # Put Tongsuo first so Boost's -I prefix cannot shadow its NTLS headers. + set_target_properties(iotdb_session PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) + target_include_directories(iotdb_session BEFORE PRIVATE "${OPENSSL_INCLUDE_DIR}") +endif() + if(APPLE) target_link_libraries(iotdb_session PRIVATE "-Wl,-force_load,${THRIFT_STATIC_LIB_PATH}") elseif(UNIX AND NOT MSVC) @@ -194,8 +214,14 @@ else() endif() if(WITH_SSL) - target_link_libraries(iotdb_session PUBLIC OpenSSL::SSL OpenSSL::Crypto) target_compile_definitions(iotdb_session PUBLIC WITH_SSL=1) + if(IOTDB_NTLS_PROVIDER STREQUAL "GMSSL") + target_link_libraries(iotdb_session PRIVATE IoTDB::gmssl) + target_compile_definitions(iotdb_session PUBLIC IOTDB_NTLS_PROVIDER_GMSSL=1) + else() + target_link_libraries(iotdb_session PUBLIC OpenSSL::SSL OpenSSL::Crypto) + target_compile_definitions(iotdb_session PUBLIC IOTDB_NTLS_PROVIDER_TONGSUO=1) + endif() else() target_compile_definitions(iotdb_session PUBLIC WITH_SSL=0) endif() @@ -217,6 +243,7 @@ include(GNUInstallDirs) set(IOTDB_PUBLIC_HEADERS Export.h SessionConfig.h + SslConfig.h Session.h Common.h Optional.h @@ -240,8 +267,8 @@ install(TARGETS iotdb_session LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) -# Ship the OpenSSL shared libraries we link against next to iotdb_session so the -# packaged SDK is self-contained on machines without a system OpenSSL. +# Ship the Tongsuo shared libraries we link against next to iotdb_session so the +# packaged SDK is self-contained on machines without a system SSL library. if(WITH_SSL) iotdb_install_openssl_runtime() endif() @@ -296,6 +323,9 @@ file(WRITE "${CMAKE_BINARY_DIR}/package-metadata/BUILD-INFO.txt" "cmake.generator=${CMAKE_GENERATOR}\n" "cmake.build.type=${CMAKE_BUILD_TYPE}\n" "with.ssl=${WITH_SSL}\n" + "ntls.provider=${IOTDB_NTLS_PROVIDER}\n" + "tongsuo.git.ref=${TONGSUO_GIT_REF}\n" + "tongsuo.sha256=${TONGSUO_SHA256}\n" "iotdb.offline=${IOTDB_OFFLINE}\n" "iotdb.use.cxx11.abi=${IOTDB_USE_CXX11_ABI}\n" "iotdb.extra.cxx.flags=${IOTDB_EXTRA_CXX_FLAGS}\n") @@ -307,7 +337,6 @@ install(FILES "${CMAKE_BINARY_DIR}/package-metadata/VERSION" "${CMAKE_BINARY_DIR}/package-metadata/BUILD-INFO.txt" DESTINATION .) - if(BUILD_TESTING) enable_testing() add_subdirectory(test) @@ -317,6 +346,7 @@ endif() message(STATUS "iotdb_session configuration summary:") message(STATUS " WITH_SSL = ${WITH_SSL}") +message(STATUS " IOTDB_NTLS_PROVIDER = ${IOTDB_NTLS_PROVIDER}") message(STATUS " BUILD_TESTING = ${BUILD_TESTING}") message(STATUS " IOTDB_OFFLINE = ${IOTDB_OFFLINE}") message(STATUS " IOTDB_USE_CXX11_ABI = ${IOTDB_USE_CXX11_ABI}") diff --git a/iotdb-client/client-cpp/README.md b/iotdb-client/client-cpp/README.md index 852c38d447a1..c279d2f7f814 100644 --- a/iotdb-client/client-cpp/README.md +++ b/iotdb-client/client-cpp/README.md @@ -367,6 +367,8 @@ pass them as Maven properties (the POM maps them to `-D` options for CMake): | CMake variable | Maven property (`-D...`) | |----------------|--------------------------| | `WITH_SSL` | `with.ssl` (e.g. `-Dwith.ssl=ON`) | +| `IOTDB_NTLS_PROVIDER` | `ntls.provider` (`TONGSUO` or `GMSSL`) | +| `IOTDB_GMSSL_ROOT_DIR` | `gmssl.root.dir` | | `IOTDB_OFFLINE` | `iotdb.offline` | | `BUILD_TESTING` | `build.tests` | | `IOTDB_DEPS_DIR` | `iotdb.deps.dir` | @@ -378,15 +380,18 @@ etc. directly. | Option | Default | Purpose | |-----------------------|----------------------------------|----------------------------------------------------------------------------------------------------------| -| `WITH_SSL` | `ON` | Link against OpenSSL and bundle its runtime libraries. See *SSL* below. | +| `WITH_SSL` | `ON` | Link against Tongsuo (OpenSSL-compatible) and bundle its runtime libraries. See *SSL* below. | | `BUILD_TESTING` | `OFF` (Maven sets `ON` for verify) | Build Catch2 IT executables (Catch2 v2.13.7 header downloaded at configure time). | | `CATCH2_INCLUDE_DIR` | (unset) | Pre-downloaded Catch2 include dir (Maven sets this under `target/test/catch2`). | | `IOTDB_OFFLINE` | `OFF` | Disallow any network access during configure. | | `IOTDB_DEPS_DIR` | `/third-party` | Override the local tarball cache directory. | | `BOOST_VERSION` | `1.60.0` (`1.84.0` on macOS) | Boost version that CMake will look for / download. | | `THRIFT_VERSION` | `0.24.0` | Apache Thrift version to build from source. | +| `IOTDB_NTLS_PROVIDER` | `TONGSUO` | NTLS provider: `TONGSUO` or `GMSSL`. | +| `TONGSUO_GIT_REF` | commit `0aed892c` | Pinned Tongsuo 8.4-stable commit built from source when `WITH_SSL=ON`. | +| `TONGSUO_SHA256` | pinned archive hash | SHA-256 used to verify the Tongsuo source archive and offline cache. | +| `IOTDB_GMSSL_ROOT_DIR` | (unset) | Preinstalled GmSSL 3 root required by the `GMSSL` provider. | | `BOOST_ROOT` | (unset) | Existing Boost install to reuse, equivalent to `-Dboost.include.dir=...` from the legacy build. | -| `OPENSSL_ROOT_DIR` | (unset) | Existing OpenSSL install when `WITH_SSL=ON`. | | `CMAKE_INSTALL_PREFIX`| `/install` | Install location. | | `CMAKE_BUILD_TYPE` | `Release` | Single-config generator build type. Use `Debug` to produce a debug library. | @@ -427,8 +432,8 @@ cmake --build build --config Release --target install | Platform | Required files | |------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| - | `linux/` | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, `m4-1.4.19.tar.gz`, `flex-2.6.4.tar.gz`, `bison-3.8.tar.gz` (and `openssl-3.5.0.tar.gz` only when `WITH_SSL=ON` and no system OpenSSL is present) | - | `mac/` | `thrift-0.24.0.tar.gz`, `boost_1_84_0.tar.gz` (newer Boost for Xcode/Clang; Apple ships m4/flex/bison; `openssl-3.5.0.tar.gz` optional) | + | `linux/` | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, `m4-1.4.19.tar.gz`, `flex-2.6.4.tar.gz`, `bison-3.8.tar.gz`, `tongsuo-0aed892c5f48c9a52d1f5667667ae45156b9cdf4.tar.gz` | + | `mac/` | `thrift-0.24.0.tar.gz`, `boost_1_84_0.tar.gz`, `tongsuo-0aed892c5f48c9a52d1f5667667ae45156b9cdf4.tar.gz` (Apple ships m4/flex/bison) | | `windows/` | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz` (Boost headers only - no `b2` build required for `iotdb_session`) | Reference URLs (the configure step uses the same): @@ -437,7 +442,7 @@ cmake --build build --config Release --target install - GNU m4 1.4.19: - GNU flex 2.6.4: - GNU bison 3.8: - - OpenSSL 3.5.0: + - Tongsuo 8.4-stable commit `0aed892c`: 2. Run the build with offline mode enabled: @@ -492,11 +497,10 @@ Prerequisites: 2. **flex / bison.** Install and rename `win_flex.exe`→`flex.exe`, `win_bison.exe`→`bison.exe` on `PATH`. -3. **OpenSSL** *(`WITH_SSL=ON` is the default)*: install OpenSSL — e.g. - `choco install openssl`, or a Win64 OpenSSL installer from - — then pass - `-DOPENSSL_ROOT_DIR=...` to CMake if it is not auto-detected. Pass - `-DWITH_SSL=OFF` to build without SSL. +3. **Perl** (for building Tongsuo when `WITH_SSL=ON`). +4. **Tongsuo / SSL** *(`WITH_SSL=ON` is the default)*: a pinned Tongsuo 8.4-stable commit is + always built from source (requires Perl and `nmake` from the VS Developer + Command Prompt). Pass `-DWITH_SSL=OFF` to build without SSL. On Windows the SDK ships as **`iotdb_session.dll`** plus an import library **`iotdb_session.lib`**, built with **`/MD`** (dynamic CRT, same as a @@ -509,27 +513,125 @@ the GNU autotools tarballs assume a POSIX shell environment. ## SSL -`iotdb_session` builds **with OpenSSL by default** (`WITH_SSL=ON`). Disable -it with `-Dwith.ssl=OFF` (Maven) or `-DWITH_SSL=OFF` (standalone CMake). +`iotdb_session` builds with SSL/TLS by default. Supported NTLS providers: -OpenSSL **3.x** is used (Apache-2.0 licensed). Note that **OpenSSL 4.0 removed** -the legacy TLS-method APIs (`TLSv1_method`, `SSLv3_method`, …) that Apache -Thrift's `TSSLSocket` still calls, so install/point at a 3.x build, not 4.0. +- `TONGSUO` (default): pinned Tongsuo 8.4-stable commit `0aed892c`, built from source; TLS/TLCP and + PKCS12 or PEM credentials. +- `GMSSL`: preinstalled GmSSL 3.2 using its native TLCP API; TLCP with PEM + credentials. OCL is not used because it does not implement + the complete OpenSSL API required by Thrift. -CMake calls `find_package(OpenSSL)` and uses the system OpenSSL it finds. Its -shared libraries are **bundled into the package `lib/` directory** (next to -`iotdb_session`, which records an `$ORIGIN`/`@loader_path` runtime path) so the -published SDK is self-contained. +Select GmSSL with +`-DIOTDB_NTLS_PROVIDER=GMSSL -DIOTDB_GMSSL_ROOT_DIR=`. Provider runtime +libraries are bundled into the package `lib/` directory. GmSSL supports TLCP +only: set `sslProtocol("TLCP")`. PKCS12 `keyStore` is rejected; mutual +authentication requires the TLCP PEM certificate and private-key setters. +CMake probes ABI-affecting `ENABLE_*` symbols and validates the GmSSL 3.2 +headers/library pair during configuration. -Fallbacks: +Host prerequisites for the default `TONGSUO` provider: -- **Linux / macOS** – when no system OpenSSL is found (or - `-DIOTDB_OPENSSL_FROM_SOURCE=ON`, which the Linux packaging build uses so the - AlmaLinux 8 baseline's OpenSSL 1.1.1 is never redistributed), build - `openssl-3.5.0.tar.gz` from source as **shared** libraries and bundle them. -- **Windows** – fail with a friendly message; install a prebuilt OpenSSL 3.x - (e.g. the FireDaemon or slproweb 3.5.x zip) and set `-DOPENSSL_ROOT_DIR=...`. - Building OpenSSL from source via MSVC is out of scope. +- **Linux / macOS** – `perl`, `make`, and a C compiler (Tongsuo `./config`). +- **Windows** – Perl (e.g. Strawberry Perl) and `nmake` from the Visual Studio + Developer Command Prompt. + +### Client SSL / TLCP configuration + +The C++ client mirrors the Java Session API. `trustStore` accepts PKCS12 or PEM +with Tongsuo and PEM with GmSSL. JKS files must be converted first +(the C++ client does not parse JKS). + +**TLS one-way (server authentication):** + +```cpp +#include "SessionBuilder.h" + +auto session = SessionBuilder() + .host("127.0.0.1") + ->rpcPort(6667) + ->username("root") + ->password("root") + ->useSSL(true) + ->sslProtocol("TLS") + ->trustStore("/path/to/truststore.p12") + ->trustStorePwd("thrift") + ->build(); +``` + +**TLS mutual authentication:** + +```cpp +auto session = SessionBuilder() + .host("127.0.0.1") + ->rpcPort(6667) + ->useSSL(true) + ->sslProtocol("TLS") + ->trustStore("/path/to/truststore.p12") + ->trustStorePwd("thrift") + ->keyStore("/path/to/keystore.p12") + ->keyStorePwd("thrift") + ->build(); +``` + +**TLCP one-way (NTLS, GM/T):** + +```cpp +auto session = SessionBuilder() + .host("127.0.0.1") + ->rpcPort(6667) + ->useSSL(true) + ->sslProtocol("TLCP") + ->trustStore("/path/to/ca.p12") + ->trustStorePwd("thrift") + ->build(); +``` + +**TLCP mutual authentication** (dual SM2 certificates in PKCS12 `keyStore`): + +```cpp +auto session = SessionBuilder() + .host("127.0.0.1") + ->rpcPort(6667) + ->useSSL(true) + ->sslProtocol("TLCP") + ->trustStore("/path/to/ca.p12") + ->trustStorePwd("thrift") + ->keyStore("/path/to/client-dual.p12") + ->keyStorePwd("thrift") + ->build(); +``` + +For Tongsuo PEM, put signing/encryption certificates (then the CA chain) in one +file and both private keys in another. For GmSSL, use the client signing +certificate followed by its intermediate chain, plus the matching single +private key: + +```cpp +auto session = SessionBuilder() + .host("127.0.0.1") + ->rpcPort(6667) + ->useSSL(true) + ->sslProtocol("TLCP") + ->trustStore("/path/to/ca.pem") + ->tlcpCertChainFile("/path/to/client-certs.pem") + ->tlcpPrivateKeyFile("/path/to/client-keys.pem") + ->tlcpPrivateKeyPwd("secret") + ->build(); +``` + +The legacy `trustCertFilePath()` setter still works as an alias for a PEM CA +file when `trustStore` is not set. + +**C API** (configure before `ts_session_open` / `ts_table_session_open`): + +```c +CSession* session = ts_session_new("127.0.0.1", 6667, "root", "root"); +ts_session_set_use_ssl(session, true); +ts_session_set_ssl_protocol(session, "TLCP"); +ts_session_set_trust_store(session, "/path/to/ca.p12", "thrift"); +ts_session_set_key_store(session, "/path/to/client-dual.p12", "thrift"); +ts_session_open(session); +``` ## Tests diff --git a/iotdb-client/client-cpp/README_zh.md b/iotdb-client/client-cpp/README_zh.md index 7cd060c7d7c6..d1da12eb38b1 100644 --- a/iotdb-client/client-cpp/README_zh.md +++ b/iotdb-client/client-cpp/README_zh.md @@ -237,17 +237,105 @@ Maven 构建会把 SDK 安装到 `target/install/`,并生成 | CMake 变量 | Maven 属性 | |------------|------------| | `WITH_SSL` | `with.ssl`(默认 `ON`,关闭用 `-Dwith.ssl=OFF`) | +| `IOTDB_NTLS_PROVIDER` | `ntls.provider`(`TONGSUO` 或 `GMSSL`) | +| `TONGSUO_GIT_REF` | `tongsuo.git.ref`(默认固定到 commit `0aed892c`) | +| `TONGSUO_SHA256` | `tongsuo.sha256`(Tongsuo 源码归档校验值) | +| `IOTDB_GMSSL_ROOT_DIR` | `gmssl.root.dir` | | `IOTDB_OFFLINE` | `iotdb.offline` | | `BUILD_TESTING` | `build.tests` | | `IOTDB_DEPS_DIR` | `iotdb.deps.dir` | | `BOOST_INCLUDEDIR` | `boost.include.dir` | | `CMAKE_BUILD_TYPE` | `cmake.build.type`,例如 `-Dcmake.build.type=Debug` | -SSL 默认开启(`WITH_SSL=ON`)。所捆绑的 Apache Thrift 0.24 同时支持 OpenSSL 1.x -与 3.x,因此直接使用系统的 OpenSSL(任意版本)。CMake 通过 `find_package(OpenSSL)` -解析系统 OpenSSL,找不到时回退到从源码构建 OpenSSL 3.5.0;并会把所用的 OpenSSL -动态库一并复制到产物 `lib/` 目录。Windows 可用 `choco install openssl` 安装。 +SSL 默认开启(`WITH_SSL=ON`)。支持的 NTLS Provider: + +- `TONGSUO`(默认):源码构建固定到 commit `0aed892c` 的 Tongsuo 8.4-stable,支持 TLS/TLCP 及 + PKCS12、PEM 凭据。 +- `GMSSL`:使用预安装的 GmSSL 3.2 原生 TLCP API,支持 TLCP 及 PEM 凭据。 + OCL 未实现 Thrift 所需的完整 OpenSSL API,因此不使用 OCL。 + +选择 GmSSL 时传入 +`-DIOTDB_NTLS_PROVIDER=GMSSL -DIOTDB_GMSSL_ROOT_DIR=`。 +Provider 动态库会复制到产物 `lib/` 目录。GmSSL 仅支持 TLCP,必须设置 +`sslProtocol("TLCP")`;PKCS12 `keyStore` 会被拒绝,双向认证需使用 TLCP PEM +证书和私钥 setter。配置阶段会根据库符号自动推导影响 ABI 的 `ENABLE_*` +定义,并校验 GmSSL 3.2 的头文件/动态库 ABI。Tongsuo 在 Windows 上需要 +Perl 与 VS 的 `nmake`。 直接使用 CMake 时传入 `-DWITH_SSL=OFF`、`-DIOTDB_OFFLINE=ON` 等即可。 + +### 客户端 SSL / TLCP 配置 + +C++ 客户端 API 与 Java Session 对齐。Tongsuo 的 `trustStore` 支持 PKCS12 或 PEM, +GmSSL 使用 PEM;C++ 端不解析 JKS。 + +**TLS 单向认证:** + +```cpp +auto session = SessionBuilder() + .host("127.0.0.1") + ->rpcPort(6667) + ->useSSL(true) + ->sslProtocol("TLS") + ->trustStore("/path/to/truststore.p12") + ->trustStorePwd("thrift") + ->build(); +``` + +**TLCP 单向认证(国密 NTLS):** + +```cpp +auto session = SessionBuilder() + .host("127.0.0.1") + ->rpcPort(6667) + ->useSSL(true) + ->sslProtocol("TLCP") + ->trustStore("/path/to/ca.p12") + ->trustStorePwd("thrift") + ->build(); +``` + +**TLCP 双向认证**(PKCS12 `keyStore` 内含 SM2 签名/加密双证书): + +```cpp +auto session = SessionBuilder() + .host("127.0.0.1") + ->rpcPort(6667) + ->useSSL(true) + ->sslProtocol("TLCP") + ->trustStore("/path/to/ca.p12") + ->trustStorePwd("thrift") + ->keyStore("/path/to/client-dual.p12") + ->keyStorePwd("thrift") + ->build(); +``` + +Tongsuo 使用 PEM 时,将签名/加密证书及 CA 链合并到一个文件,并将两个私钥合并到另一个文件。 +GmSSL 则使用客户端签名证书及其中间证书链,以及对应的单个私钥: + +```cpp +auto session = SessionBuilder() + .host("127.0.0.1") + ->rpcPort(6667) + ->useSSL(true) + ->sslProtocol("TLCP") + ->trustStore("/path/to/ca.pem") + ->tlcpCertChainFile("/path/to/client-certs.pem") + ->tlcpPrivateKeyFile("/path/to/client-keys.pem") + ->tlcpPrivateKeyPwd("secret") + ->build(); +``` + +旧版 `trustCertFilePath()` 在未设置 `trustStore` 时仍可作为 PEM CA 路径使用。 + +**C API**(在 `ts_session_open` / `ts_table_session_open` 之前配置): + +```c +ts_session_set_use_ssl(session, true); +ts_session_set_ssl_protocol(session, "TLCP"); +ts_session_set_trust_store(session, "/path/to/ca.p12", "thrift"); +ts_session_set_key_store(session, "/path/to/client-dual.p12", "thrift"); +``` + Debug 构建请在配置阶段传入 `-DCMAKE_BUILD_TYPE=Debug`。Windows 使用 Visual Studio 生成器时也需要传入该选项,以便内置 Thrift 静态库使用 Debug MSVC 运行时; 随后用 `cmake --build build --config Debug --target install` 构建安装。 diff --git a/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake b/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake index 26c24ba6c2f8..6c93237a8c29 100644 --- a/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake +++ b/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake @@ -18,81 +18,79 @@ # ============================================================================= # FetchOpenSSL.cmake (only included when WITH_SSL=ON) # -# Apache Thrift 0.24 (bundled by this client) builds against OpenSSL 1.x and 3.x, -# so any system OpenSSL is used as-is, whatever its version. -# -# Resolution order: -# 1. find_package(OpenSSL) - any system / vendor install is taken as-is. -# 2. On Linux/macOS, when no system OpenSSL is present: -# use tarball ${IOTDB_OS_DEPS_DIR}/openssl-${OPENSSL_FALLBACK_VERSION}.tar.gz -# or download from openssl.org when not in offline mode, then -# ./config && make && make install_sw into ${CMAKE_BINARY_DIR}/_deps/openssl. -# 3. On Windows: emit a FATAL_ERROR asking for a prebuilt OpenSSL; building -# OpenSSL from source on MSVC is out of scope. +# Resolves the selected NTLS provider. Tongsuo is built from source for +# Thrift TSSLSocket; GmSSL uses a preinstalled native TLCP library. # # Side effects: -# Defines imported targets OpenSSL::SSL / OpenSSL::Crypto via find_package -# so callers can just link against them. +# TONGSUO defines OpenSSL::SSL / OpenSSL::Crypto; GMSSL defines IoTDB::gmssl. +# IOTDB_NTLS_RUNTIME_LIBRARIES lists the selected provider's runtime files. # ============================================================================= -# Version built from source when no system OpenSSL is found. Named distinctly -# from find_package's OPENSSL_VERSION output variable to avoid collisions. -set(OPENSSL_FALLBACK_VERSION "3.5.0" - CACHE STRING "OpenSSL version built from source when no system OpenSSL is found") - -# Build OpenSSL from source even if a system one exists. Used by the Linux -# packaging build, whose AlmaLinux 8 baseline ships OpenSSL 1.1.1 (EOL, not -# Apache-2.0, must not be redistributed) - we build 3.x there instead. -option(IOTDB_OPENSSL_FROM_SOURCE - "Ignore any system OpenSSL and build OpenSSL ${OPENSSL_FALLBACK_VERSION} from source" OFF) - -if(NOT IOTDB_OPENSSL_FROM_SOURCE) - find_package(OpenSSL QUIET) - if(OpenSSL_FOUND) - message(STATUS "[OpenSSL] using system OpenSSL ${OPENSSL_VERSION}") - return() - endif() +# --- Default provider: build Tongsuo ${TONGSUO_GIT_REF} from source --- +if(IOTDB_NTLS_PROVIDER STREQUAL "TONGSUO") +string(LENGTH "${TONGSUO_GIT_REF}" _tongsuo_git_ref_length) +if(TONGSUO_GIT_REF MATCHES "^[0-9a-fA-F]+$" + AND _tongsuo_git_ref_length GREATER_EQUAL 7 + AND _tongsuo_git_ref_length LESS_EQUAL 40) + set(_tongsuo_extracted_dir "Tongsuo-${TONGSUO_GIT_REF}") + set(_tongsuo_url "https://github.com/Tongsuo-Project/Tongsuo/archive/${TONGSUO_GIT_REF}.tar.gz") +else() + set(_tongsuo_extracted_dir "Tongsuo-${TONGSUO_GIT_REF}") + set(_tongsuo_url + "https://github.com/Tongsuo-Project/Tongsuo/archive/refs/heads/${TONGSUO_GIT_REF}.tar.gz") endif() -if(WIN32) +set(_tongsuo_tarname "tongsuo-${TONGSUO_GIT_REF}.tar.gz") +set(_tongsuo_tarball "${IOTDB_OS_DEPS_DIR}/${_tongsuo_tarname}") + +string(LENGTH "${TONGSUO_SHA256}" _tongsuo_sha256_length) +if(NOT TONGSUO_SHA256 MATCHES "^[0-9a-fA-F]+$" OR NOT _tongsuo_sha256_length EQUAL 64) message(FATAL_ERROR - "[OpenSSL] WITH_SSL=ON but no OpenSSL was found on Windows. " - "Please install a prebuilt OpenSSL (e.g. 'choco install openssl'), " - "then re-run the configure step with -DOPENSSL_ROOT_DIR=. " - "Pass -DWITH_SSL=OFF to build without SSL.") + "[Tongsuo] TONGSUO_SHA256 must be the 64-character SHA-256 of ${_tongsuo_tarname}") endif() -# --- Linux / macOS: build OpenSSL ${OPENSSL_FALLBACK_VERSION} from source - -set(_ossl_tarname "openssl-${OPENSSL_FALLBACK_VERSION}.tar.gz") -set(_ossl_tarball "${IOTDB_OS_DEPS_DIR}/${_ossl_tarname}") +if(EXISTS "${_tongsuo_tarball}") + file(SHA256 "${_tongsuo_tarball}" _tongsuo_cached_sha256) + if(NOT "${_tongsuo_cached_sha256}" STREQUAL "${TONGSUO_SHA256}") + if(IOTDB_OFFLINE) + message(FATAL_ERROR + "[Tongsuo] cached ${_tongsuo_tarname} has SHA-256 ${_tongsuo_cached_sha256}; " + "expected ${TONGSUO_SHA256}") + endif() + message(STATUS "[Tongsuo] removing cached archive with mismatched SHA-256") + file(REMOVE "${_tongsuo_tarball}") + endif() +endif() -if(NOT EXISTS "${_ossl_tarball}") +if(NOT EXISTS "${_tongsuo_tarball}") if(IOTDB_OFFLINE) message(FATAL_ERROR - "[OpenSSL] IOTDB_OFFLINE=ON but ${_ossl_tarname} is missing in ${IOTDB_OS_DEPS_DIR}.") + "[Tongsuo] IOTDB_OFFLINE=ON but ${_tongsuo_tarname} is missing in ${IOTDB_OS_DEPS_DIR}.") endif() - set(_ossl_url "https://www.openssl.org/source/${_ossl_tarname}") - message(STATUS "[OpenSSL] downloading ${_ossl_url}") - file(DOWNLOAD "${_ossl_url}" "${_ossl_tarball}" - SHOW_PROGRESS TLS_VERIFY ON STATUS _st) + message(STATUS "[Tongsuo] downloading ${_tongsuo_url}") + file(DOWNLOAD "${_tongsuo_url}" "${_tongsuo_tarball}" + SHOW_PROGRESS TLS_VERIFY ON + TIMEOUT 600 + EXPECTED_HASH "SHA256=${TONGSUO_SHA256}" + STATUS _st) list(GET _st 0 _code) if(NOT _code EQUAL 0) list(GET _st 1 _msg) - file(REMOVE "${_ossl_tarball}") - message(FATAL_ERROR "[OpenSSL] download failed: ${_msg}") + file(REMOVE "${_tongsuo_tarball}") + message(FATAL_ERROR "[Tongsuo] download failed: ${_msg}") endif() endif() -set(_ossl_root "${CMAKE_BINARY_DIR}/_deps/openssl") -set(_ossl_src "${_ossl_root}/src/openssl-${OPENSSL_FALLBACK_VERSION}") -set(_ossl_inst "${_ossl_root}/install") -set(_ossl_stamp "${_ossl_root}/.built-${OPENSSL_FALLBACK_VERSION}") +set(_tongsuo_root "${CMAKE_BINARY_DIR}/_deps/tongsuo") +set(_tongsuo_src "${_tongsuo_root}/src/${_tongsuo_extracted_dir}") +set(_tongsuo_inst "${_tongsuo_root}/install") +set(_tongsuo_stamp "${_tongsuo_root}/.built-${TONGSUO_GIT_REF}-${TONGSUO_SHA256}") -if(NOT EXISTS "${_ossl_stamp}") - file(REMOVE_RECURSE "${_ossl_root}/src") - file(MAKE_DIRECTORY "${_ossl_root}/src") - message(STATUS "[OpenSSL] extracting ${_ossl_tarball}") - file(ARCHIVE_EXTRACT INPUT "${_ossl_tarball}" DESTINATION "${_ossl_root}/src") +if(NOT EXISTS "${_tongsuo_stamp}") + file(REMOVE_RECURSE "${_tongsuo_root}/src") + file(MAKE_DIRECTORY "${_tongsuo_root}/src") + message(STATUS "[Tongsuo] extracting ${_tongsuo_tarball}") + file(ARCHIVE_EXTRACT INPUT "${_tongsuo_tarball}" DESTINATION "${_tongsuo_root}/src") include(ProcessorCount) ProcessorCount(_jobs) @@ -100,38 +98,282 @@ if(NOT EXISTS "${_ossl_stamp}") set(_jobs 1) endif() - message(STATUS "[OpenSSL] configuring -> ${_ossl_inst}") - # ./config auto-detects the platform target. Build SHARED libraries - # (libssl.so.3 / libcrypto.so.3) so they can be bundled next to - # libiotdb_session and shipped as the SDK's OpenSSL runtime. - execute_process( - COMMAND ./config --prefix=${_ossl_inst} --openssldir=${_ossl_inst}/ssl shared - WORKING_DIRECTORY "${_ossl_src}" - RESULT_VARIABLE _rc) - if(NOT _rc EQUAL 0) - message(FATAL_ERROR "[OpenSSL] config failed (rc=${_rc})") - endif() + if(WIN32) + # Git Bash ships a minimal MSYS perl that lacks modules required by + # Tongsuo/OpenSSL Configure (e.g. Locale::Maketext::Simple). Prefer + # Strawberry Perl installed by CI (choco) or local dev machines. + set(_strawberry_perl "C:/Strawberry/perl/bin/perl.exe") + if(EXISTS "${_strawberry_perl}") + set(PERL_EXECUTABLE "${_strawberry_perl}") + else() + find_program(PERL_EXECUTABLE NAMES perl.exe perl REQUIRED) + endif() + message(STATUS "[Tongsuo] using Perl: ${PERL_EXECUTABLE}") + find_program(NMAKE_EXECUTABLE nmake) + if(NOT NMAKE_EXECUTABLE AND CMAKE_CXX_COMPILER) + get_filename_component(_msvc_bin_dir "${CMAKE_CXX_COMPILER}" DIRECTORY) + find_program(NMAKE_EXECUTABLE nmake PATHS "${_msvc_bin_dir}" NO_DEFAULT_PATH) + endif() + if(NOT NMAKE_EXECUTABLE AND DEFINED ENV{VCINSTALLDIR}) + file(GLOB _nmake_candidates "$ENV{VCINSTALLDIR}/Tools/MSVC/*/bin/Hostx64/x64/nmake.exe") + if(_nmake_candidates) + list(GET _nmake_candidates 0 NMAKE_EXECUTABLE) + endif() + endif() + if(NOT NMAKE_EXECUTABLE) + file(GLOB _nmake_candidates + "C:/Program Files (x86)/Microsoft Visual Studio/2017/*/VC/Tools/MSVC/*/bin/Hostx64/x64/nmake.exe" + "C:/Program Files/Microsoft Visual Studio/2022/*/VC/Tools/MSVC/*/bin/Hostx64/x64/nmake.exe" + "C:/Program Files/Microsoft Visual Studio/18/*/VC/Tools/MSVC/*/bin/Hostx64/x64/nmake.exe") + if(_nmake_candidates) + list(SORT _nmake_candidates COMPARE NATURAL ORDER DESCENDING) + list(GET _nmake_candidates 0 NMAKE_EXECUTABLE) + endif() + endif() + if(NOT NMAKE_EXECUTABLE) + message(FATAL_ERROR "[Tongsuo] nmake not found (install VS Build Tools or run from Developer Command Prompt)") + endif() + message(STATUS "[Tongsuo] using nmake: ${NMAKE_EXECUTABLE}") + set(_vcvars "") + if(CMAKE_CXX_COMPILER) + get_filename_component(_cl_exe "${CMAKE_CXX_COMPILER}" REALPATH) + set(_vc_dir "${_cl_exe}") + foreach(_unused RANGE 6) + get_filename_component(_vc_dir "${_vc_dir}" DIRECTORY) + endforeach() + set(_vcvars "${_vc_dir}/Auxiliary/Build/vcvars64.bat") + elseif(DEFINED ENV{VCINSTALLDIR}) + set(_vcvars "$ENV{VCINSTALLDIR}/Auxiliary/Build/vcvars64.bat") + else() + get_filename_component(_nmake_dir "${NMAKE_EXECUTABLE}" DIRECTORY) + set(_vc_dir "${_nmake_dir}") + foreach(_unused RANGE 6) + get_filename_component(_vc_dir "${_vc_dir}" DIRECTORY) + endforeach() + set(_vcvars "${_vc_dir}/Auxiliary/Build/vcvars64.bat") + endif() + if(NOT EXISTS "${_vcvars}") + message(FATAL_ERROR "[Tongsuo] vcvars64.bat not found (CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER})") + endif() + file(TO_NATIVE_PATH "${_vcvars}" _vcvars_native) + file(TO_NATIVE_PATH "${NMAKE_EXECUTABLE}" _nmake_native) + file(TO_NATIVE_PATH "${_tongsuo_src}" _tongsuo_src_native) + set(_nmake_build_bat "${_tongsuo_root}/tongsuo-nmake-build.bat") + set(_nmake_install_bat "${_tongsuo_root}/tongsuo-nmake-install.bat") + file(WRITE "${_nmake_build_bat}" "@echo off\r\n") + file(APPEND "${_nmake_build_bat}" "call \"${_vcvars_native}\" amd64\r\n") + file(APPEND "${_nmake_build_bat}" "if errorlevel 1 exit /b 1\r\n") + file(APPEND "${_nmake_build_bat}" "cd /d \"${_tongsuo_src_native}\"\r\n") + file(APPEND "${_nmake_build_bat}" "\"${_nmake_native}\"\r\n") + file(APPEND "${_nmake_build_bat}" "exit /b %ERRORLEVEL%\r\n") + file(WRITE "${_nmake_install_bat}" "@echo off\r\n") + file(APPEND "${_nmake_install_bat}" "call \"${_vcvars_native}\" amd64\r\n") + file(APPEND "${_nmake_install_bat}" "if errorlevel 1 exit /b 1\r\n") + file(APPEND "${_nmake_install_bat}" "cd /d \"${_tongsuo_src_native}\"\r\n") + file(APPEND "${_nmake_install_bat}" "\"${_nmake_native}\" install_sw\r\n") + file(APPEND "${_nmake_install_bat}" "exit /b %ERRORLEVEL%\r\n") + set(_tongsuo_target "VC-WIN64A") + message(STATUS "[Tongsuo] configuring (${_tongsuo_target}) -> ${_tongsuo_inst}") + execute_process( + COMMAND "${CMAKE_COMMAND}" -E env "CC=cl" "CXX=cl" + "${PERL_EXECUTABLE}" Configure enable-ntls no-asm ${_tongsuo_target} + --prefix=${_tongsuo_inst} + --openssldir=${_tongsuo_inst}/ssl + WORKING_DIRECTORY "${_tongsuo_src}" + RESULT_VARIABLE _rc) + if(NOT _rc EQUAL 0) + message(FATAL_ERROR "[Tongsuo] Configure failed (rc=${_rc})") + endif() - message(STATUS "[OpenSSL] building (-j${_jobs})") - execute_process( - COMMAND make -j${_jobs} - WORKING_DIRECTORY "${_ossl_src}" - RESULT_VARIABLE _rc) - if(NOT _rc EQUAL 0) - message(FATAL_ERROR "[OpenSSL] make failed (rc=${_rc})") - endif() + message(STATUS "[Tongsuo] building") + execute_process( + COMMAND "${_nmake_build_bat}" + RESULT_VARIABLE _rc) + if(NOT _rc EQUAL 0) + message(FATAL_ERROR "[Tongsuo] nmake failed (rc=${_rc})") + endif() - execute_process( - COMMAND make install_sw - WORKING_DIRECTORY "${_ossl_src}" - RESULT_VARIABLE _rc) - if(NOT _rc EQUAL 0) - message(FATAL_ERROR "[OpenSSL] make install_sw failed (rc=${_rc})") + execute_process( + COMMAND "${_nmake_install_bat}" + RESULT_VARIABLE _rc) + if(NOT _rc EQUAL 0) + message(FATAL_ERROR "[Tongsuo] nmake install_sw failed (rc=${_rc})") + endif() + else() + message(STATUS "[Tongsuo] configuring -> ${_tongsuo_inst}") + execute_process( + COMMAND ./config --prefix=${_tongsuo_inst} --openssldir=${_tongsuo_inst}/ssl shared enable-ntls + WORKING_DIRECTORY "${_tongsuo_src}" + RESULT_VARIABLE _rc) + if(NOT _rc EQUAL 0) + message(FATAL_ERROR "[Tongsuo] config failed (rc=${_rc})") + endif() + + message(STATUS "[Tongsuo] building (-j${_jobs})") + execute_process( + COMMAND make -j${_jobs} + WORKING_DIRECTORY "${_tongsuo_src}" + RESULT_VARIABLE _rc) + if(NOT _rc EQUAL 0) + message(FATAL_ERROR "[Tongsuo] make failed (rc=${_rc})") + endif() + + execute_process( + COMMAND make install_sw + WORKING_DIRECTORY "${_tongsuo_src}" + RESULT_VARIABLE _rc) + if(NOT _rc EQUAL 0) + message(FATAL_ERROR "[Tongsuo] make install_sw failed (rc=${_rc})") + endif() endif() - file(TOUCH "${_ossl_stamp}") + file(TOUCH "${_tongsuo_stamp}") endif() -set(OPENSSL_ROOT_DIR "${_ossl_inst}" CACHE PATH "OpenSSL root" FORCE) +set(OPENSSL_ROOT_DIR "${_tongsuo_inst}" CACHE PATH "Tongsuo install root" FORCE) set(OPENSSL_USE_STATIC_LIBS OFF) +# Do not reuse paths cached by an earlier configure that resolved the system +# OpenSSL. WITH_SSL requires Tongsuo because RpcSslUtils uses its TLCP APIs. +# +# OPENSSL_ROOT_DIR alone is not sufficient on macOS: Homebrew's /usr/local/include +# can still win FindOpenSSL's header search even while the libraries are resolved +# from OPENSSL_ROOT_DIR. That produces an unusable system-header/Tongsuo-library +# combination, so pin the headers to the Tongsuo installation as well. +set(OPENSSL_INCLUDE_DIR "${_tongsuo_inst}/include" + CACHE PATH "Tongsuo include directory" FORCE) +unset(OPENSSL_SSL_LIBRARY CACHE) +unset(OPENSSL_CRYPTO_LIBRARY CACHE) find_package(OpenSSL REQUIRED) -message(STATUS "[OpenSSL] built locally (shared) at ${OPENSSL_ROOT_DIR}") +get_filename_component(_tongsuo_expected_include "${_tongsuo_inst}/include" REALPATH) +get_filename_component(_tongsuo_resolved_include "${OPENSSL_INCLUDE_DIR}" REALPATH) +if(NOT _tongsuo_resolved_include STREQUAL _tongsuo_expected_include) + message(FATAL_ERROR + "[Tongsuo] FindOpenSSL selected headers from ${OPENSSL_INCLUDE_DIR}; " + "expected ${_tongsuo_inst}/include") +endif() +set(IOTDB_NTLS_RUNTIME_LIBRARIES + "${OPENSSL_SSL_LIBRARY};${OPENSSL_CRYPTO_LIBRARY}" + CACHE INTERNAL "NTLS provider runtime libraries" FORCE) +message(STATUS "[Tongsuo] built from source (shared) at ${OPENSSL_ROOT_DIR}") + +# --- Alternative provider: use preinstalled GmSSL 3 through its native TLCP API --- +elseif(IOTDB_NTLS_PROVIDER STREQUAL "GMSSL") + if(NOT IOTDB_GMSSL_ROOT_DIR OR NOT IS_DIRECTORY "${IOTDB_GMSSL_ROOT_DIR}") + message(FATAL_ERROR + "[GmSSL] IOTDB_GMSSL_ROOT_DIR must point to a preinstalled GmSSL 3.2") + endif() + + unset(_gmssl_include_dir CACHE) + unset(_gmssl_library CACHE) + find_path(_gmssl_include_dir gmssl/tls.h + PATHS "${IOTDB_GMSSL_ROOT_DIR}/include" NO_DEFAULT_PATH REQUIRED) + find_library(_gmssl_library NAMES gmssl libgmssl + PATHS "${IOTDB_GMSSL_ROOT_DIR}/lib" "${IOTDB_GMSSL_ROOT_DIR}/lib64" + NO_DEFAULT_PATH REQUIRED) + + include(CMakePushCheckState) + include(CheckCXXSourceCompiles) + include(CheckCXXSourceRuns) + cmake_push_check_state(RESET) + set(CMAKE_REQUIRED_INCLUDES "${_gmssl_include_dir}") + set(CMAKE_REQUIRED_LIBRARIES "${_gmssl_library}") + if(WIN32) + list(APPEND CMAKE_REQUIRED_LIBRARIES ws2_32) + set(_gmssl_saved_path "$ENV{PATH}") + set(ENV{PATH} "${IOTDB_GMSSL_ROOT_DIR}/bin;$ENV{PATH}") + else() + set(_gmssl_saved_library_path "$ENV{LD_LIBRARY_PATH}") + set(ENV{LD_LIBRARY_PATH} + "${IOTDB_GMSSL_ROOT_DIR}/lib:${IOTDB_GMSSL_ROOT_DIR}/lib64:$ENV{LD_LIBRARY_PATH}") + endif() + + set(_gmssl_compile_definitions "") + macro(_iotdb_probe_gmssl_abi_definition _definition _symbol) + string(MAKE_C_IDENTIFIER + "IOTDB_GMSSL_HAS_${_definition}_${_symbol}" _probe_variable) + unset(${_probe_variable} CACHE) + check_cxx_source_compiles( + "extern \"C\" void ${_symbol}();\nint main() { ${_symbol}(); return 0; }" + ${_probe_variable}) + if(${_probe_variable}) + list(APPEND _gmssl_compile_definitions "${_definition}") + endif() + endmacro() + _iotdb_probe_gmssl_abi_definition(ENABLE_SHA1 sha1_init) + _iotdb_probe_gmssl_abi_definition(ENABLE_SHA2 sha256_init) + _iotdb_probe_gmssl_abi_definition(ENABLE_AES aes_set_encrypt_key) + _iotdb_probe_gmssl_abi_definition(ENABLE_SECP256R1 x509_key_set_secp256r1_key) + _iotdb_probe_gmssl_abi_definition(ENABLE_LMS x509_key_set_lms_key) + _iotdb_probe_gmssl_abi_definition(ENABLE_XMSS x509_key_set_xmss_key) + _iotdb_probe_gmssl_abi_definition(ENABLE_SPHINCS x509_key_set_sphincs_key) + _iotdb_probe_gmssl_abi_definition(ENABLE_KYBER x509_key_set_kyber_key) + _iotdb_probe_gmssl_abi_definition(ENABLE_SM9 x509_key_set_sm9_sign_key) + unset(_iotdb_probe_gmssl_abi_definition) + message(STATUS "[GmSSL] detected ABI definitions: ${_gmssl_compile_definitions}") + + foreach(_definition IN LISTS _gmssl_compile_definitions) + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D${_definition}") + endforeach() + unset(IOTDB_GMSSL_ABI_COMPATIBLE CACHE) + check_cxx_source_runs([=[ + #include + #include + #include + #include + #if GMSSL_VERSION_NUM < 30200 || GMSSL_VERSION_NUM >= 30300 + #error "IoTDB requires GmSSL 3.2.x" + #endif + struct GuardedContext { + TLS_CTX context; + std::uint64_t canary[8]; + }; + int main() { + GuardedContext guarded{}; + std::memset(guarded.canary, 0xA5, sizeof(guarded.canary)); + if (tls_ctx_init(&guarded.context, TLS_protocol_tlcp, 1) != 1) { + return 1; + } + const int cipher = TLS_cipher_ecc_sm4_cbc_sm3; + if (tls_ctx_set_cipher_suites(&guarded.context, &cipher, 1) != 1 || + guarded.context.is_client != 1 || + guarded.context.protocol != TLS_protocol_tlcp || + guarded.context.cipher_suites_cnt != 1 || + guarded.context.cipher_suites[0] != cipher) { + tls_ctx_cleanup(&guarded.context); + return 2; + } + const std::uint64_t expected = UINT64_C(0xA5A5A5A5A5A5A5A5); + for (std::uint64_t value : guarded.canary) { + if (value != expected) { + tls_ctx_cleanup(&guarded.context); + return 3; + } + } + tls_ctx_cleanup(&guarded.context); + return 0; + } + ]=] IOTDB_GMSSL_ABI_COMPATIBLE) + if(WIN32) + set(ENV{PATH} "${_gmssl_saved_path}") + else() + set(ENV{LD_LIBRARY_PATH} "${_gmssl_saved_library_path}") + endif() + cmake_pop_check_state() + if(NOT IOTDB_GMSSL_ABI_COMPATIBLE) + message(FATAL_ERROR + "[GmSSL] headers/library ABI check failed after probing its " + "ABI-affecting ENABLE_* symbols.") + endif() + + if(NOT TARGET IoTDB::gmssl) + add_library(IoTDB::gmssl UNKNOWN IMPORTED GLOBAL) + set_target_properties(IoTDB::gmssl PROPERTIES + IMPORTED_LOCATION "${_gmssl_library}" + INTERFACE_INCLUDE_DIRECTORIES "${_gmssl_include_dir}" + INTERFACE_COMPILE_DEFINITIONS "${_gmssl_compile_definitions}") + endif() + + set(IOTDB_NTLS_RUNTIME_LIBRARIES + "${_gmssl_library}" + CACHE INTERNAL "NTLS provider runtime libraries" FORCE) + message(STATUS "[GmSSL] using native GmSSL TLCP library ${_gmssl_library}") +endif() diff --git a/iotdb-client/client-cpp/cmake/FetchThrift.cmake b/iotdb-client/client-cpp/cmake/FetchThrift.cmake index d69b2a47ad9e..c543ec17814e 100644 --- a/iotdb-client/client-cpp/cmake/FetchThrift.cmake +++ b/iotdb-client/client-cpp/cmake/FetchThrift.cmake @@ -57,7 +57,9 @@ if(NOT EXISTS "${_thrift_tarball}") set(_thrift_url "https://archive.apache.org/dist/thrift/${THRIFT_VERSION}/${_thrift_tarname}") message(STATUS "[Thrift] downloading ${_thrift_url}") file(DOWNLOAD "${_thrift_url}" "${_thrift_tarball}" - SHOW_PROGRESS TLS_VERIFY ON STATUS _thrift_dl) + SHOW_PROGRESS TLS_VERIFY ON + TIMEOUT 600 + STATUS _thrift_dl) list(GET _thrift_dl 0 _code) if(NOT _code EQUAL 0) list(GET _thrift_dl 1 _msg) @@ -89,12 +91,27 @@ if(NOT EXISTS "${_thrift_marker}") file(TOUCH "${_thrift_marker}") endif() +# GitHub archives use thrift-, release tarballs use thrift-. +if(NOT EXISTS "${_thrift_src}/CMakeLists.txt") + file(GLOB _thrift_extracted "${_thrift_root}/src/thrift-*") + list(LENGTH _thrift_extracted _thrift_extracted_count) + if(_thrift_extracted_count EQUAL 1) + list(GET _thrift_extracted 0 _thrift_found) + if(NOT _thrift_found STREQUAL _thrift_src) + message(STATUS "[Thrift] normalizing extracted dir ${_thrift_found} -> ${_thrift_src}") + file(RENAME "${_thrift_found}" "${_thrift_src}") + endif() + endif() +endif() + if(NOT EXISTS "${_thrift_src}/CMakeLists.txt") message(FATAL_ERROR "[Thrift] could not find ${_thrift_src}/CMakeLists.txt after " "extracting ${_thrift_tarball}.") endif() +include("${CMAKE_CURRENT_LIST_DIR}/PatchThriftSsl.cmake") + # --------------------------------------------------------------------------- # ExternalProject_Add: build thrift at *configure* time so the produced # binary / library can immediately drive code generation and linking. @@ -136,9 +153,9 @@ else() "-DCMAKE_CXX_FLAGS=${_thrift_cxxflags}") endif() -if(WITH_SSL) +if(WITH_SSL AND IOTDB_NTLS_PROVIDER STREQUAL "TONGSUO") list(APPEND _thrift_cmake_args "-DWITH_OPENSSL=ON") - # Build Thrift's TSSLSocket against the same OpenSSL that iotdb_session links + # Build Thrift's TSSLSocket against the same SSL library that iotdb_session links # and bundles, so the runtime libraries match. find_package does not set # OPENSSL_ROOT_DIR itself, so derive it from the resolved include dir. if(OPENSSL_ROOT_DIR) @@ -147,6 +164,15 @@ if(WITH_SSL) get_filename_component(_thrift_ossl_root "${OPENSSL_INCLUDE_DIR}" DIRECTORY) list(APPEND _thrift_cmake_args "-DOPENSSL_ROOT_DIR=${_thrift_ossl_root}") endif() + if(OPENSSL_INCLUDE_DIR) + list(APPEND _thrift_cmake_args "-DOPENSSL_INCLUDE_DIR=${OPENSSL_INCLUDE_DIR}") + endif() + if(OPENSSL_SSL_LIBRARY) + list(APPEND _thrift_cmake_args "-DOPENSSL_SSL_LIBRARY=${OPENSSL_SSL_LIBRARY}") + endif() + if(OPENSSL_CRYPTO_LIBRARY) + list(APPEND _thrift_cmake_args "-DOPENSSL_CRYPTO_LIBRARY=${OPENSSL_CRYPTO_LIBRARY}") + endif() else() list(APPEND _thrift_cmake_args "-DWITH_OPENSSL=OFF") endif() @@ -164,12 +190,17 @@ endif() # Encode WITH_SSL in the stamp: toggling SSL changes WITH_OPENSSL, so a cached # build of the opposite flavour must not be reused (otherwise TSSLSocket is # missing/extra at link time). -if(WITH_SSL) +if(WITH_SSL AND IOTDB_NTLS_PROVIDER STREQUAL "TONGSUO") set(_thrift_ssl_stamp "-ssl") else() set(_thrift_ssl_stamp "-nossl") endif() -set(_thrift_stamp "${_thrift_build}/.built-${THRIFT_VERSION}-${_thrift_build_config}-mdll${_thrift_abi_stamp}${_thrift_ssl_stamp}") +set(_thrift_provider_signature + "${IOTDB_NTLS_PROVIDER};${IOTDB_GMSSL_ROOT_DIR};${OPENSSL_ROOT_DIR};" + "${OPENSSL_SSL_LIBRARY};${OPENSSL_CRYPTO_LIBRARY}") +string(JOIN "" _thrift_provider_signature ${_thrift_provider_signature}) +string(MD5 _thrift_provider_stamp "${_thrift_provider_signature}") +set(_thrift_stamp "${_thrift_build}/.built-${THRIFT_VERSION}-${_thrift_build_config}-mdll${_thrift_abi_stamp}${_thrift_ssl_stamp}-sslctx-${_thrift_provider_stamp}") if(NOT EXISTS "${_thrift_stamp}") file(MAKE_DIRECTORY "${_thrift_build}") message(STATUS "[Thrift] configuring ${_thrift_dirname}") diff --git a/iotdb-client/client-cpp/cmake/InstallOpenSSLRuntime.cmake b/iotdb-client/client-cpp/cmake/InstallOpenSSLRuntime.cmake index f3e181b8e8ff..e3073ff59cfc 100644 --- a/iotdb-client/client-cpp/cmake/InstallOpenSSLRuntime.cmake +++ b/iotdb-client/client-cpp/cmake/InstallOpenSSLRuntime.cmake @@ -39,7 +39,10 @@ function(_iotdb_collect_openssl_windows_dlls _out_var) if(OPENSSL_ROOT_DIR) list(APPEND _roots "${OPENSSL_ROOT_DIR}") endif() - foreach(_implib IN LISTS OPENSSL_SSL_LIBRARY OPENSSL_CRYPTO_LIBRARY OPENSSL_LIBRARIES) + if(IOTDB_GMSSL_ROOT_DIR) + list(APPEND _roots "${IOTDB_GMSSL_ROOT_DIR}") + endif() + foreach(_implib IN LISTS IOTDB_NTLS_RUNTIME_LIBRARIES OPENSSL_LIBRARIES) if(_implib AND EXISTS "${_implib}") # Walk up from the import lib (.../lib, .../lib/VC/x64/MD, ...) to find # a directory that owns a bin/ holding the DLLs. @@ -60,8 +63,12 @@ function(_iotdb_collect_openssl_windows_dlls _out_var) file(GLOB _found "${_root}/bin/libssl-${OPENSSL_VERSION_MAJOR}*.dll" "${_root}/bin/libcrypto-${OPENSSL_VERSION_MAJOR}*.dll" + "${_root}/bin/gmssl*.dll" + "${_root}/bin/libgmssl*.dll" "${_root}/libssl-${OPENSSL_VERSION_MAJOR}*.dll" - "${_root}/libcrypto-${OPENSSL_VERSION_MAJOR}*.dll") + "${_root}/libcrypto-${OPENSSL_VERSION_MAJOR}*.dll" + "${_root}/gmssl*.dll" + "${_root}/libgmssl*.dll") # The same DLL can appear under several candidate roots (e.g. bin/ and # the install root); keep only the first occurrence of each filename. foreach(_dll IN LISTS _found) @@ -99,7 +106,7 @@ function(iotdb_install_openssl_runtime) # are skipped: they are already linked into libiotdb_session. set(_files_arg "") set(_have_libs OFF) - foreach(_lib IN LISTS OPENSSL_SSL_LIBRARY OPENSSL_CRYPTO_LIBRARY) + foreach(_lib IN LISTS IOTDB_NTLS_RUNTIME_LIBRARIES) if(_lib AND EXISTS "${_lib}" AND NOT _lib MATCHES "\\.a$") string(APPEND _files_arg " \"${_lib}\"") set(_have_libs ON) diff --git a/iotdb-client/client-cpp/cmake/PatchThriftSsl.cmake b/iotdb-client/client-cpp/cmake/PatchThriftSsl.cmake new file mode 100644 index 000000000000..2e9b2e49af2b --- /dev/null +++ b/iotdb-client/client-cpp/cmake/PatchThriftSsl.cmake @@ -0,0 +1,80 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# ============================================================================= +# PatchThriftSsl.cmake +# +# Extends the vendored Apache Thrift C++ SSL transport with SSLContextFactory and +# SSLContext(SSL_CTX*) so IoTDB can inject custom OpenSSL / NTLS contexts. +# ============================================================================= + +if(NOT WITH_SSL) + return() +endif() + +set(_thrift_ssl_header "${_thrift_src}/lib/cpp/src/thrift/transport/TSSLSocket.h") +set(_thrift_ssl_cpp "${_thrift_src}/lib/cpp/src/thrift/transport/TSSLSocket.cpp") +set(_thrift_ssl_patch_marker "${_thrift_root}/.patched-ssl-context-${THRIFT_VERSION}") + +if(EXISTS "${_thrift_ssl_patch_marker}") + return() +endif() + +if(NOT EXISTS "${_thrift_ssl_header}") + message(FATAL_ERROR "[Thrift] cannot patch missing ${_thrift_ssl_header}") +endif() + +file(READ "${_thrift_ssl_header}" _thrift_ssl_header_content) +if(NOT _thrift_ssl_header_content MATCHES "SSLContextFactory") + if(NOT _thrift_ssl_header_content MATCHES "#include ") + string(REPLACE + "#include " + "#include \n#include " + _thrift_ssl_header_content "${_thrift_ssl_header_content}") + endif() + string(REPLACE + "class SSLContext;" + "class SSLContext;\ntypedef std::function()> SSLContextFactory;" + _thrift_ssl_header_content "${_thrift_ssl_header_content}") + string(REPLACE + " TSSLSocketFactory(SSLProtocol protocol = SSLTLS);" + " TSSLSocketFactory(SSLProtocol protocol = SSLTLS);\n /**\n * Constructor\n *\n * @param contextFactory Function invoked during construction to return a custom OpenSSL context.\n */\n TSSLSocketFactory(const SSLContextFactory& contextFactory);" + _thrift_ssl_header_content "${_thrift_ssl_header_content}") + string(REPLACE + " SSLContext(const SSLProtocol& protocol = SSLTLS);" + " SSLContext(const SSLProtocol& protocol = SSLTLS);\n /**\n * Wrap an existing OpenSSL SSL_CTX.\n *\n * Takes ownership of @a ctx; the caller must not call SSL_CTX_free on it.\n */\n explicit SSLContext(SSL_CTX* ctx);" + _thrift_ssl_header_content "${_thrift_ssl_header_content}") + file(WRITE "${_thrift_ssl_header}" "${_thrift_ssl_header_content}") +endif() + +if(EXISTS "${_thrift_ssl_cpp}") + file(READ "${_thrift_ssl_cpp}" _thrift_ssl_cpp_content) + if(NOT _thrift_ssl_cpp_content MATCHES "SSLContext::SSLContext\\(SSL_CTX\\* ctx\\)") + string(REPLACE + "SSLContext::~SSLContext() {" + "SSLContext::SSLContext(SSL_CTX* ctx) : ctx_(ctx) {\n if (ctx_ == nullptr) {\n string errors;\n buildErrors(errors);\n throw TSSLException(\"SSL_CTX_new: null context\");\n }\n}\n\nSSLContext::~SSLContext() {" + _thrift_ssl_cpp_content "${_thrift_ssl_cpp_content}") + string(REPLACE + "TSSLSocketFactory::TSSLSocketFactory(SSLProtocol protocol) : server_(false) {" + "TSSLSocketFactory::TSSLSocketFactory(const SSLContextFactory& contextFactory) : server_(false) {\n Guard guard(mutex_);\n if (count_ == 0) {\n if (!manualOpenSSLInitialization_) {\n didWeInitializeOpenSSL_ = true;\n initializeOpenSSL();\n }\n randomize();\n }\n count_++;\n ctx_ = contextFactory();\n}\n\nTSSLSocketFactory::TSSLSocketFactory(SSLProtocol protocol) : server_(false) {" + _thrift_ssl_cpp_content "${_thrift_ssl_cpp_content}") + file(WRITE "${_thrift_ssl_cpp}" "${_thrift_ssl_cpp_content}") + endif() +endif() + +file(TOUCH "${_thrift_ssl_patch_marker}") +message(STATUS "[Thrift] applied SSLContextFactory patch to ${_thrift_ssl_header}") diff --git a/iotdb-client/client-cpp/examples/AlignedTimeseriesSessionExample.cpp b/iotdb-client/client-cpp/examples/AlignedTimeseriesSessionExample.cpp index 80d1caadd353..5d2154193e2d 100644 --- a/iotdb-client/client-cpp/examples/AlignedTimeseriesSessionExample.cpp +++ b/iotdb-client/client-cpp/examples/AlignedTimeseriesSessionExample.cpp @@ -415,6 +415,7 @@ int main() { session->close(); delete session; + session = nullptr; cout << "finished\n" << endl; return 0; diff --git a/iotdb-client/client-cpp/examples/CMakeLists.txt b/iotdb-client/client-cpp/examples/CMakeLists.txt index f6f092045d01..ed830970b0d6 100644 --- a/iotdb-client/client-cpp/examples/CMakeLists.txt +++ b/iotdb-client/client-cpp/examples/CMakeLists.txt @@ -61,21 +61,55 @@ else() INCLUDE_DIRECTORIES("${IOTDB_SDK_ROOT}/include") endif() -option(WITH_SSL "Build with SSL support" OFF) - -IF(WITH_SSL) - FIND_PACKAGE(OpenSSL REQUIRED) - IF(OpenSSL_FOUND) - MESSAGE(STATUS "OpenSSL found: ${OPENSSL_VERSION}") - INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) - ADD_DEFINITIONS(-DWITH_SSL=1) - ELSE() - MESSAGE(FATAL_ERROR "OpenSSL not found, but WITH_SSL is enabled") - ENDIF() -ELSE() - MESSAGE(STATUS "Building without SSL support") - ADD_DEFINITIONS(-DWITH_SSL=0) -ENDIF() +# Match the SDK default (WITH_SSL=ON in the main client build). When this +# directory is added via add_subdirectory(), the parent cache value wins. +option(WITH_SSL "Build with SSL/TLS support" ON) + +set(_iotdb_use_bundled_ssl OFF) +set(_iotdb_ssl_link_libs "") + +if(NOT _iotdb_examples_in_tree) + file(GLOB _iotdb_bundled_ssl_runtime + "${IOTDB_SDK_ROOT}/lib/libssl*.so*" + "${IOTDB_SDK_ROOT}/lib/libcrypto*.so*" + "${IOTDB_SDK_ROOT}/lib/libssl*.dylib" + "${IOTDB_SDK_ROOT}/lib/libcrypto*.dylib" + "${IOTDB_SDK_ROOT}/lib/libssl*.dll" + "${IOTDB_SDK_ROOT}/lib/libcrypto*.dll") + if(_iotdb_bundled_ssl_runtime) + set(_iotdb_use_bundled_ssl ON) + set(WITH_SSL ON CACHE BOOL "Build with SSL/TLS support" FORCE) + message(STATUS "Using bundled Tongsuo/OpenSSL-compatible libraries from ${IOTDB_SDK_ROOT}/lib") + endif() +endif() + +if(WITH_SSL) + if(_iotdb_examples_in_tree) + add_compile_definitions(WITH_SSL=1) + elseif(_iotdb_use_bundled_ssl) + add_compile_definitions(WITH_SSL=1) + if(UNIX) + find_library(_iotdb_ssl_lib NAMES ssl libssl + PATHS "${IOTDB_SDK_ROOT}/lib" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) + find_library(_iotdb_crypto_lib NAMES crypto libcrypto + PATHS "${IOTDB_SDK_ROOT}/lib" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) + if(_iotdb_ssl_lib AND _iotdb_crypto_lib) + set(_iotdb_ssl_link_libs "${_iotdb_ssl_lib}" "${_iotdb_crypto_lib}") + else() + message(FATAL_ERROR + "Bundled libssl/libcrypto not found under ${IOTDB_SDK_ROOT}/lib") + endif() + endif() + else() + message(FATAL_ERROR + "WITH_SSL=ON requires building inside the IoTDB client tree, or an SDK " + "that bundles libssl/libcrypto under ${IOTDB_SDK_ROOT}/lib. " + "Pass -DWITH_SSL=OFF only for SDKs built without SSL.") + endif() +else() + message(STATUS "Building without SSL support") + add_compile_definitions(WITH_SSL=0) +endif() if(NOT _iotdb_examples_in_tree) find_package(iotdb-session CONFIG QUIET @@ -118,35 +152,61 @@ set(_example_targets tree_example table_example) -# OpenSSL runtime libraries bundled in the SDK lib/ (libssl / libcrypto). When -# building against an unpacked package, copy them next to each example binary so -# the examples run without a system OpenSSL - libiotdb_session records them as -# NEEDED and resolves them via its $ORIGIN runtime path. -set(_iotdb_sdk_ssl_runtime "") -if(NOT _iotdb_examples_in_tree) - file(GLOB _iotdb_sdk_ssl_runtime - "${IOTDB_SDK_ROOT}/lib/libssl*.so*" - "${IOTDB_SDK_ROOT}/lib/libcrypto*.so*" - "${IOTDB_SDK_ROOT}/lib/libssl*.dylib" - "${IOTDB_SDK_ROOT}/lib/libcrypto*.dylib" - "${IOTDB_SDK_ROOT}/lib/libssl*.dll" - "${IOTDB_SDK_ROOT}/lib/libcrypto*.dll") +set(_it_plain_examples "") +set(_it_ssl_examples "") +set(_it_ntls_examples "") + +if(_iotdb_examples_in_tree) + ADD_EXECUTABLE(cpp_tree_example cpp_tree_example.cpp) + ADD_EXECUTABLE(cpp_table_example cpp_table_example.cpp) + + list(APPEND _example_targets + cpp_tree_example + cpp_table_example) + + set(_it_plain_examples + cpp_tree_example + cpp_table_example + tree_example + table_example) + + # These examples exercise the OpenSSL-compatible Tongsuo API and command-line + # server. GmSSL has a separate native API and is covered by rpcNtlsUtilsTest. + if(WITH_SSL AND IOTDB_NTLS_PROVIDER STREQUAL "TONGSUO") + ADD_EXECUTABLE(cpp_tls_example cpp_tls_example.cpp ExampleTlsConfig.cpp) + ADD_EXECUTABLE(cpp_ntls_example cpp_ntls_example.cpp ExampleNtlsHandshake.cpp) + ADD_EXECUTABLE(tls_tree_example tls_tree_example.c ExampleTlsConfig.cpp) + ADD_EXECUTABLE(c_ntls_example c_ntls_example.c ExampleNtlsHandshake.cpp) + + list(APPEND _example_targets + cpp_tls_example + cpp_ntls_example + tls_tree_example + c_ntls_example) + + set(_it_ssl_examples + cpp_tls_example + tls_tree_example) + + set(_it_ntls_examples + cpp_ntls_example + c_ntls_example) + endif() endif() foreach(_t IN LISTS _example_targets) - IF(WITH_SSL) - TARGET_LINK_LIBRARIES(${_t} PRIVATE "${_iotdb_link_lib}" OpenSSL::SSL OpenSSL::Crypto) - ELSE() - TARGET_LINK_LIBRARIES(${_t} PRIVATE "${_iotdb_link_lib}") - ENDIF() + if(WITH_SSL AND _iotdb_ssl_link_libs) + target_link_libraries(${_t} PRIVATE "${_iotdb_link_lib}" ${_iotdb_ssl_link_libs}) + else() + target_link_libraries(${_t} PRIVATE "${_iotdb_link_lib}") + endif() IF(UNIX) TARGET_LINK_LIBRARIES(${_t} PRIVATE pthread) ENDIF() - # The packaged libiotdb_session records the bundled OpenSSL libs as DT_NEEDED; - # point the linker at the SDK lib/ so it can resolve them without a system - # OpenSSL present. - if(UNIX AND NOT _iotdb_examples_in_tree) + # The packaged libiotdb_session records the bundled SSL libs as DT_NEEDED; point + # the linker at the SDK lib/ so it can resolve them without a system install. + if(UNIX AND NOT _iotdb_examples_in_tree AND _iotdb_use_bundled_ssl) target_link_directories(${_t} PRIVATE "${IOTDB_SDK_ROOT}/lib") endif() @@ -162,22 +222,77 @@ foreach(_t IN LISTS _example_targets) COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ COMMENT "Copy IoTDB runtime library next to ${_t}") + if(WIN32 AND WITH_SSL) + _iotdb_collect_openssl_windows_dlls(_iotdb_ssl_runtime_dlls) + foreach(_ssl_dll IN LISTS _iotdb_ssl_runtime_dlls) + add_custom_command(TARGET ${_t} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${_ssl_dll}" $ + COMMENT "Copy bundled SSL runtime next to ${_t}") + endforeach() + endif() elseif(EXISTS "${_iotdb_runtime}") add_custom_command(TARGET ${_t} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_iotdb_runtime}" $ COMMENT "Copy IoTDB runtime library next to ${_t}") - foreach(_ssl_lib IN LISTS _iotdb_sdk_ssl_runtime) + foreach(_ssl_lib IN LISTS _iotdb_bundled_ssl_runtime) add_custom_command(TARGET ${_t} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_ssl_lib}" $ - COMMENT "Copy bundled OpenSSL runtime next to ${_t}") + COMMENT "Copy bundled SSL runtime next to ${_t}") endforeach() elseif(WIN32) message(WARNING "Missing ${_iotdb_runtime}; copy iotdb_session.dll manually before running ${_t}.") endif() endforeach() +if(_iotdb_examples_in_tree AND WITH_SSL AND IOTDB_EXAMPLES_REGISTER_TESTS) + file(TO_CMAKE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../test/fixtures" _iotdb_example_fixtures_dir) + string(REPLACE "\\" "/" _iotdb_example_fixtures_dir_fwd "${_iotdb_example_fixtures_dir}") + foreach(_t IN LISTS _it_ssl_examples _it_ntls_examples) + target_compile_definitions(${_t} PRIVATE + IOTDB_TEST_FIXTURES_DIR="${_iotdb_example_fixtures_dir_fwd}") + add_custom_command(TARGET ${_t} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/../test/fixtures" + "$/fixtures" + COMMENT "Copy SSL test fixtures next to ${_t}") + endforeach() + foreach(_t IN LISTS _it_ntls_examples) + target_sources(${_t} PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../test/cpp/SslTestFixtures.cpp") + target_include_directories(${_t} PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../test/cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/../src/rpc" + "${THRIFT_GEN_CPP_DIR}" + "${THRIFT_INCLUDE_DIR}") + if(BOOST_INCLUDE_DIR) + target_include_directories(${_t} PRIVATE "${BOOST_INCLUDE_DIR}") + endif() + # Keep Homebrew OpenSSL headers from shadowing Tongsuo for the NTLS helpers. + set_target_properties(${_t} PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) + target_include_directories(${_t} BEFORE PRIVATE "${OPENSSL_INCLUDE_DIR}") + file(TO_CMAKE_PATH "${OPENSSL_ROOT_DIR}" _iotdb_openssl_root_dir_cmake) + string(REPLACE "\\" "/" _iotdb_openssl_root_dir_fwd "${_iotdb_openssl_root_dir_cmake}") + if(WIN32) + file(TO_CMAKE_PATH "${OPENSSL_ROOT_DIR}/bin/openssl.exe" _iotdb_openssl_executable_cmake) + string(REPLACE "\\" "/" _iotdb_openssl_executable_fwd "${_iotdb_openssl_executable_cmake}") + target_compile_definitions(${_t} PRIVATE + IOTDB_OPENSSL_EXECUTABLE="${_iotdb_openssl_executable_fwd}" + IOTDB_OPENSSL_ROOT_DIR="${_iotdb_openssl_root_dir_fwd}") + target_link_libraries(${_t} PRIVATE ws2_32 "${THRIFT_STATIC_LIB_PATH}") + else() + file(TO_CMAKE_PATH "${OPENSSL_ROOT_DIR}/bin/openssl" _iotdb_openssl_executable_cmake) + string(REPLACE "\\" "/" _iotdb_openssl_executable_fwd "${_iotdb_openssl_executable_cmake}") + target_compile_definitions(${_t} PRIVATE + IOTDB_OPENSSL_EXECUTABLE="${_iotdb_openssl_executable_fwd}" + IOTDB_OPENSSL_ROOT_DIR="${_iotdb_openssl_root_dir_fwd}") + target_link_libraries(${_t} PRIVATE iotdb_thrift_static) + endif() + endforeach() +endif() + # Optional: stage a self-contained folder for copying to another machine (see package README). set(_example_dist_dir "${CMAKE_BINARY_DIR}/dist") add_custom_target(example-dist DEPENDS ${_example_targets} @@ -194,29 +309,27 @@ if(EXISTS "${_iotdb_runtime}") COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_iotdb_runtime}" "${_example_dist_dir}/") endif() -# Stage the bundled OpenSSL runtime too, so a copied dist/ runs on a machine -# without a system OpenSSL. -foreach(_ssl_lib IN LISTS _iotdb_sdk_ssl_runtime) +# Stage the bundled SSL runtime too, so a copied dist/ runs without a system SSL. +foreach(_ssl_lib IN LISTS _iotdb_bundled_ssl_runtime) add_custom_command(TARGET example-dist POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_ssl_lib}" "${_example_dist_dir}/") endforeach() if(IOTDB_EXAMPLES_REGISTER_TESTS) - set(_runnable_example_targets - SessionExample - AlignedTimeseriesSessionExample - TableModelSessionExample - tree_example - table_example) - foreach(_t IN LISTS _runnable_example_targets) + foreach(_t IN LISTS _it_plain_examples) + add_test(NAME example_${_t} COMMAND ${_t}) + set_tests_properties(example_${_t} PROPERTIES + LABELS "plain" RUN_SERIAL TRUE RESOURCE_LOCK iotdb_cpp_it_server) + endforeach() + foreach(_t IN LISTS _it_ssl_examples) + add_test(NAME example_${_t} COMMAND ${_t}) + set_tests_properties(example_${_t} PROPERTIES + LABELS "ssl" RUN_SERIAL TRUE RESOURCE_LOCK iotdb_cpp_it_server) + endforeach() + foreach(_t IN LISTS _it_ntls_examples) add_test(NAME example_${_t} COMMAND ${_t}) + set_tests_properties(example_${_t} PROPERTIES + LABELS "ntls" RUN_SERIAL TRUE RESOURCE_LOCK iotdb_cpp_it_server) endforeach() - set_tests_properties( - example_SessionExample - example_AlignedTimeseriesSessionExample - example_TableModelSessionExample - example_tree_example - example_table_example - PROPERTIES RUN_SERIAL TRUE) endif() diff --git a/iotdb-client/client-cpp/examples/ExampleNtlsHandshake.cpp b/iotdb-client/client-cpp/examples/ExampleNtlsHandshake.cpp new file mode 100644 index 000000000000..071685ce02bb --- /dev/null +++ b/iotdb-client/client-cpp/examples/ExampleNtlsHandshake.cpp @@ -0,0 +1,80 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "ExampleNtlsHandshake.h" + +#include + +#include "Common.h" +#include "RpcSslUtils.h" +#include "SslTestFixtures.h" + +namespace { + +bool fixtureExists(const std::string& path) { + std::ifstream in(path.c_str(), std::ios::binary); + return in.good(); +} + +} // namespace + +extern "C" int example_run_tlcp_handshake(void) { +#if !WITH_SSL + return 1; +#else + const std::string caFile = ssltest::tlcpFixture("ca.crt"); + const std::string signCert = ssltest::tlcpFixture("server_sign.crt"); + const std::string signKey = ssltest::tlcpFixture("server_sign.key"); + const std::string encCert = ssltest::tlcpFixture("server_enc.crt"); + const std::string encKey = ssltest::tlcpFixture("server_enc.key"); + if (!fixtureExists(caFile) || !fixtureExists(signCert) || !fixtureExists(signKey) || + !fixtureExists(encCert) || !fixtureExists(encKey)) { + return 1; + } + + ssltest::OpenSslServerProcess server; + if (!server.start({ + "-enable_ntls", + "-ntls", + "-CAfile", + caFile, + "-sign_cert", + signCert, + "-sign_key", + signKey, + "-enc_cert", + encCert, + "-enc_key", + encKey, + "-www", + })) { + return 1; + } + + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLCP"; + config.trustStore = ssltest::tlcpFixture("tlcp-trust.p12"); + config.trustStorePwd = ssltest::kStorePassword; + + const bool ok = ssltest::tlsHandshakeWithSslConfig(config, "127.0.0.1", server.port()); + server.stop(); + return ok ? 0 : 1; +#endif +} diff --git a/iotdb-client/client-cpp/examples/ExampleNtlsHandshake.h b/iotdb-client/client-cpp/examples/ExampleNtlsHandshake.h new file mode 100644 index 000000000000..69ab77ab9267 --- /dev/null +++ b/iotdb-client/client-cpp/examples/ExampleNtlsHandshake.h @@ -0,0 +1,34 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 IOTDB_EXAMPLE_NTLS_HANDSHAKE_H +#define IOTDB_EXAMPLE_NTLS_HANDSHAKE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Run TLCP one-way handshake against a local openssl NTLS s_server. Returns 0 on success. */ +int example_run_tlcp_handshake(void); + +#ifdef __cplusplus +} +#endif + +#endif // IOTDB_EXAMPLE_NTLS_HANDSHAKE_H diff --git a/iotdb-client/client-cpp/examples/ExampleTlsConfig.cpp b/iotdb-client/client-cpp/examples/ExampleTlsConfig.cpp new file mode 100644 index 000000000000..7204174a3923 --- /dev/null +++ b/iotdb-client/client-cpp/examples/ExampleTlsConfig.cpp @@ -0,0 +1,136 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "Session.h" +#include "TableSession.h" +#include "ExampleTlsConfig.h" + +#include +#include + +namespace { + +constexpr const char* kStorePassword = "thrift"; + +std::string joinPath(const std::string& base, const std::string& name) { +#if defined(_WIN32) + const char sep = '\\'; +#else + const char sep = '/'; +#endif + if (base.empty()) { + return name; + } + if (base.back() == '/' || base.back() == '\\') { + return base + name; + } + return base + sep + name; +} + +#if defined(_WIN32) +#include +#else +#include +#endif + +std::string executableDir() { +#if defined(_WIN32) + char buffer[MAX_PATH]; + const DWORD len = GetModuleFileNameA(nullptr, buffer, MAX_PATH); + if (len == 0 || len == MAX_PATH) { + return "."; + } + std::string path(buffer, len); + const auto pos = path.find_last_of("\\/"); + return pos == std::string::npos ? "." : path.substr(0, pos); +#else + char buffer[4096]; + const ssize_t len = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1); + if (len <= 0) { + return "."; + } + buffer[len] = '\0'; + std::string path(buffer); + const auto pos = path.find_last_of('/'); + return pos == std::string::npos ? "." : path.substr(0, pos); +#endif +} + +bool pathExists(const std::string& path) { + std::ifstream in(path.c_str(), std::ios::binary); + return in.good(); +} + +std::string trustStorePath() { + static const std::string path = [] { +#ifdef IOTDB_TEST_FIXTURES_DIR + const std::string configured = IOTDB_TEST_FIXTURES_DIR; + const std::string configuredPath = joinPath(joinPath(configured, "tls"), "tls-trust.p12"); + if (pathExists(configuredPath)) { + return configuredPath; + } +#endif + const std::string copied = joinPath(joinPath(executableDir(), "fixtures"), "tls/tls-trust.p12"); + return copied; + }(); + return path; +} + +} // namespace + +extern "C" const char* example_tls_trust_store_path(void) { + static std::string path = trustStorePath(); + return path.c_str(); +} + +extern "C" void example_tls_configure_tree_session(CSession* session) { + if (session == nullptr) { + return; + } + ts_session_set_use_ssl(session, true); + ts_session_set_ssl_protocol(session, "TLS"); + ts_session_set_trust_store(session, example_tls_trust_store_path(), kStorePassword); +} + +extern "C" void example_tls_configure_table_session(CTableSession* session) { + if (session == nullptr) { + return; + } + ts_table_session_set_use_ssl(session, true); + ts_table_session_set_ssl_protocol(session, "TLS"); + ts_table_session_set_trust_store(session, example_tls_trust_store_path(), kStorePassword); +} + +namespace examplessl { + +void configureTreeSessionBuilder(SessionBuilder& builder) { + builder.useSSL(true) + ->sslProtocol("TLS") + ->trustStore(trustStorePath()) + ->trustStorePwd(kStorePassword); +} + +void configureTableSessionBuilder(TableSessionBuilder& builder) { + builder.useSSL(true) + ->sslProtocol("TLS") + ->trustStore(trustStorePath()) + ->trustStorePwd(kStorePassword); +} + +} // namespace examplessl diff --git a/iotdb-client/client-cpp/examples/ExampleTlsConfig.h b/iotdb-client/client-cpp/examples/ExampleTlsConfig.h new file mode 100644 index 000000000000..8473d218bcaa --- /dev/null +++ b/iotdb-client/client-cpp/examples/ExampleTlsConfig.h @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 IOTDB_EXAMPLE_TLS_CONFIG_H +#define IOTDB_EXAMPLE_TLS_CONFIG_H + +#include "SessionC.h" + +#ifdef __cplusplus +#include "Session.h" +#include "SessionBuilder.h" +#include "TableSession.h" +#include "TableSessionBuilder.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** One-way TLS trust store used by TLS examples (PKCS12 under fixtures/tls/). */ +const char* example_tls_trust_store_path(void); + +void example_tls_configure_tree_session(CSession* session); +void example_tls_configure_table_session(CTableSession* session); + +#ifdef __cplusplus +} + +namespace examplessl { + +void configureTreeSessionBuilder(SessionBuilder& builder); +void configureTableSessionBuilder(TableSessionBuilder& builder); + +} // namespace examplessl +#endif + +#endif // IOTDB_EXAMPLE_TLS_CONFIG_H diff --git a/iotdb-client/client-cpp/examples/README.md b/iotdb-client/client-cpp/examples/README.md index 1910135eefc4..0fa15ecb0ce2 100644 --- a/iotdb-client/client-cpp/examples/README.md +++ b/iotdb-client/client-cpp/examples/README.md @@ -32,12 +32,18 @@ user `root` / `root`). | Example | Description | |---------|-------------| -| `SessionExample` | Tree model: DDL, insert, query, delete | -| `AlignedTimeseriesSessionExample` | Aligned time series and templates | -| `TableModelSessionExample` | Table (relational) model | +| `cpp_tree_example` | C++ tree model smoke test (plain RPC) | +| `cpp_table_example` | C++ table model smoke test (plain RPC) | +| `cpp_tls_example` | C++ tree model over one-way TLS | +| `cpp_ntls_example` | C++ TLCP handshake against local openssl NTLS `s_server` | +| `tree_example` | C Session API tree model (plain RPC) | +| `table_example` | C Session API table model (plain RPC) | +| `tls_tree_example` | C Session API tree model over one-way TLS | +| `c_ntls_example` | C TLCP handshake against local openssl NTLS `s_server` | +| `SessionExample` | Full tree-model walkthrough (not run in CI) | +| `AlignedTimeseriesSessionExample` | Aligned time series demo (not run in CI) | +| `TableModelSessionExample` | Full table-model walkthrough (not run in CI) | | `MultiSvrNodeClient` | Multi-node insert/query loop | -| `tree_example` | C Session API (tree model) | -| `table_example` | C Session API (table model) | ## Which SDK zip to use @@ -61,8 +67,9 @@ pre-built Thrift workflow only. Linux release packages are built in the ## SDK layout (after unpack) -The SDK zip produced by `client-cpp` contains **public headers only** and one -shared library: +The SDK zip produced by `client-cpp` contains **public headers**, the +`iotdb_session` shared library, and (when built with SSL, the default) +**bundled Tongsuo** runtime libraries (`libssl` / `libcrypto`): ``` client/ @@ -73,7 +80,9 @@ client/ └── lib/ ├── iotdb_session.dll + iotdb_session.lib (Windows) ├── libiotdb_session.so (Linux) - └── libiotdb_session.dylib (macOS) + ├── libiotdb_session.dylib (macOS) + ├── libssl-3-x64.dll + libcrypto-3-x64.dll (Windows SSL runtime, when WITH_SSL=ON) + └── libssl.so* + libcrypto.so* (Linux/macOS SSL runtime, when WITH_SSL=ON) ``` ## Build the examples @@ -106,6 +115,10 @@ cmake -S iotdb-client/client-cpp/examples -B build \ cmake --build build ``` +When the SDK bundles `libssl` / `libcrypto` under `lib/` (default `WITH_SSL=ON` +builds), CMake detects them automatically. A system OpenSSL install is not used. +Pass `-DWITH_SSL=OFF` only for SDKs built without SSL. + Windows (Visual Studio generator): ```powershell @@ -122,6 +135,7 @@ Optional staging folder for deployment: ```bash cmake --build build --target example-dist # -> build/dist/ contains all example binaries + libiotdb_session.{so,dll,dylib} +# and bundled libssl/libcrypto when WITH_SSL=ON ``` ## Run on a clean machine (no compiler, no IoTDB SDK headers) @@ -142,9 +156,12 @@ Copy either from `build/.../Release/` (Windows) / `build/` (Ninja/Make) or from ``` SessionExample.exe iotdb_session.dll +libssl-3-x64.dll +libcrypto-3-x64.dll ``` -(Repeat for the other example names if needed.) +(Repeat for the other example names if needed. Exact SSL DLL names follow the +Tongsuo major version bundled in your SDK zip.) **Prerequisites on the target PC** @@ -164,8 +181,9 @@ iotdb_session.dll If you see “The code execution cannot proceed because VCRUNRuntime140.dll was missing”, install the VC++ redistributable above. -You do **not** need a separate Thrift or Boost runtime; they are inside -`iotdb_session.dll`. +You do **not** need a separate Thrift, Boost, or system OpenSSL runtime; Thrift +and Boost are inside `iotdb_session.dll`, and SSL is provided by the bundled +Tongsuo libraries copied above. ### Linux @@ -174,9 +192,14 @@ You do **not** need a separate Thrift or Boost runtime; they are inside ``` SessionExample libiotdb_session.so +libssl.so* +libcrypto.so* chmod +x SessionExample ``` +Copy the `libssl` / `libcrypto` soname files that ship next to +`libiotdb_session.so` in the SDK `lib/` directory (Tongsuo, OpenSSL-compatible). + **Prerequisites on the target machine** - **glibc** on the target must be **≥ the glibc version on the machine that @@ -231,6 +254,28 @@ version should be **≥ the deployment target used to build the SDK**. Check wit otool -L SessionExample ``` +## SSL / TLCP examples + +When connecting to an SSL-enabled IoTDB DataNode, configure the session builder +before `build()`: + +```cpp +SessionBuilder() + .host("127.0.0.1") + ->rpcPort(6667) + ->useSSL(true) + ->sslProtocol("TLS") // or "TLCP" for NTLS / GM/T + ->trustStore("/path/to/ca.p12") + ->trustStorePwd("thrift") + ->keyStore("/path/to/client.p12") // optional, mutual auth + ->keyStorePwd("thrift") + ->build(); +``` + +Use PKCS12 stores (convert JKS with `keytool -importkeystore`). See the main +[README.md](../README.md#client-ssl--tlcp-configuration) for TLS and TLCP +details. + ## Development notes - **Windows**: Application and SDK both use **`/MD`** (dynamic CRT). This diff --git a/iotdb-client/client-cpp/examples/README_zh.md b/iotdb-client/client-cpp/examples/README_zh.md index a00b93a57d4b..0369740b20ca 100644 --- a/iotdb-client/client-cpp/examples/README_zh.md +++ b/iotdb-client/client-cpp/examples/README_zh.md @@ -59,7 +59,8 @@ Linux 发版包在 `manylinux_2_28` 容器中构建,部署机需要 glibc 2.28 ## SDK 目录结构(解压后) -`client-cpp` 打出的 SDK 压缩包只包含 **公开头文件** 和 **一个共享库**: +`client-cpp` 打出的 SDK 压缩包包含 **公开头文件**、`iotdb_session` 共享库, +以及(默认开启 SSL 时)**内置的 Tongsuo** 运行时(`libssl` / `libcrypto`): ``` client/ @@ -70,7 +71,9 @@ client/ └── lib/ ├── iotdb_session.dll + iotdb_session.lib (Windows) ├── libiotdb_session.so (Linux) - └── libiotdb_session.dylib (macOS) + ├── libiotdb_session.dylib (macOS) + ├── libssl-3-x64.dll + libcrypto-3-x64.dll (Windows SSL 运行时,WITH_SSL=ON) + └── libssl.so* + libcrypto.so* (Linux/macOS SSL 运行时,WITH_SSL=ON) ``` ## 编译示例 @@ -103,6 +106,10 @@ cmake -S iotdb-client/client-cpp/examples -B build \ cmake --build build ``` +若 SDK 的 `lib/` 下已包含 `libssl` / `libcrypto`(默认 `WITH_SSL=ON` 构建), +CMake 会自动检测并链接这些内置库,不会使用系统 OpenSSL。仅当使用未启用 SSL 的 +SDK 时才需要传入 `-DWITH_SSL=OFF`。 + Windows(Visual Studio 生成器): ```powershell @@ -119,6 +126,7 @@ cmake --build build --config Release ```bash cmake --build build --target example-dist # 生成 build/dist/,内含全部示例二进制 + libiotdb_session.{so,dll,dylib} +# 以及 WITH_SSL=ON 时的 libssl/libcrypto ``` ## 在「干净机器」上运行(无需编译器、无需 SDK 头文件) @@ -139,9 +147,11 @@ cmake --build build --target example-dist ``` SessionExample.exe iotdb_session.dll +libssl-3-x64.dll +libcrypto-3-x64.dll ``` -(其他示例同理,可执行文件与 `iotdb_session.dll` 成对拷贝。) +(其他示例同理。SSL DLL 文件名与 SDK 中打包的 Tongsuo 主版本号一致。) **目标机器前置条件** @@ -160,7 +170,8 @@ iotdb_session.dll 若提示缺少 `VCRUNTIME140.dll`,请安装上述 VC++ 可再发行包。 -Thrift、Boost 已包含在 `iotdb_session.dll` 内,无需单独部署。 +Thrift、Boost 已包含在 `iotdb_session.dll` 内;SSL 由上述内置 Tongsuo 库提供, +无需单独部署系统 OpenSSL。 ### Linux @@ -169,9 +180,14 @@ Thrift、Boost 已包含在 `iotdb_session.dll` 内,无需单独部署。 ``` SessionExample libiotdb_session.so +libssl.so* +libcrypto.so* chmod +x SessionExample ``` +请一并拷贝 SDK `lib/` 目录中与 `libiotdb_session.so` 同目录的 `libssl` / +`libcrypto` 文件(Tongsuo,OpenSSL 兼容)。 + **目标机器前置条件** - 目标机的 **glibc 版本必须 ≥ 编译 SDK 时的 glibc 版本**(仅向后兼容: @@ -226,6 +242,26 @@ export LD_LIBRARY_PATH=. otool -L SessionExample ``` +## SSL / TLCP 示例 + +连接已启用 SSL 的 DataNode 时,在 `build()` 前配置: + +```cpp +SessionBuilder() + .host("127.0.0.1") + ->rpcPort(6667) + ->useSSL(true) + ->sslProtocol("TLS") // 国密请使用 "TLCP" + ->trustStore("/path/to/ca.p12") + ->trustStorePwd("thrift") + ->keyStore("/path/to/client.p12") // 可选,双向认证 + ->keyStorePwd("thrift") + ->build(); +``` + +请使用 PKCS12 证书库(JKS 可用 `keytool -importkeystore` 转换)。详见 +[README.md](../README.md#client-ssl--tlcp-configuration)。 + ## 开发说明 - **Windows**:应用与 SDK 均使用 **`/MD`**,与 Visual Studio 默认工程一致; diff --git a/iotdb-client/client-cpp/examples/SessionExample.cpp b/iotdb-client/client-cpp/examples/SessionExample.cpp index 9b429b0189b3..1546204359e0 100644 --- a/iotdb-client/client-cpp/examples/SessionExample.cpp +++ b/iotdb-client/client-cpp/examples/SessionExample.cpp @@ -452,6 +452,7 @@ int main() { session->close(); delete session; + session = nullptr; cout << "finished!\n" << endl; return 0; diff --git a/iotdb-client/client-cpp/examples/TableModelSessionExample.cpp b/iotdb-client/client-cpp/examples/TableModelSessionExample.cpp index 3ae321e18f88..ef1195b167c1 100644 --- a/iotdb-client/client-cpp/examples/TableModelSessionExample.cpp +++ b/iotdb-client/client-cpp/examples/TableModelSessionExample.cpp @@ -24,6 +24,10 @@ using namespace std; shared_ptr session; +static void configureTableBuilder(TableSessionBuilder& builder) { + builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root"); +} + void insertRelationalTablet() { vector> schemaList{ @@ -86,12 +90,9 @@ void OutputWithType(unique_ptr& dataSet) { int main() { try { - session = (new TableSessionBuilder()) - ->host("127.0.0.1") - ->rpcPort(6667) - ->username("root") - ->password("root") - ->build(); + TableSessionBuilder builder; + configureTableBuilder(builder); + session = builder.build(); cout << "[Create Database db1,db2]\n" << endl; try { @@ -156,14 +157,10 @@ int main() { session->close(); - // specify database in constructor - session = (new TableSessionBuilder()) - ->host("127.0.0.1") - ->rpcPort(6667) - ->username("root") - ->password("root") - ->database("db1") - ->build(); + TableSessionBuilder builder2; + configureTableBuilder(builder2); + builder2.database("db1"); + session = builder2.build(); cout << "[Show tables from current database(db1)]\n" << endl; try { diff --git a/iotdb-client/client-cpp/examples/c_ntls_example.c b/iotdb-client/client-cpp/examples/c_ntls_example.c new file mode 100644 index 000000000000..b131a1009231 --- /dev/null +++ b/iotdb-client/client-cpp/examples/c_ntls_example.c @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + +#include "ExampleNtlsHandshake.h" + +int main(void) { + if (example_run_tlcp_handshake() != 0) { + fprintf(stderr, "[c_ntls_example] TLCP handshake failed\n"); + return 1; + } + printf("[c_ntls_example] ok\n"); + return 0; +} diff --git a/iotdb-client/client-cpp/examples/cpp_ntls_example.cpp b/iotdb-client/client-cpp/examples/cpp_ntls_example.cpp new file mode 100644 index 000000000000..8a0cd37f1f0d --- /dev/null +++ b/iotdb-client/client-cpp/examples/cpp_ntls_example.cpp @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + +#include "ExampleNtlsHandshake.h" + +int main(void) { + if (example_run_tlcp_handshake() != 0) { + fprintf(stderr, "[cpp_ntls_example] TLCP handshake failed\n"); + return 1; + } + printf("[cpp_ntls_example] ok\n"); + return 0; +} diff --git a/iotdb-client/client-cpp/examples/cpp_table_example.cpp b/iotdb-client/client-cpp/examples/cpp_table_example.cpp new file mode 100644 index 000000000000..f38fa9d46abd --- /dev/null +++ b/iotdb-client/client-cpp/examples/cpp_table_example.cpp @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 +#include + +#include "SessionDataSet.h" +#include "TableSession.h" +#include "TableSessionBuilder.h" + +int main() { + TableSessionBuilder builder; + builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root")->useSSL(false); + std::shared_ptr session = builder.build(); + session->open(); + + session->executeNonQueryStatement("DROP DATABASE IF EXISTS cpp_demo_table"); + session->executeNonQueryStatement("CREATE DATABASE cpp_demo_table"); + session->executeNonQueryStatement("USE cpp_demo_table"); + session->executeNonQueryStatement( + "CREATE TABLE IF NOT EXISTS demo_t (tag1 STRING TAG, value INT32 FIELD)"); + session->executeNonQueryStatement("INSERT INTO demo_t(time, tag1, value) VALUES (1, 'a', 42)"); + + std::unique_ptr dataSet( + session->executeQueryStatement("SELECT time, value FROM demo_t WHERE tag1 = 'a'")); + if (!dataSet || !dataSet->hasNext()) { + std::cerr << "[cpp_table_example] expected one row\n"; + return 1; + } + std::shared_ptr record = dataSet->next(); + if (record->fields[1].intV.value() != 42) { + std::cerr << "[cpp_table_example] unexpected value\n"; + return 1; + } + dataSet->closeOperationHandle(); + + session->executeNonQueryStatement("DROP DATABASE IF EXISTS cpp_demo_table"); + session->close(); + std::cout << "[cpp_table_example] ok\n"; + return 0; +} diff --git a/iotdb-client/client-cpp/examples/cpp_tls_example.cpp b/iotdb-client/client-cpp/examples/cpp_tls_example.cpp new file mode 100644 index 000000000000..6e20f098ebc4 --- /dev/null +++ b/iotdb-client/client-cpp/examples/cpp_tls_example.cpp @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 +#include + +#include "ExampleTlsConfig.h" +#include "Session.h" +#include "SessionBuilder.h" +#include "SessionDataSet.h" + +int main() { + SessionBuilder builder; + builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root"); + examplessl::configureTreeSessionBuilder(builder); + std::shared_ptr session = builder.build(); + session->open(false); + + const std::string database = "root.cpp_demo_tls"; + const std::string timeseries = database + ".d0.s0"; + if (session->checkTimeseriesExists(timeseries)) { + session->deleteTimeseries(timeseries); + } + try { + session->deleteStorageGroup(database); + } catch (...) { + } + + session->setStorageGroup(database); + session->createTimeseries(timeseries, TSDataType::INT32, TSEncoding::PLAIN, + CompressionType::UNCOMPRESSED); + session->insertRecord(database + ".d0", 1, {"s0"}, {"7"}); + + std::unique_ptr dataSet( + session->executeQueryStatement("SELECT s0 FROM " + database + ".d0")); + if (!dataSet || !dataSet->hasNext()) { + std::cerr << "[cpp_tls_example] expected one row\n"; + return 1; + } + dataSet->closeOperationHandle(); + + session->deleteTimeseries(timeseries); + session->deleteStorageGroup(database); + session->close(); + std::cout << "[cpp_tls_example] ok\n"; + return 0; +} diff --git a/iotdb-client/client-cpp/examples/cpp_tree_example.cpp b/iotdb-client/client-cpp/examples/cpp_tree_example.cpp new file mode 100644 index 000000000000..8043ae2eeefc --- /dev/null +++ b/iotdb-client/client-cpp/examples/cpp_tree_example.cpp @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 +#include + +#include "Session.h" +#include "SessionBuilder.h" +#include "SessionDataSet.h" + +int main() { + SessionBuilder builder; + builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root")->useSSL(false); + std::shared_ptr session = builder.build(); + session->open(false); + + const std::string database = "root.cpp_demo_tree"; + const std::string timeseries = database + ".d0.s0"; + if (session->checkTimeseriesExists(timeseries)) { + session->deleteTimeseries(timeseries); + } + try { + session->deleteStorageGroup(database); + } catch (...) { + } + + session->setStorageGroup(database); + session->createTimeseries(timeseries, TSDataType::INT64, TSEncoding::RLE, + CompressionType::SNAPPY); + session->insertRecord(database + ".d0", 1, {"s0"}, {"100"}); + + std::unique_ptr dataSet( + session->executeQueryStatement("SELECT s0 FROM " + database + ".d0")); + if (!dataSet || !dataSet->hasNext()) { + std::cerr << "[cpp_tree_example] expected one row\n"; + return 1; + } + std::shared_ptr record = dataSet->next(); + if (record->fields[0].longV.value() != 100) { + std::cerr << "[cpp_tree_example] unexpected value\n"; + return 1; + } + dataSet->closeOperationHandle(); + + session->deleteTimeseries(timeseries); + session->deleteStorageGroup(database); + session->close(); + std::cout << "[cpp_tree_example] ok\n"; + return 0; +} diff --git a/iotdb-client/client-cpp/examples/tls_tree_example.c b/iotdb-client/client-cpp/examples/tls_tree_example.c new file mode 100644 index 000000000000..edd44a250799 --- /dev/null +++ b/iotdb-client/client-cpp/examples/tls_tree_example.c @@ -0,0 +1,86 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 +#include +#include + +#include "ExampleTlsConfig.h" +#include "SessionC.h" + +#define HOST "127.0.0.1" +#define PORT 6667 +#define USER "root" +#define PASS "root" +#define TS_PATH "root.cdemo_tls.d0.s0" +#define DEVICE "root.cdemo_tls.d0" + +static void fail(const char* ctx, CSession* s) { + fprintf(stderr, "[tls_tree_example] %s failed: %s\n", ctx, ts_get_last_error()); + if (s) { + ts_session_close(s); + ts_session_destroy(s); + } + exit(1); +} + +int main(void) { + CSession* session = ts_session_new(HOST, PORT, USER, PASS); + if (!session) { + fprintf(stderr, "[tls_tree_example] ts_session_new returned NULL\n"); + return 1; + } + example_tls_configure_tree_session(session); + if (ts_session_open(session) != TS_OK) { + fail("ts_session_open", session); + } + + bool exists = false; + if (ts_session_check_timeseries_exists(session, TS_PATH, &exists) != TS_OK) { + fail("ts_session_check_timeseries_exists", session); + } + if (exists) { + (void)ts_session_delete_timeseries(session, TS_PATH); + } + if (ts_session_create_timeseries(session, TS_PATH, TS_TYPE_INT64, TS_ENCODING_RLE, + TS_COMPRESSION_SNAPPY) != TS_OK) { + fail("ts_session_create_timeseries", session); + } + + const char* measurements[] = {"s0"}; + const char* values[] = {"100"}; + if (ts_session_insert_record_str(session, DEVICE, 1LL, 1, measurements, values) != TS_OK) { + fail("ts_session_insert_record_str", session); + } + + CSessionDataSet* dataSet = NULL; + if (ts_session_execute_query(session, "select s0 from root.cdemo_tls.d0", &dataSet) != TS_OK) { + fail("ts_session_execute_query", session); + } + if (!dataSet || !ts_dataset_has_next(dataSet)) { + fail("ts_session_execute_query empty", session); + } + ts_dataset_destroy(dataSet); + + (void)ts_session_delete_timeseries(session, TS_PATH); + ts_session_close(session); + ts_session_destroy(session); + printf("[tls_tree_example] ok\n"); + return 0; +} diff --git a/iotdb-client/client-cpp/pom.xml b/iotdb-client/client-cpp/pom.xml index 288acfcb5588..19e77d75c422 100644 --- a/iotdb-client/client-cpp/pom.xml +++ b/iotdb-client/client-cpp/pom.xml @@ -38,7 +38,7 @@ 3. Packages the produced install tree (maven-assembly-plugin) Everything else - thrift download, code generation, Boost/m4/flex/bison - bootstrap, OpenSSL discovery - lives in CMake modules under cmake/. + bootstrap, Tongsuo build - lives in CMake modules under cmake/. --> https://github.com/catchorg/Catch2/releases/download/v2.13.7/catch.hpp @@ -50,7 +50,11 @@ ${project.basedir}/third-party OFF ON - OFF + 0.24.0 + TONGSUO + 0aed892c5f48c9a52d1f5667667ae45156b9cdf4 + 4bb302df8ff73a89b3483873d10e7b6a4eeb041310b8eabd2be309004a88f8b6 + ON @@ -63,6 +67,7 @@ ${os.classifier} iotdb-session-cpp-${project.version}-${client.cpp.package.classifier} ${env.GITHUB_RUN_ID} + ${project.basedir}/../../distribution/target/apache-iotdb-${project.version}-all-bin/apache-iotdb-${project.version}-all-bin @@ -113,7 +118,11 @@ - + + + + + @@ -139,16 +148,56 @@ + cmake-run-test test + none + + + + + org.codehaus.mojo + exec-maven-plugin + + + configure-iotdb-plain-it + + exec + + pre-integration-test + + ${ctest.skip.tests} + python + + ${project.basedir}/test/scripts/configure_iotdb_ssl_it.py + ${iotdb.dist.root} + ${project.basedir}/test/fixtures + disable + + + + + run-cpp-it-phases + + exec + integration-test - ${cmake.build.type} - ${cmake.project.dir} - ${maven.test.skip} + ${ctest.skip.tests} + python + + ${project.basedir}/test/scripts/run_cpp_it_phases.py + ${cmake.project.dir} + ${iotdb.dist.root} + ${project.basedir}/test/fixtures + ${project.basedir}/test/scripts + ${iotdb.start.script} + --config + ${cmake.build.type} + diff --git a/iotdb-client/client-cpp/src/assembly/client-cpp.xml b/iotdb-client/client-cpp/src/assembly/client-cpp.xml index 3a6a63136410..a9340b7f95ff 100644 --- a/iotdb-client/client-cpp/src/assembly/client-cpp.xml +++ b/iotdb-client/client-cpp/src/assembly/client-cpp.xml @@ -97,8 +97,12 @@ ${project.basedir}/examples CMakeLists.txt - *.c - *.cpp + SessionExample.cpp + AlignedTimeseriesSessionExample.cpp + TableModelSessionExample.cpp + MultiSvrNodeClient.cpp + tree_example.c + table_example.c examples diff --git a/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/DEPENDENCIES.md b/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/DEPENDENCIES.md index 696ce154d16b..bc989cca6d98 100644 --- a/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/DEPENDENCIES.md +++ b/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/DEPENDENCIES.md @@ -33,7 +33,8 @@ the [`NOTICE`](NOTICE) file in this directory; non-Apache license texts are unde | --- | --- | --- | --- | | Apache Thrift | 0.24.0 | statically linked | Apache License 2.0 | | Boost | 1.60.0 on Linux/Windows, 1.84.0 on macOS by default | statically linked (header-only) | Boost Software License 1.0 | -| OpenSSL | 3.x: system OpenSSL 3.x when present, else 3.5.0 built from source (`WITH_SSL=ON`, default) | bundled shared libs in `lib/` | Apache License 2.0 | +| Tongsuo | 8.4-stable commit `0aed892c` (default NTLS provider) | bundled shared libs in `lib/` | Apache License 2.0 | +| GmSSL | 3.2.x (optional NTLS provider) | bundled shared library in `lib/` | Apache License 2.0 | ## Build-time only (not redistributed) diff --git a/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/NOTICE b/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/NOTICE index 4da431faa062..dc01d0557fcc 100644 --- a/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/NOTICE +++ b/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/NOTICE @@ -19,9 +19,16 @@ This product includes software developed at The Apache Software Foundation (http://www.apache.org/). ------------------------------------------------------------------------------ -OpenSSL (bundled shared libraries: libssl / libcrypto, present only when the +Tongsuo (bundled shared libraries: libssl / libcrypto, present only when the SDK is built with SSL support) -Copyright 1999-2025 The OpenSSL Project Authors. All Rights Reserved. +Copyright The Tongsuo Project Authors. All Rights Reserved. +Licensed under the Apache License, Version 2.0 (see the top-level LICENSE). +Tongsuo is an OpenSSL-compatible cryptographic library with additional Chinese +commercial cipher and TLCP protocol support. + +------------------------------------------------------------------------------ +GmSSL (optional NTLS provider; bundled shared library: libgmssl) +Copyright 2014-2026 The GmSSL Project. All Rights Reserved. Licensed under the Apache License, Version 2.0 (see the top-level LICENSE). ------------------------------------------------------------------------------ diff --git a/iotdb-client/client-cpp/src/include/AbstractSessionBuilder.h b/iotdb-client/client-cpp/src/include/AbstractSessionBuilder.h index 3735dfa227d0..bf19900eb5b6 100644 --- a/iotdb-client/client-cpp/src/include/AbstractSessionBuilder.h +++ b/iotdb-client/client-cpp/src/include/AbstractSessionBuilder.h @@ -55,7 +55,16 @@ class AbstractSessionBuilder { bool enableRPCCompression = DEFAULT_ENABLE_RPC_COMPRESSION; std::vector nodeUrls; bool useSSL = false; + /** @deprecated Use trustStore() instead. Legacy PEM trust certificate path. */ std::string trustCertFilePath; + std::string sslProtocol = "TLS"; + std::string trustStore; + std::string trustStorePwd; + std::string keyStore; + std::string keyStorePwd; + std::string tlcpCertChainFile; + std::string tlcpPrivateKeyFile; + std::string tlcpPrivateKeyPwd; }; #endif // IOTDB_ABSTRACTSESSIONBUILDER_H \ No newline at end of file diff --git a/iotdb-client/client-cpp/src/include/Session.h b/iotdb-client/client-cpp/src/include/Session.h index 76895a302914..dd615420efb1 100644 --- a/iotdb-client/client-cpp/src/include/Session.h +++ b/iotdb-client/client-cpp/src/include/Session.h @@ -38,6 +38,7 @@ #include "Date.h" #include "DeviceID.h" #include "SessionDataSet.h" +#include "SslConfig.h" //== For compatible with Windows OS == #ifndef LONG_LONG_MIN @@ -606,6 +607,7 @@ class Session { void setSqlDialect(const std::string& dialect); void setDatabase(const std::string& database); + void setSslConfig(const SslConfig& sslConfig); std::string getDatabase(); void changeDatabase(const std::string& database); diff --git a/iotdb-client/client-cpp/src/include/SessionBuilder.h b/iotdb-client/client-cpp/src/include/SessionBuilder.h index 14342697eb5d..c67ec28dd60e 100644 --- a/iotdb-client/client-cpp/src/include/SessionBuilder.h +++ b/iotdb-client/client-cpp/src/include/SessionBuilder.h @@ -44,6 +44,46 @@ class SessionBuilder : public AbstractSessionBuilder { return this; } + SessionBuilder* sslProtocol(const std::string& sslProtocol) { + AbstractSessionBuilder::sslProtocol = sslProtocol; + return this; + } + + SessionBuilder* trustStore(const std::string& trustStore) { + AbstractSessionBuilder::trustStore = trustStore; + return this; + } + + SessionBuilder* trustStorePwd(const std::string& trustStorePwd) { + AbstractSessionBuilder::trustStorePwd = trustStorePwd; + return this; + } + + SessionBuilder* keyStore(const std::string& keyStore) { + AbstractSessionBuilder::keyStore = keyStore; + return this; + } + + SessionBuilder* keyStorePwd(const std::string& keyStorePwd) { + AbstractSessionBuilder::keyStorePwd = keyStorePwd; + return this; + } + + SessionBuilder* tlcpCertChainFile(const std::string& path) { + AbstractSessionBuilder::tlcpCertChainFile = path; + return this; + } + + SessionBuilder* tlcpPrivateKeyFile(const std::string& path) { + AbstractSessionBuilder::tlcpPrivateKeyFile = path; + return this; + } + + SessionBuilder* tlcpPrivateKeyPwd(const std::string& password) { + AbstractSessionBuilder::tlcpPrivateKeyPwd = password; + return this; + } + SessionBuilder* username(const std::string& username) { AbstractSessionBuilder::username = username; return this; diff --git a/iotdb-client/client-cpp/src/include/SessionC.h b/iotdb-client/client-cpp/src/include/SessionC.h index fdce5801a9de..4bb6e28f8e75 100644 --- a/iotdb-client/client-cpp/src/include/SessionC.h +++ b/iotdb-client/client-cpp/src/include/SessionC.h @@ -131,6 +131,16 @@ TsStatus ts_session_open_with_compression(CSession* session, bool enableRPCCompr TsStatus ts_session_close(CSession* session); +TsStatus ts_session_set_use_ssl(CSession* session, bool useSsl); +TsStatus ts_session_set_ssl_protocol(CSession* session, const char* sslProtocol); +TsStatus ts_session_set_trust_store(CSession* session, const char* trustStore, + const char* trustStorePwd); +TsStatus ts_session_set_key_store(CSession* session, const char* keyStore, const char* keyStorePwd); +TsStatus ts_session_set_tlcp_pem_files(CSession* session, const char* certChainFile, + const char* privateKeyFile, const char* privateKeyPwd); +/** @deprecated Use ts_session_set_trust_store() instead. */ +TsStatus ts_session_set_trust_cert_file_path(CSession* session, const char* trustCertFilePath); + /* ============================================================ * Session Lifecycle — Table Model * ============================================================ */ @@ -148,6 +158,18 @@ TsStatus ts_table_session_open(CTableSession* session); TsStatus ts_table_session_close(CTableSession* session); +TsStatus ts_table_session_set_use_ssl(CTableSession* session, bool useSsl); +TsStatus ts_table_session_set_ssl_protocol(CTableSession* session, const char* sslProtocol); +TsStatus ts_table_session_set_trust_store(CTableSession* session, const char* trustStore, + const char* trustStorePwd); +TsStatus ts_table_session_set_key_store(CTableSession* session, const char* keyStore, + const char* keyStorePwd); +TsStatus ts_table_session_set_tlcp_pem_files(CTableSession* session, const char* certChainFile, + const char* privateKeyFile, const char* privateKeyPwd); +/** @deprecated Use ts_table_session_set_trust_store() instead. */ +TsStatus ts_table_session_set_trust_cert_file_path(CTableSession* session, + const char* trustCertFilePath); + /* ============================================================ * Timezone * ============================================================ */ diff --git a/iotdb-client/client-cpp/src/include/SessionPool.h b/iotdb-client/client-cpp/src/include/SessionPool.h index 4483dab0c514..631216fbf81f 100644 --- a/iotdb-client/client-cpp/src/include/SessionPool.h +++ b/iotdb-client/client-cpp/src/include/SessionPool.h @@ -188,6 +188,14 @@ class SessionPool { SessionPool& setWaitToGetSessionTimeoutMs(int64_t timeoutMs); SessionPool& setUseSSL(bool useSSL); SessionPool& setTrustCertFilePath(std::string path); + SessionPool& setSslProtocol(std::string sslProtocol); + SessionPool& setTrustStore(std::string trustStore); + SessionPool& setTrustStorePwd(std::string trustStorePwd); + SessionPool& setKeyStore(std::string keyStore); + SessionPool& setKeyStorePwd(std::string keyStorePwd); + SessionPool& setTlcpCertChainFile(std::string path); + SessionPool& setTlcpPrivateKeyFile(std::string path); + SessionPool& setTlcpPrivateKeyPwd(std::string password); // Borrow a Session. Blocks until one is free or a new one can be created, // up to timeoutMs (<= 0 means use the pool default). Throws IoTDBException on @@ -249,6 +257,14 @@ class SessionPool { int connectTimeoutMs_ = AbstractSessionBuilder::DEFAULT_CONNECT_TIMEOUT_MS; bool useSSL_ = false; std::string trustCertFilePath_; + std::string sslProtocol_ = "TLS"; + std::string trustStore_; + std::string trustStorePwd_; + std::string keyStore_; + std::string keyStorePwd_; + std::string tlcpCertChainFile_; + std::string tlcpPrivateKeyFile_; + std::string tlcpPrivateKeyPwd_; // pool sizing / waiting policy size_t maxSize_; @@ -339,6 +355,38 @@ class SessionPoolBuilder : public AbstractSessionBuilder { AbstractSessionBuilder::trustCertFilePath = v; return this; } + SessionPoolBuilder* sslProtocol(const std::string& v) { + AbstractSessionBuilder::sslProtocol = v; + return this; + } + SessionPoolBuilder* trustStore(const std::string& v) { + AbstractSessionBuilder::trustStore = v; + return this; + } + SessionPoolBuilder* trustStorePwd(const std::string& v) { + AbstractSessionBuilder::trustStorePwd = v; + return this; + } + SessionPoolBuilder* keyStore(const std::string& v) { + AbstractSessionBuilder::keyStore = v; + return this; + } + SessionPoolBuilder* keyStorePwd(const std::string& v) { + AbstractSessionBuilder::keyStorePwd = v; + return this; + } + SessionPoolBuilder* tlcpCertChainFile(const std::string& v) { + AbstractSessionBuilder::tlcpCertChainFile = v; + return this; + } + SessionPoolBuilder* tlcpPrivateKeyFile(const std::string& v) { + AbstractSessionBuilder::tlcpPrivateKeyFile = v; + return this; + } + SessionPoolBuilder* tlcpPrivateKeyPwd(const std::string& v) { + AbstractSessionBuilder::tlcpPrivateKeyPwd = v; + return this; + } SessionPoolBuilder* maxSize(size_t v) { maxSize_ = v; return this; @@ -380,7 +428,15 @@ class SessionPoolBuilder : public AbstractSessionBuilder { .setConnectTimeoutMs(AbstractSessionBuilder::connectTimeoutMs) .setWaitToGetSessionTimeoutMs(waitTimeoutMs_) .setUseSSL(AbstractSessionBuilder::useSSL) - .setTrustCertFilePath(AbstractSessionBuilder::trustCertFilePath); + .setTrustCertFilePath(AbstractSessionBuilder::trustCertFilePath) + .setSslProtocol(AbstractSessionBuilder::sslProtocol) + .setTrustStore(AbstractSessionBuilder::trustStore) + .setTrustStorePwd(AbstractSessionBuilder::trustStorePwd) + .setKeyStore(AbstractSessionBuilder::keyStore) + .setKeyStorePwd(AbstractSessionBuilder::keyStorePwd) + .setTlcpCertChainFile(AbstractSessionBuilder::tlcpCertChainFile) + .setTlcpPrivateKeyFile(AbstractSessionBuilder::tlcpPrivateKeyFile) + .setTlcpPrivateKeyPwd(AbstractSessionBuilder::tlcpPrivateKeyPwd); return pool; } diff --git a/iotdb-client/client-cpp/src/include/SslConfig.h b/iotdb-client/client-cpp/src/include/SslConfig.h new file mode 100644 index 000000000000..b32bb96dde20 --- /dev/null +++ b/iotdb-client/client-cpp/src/include/SslConfig.h @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 IOTDB_SSL_CONFIG_H +#define IOTDB_SSL_CONFIG_H + +#include + +struct SslConfig { + bool useSsl = false; + std::string sslProtocol = "TLS"; + std::string trustStore; + std::string trustStorePwd; + std::string keyStore; + std::string keyStorePwd; + /** TLCP PEM client certificate chain; provider-specific ordering is documented in README. */ + std::string tlcpCertChainFile; + /** TLCP PEM client private key bundle; provider-specific contents are documented in README. */ + std::string tlcpPrivateKeyFile; + std::string tlcpPrivateKeyPwd; + /** Legacy PEM trust certificate path; used when trustStore is empty. */ + std::string trustCertFilePath; + + std::string effectiveTrustStore() const; +}; + +#endif // IOTDB_SSL_CONFIG_H diff --git a/iotdb-client/client-cpp/src/include/TableSession.h b/iotdb-client/client-cpp/src/include/TableSession.h index d1eecfeeabae..2944b2355e83 100644 --- a/iotdb-client/client-cpp/src/include/TableSession.h +++ b/iotdb-client/client-cpp/src/include/TableSession.h @@ -41,6 +41,7 @@ class TableSession { unique_ptr executeQueryStatement(const std::string& sql, int64_t timeoutInMs); void open(bool enableRPCCompression = false); void close(); + void setSslConfig(const SslConfig& sslConfig); }; -#endif // IOTDB_TABLESESSION_H \ No newline at end of file +#endif // IOTDB_TABLESESSION_H diff --git a/iotdb-client/client-cpp/src/include/TableSessionBuilder.h b/iotdb-client/client-cpp/src/include/TableSessionBuilder.h index 3c9739ecc8ed..687bb67d56ee 100644 --- a/iotdb-client/client-cpp/src/include/TableSessionBuilder.h +++ b/iotdb-client/client-cpp/src/include/TableSessionBuilder.h @@ -55,6 +55,46 @@ class TableSessionBuilder : public AbstractSessionBuilder { return this; } + TableSessionBuilder* sslProtocol(const std::string& sslProtocol) { + AbstractSessionBuilder::sslProtocol = sslProtocol; + return this; + } + + TableSessionBuilder* trustStore(const std::string& trustStore) { + AbstractSessionBuilder::trustStore = trustStore; + return this; + } + + TableSessionBuilder* trustStorePwd(const std::string& trustStorePwd) { + AbstractSessionBuilder::trustStorePwd = trustStorePwd; + return this; + } + + TableSessionBuilder* keyStore(const std::string& keyStore) { + AbstractSessionBuilder::keyStore = keyStore; + return this; + } + + TableSessionBuilder* keyStorePwd(const std::string& keyStorePwd) { + AbstractSessionBuilder::keyStorePwd = keyStorePwd; + return this; + } + + TableSessionBuilder* tlcpCertChainFile(const std::string& path) { + AbstractSessionBuilder::tlcpCertChainFile = path; + return this; + } + + TableSessionBuilder* tlcpPrivateKeyFile(const std::string& path) { + AbstractSessionBuilder::tlcpPrivateKeyFile = path; + return this; + } + + TableSessionBuilder* tlcpPrivateKeyPwd(const std::string& password) { + AbstractSessionBuilder::tlcpPrivateKeyPwd = password; + return this; + } + TableSessionBuilder* username(const std::string& username) { AbstractSessionBuilder::username = username; return this; diff --git a/iotdb-client/client-cpp/src/rpc/GmsslTlcpSocket.cpp b/iotdb-client/client-cpp/src/rpc/GmsslTlcpSocket.cpp new file mode 100644 index 000000000000..fb640187ea3d --- /dev/null +++ b/iotdb-client/client-cpp/src/rpc/GmsslTlcpSocket.cpp @@ -0,0 +1,197 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "GmsslTlcpSocket.h" + +#if defined(IOTDB_NTLS_PROVIDER_GMSSL) + +#include +#include + +#include +#include +#include + +using apache::thrift::transport::TTransportException; + +GmsslTlcpSocket::GmsslTlcpSocket(const std::string& host, int port, SslConfig config) + : TSocket(host, port), sslConfig_(std::move(config)) { + RpcSslUtils::validateGmsslTlcpConfig(sslConfig_); +} + +GmsslTlcpSocket::~GmsslTlcpSocket() { + close(); +} + +void GmsslTlcpSocket::open() { + if (connTimeout_ > 0) { + if (recvTimeout_ <= 0) { + setRecvTimeout(connTimeout_); + } + if (sendTimeout_ <= 0) { + setSendTimeout(connTimeout_); + } + } + TSocket::open(); + try { + int result = tls_ctx_init(&tlsContext_, TLS_protocol_tlcp, 1); + if (result != 1) { + throwTransportError("GmSSL TLCP context initialization", result); + } + contextInitialized_ = true; + RpcSslUtils::configureGmsslTlcpContext(&tlsContext_, sslConfig_); + result = tls_init(&tlsConnection_, &tlsContext_); + if (result != 1) { + throwTransportError("GmSSL TLCP connection initialization", result); + } + connectionInitialized_ = true; + result = tls_set_hostname(&tlsConnection_, getHost().c_str()); + if (result != 1) { + throwTransportError("GmSSL TLCP hostname configuration", result); + } + result = tls_set_socket(&tlsConnection_, getSocketFD()); + if (result != 1) { + throwTransportError("GmSSL TLCP socket configuration", result); + } + result = tls_do_handshake(&tlsConnection_); + if (result != 1) { + throwTransportError("GmSSL TLCP handshake", result); + } + handshakeComplete_ = true; + } catch (...) { + close(); + throw; + } +} + +void GmsslTlcpSocket::close() { + if (connectionInitialized_) { + if (handshakeComplete_) { + (void)tls_shutdown(&tlsConnection_); + } + tls_client_verify_cleanup(&tlsConnection_.client_verify_ctx); + tls_cleanup(&tlsConnection_); + connectionInitialized_ = false; + handshakeComplete_ = false; + hasPeekedByte_ = false; + } + if (contextInitialized_) { + tls_ctx_cleanup(&tlsContext_); + contextInitialized_ = false; + } + TSocket::close(); +} + +bool GmsslTlcpSocket::peek() { + if (hasPeekedByte_) { + return true; + } + if (!handshakeComplete_) { + throw TTransportException(TTransportException::NOT_OPEN, "GmSSL TLCP socket is not open"); + } + size_t received = 0; + const int result = tls_recv(&tlsConnection_, &peekedByte_, 1, &received); + if (result == 1 && received == 1) { + hasPeekedByte_ = true; + return true; + } + if (result == 0 || result == TLS_ERROR_TCP_CLOSED) { + return false; + } + throwTransportError("GmSSL TLCP peek", result); +} + +uint32_t GmsslTlcpSocket::read(uint8_t* buf, uint32_t len) { + if (!handshakeComplete_) { + throw TTransportException(TTransportException::NOT_OPEN, "GmSSL TLCP socket is not open"); + } + if (len == 0) { + return 0; + } + if (hasPeekedByte_) { + buf[0] = peekedByte_; + hasPeekedByte_ = false; + return 1; + } + size_t received = 0; + const int result = tls_recv(&tlsConnection_, buf, len, &received); + if (result == 1) { + return static_cast(received); + } + if (result == 0 || result == TLS_ERROR_TCP_CLOSED) { + return 0; + } + throwTransportError("GmSSL TLCP read", result); +} + +void GmsslTlcpSocket::write(const uint8_t* buf, uint32_t len) { + uint32_t written = 0; + while (written < len) { + written += write_partial(buf + written, len - written); + } +} + +uint32_t GmsslTlcpSocket::write_partial(const uint8_t* buf, uint32_t len) { + if (!handshakeComplete_) { + throw TTransportException(TTransportException::NOT_OPEN, "GmSSL TLCP socket is not open"); + } + size_t sent = 0; + const int result = tls_send(&tlsConnection_, buf, len, &sent); + if (result == 1 && sent > 0) { + return static_cast(sent); + } + throwTransportError("GmSSL TLCP write", result); +} + +void GmsslTlcpSocket::throwTransportError(const std::string& operation, int result) const { + const int socketError = tls_socket_get_error(); + const bool isRead = operation.find("read") != std::string::npos || + operation.find("peek") != std::string::npos || + operation.find("handshake") != std::string::npos; + const tls_socket_err_t socketErrorType = tls_socket_get_error_type(socketError, isRead ? 1 : 0); + std::ostringstream message; + message << operation << " failed (GmSSL result=" << result; + if (result == TLS_ERROR_RECV_AGAIN) { + message << "/want-read"; + } else if (result == TLS_ERROR_SEND_AGAIN) { + message << "/want-write"; + } else if (result == TLS_ERROR_TCP_CLOSED) { + message << "/tcp-closed"; + } else if (result == TLS_ERROR_SYSCALL) { + message << "/syscall"; + } + if (tlsConnection_.protocol != 0) { + message << ", protocol=" << tls_protocol_name(tlsConnection_.protocol); + } + if (tlsConnection_.cipher_suite != 0) { + message << ", cipher=" << tls_cipher_suite_name(tlsConnection_.cipher_suite); + } + message << ", handshake_state=" << tlsConnection_.handshake_state + << ", send_state=" << tlsConnection_.send_state + << ", recv_state=" << tlsConnection_.recv_state + << ", verify_result=" << tlsConnection_.verify_result << ", socket_error=" << socketError + << "/" << tls_socket_get_error_string(socketError) << ", gmssl=" << gmssl_version_str() + << "). GmSSL writes its detailed error trace to stderr."; + throw TTransportException(socketErrorType == TLS_SOCKET_ERR_TIMEOUT + ? TTransportException::TIMED_OUT + : TTransportException::UNKNOWN, + message.str()); +} + +#endif diff --git a/iotdb-client/client-cpp/src/rpc/GmsslTlcpSocket.h b/iotdb-client/client-cpp/src/rpc/GmsslTlcpSocket.h new file mode 100644 index 000000000000..1124492e540a --- /dev/null +++ b/iotdb-client/client-cpp/src/rpc/GmsslTlcpSocket.h @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 IOTDB_GMSSL_TLCP_SOCKET_H +#define IOTDB_GMSSL_TLCP_SOCKET_H + +#if defined(IOTDB_NTLS_PROVIDER_GMSSL) + +#include "RpcSslUtils.h" + +#include +#include + +#include +#include + +class GmsslTlcpSocket : public apache::thrift::transport::TSocket { +public: + GmsslTlcpSocket(const std::string& host, int port, SslConfig config); + ~GmsslTlcpSocket() override; + + void open() override; + void close() override; + bool peek() override; + uint32_t read(uint8_t* buf, uint32_t len) override; + void write(const uint8_t* buf, uint32_t len) override; + uint32_t write_partial(const uint8_t* buf, uint32_t len) override; + +private: + [[noreturn]] void throwTransportError(const std::string& operation, int result) const; + + SslConfig sslConfig_; + TLS_CTX tlsContext_{}; + TLS_CONNECT tlsConnection_{}; + uint8_t peekedByte_ = 0; + bool hasPeekedByte_ = false; + bool contextInitialized_ = false; + bool connectionInitialized_ = false; + bool handshakeComplete_ = false; +}; + +#endif + +#endif // IOTDB_GMSSL_TLCP_SOCKET_H diff --git a/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp b/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp index 55fd13a08f46..8a938274a93c 100644 --- a/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp +++ b/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp @@ -18,6 +18,7 @@ */ #include "NodesSupplier.h" #include "RpcCommon.h" +#include "RpcSslUtils.h" #include "Session.h" #include "SessionDataSet.h" #include @@ -67,33 +68,34 @@ std::vector StaticNodesSupplier::getEndPointList() { StaticNodesSupplier::~StaticNodesSupplier() = default; -std::shared_ptr NodesSupplier::create( - const std::vector& endpoints, const std::string& userName, - const std::string& password, bool useSSL, const std::string& trustCertFilePath, - const std::string& zoneId, int32_t thriftDefaultBufferSize, int32_t thriftMaxFrameSize, - int32_t connectionTimeoutInMs, bool enableRPCCompression, const std::string& version, - std::chrono::milliseconds refreshInterval, NodeSelectionPolicy policy) { +std::shared_ptr +NodesSupplier::create(const std::vector& endpoints, const std::string& userName, + const std::string& password, const SslConfig& sslConfig, + const std::string& zoneId, int32_t thriftDefaultBufferSize, + int32_t thriftMaxFrameSize, int32_t connectionTimeoutInMs, + bool enableRPCCompression, const std::string& version, + std::chrono::milliseconds refreshInterval, NodeSelectionPolicy policy) { if (endpoints.empty()) { return nullptr; } auto supplier = std::make_shared( - userName, password, useSSL, trustCertFilePath, zoneId, thriftDefaultBufferSize, - thriftMaxFrameSize, connectionTimeoutInMs, enableRPCCompression, version, endpoints, policy); + userName, password, sslConfig, zoneId, thriftDefaultBufferSize, thriftMaxFrameSize, + connectionTimeoutInMs, enableRPCCompression, version, endpoints, policy); supplier->startBackgroundRefresh(refreshInterval); return supplier; } -NodesSupplier::NodesSupplier(const std::string& userName, const std::string& password, bool useSSL, - const std::string& trustCertFilePath, const std::string& zoneId, +NodesSupplier::NodesSupplier(const std::string& userName, const std::string& password, + const SslConfig& sslConfig, const std::string& zoneId, int32_t thriftDefaultBufferSize, int32_t thriftMaxFrameSize, int32_t connectionTimeoutInMs, bool enableRPCCompression, const std::string& version, const std::vector& endpoints, NodeSelectionPolicy policy) : userName_(userName), password_(password), zoneId_(zoneId), thriftDefaultBufferSize_(thriftDefaultBufferSize), thriftMaxFrameSize_(thriftMaxFrameSize), - connectionTimeoutInMs_(connectionTimeoutInMs), useSSL_(useSSL), - trustCertFilePath_(trustCertFilePath), enableRPCCompression_(enableRPCCompression), - version_(version), endpoints_(endpoints), selectionPolicy_(policy) { + connectionTimeoutInMs_(connectionTimeoutInMs), sslConfig_(sslConfig), + enableRPCCompression_(enableRPCCompression), version_(version), endpoints_(endpoints), + selectionPolicy_(policy) { deduplicateEndpoints(); } @@ -156,8 +158,7 @@ std::vector NodesSupplier::fetchLatestEndpoints() { try { if (client_ == nullptr) { client_ = std::make_shared(endpoint); - client_->init(userName_, password_, enableRPCCompression_, useSSL_, trustCertFilePath_, - zoneId_, version_); + client_->init(userName_, password_, enableRPCCompression_, sslConfig_, zoneId_, version_); } auto sessionDataSet = client_->executeQueryStatement(SHOW_AVAILABLE_URLS_COMMAND); diff --git a/iotdb-client/client-cpp/src/rpc/NodesSupplier.h b/iotdb-client/client-cpp/src/rpc/NodesSupplier.h index c067bbb6d722..a721ebd0751f 100644 --- a/iotdb-client/client-cpp/src/rpc/NodesSupplier.h +++ b/iotdb-client/client-cpp/src/rpc/NodesSupplier.h @@ -30,6 +30,7 @@ #include #include "ThriftConnection.h" +#include "RpcSslUtils.h" class TEndPoint; @@ -78,8 +79,7 @@ class NodesSupplier : public INodesSupplier { static std::shared_ptr create(const std::vector& endpoints, const std::string& userName, - const std::string& password, bool useSSL = false, - const std::string& trustCertFilePath = "", const std::string& zoneId = "", + const std::string& password, const SslConfig& sslConfig, const std::string& zoneId = "", int32_t thriftDefaultBufferSize = ThriftConnection::THRIFT_DEFAULT_BUFFER_SIZE, int32_t thriftMaxFrameSize = ThriftConnection::THRIFT_MAX_FRAME_SIZE, int32_t connectionTimeoutInMs = ThriftConnection::CONNECTION_TIMEOUT_IN_MS, @@ -87,8 +87,8 @@ class NodesSupplier : public INodesSupplier { std::chrono::milliseconds refreshInterval = std::chrono::milliseconds(TIMEOUT_IN_MS), NodeSelectionPolicy policy = RoundRobinPolicy::select); - NodesSupplier(const std::string& userName, const std::string& password, bool useSSL, - const std::string& trustCertFilePath, const std::string& zoneId, + NodesSupplier(const std::string& userName, const std::string& password, + const SslConfig& sslConfig, const std::string& zoneId, int32_t thriftDefaultBufferSize, int32_t thriftMaxFrameSize, int32_t connectionTimeoutInMs, bool enableRPCCompression, const std::string& version, const std::vector& endpoints, @@ -106,8 +106,7 @@ class NodesSupplier : public INodesSupplier { int32_t thriftDefaultBufferSize_; int32_t thriftMaxFrameSize_; int32_t connectionTimeoutInMs_; - bool useSSL_; - std::string trustCertFilePath_; + SslConfig sslConfig_; bool enableRPCCompression_; std::string version_; std::string zoneId_; diff --git a/iotdb-client/client-cpp/src/rpc/RpcSslUtils.cpp b/iotdb-client/client-cpp/src/rpc/RpcSslUtils.cpp new file mode 100644 index 000000000000..e5a8d33bbdf6 --- /dev/null +++ b/iotdb-client/client-cpp/src/rpc/RpcSslUtils.cpp @@ -0,0 +1,851 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#if WITH_SSL && defined(IOTDB_NTLS_PROVIDER_TONGSUO) +#include +#include +#include +#include +#include +#include +#include +#elif WITH_SSL && defined(IOTDB_NTLS_PROVIDER_GMSSL) +#include +#endif + +#include "RpcSslUtils.h" + +#include "Common.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +std::string gDefaultProtocol = RpcSslUtils::DEFAULT_PROTOCOL; + +std::string trimToEmpty(const std::string& value) { + const auto start = value.find_first_not_of(" \t\r\n"); + if (start == std::string::npos) { + return ""; + } + const auto end = value.find_last_not_of(" \t\r\n"); + return value.substr(start, end - start + 1); +} + +bool hasText(const std::string& value) { + return !trimToEmpty(value).empty(); +} + +std::string toUpper(const std::string& value) { + std::string out = value; + std::transform(out.begin(), out.end(), out.begin(), + [](unsigned char c) { return static_cast(std::toupper(c)); }); + return out; +} + +bool endsWithIgnoreCase(const std::string& value, const std::string& suffix) { + if (value.size() < suffix.size()) { + return false; + } + const std::string tail = value.substr(value.size() - suffix.size()); + return toUpper(tail) == toUpper(suffix); +} + +bool isPkcs12Path(const std::string& path) { + return endsWithIgnoreCase(path, ".p12") || endsWithIgnoreCase(path, ".pfx"); +} + +#if WITH_SSL + +#if defined(IOTDB_NTLS_PROVIDER_TONGSUO) +std::string collectOpenSslErrors() { + std::string errors; + unsigned long errCode = 0; + while ((errCode = ERR_get_error()) != 0) { + char buf[256]; + ERR_error_string_n(errCode, buf, sizeof(buf)); + if (!errors.empty()) { + errors.append("; "); + } + errors.append(buf); + } + return errors.empty() ? "unknown OpenSSL error" : errors; +} + +void throwSslError(const std::string& message) { + throw IoTDBException(message + ": " + collectOpenSslErrors()); +} +#endif + +void ensureFileReadable(const std::string& path, const std::string& label) { + if (!hasText(path)) { + throw IoTDBException(label + " path is empty"); + } + std::ifstream in(path.c_str(), std::ios::binary); + if (!in.good()) { + throw IoTDBException(label + " file not found: " + path); + } +} + +#if defined(IOTDB_NTLS_PROVIDER_TONGSUO) + +PKCS12* loadPkcs12(const std::string& path, const std::string& password) { + BIO* bio = BIO_new_file(path.c_str(), "rb"); + if (bio == nullptr) { + throwSslError("Failed to open PKCS12 file " + path); + } + PKCS12* p12 = d2i_PKCS12_bio(bio, nullptr); + BIO_free(bio); + if (p12 == nullptr) { + throwSslError("Failed to parse PKCS12 file " + path); + } + (void)password; + return p12; +} + +struct Pkcs12ParsedIdentity { + EVP_PKEY* pkey = nullptr; + X509* cert = nullptr; + STACK_OF(X509) * ca = nullptr; + + ~Pkcs12ParsedIdentity() { + if (pkey != nullptr) { + EVP_PKEY_free(pkey); + } + if (cert != nullptr) { + X509_free(cert); + } + if (ca != nullptr) { + sk_X509_pop_free(ca, X509_free); + } + } + + Pkcs12ParsedIdentity() = default; + Pkcs12ParsedIdentity(const Pkcs12ParsedIdentity&) = delete; + Pkcs12ParsedIdentity& operator=(const Pkcs12ParsedIdentity&) = delete; +}; + +void parsePkcs12OrThrow(PKCS12* p12, const std::string& password, Pkcs12ParsedIdentity& parsed, + const std::string& label) { + if (PKCS12_parse(p12, password.empty() ? nullptr : password.c_str(), &parsed.pkey, &parsed.cert, + &parsed.ca) != 1) { + throwSslError("Failed to parse PKCS12 " + label); + } +} + +std::string getBagFriendlyName(PKCS12_SAFEBAG* bag) { + char* name = PKCS12_get_friendlyname(bag); + if (name == nullptr) { + return ""; + } + std::string friendlyName(name); + OPENSSL_free(name); + return friendlyName; +} + +void forEachPkcs12Bag(PKCS12* p12, const std::string& password, + const std::function& visitor) { + STACK_OF(PKCS7)* safes = PKCS12_unpack_authsafes(p12); + if (safes == nullptr) { + return; + } + for (int i = 0; i < sk_PKCS7_num(safes); ++i) { + PKCS7* p7 = sk_PKCS7_value(safes, i); + STACK_OF(PKCS12_SAFEBAG)* bags = nullptr; + if (PKCS7_type_is_data(p7)) { + bags = PKCS12_unpack_p7data(p7); + } else if (PKCS7_type_is_encrypted(p7)) { + bags = PKCS12_unpack_p7encdata(p7, password.c_str(), static_cast(password.size())); + } + if (bags == nullptr) { + continue; + } + for (int j = 0; j < sk_PKCS12_SAFEBAG_num(bags); ++j) { + visitor(sk_PKCS12_SAFEBAG_value(bags, j)); + } + sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free); + } + sk_PKCS7_pop_free(safes, PKCS7_free); +} + +EVP_PKEY* extractBagPrivateKey(PKCS12_SAFEBAG* bag, const std::string& password) { + const int bagType = PKCS12_SAFEBAG_get_nid(bag); + const PKCS8_PRIV_KEY_INFO* p8const = nullptr; + PKCS8_PRIV_KEY_INFO* p8owned = nullptr; + if (bagType == NID_pkcs8ShroudedKeyBag) { + p8owned = PKCS12_decrypt_skey(bag, password.c_str(), static_cast(password.size())); + p8const = p8owned; + } else if (bagType == NID_keyBag) { + p8const = PKCS12_SAFEBAG_get0_p8inf(bag); + } + if (p8const == nullptr) { + return nullptr; + } + EVP_PKEY* key = EVP_PKCS82PKEY(p8const); + if (p8owned != nullptr) { + PKCS8_PRIV_KEY_INFO_free(p8owned); + } + return key; +} + +bool friendlyNameContains(const std::string& friendlyName, const std::string& keyword) { + const std::string upperName = toUpper(friendlyName); + const std::string upperKeyword = toUpper(keyword); + return upperName.find(upperKeyword) != std::string::npos; +} + +void validateCertificate(X509* cert) { + if (cert == nullptr) { + return; + } +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (X509_cmp_current_time(X509_get0_notBefore(cert)) > 0 || + X509_cmp_current_time(X509_get0_notAfter(cert)) < 0) { + throw IoTDBException("Certificate is not currently valid"); + } +#else + if (X509_cmp_current_time(X509_get_notBefore(cert)) > 0 || + X509_cmp_current_time(X509_get_notAfter(cert)) < 0) { + throw IoTDBException("Certificate is not currently valid"); + } +#endif +} + +void addCertToStore(X509_STORE* store, X509* cert) { + if (store == nullptr || cert == nullptr) { + return; + } + if (X509_STORE_add_cert(store, cert) != 1) { + const unsigned long errCode = ERR_peek_last_error(); + if (ERR_GET_LIB(errCode) != ERR_LIB_X509 || + ERR_GET_REASON(errCode) != X509_R_CERT_ALREADY_IN_HASH_TABLE) { + throwSslError("Failed to add certificate to trust store"); + } + } +} + +void loadTrustFromPkcs12(SSL_CTX* ctx, const std::string& path, const std::string& password) { + PKCS12* p12 = loadPkcs12(path, password); + Pkcs12ParsedIdentity parsed; + parsePkcs12OrThrow(p12, password, parsed, "trust store " + path); + + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + if (parsed.cert != nullptr) { + validateCertificate(parsed.cert); + addCertToStore(store, parsed.cert); + } + if (parsed.ca != nullptr) { + for (int i = 0; i < sk_X509_num(parsed.ca); ++i) { + X509* caCert = sk_X509_value(parsed.ca, i); + validateCertificate(caCert); + addCertToStore(store, caCert); + } + } + + forEachPkcs12Bag(p12, password, [&](PKCS12_SAFEBAG* bag) { + if (PKCS12_SAFEBAG_get_nid(bag) == NID_certBag) { + X509* bagCert = PKCS12_certbag2x509(bag); + if (bagCert != nullptr) { + validateCertificate(bagCert); + addCertToStore(store, bagCert); + X509_free(bagCert); + } + } + }); + + PKCS12_free(p12); +} + +void loadTrustFromPem(SSL_CTX* ctx, const std::string& path) { + if (SSL_CTX_load_verify_locations(ctx, path.c_str(), nullptr) != 1) { + throwSslError("Failed to load PEM trust store " + path); + } +} + +void loadTrustStore(SSL_CTX* ctx, const std::string& path, const std::string& password) { + ensureFileReadable(path, "Trust store"); + if (isPkcs12Path(path)) { + loadTrustFromPkcs12(ctx, path, password); + } else { + loadTrustFromPem(ctx, path); + } +} + +void loadTlsIdentityFromPkcs12(SSL_CTX* ctx, const std::string& path, const std::string& password) { + PKCS12* p12 = loadPkcs12(path, password); + Pkcs12ParsedIdentity parsed; + parsePkcs12OrThrow(p12, password, parsed, "key store " + path); + PKCS12_free(p12); + + if (SSL_CTX_use_certificate(ctx, parsed.cert) != 1) { + throwSslError("Failed to load client certificate from " + path); + } + if (SSL_CTX_use_PrivateKey(ctx, parsed.pkey) != 1) { + throwSslError("Failed to load client private key from " + path); + } + if (SSL_CTX_check_private_key(ctx) != 1) { + throwSslError("Client certificate and private key do not match in " + path); + } +} + +void loadTlsIdentityFromPem(SSL_CTX* ctx, const std::string& path) { + if (SSL_CTX_use_certificate_file(ctx, path.c_str(), SSL_FILETYPE_PEM) != 1) { + throwSslError("Failed to load PEM client certificate from " + path); + } + if (SSL_CTX_use_PrivateKey_file(ctx, path.c_str(), SSL_FILETYPE_PEM) != 1) { + throwSslError("Failed to load PEM client private key from " + path); + } + if (SSL_CTX_check_private_key(ctx) != 1) { + throwSslError("Client certificate and private key do not match in " + path); + } +} + +void loadTlsKeyStore(SSL_CTX* ctx, const std::string& path, const std::string& password) { + ensureFileReadable(path, "Key store"); + if (isPkcs12Path(path)) { + loadTlsIdentityFromPkcs12(ctx, path, password); + } else { + loadTlsIdentityFromPem(ctx, path); + } +} + +struct TlcpIdentity { + X509* signCert = nullptr; + EVP_PKEY* signKey = nullptr; + X509* encCert = nullptr; + EVP_PKEY* encKey = nullptr; +}; + +void freeTlcpIdentity(TlcpIdentity& identity) { + if (identity.signCert != nullptr) { + X509_free(identity.signCert); + identity.signCert = nullptr; + } + if (identity.signKey != nullptr) { + EVP_PKEY_free(identity.signKey); + identity.signKey = nullptr; + } + if (identity.encCert != nullptr) { + X509_free(identity.encCert); + identity.encCert = nullptr; + } + if (identity.encKey != nullptr) { + EVP_PKEY_free(identity.encKey); + identity.encKey = nullptr; + } +} + +void assignTlcpMaterial(TlcpIdentity& identity, const std::string& friendlyName, X509* cert, + EVP_PKEY* key) { + if (friendlyNameContains(friendlyName, "enc")) { + if (identity.encCert != nullptr) { + X509_free(identity.encCert); + } + if (identity.encKey != nullptr) { + EVP_PKEY_free(identity.encKey); + } + identity.encCert = cert; + identity.encKey = key; + return; + } + if (friendlyNameContains(friendlyName, "sign") || identity.signCert == nullptr) { + if (identity.signCert != nullptr) { + X509_free(identity.signCert); + } + if (identity.signKey != nullptr) { + EVP_PKEY_free(identity.signKey); + } + identity.signCert = cert; + identity.signKey = key; + return; + } + if (identity.encCert == nullptr) { + identity.encCert = cert; + identity.encKey = key; + return; + } + X509_free(cert); + EVP_PKEY_free(key); +} + +void loadTlcpKeyStoreFromPkcs12(SSL_CTX* ctx, const std::string& path, + const std::string& password) { + PKCS12* p12 = loadPkcs12(path, password); + TlcpIdentity identity; + + Pkcs12ParsedIdentity parsed; + if (PKCS12_parse(p12, password.empty() ? nullptr : password.c_str(), &parsed.pkey, &parsed.cert, + &parsed.ca) == 1) { + assignTlcpMaterial(identity, "sign", parsed.cert, parsed.pkey); + parsed.cert = nullptr; + parsed.pkey = nullptr; + } + + forEachPkcs12Bag(p12, password, [&](PKCS12_SAFEBAG* bag) { + const std::string friendlyName = getBagFriendlyName(bag); + const int bagType = PKCS12_SAFEBAG_get_nid(bag); + if (bagType == NID_certBag) { + X509* cert = PKCS12_certbag2x509(bag); + if (cert != nullptr) { + if (friendlyNameContains(friendlyName, "enc")) { + if (identity.encCert != nullptr) { + X509_free(identity.encCert); + } + identity.encCert = cert; + } else if (friendlyNameContains(friendlyName, "sign") || identity.signCert == nullptr) { + if (identity.signCert != nullptr) { + X509_free(identity.signCert); + } + identity.signCert = cert; + } else if (identity.encCert == nullptr) { + identity.encCert = cert; + } else { + X509_free(cert); + } + } + } else if (bagType == NID_pkcs8ShroudedKeyBag || bagType == NID_keyBag) { + EVP_PKEY* key = extractBagPrivateKey(bag, password); + if (key != nullptr) { + if (friendlyNameContains(friendlyName, "enc")) { + if (identity.encKey != nullptr) { + EVP_PKEY_free(identity.encKey); + } + identity.encKey = key; + } else if (friendlyNameContains(friendlyName, "sign") || identity.signKey == nullptr) { + if (identity.signKey != nullptr) { + EVP_PKEY_free(identity.signKey); + } + identity.signKey = key; + } else if (identity.encKey == nullptr) { + identity.encKey = key; + } else { + EVP_PKEY_free(key); + } + } + } + }); + PKCS12_free(p12); + + if (identity.signCert == nullptr || identity.signKey == nullptr) { + freeTlcpIdentity(identity); + throw IoTDBException("TLCP PKCS12 key store must contain a signing certificate and key: " + + path); + } + + if (SSL_CTX_use_sign_certificate(ctx, identity.signCert) != 1 || + SSL_CTX_use_sign_PrivateKey(ctx, identity.signKey) != 1) { + freeTlcpIdentity(identity); + throwSslError("Failed to load TLCP signing credentials from " + path); + } + + if (identity.encCert != nullptr && identity.encKey != nullptr) { + if (SSL_CTX_use_enc_certificate(ctx, identity.encCert) != 1 || + SSL_CTX_use_enc_PrivateKey(ctx, identity.encKey) != 1) { + freeTlcpIdentity(identity); + throwSslError("Failed to load TLCP encryption credentials from " + path); + } + } + + freeTlcpIdentity(identity); +} + +void loadTlcpIdentityFromPemBundles(SSL_CTX* ctx, const std::string& certificateChainFile, + const std::string& privateKeyFile, + const std::string& privateKeyPassword) { + ensureFileReadable(certificateChainFile, "TLCP certificate chain"); + ensureFileReadable(privateKeyFile, "TLCP private key"); + + BIO* certBio = BIO_new_file(certificateChainFile.c_str(), "rb"); + if (certBio == nullptr) { + throwSslError("Failed to open TLCP certificate chain " + certificateChainFile); + } + X509* signCert = PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr); + X509* encCert = PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr); + std::vector certificateChain; + while (true) { + X509* chainCert = PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr); + if (chainCert == nullptr) { + ERR_clear_error(); + break; + } + certificateChain.push_back(chainCert); + } + BIO_free(certBio); + if (signCert == nullptr || encCert == nullptr) { + X509_free(signCert); + X509_free(encCert); + for (X509* cert : certificateChain) { + X509_free(cert); + } + throw IoTDBException( + "TLCP certificate-chain PEM must contain signing and encryption certificates: " + + certificateChainFile); + } + + BIO* keyBio = BIO_new_file(privateKeyFile.c_str(), "rb"); + if (keyBio == nullptr) { + X509_free(signCert); + X509_free(encCert); + for (X509* cert : certificateChain) { + X509_free(cert); + } + throwSslError("Failed to open TLCP private-key bundle " + privateKeyFile); + } + void* password = + privateKeyPassword.empty() ? nullptr : const_cast(privateKeyPassword.c_str()); + EVP_PKEY* signKey = PEM_read_bio_PrivateKey(keyBio, nullptr, nullptr, password); + EVP_PKEY* encKey = PEM_read_bio_PrivateKey(keyBio, nullptr, nullptr, password); + BIO_free(keyBio); + if (signKey == nullptr || encKey == nullptr) { + X509_free(signCert); + X509_free(encCert); + EVP_PKEY_free(signKey); + EVP_PKEY_free(encKey); + for (X509* cert : certificateChain) { + X509_free(cert); + } + throw IoTDBException("TLCP private-key PEM must contain signing and encryption private keys: " + + privateKeyFile); + } + + const bool loaded = SSL_CTX_use_sign_certificate(ctx, signCert) == 1 && + SSL_CTX_use_sign_PrivateKey(ctx, signKey) == 1 && + SSL_CTX_use_enc_certificate(ctx, encCert) == 1 && + SSL_CTX_use_enc_PrivateKey(ctx, encKey) == 1; + X509_free(signCert); + X509_free(encCert); + EVP_PKEY_free(signKey); + EVP_PKEY_free(encKey); + if (!loaded) { + for (X509* cert : certificateChain) { + X509_free(cert); + } + throwSslError("Failed to load TLCP PEM client credentials"); + } + for (size_t index = 0; index < certificateChain.size(); ++index) { + if (SSL_CTX_add_extra_chain_cert(ctx, certificateChain[index]) != 1) { + for (size_t remaining = index; remaining < certificateChain.size(); ++remaining) { + X509_free(certificateChain[remaining]); + } + throwSslError("Failed to load TLCP PEM client certificate chain"); + } + } +} + +void applyTlsProtocolVersion(SSL_CTX* ctx, const std::string& protocol) { + const std::string resolved = RpcSslUtils::normalizeProtocol(protocol); + const std::string upper = toUpper(resolved); + if (upper == "TLSV1.2") { + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION); + return; + } + if (upper == "TLSV1.3") { + SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION); + return; + } + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); +} + +SSL_CTX* createTlsClientContext(const SslConfig& config) { + const std::string protocol = RpcSslUtils::resolveProtocol(config.sslProtocol); + std::unique_ptr ctx(SSL_CTX_new(TLS_client_method()), + SSL_CTX_free); + if (ctx == nullptr) { + throwSslError("Failed to create TLS client context"); + } + applyTlsProtocolVersion(ctx.get(), protocol); + + const std::string trustStore = config.effectiveTrustStore(); + if (hasText(trustStore)) { + loadTrustStore(ctx.get(), trustStore, config.trustStorePwd); + SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER, nullptr); + } else { + SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_NONE, nullptr); + } + if (hasText(config.keyStore)) { + loadTlsKeyStore(ctx.get(), config.keyStore, config.keyStorePwd); + } + return ctx.release(); +} + +SSL_CTX* createTlcpClientContext(const SslConfig& config) { + std::unique_ptr ctx(SSL_CTX_new(NTLS_client_method()), + SSL_CTX_free); + if (ctx == nullptr) { + throwSslError("Failed to create TLCP client context"); + } + SSL_CTX_enable_ntls(ctx.get()); + if (SSL_CTX_set_cipher_list(ctx.get(), RpcSslUtils::DEFAULT_TLCP_CIPHER) != 1) { + throwSslError("Failed to set TLCP cipher suite"); + } + + const std::string trustStore = config.effectiveTrustStore(); + if (hasText(trustStore)) { + loadTrustStore(ctx.get(), trustStore, config.trustStorePwd); + SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER, nullptr); + } else { + SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_NONE, nullptr); + } + if (hasText(config.keyStore)) { + loadTlcpKeyStoreFromPkcs12(ctx.get(), config.keyStore, config.keyStorePwd); + } + const bool hasCertificate = hasText(config.tlcpCertChainFile); + const bool hasPrivateKey = hasText(config.tlcpPrivateKeyFile); + if (hasCertificate != hasPrivateKey) { + throw IoTDBException( + "Mutual TLCP authentication requires both certificate-chain and private-key PEM files."); + } + if (hasText(config.keyStore) && hasCertificate) { + throw IoTDBException("Configure either a TLCP PKCS12 key store or PEM credentials, not both."); + } + if (hasCertificate) { + loadTlcpIdentityFromPemBundles(ctx.get(), config.tlcpCertChainFile, config.tlcpPrivateKeyFile, + config.tlcpPrivateKeyPwd); + } + return ctx.release(); +} + +void validatePkcs12Store(const std::string& path, const std::string& password) { + PKCS12* p12 = loadPkcs12(path, password); + Pkcs12ParsedIdentity parsed; + if (PKCS12_parse(p12, password.empty() ? nullptr : password.c_str(), &parsed.pkey, &parsed.cert, + &parsed.ca) != 1) { + PKCS12_free(p12); + throw IoTDBException("Failed to parse PKCS12 store: " + path); + } + if (parsed.cert != nullptr) { + validateCertificate(parsed.cert); + } + if (parsed.ca != nullptr) { + for (int i = 0; i < sk_X509_num(parsed.ca); ++i) { + validateCertificate(sk_X509_value(parsed.ca, i)); + } + } + + forEachPkcs12Bag(p12, password, [&](PKCS12_SAFEBAG* bag) { + if (PKCS12_SAFEBAG_get_nid(bag) == NID_certBag) { + X509* bagCert = PKCS12_certbag2x509(bag); + if (bagCert != nullptr) { + validateCertificate(bagCert); + X509_free(bagCert); + } + } + }); + PKCS12_free(p12); +} + +void validatePemStore(const std::string& path) { + BIO* bio = BIO_new_file(path.c_str(), "rb"); + if (bio == nullptr) { + throw IoTDBException("Store file not found: " + path); + } + bool foundCert = false; + while (true) { + X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + if (cert == nullptr) { + break; + } + validateCertificate(cert); + X509_free(cert); + foundCert = true; + } + BIO_free(bio); + if (!foundCert) { + throw IoTDBException("No valid certificate found in PEM store: " + path); + } +} + +#elif defined(IOTDB_NTLS_PROVIDER_GMSSL) + +void configureGmsslTlcpContextImpl(TLS_CTX* ctx, const SslConfig& config) { + const int cipherSuite = TLS_cipher_ecc_sm4_cbc_sm3; + if (tls_ctx_set_cipher_suites(ctx, &cipherSuite, 1) != 1) { + throw IoTDBException("Failed to configure GmSSL TLCP cipher suite"); + } + + const std::string trustStore = config.effectiveTrustStore(); + if (hasText(trustStore)) { + ensureFileReadable(trustStore, "Trust store"); + if (isPkcs12Path(trustStore)) { + throw IoTDBException("The GmSSL provider requires a PEM trust store: " + trustStore); + } + if (tls_ctx_set_ca_certificates(ctx, trustStore.c_str(), TLS_DEFAULT_VERIFY_DEPTH) != 1) { + throw IoTDBException("Failed to load GmSSL PEM trust store " + trustStore); + } + } + + const bool hasCertificate = hasText(config.tlcpCertChainFile); + const bool hasPrivateKey = hasText(config.tlcpPrivateKeyFile); + if (hasCertificate != hasPrivateKey) { + throw IoTDBException( + "GmSSL mutual TLCP authentication requires both certificate-chain and private-key PEM " + "files."); + } + if (hasCertificate) { + ensureFileReadable(config.tlcpCertChainFile, "TLCP certificate chain"); + ensureFileReadable(config.tlcpPrivateKeyFile, "TLCP private key"); + const char* password = + config.tlcpPrivateKeyPwd.empty() ? nullptr : config.tlcpPrivateKeyPwd.c_str(); + if (tls_ctx_set_certificate_and_key(ctx, config.tlcpCertChainFile.c_str(), + config.tlcpPrivateKeyFile.c_str(), password) != 1) { + throw IoTDBException("Failed to load GmSSL TLCP client credentials"); + } + } +} + +#endif + +#endif // WITH_SSL + +} // namespace + +std::string SslConfig::effectiveTrustStore() const { + if (hasText(trustStore)) { + return trimToEmpty(trustStore); + } + return trimToEmpty(trustCertFilePath); +} + +void RpcSslUtils::configure(const std::string& sslProtocol) { + gDefaultProtocol = normalizeProtocol(sslProtocol); +} + +std::string RpcSslUtils::getProtocol() { + return gDefaultProtocol; +} + +bool RpcSslUtils::isTlcpProtocol(const std::string& protocol) { + return toUpper(trimToEmpty(protocol)).find("TLCP") == 0; +} + +std::string RpcSslUtils::normalizeProtocol(const std::string& value) { + const std::string trimmed = trimToEmpty(value); + return trimmed.empty() ? DEFAULT_PROTOCOL : trimmed; +} + +std::string RpcSslUtils::resolveProtocol(const std::string& value) { + const std::string trimmed = trimToEmpty(value); + return trimmed.empty() ? gDefaultProtocol : trimmed; +} + +void RpcSslUtils::validateTrustStore(const std::string& trustStorePath, + const std::string& trustStorePassword) { +#if WITH_SSL + ensureFileReadable(trustStorePath, "Trust store"); +#if defined(IOTDB_NTLS_PROVIDER_TONGSUO) + if (isPkcs12Path(trustStorePath)) { + validatePkcs12Store(trustStorePath, trustStorePassword); + } else { + validatePemStore(trustStorePath); + } +#else + (void)trustStorePassword; + if (isPkcs12Path(trustStorePath)) { + throw IoTDBException("The GmSSL provider requires a PEM trust store: " + trustStorePath); + } +#endif +#else + (void)trustStorePath; + (void)trustStorePassword; + throw IoTDBException("SSL/TLS support is not enabled in this build."); +#endif +} + +void RpcSslUtils::validateKeyStore(const std::string& keyStorePath, + const std::string& keyStorePassword) { +#if WITH_SSL + ensureFileReadable(keyStorePath, "Key store"); +#if defined(IOTDB_NTLS_PROVIDER_TONGSUO) + if (isPkcs12Path(keyStorePath)) { + validatePkcs12Store(keyStorePath, keyStorePassword); + } else { + validatePemStore(keyStorePath); + } +#else + (void)keyStorePassword; + if (isPkcs12Path(keyStorePath)) { + throw IoTDBException("The GmSSL provider requires PEM client credentials: " + keyStorePath); + } +#endif +#else + (void)keyStorePath; + (void)keyStorePassword; + throw IoTDBException("SSL/TLS support is not enabled in this build."); +#endif +} + +#if WITH_SSL + +#if defined(IOTDB_NTLS_PROVIDER_TONGSUO) +SSL_CTX* RpcSslUtils::createClientSslContext(const SslConfig& config) { + const std::string protocol = resolveProtocol(config.sslProtocol); + if (isTlcpProtocol(protocol)) { + return createTlcpClientContext(config); + } + return createTlsClientContext(config); +} + +std::shared_ptr +RpcSslUtils::createSslSocketFactory(const SslConfig& config) { + auto sslConfig = std::make_shared(config); + auto factory = std::make_shared( + [sslConfig]() -> std::shared_ptr { + SSL_CTX* ctx = createClientSslContext(*sslConfig); + return std::make_shared(ctx); + }); + return factory; +} +#elif defined(IOTDB_NTLS_PROVIDER_GMSSL) +void RpcSslUtils::validateGmsslTlcpConfig(const SslConfig& config) { + if (!isTlcpProtocol(resolveProtocol(config.sslProtocol))) { + throw IoTDBException("The GmSSL provider supports TLCP only; configure sslProtocol as TLCP."); + } + if (hasText(config.keyStore)) { + throw IoTDBException( + "The GmSSL provider does not support PKCS12 keyStore; configure TLCP PEM certificate and " + "private-key files."); + } +} + +void RpcSslUtils::configureGmsslTlcpContext(TLS_CTX* context, const SslConfig& config) { + if (context == nullptr) { + throw IoTDBException("GmSSL TLCP context must not be null."); + } + validateGmsslTlcpConfig(config); + configureGmsslTlcpContextImpl(context, config); +} +#endif + +#endif diff --git a/iotdb-client/client-cpp/src/rpc/RpcSslUtils.h b/iotdb-client/client-cpp/src/rpc/RpcSslUtils.h new file mode 100644 index 000000000000..e6e1b20884e2 --- /dev/null +++ b/iotdb-client/client-cpp/src/rpc/RpcSslUtils.h @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 IOTDB_RPC_SSL_UTILS_H +#define IOTDB_RPC_SSL_UTILS_H + +#include +#include + +#include "SslConfig.h" + +#if WITH_SSL +#if defined(IOTDB_NTLS_PROVIDER_TONGSUO) +#include +#include +#elif defined(IOTDB_NTLS_PROVIDER_GMSSL) +#include +#endif +#endif + +class RpcSslUtils { +public: + static constexpr const char* DEFAULT_PROTOCOL = "TLS"; + static constexpr const char* DEFAULT_TLCP_CIPHER = "ECC-SM2-WITH-SM4-SM3"; + + static void configure(const std::string& sslProtocol); + static std::string getProtocol(); + + static bool isTlcpProtocol(const std::string& protocol); + static std::string normalizeProtocol(const std::string& value); + static std::string resolveProtocol(const std::string& value); + + static void validateTrustStore(const std::string& trustStorePath, + const std::string& trustStorePassword); + static void validateKeyStore(const std::string& keyStorePath, + const std::string& keyStorePassword); + +#if WITH_SSL +#if defined(IOTDB_NTLS_PROVIDER_TONGSUO) + static SSL_CTX* createClientSslContext(const SslConfig& config); + static std::shared_ptr + createSslSocketFactory(const SslConfig& config); +#elif defined(IOTDB_NTLS_PROVIDER_GMSSL) + static void validateGmsslTlcpConfig(const SslConfig& config); + static void configureGmsslTlcpContext(TLS_CTX* context, const SslConfig& config); +#endif +#endif +}; + +#endif // IOTDB_RPC_SSL_UTILS_H diff --git a/iotdb-client/client-cpp/src/rpc/SessionConnection.cpp b/iotdb-client/client-cpp/src/rpc/SessionConnection.cpp index dfdb0198e387..77afa6e018a6 100644 --- a/iotdb-client/client-cpp/src/rpc/SessionConnection.cpp +++ b/iotdb-client/client-cpp/src/rpc/SessionConnection.cpp @@ -17,7 +17,11 @@ * under the License. */ #include "SessionConnection.h" +#if defined(IOTDB_NTLS_PROVIDER_GMSSL) +#include "GmsslTlcpSocket.h" +#endif #include "SessionImpl.h" +#include "RpcSslUtils.h" #include "RpcCommon.h" #include "common_types.h" #include @@ -46,7 +50,7 @@ SessionConnection::SessionConnection(Session::Impl* session_ptr, const TEndPoint sqlDialect(std::move(dialect)), database(std::move(db)) { this->zoneId = zoneId.empty() ? getSystemDefaultZoneId() : zoneId; endPointList.push_back(endpoint); - init(endPoint, session->useSSL_, session->trustCertFilePath_); + init(endPoint, session->sslConfig_); } void SessionConnection::close() { @@ -92,13 +96,15 @@ SessionConnection::~SessionConnection() { } } -void SessionConnection::init(const TEndPoint& endpoint, bool useSSL, - const std::string& trustCertFilePath) { - if (useSSL) { +void SessionConnection::init(const TEndPoint& endpoint, const SslConfig& sslConfig) { + if (sslConfig.useSsl) { #if WITH_SSL - socketFactory_->loadTrustedCertificates(trustCertFilePath.c_str()); - socketFactory_->authenticate(false); +#if defined(IOTDB_NTLS_PROVIDER_GMSSL) + auto sslSocket = std::make_shared(endPoint.ip, endPoint.port, sslConfig); +#else + socketFactory_ = RpcSslUtils::createSslSocketFactory(sslConfig); auto sslSocket = socketFactory_->createSocket(endPoint.ip, endPoint.port); +#endif sslSocket->setConnTimeout(connectionTimeoutInMs); transport = std::make_shared(sslSocket); #else @@ -332,7 +338,7 @@ bool SessionConnection::reconnect() { } tryHostNum++; try { - init(this->endPoint, this->session->useSSL_, this->session->trustCertFilePath_); + init(this->endPoint, this->session->sslConfig_); reconnect = true; } catch (const IoTDBConnectionException& e) { log_warn("The current node may have been down, connection exception: %s", e.what()); diff --git a/iotdb-client/client-cpp/src/rpc/SessionConnection.h b/iotdb-client/client-cpp/src/rpc/SessionConnection.h index 472e29fd6654..f19bb07ef8dd 100644 --- a/iotdb-client/client-cpp/src/rpc/SessionConnection.h +++ b/iotdb-client/client-cpp/src/rpc/SessionConnection.h @@ -23,7 +23,7 @@ #include #include #include -#if WITH_SSL +#if WITH_SSL && defined(IOTDB_NTLS_PROVIDER_TONGSUO) #include #endif @@ -53,7 +53,7 @@ class SessionConnection : public std::enable_shared_from_this const TEndPoint& getEndPoint(); - void init(const TEndPoint& endpoint, bool useSSL, const std::string& trustCertFilePath); + void init(const TEndPoint& endpoint, const SslConfig& sslConfig); void insertStringRecord(const TSInsertStringRecordReq& request); @@ -183,7 +183,7 @@ class SessionConnection : public std::enable_shared_from_this TSStatus insertTabletsInternal(TSInsertTabletsReq request); TSStatus deleteDataInternal(TSDeleteDataReq request); -#if WITH_SSL +#if WITH_SSL && defined(IOTDB_NTLS_PROVIDER_TONGSUO) std::shared_ptr socketFactory_ = std::make_shared(); #endif diff --git a/iotdb-client/client-cpp/src/rpc/SessionImpl.h b/iotdb-client/client-cpp/src/rpc/SessionImpl.h index 406537486c09..c55c05cd1d8e 100644 --- a/iotdb-client/client-cpp/src/rpc/SessionImpl.h +++ b/iotdb-client/client-cpp/src/rpc/SessionImpl.h @@ -31,6 +31,7 @@ #include "DeviceID.h" #include "Endpoint.h" #include "NodesSupplier.h" +#include "RpcSslUtils.h" #include "Session.h" #include "SessionConnection.h" #include "ThriftConvert.h" @@ -41,8 +42,7 @@ class Session::Impl { public: std::string host_; int rpcPort_ = 6667; - bool useSSL_ = false; - std::string trustCertFilePath_; + SslConfig sslConfig_; std::vector nodeUrls_; std::string username_ = "root"; std::string password_ = "root"; diff --git a/iotdb-client/client-cpp/src/rpc/ThriftConnection.cpp b/iotdb-client/client-cpp/src/rpc/ThriftConnection.cpp index 1cc6c5417b2d..dcab419f813e 100644 --- a/iotdb-client/client-cpp/src/rpc/ThriftConnection.cpp +++ b/iotdb-client/client-cpp/src/rpc/ThriftConnection.cpp @@ -17,6 +17,10 @@ * under the License. */ #include "ThriftConnection.h" +#if defined(IOTDB_NTLS_PROVIDER_GMSSL) +#include "GmsslTlcpSocket.h" +#endif +#include "RpcSslUtils.h" #include #include #include @@ -64,14 +68,16 @@ void ThriftConnection::initZoneId() { } void ThriftConnection::init(const std::string& username, const std::string& password, - bool enableRPCCompression, bool useSSL, - const std::string& trustCertFilePath, const std::string& zoneId, - const std::string& version) { - if (useSSL) { + bool enableRPCCompression, const SslConfig& sslConfig, + const std::string& zoneId, const std::string& version) { + if (sslConfig.useSsl) { #if WITH_SSL - socketFactory_->loadTrustedCertificates(trustCertFilePath.c_str()); - socketFactory_->authenticate(false); +#if defined(IOTDB_NTLS_PROVIDER_GMSSL) + auto sslSocket = std::make_shared(endPoint_.ip, endPoint_.port, sslConfig); +#else + socketFactory_ = RpcSslUtils::createSslSocketFactory(sslConfig); auto sslSocket = socketFactory_->createSocket(endPoint_.ip, endPoint_.port); +#endif sslSocket->setConnTimeout(connectionTimeoutInMs_); transport_ = std::make_shared(sslSocket); #else diff --git a/iotdb-client/client-cpp/src/rpc/ThriftConnection.h b/iotdb-client/client-cpp/src/rpc/ThriftConnection.h index 286911740316..70fb1961d7a1 100644 --- a/iotdb-client/client-cpp/src/rpc/ThriftConnection.h +++ b/iotdb-client/client-cpp/src/rpc/ThriftConnection.h @@ -20,10 +20,11 @@ #define IOTDB_THRIFTCONNECTION_H #include -#if WITH_SSL +#if WITH_SSL && defined(IOTDB_NTLS_PROVIDER_TONGSUO) #include #endif #include "IClientRPCService.h" +#include "RpcSslUtils.h" #include "SessionConfig.h" class SessionDataSet; @@ -43,9 +44,8 @@ class ThriftConnection { ~ThriftConnection(); void init(const std::string& username, const std::string& password, - bool enableRPCCompression = false, bool useSSL = false, - const std::string& trustCertFilePath = "", const std::string& zoneId = std::string(), - const std::string& version = "V_1_0"); + bool enableRPCCompression = false, const SslConfig& sslConfig = SslConfig(), + const std::string& zoneId = std::string(), const std::string& version = "V_1_0"); std::unique_ptr executeQueryStatement(const std::string& sql, int64_t timeoutInMs = -1); @@ -60,7 +60,7 @@ class ThriftConnection { int connectionTimeoutInMs_; int fetchSize_; -#if WITH_SSL +#if WITH_SSL && defined(IOTDB_NTLS_PROVIDER_TONGSUO) std::shared_ptr socketFactory_ = std::make_shared(); #endif diff --git a/iotdb-client/client-cpp/src/session/Session.cpp b/iotdb-client/client-cpp/src/session/Session.cpp index ab71732ebe12..ca9421f3921d 100644 --- a/iotdb-client/client-cpp/src/session/Session.cpp +++ b/iotdb-client/client-cpp/src/session/Session.cpp @@ -27,6 +27,7 @@ #include #include #include "SessionImpl.h" +#include "RpcSslUtils.h" #include "SessionDataSet.h" #include "ThriftConvert.h" @@ -632,8 +633,16 @@ Session::Session(AbstractSessionBuilder* builder) : impl_(new Impl()) { impl_->enableRedirection_ = builder->enableRedirections; impl_->connectTimeoutMs_ = builder->connectTimeoutMs; impl_->nodeUrls_ = builder->nodeUrls; - impl_->useSSL_ = builder->useSSL; - impl_->trustCertFilePath_ = builder->trustCertFilePath; + impl_->sslConfig_.useSsl = builder->useSSL; + impl_->sslConfig_.sslProtocol = builder->sslProtocol; + impl_->sslConfig_.trustStore = builder->trustStore; + impl_->sslConfig_.trustStorePwd = builder->trustStorePwd; + impl_->sslConfig_.keyStore = builder->keyStore; + impl_->sslConfig_.keyStorePwd = builder->keyStorePwd; + impl_->sslConfig_.tlcpCertChainFile = builder->tlcpCertChainFile; + impl_->sslConfig_.tlcpPrivateKeyFile = builder->tlcpPrivateKeyFile; + impl_->sslConfig_.tlcpPrivateKeyPwd = builder->tlcpPrivateKeyPwd; + impl_->sslConfig_.trustCertFilePath = builder->trustCertFilePath; impl_->initZoneId(); impl_->initNodesSupplier(impl_->nodeUrls_); } @@ -646,6 +655,13 @@ void Session::setDatabase(const std::string& database) { impl_->database_ = database; } +void Session::setSslConfig(const SslConfig& sslConfig) { + if (!impl_->isClosed_) { + throw IoTDBException("Cannot change SSL configuration after Session is opened."); + } + impl_->sslConfig_ = sslConfig; +} + std::string Session::getDatabase() { return impl_->database_; } @@ -961,8 +977,7 @@ void Session::Impl::initNodesSupplier(const std::vector& nodeUrls) } if (enableAutoFetch_) { - nodesSupplier_ = - NodesSupplier::create(endPoints, username_, password_, useSSL_, trustCertFilePath_); + nodesSupplier_ = NodesSupplier::create(endPoints, username_, password_, sslConfig_); } else { nodesSupplier_ = make_shared(endPoints); } diff --git a/iotdb-client/client-cpp/src/session/SessionC.cpp b/iotdb-client/client-cpp/src/session/SessionC.cpp index 7365d6ff645b..da64f0264673 100644 --- a/iotdb-client/client-cpp/src/session/SessionC.cpp +++ b/iotdb-client/client-cpp/src/session/SessionC.cpp @@ -24,6 +24,7 @@ #include "TableSessionBuilder.h" #include "SessionBuilder.h" #include "SessionDataSet.h" +#include "RpcSslUtils.h" #include #include @@ -39,10 +40,14 @@ struct CSession_ { std::shared_ptr cpp; + SslConfig sslConfig; + bool sslConfigured = false; }; struct CTableSession_ { std::shared_ptr cpp; + SslConfig sslConfig; + bool sslConfigured = false; }; struct CTablet_ { @@ -154,6 +159,32 @@ static std::map toStringMap(int count, const char* con return m; } +static void applyPendingSslConfig(CSession* session) { + if (session != nullptr && session->sslConfigured) { + session->cpp->setSslConfig(session->sslConfig); + } +} + +static void applyPendingSslConfig(CTableSession* session) { + if (session != nullptr && session->sslConfigured) { + session->cpp->setSslConfig(session->sslConfig); + } +} + +static TsStatus setSslStringField(std::string& field, const char* value, const char* label) { + if (value == nullptr) { + return setError(TS_ERR_INVALID_PARAM, std::string(label) + " is null"); + } + field = value; + return TS_OK; +} + +static std::shared_ptr createTableSession(TableSessionBuilder* builder) { + builder->sqlDialect = "table"; + auto session = std::make_shared(builder); + return std::make_shared(session); +} + /** * Convert C typed values (void* const* values, TSDataType_C* types, int count) * to C++ vector that Session expects. @@ -301,6 +332,7 @@ TsStatus ts_session_open(CSession* session) { if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); try { + applyPendingSslConfig(session); session->cpp->open(); return TS_OK; } catch (const std::exception& e) { @@ -313,6 +345,7 @@ TsStatus ts_session_open_with_compression(CSession* session, bool enableRPCCompr if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); try { + applyPendingSslConfig(session); session->cpp->open(enableRPCCompression); return TS_OK; } catch (const std::exception& e) { @@ -332,6 +365,92 @@ TsStatus ts_session_close(CSession* session) { } } +TsStatus ts_session_set_use_ssl(CSession* session, bool useSsl) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + session->sslConfig.useSsl = useSsl; + session->sslConfigured = true; + return TS_OK; +} + +TsStatus ts_session_set_ssl_protocol(CSession* session, const char* sslProtocol) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + TsStatus status = setSslStringField(session->sslConfig.sslProtocol, sslProtocol, "sslProtocol"); + if (status == TS_OK) { + session->sslConfigured = true; + } + return status; +} + +TsStatus ts_session_set_trust_store(CSession* session, const char* trustStore, + const char* trustStorePwd) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + TsStatus status = setSslStringField(session->sslConfig.trustStore, trustStore, "trustStore"); + if (status != TS_OK) { + return status; + } + if (trustStorePwd != nullptr) { + session->sslConfig.trustStorePwd = trustStorePwd; + } + session->sslConfigured = true; + return TS_OK; +} + +TsStatus ts_session_set_key_store(CSession* session, const char* keyStore, + const char* keyStorePwd) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + TsStatus status = setSslStringField(session->sslConfig.keyStore, keyStore, "keyStore"); + if (status != TS_OK) { + return status; + } + if (keyStorePwd != nullptr) { + session->sslConfig.keyStorePwd = keyStorePwd; + } + session->sslConfigured = true; + return TS_OK; +} + +TsStatus ts_session_set_tlcp_pem_files(CSession* session, const char* certChainFile, + const char* privateKeyFile, const char* privateKeyPwd) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + TsStatus status = + setSslStringField(session->sslConfig.tlcpCertChainFile, certChainFile, "certChainFile"); + if (status != TS_OK) { + return status; + } + status = + setSslStringField(session->sslConfig.tlcpPrivateKeyFile, privateKeyFile, "privateKeyFile"); + if (status != TS_OK) { + return status; + } + if (privateKeyPwd != nullptr) { + session->sslConfig.tlcpPrivateKeyPwd = privateKeyPwd; + } + session->sslConfigured = true; + return TS_OK; +} + +TsStatus ts_session_set_trust_cert_file_path(CSession* session, const char* trustCertFilePath) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + TsStatus status = setSslStringField(session->sslConfig.trustCertFilePath, trustCertFilePath, + "trustCertFilePath"); + if (status == TS_OK) { + session->sslConfigured = true; + } + return status; +} + /* ============================================================ * Session Lifecycle — Table Model * ============================================================ */ @@ -340,17 +459,14 @@ CTableSession* ts_table_session_new(const char* host, int rpcPort, const char* u const char* password, const char* database) { clearError(); try { - std::unique_ptr builder(new TableSessionBuilder()); - auto tableSession = builder->host(std::string(host)) - ->rpcPort(rpcPort) - ->username(std::string(username)) - ->password(std::string(password)) - ->database(std::string(database ? database : "")) - ->build(); - CTableSession_ tmp{}; - tmp.cpp = std::move(tableSession); + TableSessionBuilder builder; + builder.host(std::string(host)) + ->rpcPort(rpcPort) + ->username(std::string(username)) + ->password(std::string(password)) + ->database(std::string(database ? database : "")); auto* cts = new CTableSession_(); - cts->cpp = std::move(tmp.cpp); + cts->cpp = createTableSession(&builder); return cts; } catch (const std::exception& e) { handleException(e); @@ -364,16 +480,13 @@ CTableSession* ts_table_session_new_multi_node(const char* const* nodeUrls, int clearError(); try { auto urls = toStringVec(nodeUrls, urlCount); - std::unique_ptr builder(new TableSessionBuilder()); - auto tableSession = builder->nodeUrls(urls) - ->username(std::string(username)) - ->password(std::string(password)) - ->database(std::string(database ? database : "")) - ->build(); - CTableSession_ tmp{}; - tmp.cpp = std::move(tableSession); + TableSessionBuilder builder; + builder.nodeUrls(urls) + ->username(std::string(username)) + ->password(std::string(password)) + ->database(std::string(database ? database : "")); auto* cts = new CTableSession_(); - cts->cpp = std::move(tmp.cpp); + cts->cpp = createTableSession(&builder); return cts; } catch (const std::exception& e) { handleException(e); @@ -390,6 +503,7 @@ TsStatus ts_table_session_open(CTableSession* session) { if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); try { + applyPendingSslConfig(session); session->cpp->open(); return TS_OK; } catch (const std::exception& e) { @@ -409,6 +523,94 @@ TsStatus ts_table_session_close(CTableSession* session) { } } +TsStatus ts_table_session_set_use_ssl(CTableSession* session, bool useSsl) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + session->sslConfig.useSsl = useSsl; + session->sslConfigured = true; + return TS_OK; +} + +TsStatus ts_table_session_set_ssl_protocol(CTableSession* session, const char* sslProtocol) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + TsStatus status = setSslStringField(session->sslConfig.sslProtocol, sslProtocol, "sslProtocol"); + if (status == TS_OK) { + session->sslConfigured = true; + } + return status; +} + +TsStatus ts_table_session_set_trust_store(CTableSession* session, const char* trustStore, + const char* trustStorePwd) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + TsStatus status = setSslStringField(session->sslConfig.trustStore, trustStore, "trustStore"); + if (status != TS_OK) { + return status; + } + if (trustStorePwd != nullptr) { + session->sslConfig.trustStorePwd = trustStorePwd; + } + session->sslConfigured = true; + return TS_OK; +} + +TsStatus ts_table_session_set_key_store(CTableSession* session, const char* keyStore, + const char* keyStorePwd) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + TsStatus status = setSslStringField(session->sslConfig.keyStore, keyStore, "keyStore"); + if (status != TS_OK) { + return status; + } + if (keyStorePwd != nullptr) { + session->sslConfig.keyStorePwd = keyStorePwd; + } + session->sslConfigured = true; + return TS_OK; +} + +TsStatus ts_table_session_set_tlcp_pem_files(CTableSession* session, const char* certChainFile, + const char* privateKeyFile, + const char* privateKeyPwd) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + TsStatus status = + setSslStringField(session->sslConfig.tlcpCertChainFile, certChainFile, "certChainFile"); + if (status != TS_OK) { + return status; + } + status = + setSslStringField(session->sslConfig.tlcpPrivateKeyFile, privateKeyFile, "privateKeyFile"); + if (status != TS_OK) { + return status; + } + if (privateKeyPwd != nullptr) { + session->sslConfig.tlcpPrivateKeyPwd = privateKeyPwd; + } + session->sslConfigured = true; + return TS_OK; +} + +TsStatus ts_table_session_set_trust_cert_file_path(CTableSession* session, + const char* trustCertFilePath) { + clearError(); + if (!session) + return setError(TS_ERR_NULL_PTR, "session is null"); + TsStatus status = setSslStringField(session->sslConfig.trustCertFilePath, trustCertFilePath, + "trustCertFilePath"); + if (status == TS_OK) { + session->sslConfigured = true; + } + return status; +} + /* ============================================================ * Timezone * ============================================================ */ diff --git a/iotdb-client/client-cpp/src/session/SessionPool.cpp b/iotdb-client/client-cpp/src/session/SessionPool.cpp index a828f0ac2c6d..1f5453c994ea 100644 --- a/iotdb-client/client-cpp/src/session/SessionPool.cpp +++ b/iotdb-client/client-cpp/src/session/SessionPool.cpp @@ -109,6 +109,46 @@ SessionPool& SessionPool::setTrustCertFilePath(std::string path) { return *this; } +SessionPool& SessionPool::setSslProtocol(std::string sslProtocol) { + sslProtocol_ = std::move(sslProtocol); + return *this; +} + +SessionPool& SessionPool::setTrustStore(std::string trustStore) { + trustStore_ = std::move(trustStore); + return *this; +} + +SessionPool& SessionPool::setTrustStorePwd(std::string trustStorePwd) { + trustStorePwd_ = std::move(trustStorePwd); + return *this; +} + +SessionPool& SessionPool::setKeyStore(std::string keyStore) { + keyStore_ = std::move(keyStore); + return *this; +} + +SessionPool& SessionPool::setKeyStorePwd(std::string keyStorePwd) { + keyStorePwd_ = std::move(keyStorePwd); + return *this; +} + +SessionPool& SessionPool::setTlcpCertChainFile(std::string path) { + tlcpCertChainFile_ = std::move(path); + return *this; +} + +SessionPool& SessionPool::setTlcpPrivateKeyFile(std::string path) { + tlcpPrivateKeyFile_ = std::move(path); + return *this; +} + +SessionPool& SessionPool::setTlcpPrivateKeyPwd(std::string password) { + tlcpPrivateKeyPwd_ = std::move(password); + return *this; +} + std::shared_ptr SessionPool::constructNewSession() { AbstractSessionBuilder builder; builder.host = host_; @@ -126,6 +166,14 @@ std::shared_ptr SessionPool::constructNewSession() { builder.connectTimeoutMs = connectTimeoutMs_; builder.useSSL = useSSL_; builder.trustCertFilePath = trustCertFilePath_; + builder.sslProtocol = sslProtocol_; + builder.trustStore = trustStore_; + builder.trustStorePwd = trustStorePwd_; + builder.keyStore = keyStore_; + builder.keyStorePwd = keyStorePwd_; + builder.tlcpCertChainFile = tlcpCertChainFile_; + builder.tlcpPrivateKeyFile = tlcpPrivateKeyFile_; + builder.tlcpPrivateKeyPwd = tlcpPrivateKeyPwd_; auto session = std::make_shared(&builder); session->open(enableRPCCompression_, connectTimeoutMs_); diff --git a/iotdb-client/client-cpp/src/session/TableSession.cpp b/iotdb-client/client-cpp/src/session/TableSession.cpp index 9cd80b7dd789..4c7fc9b5edb1 100644 --- a/iotdb-client/client-cpp/src/session/TableSession.cpp +++ b/iotdb-client/client-cpp/src/session/TableSession.cpp @@ -20,6 +20,7 @@ // This file is a translation of the Java file iotdb-client/session/src/main/java/org/apache/iotdb/session/TableSession.java #include "TableSession.h" +#include "RpcSslUtils.h" #include "SessionDataSet.h" void TableSession::insert(Tablet& tablet, bool sorted) { @@ -43,4 +44,8 @@ void TableSession::open(bool enableRPCCompression) { } void TableSession::close() { session_->close(); +} + +void TableSession::setSslConfig(const SslConfig& sslConfig) { + session_->setSslConfig(sslConfig); } \ No newline at end of file diff --git a/iotdb-client/client-cpp/test/CMakeLists.txt b/iotdb-client/client-cpp/test/CMakeLists.txt index b4a7cf1f7678..a8c55d95216f 100644 --- a/iotdb-client/client-cpp/test/CMakeLists.txt +++ b/iotdb-client/client-cpp/test/CMakeLists.txt @@ -31,6 +31,7 @@ if(CATCH2_INCLUDE_DIR) else() set(_catch2_include_dir "${CMAKE_CURRENT_BINARY_DIR}/catch2") endif() + set(_catch2_header "${_catch2_include_dir}/catch.hpp") if(NOT EXISTS "${_catch2_header}") file(MAKE_DIRECTORY "${_catch2_include_dir}") @@ -38,36 +39,130 @@ if(NOT EXISTS "${_catch2_header}") file(DOWNLOAD "${CATCH2_URL}" "${_catch2_header}" SHOW_PROGRESS TLS_VERIFY ON) endif() -set(_test_targets +set(_plain_test_targets session_tests session_relational_tests session_c_tests session_c_relational_tests session_utils_tests) +set(_rpc_ssl_test_targets rpc_ssl_utils_tests rpc_ntls_utils_tests) + +set(_test_targets ${_plain_test_targets} ${_rpc_ssl_test_targets}) + add_executable(session_tests main.cpp cpp/sessionIT.cpp) add_executable(session_relational_tests main_Relational.cpp cpp/sessionRelationalIT.cpp) add_executable(session_c_tests main_c.cpp cpp/sessionCIT.cpp) add_executable(session_c_relational_tests main_c_Relational.cpp cpp/sessionCRelationalIT.cpp) add_executable(session_utils_tests main_utils.cpp cpp/sessionUtilsTest.cpp) +if(IOTDB_NTLS_PROVIDER STREQUAL "GMSSL") + add_executable(rpc_ssl_utils_tests + main_rpc_ssl.cpp + cpp/RpcSslUtilsTest.cpp) + add_executable(rpc_ntls_utils_tests + main_rpc_ntls.cpp + cpp/RpcGmsslNtlsTest.cpp) +else() + add_executable(rpc_ssl_utils_tests + main_rpc_ssl.cpp + cpp/RpcSslUtilsTest.cpp + cpp/RpcSslTlsMutualAuthTest.cpp + cpp/RpcSslIotdbE2eTest.cpp + cpp/SslTestFixtures.cpp + cpp/ItSslConnection.cpp) + add_executable(rpc_ntls_utils_tests + main_rpc_ntls.cpp + cpp/RpcSslTlcpMutualAuthTest.cpp + cpp/RpcNtlsE2eTest.cpp + cpp/SslTestFixtures.cpp) +endif() foreach(_t IN LISTS _test_targets) target_include_directories(${_t} PRIVATE "${_catch2_include_dir}" + "${CMAKE_CURRENT_SOURCE_DIR}/cpp" "${CMAKE_CURRENT_SOURCE_DIR}/../src/rpc" "${THRIFT_GEN_CPP_DIR}" "${THRIFT_INCLUDE_DIR}") if(BOOST_INCLUDE_DIR) target_include_directories(${_t} PRIVATE "${BOOST_INCLUDE_DIR}") endif() + if(WITH_SSL AND IOTDB_NTLS_PROVIDER STREQUAL "TONGSUO") + # Keep Homebrew OpenSSL headers from shadowing Tongsuo for test-only sources. + set_target_properties(${_t} PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) + target_include_directories(${_t} BEFORE PRIVATE "${OPENSSL_INCLUDE_DIR}") + endif() target_link_libraries(${_t} PRIVATE iotdb_session) if(WITH_SSL) - target_link_libraries(${_t} PRIVATE OpenSSL::SSL OpenSSL::Crypto) + if(IOTDB_NTLS_PROVIDER STREQUAL "GMSSL") + target_link_libraries(${_t} PRIVATE IoTDB::gmssl) + else() + target_link_libraries(${_t} PRIVATE OpenSSL::SSL OpenSSL::Crypto) + endif() endif() endforeach() +foreach(_t IN LISTS _rpc_ssl_test_targets) + # Catch2 2.13.7 declares a SIGSTKSZ-sized static array, which is not a + # constant expression with newer glibc versions. + target_compile_definitions(${_t} PRIVATE CATCH_CONFIG_NO_POSIX_SIGNALS=1) +endforeach() + +if(WITH_SSL) + if(IOTDB_NTLS_PROVIDER STREQUAL "TONGSUO") + target_compile_definitions(rpc_ssl_utils_tests PRIVATE IOTDB_RPC_SSL_IT=1) + endif() + + if(IOTDB_NTLS_PROVIDER STREQUAL "GMSSL") + if(WIN32) + set(_iotdb_openssl_executable "${IOTDB_GMSSL_ROOT_DIR}/bin/gmssl.exe") + else() + set(_iotdb_openssl_executable "${IOTDB_GMSSL_ROOT_DIR}/bin/gmssl") + endif() + elseif(WIN32) + set(_iotdb_openssl_executable "${OPENSSL_ROOT_DIR}/bin/openssl.exe") + else() + set(_iotdb_openssl_executable "${OPENSSL_ROOT_DIR}/bin/openssl") + endif() + file(TO_CMAKE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/fixtures" _iotdb_test_fixtures_dir) + file(TO_CMAKE_PATH "${_iotdb_openssl_executable}" _iotdb_openssl_executable_cmake) + file(TO_CMAKE_PATH "${OPENSSL_ROOT_DIR}" _iotdb_openssl_root_dir) + string(REPLACE "\\" "/" _iotdb_test_fixtures_dir_fwd "${_iotdb_test_fixtures_dir}") + string(REPLACE "\\" "/" _iotdb_openssl_executable_fwd "${_iotdb_openssl_executable_cmake}") + string(REPLACE "\\" "/" _iotdb_openssl_root_dir_fwd "${_iotdb_openssl_root_dir}") + foreach(_t IN LISTS _rpc_ssl_test_targets) + target_compile_definitions(${_t} PRIVATE + IOTDB_TEST_FIXTURES_DIR="${_iotdb_test_fixtures_dir_fwd}") + add_custom_command(TARGET ${_t} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/fixtures" + "$/fixtures" + COMMENT "Copy SSL test fixtures next to ${_t}") + endforeach() + target_compile_definitions(rpc_ssl_utils_tests PRIVATE + IOTDB_OPENSSL_EXECUTABLE="${_iotdb_openssl_executable_fwd}" + IOTDB_OPENSSL_ROOT_DIR="${_iotdb_openssl_root_dir_fwd}") + target_compile_definitions(rpc_ntls_utils_tests PRIVATE + IOTDB_OPENSSL_EXECUTABLE="${_iotdb_openssl_executable_fwd}" + IOTDB_OPENSSL_ROOT_DIR="${_iotdb_openssl_root_dir_fwd}") + if(IOTDB_NTLS_PROVIDER STREQUAL "GMSSL") + target_compile_definitions(rpc_ntls_utils_tests PRIVATE + IOTDB_GMSSL_EXECUTABLE="${_iotdb_openssl_executable_fwd}") + endif() + if(WIN32) + foreach(_t IN LISTS _rpc_ssl_test_targets) + target_link_libraries(${_t} PRIVATE ws2_32 "${THRIFT_STATIC_LIB_PATH}") + endforeach() + else() + foreach(_t IN LISTS _rpc_ssl_test_targets) + target_link_libraries(${_t} PRIVATE iotdb_thrift_static) + endforeach() + endif() +endif() if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT MSVC) - foreach(_t IN LISTS _test_targets) + set(_iotdb_asan_targets session_tests session_relational_tests session_c_tests + session_c_relational_tests session_utils_tests rpc_ssl_utils_tests rpc_ntls_utils_tests) + foreach(_t IN LISTS _iotdb_asan_targets) target_compile_options(${_t} PRIVATE -fsanitize=address -fno-omit-frame-pointer) target_link_options(${_t} PRIVATE -fsanitize=address) endforeach() @@ -89,10 +184,21 @@ if(MSVC) add_test(NAME sessionCIT CONFIGURATIONS Release COMMAND session_c_tests) add_test(NAME sessionCRelationalIT CONFIGURATIONS Release COMMAND session_c_relational_tests) add_test(NAME sessionUtilsTest CONFIGURATIONS Release COMMAND session_utils_tests) + add_test(NAME rpcSslUtilsTest CONFIGURATIONS Release COMMAND rpc_ssl_utils_tests) + add_test(NAME rpcNtlsUtilsTest CONFIGURATIONS Release COMMAND rpc_ntls_utils_tests) foreach(_t IN LISTS _test_targets) add_custom_command(TARGET ${_t} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $) + if(WITH_SSL AND ${_t} IN_LIST _rpc_ssl_test_targets) + _iotdb_collect_openssl_windows_dlls(_iotdb_ssl_runtime_dlls) + foreach(_ssl_dll IN LISTS _iotdb_ssl_runtime_dlls) + add_custom_command(TARGET ${_t} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${_ssl_dll}" $ + COMMENT "Copy bundled SSL runtime next to ${_t}") + endforeach() + endif() endforeach() else() add_test(NAME sessionIT COMMAND session_tests) @@ -100,10 +206,50 @@ else() add_test(NAME sessionCIT COMMAND session_c_tests) add_test(NAME sessionCRelationalIT COMMAND session_c_relational_tests) add_test(NAME sessionUtilsTest COMMAND session_utils_tests) + add_test(NAME rpcSslUtilsTest COMMAND rpc_ssl_utils_tests) + add_test(NAME rpcNtlsUtilsTest COMMAND rpc_ntls_utils_tests) + foreach(_t IN LISTS _test_targets) + add_custom_command(TARGET ${_t} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ $ + COMMENT "Copy IoTDB runtime library next to ${_t}") + if(WITH_SSL AND ${_t} IN_LIST _rpc_ssl_test_targets) + foreach(_ssl_lib IN LISTS IOTDB_NTLS_RUNTIME_LIBRARIES) + if(_ssl_lib AND EXISTS "${_ssl_lib}") + add_custom_command(TARGET ${_t} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${_ssl_lib}" $ + COMMENT "Copy bundled SSL runtime next to ${_t}") + endif() + endforeach() + endif() + endforeach() +endif() + +if(WITH_SSL AND IOTDB_NTLS_PROVIDER STREQUAL "TONGSUO") + if(MSVC) + add_test(NAME rpcSslMutualIotdbTest CONFIGURATIONS Release + COMMAND rpc_ssl_utils_tests "[iotdb][e2e]") + else() + add_test(NAME rpcSslMutualIotdbTest COMMAND rpc_ssl_utils_tests "[iotdb][e2e]") + endif() + set_tests_properties(rpcSslMutualIotdbTest PROPERTIES + ENVIRONMENT "IOTDB_CPP_SSL_MUTUAL_AUTH=1") endif() # Run sequentially: parallel ctest overloads the single local IoTDB instance. # sessionUtilsTest is a pure unit test and can run anytime. set_tests_properties( sessionIT sessionRelationalIT sessionCIT sessionCRelationalIT - PROPERTIES RUN_SERIAL TRUE) + PROPERTIES LABELS "plain" RUN_SERIAL TRUE RESOURCE_LOCK iotdb_cpp_it_server) +set_tests_properties( + rpcSslUtilsTest + PROPERTIES LABELS "ssl" RUN_SERIAL TRUE RESOURCE_LOCK iotdb_cpp_it_server) +set_tests_properties( + rpcNtlsUtilsTest + PROPERTIES LABELS "ntls" RUN_SERIAL TRUE RESOURCE_LOCK iotdb_cpp_it_server) +if(TEST rpcSslMutualIotdbTest) + set_tests_properties( + rpcSslMutualIotdbTest + PROPERTIES LABELS "mtls" RUN_SERIAL TRUE RESOURCE_LOCK iotdb_cpp_it_server) +endif() diff --git a/iotdb-client/client-cpp/test/cpp/ItSslConnection.cpp b/iotdb-client/client-cpp/test/cpp/ItSslConnection.cpp new file mode 100644 index 000000000000..077afa78e487 --- /dev/null +++ b/iotdb-client/client-cpp/test/cpp/ItSslConnection.cpp @@ -0,0 +1,206 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "ItSslConnection.h" + +#if defined(WITH_SSL) && defined(IOTDB_RPC_SSL_IT) + +#include +#include +#include + +#include "SessionPool.h" + +#if defined(_WIN32) +#include +#else +#include +#endif + +namespace { + +constexpr const char* kStorePassword = "thrift"; + +std::string joinPath(const std::string& base, const std::string& name) { +#if defined(_WIN32) + const char sep = '\\'; +#else + const char sep = '/'; +#endif + if (base.empty()) { + return name; + } + if (base.back() == '/' || base.back() == '\\') { + return base + name; + } + return base + sep + name; +} + +std::string executableDir() { +#if defined(_WIN32) + char buffer[MAX_PATH]; + const DWORD len = GetModuleFileNameA(nullptr, buffer, MAX_PATH); + if (len == 0 || len == MAX_PATH) { + return "."; + } + std::string path(buffer, len); + const auto pos = path.find_last_of("\\/"); + return pos == std::string::npos ? "." : path.substr(0, pos); +#else + char buffer[4096]; + const ssize_t len = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1); + if (len <= 0) { + return "."; + } + buffer[len] = '\0'; + std::string path(buffer); + const auto pos = path.find_last_of('/'); + return pos == std::string::npos ? "." : path.substr(0, pos); +#endif +} + +bool pathExists(const std::string& path) { + std::ifstream in(path.c_str(), std::ios::binary); + return in.good(); +} + +std::string fixturesRoot() { +#ifdef IOTDB_TEST_FIXTURES_DIR + const std::string configured = IOTDB_TEST_FIXTURES_DIR; + if (pathExists(joinPath(configured, "tls/tls-trust.p12")) || + pathExists(joinPath(configured, "tls\\tls-trust.p12"))) { + return configured; + } +#endif + const std::string copied = joinPath(executableDir(), "fixtures"); + if (pathExists(joinPath(copied, "tls/tls-trust.p12")) || + pathExists(joinPath(copied, "tls\\tls-trust.p12"))) { + return copied; + } +#ifdef IOTDB_TEST_FIXTURES_DIR + return IOTDB_TEST_FIXTURES_DIR; +#else + return copied; +#endif +} + +std::string tlsTrustStorePath() { + static const std::string path = joinPath(joinPath(fixturesRoot(), "tls"), "tls-trust.p12"); + return path; +} + +std::string tlsKeyStorePath() { + static const std::string path = joinPath(joinPath(fixturesRoot(), "tls"), "tls-client.p12"); + return path; +} + +bool mutualTlsEnabled() { + const char* value = std::getenv("IOTDB_CPP_SSL_MUTUAL_AUTH"); + return value != nullptr && std::string(value) == "1"; +} + +} // namespace + +void it_ssl_configure_tree_session(CSession* session) { + if (session == nullptr) { + return; + } + ts_session_set_use_ssl(session, true); + ts_session_set_ssl_protocol(session, "TLS"); + ts_session_set_trust_store(session, tlsTrustStorePath().c_str(), kStorePassword); + if (mutualTlsEnabled()) { + ts_session_set_key_store(session, tlsKeyStorePath().c_str(), kStorePassword); + } +} + +void it_ssl_configure_table_session(CTableSession* session) { + if (session == nullptr) { + return; + } + ts_table_session_set_use_ssl(session, true); + ts_table_session_set_ssl_protocol(session, "TLS"); + ts_table_session_set_trust_store(session, tlsTrustStorePath().c_str(), kStorePassword); + if (mutualTlsEnabled()) { + ts_table_session_set_key_store(session, tlsKeyStorePath().c_str(), kStorePassword); + } +} + +namespace itssl { + +void configureSessionBuilder(SessionBuilder& builder) { + builder.useSSL(true) + ->sslProtocol("TLS") + ->trustStore(tlsTrustStorePath()) + ->trustStorePwd(kStorePassword); + if (mutualTlsEnabled()) { + builder.keyStore(tlsKeyStorePath())->keyStorePwd(kStorePassword); + } +} + +void configureTableSessionBuilder(TableSessionBuilder& builder) { + builder.useSSL(true) + ->sslProtocol("TLS") + ->trustStore(tlsTrustStorePath()) + ->trustStorePwd(kStorePassword); + if (mutualTlsEnabled()) { + builder.keyStore(tlsKeyStorePath())->keyStorePwd(kStorePassword); + } +} + +void configureSessionPoolBuilder(SessionPoolBuilder& builder) { + builder.useSSL(true) + ->sslProtocol("TLS") + ->trustStore(tlsTrustStorePath()) + ->trustStorePwd(kStorePassword); + if (mutualTlsEnabled()) { + builder.keyStore(tlsKeyStorePath())->keyStorePwd(kStorePassword); + } +} + +std::shared_ptr newOpenedTreeSession() { + SessionBuilder builder; + builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root"); + configureSessionBuilder(builder); + std::shared_ptr session = builder.build(); + session->open(false); + return session; +} + +std::shared_ptr newOpenedTableSession() { + TableSessionBuilder builder; + builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root"); + configureTableSessionBuilder(builder); + std::shared_ptr session = builder.build(); + session->open(); + return session; +} + +} // namespace itssl + +#else // WITH_SSL && IOTDB_RPC_SSL_IT + +void it_ssl_configure_tree_session(CSession* session) { + (void)session; +} + +void it_ssl_configure_table_session(CTableSession* session) { + (void)session; +} + +#endif // WITH_SSL && IOTDB_RPC_SSL_IT diff --git a/iotdb-client/client-cpp/test/cpp/ItSslConnection.h b/iotdb-client/client-cpp/test/cpp/ItSslConnection.h new file mode 100644 index 000000000000..ac13c02e8d12 --- /dev/null +++ b/iotdb-client/client-cpp/test/cpp/ItSslConnection.h @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 IOTDB_IT_SSL_CONNECTION_H +#define IOTDB_IT_SSL_CONNECTION_H + +#include "SessionC.h" + +#ifdef __cplusplus +#include + +#include "Session.h" +#include "SessionBuilder.h" +#include "SessionPool.h" +#include "TableSession.h" +#include "TableSessionBuilder.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** Apply TLS settings for integration tests, including a client key store in mutual TLS mode. */ +void it_ssl_configure_tree_session(CSession* session); +void it_ssl_configure_table_session(CTableSession* session); + +#ifdef __cplusplus +} + +namespace itssl { + +#if defined(WITH_SSL) && defined(IOTDB_RPC_SSL_IT) +void configureSessionBuilder(SessionBuilder& builder); +void configureTableSessionBuilder(TableSessionBuilder& builder); +void configureSessionPoolBuilder(SessionPoolBuilder& builder); +std::shared_ptr newOpenedTreeSession(); +std::shared_ptr newOpenedTableSession(); +#endif + +} // namespace itssl +#endif + +#endif // IOTDB_IT_SSL_CONNECTION_H diff --git a/iotdb-client/client-cpp/test/cpp/RpcGmsslNtlsTest.cpp b/iotdb-client/client-cpp/test/cpp/RpcGmsslNtlsTest.cpp new file mode 100644 index 000000000000..f085b6779d28 --- /dev/null +++ b/iotdb-client/client-cpp/test/cpp/RpcGmsslNtlsTest.cpp @@ -0,0 +1,338 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + +#include "Common.h" +#include "GmsslTlcpSocket.h" +#include "RpcSslUtils.h" + +#if defined(_WIN32) +#include +#else +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include + +namespace { + +std::string fixture(const std::string& name) { + return std::string(IOTDB_TEST_FIXTURES_DIR) + "/gmssl/" + name; +} + +void initializeSockets() { +#if defined(_WIN32) + static const bool winsockInitialized = [] { + WSADATA data; + return WSAStartup(MAKEWORD(2, 2), &data) == 0; + }(); + REQUIRE(winsockInitialized); +#endif +} + +void closeSocket(tls_socket_t socket) { +#if defined(_WIN32) + closesocket(socket); +#else + close(socket); +#endif +} + +bool isValidSocket(tls_socket_t socket) { +#if defined(_WIN32) + return socket != INVALID_SOCKET; +#else + return socket >= 0; +#endif +} + +void setSocketTimeout(tls_socket_t socket) { +#if defined(_WIN32) + const DWORD timeout = 3000; + setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeout), + sizeof(timeout)); + setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&timeout), + sizeof(timeout)); +#else + const timeval timeout{3, 0}; + setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); +#endif +} + +class GmsslTestServer { +public: + explicit GmsslTestServer(bool requireClientCertificate, bool exchangeFrame = false) + : requireClientCertificate_(requireClientCertificate), exchangeFrame_(exchangeFrame) { + initializeSockets(); + listener_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + REQUIRE(isValidSocket(listener_)); + sockaddr_in address{}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = 0; + REQUIRE(::bind(listener_, reinterpret_cast(&address), sizeof(address)) == 0); + REQUIRE(listen(listener_, 1) == 0); +#if defined(_WIN32) + int length = sizeof(address); +#else + socklen_t length = sizeof(address); +#endif + REQUIRE(getsockname(listener_, reinterpret_cast(&address), &length) == 0); + port_ = ntohs(address.sin_port); + thread_ = std::thread(&GmsslTestServer::serve, this); + } + + ~GmsslTestServer() { + if (isValidSocket(listener_)) { + closeSocket(listener_); + listener_ = invalidSocket(); + } + if (thread_.joinable()) { + thread_.join(); + } + } + + int port() const { + return port_; + } + + void finish() { + if (thread_.joinable()) { + thread_.join(); + } + if (isValidSocket(listener_)) { + closeSocket(listener_); + listener_ = invalidSocket(); + } + REQUIRE(serverError_.empty()); + } + +private: + static tls_socket_t invalidSocket() { +#if defined(_WIN32) + return INVALID_SOCKET; +#else + return -1; +#endif + } + + static bool receiveAll(TLS_CONNECT* connection, uint8_t* data, size_t size) { + size_t offset = 0; + while (offset < size) { + size_t received = 0; + if (tls_recv(connection, data + offset, size - offset, &received) != 1 || received == 0) { + return false; + } + offset += received; + } + return true; + } + + static bool sendAll(TLS_CONNECT* connection, const uint8_t* data, size_t size) { + size_t offset = 0; + while (offset < size) { + size_t sent = 0; + if (tls_send(connection, data + offset, size - offset, &sent) != 1 || sent == 0) { + return false; + } + offset += sent; + } + return true; + } + + void serve() { + tls_socket_t client = accept(listener_, nullptr, nullptr); + if (!isValidSocket(client)) { + serverError_ = "accept failed"; + return; + } + setSocketTimeout(client); + + TLS_CTX context{}; + TLS_CONNECT connection{}; + bool contextInitialized = false; + bool connectionInitialized = false; + bool handshakeComplete = false; + const int cipherSuite = TLS_cipher_ecc_sm4_cbc_sm3; + if (tls_ctx_init(&context, TLS_protocol_tlcp, 0) != 1) { + serverError_ = "server context initialization failed"; + goto cleanup; + } + contextInitialized = true; + if (tls_ctx_set_cipher_suites(&context, &cipherSuite, 1) != 1 || + tls_ctx_set_tlcp_server_certificate_and_keys(&context, fixture("server-certs.pem").c_str(), + fixture("server-keys.pem").c_str(), + "thrift") != 1) { + serverError_ = "server credentials failed"; + goto cleanup; + } + if (requireClientCertificate_ && + tls_ctx_set_ca_certificates(&context, fixture("ca.crt").c_str(), + TLS_DEFAULT_VERIFY_DEPTH) != 1) { + serverError_ = "server CA configuration failed"; + goto cleanup; + } + if (tls_init(&connection, &context) != 1 || tls_set_socket(&connection, client) != 1) { + serverError_ = "server connection initialization failed"; + goto cleanup; + } + connectionInitialized = true; + if (tls_do_handshake(&connection) != 1) { + serverError_ = "server handshake failed"; + goto cleanup; + } + handshakeComplete = true; + if (exchangeFrame_) { + std::array request{}; + if (!receiveAll(&connection, request.data(), request.size()) || + request != std::array{{0, 0, 0, 4, 'p', 'i', 'n', 'g'}}) { + serverError_ = "server framed request mismatch"; + goto cleanup; + } + const std::array response{{0, 0, 0, 4, 'p', 'o', 'n', 'g'}}; + if (!sendAll(&connection, response.data(), response.size())) { + serverError_ = "server framed response failed"; + } + } + + cleanup: + if (connectionInitialized) { + if (handshakeComplete) { + (void)tls_shutdown(&connection); + } + tls_client_verify_cleanup(&connection.client_verify_ctx); + tls_cleanup(&connection); + } + if (contextInitialized) { + tls_ctx_cleanup(&context); + } + closeSocket(client); + } + + tls_socket_t listener_ = invalidSocket(); + std::thread thread_; + std::string serverError_; + bool requireClientCertificate_; + bool exchangeFrame_; + int port_; +}; + +SslConfig gmsslConfig(bool mutual) { + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLCP"; + config.trustStore = fixture("ca.crt"); + if (mutual) { + config.tlcpCertChainFile = fixture("client.crt"); + config.tlcpPrivateKeyFile = fixture("client.key"); + config.tlcpPrivateKeyPwd = "thrift"; + } + return config; +} + +} // namespace + +TEST_CASE("GmSSL configures a one-way TLCP client context", "[rpc][ntls][gmssl]") { + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLCP"; + config.trustStore = fixture("ca.crt"); + + TLS_CTX context{}; + REQUIRE(tls_ctx_init(&context, TLS_protocol_tlcp, 1) == 1); + REQUIRE_NOTHROW(RpcSslUtils::configureGmsslTlcpContext(&context, config)); + tls_ctx_cleanup(&context); +} + +TEST_CASE("GmSSL loads mutual TLCP PEM bundles", "[rpc][ntls][gmssl]") { + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLCP"; + config.trustStore = fixture("ca.crt"); + config.tlcpCertChainFile = fixture("client.crt"); + config.tlcpPrivateKeyFile = fixture("client.key"); + config.tlcpPrivateKeyPwd = "thrift"; + + TLS_CTX context{}; + REQUIRE(tls_ctx_init(&context, TLS_protocol_tlcp, 1) == 1); + REQUIRE_NOTHROW(RpcSslUtils::configureGmsslTlcpContext(&context, config)); + tls_ctx_cleanup(&context); +} + +TEST_CASE("GmSSL rejects unsupported TLS and PKCS12 client stores", "[rpc][ntls][gmssl]") { + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLS"; + REQUIRE_THROWS_WITH(RpcSslUtils::validateGmsslTlcpConfig(config), + Catch::Contains("supports TLCP only")); + + config.sslProtocol = "TLCP"; + config.keyStore = "client.p12"; + REQUIRE_THROWS_WITH(RpcSslUtils::validateGmsslTlcpConfig(config), + Catch::Contains("does not support PKCS12 keyStore")); +} + +TEST_CASE("GmSSL native transport completes one-way TLCP handshake", "[rpc][ntls][gmssl][e2e]") { + GmsslTestServer server(false); + GmsslTlcpSocket socket("127.0.0.1", server.port(), gmsslConfig(false)); + socket.setConnTimeout(3000); + REQUIRE_NOTHROW(socket.open()); + socket.close(); + server.finish(); +} + +TEST_CASE("GmSSL native transport completes mutual TLCP handshake", "[rpc][ntls][gmssl][e2e]") { + GmsslTestServer server(true); + GmsslTlcpSocket socket("127.0.0.1", server.port(), gmsslConfig(true)); + socket.setConnTimeout(3000); + REQUIRE_NOTHROW(socket.open()); + socket.close(); + server.finish(); +} + +TEST_CASE("GmSSL transport exchanges a Thrift frame and peeks decrypted data", + "[rpc][ntls][gmssl][e2e]") { + GmsslTestServer server(false, true); + auto socket = std::make_shared("127.0.0.1", server.port(), gmsslConfig(false)); + socket->setConnTimeout(3000); + apache::thrift::transport::TFramedTransport transport(socket); + + transport.open(); + const std::array request{{'p', 'i', 'n', 'g'}}; + transport.write(request.data(), request.size()); + transport.flush(); + REQUIRE(transport.peek()); + std::array response{}; + REQUIRE(transport.readAll(response.data(), response.size()) == response.size()); + const std::array expected{{'p', 'o', 'n', 'g'}}; + REQUIRE(response == expected); + transport.close(); + server.finish(); +} diff --git a/iotdb-client/client-cpp/test/cpp/RpcNtlsE2eTest.cpp b/iotdb-client/client-cpp/test/cpp/RpcNtlsE2eTest.cpp new file mode 100644 index 000000000000..edb422c80117 --- /dev/null +++ b/iotdb-client/client-cpp/test/cpp/RpcNtlsE2eTest.cpp @@ -0,0 +1,154 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + +#include +#include +#include +#include +#include + +#include "Common.h" +#include "RpcSslUtils.h" +#include "SslTestFixtures.h" + +namespace { + +bool fixtureExists(const std::string& path) { + std::ifstream in(path.c_str(), std::ios::binary); + return in.good(); +} + +SslConfig tlcpTrustOnlyConfig() { + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLCP"; + config.trustStore = ssltest::tlcpFixture("tlcp-trust.p12"); + config.trustStorePwd = ssltest::kStorePassword; + return config; +} + +SslConfig tlcpMutualConfig() { + SslConfig config = tlcpTrustOnlyConfig(); + config.keyStore = ssltest::buildTlcpDualKeyStoreP12(); + config.keyStorePwd = ssltest::kStorePassword; + return config; +} + +void concatenate(const std::string& output, std::initializer_list inputs) { + std::ofstream out(output, std::ios::binary | std::ios::trunc); + REQUIRE(out.good()); + for (const auto& input : inputs) { + std::ifstream in(input, std::ios::binary); + REQUIRE(in.good()); + out << in.rdbuf() << '\n'; + } +} + +struct IntermediatePemCredentials { + std::string certChain = "tongsuo-intermediate-client-certs.pem"; + std::string privateKeys = "tongsuo-intermediate-client-keys.pem"; + + IntermediatePemCredentials() { + concatenate(certChain, {ssltest::tlcpFixture("intermediate_client_sign.crt"), + ssltest::tlcpFixture("intermediate_client_enc.crt"), + ssltest::tlcpFixture("intermediate_ca.crt")}); + concatenate(privateKeys, {ssltest::tlcpFixture("intermediate_client_sign.key"), + ssltest::tlcpFixture("intermediate_client_enc.key")}); + } + + ~IntermediatePemCredentials() { + std::remove(certChain.c_str()); + std::remove(privateKeys.c_str()); + } +}; + +bool startTlcpServer(ssltest::OpenSslServerProcess& server, bool requireClientCert, + const std::string& clientCaFile = "") { + const std::string caFile = clientCaFile.empty() ? ssltest::tlcpFixture("ca.crt") : clientCaFile; + const std::string signCert = ssltest::tlcpFixture("server_sign.crt"); + const std::string signKey = ssltest::tlcpFixture("server_sign.key"); + const std::string encCert = ssltest::tlcpFixture("server_enc.crt"); + const std::string encKey = ssltest::tlcpFixture("server_enc.key"); + if (!fixtureExists(caFile) || !fixtureExists(signCert) || !fixtureExists(signKey) || + !fixtureExists(encCert) || !fixtureExists(encKey)) { + return false; + } + + std::vector args = { + "-enable_ntls", "-ntls", "-CAfile", caFile, "-sign_cert", signCert, "-sign_key", + signKey, "-enc_cert", encCert, "-enc_key", encKey, "-www", + }; + if (requireClientCert) { + args.push_back("-Verify"); + args.push_back("2"); + args.push_back("-verify_return_error"); + } + return server.start(args) && server.running() && server.port() > 0; +} + +} // namespace + +TEST_CASE("TLCP one-way handshake with openssl NTLS s_server", "[rpc][ntls][e2e]") { +#if WITH_SSL + ssltest::OpenSslServerProcess server; + REQUIRE(startTlcpServer(server, false)); + REQUIRE(ssltest::tlsHandshakeWithSslConfig(tlcpTrustOnlyConfig(), "127.0.0.1", server.port())); + server.stop(); +#endif +} + +TEST_CASE("TLCP one-way auth fails when server requires client certificate", "[rpc][ntls][e2e]") { +#if WITH_SSL + ssltest::OpenSslServerProcess server; + REQUIRE(startTlcpServer(server, true)); + REQUIRE_FALSE( + ssltest::tlsHandshakeWithSslConfig(tlcpTrustOnlyConfig(), "127.0.0.1", server.port())); + server.stop(); +#endif +} + +TEST_CASE("TLCP mutual auth handshake with dual PKCS12 client store", "[rpc][ntls][e2e]") { +#if WITH_SSL + ssltest::OpenSslServerProcess server; + REQUIRE(startTlcpServer(server, true)); + const SslConfig config = tlcpMutualConfig(); + REQUIRE_FALSE(config.keyStore.empty()); + REQUIRE(ssltest::tlsHandshakeWithSslConfig(config, "127.0.0.1", server.port())); + SSL_CTX* ctx = RpcSslUtils::createClientSslContext(config); + REQUIRE(ctx != nullptr); + SSL_CTX_free(ctx); + server.stop(); +#endif +} + +TEST_CASE("TLCP mutual auth sends the intermediate PEM certificate chain", "[rpc][ntls][e2e]") { +#if WITH_SSL + IntermediatePemCredentials files; + ssltest::OpenSslServerProcess server; + REQUIRE(startTlcpServer(server, true, ssltest::tlcpFixture("intermediate_root.crt"))); + + SslConfig config = tlcpTrustOnlyConfig(); + config.tlcpCertChainFile = files.certChain; + config.tlcpPrivateKeyFile = files.privateKeys; + REQUIRE(ssltest::tlsHandshakeWithSslConfig(config, "127.0.0.1", server.port())); + server.stop(); +#endif +} diff --git a/iotdb-client/client-cpp/test/cpp/RpcSslIotdbE2eTest.cpp b/iotdb-client/client-cpp/test/cpp/RpcSslIotdbE2eTest.cpp new file mode 100644 index 000000000000..36cbd73b5326 --- /dev/null +++ b/iotdb-client/client-cpp/test/cpp/RpcSslIotdbE2eTest.cpp @@ -0,0 +1,196 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + +#include +#include + +#include "Common.h" +#include "ItSslConnection.h" +#include "Session.h" +#include "SessionBuilder.h" +#include "SessionC.h" +#include "SessionDataSet.h" +#include "SslTestFixtures.h" +#include "TableSessionBuilder.h" + +#if defined(WITH_SSL) && defined(IOTDB_RPC_SSL_IT) + +TEST_CASE("TLS tree Session connects to IoTDB and runs SQL", "[rpc][ssl][iotdb][e2e]") { + auto session = itssl::newOpenedTreeSession(); + REQUIRE(session != nullptr); + + const std::string database = "root.cpp_ssl_it_tree"; + const std::string timeseries = database + ".d1.s1"; + if (session->checkTimeseriesExists(timeseries)) { + session->deleteTimeseries(timeseries); + } + try { + session->deleteStorageGroup(database); + } catch (...) { + } + + session->setStorageGroup(database); + session->createTimeseries(timeseries, TSDataType::INT32, TSEncoding::PLAIN, + CompressionType::UNCOMPRESSED); + session->insertRecord(database + ".d1", 1, {"s1"}, {"1"}); + + std::unique_ptr dataSet( + session->executeQueryStatement("SELECT s1 FROM " + database + ".d1")); + REQUIRE(dataSet != nullptr); + REQUIRE(dataSet->hasNext()); + std::shared_ptr record = dataSet->next(); + REQUIRE(record != nullptr); + REQUIRE(record->timestamp == 1); + REQUIRE(record->fields.size() == 1); + REQUIRE(record->fields[0].intV.value() == 1); + REQUIRE_FALSE(dataSet->hasNext()); + dataSet->closeOperationHandle(); + + session->deleteTimeseries(timeseries); + session->deleteStorageGroup(database); + session->close(); +} + +TEST_CASE("TLS table Session connects to IoTDB and runs SQL", "[rpc][ssl][iotdb][e2e]") { + auto session = itssl::newOpenedTableSession(); + REQUIRE(session != nullptr); + + session->executeNonQueryStatement("CREATE DATABASE IF NOT EXISTS cpp_ssl_it_table"); + session->executeNonQueryStatement("USE cpp_ssl_it_table"); + session->executeNonQueryStatement( + "CREATE TABLE IF NOT EXISTS ssl_it_table (tag1 STRING TAG, value INT32 FIELD)"); + session->executeNonQueryStatement( + "INSERT INTO ssl_it_table(time, tag1, value) VALUES (1, 't1', 42)"); + + std::unique_ptr dataSet( + session->executeQueryStatement("SELECT time, value FROM ssl_it_table WHERE tag1 = 't1'")); + REQUIRE(dataSet != nullptr); + REQUIRE(dataSet->hasNext()); + std::shared_ptr record = dataSet->next(); + REQUIRE(record != nullptr); + REQUIRE(record->fields.size() == 2); + REQUIRE(record->fields[0].longV.value() == 1); + REQUIRE(record->fields[1].intV.value() == 42); + REQUIRE_FALSE(dataSet->hasNext()); + dataSet->closeOperationHandle(); + + session->executeNonQueryStatement("DROP DATABASE IF EXISTS cpp_ssl_it_table"); + session->close(); +} + +TEST_CASE("TLS C tree Session connects to IoTDB", "[rpc][ssl][iotdb][e2e]") { + CSession* session = ts_session_new("127.0.0.1", 6667, "root", "root"); + REQUIRE(session != nullptr); + it_ssl_configure_tree_session(session); + REQUIRE(ts_session_open(session) == TS_OK); + + const char* path = "root.cpp_ssl_it_c.d1.s1"; + bool exists = false; + REQUIRE(ts_session_check_timeseries_exists(session, path, &exists) == TS_OK); + if (exists) { + REQUIRE(ts_session_delete_timeseries(session, path) == TS_OK); + } + + REQUIRE(ts_session_create_database(session, "root.cpp_ssl_it_c") == TS_OK); + REQUIRE(ts_session_create_timeseries(session, path, TS_TYPE_INT32, TS_ENCODING_PLAIN, + TS_COMPRESSION_UNCOMPRESSED) == TS_OK); + const char* measurements[] = {"s1"}; + const char* values[] = {"1"}; + REQUIRE(ts_session_insert_record_str(session, "root.cpp_ssl_it_c.d1", 1, 1, measurements, + values) == TS_OK); + + CSessionDataSet* dataSet = nullptr; + REQUIRE(ts_session_execute_query(session, "SELECT s1 FROM root.cpp_ssl_it_c.d1", &dataSet) == + TS_OK); + REQUIRE(dataSet != nullptr); + REQUIRE(ts_dataset_has_next(dataSet)); + CRowRecord* record = ts_dataset_next(dataSet); + REQUIRE(record != nullptr); + REQUIRE(ts_row_record_get_timestamp(record) == 1); + REQUIRE(ts_row_record_get_field_count(record) == 1); + REQUIRE(ts_row_record_get_int32(record, 0) == 1); + ts_row_record_destroy(record); + REQUIRE_FALSE(ts_dataset_has_next(dataSet)); + ts_dataset_destroy(dataSet); + + REQUIRE(ts_session_delete_timeseries(session, path) == TS_OK); + REQUIRE(ts_session_delete_database(session, "root.cpp_ssl_it_c") == TS_OK); + REQUIRE(ts_session_close(session) == TS_OK); + ts_session_destroy(session); +} + +TEST_CASE("TLS C table Session connects to IoTDB", "[rpc][ssl][iotdb][e2e]") { + CTableSession* session = ts_table_session_new("127.0.0.1", 6667, "root", "root", ""); + REQUIRE(session != nullptr); + it_ssl_configure_table_session(session); + REQUIRE(ts_table_session_open(session) == TS_OK); + + REQUIRE(ts_table_session_execute_non_query( + session, "CREATE DATABASE IF NOT EXISTS cpp_ssl_it_c_table") == TS_OK); + REQUIRE(ts_table_session_execute_non_query(session, "USE cpp_ssl_it_c_table") == TS_OK); + REQUIRE(ts_table_session_execute_non_query( + session, + "CREATE TABLE IF NOT EXISTS ssl_it_c_table (tag1 STRING TAG, value INT32 FIELD)") == + TS_OK); + REQUIRE(ts_table_session_execute_non_query( + session, "INSERT INTO ssl_it_c_table(time, tag1, value) VALUES (1, 't1', 42)") == + TS_OK); + + CSessionDataSet* dataSet = nullptr; + REQUIRE(ts_table_session_execute_query(session, + "SELECT time, value FROM ssl_it_c_table WHERE tag1 = 't1'", + &dataSet) == TS_OK); + REQUIRE(dataSet != nullptr); + REQUIRE(ts_dataset_has_next(dataSet)); + CRowRecord* record = ts_dataset_next(dataSet); + REQUIRE(record != nullptr); + REQUIRE(ts_row_record_get_field_count(record) >= 2); + REQUIRE(ts_row_record_get_int64(record, 0) == 1); + REQUIRE(ts_row_record_get_int32(record, 1) == 42); + ts_row_record_destroy(record); + ts_dataset_destroy(dataSet); + + REQUIRE(ts_table_session_execute_non_query( + session, "DROP DATABASE IF EXISTS cpp_ssl_it_c_table") == TS_OK); + REQUIRE(ts_table_session_close(session) == TS_OK); + ts_table_session_destroy(session); +} + +TEST_CASE("Plain client cannot connect to TLS-enabled IoTDB", "[rpc][ssl][iotdb][e2e]") { + Session session("127.0.0.1", 6667, "root", "root"); + REQUIRE_THROWS_AS(session.open(false), IoTDBException); +} + +TEST_CASE("TLS client without a key store cannot connect to mutual TLS IoTDB", + "[.][rpc][ssl][iotdb][mutual][e2e]") { + SessionBuilder builder; + builder.host("127.0.0.1") + ->rpcPort(6667) + ->username("root") + ->password("root") + ->useSSL(true) + ->sslProtocol("TLS") + ->trustStore(ssltest::tlsFixture("tls-trust.p12")) + ->trustStorePwd(ssltest::kStorePassword); + REQUIRE_THROWS(builder.build()); +} + +#endif // WITH_SSL && IOTDB_RPC_SSL_IT diff --git a/iotdb-client/client-cpp/test/cpp/RpcSslTlcpMutualAuthTest.cpp b/iotdb-client/client-cpp/test/cpp/RpcSslTlcpMutualAuthTest.cpp new file mode 100644 index 000000000000..04584a80137d --- /dev/null +++ b/iotdb-client/client-cpp/test/cpp/RpcSslTlcpMutualAuthTest.cpp @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + +#include +#include +#include + +#include "Common.h" +#include "RpcSslUtils.h" +#include "SslTestFixtures.h" + +namespace { + +bool fixtureExists(const std::string& path) { + std::ifstream in(path.c_str(), std::ios::binary); + return in.good(); +} + +void concatenate(const std::string& output, std::initializer_list inputs) { + std::ofstream out(output, std::ios::binary | std::ios::trunc); + REQUIRE(out.good()); + for (const auto& input : inputs) { + std::ifstream in(input, std::ios::binary); + REQUIRE(in.good()); + out << in.rdbuf(); + out << '\n'; + } +} + +struct TemporaryPemBundles { + std::string certChain = "tongsuo-client-certs.pem"; + std::string privateKeys = "tongsuo-client-keys.pem"; + + ~TemporaryPemBundles() { + std::remove(certChain.c_str()); + std::remove(privateKeys.c_str()); + } +}; + +} // namespace + +TEST_CASE("TLCP mutual auth creates client SSL_CTX from dual PKCS12", "[rpc][ssl][mutual]") { +#if WITH_SSL + const std::string trustStore = ssltest::tlcpFixture("tlcp-trust.p12"); + REQUIRE(fixtureExists(trustStore)); + const std::string keyStore = ssltest::buildTlcpDualKeyStoreP12(); + REQUIRE_FALSE(keyStore.empty()); + REQUIRE(fixtureExists(keyStore)); + + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLCP"; + config.trustStore = trustStore; + config.trustStorePwd = ssltest::kStorePassword; + config.keyStore = keyStore; + config.keyStorePwd = ssltest::kStorePassword; + + REQUIRE_NOTHROW(RpcSslUtils::validateTrustStore(trustStore, config.trustStorePwd)); + REQUIRE_NOTHROW(RpcSslUtils::validateKeyStore(keyStore, config.keyStorePwd)); + + SSL_CTX* ctx = RpcSslUtils::createClientSslContext(config); + REQUIRE(ctx != nullptr); + SSL_CTX_free(ctx); +#endif +} + +TEST_CASE("TLCP mutual auth creates client SSL_CTX from PEM bundles", "[rpc][ssl][mutual]") { +#if WITH_SSL + TemporaryPemBundles files; + concatenate(files.certChain, + {ssltest::tlcpFixture("client_sign.crt"), ssltest::tlcpFixture("client_enc.crt"), + ssltest::tlcpFixture("ca.crt")}); + concatenate(files.privateKeys, + {ssltest::tlcpFixture("client_sign.key"), ssltest::tlcpFixture("client_enc.key")}); + + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLCP"; + config.trustStore = ssltest::tlcpFixture("ca.crt"); + config.tlcpCertChainFile = files.certChain; + config.tlcpPrivateKeyFile = files.privateKeys; + config.tlcpPrivateKeyPwd = ssltest::kStorePassword; + + SSL_CTX* ctx = RpcSslUtils::createClientSslContext(config); + REQUIRE(ctx != nullptr); + STACK_OF(X509)* certificateChain = nullptr; + SSL_CTX_get_extra_chain_certs(ctx, &certificateChain); + REQUIRE(certificateChain != nullptr); + REQUIRE(sk_X509_num(certificateChain) == 1); + SSL_CTX_free(ctx); +#endif +} diff --git a/iotdb-client/client-cpp/test/cpp/RpcSslTlsMutualAuthTest.cpp b/iotdb-client/client-cpp/test/cpp/RpcSslTlsMutualAuthTest.cpp new file mode 100644 index 000000000000..092226d2e37f --- /dev/null +++ b/iotdb-client/client-cpp/test/cpp/RpcSslTlsMutualAuthTest.cpp @@ -0,0 +1,169 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + +#include + +#include "Common.h" +#include "RpcSslUtils.h" +#include "SslTestFixtures.h" + +namespace { + +bool fixtureExists(const std::string& path) { + std::ifstream in(path.c_str(), std::ios::binary); + return in.good(); +} + +} // namespace + +TEST_CASE("TLS mutual auth creates client SSL_CTX with trust and key stores", + "[rpc][ssl][mutual]") { +#if WITH_SSL + const std::string trustStore = ssltest::tlsFixture("tls-trust.p12"); + const std::string keyStore = ssltest::tlsFixture("tls-client.p12"); + REQUIRE(fixtureExists(trustStore)); + REQUIRE(fixtureExists(keyStore)); + + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLS"; + config.trustStore = trustStore; + config.trustStorePwd = ssltest::kStorePassword; + config.keyStore = keyStore; + config.keyStorePwd = ssltest::kStorePassword; + + REQUIRE_NOTHROW(RpcSslUtils::validateTrustStore(trustStore, config.trustStorePwd)); + REQUIRE_NOTHROW(RpcSslUtils::validateKeyStore(keyStore, config.keyStorePwd)); + + SSL_CTX* ctx = RpcSslUtils::createClientSslContext(config); + REQUIRE(ctx != nullptr); + REQUIRE(ssltest::sslContextHasClientCertificate(ctx)); + SSL_CTX_free(ctx); +#endif +} + +TEST_CASE("TLS mutual auth handshake with openssl s_server", "[rpc][ssl][mutual][e2e]") { +#if WITH_SSL + const std::string caFile = ssltest::tlsFixture("ca.crt"); + const std::string serverCert = ssltest::tlsFixture("server.crt"); + const std::string serverKey = ssltest::tlsFixture("server.key"); + REQUIRE(fixtureExists(caFile)); + REQUIRE(fixtureExists(serverCert)); + REQUIRE(fixtureExists(serverKey)); + + ssltest::OpenSslServerProcess server; + const bool started = server.start({ + "-tls1_2", + "-Verify", + "1", + "-CAfile", + caFile, + "-cert", + serverCert, + "-key", + serverKey, + "-www", + }); + REQUIRE(started); + REQUIRE(server.running()); + REQUIRE(server.port() > 0); + + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLS"; + config.trustStore = ssltest::tlsFixture("tls-trust.p12"); + config.trustStorePwd = ssltest::kStorePassword; + config.keyStore = ssltest::tlsFixture("tls-client.p12"); + config.keyStorePwd = ssltest::kStorePassword; + + REQUIRE(ssltest::tlsHandshakeWithSslConfig(config, "127.0.0.1", server.port())); + server.stop(); +#endif +} + +TEST_CASE("TLS Thrift socket rejects a server outside the configured trust store", + "[rpc][ssl][verify][e2e]") { +#if WITH_SSL + const std::string serverCert = ssltest::tlsFixture("server.crt"); + const std::string serverKey = ssltest::tlsFixture("server.key"); + REQUIRE(fixtureExists(serverCert)); + REQUIRE(fixtureExists(serverKey)); + + ssltest::OpenSslServerProcess server; + REQUIRE(server.start({ + "-tls1_2", + "-cert", + serverCert, + "-key", + serverKey, + "-www", + })); + + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLS"; + config.trustStore = ssltest::tlcpFixture("tlcp-trust.p12"); + config.trustStorePwd = ssltest::kStorePassword; + + auto factory = RpcSslUtils::createSslSocketFactory(config); + auto socket = factory->createSocket("127.0.0.1", server.port()); + socket->setConnTimeout(2000); + REQUIRE_NOTHROW(socket->open()); + const uint8_t requestByte = 0; + REQUIRE_THROWS(socket->write(&requestByte, 1)); + socket->close(); + server.stop(); +#endif +} + +TEST_CASE("TLS one-way auth fails when server requires client certificate", + "[rpc][ssl][mutual][e2e]") { +#if WITH_SSL + const std::string caFile = ssltest::tlsFixture("ca.crt"); + const std::string serverCert = ssltest::tlsFixture("server.crt"); + const std::string serverKey = ssltest::tlsFixture("server.key"); + REQUIRE(fixtureExists(caFile)); + + ssltest::OpenSslServerProcess server; + const bool started = server.start({ + "-tls1_2", + "-Verify", + "1", + "-CAfile", + caFile, + "-cert", + serverCert, + "-key", + serverKey, + "-www", + }); + REQUIRE(started); + + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLS"; + config.trustStore = ssltest::tlsFixture("tls-trust.p12"); + config.trustStorePwd = ssltest::kStorePassword; + + REQUIRE_FALSE(ssltest::tlsHandshakeWithSslConfig(config, "127.0.0.1", server.port())); + server.stop(); +#endif +} diff --git a/iotdb-client/client-cpp/test/cpp/RpcSslUtilsTest.cpp b/iotdb-client/client-cpp/test/cpp/RpcSslUtilsTest.cpp new file mode 100644 index 000000000000..4502aec8379f --- /dev/null +++ b/iotdb-client/client-cpp/test/cpp/RpcSslUtilsTest.cpp @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + +#include "Common.h" +#include "RpcSslUtils.h" + +TEST_CASE("RpcSslUtils protocol helpers", "[rpc][ssl]") { + REQUIRE(RpcSslUtils::normalizeProtocol("") == "TLS"); + REQUIRE(RpcSslUtils::normalizeProtocol(" TLSv1.3 ") == "TLSv1.3"); + REQUIRE(RpcSslUtils::isTlcpProtocol("TLCP") == true); + REQUIRE(RpcSslUtils::isTlcpProtocol(" tlcp1.1 ") == true); + REQUIRE(RpcSslUtils::isTlcpProtocol("TLS") == false); + + const std::string origin = RpcSslUtils::getProtocol(); + RpcSslUtils::configure("ConfiguredProtocol"); + REQUIRE(RpcSslUtils::resolveProtocol("") == "ConfiguredProtocol"); + REQUIRE(RpcSslUtils::resolveProtocol(" ExplicitProtocol ") == "ExplicitProtocol"); + RpcSslUtils::configure(origin); +} + +TEST_CASE("SslConfig effectiveTrustStore backward compatibility", "[rpc][ssl]") { + SslConfig config; + config.trustStore = "/path/to/trust.p12"; + config.trustCertFilePath = "/legacy/ca.pem"; + REQUIRE(config.effectiveTrustStore() == "/path/to/trust.p12"); + + config.trustStore.clear(); + config.trustCertFilePath = "/legacy/ca.pem"; + REQUIRE(config.effectiveTrustStore() == "/legacy/ca.pem"); +} + +TEST_CASE("RpcSslUtils store validation rejects missing files", "[rpc][ssl]") { + REQUIRE_THROWS_AS(RpcSslUtils::validateTrustStore("/path/does/not/exist.pem", ""), + IoTDBException); + REQUIRE_THROWS_AS(RpcSslUtils::validateKeyStore("/path/does/not/exist.p12", "pwd"), + IoTDBException); +} + +#if WITH_SSL && defined(IOTDB_NTLS_PROVIDER_TONGSUO) +TEST_CASE("RpcSslUtils createClientSslContext for TLS without trust store", "[rpc][ssl]") { + SslConfig config; + config.useSsl = true; + config.sslProtocol = "TLS"; + SSL_CTX* ctx = RpcSslUtils::createClientSslContext(config); + REQUIRE(ctx != nullptr); + SSL_CTX_free(ctx); +} +#endif diff --git a/iotdb-client/client-cpp/test/cpp/SslTestFixtures.cpp b/iotdb-client/client-cpp/test/cpp/SslTestFixtures.cpp new file mode 100644 index 000000000000..2500e5e8ebd5 --- /dev/null +++ b/iotdb-client/client-cpp/test/cpp/SslTestFixtures.cpp @@ -0,0 +1,676 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +#if WITH_SSL +#include +#include +#include +#include +#include +#endif + +#include "RpcSslUtils.h" +#include "SslTestFixtures.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ssltest { +namespace { + +std::string joinPath(const std::string& base, const std::string& name) { +#if defined(_WIN32) + const char sep = '\\'; +#else + const char sep = '/'; +#endif + if (base.empty()) { + return name; + } + if (base.back() == '/' || base.back() == '\\') { + return base + name; + } + return base + sep + name; +} + +std::string executableDir() { +#if defined(_WIN32) + char buffer[MAX_PATH]; + const DWORD len = GetModuleFileNameA(nullptr, buffer, MAX_PATH); + if (len == 0 || len == MAX_PATH) { + return "."; + } + std::string path(buffer, len); + const auto pos = path.find_last_of("\\/"); + return pos == std::string::npos ? "." : path.substr(0, pos); +#else + char buffer[4096]; + const ssize_t len = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1); + if (len <= 0) { + return "."; + } + buffer[len] = '\0'; + std::string path(buffer); + const auto pos = path.find_last_of('/'); + return pos == std::string::npos ? "." : path.substr(0, pos); +#endif +} + +bool pathExists(const std::string& path) { + std::ifstream in(path.c_str(), std::ios::binary); + return in.good(); +} + +std::string configuredOrEmpty() { +#ifdef IOTDB_TEST_FIXTURES_DIR + return IOTDB_TEST_FIXTURES_DIR; +#else + return joinPath(executableDir(), "fixtures"); +#endif +} + +std::string firstExistingRoot() { +#ifdef IOTDB_TEST_FIXTURES_DIR + const std::string configured = IOTDB_TEST_FIXTURES_DIR; + if (pathExists(joinPath(configured, "tls/tls-trust.p12")) || + pathExists(joinPath(configured, "tls\\tls-trust.p12"))) { + return configured; + } +#endif + const std::string copied = joinPath(executableDir(), "fixtures"); + if (pathExists(joinPath(copied, "tls/tls-trust.p12")) || + pathExists(joinPath(copied, "tls\\tls-trust.p12"))) { + return copied; + } + return configuredOrEmpty(); +} + +#if WITH_SSL +EVP_PKEY* readPrivateKeyPem(const std::string& path) { + BIO* bio = BIO_new_file(path.c_str(), "rb"); + if (bio == nullptr) { + return nullptr; + } + EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); + BIO_free(bio); + return key; +} + +X509* readCertificatePem(const std::string& path) { + BIO* bio = BIO_new_file(path.c_str(), "rb"); + if (bio == nullptr) { + return nullptr; + } + X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + BIO_free(bio); + return cert; +} + +void addLocalKeyId(PKCS12_SAFEBAG* bag, X509* cert) { + unsigned char keyid[EVP_MAX_MD_SIZE]; + unsigned int keyidLen = 0; + if (X509_pubkey_digest(cert, EVP_sha1(), keyid, &keyidLen) == 1) { + PKCS12_add_localkeyid(bag, keyid, static_cast(keyidLen)); + } +} + +void addCertAndKeyBags(STACK_OF(PKCS12_SAFEBAG) * bags, X509* cert, EVP_PKEY* key, + const char* friendlyName, const std::string& password) { + PKCS12_SAFEBAG* certbag = PKCS12_SAFEBAG_create_cert(cert); + PKCS12_add_friendlyname_utf8(certbag, friendlyName, -1); + addLocalKeyId(certbag, cert); + sk_PKCS12_SAFEBAG_push(bags, certbag); + + PKCS8_PRIV_KEY_INFO* p8 = EVP_PKEY2PKCS8(key); + if (p8 == nullptr) { + return; + } + PKCS12_SAFEBAG* keybag = PKCS12_SAFEBAG_create_pkcs8_encrypt( + NID_pbes2, password.c_str(), static_cast(password.size()), nullptr, 0, 2048, p8); + PKCS8_PRIV_KEY_INFO_free(p8); + if (keybag == nullptr) { + return; + } + PKCS12_add_friendlyname_utf8(keybag, friendlyName, -1); + addLocalKeyId(keybag, cert); + sk_PKCS12_SAFEBAG_push(bags, keybag); +} + +bool writePkcs12File(PKCS12* p12, const std::string& path) { + BIO* bio = BIO_new_file(path.c_str(), "wb"); + if (bio == nullptr) { + return false; + } + const int rc = i2d_PKCS12_bio(bio, p12); + BIO_free(bio); + return rc == 1; +} + +PKCS12* readPkcs12File(const std::string& path) { + BIO* bio = BIO_new_file(path.c_str(), "rb"); + if (bio == nullptr) { + return nullptr; + } + PKCS12* p12 = d2i_PKCS12_bio(bio, nullptr); + BIO_free(bio); + return p12; +} + +void forEachPkcs12Bag(PKCS12* p12, const std::string& password, + const std::function& visitor) { + STACK_OF(PKCS7)* safes = PKCS12_unpack_authsafes(p12); + if (safes == nullptr) { + return; + } + for (int i = 0; i < sk_PKCS7_num(safes); ++i) { + PKCS7* p7 = sk_PKCS7_value(safes, i); + STACK_OF(PKCS12_SAFEBAG)* bags = nullptr; + if (PKCS7_type_is_data(p7)) { + bags = PKCS12_unpack_p7data(p7); + } else if (PKCS7_type_is_encrypted(p7)) { + bags = PKCS12_unpack_p7encdata(p7, password.c_str(), static_cast(password.size())); + } + if (bags == nullptr) { + continue; + } + for (int j = 0; j < sk_PKCS12_SAFEBAG_num(bags); ++j) { + visitor(sk_PKCS12_SAFEBAG_value(bags, j)); + } + sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free); + } + sk_PKCS7_pop_free(safes, PKCS7_free); +} + +void appendPkcs12Bags(STACK_OF(PKCS12_SAFEBAG) * target, PKCS12* source, + const std::string& password) { + forEachPkcs12Bag(source, password, [&](PKCS12_SAFEBAG* bag) { + const int bagType = PKCS12_SAFEBAG_get_nid(bag); + char* friendlyName = PKCS12_get_friendlyname(bag); + if (bagType == NID_certBag) { + X509* cert = PKCS12_certbag2x509(bag); + if (cert != nullptr) { + PKCS12_SAFEBAG* newBag = PKCS12_SAFEBAG_create_cert(cert); + X509_free(cert); + if (newBag != nullptr) { + if (friendlyName != nullptr) { + PKCS12_add_friendlyname_utf8(newBag, friendlyName, -1); + } + sk_PKCS12_SAFEBAG_push(target, newBag); + } + } + } else if (bagType == NID_pkcs8ShroudedKeyBag || bagType == NID_keyBag) { + EVP_PKEY* key = nullptr; + if (bagType == NID_pkcs8ShroudedKeyBag) { + PKCS8_PRIV_KEY_INFO* p8 = + PKCS12_decrypt_skey(bag, password.c_str(), static_cast(password.size())); + if (p8 != nullptr) { + key = EVP_PKCS82PKEY(p8); + PKCS8_PRIV_KEY_INFO_free(p8); + } + } else { + const PKCS8_PRIV_KEY_INFO* p8 = PKCS12_SAFEBAG_get0_p8inf(bag); + if (p8 != nullptr) { + key = EVP_PKCS82PKEY(p8); + } + } + if (key != nullptr) { + PKCS8_PRIV_KEY_INFO* p8 = EVP_PKEY2PKCS8(key); + EVP_PKEY_free(key); + if (p8 != nullptr) { + PKCS12_SAFEBAG* newBag = PKCS12_SAFEBAG_create_pkcs8_encrypt( + NID_pbes2, password.c_str(), static_cast(password.size()), nullptr, 0, 2048, p8); + PKCS8_PRIV_KEY_INFO_free(p8); + if (newBag != nullptr) { + if (friendlyName != nullptr) { + PKCS12_add_friendlyname_utf8(newBag, friendlyName, -1); + } + sk_PKCS12_SAFEBAG_push(target, newBag); + } + } + } + } + if (friendlyName != nullptr) { + OPENSSL_free(friendlyName); + } + }); +} + +std::string opensslExecutable() { +#ifdef IOTDB_OPENSSL_EXECUTABLE + return IOTDB_OPENSSL_EXECUTABLE; +#else + return "openssl"; +#endif +} + +#if !defined(_WIN32) +void prependOpenSslRuntimeToLdLibraryPath() { +#ifdef IOTDB_OPENSSL_ROOT_DIR + const std::string root = IOTDB_OPENSSL_ROOT_DIR; + std::string libPath = joinPath(root, "lib64"); + const std::string lib = joinPath(root, "lib"); + if (pathExists(lib)) { + libPath = libPath + ":" + lib; + } + const char* existing = std::getenv("LD_LIBRARY_PATH"); + if (existing != nullptr && existing[0] != '\0') { + libPath = libPath + ":" + existing; + } + setenv("LD_LIBRARY_PATH", libPath.c_str(), 1); +#endif +} +#endif + +std::string quoteArg(const std::string& arg) { +#if defined(_WIN32) + return "\"" + arg + "\""; +#else + if (arg.find(' ') != std::string::npos) { + return "\"" + arg + "\""; + } + return arg; +#endif +} + +} // namespace + +std::string fixturesRoot() { + return firstExistingRoot(); +} + +std::string tlsFixture(const std::string& name) { + return joinPath(joinPath(fixturesRoot(), "tls"), name); +} + +std::string tlcpFixture(const std::string& name) { + return joinPath(joinPath(fixturesRoot(), "tlcp"), name); +} + +std::string buildTlcpDualKeyStoreP12() { + const std::string password = kStorePassword; + const std::string outPath = joinPath(executableDir(), "tlcp-client-dual.p12"); + + PKCS12* signStore = readPkcs12File(tlcpFixture("tlcp-client-sign.p12")); + PKCS12* encStore = readPkcs12File(tlcpFixture("tlcp-client-enc.p12")); + if (signStore == nullptr || encStore == nullptr) { + if (signStore != nullptr) { + PKCS12_free(signStore); + } + if (encStore != nullptr) { + PKCS12_free(encStore); + } + return ""; + } + + STACK_OF(PKCS7)* safes = PKCS12_unpack_authsafes(signStore); + STACK_OF(PKCS7)* encSafes = PKCS12_unpack_authsafes(encStore); + PKCS12_free(signStore); + PKCS12_free(encStore); + if (safes == nullptr || encSafes == nullptr) { + if (safes != nullptr) { + sk_PKCS7_pop_free(safes, PKCS7_free); + } + if (encSafes != nullptr) { + sk_PKCS7_pop_free(encSafes, PKCS7_free); + } + return ""; + } + while (sk_PKCS7_num(encSafes) > 0) { + PKCS7* p7 = sk_PKCS7_pop(encSafes); + if (p7 == nullptr || sk_PKCS7_push(safes, p7) == 0) { + PKCS7_free(p7); + sk_PKCS7_pop_free(safes, PKCS7_free); + sk_PKCS7_pop_free(encSafes, PKCS7_free); + return ""; + } + } + sk_PKCS7_free(encSafes); + + PKCS12* p12 = PKCS12_init(NID_pkcs7_data); + if (p12 == nullptr) { + sk_PKCS7_pop_free(safes, PKCS7_free); + return ""; + } + if (PKCS12_pack_authsafes(p12, safes) != 1) { + sk_PKCS7_pop_free(safes, PKCS7_free); + PKCS12_free(p12); + return ""; + } + // PKCS12_pack_authsafes only encodes safes into p12; it does not take ownership. + sk_PKCS7_pop_free(safes, PKCS7_free); + + const bool written = writePkcs12File(p12, outPath); + PKCS12_free(p12); + (void)password; + return written ? outPath : ""; +} + +bool sslContextHasClientCertificate(SSL_CTX* ctx) { + if (ctx == nullptr) { + return false; + } + X509* cert = SSL_CTX_get0_certificate(ctx); + EVP_PKEY* key = SSL_CTX_get0_privatekey(ctx); + return cert != nullptr && key != nullptr; +} + +bool tlcpContextHasDualCredentials(SSL_CTX* ctx) { + if (ctx == nullptr) { + return false; + } + SSL* ssl = SSL_new(ctx); + if (ssl == nullptr) { + return false; + } + SSL_enable_ntls(ssl); + X509* signCert = SSL_get_sign_certificate_ntls(ssl); + X509* encCert = SSL_get_enc_certificate_ntls(ssl); + const bool ok = signCert != nullptr && encCert != nullptr; + SSL_free(ssl); + return ok; +} + +bool tlsHandshakeWithSslConfig(const SslConfig& config, const std::string& host, int port, + int timeoutMs) { +#if defined(_WIN32) + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); +#endif + for (int attempt = 0; attempt < 3; ++attempt) { + SSL_CTX* ctx = RpcSslUtils::createClientSslContext(config); + if (ctx == nullptr) { + continue; + } + SSL* ssl = SSL_new(ctx); + if (ssl == nullptr) { + SSL_CTX_free(ctx); + continue; + } + if (RpcSslUtils::isTlcpProtocol(config.sslProtocol)) { + SSL_enable_ntls(ssl); + } + const std::string target = host + ":" + std::to_string(port); + BIO* bio = BIO_new_connect(target.c_str()); + if (bio == nullptr) { + SSL_free(ssl); + SSL_CTX_free(ctx); + continue; + } + BIO_set_conn_hostname(bio, host.c_str()); + if (BIO_do_connect(bio) <= 0) { + BIO_free_all(bio); + SSL_free(ssl); + SSL_CTX_free(ctx); + continue; + } + SSL_set_bio(ssl, bio, bio); + const int rc = SSL_connect(ssl); + const bool ok = rc == 1; + if (ok) { + SSL_shutdown(ssl); + } + SSL_free(ssl); + SSL_CTX_free(ctx); + if (ok) { +#if defined(_WIN32) + WSACleanup(); +#endif + return true; + } + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + } +#if defined(_WIN32) + WSACleanup(); +#endif + (void)timeoutMs; + return false; +} + +int findFreeTcpPort() { +#if defined(_WIN32) + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + return 0; + } +#endif + const int fd = static_cast(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); + if (fd < 0) { +#if defined(_WIN32) + WSACleanup(); +#endif + return 0; + } + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + if (bind(fd, reinterpret_cast(&addr), sizeof(addr)) != 0) { +#if defined(_WIN32) + closesocket(fd); + WSACleanup(); +#else + close(fd); +#endif + return 0; + } + socklen_t len = sizeof(addr); + if (getsockname(fd, reinterpret_cast(&addr), &len) != 0) { +#if defined(_WIN32) + closesocket(fd); + WSACleanup(); +#else + close(fd); +#endif + return 0; + } + const int port = ntohs(addr.sin_port); +#if defined(_WIN32) + closesocket(fd); + WSACleanup(); +#else + close(fd); +#endif + return port; +} + +OpenSslServerProcess::OpenSslServerProcess() = default; + +OpenSslServerProcess::~OpenSslServerProcess() { + stop(); +} + +bool OpenSslServerProcess::start(const std::vector& args) { + stop(); + port_ = findFreeTcpPort(); + if (port_ <= 0) { + return false; + } + + const std::string portArg = std::to_string(port_); + const std::string exe = opensslExecutable(); + std::vector argStorage; + argStorage.reserve(args.size() + 5); + argStorage.push_back(exe); + argStorage.emplace_back("s_server"); + argStorage.emplace_back("-accept"); + argStorage.emplace_back(portArg); + for (const std::string& arg : args) { + argStorage.push_back(arg); + } + argStorage.emplace_back("-quiet"); + + std::vector argv; + argv.reserve(argStorage.size() + 1); + for (const std::string& arg : argStorage) { + argv.push_back(arg.c_str()); + } + argv.push_back(nullptr); + +#if defined(_WIN32) + std::string cmdline = quoteArg(exe); + for (size_t i = 1; i < argStorage.size(); ++i) { + cmdline.push_back(' '); + cmdline.append(quoteArg(argStorage[i])); + } + std::vector mutableCmdline(cmdline.begin(), cmdline.end()); + mutableCmdline.push_back('\0'); + + STARTUPINFOA si{}; + si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + PROCESS_INFORMATION pi{}; + if (!CreateProcessA(nullptr, mutableCmdline.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, + nullptr, nullptr, &si, &pi)) { + return false; + } + processHandle_ = pi.hProcess; + processId_ = pi.dwProcessId; + CloseHandle(pi.hThread); +#else + std::vector execArgv; + execArgv.reserve(argStorage.size() + 1); + for (std::string& arg : argStorage) { + execArgv.push_back(const_cast(arg.c_str())); + } + execArgv.push_back(nullptr); + const pid_t pid = fork(); + if (pid < 0) { + return false; + } + if (pid == 0) { + prependOpenSslRuntimeToLdLibraryPath(); + execv(exe.c_str(), execArgv.data()); + _exit(127); + } + childPid_ = pid; +#endif + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + return running(); +} + +void OpenSslServerProcess::stop() { +#if defined(_WIN32) + if (processHandle_ != nullptr) { + TerminateProcess(static_cast(processHandle_), 0); + WaitForSingleObject(static_cast(processHandle_), 2000); + CloseHandle(static_cast(processHandle_)); + processHandle_ = nullptr; + processId_ = 0; + } +#else + if (childPid_ > 0) { + kill(childPid_, SIGTERM); + waitpid(childPid_, nullptr, 0); + childPid_ = -1; + } +#endif + port_ = 0; +} + +bool OpenSslServerProcess::running() const { +#if defined(_WIN32) + if (processHandle_ == nullptr) { + return false; + } + DWORD code = STILL_ACTIVE; + if (!GetExitCodeProcess(static_cast(processHandle_), &code)) { + return false; + } + return code == STILL_ACTIVE; +#else + if (childPid_ <= 0) { + return false; + } + int status = 0; + const pid_t rc = waitpid(childPid_, &status, WNOHANG); + return rc == 0; +#endif +} + +int OpenSslServerProcess::port() const { + return port_; +} + +#else // WITH_SSL + +std::string fixturesRoot() { + return firstExistingRoot(); +} + +std::string tlsFixture(const std::string& name) { + return joinPath(joinPath(fixturesRoot(), "tls"), name); +} + +std::string tlcpFixture(const std::string& name) { + return joinPath(joinPath(fixturesRoot(), "tlcp"), name); +} + +std::string buildTlcpDualKeyStoreP12() { + return ""; +} + +int findFreeTcpPort() { + return 0; +} + +OpenSslServerProcess::OpenSslServerProcess() = default; +OpenSslServerProcess::~OpenSslServerProcess() = default; +bool OpenSslServerProcess::start(const std::vector&) { + return false; +} +void OpenSslServerProcess::stop() {} +bool OpenSslServerProcess::running() const { + return false; +} +int OpenSslServerProcess::port() const { + return 0; +} + +#endif // WITH_SSL + +} // namespace ssltest diff --git a/iotdb-client/client-cpp/test/cpp/SslTestFixtures.h b/iotdb-client/client-cpp/test/cpp/SslTestFixtures.h new file mode 100644 index 000000000000..0787e9062aec --- /dev/null +++ b/iotdb-client/client-cpp/test/cpp/SslTestFixtures.h @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 IOTDB_SSL_TEST_FIXTURES_H +#define IOTDB_SSL_TEST_FIXTURES_H + +#include +#include +#include +#include + +namespace ssltest { + +constexpr const char* kStorePassword = "thrift"; + +/** Root directory containing tls/ and tlcp/ fixture subfolders. */ +std::string fixturesRoot(); + +std::string tlsFixture(const std::string& name); +std::string tlcpFixture(const std::string& name); + +/** Build a TLCP dual-cert PKCS12 key store from PEM fixtures (sign + enc). */ +std::string buildTlcpDualKeyStoreP12(); + +#if WITH_SSL +#include + +bool sslContextHasClientCertificate(SSL_CTX* ctx); +bool tlcpContextHasDualCredentials(SSL_CTX* ctx); + +bool tlsHandshakeWithSslConfig(const SslConfig& config, const std::string& host, int port, + int timeoutMs = 5000); +#endif + +/** Spawn bundled Tongsuo openssl s_server for integration-style handshake tests. */ +class OpenSslServerProcess { +public: + OpenSslServerProcess(); + ~OpenSslServerProcess(); + + OpenSslServerProcess(const OpenSslServerProcess&) = delete; + OpenSslServerProcess& operator=(const OpenSslServerProcess&) = delete; + + bool start(const std::vector& args); + void stop(); + bool running() const; + int port() const; + +private: +#if defined(_WIN32) + void* processHandle_ = nullptr; + unsigned long processId_ = 0; +#else + int childPid_ = -1; +#endif + int port_ = 0; +}; + +int findFreeTcpPort(); + +} // namespace ssltest + +#endif // IOTDB_SSL_TEST_FIXTURES_H diff --git a/iotdb-client/client-cpp/test/cpp/sessionCRelationalIT.cpp b/iotdb-client/client-cpp/test/cpp/sessionCRelationalIT.cpp index 4a298dd1c1a1..0eb9512f0a1c 100644 --- a/iotdb-client/client-cpp/test/cpp/sessionCRelationalIT.cpp +++ b/iotdb-client/client-cpp/test/cpp/sessionCRelationalIT.cpp @@ -19,6 +19,7 @@ #include "catch.hpp" #include "SessionC.h" +#include "SessionC.h" #include #include #include @@ -245,8 +246,10 @@ TEST_CASE("C API Table - Multi-node table session", "[c_table_multiNode][c_table CTableSession* localSession = ts_table_session_new_multi_node(urls, 1, "root", "root", ""); REQUIRE(localSession != nullptr); - TsStatus status = - ts_table_session_execute_non_query(localSession, "DROP DATABASE IF EXISTS c_db5"); + TsStatus status = ts_table_session_open(localSession); + REQUIRE(status == TS_OK); + + status = ts_table_session_execute_non_query(localSession, "DROP DATABASE IF EXISTS c_db5"); REQUIRE(status == TS_OK); ts_table_session_execute_non_query(localSession, "CREATE DATABASE c_db5"); diff --git a/iotdb-client/client-cpp/test/cpp/sessionIT.cpp b/iotdb-client/client-cpp/test/cpp/sessionIT.cpp index 45624f0300df..20f74b473e54 100644 --- a/iotdb-client/client-cpp/test/cpp/sessionIT.cpp +++ b/iotdb-client/client-cpp/test/cpp/sessionIT.cpp @@ -91,7 +91,7 @@ TEST_CASE("Test Session constructor with nodeUrls", "[SessionInitAndOperate]") { std::vector nodeUrls = {"127.0.0.1:6667"}; std::shared_ptr localSession = std::make_shared(nodeUrls, "root", "root"); - localSession->open(); + localSession->open(false); if (!localSession->checkTimeseriesExists("root.test.d1.s1")) { localSession->createTimeseries("root.test.d1.s1", TSDataType::INT64, TSEncoding::RLE, CompressionType::SNAPPY); @@ -106,9 +106,9 @@ TEST_CASE("Test Session builder with nodeUrls", "[SessionBuilderInit]") { std::vector nodeUrls = {"127.0.0.1:6667"}; auto builder = std::unique_ptr(new SessionBuilder()); - std::shared_ptr session = std::shared_ptr( - builder->username("root")->password("root")->nodeUrls(nodeUrls)->build()); - session->open(); + builder->username("root")->password("root")->nodeUrls(nodeUrls); + std::shared_ptr session = builder->build(); + session->open(false); if (!session->checkTimeseriesExists("root.test.d1.s1")) { session->createTimeseries("root.test.d1.s1", TSDataType::INT64, TSEncoding::RLE, CompressionType::SNAPPY); @@ -386,9 +386,9 @@ TEST_CASE("Tablet index bounds", "[tabletBounds]") { TEST_CASE("Session rejects SQL after close", "[sessionClose]") { CaseReporter cr("sessionClose"); SessionBuilder builder; - auto localSession = - builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root")->build(); - localSession->open(); + builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root"); + auto localSession = builder.build(); + localSession->open(false); localSession->close(); REQUIRE_THROWS_AS(localSession->executeNonQueryStatement("show databases"), IoTDBConnectionException); @@ -948,13 +948,9 @@ TEST_CASE("Numeric column widening getters align with Java TsFile", "[column]") } TEST_CASE("SessionPool basic borrow/insert/query via RAII lease", "[sessionPool]") { CaseReporter cr("SessionPool basic"); - auto pool = SessionPoolBuilder() - .host("127.0.0.1") - ->rpcPort(6667) - ->username("root") - ->password("root") - ->maxSize(3) - ->build(); + SessionPoolBuilder poolBuilder; + poolBuilder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root")->maxSize(3); + auto pool = poolBuilder.build(); { PooledSession s = pool->getSession(); @@ -990,13 +986,9 @@ TEST_CASE("SessionPool basic borrow/insert/query via RAII lease", "[sessionPool] TEST_CASE("SessionPool is safe under concurrent writers", "[sessionPool]") { CaseReporter cr("SessionPool concurrency"); - auto pool = SessionPoolBuilder() - .host("127.0.0.1") - ->rpcPort(6667) - ->username("root") - ->password("root") - ->maxSize(4) - ->build(); + SessionPoolBuilder poolBuilder; + poolBuilder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root")->maxSize(4); + auto pool = poolBuilder.build(); { PooledSession s = pool->getSession(); @@ -1055,14 +1047,14 @@ TEST_CASE("SessionPool is safe under concurrent writers", "[sessionPool]") { TEST_CASE("SessionPool getSession times out when exhausted", "[sessionPool]") { CaseReporter cr("SessionPool exhaustion timeout"); - auto pool = SessionPoolBuilder() - .host("127.0.0.1") - ->rpcPort(6667) - ->username("root") - ->password("root") - ->maxSize(1) - ->waitToGetSessionTimeoutMs(200) - ->build(); + SessionPoolBuilder poolBuilder; + poolBuilder.host("127.0.0.1") + ->rpcPort(6667) + ->username("root") + ->password("root") + ->maxSize(1) + ->waitToGetSessionTimeoutMs(200); + auto pool = poolBuilder.build(); PooledSession held = pool->getSession(); REQUIRE(static_cast(held)); diff --git a/iotdb-client/client-cpp/test/cpp/sessionRelationalIT.cpp b/iotdb-client/client-cpp/test/cpp/sessionRelationalIT.cpp index 9ed3d334b1c4..f3c5aaf32c49 100644 --- a/iotdb-client/client-cpp/test/cpp/sessionRelationalIT.cpp +++ b/iotdb-client/client-cpp/test/cpp/sessionRelationalIT.cpp @@ -74,8 +74,8 @@ TEST_CASE("Test TableSession builder with nodeUrls", "[SessionBuilderInit]") { std::vector nodeUrls = {"127.0.0.1:6667"}; auto builder = std::unique_ptr(new TableSessionBuilder()); - std::shared_ptr session = std::shared_ptr( - builder->username("root")->password("root")->nodeUrls(nodeUrls)->build()); + builder->username("root")->password("root")->nodeUrls(nodeUrls); + std::shared_ptr session = builder->build(); session->open(); session->executeNonQueryStatement("DROP DATABASE IF EXISTS db1"); @@ -90,8 +90,8 @@ TEST_CASE("TableSession rejects SQL after close", "[tableSessionClose]") { CaseReporter cr("tableSessionClose"); TableSessionBuilder builder; - auto localSession = - builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root")->build(); + builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root"); + auto localSession = builder.build(); localSession->open(); localSession->close(); diff --git a/iotdb-client/client-cpp/test/fixtures/.gitignore b/iotdb-client/client-cpp/test/fixtures/.gitignore new file mode 100644 index 000000000000..425d89e3c2dc --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/.gitignore @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +*.csr +*.srl +ca.key +tlcp-client-dual.p12 +_gen/ diff --git a/iotdb-client/client-cpp/test/fixtures/README.md b/iotdb-client/client-cpp/test/fixtures/README.md new file mode 100644 index 000000000000..6cd6b14a03f9 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/README.md @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Self-signed TLS/TLCP test certificates for C++ client SSL unit tests only. +# Password for all PKCS12 files: thrift +# +# Regenerate with: +# test/fixtures/generate_fixtures.cmd (Windows) +# test/fixtures/generate_fixtures.sh (Linux/macOS) diff --git a/iotdb-client/client-cpp/test/fixtures/generate_fixtures.cmd b/iotdb-client/client-cpp/test/fixtures/generate_fixtures.cmd new file mode 100644 index 000000000000..713ace0a2c4a --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/generate_fixtures.cmd @@ -0,0 +1,69 @@ +@echo off +REM Licensed to the Apache Software Foundation (ASF) under one +REM or more contributor license agreements. See the NOTICE file +REM distributed with this work for additional information +REM regarding copyright ownership. The ASF licenses this file +REM to you under the Apache License, Version 2.0 (the +REM "License"); you may not use this file except in compliance +REM with the License. You may obtain a copy of the License at +REM +REM http://www.apache.org/licenses/LICENSE-2.0 +REM +REM Unless required by applicable law or agreed to in writing, +REM software distributed under the License is distributed on an +REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +REM KIND, either express or implied. See the License for the +REM specific language governing permissions and limitations +REM under the License. +REM +REM Regenerate TLS/TLCP PKCS12 and PEM fixtures using the bundled Tongsuo openssl. +REM Usage (from client-cpp/test/fixtures, after cmake build): +REM generate_fixtures.cmd [path\to\openssl.exe] + +@echo off +setlocal enabledelayedexpansion + +set "SCRIPT_DIR=%~dp0" +set "TLS_DIR=%SCRIPT_DIR%tls" +set "TLCP_DIR=%SCRIPT_DIR%tlcp" +set "OPENSSL=%~1" +if "%OPENSSL%"=="" set "OPENSSL=..\..\target\build\_deps\tongsuo\install\bin\openssl.exe" +if not exist "%OPENSSL%" ( + echo OpenSSL executable not found: %OPENSSL% + exit /b 1 +) + +set "PASS=thrift" +mkdir "%TLS_DIR%" 2>nul +mkdir "%TLCP_DIR%" 2>nul + +echo [fixtures] generating TLS RSA fixtures... +"%OPENSSL%" genrsa -out "%TLS_DIR%\ca.key" 2048 +"%OPENSSL%" req -new -x509 -days 3650 -key "%TLS_DIR%\ca.key" -out "%TLS_DIR%\ca.crt" -subj "/CN=IoTDB Test CA" +"%OPENSSL%" genrsa -out "%TLS_DIR%\server.key" 2048 +"%OPENSSL%" req -new -key "%TLS_DIR%\server.key" -out "%TLS_DIR%\server.csr" -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" +"%OPENSSL%" x509 -req -days 3650 -in "%TLS_DIR%\server.csr" -CA "%TLS_DIR%\ca.crt" -CAkey "%TLS_DIR%\ca.key" -CAcreateserial -out "%TLS_DIR%\server.crt" -copy_extensions copy +"%OPENSSL%" genrsa -out "%TLS_DIR%\client.key" 2048 +"%OPENSSL%" req -new -key "%TLS_DIR%\client.key" -out "%TLS_DIR%\client.csr" -subj "/CN=IoTDB Test Client" +"%OPENSSL%" x509 -req -days 3650 -in "%TLS_DIR%\client.csr" -CA "%TLS_DIR%\ca.crt" -CAkey "%TLS_DIR%\ca.key" -CAcreateserial -out "%TLS_DIR%\client.crt" +"%OPENSSL%" pkcs12 -export -nokeys -in "%TLS_DIR%\ca.crt" -out "%TLS_DIR%\tls-trust.p12" -password pass:%PASS% +"%OPENSSL%" pkcs12 -export -in "%TLS_DIR%\client.crt" -inkey "%TLS_DIR%\client.key" -out "%TLS_DIR%\tls-client.p12" -password pass:%PASS% -name client +"%OPENSSL%" pkcs12 -export -in "%TLS_DIR%\server.crt" -inkey "%TLS_DIR%\server.key" -out "%TLS_DIR%\tls-server.p12" -password pass:%PASS% -name server + +echo [fixtures] generating TLCP SM2 fixtures... +"%OPENSSL%" ecparam -genkey -name SM2 -out "%TLCP_DIR%\ca.key" +"%OPENSSL%" req -new -x509 -days 3650 -key "%TLCP_DIR%\ca.key" -out "%TLCP_DIR%\ca.crt" -subj "/CN=IoTDB TLCP CA" -sm3 +for %%R in (client server) do ( + "%OPENSSL%" ecparam -genkey -name SM2 -out "%TLCP_DIR%\%%R_sign.key" + "%OPENSSL%" req -new -key "%TLCP_DIR%\%%R_sign.key" -out "%TLCP_DIR%\%%R_sign.csr" -subj "/CN=%%R sign" -sm3 + "%OPENSSL%" x509 -req -days 3650 -in "%TLCP_DIR%\%%R_sign.csr" -CA "%TLCP_DIR%\ca.crt" -CAkey "%TLCP_DIR%\ca.key" -CAcreateserial -out "%TLCP_DIR%\%%R_sign.crt" -sm3 + "%OPENSSL%" ecparam -genkey -name SM2 -out "%TLCP_DIR%\%%R_enc.key" + "%OPENSSL%" req -new -key "%TLCP_DIR%\%%R_enc.key" -out "%TLCP_DIR%\%%R_enc.csr" -subj "/CN=%%R enc" -sm3 + "%OPENSSL%" x509 -req -days 3650 -in "%TLCP_DIR%\%%R_enc.csr" -CA "%TLCP_DIR%\ca.crt" -CAkey "%TLCP_DIR%\ca.key" -CAcreateserial -out "%TLCP_DIR%\%%R_enc.crt" -sm3 +) +"%OPENSSL%" pkcs12 -export -nokeys -in "%TLCP_DIR%\ca.crt" -out "%TLCP_DIR%\tlcp-trust.p12" -password pass:%PASS% +"%OPENSSL%" pkcs12 -export -in "%TLCP_DIR%\client_sign.crt" -inkey "%TLCP_DIR%\client_sign.key" -out "%TLCP_DIR%\tlcp-client-sign.p12" -password pass:%PASS% -name "client.sign" +"%OPENSSL%" pkcs12 -export -in "%TLCP_DIR%\client_enc.crt" -inkey "%TLCP_DIR%\client_enc.key" -out "%TLCP_DIR%\tlcp-client-enc.p12" -password pass:%PASS% -name "client.enc" + +del /q "%TLS_DIR%\*.csr" "%TLS_DIR%\*.srl" "%TLCP_DIR%\*.csr" "%TLCP_DIR%\*.srl" 2>nul +echo [fixtures] done. Password for all PKCS12 files: %PASS% diff --git a/iotdb-client/client-cpp/test/fixtures/gmssl/ca.crt b/iotdb-client/client-cpp/test/fixtures/gmssl/ca.crt new file mode 100644 index 000000000000..c0cf392cceac --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/gmssl/ca.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBhzCCAS2gAwIBAgIMKH7jQLYO1PZdqFHcMAoGCCqBHM9VAYN1MB4xHDAaBgNV +BAMTE0lvVERCIEdtU1NMIFRlc3QgQ0EwHhcNMjYwOTA3MDYyMDU2WhcNMzYwOTA0 +MDYyMDU2WjAeMRwwGgYDVQQDExNJb1REQiBHbVNTTCBUZXN0IENBMFkwEwYHKoZI +zj0CAQYIKoEcz1UBgi0DQgAEc64x3K+eja7LVGkHdG6NgB7PDde/pZ9CsBeR2Vy6 +odKBxF+8E0bPhIF96MK/5OvMPK5/On8ErutTCpmKqT7W46NRME8wKQYDVR0OBCIE +IA9trTVKmcx2xJmwRczKPKcAkiDZKqlR5dFk5Tw7e9XbMA4GA1UdDwEB/wQEAwIB +BjASBgNVHRMBAf8ECDAGAQH/AgEBMAoGCCqBHM9VAYN1A0gAMEUCIQDV+AR+bVex +wGqi2P2ndiqldKEgMI8VEei5dEEUuIn7swIgEzbvHHoVTGBzZ8tOZm/iVRPcc4qE +NSV7TZhvJM5RTYM= +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/gmssl/client.crt b/iotdb-client/client-cpp/test/fixtures/gmssl/client.crt new file mode 100644 index 000000000000..52515f0fdd5b --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/gmssl/client.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBqDCCAU6gAwIBAgIMHKxjOdCmc6zYLMjeMAoGCCqBHM9VAYN1MB4xHDAaBgNV +BAMTE0lvVERCIEdtU1NMIFRlc3QgQ0EwHhcNMjYwOTA3MDYyMDU2WhcNMzYwOTA0 +MDYyMDU2WjARMQ8wDQYDVQQDEwZjbGllbnQwWTATBgcqhkjOPQIBBggqgRzPVQGC +LQNCAASU68HqabwxAdYF3l/UJpS+5U9eAOxK0XCeDj+eQ7HFD2D5YzBjro4h5JAF +pLnpOJ7bL6OMaXfjnjGQh857vmupo38wfTArBgNVHSMEJDAigCAPba01SpnMdsSZ +sEXMyjynAJIg2SqpUeXRZOU8O3vV2zApBgNVHQ4EIgQg2oPYwVHqpCS+64gEo8jE +ATEtDXfgN8R7T0mNEEkM8PEwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsG +AQUFBwMCMAoGCCqBHM9VAYN1A0gAMEUCIEL4Bdk83gz9he5bosK9dSBITb4uLBOy +TJv1xX2wIQFdAiEAiontpSPcEa/+pP04BlIkgkBHvp+iJ2U+saNYmCZnrcQ= +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/gmssl/client.key b/iotdb-client/client-cpp/test/fixtures/gmssl/client.key new file mode 100644 index 000000000000..7efecb00b4f9 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/gmssl/client.key @@ -0,0 +1,8 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIBBjBhBgkqhkiG9w0BBQ0wVDA0BgkqhkiG9w0BBQwwJwQQ5CMX/QhnD5xS9Thl +e41MuwIDAQAAAgEQMAsGCSqBHM9VAYMRAjAcBggqgRzPVQFoAgQQvfnM72RRJNTE +HFqx4mBGcQSBoJiwMS7M2EcQAv32vi0pht9ggUrCmzkaLNB8w1Tq608Q94882GlU +yx0q7HL/RwLczHz/qsLKQcGzcKCg0+69v+WE7Wf99p6bhQ0+/OuAW82iu8zVTvx8 +0C7/37R56YyzQjGUjvbyZxPvMLmPAWm78OPE2OFDbNBE2P2SqLZgVOv9E2E2PM7x +gaix/uAStdKwUt6nMWf9peFyYCLRDY7VIww= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/iotdb-client/client-cpp/test/fixtures/gmssl/server-certs.pem b/iotdb-client/client-cpp/test/fixtures/gmssl/server-certs.pem new file mode 100644 index 000000000000..9796ce26b621 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/gmssl/server-certs.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIBzzCCAXWgAwIBAgINAJIKP/zXtMYmxWeLVTAKBggqgRzPVQGDdTAeMRwwGgYD +VQQDExNJb1REQiBHbVNTTCBUZXN0IENBMB4XDTI2MDkwNzA2MjA1NloXDTM2MDkw +NDA2MjA1NlowFDESMBAGA1UEAxMJMTI3LjAuMC4xMFkwEwYHKoZIzj0CAQYIKoEc +z1UBgi0DQgAEPin+LRtg2n6WQ7PI+niMhhGNqRiCTZnHsBXnLdQewRJ5x9zsXQ50 +HnPAkXdJkV0vu55CJmSkIsefp9jND1xpCaOBoTCBnjArBgNVHSMEJDAigCAPba01 +SpnMdsSZsEXMyjynAJIg2SqpUeXRZOU8O3vV2zApBgNVHQ4EIgQgc+fYClafjgFJ +Qyq7pYFG+8BE2pqayLFSBwg7GeyZW/gwDgYDVR0PAQH/BAQDAgeAMB8GA1UdEQQY +MBaCCTEyNy4wLjAuMYIJbG9jYWxob3N0MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAoG +CCqBHM9VAYN1A0gAMEUCIQC9jAE3oSgLriSuBRkr+ZrFJcxkTMmUldMpFACDErXm +dAIgLumi8hoervwNT6W6jllje9MF/adCeZza19JKnEPzP/E= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBzjCCAXSgAwIBAgIMDvk3T/Q22AFSlXI0MAoGCCqBHM9VAYN1MB4xHDAaBgNV +BAMTE0lvVERCIEdtU1NMIFRlc3QgQ0EwHhcNMjYwOTA3MDYyMDU2WhcNMzYwOTA0 +MDYyMDU2WjAUMRIwEAYDVQQDEwkxMjcuMC4wLjEwWTATBgcqhkjOPQIBBggqgRzP +VQGCLQNCAAQ+LhfEdMZUhIQLnObbSC0NN+anF0aL5hj1M+s6Pfg+Et7r0pm2wk57 +sD5NnZp8kLlHHFDSm71RxAHO8C7Fbf0Ro4GhMIGeMCsGA1UdIwQkMCKAIA9trTVK +mcx2xJmwRczKPKcAkiDZKqlR5dFk5Tw7e9XbMCkGA1UdDgQiBCDnpXOxsaMMX/T0 +9jXGj8vLnIeYLduZN80UYov7Hpkb9zAOBgNVHQ8BAf8EBAMCAzgwHwYDVR0RBBgw +FoIJMTI3LjAuMC4xgglsb2NhbGhvc3QwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCgYI +KoEcz1UBg3UDSAAwRQIgDlidEsxL1G0tz5hjuzmLecM6aVou9ZcMXtu/bYatX+YC +IQC28s0mFYlUmV0hNCC8GDJfjgDyHiJoX2lJ7taaU6toxg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBhzCCAS2gAwIBAgIMKH7jQLYO1PZdqFHcMAoGCCqBHM9VAYN1MB4xHDAaBgNV +BAMTE0lvVERCIEdtU1NMIFRlc3QgQ0EwHhcNMjYwOTA3MDYyMDU2WhcNMzYwOTA0 +MDYyMDU2WjAeMRwwGgYDVQQDExNJb1REQiBHbVNTTCBUZXN0IENBMFkwEwYHKoZI +zj0CAQYIKoEcz1UBgi0DQgAEc64x3K+eja7LVGkHdG6NgB7PDde/pZ9CsBeR2Vy6 +odKBxF+8E0bPhIF96MK/5OvMPK5/On8ErutTCpmKqT7W46NRME8wKQYDVR0OBCIE +IA9trTVKmcx2xJmwRczKPKcAkiDZKqlR5dFk5Tw7e9XbMA4GA1UdDwEB/wQEAwIB +BjASBgNVHRMBAf8ECDAGAQH/AgEBMAoGCCqBHM9VAYN1A0gAMEUCIQDV+AR+bVex +wGqi2P2ndiqldKEgMI8VEei5dEEUuIn7swIgEzbvHHoVTGBzZ8tOZm/iVRPcc4qE +NSV7TZhvJM5RTYM= +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/gmssl/server-keys.pem b/iotdb-client/client-cpp/test/fixtures/gmssl/server-keys.pem new file mode 100644 index 000000000000..af019f76d426 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/gmssl/server-keys.pem @@ -0,0 +1,16 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIBBjBhBgkqhkiG9w0BBQ0wVDA0BgkqhkiG9w0BBQwwJwQQ+pKL+uznRIQB///N +SXHRIAIDAQAAAgEQMAsGCSqBHM9VAYMRAjAcBggqgRzPVQFoAgQQBcrcir58gyH/ +zVe/1FhdWASBoK82GtpTf0d0OwPI9jbZ/NDgv5GKxqBIz21AGrDrjG3QD+sFW8AV +zyL42Yltvdm9J0OVuVwHCVGVm+r0zASHtx1lA4EY1B9s2rLuXjAk34xVzh4cH2bJ +BVHDfzfauSFjMal0zl4E77qjS8yxv8uZLE7N0k+px2WbkpCS9tUg5yYT2ws+xt32 +U+YHKugK3g/n2Y1IpPNFGomH7DBDWMTxt5Y= +-----END ENCRYPTED PRIVATE KEY----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIBBjBhBgkqhkiG9w0BBQ0wVDA0BgkqhkiG9w0BBQwwJwQQrsn3/cFqcTiLF9S5 +X+WiWAIDAQAAAgEQMAsGCSqBHM9VAYMRAjAcBggqgRzPVQFoAgQQJuYnZjhNprza +/R6OFWj+FASBoAhcnfsL/2E6VTEHP3Ww7Gz6GlIIXcXYOkQC0F40zDWhEEfbvw5j +scef8GNFytpkuhWXhLzhZfyzqs0KU0pRMDVwIkGcbCF5i86zIYYkkAmeZ6KzsNYT +GGtU1y1sfb7l9rGtZ32xnb/eCCgHufuB6VQ+DG8apU0omkhhUIIHndWT8nITqIPD +gIoFEt8HTXy1JILfp7O1psNMo9RphDp+AZg= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/ca.crt b/iotdb-client/client-cpp/test/fixtures/tlcp/ca.crt new file mode 100644 index 000000000000..07ade3a4ced4 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/ca.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBhTCCASugAwIBAgIUKvwxT3ypjPE0o1Xm4uy26vAV7dEwCgYIKoEcz1UBg3Uw +GDEWMBQGA1UEAwwNSW9UREIgVExDUCBDQTAeFw0yNjA3MDMwMzM0MzFaFw0zNjA2 +MzAwMzM0MzFaMBgxFjAUBgNVBAMMDUlvVERCIFRMQ1AgQ0EwWTATBgcqhkjOPQIB +BggqgRzPVQGCLQNCAARTre1ea094xClkcp6tz88qakjD3QL3VGQK2OBHWEECG8+v +bCqYUsbcOdNshtjk8MZcpznViFQaS3K+3Bf7FQwzo1MwUTAdBgNVHQ4EFgQUtwWN +1oBD+b/DANRs2So52umc9WEwHwYDVR0jBBgwFoAUtwWN1oBD+b/DANRs2So52umc +9WEwDwYDVR0TAQH/BAUwAwEB/zAKBggqgRzPVQGDdQNIADBFAiEAh8/BGnVxwjuL +yDkaOK/J1IL1c8wIGx6TqW7Re25CkCkCIDtLgej8xmZI4I0nL9Er+YhN8FD4BwzK +qoK4jYsnVf1Z +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/client_enc.crt b/iotdb-client/client-cpp/test/fixtures/tlcp/client_enc.crt new file mode 100644 index 000000000000..8b3b3a0cddcc --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/client_enc.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBJzCBzgIUWlOhvwyTl25h6Y2n3L//h7bOyXQwCgYIKoEcz1UBg3UwGDEWMBQG +A1UEAwwNSW9UREIgVExDUCBDQTAeFw0yNjA3MDMwMzM0MzFaFw0zNjA2MzAwMzM0 +MzFaMBUxEzARBgNVBAMMCmNsaWVudCBlbmMwWTATBgcqhkjOPQIBBggqgRzPVQGC +LQNCAASRqpdiAJcuzGV2xI7NveKK4e/NtlRfnYg4DViomBN4a1sMwYCoz+5hun9S +mlsp/46HmgsHdCfySrMpAapjombnMAoGCCqBHM9VAYN1A0gAMEUCIGjsv/DgrY85 +W0GSyaB0KpFkId0D/s8Vc5hETJw/anC5AiEAyWw7RfrgYrLsSrvyh1rC9xd17jsV +ASgtvkYznvtAuYM= +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/client_enc.key b/iotdb-client/client-cpp/test/fixtures/tlcp/client_enc.key new file mode 100644 index 000000000000..a6d75ee094b6 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/client_enc.key @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqgRzPVQGCLQ== +-----END EC PARAMETERS----- +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgpM8Gsh1RlMa7+mM7 +rBwXX+zmn3rLR9xrM5CDyXQKvv2hRANCAASRqpdiAJcuzGV2xI7NveKK4e/NtlRf +nYg4DViomBN4a1sMwYCoz+5hun9Smlsp/46HmgsHdCfySrMpAapjombn +-----END PRIVATE KEY----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/client_sign.crt b/iotdb-client/client-cpp/test/fixtures/tlcp/client_sign.crt new file mode 100644 index 000000000000..6d64c3cd1783 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/client_sign.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBKDCBzwIUJ0vuKfRYbq5vlfVOrnG+BuyB9EIwCgYIKoEcz1UBg3UwGDEWMBQG +A1UEAwwNSW9UREIgVExDUCBDQTAeFw0yNjA3MDMwMzM0MzFaFw0zNjA2MzAwMzM0 +MzFaMBYxFDASBgNVBAMMC2NsaWVudCBzaWduMFkwEwYHKoZIzj0CAQYIKoEcz1UB +gi0DQgAEte6Lr50Tithv28OXSI+yewqHNbyGl+5vrgJg93LHzD+wyODruv+bqBF6 +N1KinzdYJrPtQiQOqTR4Zmw32bWVAzAKBggqgRzPVQGDdQNIADBFAiAD7TwAMMdd +r5EmQrDN9v/UGCaQnLOhIL3hoTlgCqR5EwIhAOeOx24taX93GkXWBym//EdUqeJ+ +jPJtJVNVUG5kCtla +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/client_sign.key b/iotdb-client/client-cpp/test/fixtures/tlcp/client_sign.key new file mode 100644 index 000000000000..15537f66dca4 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/client_sign.key @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqgRzPVQGCLQ== +-----END EC PARAMETERS----- +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQggl/d0rcpSnK+Dawf +D6bzcQvOp4DyCMikGDAeYwY/BnKhRANCAAS17ouvnROK2G/bw5dIj7J7Coc1vIaX +7m+uAmD3csfMP7DI4Ou6/5uoEXo3UqKfN1gms+1CJA6pNHhmbDfZtZUD +-----END PRIVATE KEY----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_ca.crt b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_ca.crt new file mode 100644 index 000000000000..cc062198a1ab --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_ca.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBtDCCAVqgAwIBAgIUGcWM1awdQfYpDykkpQljaD7SJvAwCgYIKoEcz1UBg3Uw +JzElMCMGA1UEAwwcSW9UREIgVExDUCBJbnRlcm1lZGlhdGUgUm9vdDAeFw0yNjA5 +MDcwNjE3NTJaFw0zNjA5MDQwNjE3NTJaMCUxIzAhBgNVBAMMGklvVERCIFRMQ1Ag +SW50ZXJtZWRpYXRlIENBMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEyklx/mPp +E0GD6D9oJIwc326XkGMaG+5O9KePwF0MBdkYmDayXCu5r1vv8k1gwXAE2YdXBQ9L +CuVqnN0wshmwJaNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFM1GZKmy204Fl9/yNmcUdTDjOjgrMB8GA1UdIwQYMBaAFBsz +Te+705g3nVf3nKqijkDIjYZWMAoGCCqBHM9VAYN1A0gAMEUCIEgJl6LSXSCQFX+5 +5UUk87baiup9s4m90y9odgCQ+fF5AiEA+gsn8Bp4fZvdrvC1sjgnwuhbsv/s0LFk +oG5j3jv7LXQ= +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_enc.crt b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_enc.crt new file mode 100644 index 000000000000..a730c1cef66e --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_enc.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBvjCCAWSgAwIBAgIUWiE+1u/yvx+ZiTydD5HQLOwjLYwwCgYIKoEcz1UBg3Uw +JTEjMCEGA1UEAwwaSW9UREIgVExDUCBJbnRlcm1lZGlhdGUgQ0EwHhcNMjYwOTA3 +MDYxNzUyWhcNMzYwOTA0MDYxNzUyWjAiMSAwHgYDVQQDDBdpbnRlcm1lZGlhdGUg +Y2xpZW50IGVuYzBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABH3Sj1uxAJF2xsvY +Md3K+jkromLK9lcQy9isW4cT+53idoNdpEgsd1yDslYReDA3HGV8RooeimI45wE0 +PeJ7LZSjdTBzMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgM4MBMGA1UdJQQM +MAoGCCsGAQUFBwMCMB0GA1UdDgQWBBTgU0BAwtD0fq7kg22GWpudha3SwjAfBgNV +HSMEGDAWgBTNRmSpsttOBZff8jZnFHUw4zo4KzAKBggqgRzPVQGDdQNIADBFAiEA +pxaJNVW9T2Boaa4uJjHt6c7NVA8ZKp6UhuOfAnJ2XQkCICrPpWQCfzT/j1I+ZWUI +xC9Zn+v2Vvkx/QzX3YFekgqT +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_enc.key b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_enc.key new file mode 100644 index 000000000000..c8d451439a63 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_enc.key @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqgRzPVQGCLQ== +-----END EC PARAMETERS----- +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgejUmcf19CjRBRNRe +8GKO2DGlx5A4naZ3PQ0Yeo94MLuhRANCAAR90o9bsQCRdsbL2DHdyvo5K6JiyvZX +EMvYrFuHE/ud4naDXaRILHdcg7JWEXgwNxxlfEaKHopiOOcBND3iey2U +-----END PRIVATE KEY----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_sign.crt b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_sign.crt new file mode 100644 index 000000000000..5e59acf0ccec --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_sign.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBvjCCAWWgAwIBAgIUIFFwN1rsGTOcYrJl4GmIAj9LHIEwCgYIKoEcz1UBg3Uw +JTEjMCEGA1UEAwwaSW9UREIgVExDUCBJbnRlcm1lZGlhdGUgQ0EwHhcNMjYwOTA3 +MDYxNzUyWhcNMzYwOTA0MDYxNzUyWjAjMSEwHwYDVQQDDBhpbnRlcm1lZGlhdGUg +Y2xpZW50IHNpZ24wWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAARvB9v4Juui1DIO +XMdGhE6Hn6vjYSDmS+SfPJSNGMLlV46zIsRwnfCrbj3+BHUTuQ0UAqqoMbs6dSdw +vXOLoE3lo3UwczAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE +DDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQUeE81HElIpt0iKxzBKYh740xiXTMwHwYD +VR0jBBgwFoAUzUZkqbLbTgWX3/I2ZxR1MOM6OCswCgYIKoEcz1UBg3UDRwAwRAIg +W1AXRQHe81XikQWvXBDfQ5iMJCsrY2J71f55LPE5xlACIBELIjF3peiNC1DgYN6K +mG9+GI/ejW9kHVJcCFhVGkJd +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_sign.key b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_sign.key new file mode 100644 index 000000000000..7b0b25f1d11d --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_client_sign.key @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqgRzPVQGCLQ== +-----END EC PARAMETERS----- +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgVds7KmKbH8/BFp5q +hcsONAkyiIL86b/kMzvEpTEONzGhRANCAARvB9v4Juui1DIOXMdGhE6Hn6vjYSDm +S+SfPJSNGMLlV46zIsRwnfCrbj3+BHUTuQ0UAqqoMbs6dSdwvXOLoE3l +-----END PRIVATE KEY----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_root.crt b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_root.crt new file mode 100644 index 000000000000..d0f7b68f3f1a --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/intermediate_root.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBtzCCAVygAwIBAgIUHY5kMnQgGgMfnGOyj4FLPWtYHrYwCgYIKoEcz1UBg3Uw +JzElMCMGA1UEAwwcSW9UREIgVExDUCBJbnRlcm1lZGlhdGUgUm9vdDAeFw0yNjA5 +MDcwNjE3NTJaFw0zNjA5MDQwNjE3NTJaMCcxJTAjBgNVBAMMHElvVERCIFRMQ1Ag +SW50ZXJtZWRpYXRlIFJvb3QwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAARmLxUo +/zSpCqeRou/n8sAYa4RJLA7vvFoh1tRLm/12NKVIP+aqow1wC1HrtteGKskLnNfn +Ea65DWj69MYz/fsAo2YwZDAdBgNVHQ4EFgQUGzNN77vTmDedV/ecqqKOQMiNhlYw +HwYDVR0jBBgwFoAUGzNN77vTmDedV/ecqqKOQMiNhlYwEgYDVR0TAQH/BAgwBgEB +/wIBATAOBgNVHQ8BAf8EBAMCAQYwCgYIKoEcz1UBg3UDSQAwRgIhAKTl76cpA4Vz +KQMYrU6PN/JUZuSR0bodJQ9YW4RQyMAbAiEAp6OAiZthuja07qycjtxdgbNKpUnR +339zlvTEGNzE98o= +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/server_enc.crt b/iotdb-client/client-cpp/test/fixtures/tlcp/server_enc.crt new file mode 100644 index 000000000000..7e52a1280caa --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/server_enc.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBJzCBzgIUGQOTcoIIr50fdhDjoPyyTTrYga0wCgYIKoEcz1UBg3UwGDEWMBQG +A1UEAwwNSW9UREIgVExDUCBDQTAeFw0yNjA3MDMwMzM0MzJaFw0zNjA2MzAwMzM0 +MzJaMBUxEzARBgNVBAMMCnNlcnZlciBlbmMwWTATBgcqhkjOPQIBBggqgRzPVQGC +LQNCAARxp2OqYF3uklRiNg5Dz89V/EDsw3uPaXKxqETMs0Jv0AynA/OINtjY1IK2 +jq5eoSIOJKAYV7kIXg8xEAxF1dpQMAoGCCqBHM9VAYN1A0gAMEUCIQC5zmE3XgT6 +qlnFNhUhtk2gTsbC0D0iiVh7oGDHsdV31wIgG/xfFl46bsoXkRdrZTgrDyuSjQ9r +b+SE017fmwAVLUo= +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/server_enc.key b/iotdb-client/client-cpp/test/fixtures/tlcp/server_enc.key new file mode 100644 index 000000000000..0edfda1a4258 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/server_enc.key @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqgRzPVQGCLQ== +-----END EC PARAMETERS----- +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQg8n5MGmIXrvuybXKW +T/xAMviJsTFOfV/ZSjhcpdrX6PehRANCAARxp2OqYF3uklRiNg5Dz89V/EDsw3uP +aXKxqETMs0Jv0AynA/OINtjY1IK2jq5eoSIOJKAYV7kIXg8xEAxF1dpQ +-----END PRIVATE KEY----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/server_sign.crt b/iotdb-client/client-cpp/test/fixtures/tlcp/server_sign.crt new file mode 100644 index 000000000000..a9176c0c62a8 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/server_sign.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBJzCBzwIURRSH5ItGF6FHEy/mLryngsiS7AMwCgYIKoEcz1UBg3UwGDEWMBQG +A1UEAwwNSW9UREIgVExDUCBDQTAeFw0yNjA3MDMwMzM0MzJaFw0zNjA2MzAwMzM0 +MzJaMBYxFDASBgNVBAMMC3NlcnZlciBzaWduMFkwEwYHKoZIzj0CAQYIKoEcz1UB +gi0DQgAE7eWZ4hSOUnlLO1ZYHGiM+tkYmidfNEIgx/p3bAXB3aWl49WKbA8uMVjI +75QSXvYW1EqYThHRd3Zz2NU1NWBSDDAKBggqgRzPVQGDdQNHADBEAiBbMYyjibY0 +1mPxsDf1KemntnmhTaSukWpyDTu9bdWYKQIgKT9Rsri4T6eCGyeTtU+olCH5S38+ +PHYQ42imyJU6oHw= +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/server_sign.key b/iotdb-client/client-cpp/test/fixtures/tlcp/server_sign.key new file mode 100644 index 000000000000..9f546d2f1843 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tlcp/server_sign.key @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqgRzPVQGCLQ== +-----END EC PARAMETERS----- +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQg9FrxcDNbCS5UxHuN +zF2fRsR5Xcn82MG1DxwgtcQzc6GhRANCAATt5ZniFI5SeUs7VlgcaIz62RiaJ180 +QiDH+ndsBcHdpaXj1YpsDy4xWMjvlBJe9hbUSphOEdF3dnPY1TU1YFIM +-----END PRIVATE KEY----- diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/tlcp-client-enc.p12 b/iotdb-client/client-cpp/test/fixtures/tlcp/tlcp-client-enc.p12 new file mode 100644 index 000000000000..fcbf1c7c1716 Binary files /dev/null and b/iotdb-client/client-cpp/test/fixtures/tlcp/tlcp-client-enc.p12 differ diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/tlcp-client-sign.p12 b/iotdb-client/client-cpp/test/fixtures/tlcp/tlcp-client-sign.p12 new file mode 100644 index 000000000000..d82ebeba9be4 Binary files /dev/null and b/iotdb-client/client-cpp/test/fixtures/tlcp/tlcp-client-sign.p12 differ diff --git a/iotdb-client/client-cpp/test/fixtures/tlcp/tlcp-trust.p12 b/iotdb-client/client-cpp/test/fixtures/tlcp/tlcp-trust.p12 new file mode 100644 index 000000000000..e7322f292e79 Binary files /dev/null and b/iotdb-client/client-cpp/test/fixtures/tlcp/tlcp-trust.p12 differ diff --git a/iotdb-client/client-cpp/test/fixtures/tls/ca.crt b/iotdb-client/client-cpp/test/fixtures/tls/ca.crt new file mode 100644 index 000000000000..b356ec366329 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tls/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDETCCAfmgAwIBAgIUFNsUYuwzkISBNzd0RdZEMLRjhu8wDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNSW9UREIgVGVzdCBDQTAeFw0yNjA3MDMwMzM0MzFaFw0z +NjA2MzAwMzM0MzFaMBgxFjAUBgNVBAMMDUlvVERCIFRlc3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCutbmN5+3qN8hGPzIys3XH5sSTnBmXbGNO +MViLiE8kysCfRMlc4ckHri/EdTsgH+V6mjf0rxuyH2+TkE7kATiYSU+a6EB/N1Fv +hpkEi7pL8lProdtcyriTTE8PahjdbWnpTe8lNjQFbkhRnQaJr0R8DGEXpVdsAVez +gcG5lruj0lYzZRIWhVxvSEzKTUnqaO83NcEqaRobTLj2uCmfLo4jLd4OQGf3J94w +6ayhNfP7U4iQeReheI9YhDjNIgkClVKgmmiyQb0VfE+O/nL1OVOazybkEHXNA8So +mN8MRafKFCSm+T1t9MBHHbcXp1tZRUHN0x4RjmAU8MPZiGQ55OsBAgMBAAGjUzBR +MB0GA1UdDgQWBBQWSSihr7kDyU9GS6cQaPz5+vtwhTAfBgNVHSMEGDAWgBQWSSih +r7kDyU9GS6cQaPz5+vtwhTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQCT2TqltOY8slGpF+wB+pBELa1vcnZqajju5OO00uOYvkbKBEZ87wSO4Rag +idRnKKeKPe3KcvT7DdR2z21hl36pt8neDC1b8OohL4quBcO21t7gUbmxvfcQLijd +V9wd9sm8TriDIO/mx8ZsDwhu/dupGobboqy0r+C9t5/GsjdL17Kp30st0KffKsSs +UxCaeL0sMD2tVQvx1a5BRrQl8IrLEzpVWDhVFiWY7iwimMX0c+bI2CcvWb8Hd/Km +0l8Wt84XAYecWTVMEBLc/T5kj3DGv8S6TkAP4AgMn5Zjv59va+Pt7CoXbxmdjO8T +agd3Xt8jfmKDNmnQkPmKTo5uuErR +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tls/client.crt b/iotdb-client/client-cpp/test/fixtures/tls/client.crt new file mode 100644 index 000000000000..2d3dbc14fda9 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tls/client.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICuzCCAaMCFBWG7ViMzmyrCBoJYpbg0zkC6USVMA0GCSqGSIb3DQEBCwUAMBgx +FjAUBgNVBAMMDUlvVERCIFRlc3QgQ0EwHhcNMjYwNzAzMDMzNDMxWhcNMzYwNjMw +MDMzNDMxWjAcMRowGAYDVQQDDBFJb1REQiBUZXN0IENsaWVudDCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANsjPpYWA5e0HKyUxbdoVtdYjtnJegHbHdz0 +I4hbvBDe5ySMdBIEUtNTb/zGmxb0nhkTjIxV9wh3Wb3JVhNJ4oaIclnIjfMWNc0/ +o+j8E+lce1VIV5CfZwiYUI6cilP7H4vkaGrTW14x0LcJgU8BhoQbzk5GzRdVcayc +h+nDIsTfbMoT6Ag7dq2mS32Iq0F58IFP9ELT8cJ9Ue1mfWE74d+O5P/NtPU2CWdZ +JXu4yka1Li8Ug3Jq+6I2LmDlBbiq+IjF5kj3iyDIBU34b2WdiOChuhaB4EyhiXf6 +j+nQM7D+N1CCf55AtfJKsiLtA3Dp73uL3OE7yr/e1scHxOG6SOECAwEAATANBgkq +hkiG9w0BAQsFAAOCAQEAnCt5Ffs8FkKRq8SkFnqLgZX2M0mlfXe8SzQk+dFPX1s+ +/2A+6JkiZ9JniR22uryUt40B3Cq2U5zhsINVlR3voye1F8MjJxEtaIfPTTh8MI2L +vyAQaIKtBj/VJX+tCiaYyO0tSCrAyBvdzArGcwcr3V0SdPxLzT7q4DrDM9F0uf1x +dDUgn9inGDBpWXHNgnOLzqM7Xjzs4+vbZSCQBbYY9HTmyvp+NDFmTT8dKC2lvMZH +Cugw0tTHv2N+wXwx33LUtAPxO5WRCZQ8PhWxJ0lGtV9MMJK2YvNyf9qCHGMiOgX0 +w4q1Gwh6ZTF9Nhsk7gNtDit+bDLb06gtA6oeNI/d8A== +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tls/client.key b/iotdb-client/client-cpp/test/fixtures/tls/client.key new file mode 100644 index 000000000000..1686d990843d --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tls/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDbIz6WFgOXtBys +lMW3aFbXWI7ZyXoB2x3c9COIW7wQ3uckjHQSBFLTU2/8xpsW9J4ZE4yMVfcId1m9 +yVYTSeKGiHJZyI3zFjXNP6Po/BPpXHtVSFeQn2cImFCOnIpT+x+L5Ghq01teMdC3 +CYFPAYaEG85ORs0XVXGsnIfpwyLE32zKE+gIO3atpkt9iKtBefCBT/RC0/HCfVHt +Zn1hO+HfjuT/zbT1NglnWSV7uMpGtS4vFINyavuiNi5g5QW4qviIxeZI94sgyAVN ++G9lnYjgoboWgeBMoYl3+o/p0DOw/jdQgn+eQLXySrIi7QNw6e97i9zhO8q/3tbH +B8ThukjhAgMBAAECggEAH3DoGuqfq1V5Q724vH7o7s7S+CZzLe79UuVob7kRu63v +pgvM34TlSVLQX4kzWVDRmjF22e+/mORe6N8JTY0tRjYvifg/faAzKfa2ksgQJ0xQ +mcTeY26rfs0zybJmGnSOayjjXmhi1Jn7Izfm6KoEXdILgKmh5XYp8CUpTv3jcDGG +MgdlqxG8rakJ4NHtO6qjgbaEAEsI7JbJj0T+7YPPD42KWvy9f9LYUSN0eCO/7TWw +Cvvl6NX55tz+WwpWdDtIKVjWZRnx4ZA3cZizWGZfSDaoRJWfDTcUKJ4zXHDdxXRL +6ha0cD7N6HJtQFvrxnyH/Uqpnm9rTKhfHvj5uTmaLQKBgQDdufi+60dIjVzQxYhy +4w+BUaI5PQCN0naX+uvlUBzlctrImWQyGsLj2yDbo9IuDBU6qaGPC0Sl29ywBf7W +QxsWbxE/rb9MKO2SEdLRq45W/H/Llr1IV494upWnDpWgvanBeanITveU9HA0/Fm4 +U0PrfExeBXca0dTfAD07Jr3gfwKBgQD9AtTKku/+jXfpQe4IPtZ8rjG6Ezg6KCw9 +JQVwHQaTked82Fj/1F6BiutVQwbQ6UI8FfZ7uF239Cw2O/PI28zpaCtOUPCP5TOI +A1LdwhJAtogfXK1vSX4qxog4sNwmlboxAMixdSZGuBfO/vUxL6nQb+OH3g3gTqS5 +CjnKAQcmnwKBgQCAkASbLvD2MIFQzDiB5QZohVz6s1RO52m8VdHR9NHMePxCtC5U +nw/B7pzuvd5wtLDaguEaf/4d7Y3YwqEwu1hJeb0Wnzf8gP6/Y3ZJ/J9b8Kxo785w +09RsvENpyhsYSODVPiYj7yW/SLyG/ItJRX5sXHYrTh/xfRlg9FKMqboPIQKBgQDz +0K2kxTKXOFbspocu1Pc20VrEOM8/ZAU1qx5xatcykDDmo0ooxsuHxIqB8IR5/76/ +Tl7n3MQbiCau4NlNn1r5NlQ9NUyNLk+Za7KIVwPl7sCAkHvluYnmyMju8KhGWpVB +scK1F/KZxb/TzugTzR206o32GWt/0+lzE8KawqDUewKBgCIc9mkWBtyA5Z7qKDmZ +6yaKs5210GzXGHBccVn6ABzV9BsWh+9r8guT2WxH6q+i4KBmFkRAk1u/AK7o/WOi +2HdOTMgQe5j9Jxnzr6sOQcSHJLYblKbkzGonJ0eEiZH2qFtjjPIz6lqSiuXK6PPr +fBH/Y8bZLE6KjrsBiqlzUiqV +-----END PRIVATE KEY----- diff --git a/iotdb-client/client-cpp/test/fixtures/tls/server.crt b/iotdb-client/client-cpp/test/fixtures/tls/server.crt new file mode 100644 index 000000000000..5b36e382d13b --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tls/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGDCCAgCgAwIBAgIUIzsFiFOpqHEmojkqKN9uUGU4uw4wDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNSW9UREIgVGVzdCBDQTAeFw0yNjA3MDMwMzM0MzFaFw0z +NjA2MzAwMzM0MzFaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAJvzk6OA9O1zWJjPd0wOTqFcGz62XWxWDlFvUeoS +SHimZkSHyIUxduWgXvj5xKnH9372ZFN+K0twmaubDWLxfXyXBTPV+w8TXNoxVP3e +Ibj0Bora595egt/iEQCh3R095DFzK7nTFrnRWahojg4ZJViz1zREr2DLGB1Lz1aM +hfmUdExcjMP0MM0JIhhieQ7GoD353DUR38vpaEoTO+3KUDLxByfcm+bsRYCHA7OS +n0qYM4Zm20+RHFiu3ynSh4fRoWZ0OE+XfvG+buEX3vlWW7NcRRmsK3QhCmwB/TMn +DlTfmXIQzy5jL025GShHNvJRJV6bhAVVP4Zbg3ADlX4jf+UCAwEAAaNeMFwwGgYD +VR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMB0GA1UdDgQWBBT6g7pH7yv5c5TjadgH +3JGeWyYQRDAfBgNVHSMEGDAWgBQWSSihr7kDyU9GS6cQaPz5+vtwhTANBgkqhkiG +9w0BAQsFAAOCAQEAq62942UsASWaOfbRuI4TYfXWDrOtmBnXzWldlnkiBomtph8c +CLdKPQYmY6/UuIh66/vaBQuyqvThWTBDZG5eEx33oxNvXBltMxxiiEf10o4RNpmz +WzNcN4kW058RJtiK4e3T3XOoTObVVEyOzM8nsORZH2ayuEe+KOQ66Gm/OBThSt/i +YgjbWnyaFbWEKIzkzPp/FqTnx7qJQ5Bm3pMD6pB3HI3CVXL14U13iK+B45bdF1E5 ++NbMVGuXkngNZQSGjQBMbYFBvlDJK7N7REdvBnshXgYCrDQQpe3fRpYgNA/RwkFs +w6/YiYe3AoFCUnXQfeViRUkGZfxJFhUFBAKjdQ== +-----END CERTIFICATE----- diff --git a/iotdb-client/client-cpp/test/fixtures/tls/server.key b/iotdb-client/client-cpp/test/fixtures/tls/server.key new file mode 100644 index 000000000000..cb3724767015 --- /dev/null +++ b/iotdb-client/client-cpp/test/fixtures/tls/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCb85OjgPTtc1iY +z3dMDk6hXBs+tl1sVg5Rb1HqEkh4pmZEh8iFMXbloF74+cSpx/d+9mRTfitLcJmr +mw1i8X18lwUz1fsPE1zaMVT93iG49AaK2ufeXoLf4hEAod0dPeQxcyu50xa50Vmo +aI4OGSVYs9c0RK9gyxgdS89WjIX5lHRMXIzD9DDNCSIYYnkOxqA9+dw1Ed/L6WhK +EzvtylAy8Qcn3Jvm7EWAhwOzkp9KmDOGZttPkRxYrt8p0oeH0aFmdDhPl37xvm7h +F975VluzXEUZrCt0IQpsAf0zJw5U35lyEM8uYy9NuRkoRzbyUSVem4QFVT+GW4Nw +A5V+I3/lAgMBAAECggEAOiyHc2OEehsf/ojNqJphrIGOTDt86A/F8YzEErvVOuRf +m8rG+yBziL7lDp0lRmwon7zLufWDsvWC7We+e503wUUYlLiKmZoQdfgXC0hUbgjh +c+Sqv+Gjkl5jF0hKEkFnISckNYJPpOs6Nb8i1pF8w/T6Hy5L+aBpE2yXIGL1Cx9H +fID+vTpYI3W0pQajQJr8RLVRUbVn9ZBNIwqteAxHlW7OlZF9SqrNYo64ANh4f7Pa +So+Roxx9bJWj7sbf97HEMEI9S2sdg03UTFlC6noEzSZbouTwURG1Jj0zlexpKqbG +QYc0D/NG+IEifAwv35jOhWjhUhz5PnZNyNX9IoJx1QKBgQDQNAClbbTEYTm/Mjn+ +iqxqfAMgOJJkRBTmmKnJxJDq/mTDw73E2udGwzHV7Ia2qTmmDChhWnPhiAL7YGPz +Ha5cZ/SvN8Mg+IgEGhiJtLtFd8Wa3NL/5YuHSMVRVcohIb5kNfPJ2grykceuhvj8 +eTfYGYtm/yFbjFK3iiEHOR9nhwKBgQC/wMTrzlM4ttn42F0wms1pkGU7Dy6nzhIy +uQwzgGyS9XZzXkFt5k9n7GF0x54UILCz4hUMbXPt48Vh/z9oYBvV++2bhthzpTdN +Bos7N6HT4fsp2lM/CLxBSDgY2iXzFYmZwmLkT5DpGXl8Kw2JFfpLSdLF++1MKjVn +LixMGscgMwKBgFR59RUqKSFRDZwtJejInWJrRN1q9sLl+NEDekiaj+45H+tqXXIl +G5fTlUHmQVaV3QUpg6zUhZYpmIQkPQmkrl1h9J6vcmXLUWzQpcoh4aYzDaNjG92m +ZnGSrjTtKSE+TsDcPzlUVgLL8Yg3zYirKmRtIOm+dOtvSRSdX/9NRxe7AoGBAJpA +iCjpISlObqov40dmQDfbYJPR8sYqj5keIyKK/Mx7iX3lJN0zmE0RapR2wEOQyJTs +GeKVadzEjdP3cRqVtc69irbCEv10urfLu9U3O4cnEtToPG9Ip6gcYIQdeMnDWZ3H +MaZYG0poo8GvIIRWmbJXAKcjshDDI5KNIjVWlTjhAoGABmQYrlSDsdCuj4w81NBL +GDuhEiHEUYWAZjut1oeaoW3fN0FRX0mN0/MupJFY9GumduD1cE5nEAfoWcXmVOmv +Qup6kAIne0ulO7AlyM6OBjMQm9U52PuFkyZYKXppQLXcU5AXHNvjS6ychrvtPsBJ +6CyxH51/2GhMXFx5Gr7nIao= +-----END PRIVATE KEY----- diff --git a/iotdb-client/client-cpp/test/fixtures/tls/tls-client.p12 b/iotdb-client/client-cpp/test/fixtures/tls/tls-client.p12 new file mode 100644 index 000000000000..063272846dd1 Binary files /dev/null and b/iotdb-client/client-cpp/test/fixtures/tls/tls-client.p12 differ diff --git a/iotdb-client/client-cpp/test/fixtures/tls/tls-server.p12 b/iotdb-client/client-cpp/test/fixtures/tls/tls-server.p12 new file mode 100644 index 000000000000..53ac363590cc Binary files /dev/null and b/iotdb-client/client-cpp/test/fixtures/tls/tls-server.p12 differ diff --git a/iotdb-client/client-cpp/test/fixtures/tls/tls-trust.p12 b/iotdb-client/client-cpp/test/fixtures/tls/tls-trust.p12 new file mode 100644 index 000000000000..f672208e9d76 Binary files /dev/null and b/iotdb-client/client-cpp/test/fixtures/tls/tls-trust.p12 differ diff --git a/iotdb-client/client-cpp/test/main.cpp b/iotdb-client/client-cpp/test/main.cpp index 474cb552abbf..03aa620fdf3b 100644 --- a/iotdb-client/client-cpp/test/main.cpp +++ b/iotdb-client/client-cpp/test/main.cpp @@ -35,12 +35,8 @@ struct SessionListener : Catch::TestEventListenerBase { } if (!session) { SessionBuilder builder; - session = builder.host("127.0.0.1") - ->rpcPort(6667) - ->username("root") - ->password("root") - ->useSSL(false) - ->build(); + builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root")->useSSL(false); + session = builder.build(); } else { session->open(false); } diff --git a/iotdb-client/client-cpp/test/main_Relational.cpp b/iotdb-client/client-cpp/test/main_Relational.cpp index de808c23224e..dcb045e25fe6 100644 --- a/iotdb-client/client-cpp/test/main_Relational.cpp +++ b/iotdb-client/client-cpp/test/main_Relational.cpp @@ -30,8 +30,8 @@ struct SessionListener : Catch::TestEventListenerBase { void testCaseStarting(Catch::TestCaseInfo const& testInfo) override { if (!session) { TableSessionBuilder builder; - session = - builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root")->build(); + builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root")->useSSL(false); + session = builder.build(); } else { session->open(); } diff --git a/iotdb-client/client-cpp/test/main_rpc_ntls.cpp b/iotdb-client/client-cpp/test/main_rpc_ntls.cpp new file mode 100644 index 000000000000..ec0bec9adb32 --- /dev/null +++ b/iotdb-client/client-cpp/test/main_rpc_ntls.cpp @@ -0,0 +1,21 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#define CATCH_CONFIG_MAIN +#include diff --git a/iotdb-client/client-cpp/test/main_rpc_ssl.cpp b/iotdb-client/client-cpp/test/main_rpc_ssl.cpp new file mode 100644 index 000000000000..ec0bec9adb32 --- /dev/null +++ b/iotdb-client/client-cpp/test/main_rpc_ssl.cpp @@ -0,0 +1,21 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#define CATCH_CONFIG_MAIN +#include diff --git a/iotdb-client/client-cpp/test/scripts/configure_iotdb_ssl_it.py b/iotdb-client/client-cpp/test/scripts/configure_iotdb_ssl_it.py new file mode 100644 index 000000000000..5951fbc57dbb --- /dev/null +++ b/iotdb-client/client-cpp/test/scripts/configure_iotdb_ssl_it.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +"""Patch IoTDB distribution RPC SSL settings for C++ integration tests.""" + +from __future__ import annotations + +import re +import shutil +import subprocess +import sys +import time +from pathlib import Path + +STORE_PASSWORD = "thrift" +SERVER_PKCS12 = "tls-server.p12" +SERVER_TRUSTSTORE = "tls-server-trust.p12" + + +def replace_property(text: str, key: str, value: str) -> str: + pattern = re.compile(rf"^{re.escape(key)}=.*$", re.MULTILINE) + replacement = f"{key}={value}" + if pattern.search(text): + return pattern.sub(replacement, text, count=1) + return text.rstrip() + "\n" + replacement + "\n" + + +def stop_iotdb(dist_root: Path) -> None: + if sys.platform == "win32": + stop_script = dist_root / "sbin" / "windows" / "stop-standalone.bat" + else: + stop_script = dist_root / "sbin" / "stop-standalone.sh" + if not stop_script.is_file(): + print(f"stop script not found, skip stop: {stop_script}", file=sys.stderr) + return + print(f"Stopping IoTDB via {stop_script}") + subprocess.run([str(stop_script)], cwd=str(dist_root), check=False, shell=True) + time.sleep(15) + + +def configure_plain(dist_root: Path) -> int: + props_path = dist_root / "conf" / "iotdb-system.properties" + if not props_path.is_file(): + print(f"iotdb-system.properties not found: {props_path}", file=sys.stderr) + return 1 + + text = props_path.read_text(encoding="utf-8") + text = replace_property(text, "enable_thrift_ssl", "false") + text = replace_property(text, "thrift_ssl_client_auth", "false") + text = replace_property(text, "key_store_path", "") + text = replace_property(text, "key_store_pwd", "") + text = replace_property(text, "trust_store_path", "") + text = replace_property(text, "trust_store_pwd", "") + text = replace_property(text, "ssl_protocol", "TLS") + props_path.write_text(text, encoding="utf-8", newline="\n") + print(f"Configured plain RPC in {props_path}") + return 0 + + +def configure_tls(dist_root: Path, fixtures_root: Path, require_client_auth: bool) -> int: + props_path = dist_root / "conf" / "iotdb-system.properties" + if not props_path.is_file(): + print(f"iotdb-system.properties not found: {props_path}", file=sys.stderr) + return 1 + + ssl_dir = dist_root / "conf" / "cpp-ssl-it" + ssl_dir.mkdir(parents=True, exist_ok=True) + source = fixtures_root / "tls" / SERVER_PKCS12 + if not source.is_file(): + print(f"fixture missing: {source}", file=sys.stderr) + return 1 + shutil.copy2(source, ssl_dir / SERVER_PKCS12) + + key_store = (ssl_dir / SERVER_PKCS12).as_posix() + trust_store = "" + if require_client_auth: + ca_cert = fixtures_root / "tls" / "ca.crt" + if not ca_cert.is_file(): + print(f"fixture missing: {ca_cert}", file=sys.stderr) + return 1 + keytool = shutil.which("keytool") + if keytool is None: + print("keytool not found; a JDK is required for the mutual TLS IT", file=sys.stderr) + return 1 + trust_store_path = ssl_dir / SERVER_TRUSTSTORE + trust_store_path.unlink(missing_ok=True) + subprocess.run( + [ + keytool, + "-importcert", + "-noprompt", + "-alias", + "cpp-ssl-it-ca", + "-file", + str(ca_cert), + "-keystore", + str(trust_store_path), + "-storetype", + "PKCS12", + "-storepass", + STORE_PASSWORD, + ], + check=True, + ) + trust_store = trust_store_path.as_posix() + + text = props_path.read_text(encoding="utf-8") + text = replace_property(text, "enable_thrift_ssl", "true") + text = replace_property( + text, "thrift_ssl_client_auth", str(require_client_auth).lower() + ) + text = replace_property(text, "key_store_path", key_store) + text = replace_property(text, "key_store_pwd", STORE_PASSWORD) + text = replace_property(text, "trust_store_path", trust_store) + text = replace_property( + text, "trust_store_pwd", STORE_PASSWORD if require_client_auth else "" + ) + text = replace_property(text, "ssl_protocol", "TLS") + props_path.write_text(text, encoding="utf-8", newline="\n") + mode = "mutual TLS" if require_client_auth else "TLS" + print(f"Configured {mode} IT server properties in {props_path}") + return 0 + + +def main() -> int: + if len(sys.argv) < 3: + print( + "usage: configure_iotdb_ssl_it.py " + "[enable|mutual|disable]", + file=sys.stderr, + ) + return 2 + + dist_root = Path(sys.argv[1]).resolve() + fixtures_root = Path(sys.argv[2]).resolve() + mode = sys.argv[3].lower() if len(sys.argv) >= 4 else "enable" + + if mode in ("disable", "plain", "off"): + stop_iotdb(dist_root) + return configure_plain(dist_root) + + if mode in ("enable", "tls", "on"): + stop_iotdb(dist_root) + return configure_tls(dist_root, fixtures_root, False) + + if mode in ("mutual", "mtls"): + stop_iotdb(dist_root) + return configure_tls(dist_root, fixtures_root, True) + + print(f"unknown mode: {mode}", file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/iotdb-client/client-cpp/test/scripts/run_cpp_it_phases.py b/iotdb-client/client-cpp/test/scripts/run_cpp_it_phases.py new file mode 100644 index 000000000000..9b068014eebd --- /dev/null +++ b/iotdb-client/client-cpp/test/scripts/run_cpp_it_phases.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +"""Run C++ client integration tests against plain, TLS, and mutual TLS IoTDB.""" + +from __future__ import annotations + +import argparse +import subprocess +import sys +import time +from pathlib import Path + + +def run(cmd: list[str], cwd: Path) -> None: + print(f"+ {' '.join(cmd)}", flush=True) + subprocess.run(cmd, cwd=str(cwd), check=True) + + +def stop_iotdb(dist_root: Path) -> None: + if sys.platform == "win32": + stop_script = dist_root / "sbin" / "windows" / "stop-standalone.bat" + else: + stop_script = dist_root / "sbin" / "stop-standalone.sh" + if not stop_script.is_file(): + print(f"stop script not found, skip stop: {stop_script}", file=sys.stderr) + return + print(f"Stopping IoTDB via {stop_script}") + subprocess.run([str(stop_script)], cwd=str(dist_root), check=False, shell=True) + time.sleep(15) + + +def start_iotdb(dist_root: Path, start_script: Path, wait_s: int) -> None: + if not start_script.is_file(): + raise FileNotFoundError(f"start script not found: {start_script}") + print(f"Starting IoTDB via {start_script}") + subprocess.Popen( + [str(start_script)], + cwd=str(dist_root), + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + print(f"Waiting {wait_s}s for IoTDB to become ready") + time.sleep(wait_s) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "build_dir", help="CMake build directory containing CTestTestfile.cmake" + ) + parser.add_argument("dist_root", help="IoTDB distribution root") + parser.add_argument("fixtures_root", help="C++ test fixtures root") + parser.add_argument( + "scripts_root", help="Directory containing configure_iotdb_ssl_it.py" + ) + parser.add_argument( + "start_script", help="Relative path to start-standalone script under dist sbin/" + ) + parser.add_argument( + "--config", default="Release", help="CTest build configuration (MSVC)" + ) + parser.add_argument( + "--wait-seconds", type=int, default=45, help="Seconds to wait after IoTDB start" + ) + args = parser.parse_args() + + build_dir = Path(args.build_dir).resolve() + dist_root = Path(args.dist_root).resolve() + fixtures_root = Path(args.fixtures_root).resolve() + scripts_root = Path(args.scripts_root).resolve() + start_script = dist_root / "sbin" / args.start_script + + ctest_base = ["ctest", "-j", "1", "--output-on-failure"] + if args.config: + ctest_base.extend(["-C", args.config]) + + print("=== Phase 1: plain IoTDB (session IT + examples) ===") + run(ctest_base + ["-L", "plain"], build_dir) + + print("=== Phase 2: restart IoTDB with TLS (rpc SSL/NTLS IT) ===") + stop_iotdb(dist_root) + configure = scripts_root / "configure_iotdb_ssl_it.py" + run( + [sys.executable, str(configure), str(dist_root), str(fixtures_root), "enable"], + cwd=scripts_root, + ) + try: + start_iotdb(dist_root, start_script, args.wait_seconds) + run(ctest_base + ["-L", "ssl"], build_dir) + finally: + stop_iotdb(dist_root) + + print("=== Phase 3: restart IoTDB with mutual TLS ===") + run( + [sys.executable, str(configure), str(dist_root), str(fixtures_root), "mutual"], + cwd=scripts_root, + ) + try: + start_iotdb(dist_root, start_script, args.wait_seconds) + run(ctest_base + ["-L", "mtls"], build_dir) + finally: + stop_iotdb(dist_root) + + print("=== Phase 4: NTLS (no IoTDB; openssl s_server) ===") + run(ctest_base + ["-L", "ntls"], build_dir) + + print("All C++ integration test phases passed.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/iotdb-client/client-cpp/test/tools/GenTlcpDualP12.cpp b/iotdb-client/client-cpp/test/tools/GenTlcpDualP12.cpp new file mode 100644 index 000000000000..a8fb9192066c --- /dev/null +++ b/iotdb-client/client-cpp/test/tools/GenTlcpDualP12.cpp @@ -0,0 +1,159 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +std::string joinPath(const std::string& base, const std::string& name) { + if (base.empty()) { + return name; + } + const char sep = (base.find('\\') != std::string::npos) ? '\\' : '/'; + if (base.back() == '/' || base.back() == '\\') { + return base + name; + } + return base + sep + name; +} + +EVP_PKEY* readPrivateKeyPem(const std::string& path) { + BIO* bio = BIO_new_file(path.c_str(), "rb"); + if (bio == nullptr) { + return nullptr; + } + EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); + BIO_free(bio); + return key; +} + +X509* readCertificatePem(const std::string& path) { + BIO* bio = BIO_new_file(path.c_str(), "rb"); + if (bio == nullptr) { + return nullptr; + } + X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + BIO_free(bio); + return cert; +} + +void addLocalKeyId(PKCS12_SAFEBAG* bag, X509* cert) { + unsigned char keyid[EVP_MAX_MD_SIZE]; + unsigned int keyidLen = 0; + if (X509_pubkey_digest(cert, EVP_sha1(), keyid, &keyidLen) == 1) { + PKCS12_add_localkeyid(bag, keyid, static_cast(keyidLen)); + } +} + +void addCertAndKeyBags(STACK_OF(PKCS12_SAFEBAG) * bags, X509* cert, EVP_PKEY* key, + const char* friendlyName, const std::string& password) { + PKCS12_SAFEBAG* certbag = PKCS12_SAFEBAG_create_cert(cert); + PKCS12_add_friendlyname_utf8(certbag, friendlyName, -1); + addLocalKeyId(certbag, cert); + sk_PKCS12_SAFEBAG_push(bags, certbag); + + PKCS8_PRIV_KEY_INFO* p8 = EVP_PKEY2PKCS8(key); + if (p8 == nullptr) { + return; + } + PKCS12_SAFEBAG* keybag = PKCS12_SAFEBAG_create_pkcs8_encrypt( + NID_pbes2, password.c_str(), static_cast(password.size()), nullptr, 0, 2048, p8); + PKCS8_PRIV_KEY_INFO_free(p8); + if (keybag == nullptr) { + return; + } + PKCS12_add_friendlyname_utf8(keybag, friendlyName, -1); + addLocalKeyId(keybag, cert); + sk_PKCS12_SAFEBAG_push(bags, keybag); +} + +} // namespace + +int main(int argc, char** argv) { + if (argc < 2) { + std::cerr << "usage: gen_tlcp_dual_p12 \n"; + return 1; + } + OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, nullptr); + const std::string dir = argv[1]; + const std::string password = "thrift"; + const std::string outPath = joinPath(dir, "tlcp-client-dual.p12"); + + X509* signCert = readCertificatePem(joinPath(dir, "client_sign.crt")); + EVP_PKEY* signKey = readPrivateKeyPem(joinPath(dir, "client_sign.key")); + X509* encCert = readCertificatePem(joinPath(dir, "client_enc.crt")); + EVP_PKEY* encKey = readPrivateKeyPem(joinPath(dir, "client_enc.key")); + if (signCert == nullptr || signKey == nullptr || encCert == nullptr || encKey == nullptr) { + std::cerr << "failed to read TLCP PEM fixtures\n"; + return 2; + } + + STACK_OF(PKCS12_SAFEBAG)* bags = sk_PKCS12_SAFEBAG_new_null(); + addCertAndKeyBags(bags, signCert, signKey, "client.sign", password); + addCertAndKeyBags(bags, encCert, encKey, "client.enc", password); + PKCS7* p7 = PKCS12_pack_p7encdata(NID_pbes2, password.c_str(), static_cast(password.size()), + nullptr, 0, 2048, bags); + sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free); + if (p7 == nullptr) { + std::cerr << "failed to pack PKCS12 bags\n"; + return 3; + } + + PKCS12* p12 = PKCS12_init(NID_pkcs7_data); + STACK_OF(PKCS7)* safes = sk_PKCS7_new_null(); + sk_PKCS7_push(safes, p7); + if (PKCS12_pack_authsafes(p12, safes) != 1) { + sk_PKCS7_pop_free(safes, PKCS7_free); + PKCS12_free(p12); + std::cerr << "failed to pack PKCS12 authsafes\n"; + return 4; + } + sk_PKCS7_pop_free(safes, PKCS7_free); + + BIO* bio = BIO_new_file(outPath.c_str(), "wb"); + if (bio == nullptr || i2d_PKCS12_bio(bio, p12) != 1) { + std::cerr << "failed to write " << outPath << "\n"; + BIO_free(bio); + PKCS12_free(p12); + return 5; + } + BIO_free(bio); + PKCS12_free(p12); + X509_free(signCert); + EVP_PKEY_free(signKey); + X509_free(encCert); + EVP_PKEY_free(encKey); + std::cout << "wrote " << outPath << "\n"; + return 0; +} diff --git a/iotdb-client/client-cpp/third-party/README.md b/iotdb-client/client-cpp/third-party/README.md index a430adc30c52..16c8923ddba4 100644 --- a/iotdb-client/client-cpp/third-party/README.md +++ b/iotdb-client/client-cpp/third-party/README.md @@ -68,7 +68,7 @@ Alternatively copy files manually from the URLs listed in | Platform | Typical files | |------------|---------------| -| `linux/` | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, `m4-1.4.19.tar.gz`, `flex-2.6.4.tar.gz`, `bison-3.8.tar.gz` (+ `openssl-3.5.0.tar.gz` only when `WITH_SSL=ON` and no system OpenSSL is present) | +| `linux/` | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, `m4-1.4.19.tar.gz`, `flex-2.6.4.tar.gz`, `bison-3.8.tar.gz`, `tongsuo-0aed892c5f48c9a52d1f5667667ae45156b9cdf4.tar.gz` (when `WITH_SSL=ON`, default) | | `mac/` | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz` (Xcode CLT usually provides m4/flex/bison) | | `windows/` | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, `win_flex_bison-2.5.25.zip` (or any `win_flex_bison*.zip`; skip if flex/bison already on `PATH`) | diff --git a/pom.xml b/pom.xml index 90ac0c70ce7c..6c3139505dd4 100644 --- a/pom.xml +++ b/pom.xml @@ -766,6 +766,11 @@ **/package-metadata/third_party/NOTICE **/package-metadata/third_party/licenses/** + + **/client-cpp/test/fixtures/** + + test/fixtures/**/*.crt + test/fixtures/**/*.key hadoopbin windowssystem32