| # Copyright (C) 2015-2026 maClara, LLC <info@maclara-llc.com> |
| # This file is part of the JWT C Library |
| # |
| # SPDX-License-Identifier: MPL-2.0 |
| # This Source Code Form is subject to the terms of the Mozilla Public |
| # License, v. 2.0. If a copy of the MPL was not distributed with this |
| # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| |
| cmake_minimum_required (VERSION 3.7...3.15) |
| list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") |
| |
| include(LibJWTVersions) |
| |
| project(${LIBJWT_PROJECT} |
| VERSION ${LIBJWT_VERSION} |
| DESCRIPTION ${LIBJWT_DESCRIPTION} |
| HOMEPAGE_URL ${LIBJWT_HOMEPAGE_URL} |
| LANGUAGES C) |
| |
| set(MEMORYCHECK_COMMAND_OPTIONS "-q --tool=memcheck --leak-check=yes --num-callers=50 --trace-children=yes --leak-check=full --track-origins=yes --gen-suppressions=all") |
| |
| set(CMAKE_POSITION_INDEPENDENT_CODE ON) |
| add_compile_options(-Wall -Werror -Wextra -Wunused) |
| |
| # Must be set after the above |
| include(GNUInstallDirs) |
| |
| # Find all the things we need for the library |
| find_package(PkgConfig REQUIRED) |
| |
| option(WITH_JSON_C "Whether to use json-c instead of jansson (default is OFF)" OFF) |
| |
| if (WITH_JSON_C) |
| pkg_check_modules(JSONC json-c>=0.16 REQUIRED IMPORTED_TARGET) |
| else() |
| pkg_check_modules(JANSSON jansson>=2.0 REQUIRED IMPORTED_TARGET) |
| endif() |
| |
| if (NOT DEFINED WITH_GNUTLS) |
| set(GNUTLS_AUTO TRUE) |
| endif() |
| |
| option(WITH_GNUTLS "Whether to use GnuTLS (default is auto detect)" ON) |
| option(WITH_MBEDTLS "Whether to use mbedTLS (default is OFF)" OFF) |
| option(WITH_LIBCURL "Whether to include CUrl for retrieving JWKS (default is OFF)" OFF) |
| option(WITH_TESTS "Whether to build and run the testsuite (default is ON)" ON) |
| option(WITH_KCAPI_MD "Whether to use the Linux Kernel Crypto API to offload hmac (default OFF)" OFF) |
| option(WITH_OPENSSL "Whether to use OpenSSL (default is ON)" ON) |
| option(WITH_ML_DSA "Whether to enable experimental ML-DSA (FIPS 204) support (default is OFF)" OFF) |
| |
| # Optional |
| if (WITH_GNUTLS) |
| if (NOT GNUTLS_AUTO) |
| set(GNUTLS_REQUIRED REQUIRED) |
| endif() |
| pkg_check_modules(GNUTLS gnutls>=3.8.8 IMPORTED_TARGET |
| ${GNUTLS_REQUIRED}) |
| endif() |
| |
| if (WITH_MBEDTLS) |
| pkg_check_modules(MBEDTLS mbedcrypto>=3.6.0 IMPORTED_TARGET REQUIRED) |
| endif() |
| |
| if (WITH_LIBCURL) |
| pkg_check_modules(LIBCURL libcurl>=7.8.8 IMPORTED_TARGET REQUIRED) |
| endif() |
| |
| if (WITH_KCAPI_MD) |
| find_library(HAVE_KCAPI kcapi REQUIRED) |
| endif() |
| |
| # Optional (default ON). OpenSSL was historically mandatory; it is now one of |
| # the interchangeable crypto backends, like GnuTLS and MbedTLS. |
| if (WITH_OPENSSL) |
| pkg_check_modules(OPENSSL openssl>=3.0.0 IMPORTED_TARGET REQUIRED) |
| endif() |
| |
| # Experimental ML-DSA (FIPS 204 / RFC 9964) support. Implemented by OpenSSL |
| # >= 3.5 and by GnuTLS >= 3.8.10 (the latter requires a GnuTLS built with a |
| # PQC provider, e.g. --with-leancrypto); MbedTLS has no ML-DSA and cleanly |
| # rejects AKP keys. This drives the public LIBJWT_HAVE_ML_DSA macro (emitted in |
| # jwt_export.h) which gates the backend-independent ML-DSA code; each backend's |
| # own code is additionally guarded by its own version check. Do NOT bump the |
| # OpenSSL/GnuTLS floors above; these are additive capability checks. |
| if (WITH_ML_DSA) |
| set(_mldsa_via "") |
| if (OPENSSL_FOUND AND OPENSSL_VERSION VERSION_GREATER_EQUAL 3.5) |
| set(_mldsa_via "${_mldsa_via} OpenSSL ${OPENSSL_VERSION}") |
| endif() |
| if (GNUTLS_FOUND AND GNUTLS_VERSION VERSION_GREATER_EQUAL 3.8.10) |
| set(_mldsa_via "${_mldsa_via} GnuTLS ${GNUTLS_VERSION}") |
| endif() |
| if (_mldsa_via) |
| set(LIBJWT_HAVE_ML_DSA ON) |
| message(STATUS "ML-DSA (experimental) enabled via:${_mldsa_via}") |
| else() |
| message(WARNING |
| "WITH_ML_DSA was requested but no capable crypto backend " |
| "was found (needs OpenSSL >= 3.5 or GnuTLS >= 3.8.10); " |
| "ML-DSA stays disabled") |
| endif() |
| endif() |
| |
| add_library(jwt SHARED) |
| add_library(jwt_static STATIC) |
| set_target_properties(jwt_static PROPERTIES |
| OUTPUT_NAME jwt |
| COMPILE_FLAGS -DJWT_STATIC_DEFINE) |
| |
| if (HAVE_KCAPI) |
| target_link_libraries(jwt PRIVATE kcapi) |
| target_link_libraries(jwt_static PRIVATE kcapi) |
| add_definitions(-DUSE_KCAPI_MD) |
| endif() |
| |
| add_custom_command( |
| OUTPUT jwt-builder.i |
| COMMAND bash -c "${CMAKE_C_COMPILER} -E -DJWT_BUILDER ${CMAKE_SOURCE_DIR}/libjwt/jwt-common.c -o jwt-builder.i ${CMAKE_C_FLAGS}" |
| -o jwt-builder.i |
| DEPENDS libjwt/jwt-common.c) |
| |
| add_custom_command( |
| OUTPUT jwt-checker.i |
| COMMAND bash -c "${CMAKE_C_COMPILER} -E -DJWT_CHECKER ${CMAKE_SOURCE_DIR}/libjwt/jwt-common.c -o jwt-checker.i ${CMAKE_C_FLAGS}" |
| -o jwt-checker.i |
| DEPENDS libjwt/jwt-common.c) |
| |
| add_custom_command( |
| OUTPUT jwe-builder.i |
| COMMAND bash -c "${CMAKE_C_COMPILER} -E -DJWE_BUILDER ${CMAKE_SOURCE_DIR}/libjwt/jwe-common.c -o jwe-builder.i ${CMAKE_C_FLAGS}" |
| -o jwe-builder.i |
| DEPENDS libjwt/jwe-common.c) |
| |
| add_custom_command( |
| OUTPUT jwe-checker.i |
| COMMAND bash -c "${CMAKE_C_COMPILER} -E -DJWE_CHECKER ${CMAKE_SOURCE_DIR}/libjwt/jwe-common.c -o jwe-checker.i ${CMAKE_C_FLAGS}" |
| -o jwe-checker.i |
| DEPENDS libjwt/jwe-common.c) |
| |
| add_custom_target(gen_jwt_builder ALL DEPENDS jwt-builder.i) |
| add_custom_target(gen_jwt_checker ALL DEPENDS jwt-checker.i) |
| add_custom_target(gen_jwe_builder ALL DEPENDS jwe-builder.i) |
| add_custom_target(gen_jwe_checker ALL DEPENDS jwe-checker.i) |
| |
| add_dependencies(jwt gen_jwt_builder gen_jwt_checker |
| gen_jwe_builder gen_jwe_checker) |
| add_dependencies(jwt_static gen_jwt_builder gen_jwt_checker |
| gen_jwe_builder gen_jwe_checker) |
| |
| set(JWT_SOURCES libjwt/jwt-memory.c |
| libjwt/jwt.c |
| libjwt/jwks.c |
| libjwt/jwt-setget.c |
| libjwt/jwt-crypto-ops.c |
| libjwt/jwt-encode.c |
| libjwt/jwt-verify.c |
| libjwt/jwt-builder.c |
| libjwt/jwt-checker.c |
| libjwt/jwe-setget.c |
| libjwt/jwe.c |
| libjwt/jwe-builder.c |
| libjwt/jwe-checker.c |
| libjwt/jwks-curl.c |
| libjwt/jwk-export.c) |
| |
| # Allow building without deprecated functions (suggested) |
| option(EXCLUDE_DEPRECATED |
| "Exclude deprecated parts of the library (default included)" FALSE) |
| if (EXCLUDE_DEPRECATED) |
| set(NO_BUILD_DEPRECATED DEFINE_NO_DEPRECATED) |
| endif() |
| |
| # Must come after the above so headers are available |
| include(GenerateExportHeader) |
| |
| generate_export_header(jwt ${NO_BUILD_DEPRECATED}) |
| |
| include_directories(${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR} |
| ${CMAKE_SOURCE_DIR}/libjwt) |
| |
| if (WITH_JSON_C) |
| add_definitions(-DHAVE_JSON_C) |
| target_link_libraries(jwt PUBLIC PkgConfig::JSONC) |
| target_link_libraries(jwt_static PUBLIC PkgConfig::JSONC) |
| list(APPEND JWT_SOURCES |
| libjwt/json-c/jwt-json-c.c) |
| else() |
| target_link_libraries(jwt PUBLIC PkgConfig::JANSSON) |
| target_link_libraries(jwt_static PUBLIC PkgConfig::JANSSON) |
| list(APPEND JWT_SOURCES |
| libjwt/jansson/jwt-jansson.c) |
| endif() |
| |
| # Process the detected packages |
| set(HAVE_CRYPTO FALSE) |
| if (GNUTLS_FOUND) |
| set(HAVE_CRYPTO TRUE) |
| add_definitions(-DHAVE_GNUTLS) |
| target_link_libraries(jwt PUBLIC PkgConfig::GNUTLS) |
| target_link_libraries(jwt_static PUBLIC PkgConfig::GNUTLS) |
| list(APPEND JWT_SOURCES |
| libjwt/gnutls/jwk-parse.c |
| libjwt/gnutls/jwk-export.c |
| libjwt/gnutls/sign-verify.c |
| libjwt/gnutls/jwe.c) |
| endif() |
| |
| if (MBEDTLS_FOUND) |
| set(HAVE_CRYPTO TRUE) |
| add_definitions(-DHAVE_MBEDTLS) |
| target_link_libraries(jwt PUBLIC PkgConfig::MBEDTLS) |
| target_link_libraries(jwt_static PUBLIC PkgConfig::MBEDTLS) |
| list(APPEND JWT_SOURCES |
| libjwt/mbedtls/jwk-parse.c |
| libjwt/mbedtls/jwk-export.c |
| libjwt/mbedtls/sign-verify.c |
| libjwt/mbedtls/jwe.c) |
| endif() |
| |
| if (OPENSSL_FOUND) |
| set(HAVE_CRYPTO TRUE) |
| add_definitions(-DHAVE_OPENSSL) |
| target_link_libraries(jwt PUBLIC PkgConfig::OPENSSL) |
| target_link_libraries(jwt_static PUBLIC PkgConfig::OPENSSL) |
| list(APPEND JWT_SOURCES |
| libjwt/openssl/jwk-parse.c |
| libjwt/openssl/jwk-export.c |
| libjwt/openssl/sign-verify.c |
| libjwt/openssl/jwe.c) |
| endif() |
| |
| if (LIBCURL_FOUND) |
| add_definitions(-DHAVE_LIBCURL) |
| target_link_libraries(jwt PUBLIC PkgConfig::LIBCURL) |
| target_link_libraries(jwt_static PUBLIC PkgConfig::LIBCURL) |
| endif() |
| |
| set(TOOLS) |
| |
| function(jwt_add_tool) |
| set(oneValueArgs NAME SRC DIR) |
| cmake_parse_arguments(Tool "" "${oneValueArgs}" "" ${ARGN}) |
| |
| list(APPEND TOOLS ${Tool_NAME}) |
| add_executable(${Tool_NAME} ${Tool_SRC}) |
| # Tools use only the public libjwt API; they need no crypto backend link. |
| target_link_libraries(${Tool_NAME} |
| PRIVATE jwt) |
| set_target_properties(${Tool_NAME} PROPERTIES |
| RUNTIME_OUTPUT_DIRECTORY |
| "${CMAKE_BINARY_DIR}/tools") |
| install(TARGETS ${Tool_NAME} |
| RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) |
| endfunction() |
| |
| jwt_add_tool(NAME jwt-verify |
| SRC tools/jwt-verify.c) |
| jwt_add_tool(NAME jwt-generate |
| SRC tools/jwt-generate.c) |
| jwt_add_tool(NAME jwk2key |
| SRC tools/jwk2key.c) |
| jwt_add_tool(NAME key2jwk |
| SRC tools/key2jwk.c) |
| jwt_add_tool(NAME jwe-encrypt |
| SRC tools/jwe-encrypt.c) |
| jwt_add_tool(NAME jwe-decrypt |
| SRC tools/jwe-decrypt.c) |
| install(DIRECTORY ${CMAKE_SOURCE_DIR}/tools/ |
| DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 |
| FILES_MATCHING PATTERN "*.1") |
| |
| # We need one of the things above to even work |
| if (NOT HAVE_CRYPTO) |
| message(FATAL_ERROR "No crypto support detected") |
| endif() |
| |
| target_sources(jwt PRIVATE ${JWT_SOURCES}) |
| target_sources(jwt_static PRIVATE ${JWT_SOURCES}) |
| |
| target_include_directories(jwt PUBLIC |
| $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include> |
| $<INSTALL_INTERFACE:include> |
| ) |
| |
| # Define versioning for the library (comes from configure.ac) |
| set_target_properties(jwt PROPERTIES |
| VERSION ${LIBJWT_VERSION_INFO} |
| SOVERSION ${LIBJWT_COMPATVERSION} |
| ) |
| |
| add_definitions(-D_GNU_SOURCE -DKEYDIR=\"${CMAKE_SOURCE_DIR}/tests/keys\") |
| |
| # Install header |
| install(FILES include/jwt.h |
| ${CMAKE_BINARY_DIR}/jwt_export.h |
| DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) |
| install(FILES LICENSE README.md |
| DESTINATION ${CMAKE_INSTALL_DOCDIR}) |
| |
| # Install library |
| install(TARGETS jwt |
| EXPORT ${LIBJWT_PROJECT}Targets |
| ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} |
| LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} |
| RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} |
| ) |
| |
| # Install Static library |
| install(TARGETS jwt_static |
| EXPORT ${LIBJWT_PROJECT}StaticTargets |
| ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} |
| ) |
| |
| # For cmake users |
| install(EXPORT ${LIBJWT_PROJECT}Targets |
| FILE ${LIBJWT_PROJECT}Config.cmake |
| NAMESPACE ${LIBJWT_PROJECT}:: |
| DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LIBJWT_PROJECT} |
| ) |
| |
| # For pkg-config users |
| unset(LIBJWT_LDFLAGS) |
| if (WITH_JSON_C) |
| set(_JSON_LDFLAGS ${JSONC_LDFLAGS}) |
| else() |
| set(_JSON_LDFLAGS ${JANSSON_LDFLAGS}) |
| endif() |
| foreach (FLAG ${_JSON_LDFLAGS} ${OPENSSL_LDFLAGS} ${GNUTLS_LDFLAGS} |
| ${MBEDTLS_LDFLAGS} ${LIBCURL_LDFLAGS}) |
| string(APPEND LIBJWT_LDFLAGS " " ${FLAG}) |
| endforeach() |
| |
| # Populate the pkg-config "Requires" line from the actually-enabled backends, |
| # so consumers only pull in the crypto libraries this build was linked against. |
| if (WITH_JSON_C) |
| set(JSON_MODULE_NAME "json-c") |
| else() |
| set(JSON_MODULE_NAME "jansson") |
| endif() |
| set(OPENSSL_MODULE_NAME "") |
| set(GNUTLS_MODULE_NAME "") |
| set(MBEDTLS_MODULE_NAME "") |
| if (OPENSSL_FOUND) |
| set(OPENSSL_MODULE_NAME "openssl") |
| endif() |
| if (GNUTLS_FOUND) |
| set(GNUTLS_MODULE_NAME "gnutls") |
| endif() |
| if (MBEDTLS_FOUND) |
| set(MBEDTLS_MODULE_NAME "mbedcrypto") |
| endif() |
| |
| configure_file(libjwt/libjwt.pc.in libjwt.pc @ONLY) |
| install(FILES ${CMAKE_BINARY_DIR}/libjwt.pc |
| DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) |
| |
| find_package(Doxygen 1.9.8) |
| |
| if (DOXYGEN_FOUND) |
| set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/doxygen-doc) |
| include(LibJWTDoxyfile) |
| doxygen_add_docs(doxygen-doc ALL include/jwt.h) |
| |
| install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doxygen-doc/man/man3/ |
| DESTINATION ${CMAKE_INSTALL_MANDIR}/man3 |
| FILES_MATCHING PATTERN "man3/JW*.3" |
| PATTERN "man3/jw*.3") |
| |
| install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doxygen-doc/html/ |
| DESTINATION ${CMAKE_INSTALL_DOCDIR}/html/) |
| endif() |
| |
| option(ENABLE_COVERAGE "Enable code coverage rules" OFF) |
| |
| # Tests and coverage depend on this, but optional |
| if (WITH_TESTS) |
| if (ENABLE_COVERAGE) |
| set(CHECK_REQUIRED REQUIRED) |
| endif() |
| pkg_check_modules(CHECK check>=0.9.10 IMPORTED_TARGET ${CHECK_REQUIRED}) |
| find_program (BATS_CMD bats) |
| else() |
| if (ENABLE_COVERAGE) |
| message(SEND_ERROR "You must set WITH_TESTS=ON to enable code coverage") |
| endif() |
| endif() |
| |
| function(jwt_add_test) |
| set(oneValueArgs NAME) |
| cmake_parse_arguments(LibTest "" "${oneValueArgs}" "" ${ARGN}) |
| |
| add_executable(${LibTest_NAME} tests/${LibTest_NAME}.c) |
| target_link_libraries(${LibTest_NAME} PRIVATE jwt) |
| set_target_properties(${LibTest_NAME} PROPERTIES |
| RUNTIME_OUTPUT_DIRECTORY |
| ${CMAKE_BINARY_DIR}/tests) |
| |
| target_link_libraries(${LibTest_NAME} PRIVATE PkgConfig::CHECK) |
| add_test(NAME ${LibTest_NAME} COMMAND /bin/bash -c |
| "export TEST=${LibTest_NAME}; . ${CMAKE_SOURCE_DIR}/tests/test-env.sh; exec ${CMAKE_BINARY_DIR}/tests/${LibTest_NAME}") |
| endfunction() |
| |
| if (CHECK_FOUND) |
| include(CTest) |
| |
| set (UNIT_TESTS jwt_crypto) |
| |
| # JWKS Tests |
| list (APPEND UNIT_TESTS jwt_jwks jwt_jwks_errors |
| jwt_ec jwt_rsa jwt_hs jwt_jwks_pem) |
| |
| # Checker and Builder |
| list (APPEND UNIT_TESTS jwt_builder jwt_checker jwt_flipflop) |
| |
| # ML-DSA (FIPS 204 / RFC 9964). The test is a no-op unless the build |
| # enabled WITH_ML_DSA against a capable backend. |
| list (APPEND UNIT_TESTS jwt_mldsa) |
| |
| # JWE |
| list (APPEND UNIT_TESTS jwe_foundation jwe_keys jwe_gcm jwe_cbc |
| jwe_aeskw jwe_rsa jwe_ecdh jwe_json jwe_aad jwe_multi |
| jwe_json_neg) |
| |
| # Claims |
| list (APPEND UNIT_TESTS jwt_claims) |
| |
| # Security |
| list (APPEND UNIT_TESTS jwt_security) |
| |
| foreach (TEST ${UNIT_TESTS}) |
| jwt_add_test(NAME ${TEST}) |
| endforeach() |
| |
| if (BATS_CMD) |
| add_test(NAME jwt_cli COMMAND /bin/bash -c |
| "export SRCDIR=\"${CMAKE_SOURCE_DIR}\"; \"${CMAKE_SOURCE_DIR}\"/tests/jwt-cli.bats") |
| endif() |
| |
| add_custom_target(check |
| COMMAND ${CMAKE_CTEST_COMMAND} |
| DEPENDS ${UNIT_TESTS} ${TOOLS}) |
| |
| if (ENABLE_COVERAGE) |
| set(CMAKE_BUILD_TYPE "Debug") |
| include(CodeCoverage) |
| append_coverage_compiler_flags() |
| |
| set(COVERAGE_LCOV_INCLUDES |
| "${CMAKE_SOURCE_DIR}/libjwt/" |
| "${CMAKE_SOURCE_DIR}/tools/") |
| setup_target_for_coverage_lcov( |
| NAME check-code-coverage |
| OUTPUT "${PROJECT_NAME}-${PROJECT_VERSION}-coverage" |
| TITLE "${PROJECT_NAME}-${PROJECT_VERSION} Code Coverage" |
| EXECUTABLE ctest -j ${PROCESSOR_COUNT} |
| DEPENDENCIES ${UNIT_TESTS} ${TOOLS}) |
| endif() |
| elseif(ENABLE_COVERAGE) |
| message(SEND_ERROR "Coverage enabled, but did not find check library") |
| endif() |
| |
| if (NOT ENABLE_COVERAGE) |
| add_custom_target(check-code-coverage |
| COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --cyan |
| "Coverage needs to be enabled for this target (ENABLE_COVERAGE=YES)" |
| VERBATIM) |
| endif() |
| |
| set(CPACK_PROPERTIES_FILE "${CMAKE_SOURCE_DIR}/cmake/CPackConfig.cmake") |
| include(CPack) |